diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 697f87356..0bfd8534e 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 53979 - Support fetching properties of Numbered Lists from PPT files 53784 - Partial HSMF support for fixed sized properties 53943 - added method processSymbol() to allow converting word symbols 53763 - avoid style mess when using HSSFOptimiser diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java index 7850d774b..217a60f85 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java @@ -122,13 +122,13 @@ public abstract class Sheet { */ public static TextRun[] findTextRuns(PPDrawing ppdrawing) { final List runsV = new ArrayList(); - EscherTextboxWrapper[] wrappers = ppdrawing.getTextboxWrappers(); + final EscherTextboxWrapper[] wrappers = ppdrawing.getTextboxWrappers(); for (int i = 0; i < wrappers.length; i++) { int s1 = runsV.size(); // propagate parents to parent-aware records RecordContainer.handleParentAwareRecords(wrappers[i]); - findTextRuns(wrappers[i].getChildRecords(), runsV); + findTextRuns(wrappers[i], runsV); int s2 = runsV.size(); if (s2 != s1){ TextRun t = runsV.get(runsV.size()-1); @@ -137,7 +137,6 @@ public abstract class Sheet { } return runsV.toArray(new TextRun[runsV.size()]); } - /** * Scans through the supplied record array, looking for * 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 found vector to add any found to */ - protected static void findTextRuns(Record[] records, List found) { - // Look for a TextHeaderAtom + protected static void findTextRuns(final Record[] records, final List found) { + 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 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 found, final StyleTextProp9Atom styleTextProp9Atom) { for (int i = 0, slwtIndex=0; i < (records.length - 1); i++) { if (records[i] instanceof TextHeaderAtom) { TextRun trun = null; @@ -169,7 +190,7 @@ public abstract class Sheet { TextBytesAtom tba = (TextBytesAtom) records[i + 1]; trun = new TextRun(tha, tba, stpa); } else if (records[i + 1].getRecordType() == 4001l) { - // StyleTextPropAtom - Safe to ignore + stpa = (StyleTextPropAtom) records[i + 1]; } else if (records[i + 1].getRecordType() == 4010l) { // TextSpecInfoAtom - Safe to ignore } else { @@ -177,7 +198,7 @@ public abstract class Sheet { } if (trun != null) { - ArrayList lst = new ArrayList(); + List lst = new ArrayList(); for (int j = i; j < records.length; j++) { if(j > i && records[j] instanceof TextHeaderAtom) break; lst.add(records[j]); @@ -186,7 +207,7 @@ public abstract class Sheet { lst.toArray(recs); trun._records = recs; trun.setIndex(slwtIndex); - + trun.setStyleTextProp9Atom(styleTextProp9Atom); found.add(trun); i++; } else { diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java index 0decdaa44..60ab2b120 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java @@ -18,7 +18,8 @@ package org.apache.poi.hslf.model; 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.EscherDgRecord; @@ -26,11 +27,13 @@ import org.apache.poi.ddf.EscherDggRecord; import org.apache.poi.ddf.EscherSpRecord; import org.apache.poi.hslf.record.ColorSchemeAtom; 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.Record; import org.apache.poi.hslf.record.RecordContainer; import org.apache.poi.hslf.record.RecordTypes; 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.SlideListWithText.SlideAtomsSet; @@ -53,7 +56,7 @@ public final class Slide extends Sheet /** * Constructs a Slide from the Slide record, and the SlideAtomsSet * 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 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: // Build up TextRuns from pairs of TextHeaderAtom and // one of TextBytesAtom or TextCharsAtom - Vector textRuns = new Vector(); + final List textRuns = new LinkedList(); if(_atomSet != null) { findTextRuns(_atomSet.getSlideRecords(),textRuns); } else { @@ -476,4 +479,13 @@ public final class Slide extends Sheet _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(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java index ee69d87a7..5c32b9d13 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java @@ -54,6 +54,8 @@ public final class TextRun * (there can be misc InteractiveInfo, TxInteractiveInfo and other records) */ protected Record[] _records; + private StyleTextPropAtom styleTextPropAtom; + private StyleTextProp9Atom styleTextProp9Atom; /** * Constructs a Text Run from a Unicode text block @@ -702,5 +704,18 @@ public final class TextRun public Record[] getRecords(){ 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; + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPFException9.java b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPFException9.java new file mode 100644 index 000000000..aa30cb611 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/textproperties/TextPFException9.java @@ -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(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/BinaryTagDataBlob.java b/src/scratchpad/src/org/apache/poi/hslf/record/BinaryTagDataBlob.java new file mode 100644 index 000000000..160e2bdbd --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/BinaryTagDataBlob.java @@ -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); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/EscherTextboxWrapper.java b/src/scratchpad/src/org/apache/poi/hslf/record/EscherTextboxWrapper.java index 37583354d..715eb8179 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/EscherTextboxWrapper.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/EscherTextboxWrapper.java @@ -36,6 +36,8 @@ public final class EscherTextboxWrapper extends RecordContainer { private EscherTextboxRecord _escherRecord; private long _type; private int shapeId; + private StyleTextPropAtom styleTextPropAtom; + private StyleTextProp9Atom styleTextProp9Atom; /** * Returns the underlying DDF Escher Record @@ -52,6 +54,9 @@ public final class EscherTextboxWrapper extends RecordContainer { // Find the child records in the escher data byte[] data = _escherRecord.getData(); _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){ shapeId = id; } + + public StyleTextPropAtom getStyleTextPropAtom() { + return styleTextPropAtom; + } + + public void setStyleTextProp9Atom(final StyleTextProp9Atom nineAtom) { + this.styleTextProp9Atom = nineAtom; + } + public StyleTextProp9Atom getStyleTextProp9Atom() { + return this.styleTextProp9Atom; + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java index b77be59c2..dca88b519 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java @@ -25,6 +25,7 @@ import org.apache.poi.hslf.model.ShapeTypes; import java.io.IOException; import java.io.OutputStream; +import java.util.LinkedList; import java.util.List; import java.util.Vector; import java.util.Iterator; @@ -78,29 +79,69 @@ public final class PPDrawing extends RecordAtom { _type = LittleEndian.getUShort(_header,2); // Get the contents for now - byte[] contents = new byte[len]; + final byte[] contents = new byte[len]; System.arraycopy(source,start,contents,0,len); - // Build up a tree of Escher records contained within - DefaultEscherRecordFactory erf = new DefaultEscherRecordFactory(); - Vector escherChildren = new Vector(); - findEscherChildren(erf,contents,8,len-8,escherChildren); + final DefaultEscherRecordFactory erf = new DefaultEscherRecordFactory(); + final Vector escherChildren = new Vector(); + 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 textboxes = new Vector(); + findEscherTextboxRecord(childRecords, textboxes); + this.textboxWrappers = (EscherTextboxWrapper[]) textboxes.toArray(new EscherTextboxWrapper[textboxes.size()]); } } - + private EscherTextboxWrapper[] findInDgContainer(final EscherContainerRecord escherContainerF002) { + final List found = new LinkedList(); + 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 * or Notes) @@ -118,7 +159,7 @@ public final class PPDrawing extends RecordAtom { /** * 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 found) { 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. * - * 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. */ if(size != escherBytes){ @@ -155,7 +196,7 @@ public final class PPDrawing extends RecordAtom { /** * Look for EscherTextboxRecords */ - private void findEscherTextboxRecord(EscherRecord[] toSearch, Vector found) { + private void findEscherTextboxRecord(EscherRecord[] toSearch, Vector found) { for(int i=0; i 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 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 children = parent.getChildContainers(); + final List result = new LinkedList(); + 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 result = new LinkedList(); + 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()]); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java index 0920b1438..455e261c2 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java @@ -84,7 +84,7 @@ public final class RecordTypes { public static final Type OutlineTextRefAtom = new Type(3998,OutlineTextRefAtom.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 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 TxMasterStyleAtom = new Type(4003,TxMasterStyleAtom.class); 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 TextSpecInfoAtom = new Type(4010, TextSpecInfoAtom.class); 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 FontEmbeddedData = new Type(4024,null); 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 ProgStringTag = new Type(5001,null); 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 PersistPtrFullBlock = new Type(6001,PersistPtrHolder.class); public static final Type PersistPtrIncrementalBlock = new Type(6002,PersistPtrHolder.class); diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextProp9Atom.java b/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextProp9Atom.java new file mode 100644 index 000000000..17c9107e4 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextProp9Atom.java @@ -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 schemes = new LinkedList(); + 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); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/TextAutoNumberSchemeEnum.java b/src/scratchpad/src/org/apache/poi/hslf/record/TextAutoNumberSchemeEnum.java new file mode 100644 index 000000000..ab706592e --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/TextAutoNumberSchemeEnum.java @@ -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; + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList.java new file mode 100644 index 000000000..e7fec8a6b --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList.java @@ -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 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 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()); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList2.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList2.java new file mode 100644 index 000000000..264a90bb8 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList2.java @@ -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 + * PPT: Missing TextAutoNumberScheme structure providing the style of the number bullets + * + * @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 textProps = styleTextPropAtom.getCharacterStyles(); + assertEquals(1, textProps.size()); + final TextPropCollection[] props = (TextPropCollection[]) textProps.toArray(new TextPropCollection[textProps.size()]); + assertEquals(exceptedLength, props[0].getCharactersCovered()); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList3.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList3.java new file mode 100644 index 000000000..b10bccd96 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestNumberedList3.java @@ -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 + * PPT: Missing TextAutoNumberScheme structure providing the style of the number bullets + * + * @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 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 textProps = styleTextPropAtom.getCharacterStyles(); + assertEquals(1, textProps.size()); + final TextPropCollection[] props = (TextPropCollection[]) textProps.toArray(new TextPropCollection[textProps.size()]); + assertEquals(exceptedLength, props[0].getCharactersCovered()); + } +} diff --git a/test-data/slideshow/numbers.ppt b/test-data/slideshow/numbers.ppt new file mode 100644 index 000000000..970c3d75b Binary files /dev/null and b/test-data/slideshow/numbers.ppt differ diff --git a/test-data/slideshow/numbers2.ppt b/test-data/slideshow/numbers2.ppt new file mode 100644 index 000000000..839cadd60 Binary files /dev/null and b/test-data/slideshow/numbers2.ppt differ diff --git a/test-data/slideshow/numbers3.ppt b/test-data/slideshow/numbers3.ppt new file mode 100644 index 000000000..d76df104e Binary files /dev/null and b/test-data/slideshow/numbers3.ppt differ