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

View File

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

View File

@ -48,7 +48,7 @@ public class Tutorial6 {
XSLFTextRun r2 = shape2.addNewTextParagraph().addNewTextRun(); XSLFTextRun r2 = shape2.addNewTextParagraph().addNewTextRun();
XSLFHyperlink link2 = r2.createHyperlink(); XSLFHyperlink link2 = r2.createHyperlink();
r2.setText("Go to the second slide"); // visible text 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 * Returns an ImageRenderer for the PictureData
* *
* @param graphics * @param graphics
* @return * @return the image renderer
*/ */
public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) { public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) {
ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER); ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER);

View File

@ -20,5 +20,59 @@ package org.apache.poi.sl.usermodel;
/** /**
* A PowerPoint hyperlink * 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 * the solid fill attribute from the underlying implementation
*/ */
void setFillColor(Color color); 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
* *
* @return the associated hyperlink or null if no hyperlink was set * @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 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. * Sets (overwrites) the current text.
* Uses the properties of the first paragraph / textrun. * Uses the properties of the first paragraph / textrun.
@ -129,6 +139,18 @@ public interface TextShape<
* @return the last text run of the - potential split - text * @return the last text run of the - potential split - text
*/ */
TextRun setText(String text); 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 * @return the TextParagraphs for this text box

View File

@ -16,20 +16,23 @@
==================================================================== */ ==================================================================== */
package org.apache.poi.xslf.usermodel; 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.PackageRelationship;
import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.sl.usermodel.Hyperlink; import org.apache.poi.sl.usermodel.Hyperlink;
import org.apache.poi.sl.usermodel.Slide;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink; import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink;
import java.net.URI; public class XSLFHyperlink implements Hyperlink<XSLFShape,XSLFTextParagraph> {
final XSLFSheet _sheet;
public class XSLFHyperlink implements Hyperlink {
final XSLFTextRun _r;
final CTHyperlink _link; final CTHyperlink _link;
XSLFHyperlink(CTHyperlink link, XSLFTextRun r){ XSLFHyperlink(CTHyperlink link, XSLFSheet sheet){
_r = r; _sheet = sheet;
_link = link; _link = link;
} }
@ -39,24 +42,27 @@ public class XSLFHyperlink implements Hyperlink {
} }
@Override @Override
public void setAddress(String address){ public void setAddress(String address) {
XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet(); linkToUrl(address);
PackageRelationship rel =
sheet.getPackagePart().
addExternalRelationship(address, XSLFRelation.HYPERLINK.getRelation());
_link.setId(rel.getId());
} }
@Override @Override
public String getAddress() { 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 @Override
public String getLabel() { public String getLabel() {
return _link.getTooltip(); return _link.getTooltip();
} }
@Override @Override
public void setLabel(String label) { public void setLabel(String label) {
_link.setTooltip(label); _link.setTooltip(label);
@ -64,28 +70,88 @@ public class XSLFHyperlink implements Hyperlink {
@Override @Override
public int getType() { public int getType() {
// TODO: currently this just returns nonsense String action = _link.getAction();
if ("ppaction://hlinksldjump".equals(_link.getAction())) { if (action == null) {
action = "";
}
if (action.equals("ppaction://hlinksldjump") || action.startsWith("ppaction://hlinkshowjump")) {
return LINK_DOCUMENT; 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){ @Override
XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet(); 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 = PackageRelationship rel =
sheet.getPackagePart(). thisPP.addRelationship(otherPPN, TargetMode.INTERNAL, XSLFRelation.SLIDE.getRelation());
addRelationship(slide.getPackagePart().getPartName(),
TargetMode.INTERNAL,
XSLFRelation.SLIDE.getRelation());
_link.setId(rel.getId()); _link.setId(rel.getId());
_link.setAction("ppaction://hlinksldjump"); _link.setAction("ppaction://hlinksldjump");
} }
@Internal @Override
public URI getTargetURI(){ public void linkToNextSlide() {
XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet(); linkToRelativeSlide("nextslide");
String id = _link.getId();
return sheet.getPackagePart().getRelationship(id).getTargetURI();
} }
}
@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.DecorationShape;
import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize; import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize;
import org.apache.poi.sl.usermodel.PaintStyle; 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.PaintStyle.SolidPaint;
import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.sl.usermodel.ShapeType; import org.apache.poi.sl.usermodel.ShapeType;
import org.apache.poi.sl.usermodel.SimpleShape; import org.apache.poi.sl.usermodel.SimpleShape;
import org.apache.poi.sl.usermodel.StrokeStyle; 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.CTLineEndProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties; import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTLineStyleList; 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.CTOuterShadowEffect;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D; import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
@ -923,4 +924,23 @@ public abstract class XSLFSimpleShape extends XSLFShape
public void setPlaceholder(Placeholder placeholder) { public void setPlaceholder(Placeholder placeholder) {
super.setPlaceholder(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(); return "[" + getClass() + "]" + getRawText();
} }
@Override
public XSLFHyperlink createHyperlink(){ public XSLFHyperlink createHyperlink(){
XSLFHyperlink link = new XSLFHyperlink(_r.getRPr().addNewHlinkClick(), this); XSLFHyperlink hl = getHyperlink();
return link; if (hl == null) {
hl = new XSLFHyperlink(_r.getRPr().addNewHlinkClick(), _p.getParentShape().getSheet());
}
return hl;
} }
@Override @Override
public XSLFHyperlink getHyperlink(){ public XSLFHyperlink getHyperlink(){
if(!_r.getRPr().isSetHlinkClick()) return null; if(!_r.getRPr().isSetHlinkClick()) return null;
return new XSLFHyperlink(_r.getRPr().getHlinkClick(), _p.getParentShape().getSheet());
return new XSLFHyperlink(_r.getRPr().getHlinkClick(), this);
} }
private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){ private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){

View File

@ -71,10 +71,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape
return getTextParagraphs().iterator(); return getTextParagraphs().iterator();
} }
/** @Override
*
* @return text contained within this shape or empty string
*/
public String getText() { public String getText() {
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
for (XSLFTextParagraph p : _paragraphs) { for (XSLFTextParagraph p : _paragraphs) {
@ -95,50 +92,76 @@ public abstract class XSLFTextShape extends XSLFSimpleShape
@Override @Override
public XSLFTextRun setText(String text) { 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; CTTextParagraphProperties pPr = null;
CTTextCharacterProperties rPr = null; CTTextCharacterProperties rPr = null;
if (!_paragraphs.isEmpty()) {
XSLFTextParagraph p0 = _paragraphs.get(0); boolean firstPara;
pPr = p0.getXmlObject().getPPr(); XSLFTextParagraph para;
if (!p0.getTextRuns().isEmpty()) { if (_paragraphs.isEmpty()) {
XSLFTextRun r0 = p0.getTextRuns().get(0); 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(); 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 run = null;
XSLFTextRun r = null; for (String lineTxt : text.split("\\r\\n?|\\n")) {
for (String paraText : text.split("\\r\\n?|\\n")) { if (!firstPara) {
XSLFTextParagraph para = addNewTextParagraph(); if (para != null && para.getXmlObject().isSetEndParaRPr()) {
if (pPr != null) { para.getXmlObject().unsetEndParaRPr();
para.getXmlObject().setPPr(pPr); }
para = addNewTextParagraph();
if (pPr != null) {
para.getXmlObject().setPPr(pPr);
}
} }
boolean first = true; boolean firstRun = true;
for (String runText : paraText.split("[\u000b]")) { for (String runText : lineTxt.split("[\u000b]")) {
if (!first) { if (!firstRun) {
para.addLineBreak(); para.addLineBreak();
} }
r = para.addNewTextRun(); run = para.addNewTextRun();
r.setText(runText); run.setText(runText);
if (rPr != null) { 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 assert(run != null);
for (int i = cntPs-1; i >= 0; i--) { return run;
txBody.removeP(i);
}
return r;
} }
@Override @Override

View File

@ -19,19 +19,17 @@ package org.apache.poi.xslf.usermodel;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import java.awt.geom.Rectangle2D;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.util.List; import java.util.List;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.sl.usermodel.Hyperlink;
import org.apache.poi.xslf.XSLFTestDataSamples; import org.apache.poi.xslf.XSLFTestDataSamples;
import org.junit.Test; import org.junit.Test;
/**
* @author Yegor Kozlov
*/
public class TestXSLFHyperlink { public class TestXSLFHyperlink {
@Test @Test
@ -45,19 +43,19 @@ public class TestXSLFHyperlink {
assertEquals("Web Page", cell1.getText()); assertEquals("Web Page", cell1.getText());
XSLFHyperlink link1 = cell1.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink(); XSLFHyperlink link1 = cell1.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(link1); 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); XSLFTableCell cell2 = tbl.getRows().get(2).getCells().get(0);
assertEquals("Place in this document", cell2.getText()); assertEquals("Place in this document", cell2.getText());
XSLFHyperlink link2 = cell2.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink(); XSLFHyperlink link2 = cell2.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(link2); 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); XSLFTableCell cell3 = tbl.getRows().get(3).getCells().get(0);
assertEquals("Email", cell3.getText()); assertEquals("Email", cell3.getText());
XSLFHyperlink link3 = cell3.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink(); XSLFHyperlink link3 = cell3.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();
assertNotNull(link3); 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(); ppt.close();
} }
@ -75,7 +73,7 @@ public class TestXSLFHyperlink {
r1.setText("Web Page"); r1.setText("Web Page");
XSLFHyperlink link1 = r1.createHyperlink(); XSLFHyperlink link1 = r1.createHyperlink();
link1.setAddress("http://poi.apache.org/"); 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()); assertEquals(numRel + 1, slide1.getPackagePart().getRelationships().size());
String id1 = link1.getXmlObject().getId(); String id1 = link1.getXmlObject().getId();
@ -90,8 +88,8 @@ public class TestXSLFHyperlink {
XSLFTextRun r2 = sh2.addNewTextParagraph().addNewTextRun(); XSLFTextRun r2 = sh2.addNewTextParagraph().addNewTextRun();
r2.setText("Place in this document"); r2.setText("Place in this document");
XSLFHyperlink link2 = r2.createHyperlink(); XSLFHyperlink link2 = r2.createHyperlink();
link2.setAddress(slide2); link2.linkToSlide(slide2);
assertEquals(URI.create("/ppt/slides/slide2.xml"), link2.getTargetURI()); assertEquals("/ppt/slides/slide2.xml", link2.getAddress());
assertEquals(numRel + 2, slide1.getPackagePart().getRelationships().size()); assertEquals(numRel + 2, slide1.getPackagePart().getRelationships().size());
String id2 = link2.getXmlObject().getId(); String id2 = link2.getXmlObject().getId();
@ -104,4 +102,76 @@ public class TestXSLFHyperlink {
ppt.close(); 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){ if(_exEmbed == null){
HSLFSlideShow ppt = getSheet().getSlideShow(); HSLFSlideShow ppt = getSheet().getSlideShow();
ExObjList lst = ppt.getDocumentRecord().getExObjList(); ExObjList lst = ppt.getDocumentRecord().getExObjList(false);
if(lst == null){ if(lst == null){
logger.log(POILogger.WARN, "ExObjList not found"); logger.log(POILogger.WARN, "ExObjList not found");
return null; return null;

View File

@ -46,22 +46,33 @@ public final class Document extends PositionDependentRecordContainer
* Returns the DocumentAtom of this Document * Returns the DocumentAtom of this Document
*/ */
public DocumentAtom getDocumentAtom() { return documentAtom; } public DocumentAtom getDocumentAtom() { return documentAtom; }
/** /**
* Returns the Environment of this Notes, which lots of * 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; } public Environment getEnvironment() { return environment; }
/** /**
* Returns the PPDrawingGroup, which holds an Escher Structure * 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; } public PPDrawingGroup getPPDrawingGroup() { return ppDrawing; }
/** /**
* Returns the ExObjList, which holds the references to * Returns the ExObjList, which holds the references to
* external objects used in the slides. This may be null, if * external objects used in the slides. This may be null, if
* there are no external references. * 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 * Returns all the SlideListWithTexts that are defined for

View File

@ -67,9 +67,9 @@ public class ExHyperlink extends RecordContainer {
linkDetailsB.setText(url); linkDetailsB.setText(url);
} }
} }
public void setLinkURL(String url, int options) {
public void setLinkOptions(int options) {
if(linkDetailsB != null) { if(linkDetailsB != null) {
linkDetailsB.setText(url);
linkDetailsB.setOptions(options); linkDetailsB.setOptions(options);
} }
} }
@ -79,7 +79,7 @@ public class ExHyperlink extends RecordContainer {
linkDetailsA.setText(title); linkDetailsA.setText(title);
} }
} }
/** /**
* Get the link details (field A) * Get the link details (field A)
*/ */

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import org.apache.poi.hslf.record.ExHyperlink; 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.ExObjList;
import org.apache.poi.hslf.record.HSLFEscherClientDataRecord; import org.apache.poi.hslf.record.HSLFEscherClientDataRecord;
import org.apache.poi.hslf.record.InteractiveInfo; 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.Record;
import org.apache.poi.hslf.record.TxInteractiveInfoAtom; import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
import org.apache.poi.sl.usermodel.Hyperlink; import org.apache.poi.sl.usermodel.Hyperlink;
import org.apache.poi.sl.usermodel.Slide;
/** /**
* Represents a hyperlink in a PowerPoint document * Represents a hyperlink in a PowerPoint document
*/ */
public final class HSLFHyperlink implements Hyperlink { public final class HSLFHyperlink implements Hyperlink<HSLFShape,HSLFTextParagraph> {
public static final byte LINK_NEXTSLIDE = InteractiveInfoAtom.LINK_NextSlide; private final ExHyperlink exHyper;
public static final byte LINK_PREVIOUSSLIDE = InteractiveInfoAtom.LINK_PreviousSlide; private final InteractiveInfo info;
public static final byte LINK_FIRSTSLIDE = InteractiveInfoAtom.LINK_FirstSlide; private TxInteractiveInfoAtom txinfo;
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;
private int id=-1; protected HSLFHyperlink(ExHyperlink exHyper, InteractiveInfo info) {
private int type; this.info = info;
private String address; this.exHyper = exHyper;
private String label; }
private int startIndex, endIndex;
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;
}
/** /**
* Gets the type of the hyperlink action. * Gets the type of the hyperlink action.
@ -58,70 +130,130 @@ public final class HSLFHyperlink implements Hyperlink {
*/ */
@Override @Override
public int getType() { public int getType() {
return type; switch (info.getInteractiveInfoAtom().getHyperlinkType()) {
} case InteractiveInfoAtom.LINK_Url:
return (exHyper.getLinkURL().startsWith("mailto:")) ? LINK_EMAIL : LINK_URL;
public void setType(int val) { case InteractiveInfoAtom.LINK_NextSlide:
type = val; case InteractiveInfoAtom.LINK_PreviousSlide:
switch(type){ case InteractiveInfoAtom.LINK_FirstSlide:
case LINK_NEXTSLIDE: case InteractiveInfoAtom.LINK_LastSlide:
label = "NEXT"; case InteractiveInfoAtom.LINK_SlideNumber:
address = "1,-1,NEXT"; return LINK_DOCUMENT;
break; case InteractiveInfoAtom.LINK_CustomShow:
case LINK_PREVIOUSSLIDE: case InteractiveInfoAtom.LINK_OtherPresentation:
label = "PREV"; case InteractiveInfoAtom.LINK_OtherFile:
address = "1,-1,PREV"; return LINK_FILE;
break; default:
case LINK_FIRSTSLIDE: case InteractiveInfoAtom.LINK_NULL:
label = "FIRST"; return -1;
address = "1,-1,FIRST";
break;
case LINK_LASTSLIDE:
label = "LAST";
address = "1,-1,LAST";
break;
case LINK_SLIDENUMBER:
break;
default:
label = "";
address = "";
break;
} }
} }
@Override @Override
public String getAddress() { public void linkToEmail(String emailAddress) {
return address; 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) { @Override
String href = slide._getSheetNumber() + ","+slide.getSlideNumber()+",Slide " + slide.getSlideNumber(); public void linkToUrl(String url) {
setAddress(href);; InteractiveInfoAtom iia = info.getInteractiveInfoAtom();
setLabel("Slide " + slide.getSlideNumber()); iia.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
setType(HSLFHyperlink.LINK_SLIDENUMBER); 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 @Override
public void setAddress(String str) { public void setAddress(String str) {
address = str; exHyper.setLinkURL(str);
} }
public int getId() { public int getId() {
return id; return exHyper.getExHyperlinkAtom().getNumber();
}
public void setId(int id) {
this.id = id;
} }
@Override @Override
public String getLabel() { public String getLabel() {
return label; return exHyper.getLinkTitle();
} }
@Override @Override
public void setLabel(String str) { public void setLabel(String label) {
label = str; exHyper.setLinkTitle(label);
} }
/** /**
@ -130,7 +262,7 @@ public final class HSLFHyperlink implements Hyperlink {
* @return the beginning character position * @return the beginning character position
*/ */
public int getStartIndex() { public int getStartIndex() {
return startIndex; return (txinfo == null) ? -1 : txinfo.getStartIndex();
} }
/** /**
@ -139,16 +271,18 @@ public final class HSLFHyperlink implements Hyperlink {
* @param startIndex the beginning character position * @param startIndex the beginning character position
*/ */
public void setStartIndex(int startIndex) { public void setStartIndex(int startIndex) {
this.startIndex = startIndex; if (txinfo != null) {
txinfo.setStartIndex(startIndex);
}
} }
/** /**
* Gets the ending character position * Gets the ending character position
* *
* @return the ending character position * @return the ending character position
*/ */
public int getEndIndex() { public int getEndIndex() {
return endIndex; return (txinfo == null) ? -1 : txinfo.getEndIndex();
} }
/** /**
@ -157,9 +291,11 @@ public final class HSLFHyperlink implements Hyperlink {
* @param endIndex the ending character position * @param endIndex the ending character position
*/ */
public void setEndIndex(int endIndex) { public void setEndIndex(int endIndex) {
this.endIndex = endIndex; if (txinfo != null) {
txinfo.setEndIndex(endIndex);
}
} }
/** /**
* Find hyperlinks in a text shape * Find hyperlinks in a text shape
* *
@ -177,15 +313,15 @@ public final class HSLFHyperlink implements Hyperlink {
* @return found hyperlinks * @return found hyperlinks
*/ */
@SuppressWarnings("resource") @SuppressWarnings("resource")
public static List<HSLFHyperlink> find(List<HSLFTextParagraph> paragraphs){ protected static List<HSLFHyperlink> find(List<HSLFTextParagraph> paragraphs){
List<HSLFHyperlink> lst = new ArrayList<HSLFHyperlink>(); List<HSLFHyperlink> lst = new ArrayList<HSLFHyperlink>();
if (paragraphs == null || paragraphs.isEmpty()) return lst; if (paragraphs == null || paragraphs.isEmpty()) return lst;
HSLFTextParagraph firstPara = paragraphs.get(0); HSLFTextParagraph firstPara = paragraphs.get(0);
HSLFSlideShow ppt = firstPara.getSheet().getSlideShow(); HSLFSlideShow ppt = firstPara.getSheet().getSlideShow();
//document-level container which stores info about all links in a presentation //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) { if (exobj != null) {
Record[] records = firstPara.getRecords(); Record[] records = firstPara.getRecords();
find(Arrays.asList(records), exobj, lst); find(Arrays.asList(records), exobj, lst);
@ -201,10 +337,10 @@ public final class HSLFHyperlink implements Hyperlink {
* @return found hyperlink or <code>null</code> * @return found hyperlink or <code>null</code>
*/ */
@SuppressWarnings("resource") @SuppressWarnings("resource")
public static HSLFHyperlink find(HSLFShape shape){ protected static HSLFHyperlink find(HSLFShape shape){
HSLFSlideShow ppt = shape.getSheet().getSlideShow(); HSLFSlideShow ppt = shape.getSheet().getSlideShow();
//document-level container which stores info about all links in a presentation //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); HSLFEscherClientDataRecord cldata = shape.getClientData(false);
if (exobj != null && cldata != null) { if (exobj != null && cldata != null) {
@ -228,16 +364,12 @@ public final class HSLFHyperlink implements Hyperlink {
InteractiveInfo hldr = (InteractiveInfo)r; InteractiveInfo hldr = (InteractiveInfo)r;
InteractiveInfoAtom info = hldr.getInteractiveInfoAtom(); InteractiveInfoAtom info = hldr.getInteractiveInfoAtom();
int id = info.getHyperlinkID(); int id = info.getHyperlinkID();
ExHyperlink linkRecord = exobj.get(id); ExHyperlink exHyper = exobj.get(id);
if (linkRecord == null) { if (exHyper == null) {
continue; continue;
} }
HSLFHyperlink link = new HSLFHyperlink(); HSLFHyperlink link = new HSLFHyperlink(exHyper, hldr);
link.setId(id);
link.setType(info.getAction());
link.setLabel(linkRecord.getLinkTitle());
link.setAddress(linkRecord.getLinkURL());
out.add(link); out.add(link);
if (iter.hasNext()) { if (iter.hasNext()) {
@ -246,9 +378,7 @@ public final class HSLFHyperlink implements Hyperlink {
iter.previous(); iter.previous();
continue; continue;
} }
TxInteractiveInfoAtom txinfo = (TxInteractiveInfoAtom)r; link.setTextRunInfo((TxInteractiveInfoAtom)r);
link.setStartIndex(txinfo.getStartIndex());
link.setEndIndex(txinfo.getEndIndex());
} }
} }
} }

View File

@ -28,7 +28,6 @@ import org.apache.poi.ddf.EscherChildAnchorRecord;
import org.apache.poi.ddf.EscherClientAnchorRecord; import org.apache.poi.ddf.EscherClientAnchorRecord;
import org.apache.poi.ddf.EscherColorRef; import org.apache.poi.ddf.EscherColorRef;
import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherOptRecord;
import org.apache.poi.ddf.EscherProperties; import org.apache.poi.ddf.EscherProperties;
import org.apache.poi.ddf.EscherProperty; import org.apache.poi.ddf.EscherProperty;
import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ddf.EscherRecord;
@ -60,8 +59,6 @@ import org.apache.poi.util.Units;
* in points (72 points = 1 inch). * in points (72 points = 1 inch).
* </p> * </p>
* <p> * <p>
*
* @author Yegor Kozlov
*/ */
public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> { public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
@ -89,7 +86,7 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
* Fill * Fill
*/ */
protected HSLFFill _fill; protected HSLFFill _fill;
/** /**
* Create a Shape object. This constructor is used when an existing Shape is read from from a PowerPoint document. * Create a Shape object. This constructor is used when an existing Shape is read from from a PowerPoint document.
* *
@ -445,16 +442,6 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
return getFill().getFillStyle(); 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){ public void draw(Graphics2D graphics){
logger.log(POILogger.INFO, "Rendering " + getShapeName()); 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; if (trs == null) return;
for (List<HSLFTextParagraph> ltp : trs) { for (List<HSLFTextParagraph> ltp : trs) {
HSLFTextParagraph.supplySheet(ltp, this); 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(); EscherContainerRecord sp = (EscherContainerRecord) it.next();
HSLFShape sh = HSLFShapeFactory.createShape(sp, null); HSLFShape sh = HSLFShapeFactory.createShape(sp, null);
sh.setSheet(this); sh.setSheet(this);
if (sh instanceof HSLFSimpleShape) {
HSLFHyperlink link = HSLFHyperlink.find(sh);
if (link != null) {
((HSLFSimpleShape)sh).setHyperlink(link);
}
}
shapeList.add(sh); 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.ddf.EscherSpRecord;
import org.apache.poi.hslf.exceptions.HSLFException; import org.apache.poi.hslf.exceptions.HSLFException;
import org.apache.poi.hslf.record.HSLFEscherClientDataRecord; 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.OEPlaceholderAtom;
import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.RoundTripHFPlaceholder12; 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; 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. * 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); 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) { public Guide getAdjustValue(String name) {
if (name == null || !name.matches("adj([1-9]|10)?")) { if (name == null || !name.matches("adj([1-9]|10)?")) {
throw new IllegalArgumentException("Adjust value '"+name+"' not supported."); 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; 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 * Add a embedded object to this presentation
* *
@ -1104,11 +1082,7 @@ public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagrap
} }
protected int addToObjListAtom(RecordContainer exObj) { protected int addToObjListAtom(RecordContainer exObj) {
ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID); ExObjList lst = getDocumentRecord().getExObjList(true);
if (lst == null) {
lst = new ExObjList();
_documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom());
}
ExObjListAtom objAtom = lst.getExObjListAtom(); ExObjListAtom objAtom = lst.getExObjListAtom();
// increment the object ID seed // increment the object ID seed
int objectId = (int) objAtom.getObjectIDSeed() + 1; 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.awt.Color;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; 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.ColorSchemeAtom;
import org.apache.poi.hslf.record.EscherTextboxWrapper; import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.FontCollection; 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.MasterTextPropAtom;
import org.apache.poi.hslf.record.OutlineTextRefAtom; import org.apache.poi.hslf.record.OutlineTextRefAtom;
import org.apache.poi.hslf.record.PPDrawing; 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.TextHeaderAtom;
import org.apache.poi.hslf.record.TextRulerAtom; import org.apache.poi.hslf.record.TextRulerAtom;
import org.apache.poi.hslf.record.TextSpecInfoAtom; 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.draw.DrawPaint;
import org.apache.poi.sl.usermodel.AutoNumberingScheme; import org.apache.poi.sl.usermodel.AutoNumberingScheme;
import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle;
@ -102,7 +103,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
private StyleTextProp9Atom styleTextProp9Atom; private StyleTextProp9Atom styleTextProp9Atom;
private boolean _dirty = false; private boolean _dirty = false;
private final List<HSLFTextParagraph> parentList; private final List<HSLFTextParagraph> parentList;
/** /**
@ -162,14 +163,14 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
* Setting a master style reference * Setting a master style reference
* *
* @param paragraphStyle the master style reference * @param paragraphStyle the master style reference
* *
* @since POI 3.14-Beta1 * @since POI 3.14-Beta1
*/ */
@Internal @Internal
/* package */ void setMasterStyleReference(TextPropCollection paragraphStyle) { /* package */ void setMasterStyleReference(TextPropCollection paragraphStyle) {
_paragraphStyle = paragraphStyle; _paragraphStyle = paragraphStyle;
} }
/** /**
* Supply the Sheet we belong to, which might have an assigned SlideShow * Supply the Sheet we belong to, which might have an assigned SlideShow
* Also passes it on to our child RichTextRuns * Also passes it on to our child RichTextRuns
@ -183,9 +184,8 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
} }
assert(sheet.getSlideShow() != null); assert(sheet.getSlideShow() != null);
applyHyperlinks(paragraphs);
} }
/** /**
* Supply the Sheet we belong to, which might have an assigned SlideShow * Supply the Sheet we belong to, which might have an assigned SlideShow
* Also passes it on to our child RichTextRuns * Also passes it on to our child RichTextRuns
@ -519,7 +519,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
} }
} }
} }
@Override @Override
public HSLFTextShape getParentShape() { public HSLFTextShape getParentShape() {
return _parentShape; return _parentShape;
@ -603,12 +603,12 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
if (_runs.isEmpty()) { if (_runs.isEmpty()) {
return null; return null;
} }
SolidPaint sp = _runs.get(0).getFontColor(); SolidPaint sp = _runs.get(0).getFontColor();
if(sp == null) { if(sp == null) {
return null; return null;
} }
return DrawPaint.applyColorTransform(sp.getSolidColor()); return DrawPaint.applyColorTransform(sp.getSolidColor());
} }
@ -709,7 +709,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
* Fetch the value of the given Paragraph related TextProp. Returns null if * Fetch the value of the given Paragraph related TextProp. Returns null if
* that TextProp isn't present. If the TextProp isn't present, the value * that TextProp isn't present. If the TextProp isn't present, the value
* from the appropriate Master Sheet will apply. * from the appropriate Master Sheet will apply.
* *
* The propName can be a comma-separated list, in case multiple equivalent values * The propName can be a comma-separated list, in case multiple equivalent values
* are queried * are queried
*/ */
@ -733,12 +733,12 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
} }
boolean isChar = props.getTextPropType() == TextPropType.character; boolean isChar = props.getTextPropType() == TextPropType.character;
for (String pn : propNames) { for (String pn : propNames) {
TextProp prop = master.getStyleAttribute(txtype, paragraph.getIndentLevel(), pn, isChar); TextProp prop = master.getStyleAttribute(txtype, paragraph.getIndentLevel(), pn, isChar);
if (prop != null) return prop; if (prop != null) return prop;
} }
return null; return null;
} }
@ -821,8 +821,21 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
*/ */
protected static void storeText(List<HSLFTextParagraph> paragraphs) { protected static void storeText(List<HSLFTextParagraph> paragraphs) {
fixLineEndings(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? // Will it fit in a 8 bit atom?
boolean isUnicode = StringUtil.hasMultibyte(rawText); 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 // Update the text length for its Paragraph and Character stylings
// * reset the length, to the new string's length // * reset the length, to the new string's length
// * add on +1 if the last block // * add on +1 if the last block
@ -933,7 +956,54 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
break; 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) { if (_txtbox instanceof EscherTextboxWrapper) {
try { try {
((EscherTextboxWrapper) _txtbox).writeOut(null); ((EscherTextboxWrapper) _txtbox).writeOut(null);
@ -941,10 +1011,6 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
throw new RuntimeException("failed dummy write", e); throw new RuntimeException("failed dummy write", e);
} }
} }
for (HSLFTextParagraph p : paragraphs) {
p._dirty = false;
}
} }
/** /**
@ -1043,7 +1109,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
} }
return sb.toString(); return sb.toString();
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@ -1266,20 +1332,46 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
protected static void applyHyperlinks(List<HSLFTextParagraph> paragraphs) { protected static void applyHyperlinks(List<HSLFTextParagraph> paragraphs) {
List<HSLFHyperlink> links = HSLFHyperlink.find(paragraphs); List<HSLFHyperlink> links = HSLFHyperlink.find(paragraphs);
for (HSLFHyperlink h : links) { for (HSLFHyperlink h : links) {
int csIdx = 0; int csIdx = 0;
for (HSLFTextParagraph p : paragraphs) { for (HSLFTextParagraph p : paragraphs) {
for (HSLFTextRun r : p) { if (csIdx > h.getEndIndex()) break;
if (h.getStartIndex() <= csIdx && csIdx < h.getEndIndex()) { List<HSLFTextRun> runs = p.getTextRuns();
r.setHyperlinkId(h.getId()); 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();
} }
} }
} }
} }
protected static void applyCharacterStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> charStyles) { protected static void applyCharacterStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> charStyles) {
int paraIdx = 0, runIdx = 0; int paraIdx = 0, runIdx = 0;
HSLFTextRun trun; HSLFTextRun trun;
@ -1444,7 +1536,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
public boolean isDirty() { public boolean isDirty() {
return _dirty; return _dirty;
} }
/** /**
* Calculates the start index of the given text run * Calculates the start index of the given text run
* *

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.TextProp;
import org.apache.poi.hslf.model.textproperties.TextPropCollection; import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType; 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.draw.DrawPaint;
import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
@ -51,7 +47,7 @@ public final class HSLFTextRun implements TextRun {
private HSLFTextParagraph parentParagraph; private HSLFTextParagraph parentParagraph;
private String _runText = ""; private String _runText = "";
private String _fontFamily; private String _fontFamily;
private int hyperlinkId = -1; private HSLFHyperlink link;
/** /**
* Our paragraph and character style. * Our paragraph and character style.
@ -395,64 +391,27 @@ public final class HSLFTextRun implements TextRun {
public byte getPitchAndFamily() { public byte getPitchAndFamily() {
return 0; return 0;
} }
/**
* Sets the associated hyperlink id - currently this is only used while parsing and
* can't be used for update a ppt
*
* @param hyperlinkId the id or -1 to unset it
*/
public void setHyperlinkId(int hyperlinkId) {
this.hyperlinkId = hyperlinkId;
}
/**
* Returns the associated hyperlink id
*
* @return the hyperlink id
*/
public int getHyperlinkId() {
return hyperlinkId;
}
/**
* Sets the hyperlink - used when parsing the document
*
* @param link the hyperlink
*/
protected void setHyperlink(HSLFHyperlink link) {
this.link = link;
}
@Override @Override
public HSLFHyperlink getHyperlink() { public HSLFHyperlink getHyperlink() {
if (hyperlinkId == -1) { return link;
return null; }
@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; 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.exceptions.HSLFException;
import org.apache.poi.hslf.model.HSLFMetroShape; import org.apache.poi.hslf.model.HSLFMetroShape;
import org.apache.poi.hslf.record.EscherTextboxWrapper; 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.OEPlaceholderAtom;
import org.apache.poi.hslf.record.PPDrawing; import org.apache.poi.hslf.record.PPDrawing;
import org.apache.poi.hslf.record.RoundTripHFPlaceholder12; import org.apache.poi.hslf.record.RoundTripHFPlaceholder12;
import org.apache.poi.hslf.record.TextHeaderAtom; 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.DrawFactory;
import org.apache.poi.sl.draw.DrawTextShape; import org.apache.poi.sl.draw.DrawTextShape;
import org.apache.poi.sl.usermodel.Insets2D; import org.apache.poi.sl.usermodel.Insets2D;
@ -622,33 +619,6 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
return getClientDataRecord(RoundTripHFPlaceholder12.typeID); 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 @Override
public boolean isPlaceholder() { public boolean isPlaceholder() {
OEPlaceholderAtom oep = getPlaceholderAtom(); OEPlaceholderAtom oep = getPlaceholderAtom();
@ -729,50 +699,38 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
return HSLFTextParagraph.getRawText(getTextParagraphs()); return HSLFTextParagraph.getRawText(getTextParagraphs());
} }
/** @Override
* 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.
*/
public String getText() { public String getText() {
String rawText = getRawText(); String rawText = getRawText();
return HSLFTextParagraph.toExternalString(rawText, getRunType()); 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, * Saves the modified paragraphs/textrun to the records.
* creating a new RichTextRun for it to sit in. * Also updates the styles to the correct text length.
* */
* @param text the text string used by this object. protected void storeText() {
*/ List<HSLFTextParagraph> paras = getTextParagraphs();
public HSLFTextRun appendText(String text, boolean newParagraph) { HSLFTextParagraph.storeText(paras);
// 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
/** /**
* Returns the array of all hyperlinks in this text run * 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.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.poi.POIDataSamples; 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; import org.junit.Test;
/** /**
* Test Hyperlink. * Test Hyperlink.
*
* @author Yegor Kozlov
*/ */
public final class TestHyperlink { public final class TestHyperlink {
private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance(); private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
@ -42,7 +51,7 @@ public final class TestHyperlink {
HSLFSlide slide = ppt.getSlides().get(0); HSLFSlide slide = ppt.getSlides().get(0);
List<HSLFTextParagraph> para = slide.getTextParagraphs().get(1); List<HSLFTextParagraph> para = slide.getTextParagraphs().get(1);
String rawText = toExternalString(getRawText(para), para.get(0).getRunType()); String rawText = toExternalString(getRawText(para), para.get(0).getRunType());
String expected = String expected =
"This page has two links:\n"+ "This page has two links:\n"+
@ -52,9 +61,8 @@ public final class TestHyperlink {
"\n"+ "\n"+
"In addition, its notes has one link"; "In addition, its notes has one link";
assertEquals(expected, rawText); assertEquals(expected, rawText);
List<HSLFHyperlink> links = HSLFHyperlink.find(para); List<HSLFHyperlink> links = findHyperlinks(para);
assertNotNull(links);
assertEquals(2, links.size()); assertEquals(2, links.size());
assertEquals("http://jakarta.apache.org/poi/", links.get(0).getLabel()); assertEquals("http://jakarta.apache.org/poi/", links.get(0).getLabel());
@ -68,17 +76,97 @@ public final class TestHyperlink {
slide = ppt.getSlides().get(1); slide = ppt.getSlides().get(1);
para = slide.getTextParagraphs().get(1); para = slide.getTextParagraphs().get(1);
rawText = toExternalString(getRawText(para), para.get(0).getRunType()); rawText = toExternalString(getRawText(para), para.get(0).getRunType());
expected = expected =
"I have the one link:\n" + "I have the one link:\n" +
"Jakarta HSSF"; "Jakarta HSSF";
assertEquals(expected, rawText); assertEquals(expected, rawText);
links = HSLFHyperlink.find(para); links.clear();
links = findHyperlinks(para);
assertNotNull(links); assertNotNull(links);
assertEquals(1, links.size()); assertEquals(1, links.size());
assertEquals("Open Jakarta POI HSSF module test ", links.get(0).getLabel()); assertEquals("Open Jakarta POI HSSF module test ", links.get(0).getLabel());
assertEquals("http://jakarta.apache.org/poi/hssf/", links.get(0).getAddress()); 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)); 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 // Get the document
Document doc = ss.getDocumentRecord(); Document doc = ss.getDocumentRecord();
// Get the ExObjList // Get the ExObjList
ExObjList exObjList = doc.getExObjList(); ExObjList exObjList = doc.getExObjList(false);
assertNotNull(exObjList); assertNotNull(exObjList);
assertEquals(1033l, exObjList.getRecordType()); assertEquals(1033l, exObjList.getRecordType());