Bugzilla 53979 - Support fetching properties of Numbered Lists from PPT files

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1401652 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2012-10-24 12:39:58 +00:00
parent d6e89353dc
commit b128eeacc8
17 changed files with 1031 additions and 33 deletions

View File

@ -34,6 +34,7 @@
<changes> <changes>
<release version="3.9-beta1" date="2012-??-??"> <release version="3.9-beta1" date="2012-??-??">
<action dev="poi-developers" type="add">53979 - Support fetching properties of Numbered Lists from PPT files</action>
<action dev="poi-developers" type="add">53784 - Partial HSMF support for fixed sized properties</action> <action dev="poi-developers" type="add">53784 - Partial HSMF support for fixed sized properties</action>
<action dev="poi-developers" type="add">53943 - added method processSymbol() to allow converting word symbols </action> <action dev="poi-developers" type="add">53943 - added method processSymbol() to allow converting word symbols </action>
<action dev="poi-developers" type="fix">53763 - avoid style mess when using HSSFOptimiser </action> <action dev="poi-developers" type="fix">53763 - avoid style mess when using HSSFOptimiser </action>

View File

@ -122,13 +122,13 @@ public abstract class Sheet {
*/ */
public static TextRun[] findTextRuns(PPDrawing ppdrawing) { public static TextRun[] findTextRuns(PPDrawing ppdrawing) {
final List<TextRun> runsV = new ArrayList<TextRun>(); final List<TextRun> runsV = new ArrayList<TextRun>();
EscherTextboxWrapper[] wrappers = ppdrawing.getTextboxWrappers(); final EscherTextboxWrapper[] wrappers = ppdrawing.getTextboxWrappers();
for (int i = 0; i < wrappers.length; i++) { for (int i = 0; i < wrappers.length; i++) {
int s1 = runsV.size(); int s1 = runsV.size();
// propagate parents to parent-aware records // propagate parents to parent-aware records
RecordContainer.handleParentAwareRecords(wrappers[i]); RecordContainer.handleParentAwareRecords(wrappers[i]);
findTextRuns(wrappers[i].getChildRecords(), runsV); findTextRuns(wrappers[i], runsV);
int s2 = runsV.size(); int s2 = runsV.size();
if (s2 != s1){ if (s2 != s1){
TextRun t = runsV.get(runsV.size()-1); TextRun t = runsV.get(runsV.size()-1);
@ -137,7 +137,6 @@ public abstract class Sheet {
} }
return runsV.toArray(new TextRun[runsV.size()]); return runsV.toArray(new TextRun[runsV.size()]);
} }
/** /**
* Scans through the supplied record array, looking for * Scans through the supplied record array, looking for
* a TextHeaderAtom followed by one of a TextBytesAtom or * a TextHeaderAtom followed by one of a TextBytesAtom or
@ -146,8 +145,30 @@ public abstract class Sheet {
* @param records the records to build from * @param records the records to build from
* @param found vector to add any found to * @param found vector to add any found to
*/ */
protected static void findTextRuns(Record[] records, List<TextRun> found) { protected static void findTextRuns(final Record[] records, final List<TextRun> found) {
// Look for a TextHeaderAtom findTextRuns(records, found, null);
}
/**
* Scans through the supplied record array, looking for
* a TextHeaderAtom followed by one of a TextBytesAtom or
* a TextCharsAtom. Builds up TextRuns from these
*
* @param wrapper an EscherTextboxWrapper
* @param found vector to add any found to
*/
protected static void findTextRuns(final EscherTextboxWrapper wrapper, final List<TextRun> found) {
findTextRuns(wrapper.getChildRecords(), found, wrapper.getStyleTextProp9Atom());
}
/**
* Scans through the supplied record array, looking for
* a TextHeaderAtom followed by one of a TextBytesAtom or
* a TextCharsAtom. Builds up TextRuns from these
*
* @param records the records to build from
* @param found vector to add any found to
* @param styleTextProp9Atom a StyleTextProp9Atom with numbered lists info
*/
protected static void findTextRuns(final Record[] records, final List<TextRun> found, final StyleTextProp9Atom styleTextProp9Atom) {
for (int i = 0, slwtIndex=0; i < (records.length - 1); i++) { for (int i = 0, slwtIndex=0; i < (records.length - 1); i++) {
if (records[i] instanceof TextHeaderAtom) { if (records[i] instanceof TextHeaderAtom) {
TextRun trun = null; TextRun trun = null;
@ -169,7 +190,7 @@ public abstract class Sheet {
TextBytesAtom tba = (TextBytesAtom) records[i + 1]; TextBytesAtom tba = (TextBytesAtom) records[i + 1];
trun = new TextRun(tha, tba, stpa); trun = new TextRun(tha, tba, stpa);
} else if (records[i + 1].getRecordType() == 4001l) { } else if (records[i + 1].getRecordType() == 4001l) {
// StyleTextPropAtom - Safe to ignore stpa = (StyleTextPropAtom) records[i + 1];
} else if (records[i + 1].getRecordType() == 4010l) { } else if (records[i + 1].getRecordType() == 4010l) {
// TextSpecInfoAtom - Safe to ignore // TextSpecInfoAtom - Safe to ignore
} else { } else {
@ -177,7 +198,7 @@ public abstract class Sheet {
} }
if (trun != null) { if (trun != null) {
ArrayList lst = new ArrayList(); List<Record> lst = new ArrayList<Record>();
for (int j = i; j < records.length; j++) { for (int j = i; j < records.length; j++) {
if(j > i && records[j] instanceof TextHeaderAtom) break; if(j > i && records[j] instanceof TextHeaderAtom) break;
lst.add(records[j]); lst.add(records[j]);
@ -186,7 +207,7 @@ public abstract class Sheet {
lst.toArray(recs); lst.toArray(recs);
trun._records = recs; trun._records = recs;
trun.setIndex(slwtIndex); trun.setIndex(slwtIndex);
trun.setStyleTextProp9Atom(styleTextProp9Atom);
found.add(trun); found.add(trun);
i++; i++;
} else { } else {

View File

@ -18,7 +18,8 @@
package org.apache.poi.hslf.model; package org.apache.poi.hslf.model;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.util.Vector; import java.util.LinkedList;
import java.util.List;
import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherDgRecord; import org.apache.poi.ddf.EscherDgRecord;
@ -26,11 +27,13 @@ import org.apache.poi.ddf.EscherDggRecord;
import org.apache.poi.ddf.EscherSpRecord; import org.apache.poi.ddf.EscherSpRecord;
import org.apache.poi.hslf.record.ColorSchemeAtom; import org.apache.poi.hslf.record.ColorSchemeAtom;
import org.apache.poi.hslf.record.Comment2000; import org.apache.poi.hslf.record.Comment2000;
import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.HeadersFootersContainer; import org.apache.poi.hslf.record.HeadersFootersContainer;
import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.RecordContainer; import org.apache.poi.hslf.record.RecordContainer;
import org.apache.poi.hslf.record.RecordTypes; import org.apache.poi.hslf.record.RecordTypes;
import org.apache.poi.hslf.record.SlideAtom; import org.apache.poi.hslf.record.SlideAtom;
import org.apache.poi.hslf.record.StyleTextProp9Atom;
import org.apache.poi.hslf.record.TextHeaderAtom; import org.apache.poi.hslf.record.TextHeaderAtom;
import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet; import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet;
@ -53,7 +56,7 @@ public final class Slide extends Sheet
/** /**
* Constructs a Slide from the Slide record, and the SlideAtomsSet * Constructs a Slide from the Slide record, and the SlideAtomsSet
* containing the text. * containing the text.
* Initialises TextRuns, to provide easier access to the text * Initializes TextRuns, to provide easier access to the text
* *
* @param slide the Slide record we're based on * @param slide the Slide record we're based on
* @param notes the Notes sheet attached to us * @param notes the Notes sheet attached to us
@ -72,7 +75,7 @@ public final class Slide extends Sheet
// For the text coming in from the SlideAtomsSet: // For the text coming in from the SlideAtomsSet:
// Build up TextRuns from pairs of TextHeaderAtom and // Build up TextRuns from pairs of TextHeaderAtom and
// one of TextBytesAtom or TextCharsAtom // one of TextBytesAtom or TextCharsAtom
Vector textRuns = new Vector(); final List<TextRun> textRuns = new LinkedList<TextRun>();
if(_atomSet != null) { if(_atomSet != null) {
findTextRuns(_atomSet.getSlideRecords(),textRuns); findTextRuns(_atomSet.getSlideRecords(),textRuns);
} else { } else {
@ -476,4 +479,13 @@ public final class Slide extends Sheet
_runs = tmp; _runs = tmp;
} }
} }
/** This will return an atom per TextBox, so if the page has two text boxes the method should return two atoms. */
public StyleTextProp9Atom[] getNumberedListInfo() {
return this.getPPDrawing().getNumberedListInfo();
}
public EscherTextboxWrapper[] getTextboxWrappers() {
return this.getPPDrawing().getTextboxWrappers();
}
} }

View File

@ -54,6 +54,8 @@ public final class TextRun
* (there can be misc InteractiveInfo, TxInteractiveInfo and other records) * (there can be misc InteractiveInfo, TxInteractiveInfo and other records)
*/ */
protected Record[] _records; protected Record[] _records;
private StyleTextPropAtom styleTextPropAtom;
private StyleTextProp9Atom styleTextProp9Atom;
/** /**
* Constructs a Text Run from a Unicode text block * Constructs a Text Run from a Unicode text block
@ -702,5 +704,18 @@ public final class TextRun
public Record[] getRecords(){ public Record[] getRecords(){
return _records; return _records;
} }
/** Numbered List info */
public void setStyleTextProp9Atom(final StyleTextProp9Atom styleTextProp9Atom) {
this.styleTextProp9Atom = styleTextProp9Atom;
}
/** Numbered List info */
public StyleTextProp9Atom getStyleTextProp9Atom() {
return this.styleTextProp9Atom;
}
/** Characters covered */
public StyleTextPropAtom getStyleTextPropAtom() {
return this._styleAtom;
}
} }

View File

@ -0,0 +1,117 @@
/* ====================================================================
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.
==================================================================== */
/**
* A structure that specifies additional paragraph-level formatting
* such as Bullet Auto Number Scheme.
*/
package org.apache.poi.hslf.model.textproperties;
import org.apache.poi.hslf.record.TextAutoNumberSchemeEnum;
import org.apache.poi.util.LittleEndian;
/**
* This structure store text autonumber scheme and start number.
* If a paragraph has an autonumber(fBulletHasAutoNumber = 0x0001) but start number and scheme are empty,
* this means the default values will be used: statNumber=1 and sheme=ANM_ArabicPeriod
* @see http://social.msdn.microsoft.com/Forums/mr-IN/os_binaryfile/thread/650888db-fabd-4b95-88dc-f0455f6e2d28
*
* @author Alex Nikiforov [mailto:anikif@gmail.com]
*
*/
public class TextPFException9 {
//private final byte mask1;
//private final byte mask2;
private final byte mask3;
private final byte mask4;
private final Short bulletBlipRef;
private final Short fBulletHasAutoNumber;
private final TextAutoNumberSchemeEnum autoNumberScheme;
private final static TextAutoNumberSchemeEnum DEFAULT_AUTONUMBER_SHEME = TextAutoNumberSchemeEnum.ANM_ArabicPeriod;
private final Short autoNumberStartNumber;
private final static Short DEFAULT_START_NUMBER = new Short((short)1);
private final int recordLength;
public TextPFException9(final byte[] source, final int startIndex) {
//this.mask1 = source[startIndex];
//this.mask2 = source[startIndex + 1];
this.mask3 = source[startIndex + 2];
this.mask4 = source[startIndex + 3];
int length = 4;
int index = startIndex + 4;
if (0 == (mask3 & (byte)0x80 )) {
this.bulletBlipRef = null;
} else {
this.bulletBlipRef = LittleEndian.getShort(source, index);
index +=2;
length = 6;
}
if (0 == (mask4 & 2)) {
this.fBulletHasAutoNumber = null;
} else {
this.fBulletHasAutoNumber = LittleEndian.getShort(source, index);
index +=2;
length +=2;
}
if (0 == (mask4 & 1)) {
this.autoNumberScheme = null;
this.autoNumberStartNumber = null;
} else {
this.autoNumberScheme = TextAutoNumberSchemeEnum.valueOf(LittleEndian.getShort(source, index));
index +=2;
this.autoNumberStartNumber = LittleEndian.getShort(source, index);
index +=2;
length +=4;
}
this.recordLength = length;
}
public Short getBulletBlipRef() {
return bulletBlipRef;
}
public Short getfBulletHasAutoNumber() {
return fBulletHasAutoNumber;
}
public TextAutoNumberSchemeEnum getAutoNumberScheme() {
if (null != this.autoNumberScheme) {
return this.autoNumberScheme;
}
if (null != this.fBulletHasAutoNumber && 1 == this.fBulletHasAutoNumber.shortValue()) {
return DEFAULT_AUTONUMBER_SHEME;
}
return null;
}
public Short getAutoNumberStartNumber() {
if (null != this.autoNumberStartNumber) {
return this.autoNumberStartNumber;
}
if (null != this.fBulletHasAutoNumber && 1 == this.fBulletHasAutoNumber.shortValue()) {
return DEFAULT_START_NUMBER;
}
return null;
}
public int getRecordLength() {
return recordLength;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Record length: ").append(this.recordLength).append(" bytes\n");
sb.append("bulletBlipRef: ").append(this.bulletBlipRef).append("\n");
sb.append("fBulletHasAutoNumber: ").append(this.fBulletHasAutoNumber).append("\n");
sb.append("autoNumberScheme: ").append(this.autoNumberScheme).append("\n");
sb.append("autoNumberStartNumber: ").append(this.autoNumberStartNumber).append("\n");
return sb.toString();
}
}

View File

@ -0,0 +1,67 @@
/* ====================================================================
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.hslf.record;
import org.apache.poi.util.LittleEndian;
import java.io.IOException;
import java.io.OutputStream;
/**
* If we come across a record we know has children of (potential)
* interest, but where the record itself is boring, but where other
* records may care about where this one lives, we create one
* of these. It allows us to get at the children, and track where on
* disk this is, but not much else.
* Anything done using this should quite quickly be transitioned to its
* own proper record class!
*
* @author Nick Burch
*/
public final class BinaryTagDataBlob extends PositionDependentRecordContainer
{
private byte[] _header;
private long _type;
/**
* Create a new holder for a boring record with children, but with
* position dependent characteristics
*/
protected BinaryTagDataBlob(byte[] source, int start, int len) {
// Just grab the header, not the whole contents
_header = new byte[8];
System.arraycopy(source,start,_header,0,8);
_type = LittleEndian.getUShort(_header,2);
// Find our children
_children = Record.findChildRecords(source,start+8,len-8);
}
/**
* Return the value we were given at creation
*/
public long getRecordType() { return _type; }
/**
* Write the contents of the record back, so it can be written
* to disk
*/
public void writeOut(OutputStream out) throws IOException {
writeOut(_header[0],_header[1],_type,_children,out);
}
}

View File

@ -36,6 +36,8 @@ public final class EscherTextboxWrapper extends RecordContainer {
private EscherTextboxRecord _escherRecord; private EscherTextboxRecord _escherRecord;
private long _type; private long _type;
private int shapeId; private int shapeId;
private StyleTextPropAtom styleTextPropAtom;
private StyleTextProp9Atom styleTextProp9Atom;
/** /**
* Returns the underlying DDF Escher Record * Returns the underlying DDF Escher Record
@ -52,6 +54,9 @@ public final class EscherTextboxWrapper extends RecordContainer {
// Find the child records in the escher data // Find the child records in the escher data
byte[] data = _escherRecord.getData(); byte[] data = _escherRecord.getData();
_children = Record.findChildRecords(data,0,data.length); _children = Record.findChildRecords(data,0,data.length);
for (Record r : this._children) {
if (r instanceof StyleTextPropAtom) { this.styleTextPropAtom = (StyleTextPropAtom) r; }
}
} }
/** /**
@ -104,4 +109,15 @@ public final class EscherTextboxWrapper extends RecordContainer {
public void setShapeId(int id){ public void setShapeId(int id){
shapeId = id; shapeId = id;
} }
public StyleTextPropAtom getStyleTextPropAtom() {
return styleTextPropAtom;
}
public void setStyleTextProp9Atom(final StyleTextProp9Atom nineAtom) {
this.styleTextProp9Atom = nineAtom;
}
public StyleTextProp9Atom getStyleTextProp9Atom() {
return this.styleTextProp9Atom;
}
} }

View File

@ -25,6 +25,7 @@ import org.apache.poi.hslf.model.ShapeTypes;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Vector; import java.util.Vector;
import java.util.Iterator; import java.util.Iterator;
@ -78,29 +79,69 @@ public final class PPDrawing extends RecordAtom {
_type = LittleEndian.getUShort(_header,2); _type = LittleEndian.getUShort(_header,2);
// Get the contents for now // Get the contents for now
byte[] contents = new byte[len]; final byte[] contents = new byte[len];
System.arraycopy(source,start,contents,0,len); System.arraycopy(source,start,contents,0,len);
// Build up a tree of Escher records contained within // Build up a tree of Escher records contained within
DefaultEscherRecordFactory erf = new DefaultEscherRecordFactory(); final DefaultEscherRecordFactory erf = new DefaultEscherRecordFactory();
Vector escherChildren = new Vector(); final Vector<EscherRecord> escherChildren = new Vector<EscherRecord>();
findEscherChildren(erf,contents,8,len-8,escherChildren); findEscherChildren(erf, contents, 8, len-8, escherChildren);
this.childRecords = (EscherRecord[]) escherChildren.toArray(new EscherRecord[escherChildren.size()]);
childRecords = new EscherRecord[escherChildren.size()];
for(int i=0; i<childRecords.length; i++) {
childRecords[i] = (EscherRecord)escherChildren.get(i);
}
if (1 == this.childRecords.length && (short)0xf002 == this.childRecords[0].getRecordId() && this.childRecords[0] instanceof EscherContainerRecord) {
this.textboxWrappers = findInDgContainer((EscherContainerRecord) this.childRecords[0]);
} else {
// Find and EscherTextboxRecord's, and wrap them up // Find and EscherTextboxRecord's, and wrap them up
Vector textboxes = new Vector(); final Vector<EscherTextboxWrapper> textboxes = new Vector<EscherTextboxWrapper>();
findEscherTextboxRecord(childRecords, textboxes); findEscherTextboxRecord(childRecords, textboxes);
textboxWrappers = new EscherTextboxWrapper[textboxes.size()]; this.textboxWrappers = (EscherTextboxWrapper[]) textboxes.toArray(new EscherTextboxWrapper[textboxes.size()]);
for(int i=0; i<textboxWrappers.length; i++) {
textboxWrappers[i] = (EscherTextboxWrapper)textboxes.get(i);
} }
} }
private EscherTextboxWrapper[] findInDgContainer(final EscherContainerRecord escherContainerF002) {
final List<EscherTextboxWrapper> found = new LinkedList<EscherTextboxWrapper>();
final EscherContainerRecord SpgrContainer = findFirstEscherContainerRecordOfType((short)0xf003, escherContainerF002);
final EscherContainerRecord[] escherContainersF004 = findAllEscherContainerRecordOfType((short)0xf004, SpgrContainer);
for (EscherContainerRecord spContainer : escherContainersF004) {
StyleTextProp9Atom nineAtom = findInSpContainer(spContainer);
EscherSpRecord sp = null;
final EscherRecord escherContainerF00A = findFirstEscherRecordOfType((short)0xf00a, spContainer);
if (null != escherContainerF00A) {
if (escherContainerF00A instanceof EscherSpRecord) {
sp = (EscherSpRecord) escherContainerF00A;
}
}
final EscherRecord escherContainerF00D = findFirstEscherRecordOfType((short)0xf00d, spContainer);
if (null == escherContainerF00D) { continue; }
if (escherContainerF00D instanceof EscherTextboxRecord) {
EscherTextboxRecord tbr = (EscherTextboxRecord) escherContainerF00D;
EscherTextboxWrapper w = new EscherTextboxWrapper(tbr);
w.setStyleTextProp9Atom(nineAtom);
if (null != sp) {
w.setShapeId(sp.getShapeId());
}
found.add(w);
}
}
return (EscherTextboxWrapper[]) found.toArray(new EscherTextboxWrapper[found.size()]);
}
private StyleTextProp9Atom findInSpContainer(final EscherContainerRecord spContainer) {
final EscherContainerRecord escherContainerF011 = findFirstEscherContainerRecordOfType((short)0xf011, spContainer);
if (null == escherContainerF011) { return null; }
final EscherContainerRecord escherContainer1388 = findFirstEscherContainerRecordOfType((short)0x1388, escherContainerF011);
if (null == escherContainer1388) { return null; }
final EscherContainerRecord escherContainer138A = findFirstEscherContainerRecordOfType((short)0x138A, escherContainer1388);
if (null == escherContainer138A) { return null; }
int size = escherContainer138A.getChildRecords().size();
if (2 != size) { return null; }
final Record r0 = buildFromUnknownEscherRecord((UnknownEscherRecord) escherContainer138A.getChild(0));
final Record r1 = buildFromUnknownEscherRecord((UnknownEscherRecord) escherContainer138A.getChild(1));
if (!(r0 instanceof CString)) { return null; }
if (!("___PPT9".equals(((CString) r0).getText()))) { return null; };
if (!(r1 instanceof BinaryTagDataBlob )) { return null; }
final BinaryTagDataBlob blob = (BinaryTagDataBlob) r1;
if (1 != blob.getChildRecords().length) { return null; }
return (StyleTextProp9Atom) blob.findFirstOfType(0x0FACL);
}
/** /**
* Creates a new, empty, PPDrawing (typically for use with a new Slide * Creates a new, empty, PPDrawing (typically for use with a new Slide
* or Notes) * or Notes)
@ -118,7 +159,7 @@ public final class PPDrawing extends RecordAtom {
/** /**
* Tree walking way of finding Escher Child Records * Tree walking way of finding Escher Child Records
*/ */
private void findEscherChildren(DefaultEscherRecordFactory erf, byte[] source, int startPos, int lenToGo, Vector found) { private void findEscherChildren(DefaultEscherRecordFactory erf, byte[] source, int startPos, int lenToGo, Vector<EscherRecord> found) {
int escherBytes = LittleEndian.getInt( source, startPos + 4 ) + 8; int escherBytes = LittleEndian.getInt( source, startPos + 4 ) + 8;
@ -138,7 +179,7 @@ public final class PPDrawing extends RecordAtom {
/** /**
* Sanity check. Always advance the cursor by the correct value. * Sanity check. Always advance the cursor by the correct value.
* *
* getRecordSize() must return exatcly the same number of bytes that was written in fillFields. * getRecordSize() must return exactly the same number of bytes that was written in fillFields.
* Sometimes it is not so, see an example in bug #44770. Most likely reason is that one of ddf records calculates wrong size. * Sometimes it is not so, see an example in bug #44770. Most likely reason is that one of ddf records calculates wrong size.
*/ */
if(size != escherBytes){ if(size != escherBytes){
@ -155,7 +196,7 @@ public final class PPDrawing extends RecordAtom {
/** /**
* Look for EscherTextboxRecords * Look for EscherTextboxRecords
*/ */
private void findEscherTextboxRecord(EscherRecord[] toSearch, Vector found) { private void findEscherTextboxRecord(EscherRecord[] toSearch, Vector<EscherTextboxWrapper> found) {
for(int i=0; i<toSearch.length; i++) { for(int i=0; i<toSearch.length; i++) {
if(toSearch[i] instanceof EscherTextboxRecord) { if(toSearch[i] instanceof EscherTextboxRecord) {
EscherTextboxRecord tbr = (EscherTextboxRecord)toSearch[i]; EscherTextboxRecord tbr = (EscherTextboxRecord)toSearch[i];
@ -313,4 +354,79 @@ public final class PPDrawing extends RecordAtom {
} }
return dg; return dg;
} }
protected EscherContainerRecord findFirstEscherContainerRecordOfType(short type, EscherContainerRecord parent) {
if (null == parent) { return null; }
final List<EscherContainerRecord> children = parent.getChildContainers();
for (EscherContainerRecord child : children) {
if (type == child.getRecordId()) {
return child;
}
}
return null;
}
protected EscherRecord findFirstEscherRecordOfType(short type, EscherContainerRecord parent) {
if (null == parent) { return null; }
final List<EscherRecord> children = parent.getChildRecords();
for (EscherRecord child : children) {
if (type == child.getRecordId()) {
return child;
}
}
return null;
}
protected EscherContainerRecord[] findAllEscherContainerRecordOfType(short type, EscherContainerRecord parent) {
if (null == parent) { return new EscherContainerRecord[0]; }
final List<EscherContainerRecord> children = parent.getChildContainers();
final List<EscherContainerRecord> result = new LinkedList<EscherContainerRecord>();
for (EscherContainerRecord child : children) {
if (type == child.getRecordId()) {
result.add(child);
}
}
return (EscherContainerRecord[]) result.toArray(new EscherContainerRecord[result.size()]);
}
protected Record buildFromUnknownEscherRecord(UnknownEscherRecord unknown) {
byte[] bingo = unknown.getData();
byte[] restoredRecord = new byte[8 + bingo.length];
System.arraycopy(bingo, 0, restoredRecord, 8, bingo.length);
short recordVersion = unknown.getVersion();
short recordId = unknown.getRecordId();
int recordLength = unknown.getRecordSize();
LittleEndian.putShort(restoredRecord, 0, recordVersion);
LittleEndian.putShort(restoredRecord, 2, recordId);
LittleEndian.putInt(restoredRecord, 4, recordLength);
return Record.createRecordForType(recordId, restoredRecord, 0, restoredRecord.length);
}
public StyleTextProp9Atom[] getNumberedListInfo() {
final List<StyleTextProp9Atom> result = new LinkedList<StyleTextProp9Atom>();
EscherRecord[] escherRecords = this.getEscherRecords();
for (EscherRecord escherRecord : escherRecords) {
if (escherRecord instanceof EscherContainerRecord && (short)0xf002 == escherRecord.getRecordId()) {
EscherContainerRecord escherContainerF002 = (EscherContainerRecord) escherRecord;
final EscherContainerRecord escherContainerF003 = findFirstEscherContainerRecordOfType((short)0xf003, escherContainerF002);
final EscherContainerRecord[] escherContainersF004 = findAllEscherContainerRecordOfType((short)0xf004, escherContainerF003);
for (EscherContainerRecord containerF004 : escherContainersF004) {
final EscherContainerRecord escherContainerF011 = findFirstEscherContainerRecordOfType((short)0xf011, containerF004);
if (null == escherContainerF011) { continue; }
final EscherContainerRecord escherContainer1388 = findFirstEscherContainerRecordOfType((short)0x1388, escherContainerF011);
if (null == escherContainer1388) { continue; }
final EscherContainerRecord escherContainer138A = findFirstEscherContainerRecordOfType((short)0x138A, escherContainer1388);
if (null == escherContainer138A) { continue; }
int size = escherContainer138A.getChildRecords().size();
if (2 != size) { continue; }
final Record r0 = buildFromUnknownEscherRecord((UnknownEscherRecord) escherContainer138A.getChild(0));
final Record r1 = buildFromUnknownEscherRecord((UnknownEscherRecord) escherContainer138A.getChild(1));
if (!(r0 instanceof CString)) { continue; }
if (!("___PPT9".equals(((CString) r0).getText()))) { continue; };
if (!(r1 instanceof BinaryTagDataBlob )) { continue; }
final BinaryTagDataBlob blob = (BinaryTagDataBlob) r1;
if (1 != blob.getChildRecords().length) { continue; }
result.add((StyleTextProp9Atom) blob.findFirstOfType(0x0FACL));
}
}
}
return (StyleTextProp9Atom[]) result.toArray(new StyleTextProp9Atom[result.size()]);
}
} }

View File

@ -84,7 +84,7 @@ public final class RecordTypes {
public static final Type OutlineTextRefAtom = new Type(3998,OutlineTextRefAtom.class); public static final Type OutlineTextRefAtom = new Type(3998,OutlineTextRefAtom.class);
public static final Type TextHeaderAtom = new Type(3999,TextHeaderAtom.class); public static final Type TextHeaderAtom = new Type(3999,TextHeaderAtom.class);
public static final Type TextCharsAtom = new Type(4000,TextCharsAtom.class); public static final Type TextCharsAtom = new Type(4000,TextCharsAtom.class);
public static final Type StyleTextPropAtom = new Type(4001,StyleTextPropAtom.class); public static final Type StyleTextPropAtom = new Type(4001, StyleTextPropAtom.class);//0x0fa1 RT_StyleTextPropAtom
public static final Type BaseTextPropAtom = new Type(4002,null); public static final Type BaseTextPropAtom = new Type(4002,null);
public static final Type TxMasterStyleAtom = new Type(4003,TxMasterStyleAtom.class); public static final Type TxMasterStyleAtom = new Type(4003,TxMasterStyleAtom.class);
public static final Type TxCFStyleAtom = new Type(4004,null); public static final Type TxCFStyleAtom = new Type(4004,null);
@ -95,6 +95,7 @@ public final class RecordTypes {
public static final Type TxSIStyleAtom = new Type(4009,null); public static final Type TxSIStyleAtom = new Type(4009,null);
public static final Type TextSpecInfoAtom = new Type(4010, TextSpecInfoAtom.class); public static final Type TextSpecInfoAtom = new Type(4010, TextSpecInfoAtom.class);
public static final Type DefaultRulerAtom = new Type(4011,null); public static final Type DefaultRulerAtom = new Type(4011,null);
public static final Type StyleTextProp9Atom = new Type(4012, StyleTextProp9Atom.class); //0x0FAC RT_StyleTextProp9Atom
public static final Type FontEntityAtom = new Type(4023,FontEntityAtom.class); public static final Type FontEntityAtom = new Type(4023,FontEntityAtom.class);
public static final Type FontEmbeddedData = new Type(4024,null); public static final Type FontEmbeddedData = new Type(4024,null);
public static final Type CString = new Type(4026,CString.class); public static final Type CString = new Type(4026,CString.class);
@ -146,7 +147,7 @@ public final class RecordTypes {
public static final Type ProgTags = new Type(5000,DummyPositionSensitiveRecordWithChildren.class); public static final Type ProgTags = new Type(5000,DummyPositionSensitiveRecordWithChildren.class);
public static final Type ProgStringTag = new Type(5001,null); public static final Type ProgStringTag = new Type(5001,null);
public static final Type ProgBinaryTag = new Type(5002,DummyPositionSensitiveRecordWithChildren.class); public static final Type ProgBinaryTag = new Type(5002,DummyPositionSensitiveRecordWithChildren.class);
public static final Type BinaryTagData = new Type(5003,DummyPositionSensitiveRecordWithChildren.class); public static final Type BinaryTagData = new Type(5003, BinaryTagDataBlob.class);//0x138b RT_BinaryTagDataBlob
public static final Type PrpublicintOptions = new Type(6000,null); public static final Type PrpublicintOptions = new Type(6000,null);
public static final Type PersistPtrFullBlock = new Type(6001,PersistPtrHolder.class); public static final Type PersistPtrFullBlock = new Type(6001,PersistPtrHolder.class);
public static final Type PersistPtrIncrementalBlock = new Type(6002,PersistPtrHolder.class); public static final Type PersistPtrIncrementalBlock = new Type(6002,PersistPtrHolder.class);

View File

@ -0,0 +1,136 @@
/* ====================================================================
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.hslf.record;
import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import org.apache.poi.hslf.model.textproperties.TextPFException9;
import org.apache.poi.util.LittleEndian;
/**
* The atom record that specifies additional text formatting.
*
* @author Alex Nikiforov [mailto:anikif@gmail.com]
*/
public final class StyleTextProp9Atom extends RecordAtom {
private final TextPFException9[] autoNumberSchemes;
/** Record header. */
private byte[] header;
/** Record data. */
private byte[] data;
private short version;
private short recordId;
private int length;
/**
* Constructs the link related atom record from its
* source data.
*
* @param source the source data as a byte array.
* @param start the start offset into the byte array.
* @param len the length of the slice in the byte array.
*/
protected StyleTextProp9Atom(byte[] source, int start, int len) {
// Get the header.
final List<TextPFException9> schemes = new LinkedList<TextPFException9>();
header = new byte[8];
System.arraycopy(source,start, header,0,8);
this.version = LittleEndian.getShort(header, 0);
this.recordId = LittleEndian.getShort(header, 2);
this.length = LittleEndian.getInt(header, 4);
// Get the record data.
data = new byte[len-8];
System.arraycopy(source, start+8, data, 0, len-8);
for (int i = 0; i < data.length; ) {
final TextPFException9 item = new TextPFException9(data, i);
schemes.add(item);
i += item.getRecordLength();
//int textCfException9 = LittleEndian.getInt(data, i );
//TODO analyze textCfException when have some test data
i += 4;
int textSiException = LittleEndian.getInt(data, i );
i += + 4;//TextCFException9 + SIException
if (0 != (textSiException & 0x40)) {
i += 2; //skip fBidi
}
if (i >= data.length) {
break;
}
}
this.autoNumberSchemes = (TextPFException9[]) schemes.toArray(new TextPFException9[schemes.size()]);
}
/**
* Gets the record type.
* @return the record type.
*/
public long getRecordType() { return this.recordId; }
public short getVersion() {
return version;
}
public int getLength() {
return length;
}
public TextPFException9[] getAutoNumberTypes() {
return this.autoNumberSchemes;
}
/**
* Write the contents of the record back, so it can be written
* to disk
*
* @param out the output stream to write to.
* @throws java.io.IOException if an error occurs.
*/
public void writeOut(OutputStream out) throws IOException {
out.write(header);
out.write(data);
}
/**
* Update the text length
*
* @param size the text length
*/
public void setTextSize(int size){
LittleEndian.putInt(data, 0, size);
}
/**
* Reset the content to one info run with the default values
* @param size the site of parent text
*/
public void reset(int size){
data = new byte[10];
// 01 00 00 00
LittleEndian.putInt(data, 0, size);
// 01 00 00 00
LittleEndian.putInt(data, 4, 1); //mask
// 00 00
LittleEndian.putShort(data, 8, (short)0); //langId
// Update the size (header bytes 5-8)
LittleEndian.putInt(header, 4, data.length);
}
}

View File

@ -0,0 +1,128 @@
/*
* ====================================================================
* 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.hslf.record;
public enum TextAutoNumberSchemeEnum {
//Name Value Meaning
ANM_AlphaLcPeriod ((short) 0x0000), // "Lowercase Latin character followed by a period. Example: a., b., c., ..."),
ANM_AlphaUcPeriod ((short) 0x0001), // "Uppercase Latin character followed by a period. Example: A., B., C., ..."),
ANM_ArabicParenRight ((short) 0x0002), // "Arabic numeral followed by a closing parenthesis. Example: 1), 2), 3), ..."),
ANM_ArabicPeriod ((short) 0x0003), // "Arabic numeral followed by a period. Example: 1., 2., 3., ..."),
ANM_RomanLcParenBoth ((short) 0x0004), // "Lowercase Roman numeral enclosed in parentheses. Example: (i), (ii), (iii), ..."),
ANM_RomanLcParenRight ((short) 0x0005), // "Lowercase Roman numeral followed by a closing parenthesis. Example: i), ii), iii), ..."),
ANM_RomanLcPeriod ((short) 0x0006), // "Lowercase Roman numeral followed by a period. Example: i., ii., iii., ..."),
ANM_RomanUcPeriod ((short) 0x0007), // "Uppercase Roman numeral followed by a period. Example: I., II., III., ..."),
ANM_AlphaLcParenBoth ((short) 0x0008), // "Lowercase alphabetic character enclosed in parentheses. Example: (a), (b), (c), ..."),
ANM_AlphaLcParenRight ((short) 0x0009), // "Lowercase alphabetic character followed by a closing parenthesis. Example: a), b), c), ..."),
ANM_AlphaUcParenBoth ((short) 0x000A), // "Uppercase alphabetic character enclosed in parentheses. Example: (A), (B), (C), ..."),
ANM_AlphaUcParenRight ((short) 0x000B), // "Uppercase alphabetic character followed by a closing parenthesis. Example: A), B), C), ..."),
ANM_ArabicParenBoth ((short) 0x000C), // "Arabic numeral enclosed in parentheses. Example: (1), (2), (3), ..."),
ANM_ArabicPlain ((short) 0x000D), // "Arabic numeral. Example: 1, 2, 3, ..."),
ANM_RomanUcParenBoth ((short) 0x000E), // "Uppercase Roman numeral enclosed in parentheses. Example: (I), (II), (III), ..."),
ANM_RomanUcParenRight ((short) 0x000F), // "Uppercase Roman numeral followed by a closing parenthesis. Example: I), II), III), ..."),
ANM_ChsPlain ((short) 0x0010), // "Simplified Chinese."),
ANM_ChsPeriod ((short) 0x0011), // "Simplified Chinese with single-byte period."),
ANM_CircleNumDBPlain ((short) 0x0012), // "Double byte circle numbers."),
ANM_CircleNumWDBWhitePlain ((short) 0x0013), // "Wingdings white circle numbers."),
ANM_CircleNumWDBBlackPlain ((short) 0x0014), // "Wingdings black circle numbers."),
ANM_ChtPlain ((short) 0x0015), // "Traditional Chinese."),
ANM_ChtPeriod ((short) 0x0016), // "Traditional Chinese with single-byte period."),
ANM_Arabic1Minus ((short) 0x0017), // "Bidi Arabic 1 (AraAlpha) with ANSI minus symbol."),
ANM_Arabic2Minus ((short) 0x0018), // "Bidi Arabic 2 (AraAbjad) with ANSI minus symbol."),
ANM_Hebrew2Minus ((short) 0x0019), // "Bidi Hebrew 2 with ANSI minus symbol."),
ANM_JpnKorPlain ((short) 0x001A), // "Japanese/Korean."),
ANM_JpnKorPeriod ((short) 0x001B), // "Japanese/Korean with single-byte period."),
ANM_ArabicDbPlain ((short) 0x001C), // "Double-byte Arabic numbers."),
ANM_ArabicDbPeriod ((short) 0x001D), // "Double-byte Arabic numbers with double-byte period."),
ANM_ThaiAlphaPeriod ((short) 0x001E), // "Thai alphabetic character followed by a period."),
ANM_ThaiAlphaParenRight ((short) 0x001F), // "Thai alphabetic character followed by a closing parenthesis."),
ANM_ThaiAlphaParenBoth ((short) 0x0020), // "Thai alphabetic character enclosed by parentheses."),
ANM_ThaiNumPeriod ((short) 0x0021), // "Thai numeral followed by a period."),
ANM_ThaiNumParenRight ((short) 0x0022), // "Thai numeral followed by a closing parenthesis."),
ANM_ThaiNumParenBoth ((short) 0x0023), // "Thai numeral enclosed in parentheses."),
ANM_HindiAlphaPeriod ((short) 0x0024), // "Hindi alphabetic character followed by a period."),
ANM_HindiNumPeriod ((short) 0x0025), // "Hindi numeric character followed by a period."),
ANM_JpnChsDBPeriod ((short) 0x0026), // "Japanese with double-byte period."),
ANM_HindiNumParenRight ((short) 0x0027), // "Hindi numeric character followed by a closing parenthesis."),
ANM_HindiAlpha1Period ((short) 0x0028); // "Hindi alphabetic character followed by a period.");
private final short value;
private TextAutoNumberSchemeEnum(final short code) {
this.value = code;
}
private short getValue() { return value; }
public String getDescription() {
return TextAutoNumberSchemeEnum.getDescription(this);
}
public static String getDescription(final TextAutoNumberSchemeEnum code) {
switch (code) {
case ANM_AlphaLcPeriod : return "Lowercase Latin character followed by a period. Example: a., b., c., ...";
case ANM_AlphaUcPeriod : return "Uppercase Latin character followed by a period. Example: A., B., C., ...";
case ANM_ArabicParenRight : return "Arabic numeral followed by a closing parenthesis. Example: 1), 2), 3), ...";
case ANM_ArabicPeriod : return "Arabic numeral followed by a period. Example: 1., 2., 3., ...";
case ANM_RomanLcParenBoth : return "Lowercase Roman numeral enclosed in parentheses. Example: (i), (ii), (iii), ...";
case ANM_RomanLcParenRight : return "Lowercase Roman numeral followed by a closing parenthesis. Example: i), ii), iii), ...";
case ANM_RomanLcPeriod : return "Lowercase Roman numeral followed by a period. Example: i., ii., iii., ...";
case ANM_RomanUcPeriod : return "Uppercase Roman numeral followed by a period. Example: I., II., III., ...";
case ANM_AlphaLcParenBoth : return "Lowercase alphabetic character enclosed in parentheses. Example: (a), (b), (c), ...";
case ANM_AlphaLcParenRight : return "Lowercase alphabetic character followed by a closing parenthesis. Example: a), b), c), ...";
case ANM_AlphaUcParenBoth : return "Uppercase alphabetic character enclosed in parentheses. Example: (A), (B), (C), ...";
case ANM_AlphaUcParenRight : return "Uppercase alphabetic character followed by a closing parenthesis. Example: A), B), C), ...";
case ANM_ArabicParenBoth : return "Arabic numeral enclosed in parentheses. Example: (1), (2), (3), ...";
case ANM_ArabicPlain : return "Arabic numeral. Example: 1, 2, 3, ...";
case ANM_RomanUcParenBoth : return "Uppercase Roman numeral enclosed in parentheses. Example: (I), (II), (III), ...";
case ANM_RomanUcParenRight : return "Uppercase Roman numeral followed by a closing parenthesis. Example: I), II), III), ...";
case ANM_ChsPlain : return "Simplified Chinese.";
case ANM_ChsPeriod : return "Simplified Chinese with single-byte period.";
case ANM_CircleNumDBPlain : return "Double byte circle numbers.";
case ANM_CircleNumWDBWhitePlain : return "Wingdings white circle numbers.";
case ANM_CircleNumWDBBlackPlain : return "Wingdings black circle numbers.";
case ANM_ChtPlain : return "Traditional Chinese.";
case ANM_ChtPeriod : return "Traditional Chinese with single-byte period.";
case ANM_Arabic1Minus : return "Bidi Arabic 1 (AraAlpha) with ANSI minus symbol.";
case ANM_Arabic2Minus : return "Bidi Arabic 2 (AraAbjad) with ANSI minus symbol.";
case ANM_Hebrew2Minus : return "Bidi Hebrew 2 with ANSI minus symbol.";
case ANM_JpnKorPlain : return "Japanese/Korean.";
case ANM_JpnKorPeriod : return "Japanese/Korean with single-byte period.";
case ANM_ArabicDbPlain : return "Double-byte Arabic numbers.";
case ANM_ArabicDbPeriod : return "Double-byte Arabic numbers with double-byte period.";
case ANM_ThaiAlphaPeriod : return "Thai alphabetic character followed by a period.";
case ANM_ThaiAlphaParenRight : return "Thai alphabetic character followed by a closing parenthesis.";
case ANM_ThaiAlphaParenBoth : return "Thai alphabetic character enclosed by parentheses.";
case ANM_ThaiNumPeriod : return "Thai numeral followed by a period.";
case ANM_ThaiNumParenRight : return "Thai numeral followed by a closing parenthesis.";
case ANM_ThaiNumParenBoth : return "Thai numeral enclosed in parentheses.";
case ANM_HindiAlphaPeriod : return "Hindi alphabetic character followed by a period.";
case ANM_HindiNumPeriod : return "Hindi numeric character followed by a period.";
case ANM_JpnChsDBPeriod : return "Japanese with double-byte period.";
case ANM_HindiNumParenRight : return "Hindi numeric character followed by a closing parenthesis.";
case ANM_HindiAlpha1Period : return "Hindi alphabetic character followed by a period.";
default : return "Unknown Numbered Scheme";
}
}
public static TextAutoNumberSchemeEnum valueOf(short autoNumberScheme) {
for (TextAutoNumberSchemeEnum item: TextAutoNumberSchemeEnum.values()) {
if (autoNumberScheme == item.getValue()) {
return item;
}
}
return null;
}
}

View File

@ -0,0 +1,130 @@
/*
* ====================================================================
* 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.hslf.usermodel;
import java.util.List;
import junit.framework.TestCase;
import org.apache.poi.hslf.model.Slide;
import org.apache.poi.hslf.model.TextRun;
import org.apache.poi.hslf.model.textproperties.TextPFException9;
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.StyleTextProp9Atom;
import org.apache.poi.hslf.record.StyleTextPropAtom;
import org.apache.poi.hslf.record.TextAutoNumberSchemeEnum;
import org.apache.poi.POIDataSamples;
/**
* Test that checks numbered list functionality.
*
* @author Alex Nikiforov [mailto:anikif@gmail.com]
*/
public final class TestNumberedList extends TestCase {
private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
protected void setUp() throws Exception {
}
public void testNumberedList() throws Exception {
SlideShow ppt = new SlideShow(_slTests.openResourceAsStream("numbers.ppt"));
assertTrue("No Exceptions while reading file", true);
final Slide[] slides = ppt.getSlides();
assertEquals(2, slides.length);
checkSlide0(slides[0]);
checkSlide1(slides[1]);
}
private void checkSlide0(final Slide s) {
final StyleTextProp9Atom[] numberedListArray = s.getNumberedListInfo();
assertNotNull(numberedListArray);
assertEquals(1, numberedListArray.length);//Just one text box here
final StyleTextProp9Atom numberedListInfo = numberedListArray[0];
assertNotNull(numberedListInfo);
final TextPFException9[] autoNumbers = numberedListInfo.getAutoNumberTypes();
assertNotNull(autoNumbers);
assertEquals(4, autoNumbers.length);
assertTrue(4 == autoNumbers[0].getAutoNumberStartNumber());
assertNull(autoNumbers[1].getAutoNumberStartNumber());
assertTrue(3 == autoNumbers[2].getAutoNumberStartNumber());
assertTrue(TextAutoNumberSchemeEnum.ANM_ArabicPeriod == autoNumbers[0].getAutoNumberScheme());
assertNull(autoNumbers[1].getAutoNumberScheme());
assertTrue(TextAutoNumberSchemeEnum.ANM_AlphaLcParenRight == autoNumbers[2].getAutoNumberScheme());
TextRun[] textRuns = s.getTextRuns();
assertEquals(2, textRuns.length);
RichTextRun textRun = textRuns[0].getRichTextRuns()[0];
assertEquals("titTe", textRun.getRawText());
assertEquals(1, textRuns[0].getRichTextRuns().length);
assertFalse(textRun.isBullet());
assertEquals("This is a text placeholder that \rfollows the design pattern\rJust a test\rWithout any paragraph\rSecond paragraph first line c) ;\rSecond paragraph second line d) . \r", textRuns[1].getRawText());
final EscherTextboxWrapper[] styleAtoms = s.getTextboxWrappers();
assertEquals(textRuns.length, styleAtoms.length);
final EscherTextboxWrapper wrapper = styleAtoms[1];
final StyleTextPropAtom styleTextPropAtom = wrapper.getStyleTextPropAtom();
final List<TextPropCollection> textProps = styleTextPropAtom.getCharacterStyles();
final TextPropCollection[] props = (TextPropCollection[]) textProps.toArray(new TextPropCollection[textProps.size()]);
assertEquals(60, props[0].getCharactersCovered());
assertEquals(34, props[1].getCharactersCovered());
assertEquals(68, props[2].getCharactersCovered());
}
private void checkSlide1(final Slide s) {
final StyleTextProp9Atom[] numberedListArray = s.getNumberedListInfo();
assertNotNull(numberedListArray);
assertEquals(1, numberedListArray.length);//Just one text box here
final StyleTextProp9Atom numberedListInfo = numberedListArray[0];
assertNotNull(numberedListInfo);
final TextPFException9[] autoNumbers = numberedListInfo.getAutoNumberTypes();
assertNotNull(autoNumbers);
assertEquals(4, autoNumbers.length);
assertTrue(9 == autoNumbers[0].getAutoNumberStartNumber());
assertNull(autoNumbers[1].getAutoNumberStartNumber());
assertTrue(3 == autoNumbers[2].getAutoNumberStartNumber());
assertTrue(TextAutoNumberSchemeEnum.ANM_ArabicParenRight == autoNumbers[0].getAutoNumberScheme());
assertNull(autoNumbers[1].getAutoNumberScheme());
assertTrue(TextAutoNumberSchemeEnum.ANM_AlphaUcPeriod == autoNumbers[2].getAutoNumberScheme());
final TextRun[] textRuns = s.getTextRuns();
assertEquals(2, textRuns.length);
RichTextRun textRun = textRuns[0].getRichTextRuns()[0];
assertEquals("Second Slide Title", textRun.getRawText());
assertEquals(1, textRuns[0].getRichTextRuns().length);
assertFalse(textRun.isBullet());
assertEquals("This is a text placeholder that \rfollows the design pattern\rJust a test\rWithout any paragraph\rSecond paragraph first line c) ;\rSecond paragraph second line d) . \r", textRuns[1].getRawText());
final EscherTextboxWrapper[] styleAtoms = s.getTextboxWrappers();
assertEquals(textRuns.length, styleAtoms.length);
final EscherTextboxWrapper wrapper = styleAtoms[1];
final StyleTextPropAtom styleTextPropAtom = wrapper.getStyleTextPropAtom();
final List<TextPropCollection> textProps = styleTextPropAtom.getCharacterStyles();
final TextPropCollection[] props = (TextPropCollection[]) textProps.toArray(new TextPropCollection[textProps.size()]);
assertEquals(33, props[0].getCharactersCovered());
assertEquals(61, props[1].getCharactersCovered());
assertEquals(68, props[2].getCharactersCovered());
}
}

View File

@ -0,0 +1,128 @@
/*
* ====================================================================
* 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.hslf.usermodel;
import java.util.List;
import junit.framework.TestCase;
import org.apache.poi.hslf.model.Slide;
import org.apache.poi.hslf.model.TextRun;
import org.apache.poi.hslf.model.textproperties.TextPFException9;
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.StyleTextProp9Atom;
import org.apache.poi.hslf.record.StyleTextPropAtom;
import org.apache.poi.hslf.record.TextAutoNumberSchemeEnum;
import org.apache.poi.POIDataSamples;
/**
* Test that checks numbered list functionality.
* if a paragraph has autonumber ()
* @see <a href="http://social.msdn.microsoft.com/Forums/mr-IN/os_binaryfile/thread/650888db-fabd-4b95-88dc-f0455f6e2d28">
* PPT: Missing TextAutoNumberScheme structure providing the style of the number bullets</a>
*
* @author Alex Nikiforov [mailto:anikif@gmail.com]
*/
public final class TestNumberedList2 extends TestCase {
private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
protected void setUp() throws Exception {
}
public void testNumberedList() throws Exception {
SlideShow ppt = new SlideShow(_slTests.openResourceAsStream("numbers2.ppt"));
assertTrue("No Exceptions while reading file", true);
final Slide[] slides = ppt.getSlides();
assertEquals(2, slides.length);
checkSlide0(slides[0]);
checkSlide1(slides[1]);
}
private void checkSlide0(final Slide s) {
final StyleTextProp9Atom[] numberedListArray = s.getNumberedListInfo();
assertNotNull(numberedListArray);
assertEquals(2, numberedListArray.length);
final StyleTextProp9Atom numberedListInfoForTextBox0 = numberedListArray[0];
final StyleTextProp9Atom numberedListInfoForTextBox1 = numberedListArray[1];
assertNotNull(numberedListInfoForTextBox0);
assertNotNull(numberedListInfoForTextBox1);
final TextPFException9[] autoNumbersOfTextBox0 = numberedListInfoForTextBox0.getAutoNumberTypes();
assertEquals(Short.valueOf((short)1), autoNumbersOfTextBox0[0].getfBulletHasAutoNumber());
assertEquals(Short.valueOf((short)1), autoNumbersOfTextBox0[0].getAutoNumberStartNumber());//Default value = 1 will be used
assertTrue(TextAutoNumberSchemeEnum.ANM_ArabicPeriod == autoNumbersOfTextBox0[0].getAutoNumberScheme());
final TextPFException9[] autoNumbersOfTextBox1 = numberedListInfoForTextBox1.getAutoNumberTypes();
assertEquals(Short.valueOf((short)1), autoNumbersOfTextBox1[0].getfBulletHasAutoNumber());
assertEquals(Short.valueOf((short)6), autoNumbersOfTextBox1[0].getAutoNumberStartNumber());//Default value = 1 will be used
assertTrue(TextAutoNumberSchemeEnum.ANM_ArabicPeriod == autoNumbersOfTextBox1[0].getAutoNumberScheme());
TextRun[] textRuns = s.getTextRuns();
assertEquals(2, textRuns.length);
RichTextRun textRun = textRuns[0].getRichTextRuns()[0];
assertEquals("List Item One\rList Item Two\rList Item Three", textRun.getRawText());
assertEquals(1, textRuns[0].getRichTextRuns().length);
assertTrue(textRun.isBullet());
assertEquals("A numbered list may start at any number \rThis would be used as a continuation list on another page\rThis list should start with #6", textRuns[1].getRawText());
final EscherTextboxWrapper[] styleAtoms = s.getTextboxWrappers();
assertEquals(textRuns.length, styleAtoms.length);
checkSingleRunWrapper(44, styleAtoms[0]);
checkSingleRunWrapper(130, styleAtoms[1]);
}
private void checkSlide1(final Slide s) {
final StyleTextProp9Atom[] numberedListArray = s.getNumberedListInfo();
assertNotNull(numberedListArray);
assertEquals(1, numberedListArray.length);
final StyleTextProp9Atom numberedListInfoForTextBox = numberedListArray[0];
assertNotNull(numberedListInfoForTextBox);
final TextPFException9[] autoNumbersOfTextBox = numberedListInfoForTextBox.getAutoNumberTypes();
assertEquals(Short.valueOf((short)1), autoNumbersOfTextBox[0].getfBulletHasAutoNumber());
assertEquals(Short.valueOf((short)1), autoNumbersOfTextBox[0].getAutoNumberStartNumber());//Default value = 1 will be used
assertTrue(TextAutoNumberSchemeEnum.ANM_ArabicPeriod == autoNumbersOfTextBox[0].getAutoNumberScheme());
TextRun[] textRuns = s.getTextRuns();
assertEquals(3, textRuns.length);
RichTextRun textRun = textRuns[0].getRichTextRuns()[0];
assertEquals("Bulleted list\rMore bullets", textRun.getRawText());
assertEquals(1, textRuns[0].getRichTextRuns().length);
assertTrue(textRun.isBullet());
assertEquals("Numbered list between two bulleted lists\rSecond numbered list item", textRuns[1].getRawText());
assertEquals("Second bulleted list \u2013 should appear after numbered list\rMore bullets", textRuns[2].getRawText());
final EscherTextboxWrapper[] styleAtoms = s.getTextboxWrappers();
assertEquals(textRuns.length, styleAtoms.length);
checkSingleRunWrapper(27, styleAtoms[0]);
checkSingleRunWrapper(67, styleAtoms[1]);
checkSingleRunWrapper(70, styleAtoms[2]);
}
private void checkSingleRunWrapper(final int exceptedLength, final EscherTextboxWrapper wrapper) {
final StyleTextPropAtom styleTextPropAtom = wrapper.getStyleTextPropAtom();
final List<TextPropCollection> textProps = styleTextPropAtom.getCharacterStyles();
assertEquals(1, textProps.size());
final TextPropCollection[] props = (TextPropCollection[]) textProps.toArray(new TextPropCollection[textProps.size()]);
assertEquals(exceptedLength, props[0].getCharactersCovered());
}
}

View File

@ -0,0 +1,110 @@
/*
* ====================================================================
* 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.hslf.usermodel;
import java.util.List;
import junit.framework.TestCase;
import org.apache.poi.hslf.model.Slide;
import org.apache.poi.hslf.model.TextRun;
import org.apache.poi.hslf.model.textproperties.TextPFException9;
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.StyleTextProp9Atom;
import org.apache.poi.hslf.record.StyleTextPropAtom;
import org.apache.poi.hslf.record.TextAutoNumberSchemeEnum;
import org.apache.poi.POIDataSamples;
/**
* Test that checks numbered list functionality.
* if a paragraph has autonumber ()
* @see <a href="http://social.msdn.microsoft.com/Forums/mr-IN/os_binaryfile/thread/650888db-fabd-4b95-88dc-f0455f6e2d28">
* PPT: Missing TextAutoNumberScheme structure providing the style of the number bullets</a>
*
* @author Alex Nikiforov [mailto:anikif@gmail.com]
*/
public final class TestNumberedList3 extends TestCase {
private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
protected void setUp() throws Exception {
}
public void testNumberedList() throws Exception {
SlideShow ppt = new SlideShow(_slTests.openResourceAsStream("numbers3.ppt"));
assertTrue("No Exceptions while reading file", true);
final Slide[] slides = ppt.getSlides();
assertEquals(1, slides.length);
final Slide slide = slides[0];
checkSlide(slide);
}
private void checkSlide(final Slide s) {
final StyleTextProp9Atom[] numberedListArray = s.getNumberedListInfo();
assertNotNull(numberedListArray);
assertEquals(1, numberedListArray.length);
final StyleTextProp9Atom numberedListInfoForTextBox = numberedListArray[0];
assertNotNull(numberedListInfoForTextBox);
final TextPFException9[] autoNumbersOfTextBox0 = numberedListInfoForTextBox.getAutoNumberTypes();
assertEquals(Short.valueOf((short)1), autoNumbersOfTextBox0[0].getfBulletHasAutoNumber());
assertEquals(Short.valueOf((short)1), autoNumbersOfTextBox0[0].getAutoNumberStartNumber());//Default value = 1 will be used
assertTrue(TextAutoNumberSchemeEnum.ANM_ArabicPeriod == autoNumbersOfTextBox0[0].getAutoNumberScheme());
final TextRun[] textRuns = s.getTextRuns();
assertEquals(3, textRuns.length);
assertEquals("Bulleted list\rMore bullets\rNo bullets here", textRuns[0].getRawText());
assertEquals("Numbered list between two bulleted lists\rSecond numbered list item", textRuns[1].getRawText());
assertEquals("Second bulleted list \u2013 should appear after numbered list\rMore bullets", textRuns[2].getRawText());
assertEquals(2, textRuns[0].getRichTextRuns().length);
assertEquals(1, textRuns[1].getRichTextRuns().length);
assertEquals(1, textRuns[2].getRichTextRuns().length);
assertNull(textRuns[0].getStyleTextProp9Atom());
assertNotNull(textRuns[1].getStyleTextProp9Atom());
assertNull(textRuns[2].getStyleTextProp9Atom());
final TextPFException9[] autoNumbers = textRuns[1].getStyleTextProp9Atom().getAutoNumberTypes();
assertEquals(1, autoNumbers.length);
assertEquals(Short.valueOf((short)1), autoNumbers[0].getfBulletHasAutoNumber());
assertEquals(Short.valueOf((short)1), autoNumbers[0].getAutoNumberStartNumber());//Default value = 1 will be used
assertTrue(TextAutoNumberSchemeEnum.ANM_ArabicPeriod == autoNumbersOfTextBox0[0].getAutoNumberScheme());
final List<TextPropCollection> textProps = textRuns[1].getStyleTextPropAtom().getCharacterStyles();
assertEquals(1, textProps.size());
final TextPropCollection textProp = textProps.get(0);
assertEquals(67, textProp.getCharactersCovered());
RichTextRun textRun = textRuns[0].getRichTextRuns()[0];
assertTrue(textRun.isBullet());
final EscherTextboxWrapper[] styleAtoms = s.getTextboxWrappers();
assertEquals(textRuns.length, styleAtoms.length);
checkSingleRunWrapper(43, styleAtoms[0]);
checkSingleRunWrapper(67, styleAtoms[1]);
}
private void checkSingleRunWrapper(final int exceptedLength, final EscherTextboxWrapper wrapper) {
final StyleTextPropAtom styleTextPropAtom = wrapper.getStyleTextPropAtom();
final List<TextPropCollection> textProps = styleTextPropAtom.getCharacterStyles();
assertEquals(1, textProps.size());
final TextPropCollection[] props = (TextPropCollection[]) textProps.toArray(new TextPropCollection[textProps.size()]);
assertEquals(exceptedLength, props[0].getCharactersCovered());
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.