#41047 - Support hyperlinks in HSLF shapes and textruns

#47291 - Cannot open link correctly which insert in ppt

HSLF hyperlink code was all over the place - moved most of it into HSLFHyperlink
extended common sl for hyperlinks
extended XSLF shape linking and added XSLFTextShape.appendText to go along with HSLF
adapted/fixed documentation
added convenience methods to the hyperlink classes to address the different targets

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1726458 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-01-24 00:12:10 +00:00
parent 6d56feee90
commit 5fad9af36f
27 changed files with 949 additions and 458 deletions

View File

@ -39,38 +39,26 @@ public abstract class CreateHyperlink {
HSLFSlide slideC = ppt.createSlide();
// link to a URL
HSLFTextBox textBox1 = new HSLFTextBox();
HSLFTextBox textBox1 = slideA.createTextBox();
textBox1.setText("Apache POI");
textBox1.setAnchor(new Rectangle(100, 100, 200, 50));
String text = textBox1.getText();
HSLFHyperlink link = new HSLFHyperlink();
link.setAddress("http://www.apache.org");
link.setLabel(textBox1.getText());
int linkId = ppt.addHyperlink(link);
// apply link to the text
textBox1.setHyperlink(linkId, 0, text.length());
slideA.addShape(textBox1);
HSLFHyperlink link1 = textBox1.getTextParagraphs().get(0).getTextRuns().get(0).createHyperlink();
link1.linkToUrl("http://www.apache.org");
link1.setLabel(textBox1.getText());
// link to another slide
HSLFTextBox textBox2 = new HSLFTextBox();
HSLFTextBox textBox2 = slideA.createTextBox();
textBox2.setText("Go to slide #3");
textBox2.setAnchor(new Rectangle(100, 300, 200, 50));
HSLFHyperlink link2 = new HSLFHyperlink();
link2.setAddress(slideC);
ppt.addHyperlink(link2);
// apply link to the whole shape
textBox2.setHyperlink(link2);
slideA.addShape(textBox2);
HSLFHyperlink link2 = textBox2.getTextParagraphs().get(0).getTextRuns().get(0).createHyperlink();
link2.linkToSlide(slideC);
FileOutputStream out = new FileOutputStream("hyperlink.ppt");
ppt.write(out);
out.close();
ppt.close();
}
}

View File

@ -19,12 +19,15 @@ package org.apache.poi.hslf.examples;
import java.io.FileInputStream;
import java.util.List;
import java.util.Locale;
import org.apache.poi.hslf.usermodel.HSLFHyperlink;
import org.apache.poi.hslf.usermodel.HSLFShape;
import org.apache.poi.hslf.usermodel.HSLFSimpleShape;
import org.apache.poi.hslf.usermodel.HSLFSlide;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
import org.apache.poi.hslf.usermodel.HSLFTextRun;
/**
* Demonstrates how to read hyperlinks from a presentation
@ -44,12 +47,14 @@ public final class Hyperlinks {
// read hyperlinks from the slide's text runs
System.out.println("- reading hyperlinks from the text runs");
for (List<HSLFTextParagraph> txtParas : slide.getTextParagraphs()) {
List<HSLFHyperlink> links = HSLFHyperlink.find(txtParas);
String text = HSLFTextParagraph.getRawText(txtParas);
for (HSLFHyperlink link : links) {
System.out.println(toStr(link, text));
for (List<HSLFTextParagraph> paras : slide.getTextParagraphs()) {
for (HSLFTextParagraph para : paras) {
for (HSLFTextRun run : para) {
HSLFHyperlink link = run.getHyperlink();
if (link != null) {
System.out.println(toStr(link, run.getRawText()));
}
}
}
}
@ -58,18 +63,21 @@ public final class Hyperlinks {
// read such hyperlinks
System.out.println("- reading hyperlinks from the slide's shapes");
for (HSLFShape sh : slide.getShapes()) {
HSLFHyperlink link = HSLFHyperlink.find(sh);
if (link == null) continue;
System.out.println(toStr(link, null));
if (sh instanceof HSLFSimpleShape) {
HSLFHyperlink link = ((HSLFSimpleShape)sh).getHyperlink();
if (link != null) {
System.out.println(toStr(link, null));
}
}
}
}
ppt.close();
}
}
static String toStr(HSLFHyperlink link, String rawText) {
//in ppt end index is inclusive
String formatStr = "title: %1$s, address: %2$s" + (rawText == null ? "" : ", start: %3$s, end: %4$s, substring: %5$s");
String substring = (rawText == null) ? "" : rawText.substring(link.getStartIndex(), link.getEndIndex()-1);
return String.format(formatStr, link.getLabel(), link.getAddress(), link.getStartIndex(), link.getEndIndex(), substring);
return String.format(Locale.ROOT, formatStr, link.getLabel(), link.getAddress(), link.getStartIndex(), link.getEndIndex(), rawText);
}
}

View File

@ -48,7 +48,7 @@ public class Tutorial6 {
XSLFTextRun r2 = shape2.addNewTextParagraph().addNewTextRun();
XSLFHyperlink link2 = r2.createHyperlink();
r2.setText("Go to the second slide"); // visible text
link2.setAddress(slide2); // link address
link2.linkToSlide(slide2); // link address

View File

@ -61,7 +61,7 @@ public class DrawPictureShape extends DrawSimpleShape {
* Returns an ImageRenderer for the PictureData
*
* @param graphics
* @return
* @return the image renderer
*/
public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) {
ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER);

View File

@ -20,5 +20,59 @@ package org.apache.poi.sl.usermodel;
/**
* A PowerPoint hyperlink
*/
public interface Hyperlink extends org.apache.poi.common.usermodel.Hyperlink {
public interface Hyperlink<
S extends Shape<S,P>,
P extends TextParagraph<S,P,?>
> extends org.apache.poi.common.usermodel.Hyperlink {
/**
* Link to an email
*
* @param emailAddress the email address
* @since POI 3.14-Beta2
*/
void linkToEmail(String emailAddress);
/**
* Link to a web page / URL
*
* @param url the url
* @since POI 3.14-Beta2
*/
void linkToUrl(String url);
/**
* Link to a slide in this slideshow
*
* @param slide the linked slide
* @since POI 3.14-Beta2
*/
void linkToSlide(Slide<S,P> slide);
/**
* Link to the next slide (relative from the current)
*
* @since POI 3.14-Beta2
*/
void linkToNextSlide();
/**
* Link to the previous slide (relative from the current)
*
* @since POI 3.14-Beta2
*/
void linkToPreviousSlide();
/**
* Link to the first slide in this slideshow
*
* @since POI 3.14-Beta2
*/
void linkToFirstSlide();
/**
* Link to the last slide in this slideshow
*
* @since POI 3.14-Beta2
*/
void linkToLastSlide();
}

View File

@ -83,4 +83,24 @@ public interface SimpleShape<
* the solid fill attribute from the underlying implementation
*/
void setFillColor(Color color);
/**
* Returns the hyperlink assigned to this shape
*
* @return the hyperlink assigned to this shape
* or <code>null</code> if not found.
*
* @since POI 3.14-Beta1
*/
Hyperlink<S,P> getHyperlink();
/**
* Creates a hyperlink and asigns it to this shape.
* If the shape has already a hyperlink assigned, return it instead
*
* @return the hyperlink assigned to this shape
*
* @since POI 3.14-Beta1
*/
Hyperlink<S,P> createHyperlink();
}

View File

@ -161,6 +161,19 @@ public interface TextRun {
* Return the associated hyperlink
*
* @return the associated hyperlink or null if no hyperlink was set
*
* @since POI 3.14-Beta2
*/
Hyperlink getHyperlink();
Hyperlink<?,?> getHyperlink();
/**
* Creates a new hyperlink and assigns it to this text run.
* If the text run has already a hyperlink assigned, return it instead
*
* @return the associated hyperlink
*
* @since POI 3.14-Beta2
*/
Hyperlink<?,?> createHyperlink();
}

View File

@ -118,6 +118,16 @@ public interface TextShape<
OTHER
}
/**
* Returns the text contained in this text frame, which has been made safe
* for printing and other use.
*
* @return the text string for this textbox.
*
* @since POI 3.14-Beta2
*/
String getText();
/**
* Sets (overwrites) the current text.
* Uses the properties of the first paragraph / textrun.
@ -130,6 +140,18 @@ public interface TextShape<
*/
TextRun setText(String text);
/**
* Adds the supplied text onto the end of the TextParagraphs,
* creating a new RichTextRun for it to sit in.
*
* @param text the text string to be appended.
* @param newParagraph if true, a new paragraph will be added,
* which will contain the added text
*
* @since POI 3.14-Beta1
*/
TextRun appendText(String text, boolean newParagraph);
/**
* @return the TextParagraphs for this text box
*/

View File

@ -16,20 +16,23 @@
==================================================================== */
package org.apache.poi.xslf.usermodel;
import java.net.URI;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.sl.usermodel.Hyperlink;
import org.apache.poi.sl.usermodel.Slide;
import org.apache.poi.util.Internal;
import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink;
import java.net.URI;
public class XSLFHyperlink implements Hyperlink {
final XSLFTextRun _r;
public class XSLFHyperlink implements Hyperlink<XSLFShape,XSLFTextParagraph> {
final XSLFSheet _sheet;
final CTHyperlink _link;
XSLFHyperlink(CTHyperlink link, XSLFTextRun r){
_r = r;
XSLFHyperlink(CTHyperlink link, XSLFSheet sheet){
_sheet = sheet;
_link = link;
}
@ -39,17 +42,20 @@ public class XSLFHyperlink implements Hyperlink {
}
@Override
public void setAddress(String address){
XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet();
PackageRelationship rel =
sheet.getPackagePart().
addExternalRelationship(address, XSLFRelation.HYPERLINK.getRelation());
_link.setId(rel.getId());
public void setAddress(String address) {
linkToUrl(address);
}
@Override
public String getAddress() {
return getTargetURI().toASCIIString();
if (!_link.isSetId()) {
return _link.getAction();
}
String id = _link.getId();
URI targetURI = _sheet.getPackagePart().getRelationship(id).getTargetURI();
return targetURI.toASCIIString();
}
@Override
@ -64,28 +70,88 @@ public class XSLFHyperlink implements Hyperlink {
@Override
public int getType() {
// TODO: currently this just returns nonsense
if ("ppaction://hlinksldjump".equals(_link.getAction())) {
String action = _link.getAction();
if (action == null) {
action = "";
}
if (action.equals("ppaction://hlinksldjump") || action.startsWith("ppaction://hlinkshowjump")) {
return LINK_DOCUMENT;
}
return LINK_URL;
String address = getAddress();
if (address == null) {
address = "";
}
if (address.startsWith("mailto:")) {
return LINK_EMAIL;
} else {
return LINK_URL;
}
}
public void setAddress(XSLFSlide slide){
XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet();
@Override
public void linkToEmail(String emailAddress) {
linkToExternal("mailto:"+emailAddress);
setLabel(emailAddress);
}
@Override
public void linkToUrl(String url) {
linkToExternal(url);
setLabel(url);
}
private void linkToExternal(String url) {
PackagePart thisPP = _sheet.getPackagePart();
if (_link.isSetId() && !_link.getId().isEmpty()) {
thisPP.removeRelationship(_link.getId());
}
PackageRelationship rel = thisPP.addExternalRelationship(url, XSLFRelation.HYPERLINK.getRelation());
_link.setId(rel.getId());
if (_link.isSetAction()) {
_link.unsetAction();
}
}
@Override
public void linkToSlide(Slide<XSLFShape,XSLFTextParagraph> slide) {
PackagePart thisPP = _sheet.getPackagePart();
PackagePartName otherPPN = ((XSLFSheet)slide).getPackagePart().getPartName();
if (_link.isSetId() && !_link.getId().isEmpty()) {
thisPP.removeRelationship(_link.getId());
}
PackageRelationship rel =
sheet.getPackagePart().
addRelationship(slide.getPackagePart().getPartName(),
TargetMode.INTERNAL,
XSLFRelation.SLIDE.getRelation());
thisPP.addRelationship(otherPPN, TargetMode.INTERNAL, XSLFRelation.SLIDE.getRelation());
_link.setId(rel.getId());
_link.setAction("ppaction://hlinksldjump");
}
@Internal
public URI getTargetURI(){
XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet();
String id = _link.getId();
return sheet.getPackagePart().getRelationship(id).getTargetURI();
@Override
public void linkToNextSlide() {
linkToRelativeSlide("nextslide");
}
@Override
public void linkToPreviousSlide() {
linkToRelativeSlide("previousslide");
}
@Override
public void linkToFirstSlide() {
linkToRelativeSlide("firstslide");
}
@Override
public void linkToLastSlide() {
linkToRelativeSlide("lastslide");
}
private void linkToRelativeSlide(String jump) {
PackagePart thisPP = _sheet.getPackagePart();
if (_link.isSetId() && !_link.getId().isEmpty()) {
thisPP.removeRelationship(_link.getId());
}
_link.setId("");
_link.setAction("ppaction://hlinkshowjump?jump="+jump);
}
}

View File

@ -34,8 +34,8 @@ import org.apache.poi.sl.usermodel.LineDecoration;
import org.apache.poi.sl.usermodel.LineDecoration.DecorationShape;
import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize;
import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.sl.usermodel.ShapeType;
import org.apache.poi.sl.usermodel.SimpleShape;
import org.apache.poi.sl.usermodel.StrokeStyle;
@ -53,6 +53,7 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTGeomGuide;
import org.openxmlformats.schemas.drawingml.x2006.main.CTLineEndProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTLineStyleList;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTOuterShadowEffect;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
@ -923,4 +924,23 @@ public abstract class XSLFSimpleShape extends XSLFShape
public void setPlaceholder(Placeholder placeholder) {
super.setPlaceholder(placeholder);
}
@Override
public XSLFHyperlink getHyperlink() {
CTNonVisualDrawingProps cNvPr = getCNvPr();
if (!cNvPr.isSetHlinkClick()) {
return null;
}
return new XSLFHyperlink(cNvPr.getHlinkClick(), getSheet());
}
@Override
public XSLFHyperlink createHyperlink() {
XSLFHyperlink hl = getHyperlink();
if (hl == null) {
CTNonVisualDrawingProps cNvPr = getCNvPr();
hl = new XSLFHyperlink(cNvPr.addNewHlinkClick(), getSheet());
}
return hl;
}
}

View File

@ -994,4 +994,26 @@ public class XSLFTextParagraph implements TextParagraph<XSLFShape,XSLFTextParagr
}
}
}
/**
* Helper method for appending text and keeping paragraph and character properties.
* The character properties are moved to the end paragraph marker
*/
/* package */ void clearButKeepProperties() {
CTTextParagraph thisP = getXmlObject();
for (int i=thisP.sizeOfBrArray(); i>0; i--) {
thisP.removeBr(i-1);
}
for (int i=thisP.sizeOfFldArray(); i>0; i--) {
thisP.removeFld(i-1);
}
if (!_runs.isEmpty()) {
int size = _runs.size();
thisP.setEndParaRPr(_runs.get(size-1).getRPr());
for (int i=size; i>0; i--) {
thisP.removeR(i-1);
}
_runs.clear();
}
}
}

View File

@ -444,17 +444,19 @@ public class XSLFTextRun implements TextRun {
return "[" + getClass() + "]" + getRawText();
}
@Override
public XSLFHyperlink createHyperlink(){
XSLFHyperlink link = new XSLFHyperlink(_r.getRPr().addNewHlinkClick(), this);
return link;
XSLFHyperlink hl = getHyperlink();
if (hl == null) {
hl = new XSLFHyperlink(_r.getRPr().addNewHlinkClick(), _p.getParentShape().getSheet());
}
return hl;
}
@Override
public XSLFHyperlink getHyperlink(){
if(!_r.getRPr().isSetHlinkClick()) return null;
return new XSLFHyperlink(_r.getRPr().getHlinkClick(), this);
return new XSLFHyperlink(_r.getRPr().getHlinkClick(), _p.getParentShape().getSheet());
}
private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){

View File

@ -71,10 +71,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape
return getTextParagraphs().iterator();
}
/**
*
* @return text contained within this shape or empty string
*/
@Override
public String getText() {
StringBuilder out = new StringBuilder();
for (XSLFTextParagraph p : _paragraphs) {
@ -95,50 +92,76 @@ public abstract class XSLFTextShape extends XSLFSimpleShape
@Override
public XSLFTextRun setText(String text) {
// copy properties from first paragraph / textrun
// calling clearText or setting to a new Array leads to a XmlValueDisconnectedException
if (!_paragraphs.isEmpty()) {
CTTextBody txBody = getTextBody(false);
int cntPs = txBody.sizeOfPArray();
for (int i = cntPs; i > 1; i--) {
txBody.removeP(i-1);
_paragraphs.remove(i-1);
}
_paragraphs.get(0).clearButKeepProperties();
}
return appendText(text, false);
}
@Override
public XSLFTextRun appendText(String text, boolean newParagraph) {
if (text == null) return null;
// copy properties from last paragraph / textrun or paragraph end marker
CTTextParagraphProperties pPr = null;
CTTextCharacterProperties rPr = null;
if (!_paragraphs.isEmpty()) {
XSLFTextParagraph p0 = _paragraphs.get(0);
pPr = p0.getXmlObject().getPPr();
if (!p0.getTextRuns().isEmpty()) {
XSLFTextRun r0 = p0.getTextRuns().get(0);
boolean firstPara;
XSLFTextParagraph para;
if (_paragraphs.isEmpty()) {
firstPara = false;
para = null;
} else {
firstPara = !newParagraph;
para = _paragraphs.get(_paragraphs.size()-1);
CTTextParagraph ctp = para.getXmlObject();
pPr = ctp.getPPr();
List<XSLFTextRun> runs = para.getTextRuns();
if (!runs.isEmpty()) {
XSLFTextRun r0 = runs.get(runs.size()-1);
rPr = r0.getXmlObject().getRPr();
} else if (ctp.isSetEndParaRPr()) {
rPr = ctp.getEndParaRPr();
}
}
// can't call clearText otherwise we receive a XmlValueDisconnectedException
_paragraphs.clear();
CTTextBody txBody = getTextBody(true);
int cntPs = txBody.sizeOfPArray();
// split text by paragraph and new line char
XSLFTextRun r = null;
for (String paraText : text.split("\\r\\n?|\\n")) {
XSLFTextParagraph para = addNewTextParagraph();
if (pPr != null) {
para.getXmlObject().setPPr(pPr);
XSLFTextRun run = null;
for (String lineTxt : text.split("\\r\\n?|\\n")) {
if (!firstPara) {
if (para != null && para.getXmlObject().isSetEndParaRPr()) {
para.getXmlObject().unsetEndParaRPr();
}
para = addNewTextParagraph();
if (pPr != null) {
para.getXmlObject().setPPr(pPr);
}
}
boolean first = true;
for (String runText : paraText.split("[\u000b]")) {
if (!first) {
boolean firstRun = true;
for (String runText : lineTxt.split("[\u000b]")) {
if (!firstRun) {
para.addLineBreak();
}
r = para.addNewTextRun();
r.setText(runText);
run = para.addNewTextRun();
run.setText(runText);
if (rPr != null) {
r.getXmlObject().setRPr(rPr);
run.getXmlObject().setRPr(rPr);
}
first = false;
firstRun = false;
}
firstPara = false;
}
// simply setting a new pArray leads to XmlValueDisconnectedException
for (int i = cntPs-1; i >= 0; i--) {
txBody.removeP(i);
}
return r;
assert(run != null);
return run;
}
@Override

View File

@ -19,19 +19,17 @@ package org.apache.poi.xslf.usermodel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.sl.usermodel.Hyperlink;
import org.apache.poi.xslf.XSLFTestDataSamples;
import org.junit.Test;
/**
* @author Yegor Kozlov
*/
public class TestXSLFHyperlink {
@Test
@ -45,19 +43,19 @@ public class TestXSLFHyperlink {
assertEquals("Web Page", cell1.getText());
XSLFHyperlink link1 = cell1.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(link1);
assertEquals(URI.create("http://poi.apache.org/"), link1.getTargetURI());
assertEquals("http://poi.apache.org/", link1.getAddress());
XSLFTableCell cell2 = tbl.getRows().get(2).getCells().get(0);
assertEquals("Place in this document", cell2.getText());
XSLFHyperlink link2 = cell2.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(link2);
assertEquals(URI.create("/ppt/slides/slide2.xml"), link2.getTargetURI());
assertEquals("/ppt/slides/slide2.xml", link2.getAddress());
XSLFTableCell cell3 = tbl.getRows().get(3).getCells().get(0);
assertEquals("Email", cell3.getText());
XSLFHyperlink link3 = cell3.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(link3);
assertEquals(URI.create("mailto:dev@poi.apache.org?subject=Hi%20There"), link3.getTargetURI());
assertEquals("mailto:dev@poi.apache.org?subject=Hi%20There", link3.getAddress());
ppt.close();
}
@ -75,7 +73,7 @@ public class TestXSLFHyperlink {
r1.setText("Web Page");
XSLFHyperlink link1 = r1.createHyperlink();
link1.setAddress("http://poi.apache.org/");
assertEquals(URI.create("http://poi.apache.org/"), link1.getTargetURI());
assertEquals("http://poi.apache.org/", link1.getAddress());
assertEquals(numRel + 1, slide1.getPackagePart().getRelationships().size());
String id1 = link1.getXmlObject().getId();
@ -90,8 +88,8 @@ public class TestXSLFHyperlink {
XSLFTextRun r2 = sh2.addNewTextParagraph().addNewTextRun();
r2.setText("Place in this document");
XSLFHyperlink link2 = r2.createHyperlink();
link2.setAddress(slide2);
assertEquals(URI.create("/ppt/slides/slide2.xml"), link2.getTargetURI());
link2.linkToSlide(slide2);
assertEquals("/ppt/slides/slide2.xml", link2.getAddress());
assertEquals(numRel + 2, slide1.getPackagePart().getRelationships().size());
String id2 = link2.getXmlObject().getId();
@ -104,4 +102,76 @@ public class TestXSLFHyperlink {
ppt.close();
}
@Test
public void bug47291() throws IOException {
Rectangle2D anchor = new Rectangle2D.Double(100,100,100,100);
XMLSlideShow ppt1 = new XMLSlideShow();
XSLFSlide slide1 = ppt1.createSlide();
XSLFTextBox tb1 = slide1.createTextBox();
tb1.setAnchor(anchor);
XSLFTextRun r1 = tb1.setText("page1");
XSLFHyperlink hl1 = r1.createHyperlink();
hl1.linkToEmail("dev@poi.apache.org");
XSLFTextBox tb2 = ppt1.createSlide().createTextBox();
tb2.setAnchor(anchor);
XSLFTextRun r2 = tb2.setText("page2");
XSLFHyperlink hl2 = r2.createHyperlink();
hl2.linkToLastSlide();
XSLFSlide sl3 = ppt1.createSlide();
XSLFTextBox tb3 = sl3.createTextBox();
tb3.setAnchor(anchor);
tb3.setText("text1 ");
XSLFTextRun r3 = tb3.appendText("lin\u000bk", false);
tb3.appendText(" text2", false);
XSLFHyperlink hl3 = r3.createHyperlink();
hl3.linkToSlide(slide1);
XSLFTextBox tb4 = ppt1.createSlide().createTextBox();
tb4.setAnchor(anchor);
XSLFTextRun r4 = tb4.setText("page4");
XSLFHyperlink hl4 = r4.createHyperlink();
hl4.linkToUrl("http://poi.apache.org");
XSLFTextBox tb5 = ppt1.createSlide().createTextBox();
tb5.setAnchor(anchor);
tb5.setText("page5");
XSLFHyperlink hl5 = tb5.createHyperlink();
hl5.linkToFirstSlide();
XMLSlideShow ppt2 = XSLFTestDataSamples.writeOutAndReadBack(ppt1);
ppt1.close();
List<XSLFSlide> slides = ppt2.getSlides();
tb1 = (XSLFTextBox)slides.get(0).getShapes().get(0);
hl1 = tb1.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(hl1);
assertEquals("dev@poi.apache.org", hl1.getLabel());
assertEquals(Hyperlink.LINK_EMAIL, hl1.getType());
tb2 = (XSLFTextBox)slides.get(1).getShapes().get(0);
hl2 = tb2.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(hl2);
assertEquals("lastslide", hl2.getXmlObject().getAction().split("=")[1]);
assertEquals(Hyperlink.LINK_DOCUMENT, hl2.getType());
tb3 = (XSLFTextBox)slides.get(2).getShapes().get(0);
hl3 = tb3.getTextParagraphs().get(0).getTextRuns().get(3).getHyperlink();
assertNotNull(hl3);
assertEquals("/ppt/slides/slide1.xml", hl3.getAddress());
assertEquals(Hyperlink.LINK_DOCUMENT, hl3.getType());
tb4 = (XSLFTextBox)slides.get(3).getShapes().get(0);
hl4 = tb4.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(hl4);
assertEquals("http://poi.apache.org", hl4.getLabel());
assertEquals(Hyperlink.LINK_URL, hl4.getType());
tb5 = (XSLFTextBox)slides.get(4).getShapes().get(0);
hl5 = tb5.getHyperlink();
assertNotNull(hl5);
assertEquals("firstslide", hl5.getXmlObject().getAction().split("=")[1]);
assertEquals(Hyperlink.LINK_DOCUMENT, hl5.getType());
ppt2.close();
}
}

View File

@ -160,7 +160,7 @@ public final class OLEShape extends HSLFPictureShape {
if(_exEmbed == null){
HSLFSlideShow ppt = getSheet().getSlideShow();
ExObjList lst = ppt.getDocumentRecord().getExObjList();
ExObjList lst = ppt.getDocumentRecord().getExObjList(false);
if(lst == null){
logger.log(POILogger.WARN, "ExObjList not found");
return null;

View File

@ -46,22 +46,33 @@ public final class Document extends PositionDependentRecordContainer
* Returns the DocumentAtom of this Document
*/
public DocumentAtom getDocumentAtom() { return documentAtom; }
/**
* Returns the Environment of this Notes, which lots of
* settings for the document in it
* settings for the document in it
*/
public Environment getEnvironment() { return environment; }
/**
* Returns the PPDrawingGroup, which holds an Escher Structure
* that contains information on pictures in the slides.
* that contains information on pictures in the slides.
*/
public PPDrawingGroup getPPDrawingGroup() { return ppDrawing; }
/**
* Returns the ExObjList, which holds the references to
* external objects used in the slides. This may be null, if
* there are no external references.
* external objects used in the slides. This may be null, if
* there are no external references.
*
* @param create if true, create an ExObjList if it doesn't exist
*/
public ExObjList getExObjList() { return exObjList; }
public ExObjList getExObjList(boolean create) {
if (exObjList == null && create) {
exObjList = new ExObjList();
addChildAfter(exObjList, getDocumentAtom());
}
return exObjList;
}
/**
* Returns all the SlideListWithTexts that are defined for

View File

@ -67,9 +67,9 @@ public class ExHyperlink extends RecordContainer {
linkDetailsB.setText(url);
}
}
public void setLinkURL(String url, int options) {
public void setLinkOptions(int options) {
if(linkDetailsB != null) {
linkDetailsB.setText(url);
linkDetailsB.setOptions(options);
}
}

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.ListIterator;
import org.apache.poi.hslf.record.ExHyperlink;
import org.apache.poi.hslf.record.ExHyperlinkAtom;
import org.apache.poi.hslf.record.ExObjList;
import org.apache.poi.hslf.record.HSLFEscherClientDataRecord;
import org.apache.poi.hslf.record.InteractiveInfo;
@ -30,24 +31,95 @@ import org.apache.poi.hslf.record.InteractiveInfoAtom;
import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
import org.apache.poi.sl.usermodel.Hyperlink;
import org.apache.poi.sl.usermodel.Slide;
/**
* Represents a hyperlink in a PowerPoint document
*/
public final class HSLFHyperlink implements Hyperlink {
public static final byte LINK_NEXTSLIDE = InteractiveInfoAtom.LINK_NextSlide;
public static final byte LINK_PREVIOUSSLIDE = InteractiveInfoAtom.LINK_PreviousSlide;
public static final byte LINK_FIRSTSLIDE = InteractiveInfoAtom.LINK_FirstSlide;
public static final byte LINK_LASTSLIDE = InteractiveInfoAtom.LINK_LastSlide;
public static final byte LINK_SLIDENUMBER = InteractiveInfoAtom.LINK_SlideNumber;
public static final byte LINK_URL = InteractiveInfoAtom.LINK_Url;
public static final byte LINK_NULL = InteractiveInfoAtom.LINK_NULL;
public final class HSLFHyperlink implements Hyperlink<HSLFShape,HSLFTextParagraph> {
private final ExHyperlink exHyper;
private final InteractiveInfo info;
private TxInteractiveInfoAtom txinfo;
protected HSLFHyperlink(ExHyperlink exHyper, InteractiveInfo info) {
this.info = info;
this.exHyper = exHyper;
}
public ExHyperlink getExHyperlink() {
return exHyper;
}
public InteractiveInfo getInfo() {
return info;
}
public TxInteractiveInfoAtom getTextRunInfo() {
return txinfo;
}
protected void setTextRunInfo(TxInteractiveInfoAtom txinfo) {
this.txinfo = txinfo;
}
/**
* Creates a new Hyperlink and assign it to a shape
* This is only a helper method - use {@link HSLFSimpleShape#createHyperlink()} instead!
*
* @param shape the shape which receives the hyperlink
* @return the new hyperlink
*
* @see HSLFShape#createHyperlink()
*/
/* package */ static HSLFHyperlink createHyperlink(HSLFSimpleShape shape) {
// TODO: check if a hyperlink already exists
ExHyperlink exHyper = new ExHyperlink();
int linkId = shape.getSheet().getSlideShow().addToObjListAtom(exHyper);
ExHyperlinkAtom obj = exHyper.getExHyperlinkAtom();
obj.setNumber(linkId);
InteractiveInfo info = new InteractiveInfo();
info.getInteractiveInfoAtom().setHyperlinkID(linkId);
HSLFEscherClientDataRecord cldata = shape.getClientData(true);
cldata.addChild(info);
HSLFHyperlink hyper = new HSLFHyperlink(exHyper, info);
hyper.linkToNextSlide();
shape.setHyperlink(hyper);
return hyper;
}
/**
* Creates a new Hyperlink for a textrun.
* This is only a helper method - use {@link HSLFTextRun#createHyperlink()} instead!
*
* @param run the run which receives the hyperlink
* @return the new hyperlink
*
* @see HSLFTextRun#createHyperlink()
*/
/* package */ static HSLFHyperlink createHyperlink(HSLFTextRun run) {
// TODO: check if a hyperlink already exists
ExHyperlink exHyper = new ExHyperlink();
int linkId = run.getTextParagraph().getSheet().getSlideShow().addToObjListAtom(exHyper);
ExHyperlinkAtom obj = exHyper.getExHyperlinkAtom();
obj.setNumber(linkId);
InteractiveInfo info = new InteractiveInfo();
info.getInteractiveInfoAtom().setHyperlinkID(linkId);
// don't add the hyperlink now to text paragraph records
// this will be done, when the paragraph is saved
HSLFHyperlink hyper = new HSLFHyperlink(exHyper, info);
hyper.linkToNextSlide();
TxInteractiveInfoAtom txinfo = new TxInteractiveInfoAtom();
int startIdx = run.getTextParagraph().getStartIdxOfTextRun(run);
int endIdx = startIdx + run.getLength();
txinfo.setStartIndex(startIdx);
txinfo.setEndIndex(endIdx);
hyper.setTextRunInfo(txinfo);
run.setHyperlink(hyper);
return hyper;
}
private int id=-1;
private int type;
private String address;
private String label;
private int startIndex, endIndex;
/**
* Gets the type of the hyperlink action.
@ -58,70 +130,130 @@ public final class HSLFHyperlink implements Hyperlink {
*/
@Override
public int getType() {
return type;
}
public void setType(int val) {
type = val;
switch(type){
case LINK_NEXTSLIDE:
label = "NEXT";
address = "1,-1,NEXT";
break;
case LINK_PREVIOUSSLIDE:
label = "PREV";
address = "1,-1,PREV";
break;
case LINK_FIRSTSLIDE:
label = "FIRST";
address = "1,-1,FIRST";
break;
case LINK_LASTSLIDE:
label = "LAST";
address = "1,-1,LAST";
break;
case LINK_SLIDENUMBER:
break;
default:
label = "";
address = "";
break;
switch (info.getInteractiveInfoAtom().getHyperlinkType()) {
case InteractiveInfoAtom.LINK_Url:
return (exHyper.getLinkURL().startsWith("mailto:")) ? LINK_EMAIL : LINK_URL;
case InteractiveInfoAtom.LINK_NextSlide:
case InteractiveInfoAtom.LINK_PreviousSlide:
case InteractiveInfoAtom.LINK_FirstSlide:
case InteractiveInfoAtom.LINK_LastSlide:
case InteractiveInfoAtom.LINK_SlideNumber:
return LINK_DOCUMENT;
case InteractiveInfoAtom.LINK_CustomShow:
case InteractiveInfoAtom.LINK_OtherPresentation:
case InteractiveInfoAtom.LINK_OtherFile:
return LINK_FILE;
default:
case InteractiveInfoAtom.LINK_NULL:
return -1;
}
}
@Override
public String getAddress() {
return address;
public void linkToEmail(String emailAddress) {
InteractiveInfoAtom iia = info.getInteractiveInfoAtom();
iia.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
iia.setJump(InteractiveInfoAtom.JUMP_NONE);
iia.setHyperlinkType(InteractiveInfoAtom.LINK_Url);
exHyper.setLinkURL("mailto:"+emailAddress);
exHyper.setLinkTitle(emailAddress);
exHyper.setLinkOptions(0x10);
}
public void setAddress(HSLFSlide slide) {
String href = slide._getSheetNumber() + ","+slide.getSlideNumber()+",Slide " + slide.getSlideNumber();
setAddress(href);;
setLabel("Slide " + slide.getSlideNumber());
setType(HSLFHyperlink.LINK_SLIDENUMBER);
@Override
public void linkToUrl(String url) {
InteractiveInfoAtom iia = info.getInteractiveInfoAtom();
iia.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
iia.setJump(InteractiveInfoAtom.JUMP_NONE);
iia.setHyperlinkType(InteractiveInfoAtom.LINK_Url);
exHyper.setLinkURL(url);
exHyper.setLinkTitle(url);
exHyper.setLinkOptions(0x10);
}
@Override
public void linkToSlide(Slide<HSLFShape,HSLFTextParagraph> slide) {
assert(slide instanceof HSLFSlide);
HSLFSlide sl = (HSLFSlide)slide;
int slideNum = slide.getSlideNumber();
String alias = "Slide "+slideNum;
InteractiveInfoAtom iia = info.getInteractiveInfoAtom();
iia.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
iia.setJump(InteractiveInfoAtom.JUMP_NONE);
iia.setHyperlinkType(InteractiveInfoAtom.LINK_SlideNumber);
linkToDocument(sl._getSheetNumber(),slideNum,alias,0x30);
}
@Override
public void linkToNextSlide() {
InteractiveInfoAtom iia = info.getInteractiveInfoAtom();
iia.setAction(InteractiveInfoAtom.ACTION_JUMP);
iia.setJump(InteractiveInfoAtom.JUMP_NEXTSLIDE);
iia.setHyperlinkType(InteractiveInfoAtom.LINK_NextSlide);
linkToDocument(1,-1,"NEXT",0x10);
}
@Override
public void linkToPreviousSlide() {
InteractiveInfoAtom iia = info.getInteractiveInfoAtom();
iia.setAction(InteractiveInfoAtom.ACTION_JUMP);
iia.setJump(InteractiveInfoAtom.JUMP_PREVIOUSSLIDE);
iia.setHyperlinkType(InteractiveInfoAtom.LINK_PreviousSlide);
linkToDocument(1,-1,"PREV",0x10);
}
@Override
public void linkToFirstSlide() {
InteractiveInfoAtom iia = info.getInteractiveInfoAtom();
iia.setAction(InteractiveInfoAtom.ACTION_JUMP);
iia.setJump(InteractiveInfoAtom.JUMP_FIRSTSLIDE);
iia.setHyperlinkType(InteractiveInfoAtom.LINK_FirstSlide);
linkToDocument(1,-1,"FIRST",0x10);
}
@Override
public void linkToLastSlide() {
InteractiveInfoAtom iia = info.getInteractiveInfoAtom();
iia.setAction(InteractiveInfoAtom.ACTION_JUMP);
iia.setJump(InteractiveInfoAtom.JUMP_LASTSLIDE);
iia.setHyperlinkType(InteractiveInfoAtom.LINK_LastSlide);
linkToDocument(1,-1,"LAST",0x10);
}
private void linkToDocument(int sheetNumber, int slideNumber, String alias, int options) {
exHyper.setLinkURL(sheetNumber+","+slideNumber+","+alias);
exHyper.setLinkTitle(alias);
exHyper.setLinkOptions(options);
}
@Override
public String getAddress() {
return exHyper.getLinkURL();
}
@Override
public void setAddress(String str) {
address = str;
exHyper.setLinkURL(str);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
return exHyper.getExHyperlinkAtom().getNumber();
}
@Override
public String getLabel() {
return label;
return exHyper.getLinkTitle();
}
@Override
public void setLabel(String str) {
label = str;
public void setLabel(String label) {
exHyper.setLinkTitle(label);
}
/**
@ -130,7 +262,7 @@ public final class HSLFHyperlink implements Hyperlink {
* @return the beginning character position
*/
public int getStartIndex() {
return startIndex;
return (txinfo == null) ? -1 : txinfo.getStartIndex();
}
/**
@ -139,7 +271,9 @@ public final class HSLFHyperlink implements Hyperlink {
* @param startIndex the beginning character position
*/
public void setStartIndex(int startIndex) {
this.startIndex = startIndex;
if (txinfo != null) {
txinfo.setStartIndex(startIndex);
}
}
/**
@ -148,7 +282,7 @@ public final class HSLFHyperlink implements Hyperlink {
* @return the ending character position
*/
public int getEndIndex() {
return endIndex;
return (txinfo == null) ? -1 : txinfo.getEndIndex();
}
/**
@ -157,7 +291,9 @@ public final class HSLFHyperlink implements Hyperlink {
* @param endIndex the ending character position
*/
public void setEndIndex(int endIndex) {
this.endIndex = endIndex;
if (txinfo != null) {
txinfo.setEndIndex(endIndex);
}
}
/**
@ -177,7 +313,7 @@ public final class HSLFHyperlink implements Hyperlink {
* @return found hyperlinks
*/
@SuppressWarnings("resource")
public static List<HSLFHyperlink> find(List<HSLFTextParagraph> paragraphs){
protected static List<HSLFHyperlink> find(List<HSLFTextParagraph> paragraphs){
List<HSLFHyperlink> lst = new ArrayList<HSLFHyperlink>();
if (paragraphs == null || paragraphs.isEmpty()) return lst;
@ -185,7 +321,7 @@ public final class HSLFHyperlink implements Hyperlink {
HSLFSlideShow ppt = firstPara.getSheet().getSlideShow();
//document-level container which stores info about all links in a presentation
ExObjList exobj = ppt.getDocumentRecord().getExObjList();
ExObjList exobj = ppt.getDocumentRecord().getExObjList(false);
if (exobj != null) {
Record[] records = firstPara.getRecords();
find(Arrays.asList(records), exobj, lst);
@ -201,10 +337,10 @@ public final class HSLFHyperlink implements Hyperlink {
* @return found hyperlink or <code>null</code>
*/
@SuppressWarnings("resource")
public static HSLFHyperlink find(HSLFShape shape){
protected static HSLFHyperlink find(HSLFShape shape){
HSLFSlideShow ppt = shape.getSheet().getSlideShow();
//document-level container which stores info about all links in a presentation
ExObjList exobj = ppt.getDocumentRecord().getExObjList();
ExObjList exobj = ppt.getDocumentRecord().getExObjList(false);
HSLFEscherClientDataRecord cldata = shape.getClientData(false);
if (exobj != null && cldata != null) {
@ -228,16 +364,12 @@ public final class HSLFHyperlink implements Hyperlink {
InteractiveInfo hldr = (InteractiveInfo)r;
InteractiveInfoAtom info = hldr.getInteractiveInfoAtom();
int id = info.getHyperlinkID();
ExHyperlink linkRecord = exobj.get(id);
if (linkRecord == null) {
ExHyperlink exHyper = exobj.get(id);
if (exHyper == null) {
continue;
}
HSLFHyperlink link = new HSLFHyperlink();
link.setId(id);
link.setType(info.getAction());
link.setLabel(linkRecord.getLinkTitle());
link.setAddress(linkRecord.getLinkURL());
HSLFHyperlink link = new HSLFHyperlink(exHyper, hldr);
out.add(link);
if (iter.hasNext()) {
@ -246,9 +378,7 @@ public final class HSLFHyperlink implements Hyperlink {
iter.previous();
continue;
}
TxInteractiveInfoAtom txinfo = (TxInteractiveInfoAtom)r;
link.setStartIndex(txinfo.getStartIndex());
link.setEndIndex(txinfo.getEndIndex());
link.setTextRunInfo((TxInteractiveInfoAtom)r);
}
}
}

View File

@ -28,7 +28,6 @@ import org.apache.poi.ddf.EscherChildAnchorRecord;
import org.apache.poi.ddf.EscherClientAnchorRecord;
import org.apache.poi.ddf.EscherColorRef;
import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherOptRecord;
import org.apache.poi.ddf.EscherProperties;
import org.apache.poi.ddf.EscherProperty;
import org.apache.poi.ddf.EscherRecord;
@ -60,8 +59,6 @@ import org.apache.poi.util.Units;
* in points (72 points = 1 inch).
* </p>
* <p>
*
* @author Yegor Kozlov
*/
public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
@ -445,16 +442,6 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
return getFill().getFillStyle();
}
/**
* Returns the hyperlink assigned to this shape
*
* @return the hyperlink assigned to this shape
* or <code>null</code> if not found.
*/
public HSLFHyperlink getHyperlink(){
return HSLFHyperlink.find(this);
}
public void draw(Graphics2D graphics){
logger.log(POILogger.INFO, "Rendering " + getShapeName());
}

View File

@ -134,6 +134,7 @@ public abstract class HSLFSheet implements HSLFShapeContainer, Sheet<HSLFShape,H
if (trs == null) return;
for (List<HSLFTextParagraph> ltp : trs) {
HSLFTextParagraph.supplySheet(ltp, this);
HSLFTextParagraph.applyHyperlinks(ltp);
}
}
@ -171,6 +172,14 @@ public abstract class HSLFSheet implements HSLFShapeContainer, Sheet<HSLFShape,H
EscherContainerRecord sp = (EscherContainerRecord) it.next();
HSLFShape sh = HSLFShapeFactory.createShape(sp, null);
sh.setSheet(this);
if (sh instanceof HSLFSimpleShape) {
HSLFHyperlink link = HSLFHyperlink.find(sh);
if (link != null) {
((HSLFSimpleShape)sh).setHyperlink(link);
}
}
shapeList.add(sh);
}

View File

@ -32,8 +32,6 @@ import org.apache.poi.ddf.EscherSimpleProperty;
import org.apache.poi.ddf.EscherSpRecord;
import org.apache.poi.hslf.exceptions.HSLFException;
import org.apache.poi.hslf.record.HSLFEscherClientDataRecord;
import org.apache.poi.hslf.record.InteractiveInfo;
import org.apache.poi.hslf.record.InteractiveInfoAtom;
import org.apache.poi.hslf.record.OEPlaceholderAtom;
import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.RoundTripHFPlaceholder12;
@ -69,6 +67,11 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
public final static double DEFAULT_LINE_WIDTH = 0.75;
/**
* Hyperlink
*/
protected HSLFHyperlink _hyperlink;
/**
* Create a SimpleShape object and initialize it from the supplied Record container.
*
@ -270,56 +273,6 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
getFill().setForegroundColor(color);
}
public void setHyperlink(HSLFHyperlink link){
if(link.getId() == -1){
throw new HSLFException("You must call SlideShow.addHyperlink(Hyperlink link) first");
}
InteractiveInfo info = new InteractiveInfo();
InteractiveInfoAtom infoAtom = info.getInteractiveInfoAtom();
switch(link.getType()){
case HSLFHyperlink.LINK_FIRSTSLIDE:
infoAtom.setAction(InteractiveInfoAtom.ACTION_JUMP);
infoAtom.setJump(InteractiveInfoAtom.JUMP_FIRSTSLIDE);
infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_FirstSlide);
break;
case HSLFHyperlink.LINK_LASTSLIDE:
infoAtom.setAction(InteractiveInfoAtom.ACTION_JUMP);
infoAtom.setJump(InteractiveInfoAtom.JUMP_LASTSLIDE);
infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_LastSlide);
break;
case HSLFHyperlink.LINK_NEXTSLIDE:
infoAtom.setAction(InteractiveInfoAtom.ACTION_JUMP);
infoAtom.setJump(InteractiveInfoAtom.JUMP_NEXTSLIDE);
infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_NextSlide);
break;
case HSLFHyperlink.LINK_PREVIOUSSLIDE:
infoAtom.setAction(InteractiveInfoAtom.ACTION_JUMP);
infoAtom.setJump(InteractiveInfoAtom.JUMP_PREVIOUSSLIDE);
infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_PreviousSlide);
break;
case HSLFHyperlink.LINK_URL:
infoAtom.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
infoAtom.setJump(InteractiveInfoAtom.JUMP_NONE);
infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_Url);
break;
case HSLFHyperlink.LINK_SLIDENUMBER:
infoAtom.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
infoAtom.setJump(InteractiveInfoAtom.JUMP_NONE);
infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_SlideNumber);
break;
default:
logger.log(POILogger.WARN, "Ignore unknown hyperlink type : "+link.getLabel());
break;
}
infoAtom.setHyperlinkID(link.getId());
HSLFEscherClientDataRecord cldata = getClientData(true);
cldata.addChild(info);
}
public Guide getAdjustValue(String name) {
if (name == null || !name.matches("adj([1-9]|10)?")) {
throw new IllegalArgumentException("Adjust value '"+name+"' not supported.");
@ -653,4 +606,26 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
}
}
}
@Override
public HSLFHyperlink getHyperlink(){
return _hyperlink;
}
@Override
public HSLFHyperlink createHyperlink() {
if (_hyperlink == null) {
_hyperlink = HSLFHyperlink.createHyperlink(this);
}
return _hyperlink;
}
/**
* Sets the hyperlink - used when the document is parsed
*
* @param link the hyperlink
*/
protected void setHyperlink(HSLFHyperlink link) {
_hyperlink = link;
}
}

View File

@ -1010,28 +1010,6 @@ public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagrap
return objectId;
}
/**
* Add a hyperlink to this presentation
*
* @return 0-based index of the hyperlink
*/
public int addHyperlink(HSLFHyperlink link) {
ExHyperlink ctrl = new ExHyperlink();
ExHyperlinkAtom obj = ctrl.getExHyperlinkAtom();
if(link.getType() == HSLFHyperlink.LINK_SLIDENUMBER) {
ctrl.setLinkURL(link.getAddress(), 0x30);
} else {
ctrl.setLinkURL(link.getAddress());
}
ctrl.setLinkTitle(link.getLabel());
int objectId = addToObjListAtom(ctrl);
link.setId(objectId);
obj.setNumber(objectId);
return objectId;
}
/**
* Add a embedded object to this presentation
*
@ -1104,11 +1082,7 @@ public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagrap
}
protected int addToObjListAtom(RecordContainer exObj) {
ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID);
if (lst == null) {
lst = new ExObjList();
_documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom());
}
ExObjList lst = getDocumentRecord().getExObjList(true);
ExObjListAtom objAtom = lst.getExObjListAtom();
// increment the object ID seed
int objectId = (int) objAtom.getObjectIDSeed() + 1;

View File

@ -22,7 +22,6 @@ import static org.apache.poi.hslf.record.RecordTypes.OutlineTextRefAtom;
import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@ -40,6 +39,7 @@ import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType;
import org.apache.poi.hslf.record.ColorSchemeAtom;
import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.FontCollection;
import org.apache.poi.hslf.record.InteractiveInfo;
import org.apache.poi.hslf.record.MasterTextPropAtom;
import org.apache.poi.hslf.record.OutlineTextRefAtom;
import org.apache.poi.hslf.record.PPDrawing;
@ -55,6 +55,7 @@ import org.apache.poi.hslf.record.TextCharsAtom;
import org.apache.poi.hslf.record.TextHeaderAtom;
import org.apache.poi.hslf.record.TextRulerAtom;
import org.apache.poi.hslf.record.TextSpecInfoAtom;
import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
import org.apache.poi.sl.draw.DrawPaint;
import org.apache.poi.sl.usermodel.AutoNumberingScheme;
import org.apache.poi.sl.usermodel.PaintStyle;
@ -183,7 +184,6 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
}
assert(sheet.getSlideShow() != null);
applyHyperlinks(paragraphs);
}
/**
@ -821,8 +821,21 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
*/
protected static void storeText(List<HSLFTextParagraph> paragraphs) {
fixLineEndings(paragraphs);
updateTextAtom(paragraphs);
updateStyles(paragraphs);
updateHyperlinks(paragraphs);
refreshRecords(paragraphs);
String rawText = toInternalString(getRawText(paragraphs));
for (HSLFTextParagraph p : paragraphs) {
p._dirty = false;
}
}
/**
* Set the correct text atom depending on the multibyte usage
*/
private static void updateTextAtom(List<HSLFTextParagraph> paragraphs) {
final String rawText = toInternalString(getRawText(paragraphs));
// Will it fit in a 8 bit atom?
boolean isUnicode = StringUtil.hasMultibyte(rawText);
@ -888,6 +901,16 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
}
}
}
/**
* Update paragraph and character styles - merges them when subsequential styles match
*/
private static void updateStyles(List<HSLFTextParagraph> paragraphs) {
final String rawText = toInternalString(getRawText(paragraphs));
TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;
StyleTextPropAtom styleAtom = findStyleAtomPresent(headerAtom, rawText.length());
// Update the text length for its Paragraph and Character stylings
// * reset the length, to the new string's length
// * add on +1 if the last block
@ -933,7 +956,54 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
break;
}
}
}
private static void updateHyperlinks(List<HSLFTextParagraph> paragraphs) {
TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;
RecordContainer _txtbox = headerAtom.getParentRecord();
// remove existing hyperlink records
for (Record r : _txtbox.getChildRecords()) {
if (r instanceof InteractiveInfo || r instanceof TxInteractiveInfoAtom) {
_txtbox.removeChild(r);
}
}
// now go through all the textruns and check for hyperlinks
HSLFHyperlink lastLink = null;
for (HSLFTextParagraph para : paragraphs) {
for (HSLFTextRun run : para) {
HSLFHyperlink thisLink = run.getHyperlink();
if (thisLink != null && thisLink == lastLink) {
// the hyperlink extends over this text run, increase its length
// TODO: the text run might be longer than the hyperlink
thisLink.setEndIndex(thisLink.getEndIndex()+run.getLength());
} else {
if (lastLink != null) {
InteractiveInfo info = lastLink.getInfo();
TxInteractiveInfoAtom txinfo = lastLink.getTextRunInfo();
assert(info != null && txinfo != null);
_txtbox.appendChildRecord(info);
_txtbox.appendChildRecord(txinfo);
}
}
lastLink = thisLink;
}
}
if (lastLink != null) {
InteractiveInfo info = lastLink.getInfo();
TxInteractiveInfoAtom txinfo = lastLink.getTextRunInfo();
assert(info != null && txinfo != null);
_txtbox.appendChildRecord(info);
_txtbox.appendChildRecord(txinfo);
}
}
/**
* Writes the textbox records back to the document record
*/
private static void refreshRecords(List<HSLFTextParagraph> paragraphs) {
TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;
RecordContainer _txtbox = headerAtom.getParentRecord();
if (_txtbox instanceof EscherTextboxWrapper) {
try {
((EscherTextboxWrapper) _txtbox).writeOut(null);
@ -941,10 +1011,6 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
throw new RuntimeException("failed dummy write", e);
}
}
for (HSLFTextParagraph p : paragraphs) {
p._dirty = false;
}
}
/**
@ -1270,11 +1336,37 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
for (HSLFHyperlink h : links) {
int csIdx = 0;
for (HSLFTextParagraph p : paragraphs) {
for (HSLFTextRun r : p) {
if (h.getStartIndex() <= csIdx && csIdx < h.getEndIndex()) {
r.setHyperlinkId(h.getId());
if (csIdx > h.getEndIndex()) break;
List<HSLFTextRun> runs = p.getTextRuns();
for (int rlen=0,rIdx=0; rIdx < runs.size(); csIdx+=rlen, rIdx++) {
HSLFTextRun run = runs.get(rIdx);
rlen = run.getLength();
if (csIdx < h.getEndIndex() && h.getStartIndex() < csIdx+rlen) {
String rawText = run.getRawText();
int startIdx = h.getStartIndex()-csIdx;
if (startIdx > 0) {
// hyperlink starts within current textrun
HSLFTextRun newRun = new HSLFTextRun(p);
newRun.setCharacterStyle(run.getCharacterStyle());
newRun.setText(rawText.substring(startIdx));
run.setText(rawText.substring(0, startIdx));
runs.add(rIdx+1, newRun);
rlen = startIdx;
continue;
}
int endIdx = Math.min(rlen, h.getEndIndex()-h.getStartIndex());
if (endIdx < rlen) {
// hyperlink ends before end of current textrun
HSLFTextRun newRun = new HSLFTextRun(p);
newRun.setCharacterStyle(run.getCharacterStyle());
newRun.setText(rawText.substring(0, endIdx));
run.setText(rawText.substring(endIdx));
runs.add(rIdx, newRun);
rlen = endIdx;
run = newRun;
}
run.setHyperlink(h);
}
csIdx += r.getLength();
}
}
}

View File

@ -27,10 +27,6 @@ import org.apache.poi.hslf.model.textproperties.CharFlagsTextProp;
import org.apache.poi.hslf.model.textproperties.TextProp;
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType;
import org.apache.poi.hslf.record.ExHyperlink;
import org.apache.poi.hslf.record.InteractiveInfo;
import org.apache.poi.hslf.record.InteractiveInfoAtom;
import org.apache.poi.hslf.record.Record;
import org.apache.poi.sl.draw.DrawPaint;
import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
@ -51,7 +47,7 @@ public final class HSLFTextRun implements TextRun {
private HSLFTextParagraph parentParagraph;
private String _runText = "";
private String _fontFamily;
private int hyperlinkId = -1;
private HSLFHyperlink link;
/**
* Our paragraph and character style.
@ -397,62 +393,25 @@ public final class HSLFTextRun implements TextRun {
}
/**
* Sets the associated hyperlink id - currently this is only used while parsing and
* can't be used for update a ppt
* Sets the hyperlink - used when parsing the document
*
* @param hyperlinkId the id or -1 to unset it
* @param link the hyperlink
*/
public void setHyperlinkId(int hyperlinkId) {
this.hyperlinkId = hyperlinkId;
protected void setHyperlink(HSLFHyperlink link) {
this.link = link;
}
/**
* Returns the associated hyperlink id
*
* @return the hyperlink id
*/
public int getHyperlinkId() {
return hyperlinkId;
}
@Override
public HSLFHyperlink getHyperlink() {
if (hyperlinkId == -1) {
return null;
return link;
}
@Override
public HSLFHyperlink createHyperlink() {
if (link == null) {
link = HSLFHyperlink.createHyperlink(this);
parentParagraph.setDirty();
}
ExHyperlink linkRecord = parentParagraph.getSheet().getSlideShow().getDocumentRecord().getExObjList().get(hyperlinkId);
if (linkRecord == null) {
return null;
}
InteractiveInfoAtom info = null;
for (Record r : parentParagraph.getRecords()) {
if (r instanceof InteractiveInfo) {
InteractiveInfo ii = (InteractiveInfo)r;
InteractiveInfoAtom iia = ii.getInteractiveInfoAtom();
if (iia.getHyperlinkID() == hyperlinkId) {
info = iia;
break;
}
}
}
if (info == null) {
return null;
}
// TODO: check previous/next sibling runs for same hyperlink id and return the whole string length
int startIdx = parentParagraph.getStartIdxOfTextRun(this);
HSLFHyperlink link = new HSLFHyperlink();
link.setId(hyperlinkId);
link.setType(info.getAction());
link.setLabel(linkRecord.getLinkTitle());
link.setAddress(linkRecord.getLinkURL());
link.setStartIndex(startIdx);
link.setEndIndex(startIdx+getLength());
return link;
}
}

View File

@ -35,13 +35,10 @@ import org.apache.poi.ddf.EscherTextboxRecord;
import org.apache.poi.hslf.exceptions.HSLFException;
import org.apache.poi.hslf.model.HSLFMetroShape;
import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.InteractiveInfo;
import org.apache.poi.hslf.record.InteractiveInfoAtom;
import org.apache.poi.hslf.record.OEPlaceholderAtom;
import org.apache.poi.hslf.record.PPDrawing;
import org.apache.poi.hslf.record.RoundTripHFPlaceholder12;
import org.apache.poi.hslf.record.TextHeaderAtom;
import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawTextShape;
import org.apache.poi.sl.usermodel.Insets2D;
@ -622,33 +619,6 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
return getClientDataRecord(RoundTripHFPlaceholder12.typeID);
}
/**
*
* Assigns a hyperlink to this text shape
*
* @param linkId id of the hyperlink, @see org.apache.poi.hslf.usermodel.SlideShow#addHyperlink(Hyperlink)
* @param beginIndex the beginning index, inclusive.
* @param endIndex the ending index, exclusive.
* @see org.apache.poi.hslf.usermodel.HSLFSlideShow#addHyperlink(HSLFHyperlink)
*/
public void setHyperlink(int linkId, int beginIndex, int endIndex){
//TODO validate beginIndex and endIndex and throw IllegalArgumentException
InteractiveInfo info = new InteractiveInfo();
InteractiveInfoAtom infoAtom = info.getInteractiveInfoAtom();
infoAtom.setAction(org.apache.poi.hslf.record.InteractiveInfoAtom.ACTION_HYPERLINK);
infoAtom.setHyperlinkType(org.apache.poi.hslf.record.InteractiveInfoAtom.LINK_Url);
infoAtom.setHyperlinkID(linkId);
_txtbox.appendChildRecord(info);
TxInteractiveInfoAtom txiatom = new TxInteractiveInfoAtom();
txiatom.setStartIndex(beginIndex);
txiatom.setEndIndex(endIndex);
_txtbox.appendChildRecord(txiatom);
}
@Override
public boolean isPlaceholder() {
OEPlaceholderAtom oep = getPlaceholderAtom();
@ -729,50 +699,38 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
return HSLFTextParagraph.getRawText(getTextParagraphs());
}
/**
* Returns the text contained in this text frame, which has been made safe
* for printing and other use.
*
* @return the text string for this textbox.
*/
@Override
public String getText() {
String rawText = getRawText();
return HSLFTextParagraph.toExternalString(rawText, getRunType());
}
@Override
public HSLFTextRun appendText(String text, boolean newParagraph) {
// init paragraphs
List<HSLFTextParagraph> paras = getTextParagraphs();
HSLFTextRun htr = HSLFTextParagraph.appendText(paras, text, newParagraph);
setTextId(getRawText().hashCode());
return htr;
}
// Update methods follow
@Override
public HSLFTextRun setText(String text) {
// init paragraphs
List<HSLFTextParagraph> paras = getTextParagraphs();
HSLFTextRun htr = HSLFTextParagraph.setText(paras, text);
setTextId(getRawText().hashCode());
return htr;
}
/**
* Adds the supplied text onto the end of the TextParagraphs,
* creating a new RichTextRun for it to sit in.
*
* @param text the text string used by this object.
*/
public HSLFTextRun appendText(String text, boolean newParagraph) {
// init paragraphs
List<HSLFTextParagraph> paras = getTextParagraphs();
return HSLFTextParagraph.appendText(paras, text, newParagraph);
}
@Override
public HSLFTextRun setText(String text) {
// init paragraphs
List<HSLFTextParagraph> paras = getTextParagraphs();
HSLFTextRun htr = HSLFTextParagraph.setText(paras, text);
setTextId(text.hashCode());
return htr;
}
/**
* Saves the modified paragraphs/textrun to the records.
* Also updates the styles to the correct text length.
*/
protected void storeText() {
List<HSLFTextParagraph> paras = getTextParagraphs();
HSLFTextParagraph.storeText(paras);
}
// Accesser methods follow
/**
* Saves the modified paragraphs/textrun to the records.
* Also updates the styles to the correct text length.
*/
protected void storeText() {
List<HSLFTextParagraph> paras = getTextParagraphs();
HSLFTextParagraph.storeText(paras);
}
/**
* Returns the array of all hyperlinks in this text run

View File

@ -22,16 +22,25 @@ import static org.apache.poi.hslf.usermodel.HSLFTextParagraph.toExternalString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.POIDataSamples;
import org.apache.poi.hslf.usermodel.*;
import org.apache.poi.hslf.HSLFTestDataSamples;
import org.apache.poi.hslf.record.InteractiveInfoAtom;
import org.apache.poi.hslf.usermodel.HSLFHyperlink;
import org.apache.poi.hslf.usermodel.HSLFSlide;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.HSLFTextBox;
import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
import org.apache.poi.hslf.usermodel.HSLFTextRun;
import org.apache.poi.sl.usermodel.Hyperlink;
import org.junit.Test;
/**
* Test Hyperlink.
*
* @author Yegor Kozlov
*/
public final class TestHyperlink {
private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
@ -53,8 +62,7 @@ public final class TestHyperlink {
"In addition, its notes has one link";
assertEquals(expected, rawText);
List<HSLFHyperlink> links = HSLFHyperlink.find(para);
assertNotNull(links);
List<HSLFHyperlink> links = findHyperlinks(para);
assertEquals(2, links.size());
assertEquals("http://jakarta.apache.org/poi/", links.get(0).getLabel());
@ -73,12 +81,92 @@ public final class TestHyperlink {
"Jakarta HSSF";
assertEquals(expected, rawText);
links = HSLFHyperlink.find(para);
links.clear();
links = findHyperlinks(para);
assertNotNull(links);
assertEquals(1, links.size());
assertEquals("Open Jakarta POI HSSF module test ", links.get(0).getLabel());
assertEquals("http://jakarta.apache.org/poi/hssf/", links.get(0).getAddress());
assertEquals("Jakarta HSSF", rawText.substring(links.get(0).getStartIndex(), links.get(0).getEndIndex()-1));
ppt.close();
}
@Test
public void bug47291() throws IOException {
HSLFSlideShow ppt1 = new HSLFSlideShow();
HSLFSlide slide1 = ppt1.createSlide();
HSLFTextRun r1 = slide1.createTextBox().setText("page1");
HSLFHyperlink hl1 = r1.createHyperlink();
hl1.linkToEmail("dev@poi.apache.org");
HSLFTextRun r2 = ppt1.createSlide().createTextBox().setText("page2");
HSLFHyperlink hl2 = r2.createHyperlink();
hl2.linkToLastSlide();
HSLFSlide sl1 = ppt1.createSlide();
HSLFTextBox tb1 = sl1.createTextBox();
tb1.setAnchor(new Rectangle2D.Double(100,100,100,100));
tb1.appendText("text1 ", false);
HSLFTextRun r3 = tb1.appendText("lin\u000bk", false);
tb1.appendText(" text2", false);
HSLFHyperlink hl3 = r3.createHyperlink();
hl3.linkToSlide(slide1);
HSLFTextRun r4 = ppt1.createSlide().createTextBox().setText("page4");
HSLFHyperlink hl4 = r4.createHyperlink();
hl4.linkToUrl("http://poi.apache.org");
HSLFTextBox tb5 = ppt1.createSlide().createTextBox();
tb5.setText("page5");
HSLFHyperlink hl5 = tb5.createHyperlink();
hl5.linkToFirstSlide();
HSLFSlideShow ppt2 = HSLFTestDataSamples.writeOutAndReadBack(ppt1);
ppt1.close();
List<HSLFSlide> slides = ppt2.getSlides();
tb1 = (HSLFTextBox)slides.get(0).getShapes().get(0);
hl1 = tb1.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(hl1);
assertEquals("dev@poi.apache.org", hl1.getLabel());
assertEquals(Hyperlink.LINK_EMAIL, hl1.getType());
HSLFTextBox tb2 = (HSLFTextBox)slides.get(1).getShapes().get(0);
hl2 = tb2.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(hl2);
assertEquals(InteractiveInfoAtom.LINK_LastSlide, hl2.getInfo().getInteractiveInfoAtom().getHyperlinkType());
assertEquals(Hyperlink.LINK_DOCUMENT, hl2.getType());
HSLFTextBox tb3 = (HSLFTextBox)slides.get(2).getShapes().get(0);
hl3 = tb3.getTextParagraphs().get(0).getTextRuns().get(1).getHyperlink();
assertNotNull(hl3);
assertEquals(ppt2.getSlides().get(0)._getSheetNumber(), Integer.parseInt(hl3.getAddress().split(",")[0]));
assertEquals(Hyperlink.LINK_DOCUMENT, hl3.getType());
HSLFTextBox tb4 = (HSLFTextBox)slides.get(3).getShapes().get(0);
hl4 = tb4.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(hl4);
assertEquals("http://poi.apache.org", hl4.getLabel());
assertEquals(Hyperlink.LINK_URL, hl4.getType());
tb5 = (HSLFTextBox)slides.get(4).getShapes().get(0);
hl5 = tb5.getHyperlink();
assertNotNull(hl5);
assertEquals(InteractiveInfoAtom.LINK_FirstSlide, hl5.getInfo().getInteractiveInfoAtom().getHyperlinkType());
assertEquals(Hyperlink.LINK_DOCUMENT, hl5.getType());
ppt2.close();
}
private static List<HSLFHyperlink> findHyperlinks(List<HSLFTextParagraph> paras) {
List<HSLFHyperlink> links = new ArrayList<HSLFHyperlink>();
for (HSLFTextParagraph p : paras) {
for (HSLFTextRun r : p) {
HSLFHyperlink hl = r.getHyperlink();
if (hl != null) {
links.add(hl);
}
}
}
return links;
}
}

View File

@ -38,7 +38,7 @@ public class TestExObjList extends TestCase {
// Get the document
Document doc = ss.getDocumentRecord();
// Get the ExObjList
ExObjList exObjList = doc.getExObjList();
ExObjList exObjList = doc.getExObjList(false);
assertNotNull(exObjList);
assertEquals(1033l, exObjList.getRecordType());