Features
@@ -323,31 +324,68 @@
How to create bulleted lists
+ FileOutputStream out = new FileOutputStream("bullets.ppt");
+ ppt.write(out);
+ out.close();
+
+
+
+ How to read hyperlinks from a slide show
+
diff --git a/src/scratchpad/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java
new file mode 100644
index 0000000000..f919e545ff
--- /dev/null
+++ b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java
@@ -0,0 +1,80 @@
+/* ====================================================================
+ 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.examples;
+
+import org.apache.poi.hslf.usermodel.SlideShow;
+import org.apache.poi.hslf.model.Slide;
+import org.apache.poi.hslf.model.TextRun;
+import org.apache.poi.hslf.model.Hyperlink;
+import org.apache.poi.hslf.model.Shape;
+
+import java.io.FileInputStream;
+
+/**
+ * Demonstrates how to read hyperlinks from a presentation
+ *
+ * @author Yegor Kozlov
+ */
+public class Hyperlinks {
+
+ public static void main(String[] args) throws Exception {
+ for (int i = 0; i < args.length; i++) {
+ FileInputStream is = new FileInputStream(args[i]);
+ SlideShow ppt = new SlideShow(is);
+ is.close();
+
+ Slide[] slide = ppt.getSlides();
+ for (int j = 0; j < slide.length; j++) {
+ System.out.println("slide " + slide[j].getSlideNumber());
+
+ //read hyperlinks from the slide's text runs
+ System.out.println("reading hyperlinks from the text runs");
+ TextRun[] txt = slide[j].getTextRuns();
+ for (int k = 0; k < txt.length; k++) {
+ String text = txt[k].getText();
+ Hyperlink[] links = txt[k].getHyperlinks();
+ if(links != null) for (int l = 0; l < links.length; l++) {
+ Hyperlink link = links[l];
+ String title = link.getTitle();
+ String address = link.getAddress();
+ System.out.println(" " + title);
+ System.out.println(" " + address);
+ String substring = text.substring(link.getStartIndex(), link.getEndIndex()-1);//in ppt end index is inclusive
+ System.out.println(" " + substring);
+ }
+ }
+
+ //in PowerPoint you can assign a hyperlink to a shape without text,
+ //for example to a Line object. The code below demonstrates how to
+ //read such hyperlinks
+ System.out.println(" reading hyperlinks from the slide's shapes");
+ Shape[] sh = slide[j].getShapes();
+ for (int k = 0; k < sh.length; k++) {
+ Hyperlink link = sh[k].getHyperlink();
+ if(link != null) {
+ String title = link.getTitle();
+ String address = link.getAddress();
+ System.out.println(" " + title);
+ System.out.println(" " + address);
+ }
+ }
+ }
+
+ }
+
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Hyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/model/Hyperlink.java
new file mode 100644
index 0000000000..21a4dc59c4
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/Hyperlink.java
@@ -0,0 +1,168 @@
+
+/* ====================================================================
+ 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.model;
+
+import org.apache.poi.hslf.record.*;
+import org.apache.poi.hslf.usermodel.SlideShow;
+import org.apache.poi.ddf.EscherContainerRecord;
+import org.apache.poi.ddf.EscherRecord;
+import org.apache.poi.ddf.EscherClientDataRecord;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Iterator;
+
+/**
+ * Represents a hyperlink in a PowerPoint document
+ *
+ * @author Yegor Kozlov
+ */
+public class Hyperlink {
+
+ private int type;
+ private String address;
+ private String title;
+ private int startIndex, endIndex;
+
+ /**
+ * Gets the type of the hyperlink action.
+ * Must be a ACTION_* constant defined in InteractiveInfoAtom
+ *
+ * @return the hyperlink URL
+ * @see InteractiveInfoAtom
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Gets the hyperlink URL
+ *
+ * @return the hyperlink URL
+ */
+ public String getAddress() {
+ return address;
+ }
+
+ /**
+ * Gets the hyperlink user-friendly title (if different from URL)
+ *
+ * @return the hyperlink user-friendly title
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Gets the beginning character position
+ *
+ * @return the beginning character position
+ */
+ public int getStartIndex() {
+ return startIndex;
+ }
+
+ /**
+ * Gets the ending character position
+ *
+ * @return the ending character position
+ */
+ public int getEndIndex() {
+ return endIndex;
+ }
+
+ /**
+ * Find hyperlinks in a text run
+ *
+ * @param run TextRun to lookup hyperlinks in
+ * @return found hyperlinks or null if not found
+ */
+ protected static Hyperlink[] find(TextRun run){
+ ArrayList lst = new ArrayList();
+ SlideShow ppt = run.getSheet().getSlideShow();
+ //document-level container which stores info about all links in a presentation
+ ExObjList exobj = ppt.getDocumentRecord().getExObjList();
+ if (exobj == null) {
+ return null;
+ }
+ Record[] records = run._records;
+ if(records != null) find(records, exobj, lst);
+
+ Hyperlink[] links = null;
+ if (lst.size() > 0){
+ links = new Hyperlink[lst.size()];
+ lst.toArray(links);
+ }
+ return links;
+ }
+
+ /**
+ * Find hyperlink assigned to the supplied shape
+ *
+ * @param shape Shape to lookup hyperlink in
+ * @return found hyperlink or null
+ */
+ protected static Hyperlink find(Shape shape){
+ ArrayList lst = new ArrayList();
+ SlideShow ppt = shape.getSheet().getSlideShow();
+ //document-level container which stores info about all links in a presentation
+ ExObjList exobj = ppt.getDocumentRecord().getExObjList();
+ if (exobj == null) {
+ return null;
+ }
+
+ EscherContainerRecord spContainer = shape.getSpContainer();
+ List spchild = spContainer.getChildRecords();
+ for (Iterator it = spchild.iterator(); it.hasNext(); ) {
+ EscherRecord obj = (EscherRecord)it.next();
+ if (obj.getRecordId() == EscherClientDataRecord.RECORD_ID){
+ byte[] data = ((EscherContainerRecord)obj).serialize();
+ Record[] records = Record.findChildRecords(data, 8, data.length-8);
+ if(records != null) find(records, exobj, lst);
+ }
+ }
+
+ return lst.size() == 1 ? (Hyperlink)lst.get(0) : null;
+ }
+
+ private static void find(Record[] records, ExObjList exobj, List out){
+ for (int i = 0; i < records.length; i++) {
+ //see if we have InteractiveInfo in the textrun's records
+ if( records[i] instanceof InteractiveInfo){
+ InteractiveInfo hldr = (InteractiveInfo)records[i];
+ InteractiveInfoAtom info = hldr.getInteractiveInfoAtom();
+ int id = info.getHyperlinkID();
+ ExHyperlink linkRecord = exobj.get(id);
+ if (linkRecord != null){
+ Hyperlink link = new Hyperlink();
+ link.title = linkRecord.getLinkTitle();
+ link.address = linkRecord.getLinkURL();
+ link.type = info.getAction();
+
+ if (++i < records.length && records[i] instanceof TxInteractiveInfoAtom){
+ TxInteractiveInfoAtom txinfo = (TxInteractiveInfoAtom)records[i];
+ link.startIndex = txinfo.getStartIndex();
+ link.endIndex = txinfo.getEndIndex();
+ }
+ out.add(link);
+ }
+ }
+ }
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
index 65827bf94d..ffe89e03df 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
@@ -45,7 +45,7 @@ public abstract class Shape {
// For logging
protected POILogger logger = POILogFactory.getLogger(this.getClass());
-
+
/**
* In Escher absolute distances are specified in
* English Metric Units (EMUs), occasionally referred to as A units;
@@ -309,4 +309,15 @@ public abstract class Shape {
return new Fill(this);
}
+
+ /**
+ * Returns the hyperlink assigned to this shape
+ *
+ * @return the hyperlink assigned to this shape
+ * or null if not found.
+ */
+ public Hyperlink getHyperlink(){
+ return Hyperlink.find(this);
+ }
+
}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java
index d474a22f6f..aeb34763ec 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java
@@ -211,4 +211,13 @@ public class ShapeGroup extends Shape{
return spRecord.getOptions() >> 4;
}
+ /**
+ * Returns null - shape groups can't have hyperlinks
+ *
+ * @return null.
+ */
+ public Hyperlink getHyperlink(){
+ return null;
+ }
+
}
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 c53f7f9edd..b1761b2d9a 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java
@@ -21,6 +21,7 @@ package org.apache.poi.hslf.model;
import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherDgRecord;
import org.apache.poi.ddf.EscherRecord;
+import org.apache.poi.ddf.EscherSpRecord;
import org.apache.poi.hslf.record.*;
import org.apache.poi.hslf.usermodel.SlideShow;
@@ -127,7 +128,13 @@ public abstract class Sheet {
Vector runsV = new Vector();
EscherTextboxWrapper[] wrappers = ppdrawing.getTextboxWrappers();
for (int i = 0; i < wrappers.length; i++) {
+ int s1 = runsV.size();
findTextRuns(wrappers[i].getChildRecords(), runsV);
+ int s2 = runsV.size();
+ if (s2 != s1){
+ TextRun t = (TextRun) runsV.get(runsV.size()-1);
+ t.setShapeId(wrappers[i].getShapeId());
+ }
}
TextRun[] runs = new TextRun[runsV.size()];
for (int i = 0; i < runs.length; i++) {
@@ -176,6 +183,15 @@ public abstract class Sheet {
}
if (trun != null) {
+ ArrayList lst = new ArrayList();
+ for (int j = i; j < records.length; j++) {
+ if(j > i && records[j] instanceof TextHeaderAtom) break;
+ lst.add(records[j]);
+ }
+ Record[] recs = new Record[lst.size()];
+ lst.toArray(recs);
+ trun._records = recs;
+
found.add(trun);
i++;
} else {
@@ -232,6 +248,11 @@ public abstract class Sheet {
EscherDgRecord dg = (EscherDgRecord) Shape.getEscherChild(dgContainer, EscherDgRecord.RECORD_ID);
dg.setNumShapes(dg.getNumShapes() + 1);
+ int shapeId = dg.getLastMSOSPID()+1;
+ dg.setLastMSOSPID(shapeId);
+
+ EscherSpRecord sp = shape.getSpContainer().getChildById(EscherSpRecord.RECORD_ID);
+ if(sp != null) sp.setShapeId(shapeId);
shape.setSheet(this);
shape.afterInsert(this);
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java
index 82d9a4de7f..2d5c866f77 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java
@@ -28,7 +28,6 @@ import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.io.IOException;
-import java.util.Vector;
/**
* Represents a TextFrame shape in PowerPoint.
@@ -170,7 +169,7 @@ public class TextBox extends SimpleShape {
* @return the text string for this textbox.
*/
public String getText(){
- return _txtrun.getText();
+ return _txtrun == null ? null : _txtrun.getText();
}
/**
@@ -452,56 +451,37 @@ public class TextBox extends SimpleShape {
}
private void initTextRun(){
- TextHeaderAtom tha = null;
- TextCharsAtom tca = null;
- TextBytesAtom tba = null;
- StyleTextPropAtom sta = null;
OutlineTextRefAtom ota = null;
// Find the interesting child records
Record[] child = _txtbox.getChildRecords();
for (int i = 0; i < child.length; i++) {
- if (child[i] instanceof TextHeaderAtom) tha = (TextHeaderAtom)child[i];
- else if (child[i] instanceof TextBytesAtom) tba = (TextBytesAtom)child[i];
- else if (child[i] instanceof StyleTextPropAtom) sta = (StyleTextPropAtom)child[i];
- else if (child[i] instanceof OutlineTextRefAtom) ota = (OutlineTextRefAtom)child[i];
- else if (child[i] instanceof TextCharsAtom) tca = (TextCharsAtom)child[i];
- }
-
- // Special handling for cases where there's an OutlineTextRefAtom
- if (ota != null) {
- // TextHeaderAtom, TextBytesAtom and StyleTextPropAtom are
- // stored outside of EscherContainerRecord
- int idx = ota.getTextIndex();
- Slide sl = (Slide)getSheet();
- Record[] rec = sl.getSlideAtomsSet().getSlideRecords();
- for (int i = 0, j = 0; i < rec.length; i++) {
- if(rec[i].getRecordType() == RecordTypes.TextHeaderAtom.typeID){
- if(j++ == idx) { //we found j-th TextHeaderAtom, read the text data
- for (int k = i; k < rec.length; k++) {
- if (rec[k] instanceof TextHeaderAtom) {
- if (tha != null) break;
- else tha = (TextHeaderAtom)rec[k];
- }
- else if (rec[k] instanceof TextBytesAtom) tba = (TextBytesAtom)rec[k];
- else if (rec[k] instanceof TextCharsAtom) tca = (TextCharsAtom)rec[k];
- else if (rec[k] instanceof StyleTextPropAtom) sta = (StyleTextPropAtom)rec[k];
- }
- }
- }
+ if (child[i] instanceof OutlineTextRefAtom) {
+ ota = (OutlineTextRefAtom)child[i];
+ break;
}
}
-
- // If we found the records we needed, create a TextRun
- if(tba != null) {
- // Bytes based Text Run
- _txtrun = new TextRun(tha,tba,sta);
- } else if (tca != null) {
- // Characters (unicode) based Text Run
- _txtrun = new TextRun(tha,tca,sta);
+
+ Sheet sheet = getSheet();
+ TextRun[] runs = sheet.getTextRuns();
+ if (ota != null) {
+ int idx = ota.getTextIndex();
+ if(idx < runs.length) _txtrun = runs[idx];
+ if(_txtrun == null) {
+ logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx);
+ }
} else {
- // Empty text box
- logger.log(POILogger.WARN, "no text records found for TextBox");
+ int shapeId = _escherContainer.getChildById(EscherSpRecord.RECORD_ID).getShapeId();
+ if(runs != null) for (int i = 0; i < runs.length; i++) {
+ if(runs[i].getShapeId() == shapeId){
+ _txtrun = runs[i];
+ break;
+ }
+ }
+ if(_txtrun == null) {
+ logger.log(POILogger.WARN, "text run not found for shapeId=" + shapeId);
+ }
}
+
}
}
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 8e6ae61627..7770311477 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java
@@ -49,6 +49,12 @@ public class TextRun
protected RichTextRun[] _rtRuns;
private SlideShow slideShow;
private Sheet sheet;
+ private int shapeId;
+ /**
+ * all text run records that follow TextHeaderAtom.
+ * (there can be misc InteractiveInfo, TxInteractiveInfo and other records)
+ */
+ protected Record[] _records;
/**
* Constructs a Text Run from a Unicode text block
@@ -517,4 +523,28 @@ public class TextRun
public Sheet getSheet(){
return this.sheet;
}
+
+ /**
+ * @return Shape ID
+ */
+ protected int getShapeId(){
+ return shapeId;
+ }
+
+ /**
+ * @param id Shape ID
+ */
+ protected void setShapeId(int id){
+ shapeId = id;
+ }
+
+ /**
+ * Returns the array of all hyperlinks in this text run
+ *
+ * @return the array of all hyperlinks in this text run
+ * or null if not found.
+ */
+ public Hyperlink[] getHyperlinks(){
+ return Hyperlink.find(this);
+ }
}
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 4dd0d76ab1..142eee88fc 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/EscherTextboxWrapper.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/EscherTextboxWrapper.java
@@ -39,6 +39,7 @@ public class EscherTextboxWrapper extends RecordContainer
{
private EscherTextboxRecord _escherRecord;
private long _type;
+ private int shapeId;
/**
* Returns the underlying DDF Escher Record
@@ -93,4 +94,18 @@ public class EscherTextboxWrapper extends RecordContainer
// Save in the escher layer
_escherRecord.setData(data);
}
+
+ /**
+ * @return Shape ID
+ */
+ public int getShapeId(){
+ return shapeId;
+ }
+
+ /**
+ * @param id Shape ID
+ */
+ public void setShapeId(int id){
+ shapeId = id;
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java
index 8ccc28fb9d..8ba58cdb61 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java
@@ -42,13 +42,22 @@ public class ExHyperlink extends RecordContainer {
/**
* Returns the URL of the link.
- * TODO: Figure out which of detailsA or detailsB is the
- * one that always holds it
+ *
+ * @return the URL of the link
*/
public String getLinkURL() {
- return linkDetailsA.getText();
+ return linkDetailsB == null ? null : linkDetailsB.getText();
}
-
+
+ /**
+ * Returns the hyperlink's user-readable name
+ *
+ * @return the hyperlink's user-readable name
+ */
+ public String getLinkTitle() {
+ return linkDetailsA == null ? null : linkDetailsA.getText();
+ }
+
/**
* Sets the URL of the link
* TODO: Figure out if we should always set both
@@ -66,13 +75,13 @@ public class ExHyperlink extends RecordContainer {
* Get the link details (field A)
*/
public String _getDetailsA() {
- return linkDetailsA.getText();
+ return linkDetailsA == null ? null : linkDetailsA.getText();
}
/**
* Get the link details (field B)
*/
public String _getDetailsB() {
- return linkDetailsB.getText();
+ return linkDetailsB == null ? null : linkDetailsB.getText();
}
/**
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/ExObjList.java b/src/scratchpad/src/org/apache/poi/hslf/record/ExObjList.java
index 95fe5c967c..a511ef6095 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/ExObjList.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/ExObjList.java
@@ -109,4 +109,21 @@ public class ExObjList extends RecordContainer {
writeOut(_header[0],_header[1],_type,_children,out);
}
+ /**
+ * Lookup a hyperlink by its unique id
+ *
+ * @param id hyperlink id
+ * @return found ExHyperlink or null
+ */
+ public ExHyperlink get(int id){
+ for(int i=0; i<_children.length; i++) {
+ if(_children[i] instanceof ExHyperlink) {
+ ExHyperlink rec = (ExHyperlink)_children[i];
+ if (rec.getExHyperlinkAtom().getNumber() == id){
+ return rec;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfoAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfoAtom.java
index 40200d1301..8943f882d8 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfoAtom.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfoAtom.java
@@ -30,10 +30,37 @@ import org.apache.poi.util.LittleEndian;
* (The actual link is held Document.ExObjList.ExHyperlink)
*
* @author Nick Burch
+ * @author Yegor Kozlov
*/
public class InteractiveInfoAtom extends RecordAtom
{
+
+ /**
+ * Action Table
+ */
+ public static final int ACTION_NONE = 0;
+ public static final int ACTION_MACRO = 1;
+ public static final int ACTION_RUNPROGRAM = 2;
+ public static final int ACTION_JUMP = 3;
+ public static final int ACTION_HYPERLINK = 4;
+ public static final int ACTION_OLE = 5;
+ public static final int ACTION_MEDIA = 6;
+ public static final int ACTION_CUSTOMSHOW = 7;
+
+ /**
+ * Jump Table
+ */
+ public static final int JUMP_NONE = 0;
+ public static final int JUMP_NEXTSLIDE = 1;
+ public static final int JUMP_PREVIOUSSLIDE = 2;
+ public static final int JUMP_FIRSTSLIDE = 3;
+ public static final int JUMP_LASTSLIDE = 4;
+ public static final int JUMP_LASTSLIDEVIEWED = 5;
+ public static final int JUMP_ENDSHOW = 6;
+
+
+
/**
* Record header.
*/
@@ -90,46 +117,138 @@ public class InteractiveInfoAtom extends RecordAtom
* ExHyperlink with this number to get the details.
* @return the link number
*/
- public int getNumber() {
+ public int getHyperlinkID() {
return LittleEndian.getInt(_data,4);
}
/**
- * Sets the link number
- * @param number the link number.
+ * Sets the persistent unique identifier of the link
+ *
+ * @param number the persistent unique identifier of the link
*/
- public void setNumber(int number) {
+ public void setHyperlinkID(int number) {
LittleEndian.putInt(_data,4,number);
}
/**
- * Get the first number - meaning unknown
+ * a reference to a sound in the sound collection.
*/
- public int _getNumber1() {
+ public int getSoundRef() {
return LittleEndian.getInt(_data,0);
}
- protected void _setNumber1(int val) {
+ /**
+ * a reference to a sound in the sound collection.
+ *
+ * @param val a reference to a sound in the sound collection
+ */
+ public void setSoundRef(int val) {
LittleEndian.putInt(_data, 0, val);
}
/**
- * Get the third number - meaning unknown
+ * Hyperlink Action.
+ *
+ * see ACTION_* constants for the list of actions
+ *
+ *
+ * @return hyperlink action.
*/
- public int _getNumber3() {
- return LittleEndian.getInt(_data,8);
- }
- protected void _setNumber3(int val) {
- LittleEndian.putInt(_data, 8, val);
+ public byte getAction() {
+ return _data[8];
}
/**
- * Get the fourth number - meaning unknown
+ * Hyperlink Action
+ *
+ * see ACTION_* constants for the list of actions
+ *
+ *
+ * @param val hyperlink action.
*/
- public int _getNumber4() {
- return LittleEndian.getInt(_data,12);
+ public void setAction(byte val) {
+ _data[8] = val;
}
- protected void _setNumber4(int val) {
- LittleEndian.putInt(_data, 12, val);
+
+ /**
+ * Only valid when action == OLEAction. OLE verb to use, 0 = first verb, 1 = second verb, etc.
+ */
+ public byte getOleVerb() {
+ return _data[9];
+ }
+
+ /**
+ * Only valid when action == OLEAction. OLE verb to use, 0 = first verb, 1 = second verb, etc.
+ */
+ public void setOleVerb(byte val) {
+ _data[9] = val;
+ }
+
+ /**
+ * Jump
+ *
+ * see JUMP_* constants for the list of actions
+ *
Bit 1: Animated. If 1, then button is animated
+ *
Bit 2: Stop sound. If 1, then stop current sound when button is pressed.
+ *
Bit 3: CustomShowReturn. If 1, and this is a jump to custom show,
+ * then return to this slide after custom show.
+ *
+ */
+ public byte getFlags() {
+ return _data[11];
+ }
+
+ /**
+ * Flags
+ *
+ *
Bit 1: Animated. If 1, then button is animated
+ *
Bit 2: Stop sound. If 1, then stop current sound when button is pressed.
+ *
Bit 3: CustomShowReturn. If 1, and this is a jump to custom show,
+ * then return to this slide after custom show.
+ *
+ */
+ public void setFlags(byte val) {
+ _data[11] = val;
+ }
+
+ /**
+ * hyperlink type
+ *
+ * @return hyperlink type
+ */
+ public byte getHyperlinkType() {
+ return _data[12];
+ }
+
+ /**
+ * hyperlink type
+ *
+ * @param val hyperlink type
+ */
+ public void setHyperlinkType(byte val) {
+ _data[12] = val;
}
/**
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 e34e9d841f..db0d434aee 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java
@@ -147,6 +147,13 @@ public class PPDrawing extends RecordAtom
EscherTextboxRecord tbr = (EscherTextboxRecord)toSearch[i];
EscherTextboxWrapper w = new EscherTextboxWrapper(tbr);
found.add(w);
+ for (int j = i; j >= 0; j--) {
+ if(toSearch[j] instanceof EscherSpRecord){
+ EscherSpRecord sp = (EscherSpRecord)toSearch[j];
+ w.setShapeId(sp.getShapeId());
+ break;
+ }
+ }
} else {
// If it has children, walk them
if(toSearch[i].isContainerRecord()) {
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 3183bd553a..3c8d3aa41c 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java
@@ -112,7 +112,7 @@ public class RecordTypes {
public static final Type SlideNumberMCAtom = new Type(4056,null);
public static final Type HeadersFooters = new Type(4057,null);
public static final Type HeadersFootersAtom = new Type(4058,null);
- public static final Type TxInteractiveInfoAtom = new Type(4063,null);
+ public static final Type TxInteractiveInfoAtom = new Type(4063,TxInteractiveInfoAtom.class);
public static final Type CharFormatAtom = new Type(4066,null);
public static final Type ParaFormatAtom = new Type(4067,null);
public static final Type RecolorInfoAtom = new Type(4071,null);
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/TxInteractiveInfoAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/TxInteractiveInfoAtom.java
new file mode 100644
index 0000000000..b3db6bafaa
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/TxInteractiveInfoAtom.java
@@ -0,0 +1,122 @@
+/* ====================================================================
+ 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.OutputStream;
+import java.io.IOException;
+
+/**
+ * Tne atom that holds starting and ending character positions of a hyperlink
+ *
+ * @author Yegor Kozlov
+ */
+public class TxInteractiveInfoAtom extends RecordAtom {
+ /**
+ * Record header.
+ */
+ private byte[] _header;
+
+ /**
+ * Record data.
+ */
+ private byte[] _data;
+
+ /**
+ * Constructs a brand new link related atom record.
+ */
+ protected TxInteractiveInfoAtom() {
+ _header = new byte[8];
+ _data = new byte[8];
+
+ LittleEndian.putShort(_header, 2, (short)getRecordType());
+ LittleEndian.putInt(_header, 4, _data.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 TxInteractiveInfoAtom(byte[] source, int start, int len) {
+ // Get the header.
+ _header = new byte[8];
+ System.arraycopy(source,start,_header,0,8);
+
+ // Get the record data.
+ _data = new byte[len-8];
+ System.arraycopy(source,start+8,_data,0,len-8);
+
+ }
+
+ /**
+ * Gets the beginning character position
+ *
+ * @return the beginning character position
+ */
+ public int getStartIndex() {
+ return LittleEndian.getInt(_data, 0);
+ }
+
+ /**
+ * Sets the beginning character position
+ * @param idx the beginning character position
+ */
+ public void setStartIndex(int idx) {
+ LittleEndian.putInt(_data, 0, idx);
+ }
+
+ /**
+ * Gets the ending character position
+ *
+ * @return the ending character position
+ */
+ public int getEndIndex() {
+ return LittleEndian.getInt(_data, 4);
+ }
+
+ /**
+ * Sets the ending character position
+ *
+ * @param idx the ending character position
+ */
+ public void setEndIndex(int idx) {
+ LittleEndian.putInt(_data, 4, idx);
+ }
+
+ /**
+ * Gets the record type.
+ * @return the record type.
+ */
+ public long getRecordType() { return RecordTypes.TxInteractiveInfoAtom.typeID; }
+
+ /**
+ * 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);
+ }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java
new file mode 100644
index 0000000000..9108d3c36c
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java
@@ -0,0 +1,89 @@
+/* ====================================================================
+ 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.model;
+
+import junit.framework.TestCase;
+
+import java.io.FileInputStream;
+import java.io.File;
+
+import org.apache.poi.hslf.usermodel.SlideShow;
+
+/**
+ * Test Hyperlink.
+ *
+ * @author Yegor Kozlov
+ */
+public class TestHyperlink extends TestCase {
+ protected String cwd = System.getProperty("HSLF.testdata.path");
+
+ public void testTextRunHyperlinks() throws Exception {
+ FileInputStream is = new FileInputStream(new File(cwd, "WithLinks.ppt"));
+ SlideShow ppt = new SlideShow(is);
+ is.close();
+
+ TextRun[] run;
+ Slide slide;
+ slide = ppt.getSlides()[0];
+ run = slide.getTextRuns();
+ for (int i = 0; i < run.length; i++) {
+ String text = run[i].getText();
+ if (text.equals(
+ "This page has two links:\n" +
+ "http://jakarta.apache.org/poi/\n" +
+ "\n" +
+ "http://slashdot.org/\n" +
+ "\n" +
+ "In addition, its notes has one link")){
+
+ Hyperlink[] links = run[i].getHyperlinks();
+ assertNotNull(links);
+ assertEquals(2, links.length);
+
+ assertEquals("http://jakarta.apache.org/poi/", links[0].getTitle());
+ assertEquals("http://jakarta.apache.org/poi/", links[0].getAddress());
+ assertEquals("http://jakarta.apache.org/poi/", text.substring(links[0].getStartIndex(), links[0].getEndIndex()-1));
+
+ assertEquals("http://slashdot.org/", links[1].getTitle());
+ assertEquals("http://slashdot.org/", links[1].getAddress());
+ assertEquals("http://slashdot.org/", text.substring(links[1].getStartIndex(), links[1].getEndIndex()-1));
+
+ }
+ }
+
+ slide = ppt.getSlides()[1];
+ run = slide.getTextRuns();
+ for (int i = 0; i < run.length; i++) {
+ String text = run[i].getText();
+ if (text.equals(
+ "I have the one link:\n" +
+ "Jakarta HSSF")){
+
+ Hyperlink[] links = run[i].getHyperlinks();
+ assertNotNull(links);
+ assertEquals(1, links.length);
+
+ assertEquals("http://jakarta.apache.org/poi/hssf/", links[0].getTitle());
+ assertEquals("http://jakarta.apache.org/poi/hssf/", links[0].getAddress());
+ assertEquals("Jakarta HSSF", text.substring(links[0].getStartIndex(), links[0].getEndIndex()-1));
+
+ }
+ }
+
+ }
+
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java
index 4bd1678983..fd637774c0 100644
--- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java
@@ -161,7 +161,8 @@ public class TestShapes extends TestCase {
out.close();
ppt = new SlideShow(new HSLFSlideShow(new ByteArrayInputStream(out.toByteArray())));
-
+ sl = ppt.getSlides()[0];
+
txtbox = (TextBox)sl.getShapes()[0];
rt = txtbox.getTextRun().getRichTextRuns()[0];
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfo.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfo.java
index d3d61fbb9d..ad48bd1dcd 100644
--- a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfo.java
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfo.java
@@ -48,7 +48,7 @@ public class TestInteractiveInfo extends TestCase {
InteractiveInfo ii = new InteractiveInfo(data_a, 0, data_a.length);
InteractiveInfoAtom ia = ii.getInteractiveInfoAtom();
- assertEquals(1, ia.getNumber());
+ assertEquals(1, ia.getHyperlinkID());
}
public void testWrite() throws Exception {
@@ -69,10 +69,10 @@ public class TestInteractiveInfo extends TestCase {
InteractiveInfoAtom ia = ii.getInteractiveInfoAtom();
// Set values
- ia.setNumber(1);
- ia._setNumber1(0);
- ia._setNumber3(4);
- ia._setNumber4(8);
+ ia.setHyperlinkID(1);
+ ia.setSoundRef(0);
+ ia.setAction((byte)4);
+ ia.setHyperlinkType((byte)8);
// Check it's now the same as a
ByteArrayOutputStream baos = new ByteArrayOutputStream();
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfoAtom.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfoAtom.java
index d001ac35de..df945f5a22 100644
--- a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfoAtom.java
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfoAtom.java
@@ -53,22 +53,22 @@ public class TestInteractiveInfoAtom extends TestCase {
InteractiveInfoAtom ia = new InteractiveInfoAtom(data_a, 0, data_a.length);
InteractiveInfoAtom ib = new InteractiveInfoAtom(data_b, 0, data_b.length);
- assertEquals(1, ia.getNumber());
- assertEquals(4, ib.getNumber());
+ assertEquals(1, ia.getHyperlinkID());
+ assertEquals(4, ib.getHyperlinkID());
}
public void testGetRest() throws Exception {
InteractiveInfoAtom ia = new InteractiveInfoAtom(data_a, 0, data_a.length);
InteractiveInfoAtom ib = new InteractiveInfoAtom(data_b, 0, data_b.length);
- assertEquals(0, ia._getNumber1());
- assertEquals(0, ib._getNumber1());
+ assertEquals(0, ia.getSoundRef());
+ assertEquals(0, ib.getSoundRef());
- assertEquals(4, ia._getNumber3());
- assertEquals(4, ib._getNumber3());
+ assertEquals(4, ia.getAction());
+ assertEquals(4, ib.getAction());
- assertEquals(8, ia._getNumber4());
- assertEquals(8, ib._getNumber4());
+ assertEquals(8, ia.getHyperlinkType());
+ assertEquals(8, ib.getHyperlinkType());
}
public void testWrite() throws Exception {
@@ -88,10 +88,10 @@ public class TestInteractiveInfoAtom extends TestCase {
InteractiveInfoAtom ia = new InteractiveInfoAtom();
// Set values
- ia.setNumber(1);
- ia._setNumber1(0);
- ia._setNumber3(4);
- ia._setNumber4(8);
+ ia.setHyperlinkID(1);
+ ia.setSoundRef(0);
+ ia.setAction((byte)4);
+ ia.setHyperlinkType((byte)8);
// Check it's now the same as a
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -109,7 +109,7 @@ public class TestInteractiveInfoAtom extends TestCase {
InteractiveInfoAtom ia = new InteractiveInfoAtom(data_a, 0, data_a.length);
// Change the number
- ia.setNumber(4);
+ ia.setHyperlinkID(4);
// Check bytes are now the same
ByteArrayOutputStream baos = new ByteArrayOutputStream();
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTxInteractiveInfoAtom.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTxInteractiveInfoAtom.java
new file mode 100644
index 0000000000..9cb53d762f
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTxInteractiveInfoAtom.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.record;
+
+
+import junit.framework.TestCase;
+import java.io.ByteArrayOutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Tests that TxInteractiveInfoAtom works properly.
+ *
+ * @author Yegor Kozlov
+ */
+public class TestTxInteractiveInfoAtom extends TestCase {
+ // From WithLinks.ppt
+ private byte[] data_a = new byte[] {
+ 00, 00, (byte)0xDF, 0x0F, 0x08, 00, 00, 00,
+ 0x19, 00, 00, 00, 0x38, 00, 00, 00
+ };
+
+ private byte[] data_b = new byte[] {
+ 00, 00, (byte)0xDF, 0x0F, 0x08, 00, 00, 00,
+ 0x39, 00, 00, 00, 0x4E, 00, 00, 00
+ };
+
+ public void testRead() throws Exception {
+ TxInteractiveInfoAtom ia1 = new TxInteractiveInfoAtom(data_a, 0, data_a.length);
+
+ assertEquals(4063, ia1.getRecordType());
+ assertEquals(25, ia1.getStartIndex());
+ assertEquals(56, ia1.getEndIndex());
+
+ TxInteractiveInfoAtom ia2 = new TxInteractiveInfoAtom(data_b, 0, data_b.length);
+
+ assertEquals(4063, ia2.getRecordType());
+ assertEquals(57, ia2.getStartIndex());
+ assertEquals(78, ia2.getEndIndex());
+ }
+
+ public void testWrite() throws Exception {
+ TxInteractiveInfoAtom atom = new TxInteractiveInfoAtom(data_a, 0, data_a.length);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ atom.writeOut(baos);
+ byte[] b = baos.toByteArray();
+
+ assertEquals(data_a.length, b.length);
+ for(int i=0; i