From b585430cabe8e5cfd2130b8667423e999525d4a3 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 24 Jan 2016 00:12:10 +0000 Subject: [PATCH] #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 --- .../poi/hslf/examples/CreateHyperlink.java | 28 +- .../apache/poi/hslf/examples/Hyperlinks.java | 30 +- .../apache/poi/xslf/usermodel/Tutorial6.java | 2 +- .../apache/poi/sl/draw/DrawPictureShape.java | 2 +- .../apache/poi/sl/usermodel/Hyperlink.java | 56 +++- .../apache/poi/sl/usermodel/SimpleShape.java | 20 ++ .../org/apache/poi/sl/usermodel/TextRun.java | 15 +- .../apache/poi/sl/usermodel/TextShape.java | 22 ++ .../poi/xslf/usermodel/XSLFHyperlink.java | 128 ++++++-- .../poi/xslf/usermodel/XSLFSimpleShape.java | 22 +- .../poi/xslf/usermodel/XSLFTextParagraph.java | 22 ++ .../poi/xslf/usermodel/XSLFTextRun.java | 12 +- .../poi/xslf/usermodel/XSLFTextShape.java | 91 ++++-- .../poi/xslf/usermodel/TestXSLFHyperlink.java | 90 +++++- .../org/apache/poi/hslf/model/OLEShape.java | 2 +- .../org/apache/poi/hslf/record/Document.java | 21 +- .../apache/poi/hslf/record/ExHyperlink.java | 6 +- .../poi/hslf/usermodel/HSLFHyperlink.java | 290 +++++++++++++----- .../apache/poi/hslf/usermodel/HSLFShape.java | 15 +- .../apache/poi/hslf/usermodel/HSLFSheet.java | 9 + .../poi/hslf/usermodel/HSLFSimpleShape.java | 79 ++--- .../poi/hslf/usermodel/HSLFSlideShow.java | 28 +- .../poi/hslf/usermodel/HSLFTextParagraph.java | 142 +++++++-- .../poi/hslf/usermodel/HSLFTextRun.java | 67 +--- .../poi/hslf/usermodel/HSLFTextShape.java | 94 ++---- .../apache/poi/hslf/model/TestHyperlink.java | 106 ++++++- .../apache/poi/hslf/record/TestExObjList.java | 2 +- 27 files changed, 946 insertions(+), 455 deletions(-) diff --git a/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java b/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java index 44e5d81c1c..fc25afae55 100644 --- a/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java +++ b/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java @@ -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(); } } diff --git a/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java b/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java index 8cbbcdc852..812429295a 100644 --- a/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java +++ b/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java @@ -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 txtParas : slide.getTextParagraphs()) { - List links = HSLFHyperlink.find(txtParas); - String text = HSLFTextParagraph.getRawText(txtParas); - - for (HSLFHyperlink link : links) { - System.out.println(toStr(link, text)); + for (List 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); } } diff --git a/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial6.java b/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial6.java index e3fdd85df2..73a59d0b81 100644 --- a/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial6.java +++ b/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial6.java @@ -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 diff --git a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java index 9721660580..beadc1f988 100644 --- a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java @@ -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); diff --git a/src/java/org/apache/poi/sl/usermodel/Hyperlink.java b/src/java/org/apache/poi/sl/usermodel/Hyperlink.java index cde288492c..50500d04bc 100644 --- a/src/java/org/apache/poi/sl/usermodel/Hyperlink.java +++ b/src/java/org/apache/poi/sl/usermodel/Hyperlink.java @@ -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, + P extends TextParagraph +> 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 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(); } diff --git a/src/java/org/apache/poi/sl/usermodel/SimpleShape.java b/src/java/org/apache/poi/sl/usermodel/SimpleShape.java index 53ee6de529..aee69fb735 100644 --- a/src/java/org/apache/poi/sl/usermodel/SimpleShape.java +++ b/src/java/org/apache/poi/sl/usermodel/SimpleShape.java @@ -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 null if not found. + * + * @since POI 3.14-Beta1 + */ + Hyperlink 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 createHyperlink(); } diff --git a/src/java/org/apache/poi/sl/usermodel/TextRun.java b/src/java/org/apache/poi/sl/usermodel/TextRun.java index 28db797958..014d3036b3 100644 --- a/src/java/org/apache/poi/sl/usermodel/TextRun.java +++ b/src/java/org/apache/poi/sl/usermodel/TextRun.java @@ -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(); + + + /** + * 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 getHyperlink(); + Hyperlink createHyperlink(); } diff --git a/src/java/org/apache/poi/sl/usermodel/TextShape.java b/src/java/org/apache/poi/sl/usermodel/TextShape.java index df302ff62e..cc39a51819 100644 --- a/src/java/org/apache/poi/sl/usermodel/TextShape.java +++ b/src/java/org/apache/poi/sl/usermodel/TextShape.java @@ -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. @@ -129,6 +139,18 @@ public interface TextShape< * @return the last text run of the - potential split - 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 diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java index 0b093ecacb..6f4a5228d0 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java @@ -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 { + final XSLFSheet _sheet; final CTHyperlink _link; - XSLFHyperlink(CTHyperlink link, XSLFTextRun r){ - _r = r; + XSLFHyperlink(CTHyperlink link, XSLFSheet sheet){ + _sheet = sheet; _link = link; } @@ -39,24 +42,27 @@ 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 public String getLabel() { return _link.getTooltip(); } - + @Override public void setLabel(String label) { _link.setTooltip(label); @@ -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 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); } -} +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java index 8b92ec2316..636647ef2e 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java @@ -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; + } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java index 5abb098375..298bcc7817 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java @@ -994,4 +994,26 @@ public class XSLFTextParagraph implements TextParagraph0; 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(); + } + } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java index 1b2272ba3e..049de50261 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java @@ -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){ diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java index d78749c750..5aa11f1deb 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java @@ -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 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 diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java index 8a2f3c6cc3..53c233199c 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java @@ -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 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(); + } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java index c0e5948d77..32780264f2 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java @@ -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; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Document.java b/src/scratchpad/src/org/apache/poi/hslf/record/Document.java index 2cfafa8d2e..0c74ae2b40 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/Document.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/Document.java @@ -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 diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java index 92245598f6..ca9d68d788 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java @@ -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); } } @@ -79,7 +79,7 @@ public class ExHyperlink extends RecordContainer { linkDetailsA.setText(title); } } - + /** * Get the link details (field A) */ diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java index f896103b04..b7b697ac52 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java @@ -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; - - private int id=-1; - private int type; - private String address; - private String label; - private int startIndex, endIndex; +public final class HSLFHyperlink implements Hyperlink { + 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; + } + /** * 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 setAddress(String str) { - address = str; + public void linkToSlide(Slide 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); } - public int getId() { - return id; + @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); } - public void setId(int id) { - this.id = id; + @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) { + exHyper.setLinkURL(str); + } + + public int getId() { + 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,16 +271,18 @@ 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); + } } - + /** * Gets the ending character position * * @return the ending character position */ 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 */ public void setEndIndex(int endIndex) { - this.endIndex = endIndex; + if (txinfo != null) { + txinfo.setEndIndex(endIndex); + } } - + /** * Find hyperlinks in a text shape * @@ -177,15 +313,15 @@ public final class HSLFHyperlink implements Hyperlink { * @return found hyperlinks */ @SuppressWarnings("resource") - public static List find(List paragraphs){ + protected static List find(List paragraphs){ List lst = new ArrayList(); if (paragraphs == null || paragraphs.isEmpty()) return lst; HSLFTextParagraph firstPara = paragraphs.get(0); - + 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 null */ @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); } } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java index e4ef3b1b77..0210a1d8a6 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java @@ -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). *

*

- * - * @author Yegor Kozlov */ public abstract class HSLFShape implements Shape { @@ -89,7 +86,7 @@ public abstract class HSLFShape implements Shape { * Fill */ protected HSLFFill _fill; - + /** * 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 { return getFill().getFillStyle(); } - /** - * Returns the hyperlink assigned to this shape - * - * @return the hyperlink assigned to this shape - * or null if not found. - */ - public HSLFHyperlink getHyperlink(){ - return HSLFHyperlink.find(this); - } - public void draw(Graphics2D graphics){ logger.log(POILogger.INFO, "Rendering " + getShapeName()); } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java index ee61557734..72042769d1 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java @@ -134,6 +134,7 @@ public abstract class HSLFSheet implements HSLFShapeContainer, Sheet ltp : trs) { HSLFTextParagraph.supplySheet(ltp, this); + HSLFTextParagraph.applyHyperlinks(ltp); } } @@ -171,6 +172,14 @@ public abstract class HSLFSheet implements HSLFShapeContainer, Sheet parentList; /** @@ -162,14 +163,14 @@ public final class HSLFTextParagraph implements TextParagraph 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 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 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 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 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 paragraphs) { List links = HSLFHyperlink.find(paragraphs); - + 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 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(); } } } } - + protected static void applyCharacterStyles(List paragraphs, List charStyles) { int paraIdx = 0, runIdx = 0; HSLFTextRun trun; @@ -1444,7 +1536,7 @@ public final class HSLFTextParagraph implements TextParagraph { 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 { 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 paras = getTextParagraphs(); + HSLFTextRun htr = HSLFTextParagraph.appendText(paras, text, newParagraph); + setTextId(getRawText().hashCode()); + return htr; + } - // Update methods follow - - /** - * 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 paras = getTextParagraphs(); - return HSLFTextParagraph.appendText(paras, text, newParagraph); - } - - @Override - public HSLFTextRun setText(String text) { - // init paragraphs - List 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 paras = getTextParagraphs(); - HSLFTextParagraph.storeText(paras); - } - // Accesser methods follow + @Override + public HSLFTextRun setText(String text) { + // init paragraphs + List paras = getTextParagraphs(); + HSLFTextRun htr = HSLFTextParagraph.setText(paras, text); + setTextId(getRawText().hashCode()); + return htr; + } + + /** + * Saves the modified paragraphs/textrun to the records. + * Also updates the styles to the correct text length. + */ + protected void storeText() { + List paras = getTextParagraphs(); + HSLFTextParagraph.storeText(paras); + } /** * Returns the array of all hyperlinks in this text run diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java index 715640a1d5..7810929d78 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java @@ -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(); @@ -42,7 +51,7 @@ public final class TestHyperlink { HSLFSlide slide = ppt.getSlides().get(0); List para = slide.getTextParagraphs().get(1); - + String rawText = toExternalString(getRawText(para), para.get(0).getRunType()); String expected = "This page has two links:\n"+ @@ -52,9 +61,8 @@ public final class TestHyperlink { "\n"+ "In addition, its notes has one link"; assertEquals(expected, rawText); - - List links = HSLFHyperlink.find(para); - assertNotNull(links); + + List links = findHyperlinks(para); assertEquals(2, links.size()); assertEquals("http://jakarta.apache.org/poi/", links.get(0).getLabel()); @@ -68,17 +76,97 @@ public final class TestHyperlink { slide = ppt.getSlides().get(1); para = slide.getTextParagraphs().get(1); rawText = toExternalString(getRawText(para), para.get(0).getRunType()); - expected = + expected = "I have the one link:\n" + "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 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 findHyperlinks(List paras) { + List links = new ArrayList(); + for (HSLFTextParagraph p : paras) { + for (HSLFTextRun r : p) { + HSLFHyperlink hl = r.getHyperlink(); + if (hl != null) { + links.add(hl); + } + } + } + return links; } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestExObjList.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestExObjList.java index bf596faed8..88f6884df4 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestExObjList.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestExObjList.java @@ -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()); -- 2.39.5