]> source.dussan.org Git - poi.git/commitdiff
#41047 - Support hyperlinks in HSLF shapes and textruns
authorAndreas Beeker <kiwiwings@apache.org>
Sun, 24 Jan 2016 00:12:10 +0000 (00:12 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Sun, 24 Jan 2016 00:12:10 +0000 (00:12 +0000)
#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

27 files changed:
src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java
src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java
src/examples/src/org/apache/poi/xslf/usermodel/Tutorial6.java
src/java/org/apache/poi/sl/draw/DrawPictureShape.java
src/java/org/apache/poi/sl/usermodel/Hyperlink.java
src/java/org/apache/poi/sl/usermodel/SimpleShape.java
src/java/org/apache/poi/sl/usermodel/TextRun.java
src/java/org/apache/poi/sl/usermodel/TextShape.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java
src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java
src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java
src/scratchpad/src/org/apache/poi/hslf/record/Document.java
src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShape.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextParagraph.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextRun.java
src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFTextShape.java
src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java
src/scratchpad/testcases/org/apache/poi/hslf/record/TestExObjList.java

index 44e5d81c1c543c0c47b2e25111d01796b65c05e7..fc25afae55997a7ec8a42608c2404fc2cf7a49ec 100644 (file)
@@ -39,38 +39,26 @@ public abstract class CreateHyperlink {
         HSLFSlide slideC = ppt.createSlide();
 
         // link to a URL
-        HSLFTextBox textBox1 = new HSLFTextBox();
+        HSLFTextBox textBox1 = slideA.createTextBox();
         textBox1.setText("Apache POI");
         textBox1.setAnchor(new Rectangle(100, 100, 200, 50));
 
-        String text = textBox1.getText();
-        HSLFHyperlink link = new HSLFHyperlink();
-        link.setAddress("http://www.apache.org");
-        link.setLabel(textBox1.getText());
-        int linkId = ppt.addHyperlink(link);
-
-        // apply link to the text
-        textBox1.setHyperlink(linkId, 0, text.length());
-
-        slideA.addShape(textBox1);
+        HSLFHyperlink link1 = textBox1.getTextParagraphs().get(0).getTextRuns().get(0).createHyperlink();
+        link1.linkToUrl("http://www.apache.org");
+        link1.setLabel(textBox1.getText());
 
         // link to another slide
-        HSLFTextBox textBox2 = new HSLFTextBox();
+        HSLFTextBox textBox2 = slideA.createTextBox();
         textBox2.setText("Go to slide #3");
         textBox2.setAnchor(new Rectangle(100, 300, 200, 50));
 
-        HSLFHyperlink link2 = new HSLFHyperlink();
-        link2.setAddress(slideC);
-        ppt.addHyperlink(link2);
-
-        // apply link to the whole shape
-        textBox2.setHyperlink(link2);
-
-        slideA.addShape(textBox2);
+        HSLFHyperlink link2 = textBox2.getTextParagraphs().get(0).getTextRuns().get(0).createHyperlink();
+        link2.linkToSlide(slideC);
 
         FileOutputStream out = new FileOutputStream("hyperlink.ppt");
         ppt.write(out);
         out.close();
 
+        ppt.close();
    }
 }
index 8cbbcdc852d5fcda10acc57e837a958ce8e04c5b..812429295a34be04cbe27fc22ba772a7ed3d6794 100644 (file)
@@ -19,12 +19,15 @@ package org.apache.poi.hslf.examples;
 
 import java.io.FileInputStream;
 import java.util.List;
+import java.util.Locale;
 
 import org.apache.poi.hslf.usermodel.HSLFHyperlink;
 import org.apache.poi.hslf.usermodel.HSLFShape;
+import org.apache.poi.hslf.usermodel.HSLFSimpleShape;
 import org.apache.poi.hslf.usermodel.HSLFSlide;
 import org.apache.poi.hslf.usermodel.HSLFSlideShow;
 import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
+import org.apache.poi.hslf.usermodel.HSLFTextRun;
 
 /**
  * Demonstrates how to read hyperlinks from  a presentation
@@ -44,12 +47,14 @@ public final class Hyperlinks {
 
                 // read hyperlinks from the slide's text runs
                 System.out.println("- reading hyperlinks from the text runs");
-                for (List<HSLFTextParagraph> txtParas : slide.getTextParagraphs()) {
-                    List<HSLFHyperlink> links = HSLFHyperlink.find(txtParas);
-                    String text = HSLFTextParagraph.getRawText(txtParas);
-
-                    for (HSLFHyperlink link : links) {
-                        System.out.println(toStr(link, text));
+                for (List<HSLFTextParagraph> paras : slide.getTextParagraphs()) {
+                    for (HSLFTextParagraph para : paras) {
+                        for (HSLFTextRun run : para) {
+                            HSLFHyperlink link = run.getHyperlink();
+                            if (link != null) {
+                                System.out.println(toStr(link, run.getRawText()));
+                            }
+                        }
                     }
                 }
 
@@ -58,18 +63,21 @@ public final class Hyperlinks {
                 // read such hyperlinks
                 System.out.println("- reading hyperlinks from the slide's shapes");
                 for (HSLFShape sh : slide.getShapes()) {
-                    HSLFHyperlink link = HSLFHyperlink.find(sh);
-                    if (link == null) continue;
-                    System.out.println(toStr(link, null));
+                    if (sh instanceof HSLFSimpleShape) {
+                        HSLFHyperlink link = ((HSLFSimpleShape)sh).getHyperlink();
+                        if (link != null) {
+                            System.out.println(toStr(link, null));
+                        }
+                    }
                 }
             }
+            ppt.close();
         }
    }
 
     static String toStr(HSLFHyperlink link, String rawText) {
         //in ppt end index is inclusive
         String formatStr = "title: %1$s, address: %2$s" + (rawText == null ? "" : ", start: %3$s, end: %4$s, substring: %5$s");
-        String substring = (rawText == null) ? "" : rawText.substring(link.getStartIndex(), link.getEndIndex()-1);
-        return String.format(formatStr, link.getLabel(), link.getAddress(), link.getStartIndex(), link.getEndIndex(), substring);
+        return String.format(Locale.ROOT, formatStr, link.getLabel(), link.getAddress(), link.getStartIndex(), link.getEndIndex(), rawText);
     }
 }
index e3fdd85df2864737d344959c58cb811350320b65..73a59d0b814c9080fca406678237ea4953e764da 100644 (file)
@@ -48,7 +48,7 @@ public class Tutorial6 {
         XSLFTextRun r2 = shape2.addNewTextParagraph().addNewTextRun();\r
         XSLFHyperlink link2 = r2.createHyperlink();\r
         r2.setText("Go to the second slide"); // visible text\r
-        link2.setAddress(slide2);  // link address\r
+        link2.linkToSlide(slide2);  // link address\r
 \r
 \r
 \r
index 9721660580ccb840e73c0ef6d59f45d6faa72c07..beadc1f98833968cb448ffaff5d173b52e5e538c 100644 (file)
@@ -61,7 +61,7 @@ public class DrawPictureShape extends DrawSimpleShape {
      * Returns an ImageRenderer for the PictureData\r
      *\r
      * @param graphics\r
-     * @return\r
+     * @return the image renderer\r
      */\r
     public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) {\r
         ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER);\r
index cde288492c5dd9b87bf9dd0d481380094d0a1ada..50500d04bcd50cf705e0500603c4c76cce39e111 100644 (file)
@@ -20,5 +20,59 @@ package org.apache.poi.sl.usermodel;
 /**
  * A PowerPoint hyperlink
  */
-public interface Hyperlink extends org.apache.poi.common.usermodel.Hyperlink {
+public interface Hyperlink<
+    S extends Shape<S,P>,
+    P extends TextParagraph<S,P,?>
+> extends org.apache.poi.common.usermodel.Hyperlink {
+    /**
+     * Link to an email
+     *
+     * @param emailAddress the email address
+     * @since POI 3.14-Beta2 
+     */
+    void linkToEmail(String emailAddress);
+    
+    /**
+     * Link to a web page / URL
+     *
+     * @param url the url
+     * @since POI 3.14-Beta2
+     */
+    void linkToUrl(String url);
+
+    /**
+     * Link to a slide in this slideshow
+     *
+     * @param slide the linked slide
+     * @since POI 3.14-Beta2
+     */
+    void linkToSlide(Slide<S,P> slide);
+
+    /**
+     * Link to the next slide (relative from the current)
+     * 
+     * @since POI 3.14-Beta2
+     */
+    void linkToNextSlide();
+
+    /**
+     * Link to the previous slide (relative from the current)
+     * 
+     * @since POI 3.14-Beta2
+     */
+    void linkToPreviousSlide();
+
+    /**
+     * Link to the first slide in this slideshow
+     * 
+     * @since POI 3.14-Beta2
+     */
+    void linkToFirstSlide();
+
+    /**
+     * Link to the last slide in this slideshow
+     * 
+     * @since POI 3.14-Beta2
+     */
+    void linkToLastSlide();
 }
index 53ee6de5294c3327bc0af3f960dcc8f52b54b612..aee69fb735d683f6d097f4349dd04c9c8deba34d 100644 (file)
@@ -83,4 +83,24 @@ public interface SimpleShape<
      *              the solid fill attribute from the underlying implementation
      */
        void setFillColor(Color color);
+
+    /**
+     * Returns the hyperlink assigned to this shape
+     *
+     * @return the hyperlink assigned to this shape
+     * or <code>null</code> if not found.
+     * 
+     * @since POI 3.14-Beta1
+     */
+       Hyperlink<S,P> getHyperlink();
+       
+       /**
+     * Creates a hyperlink and asigns it to this shape.
+     * If the shape has already a hyperlink assigned, return it instead
+     *
+     * @return the hyperlink assigned to this shape
+     * 
+     * @since POI 3.14-Beta1
+     */
+    Hyperlink<S,P> createHyperlink();
 }
index 28db79795856001d2b0fe35dd9a5dd9ef32e8d33..014d3036b3b7afe0bd7b0dda4eba2ce91262c665 100644 (file)
@@ -161,6 +161,19 @@ public interface TextRun {
      * Return the associated hyperlink
      * 
      * @return the associated hyperlink or null if no hyperlink was set
+     * 
+     * @since POI 3.14-Beta2
+     */
+    Hyperlink<?,?> getHyperlink();
+    
+    
+    /**
+     * 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();
 }
index df302ff62e673bfe435b60770acea1420fc182f8..cc39a518193898010620d01a84dc840e4bf7b960 100644 (file)
@@ -118,6 +118,16 @@ public interface TextShape<
         OTHER\r
     }\r
 \r
+    /**\r
+     * Returns the text contained in this text frame, which has been made safe\r
+     * for printing and other use.\r
+     * \r
+     * @return the text string for this textbox.\r
+     * \r
+     * @since POI 3.14-Beta2\r
+     */\r
+    String getText();\r
+    \r
     /**\r
      * Sets (overwrites) the current text.\r
      * Uses the properties of the first paragraph / textrun.\r
@@ -129,6 +139,18 @@ public interface TextShape<
      * @return the last text run of the - potential split - text\r
      */\r
     TextRun setText(String text);\r
+\r
+    /**\r
+     * Adds the supplied text onto the end of the TextParagraphs,\r
+     * creating a new RichTextRun for it to sit in.\r
+     *\r
+     * @param text the text string to be appended.\r
+     * @param newParagraph if true, a new paragraph will be added,\r
+     *        which will contain the added text\r
+     *\r
+     * @since POI 3.14-Beta1\r
+     */\r
+    TextRun appendText(String text, boolean newParagraph);\r
     \r
     /**\r
      * @return the TextParagraphs for this text box\r
index 0b093ecacb7749beeb1f94895611cdfc26e63b83..6f4a5228d061d947d8db022f381436b84aecd82b 100644 (file)
 ==================================================================== */\r
 package org.apache.poi.xslf.usermodel;\r
 \r
+import java.net.URI;\r
+\r
+import org.apache.poi.openxml4j.opc.PackagePart;\r
+import org.apache.poi.openxml4j.opc.PackagePartName;\r
 import org.apache.poi.openxml4j.opc.PackageRelationship;\r
 import org.apache.poi.openxml4j.opc.TargetMode;\r
 import org.apache.poi.sl.usermodel.Hyperlink;\r
+import org.apache.poi.sl.usermodel.Slide;\r
 import org.apache.poi.util.Internal;\r
 import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink;\r
 \r
-import java.net.URI;\r
-\r
-public class XSLFHyperlink implements Hyperlink {\r
-    final XSLFTextRun _r;\r
+public class XSLFHyperlink implements Hyperlink<XSLFShape,XSLFTextParagraph> {\r
+    final XSLFSheet _sheet;\r
     final CTHyperlink _link;\r
 \r
-    XSLFHyperlink(CTHyperlink link, XSLFTextRun r){\r
-        _r = r;\r
+    XSLFHyperlink(CTHyperlink link, XSLFSheet sheet){\r
+        _sheet = sheet;\r
         _link = link;\r
     }\r
 \r
@@ -39,24 +42,27 @@ public class XSLFHyperlink implements Hyperlink {
     }\r
 \r
     @Override\r
-    public void setAddress(String address){\r
-        XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet();\r
-        PackageRelationship rel =\r
-                sheet.getPackagePart().\r
-                        addExternalRelationship(address, XSLFRelation.HYPERLINK.getRelation());\r
-        _link.setId(rel.getId());\r
+    public void setAddress(String address) {\r
+        linkToUrl(address);\r
     }\r
-    \r
+\r
     @Override\r
     public String getAddress() {\r
-        return getTargetURI().toASCIIString();\r
+        if (!_link.isSetId()) {\r
+            return _link.getAction();\r
+        }\r
+\r
+        String id = _link.getId();\r
+        URI targetURI = _sheet.getPackagePart().getRelationship(id).getTargetURI();\r
+        \r
+        return targetURI.toASCIIString();\r
     }\r
 \r
     @Override\r
     public String getLabel() {\r
         return _link.getTooltip();\r
     }\r
-    \r
+\r
     @Override\r
     public void setLabel(String label) {\r
         _link.setTooltip(label);\r
@@ -64,28 +70,88 @@ public class XSLFHyperlink implements Hyperlink {
 \r
     @Override\r
     public int getType() {\r
-        // TODO: currently this just returns nonsense\r
-        if ("ppaction://hlinksldjump".equals(_link.getAction())) {\r
+        String action = _link.getAction();\r
+        if (action == null) {\r
+            action = "";\r
+        }\r
+        if (action.equals("ppaction://hlinksldjump") || action.startsWith("ppaction://hlinkshowjump")) {\r
             return LINK_DOCUMENT;\r
         }\r
-        return LINK_URL;\r
+        \r
+        String address = getAddress();\r
+        if (address == null) {\r
+            address = "";\r
+        }\r
+        if (address.startsWith("mailto:")) {\r
+            return LINK_EMAIL;\r
+        } else {\r
+            return LINK_URL;\r
+        }\r
     }\r
-    \r
-    public void setAddress(XSLFSlide slide){\r
-        XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet();\r
+\r
+    @Override\r
+    public void linkToEmail(String emailAddress) {\r
+        linkToExternal("mailto:"+emailAddress);\r
+        setLabel(emailAddress);\r
+    }\r
+\r
+    @Override\r
+    public void linkToUrl(String url) {\r
+        linkToExternal(url);\r
+        setLabel(url);\r
+    }\r
+\r
+    private void linkToExternal(String url) {\r
+        PackagePart thisPP = _sheet.getPackagePart();\r
+        if (_link.isSetId() && !_link.getId().isEmpty()) {\r
+            thisPP.removeRelationship(_link.getId());\r
+        }\r
+        PackageRelationship rel = thisPP.addExternalRelationship(url, XSLFRelation.HYPERLINK.getRelation());\r
+        _link.setId(rel.getId());\r
+        if (_link.isSetAction()) {\r
+            _link.unsetAction();\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void linkToSlide(Slide<XSLFShape,XSLFTextParagraph> slide) {\r
+        PackagePart thisPP = _sheet.getPackagePart();\r
+        PackagePartName otherPPN = ((XSLFSheet)slide).getPackagePart().getPartName();\r
+        if (_link.isSetId() && !_link.getId().isEmpty()) {\r
+            thisPP.removeRelationship(_link.getId());\r
+        }\r
         PackageRelationship rel =\r
-                sheet.getPackagePart().\r
-                        addRelationship(slide.getPackagePart().getPartName(),\r
-                                TargetMode.INTERNAL,\r
-                                XSLFRelation.SLIDE.getRelation());\r
+            thisPP.addRelationship(otherPPN, TargetMode.INTERNAL, XSLFRelation.SLIDE.getRelation());\r
         _link.setId(rel.getId());\r
         _link.setAction("ppaction://hlinksldjump");\r
     }\r
 \r
-    @Internal\r
-    public URI getTargetURI(){\r
-        XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet();\r
-        String id = _link.getId();\r
-        return sheet.getPackagePart().getRelationship(id).getTargetURI();\r
+    @Override\r
+    public void linkToNextSlide() {\r
+        linkToRelativeSlide("nextslide");\r
+    }\r
+\r
+    @Override\r
+    public void linkToPreviousSlide() {\r
+        linkToRelativeSlide("previousslide");\r
+    }\r
+\r
+    @Override\r
+    public void linkToFirstSlide() {\r
+        linkToRelativeSlide("firstslide");\r
+    }\r
+\r
+    @Override\r
+    public void linkToLastSlide() {\r
+        linkToRelativeSlide("lastslide");\r
+    }\r
+    \r
+    private void linkToRelativeSlide(String jump) {\r
+        PackagePart thisPP = _sheet.getPackagePart();\r
+        if (_link.isSetId() && !_link.getId().isEmpty()) {\r
+            thisPP.removeRelationship(_link.getId());\r
+        }\r
+        _link.setId("");\r
+        _link.setAction("ppaction://hlinkshowjump?jump="+jump);\r
     }\r
-}\r
+}
\ No newline at end of file
index 8b92ec231600b4f7affbd2b4d28aa984e961cb85..636647ef2e86522c16884933d0a6613395b07be2 100644 (file)
@@ -34,8 +34,8 @@ import org.apache.poi.sl.usermodel.LineDecoration;
 import org.apache.poi.sl.usermodel.LineDecoration.DecorationShape;\r
 import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize;\r
 import org.apache.poi.sl.usermodel.PaintStyle;\r
-import org.apache.poi.sl.usermodel.Placeholder;\r
 import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;\r
+import org.apache.poi.sl.usermodel.Placeholder;\r
 import org.apache.poi.sl.usermodel.ShapeType;\r
 import org.apache.poi.sl.usermodel.SimpleShape;\r
 import org.apache.poi.sl.usermodel.StrokeStyle;\r
@@ -53,6 +53,7 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTGeomGuide;
 import org.openxmlformats.schemas.drawingml.x2006.main.CTLineEndProperties;\r
 import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties;\r
 import org.openxmlformats.schemas.drawingml.x2006.main.CTLineStyleList;\r
+import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;\r
 import org.openxmlformats.schemas.drawingml.x2006.main.CTOuterShadowEffect;\r
 import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;\r
 import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;\r
@@ -923,4 +924,23 @@ public abstract class XSLFSimpleShape extends XSLFShape
     public void setPlaceholder(Placeholder placeholder) {\r
         super.setPlaceholder(placeholder);\r
     }\r
+    \r
+    @Override\r
+    public XSLFHyperlink getHyperlink() {\r
+        CTNonVisualDrawingProps cNvPr = getCNvPr();\r
+        if (!cNvPr.isSetHlinkClick()) {\r
+            return null;\r
+        }\r
+        return new XSLFHyperlink(cNvPr.getHlinkClick(), getSheet());\r
+    }\r
+    \r
+    @Override\r
+    public XSLFHyperlink createHyperlink() {\r
+        XSLFHyperlink hl = getHyperlink();\r
+        if (hl == null) {\r
+            CTNonVisualDrawingProps cNvPr = getCNvPr();\r
+            hl = new XSLFHyperlink(cNvPr.addNewHlinkClick(), getSheet());\r
+        }\r
+        return hl;\r
+    }\r
 }\r
index 5abb098375bbf5a57bb4f2af5c9464a0deb25fb5..298bcc7817d2843e65552c2382a91ba696c1f5d5 100644 (file)
@@ -994,4 +994,26 @@ public class XSLFTextParagraph implements TextParagraph<XSLFShape,XSLFTextParagr
             }\r
         }\r
     }\r
+    \r
+    /**\r
+     * Helper method for appending text and keeping paragraph and character properties.\r
+     * The character properties are moved to the end paragraph marker\r
+     */\r
+    /* package */ void clearButKeepProperties() {\r
+        CTTextParagraph thisP = getXmlObject();\r
+        for (int i=thisP.sizeOfBrArray(); i>0; i--) {\r
+            thisP.removeBr(i-1);\r
+        }\r
+        for (int i=thisP.sizeOfFldArray(); i>0; i--) {\r
+            thisP.removeFld(i-1);\r
+        }\r
+        if (!_runs.isEmpty()) {\r
+            int size = _runs.size();\r
+            thisP.setEndParaRPr(_runs.get(size-1).getRPr());\r
+            for (int i=size; i>0; i--) {\r
+                thisP.removeR(i-1);\r
+            }\r
+            _runs.clear();\r
+        }\r
+    }\r
 }\r
index 1b2272ba3e49c389c253dce1b8d89e81e0a0cd15..049de50261488d86e2371933bfd01ae774d85e66 100644 (file)
@@ -444,17 +444,19 @@ public class XSLFTextRun implements TextRun {
         return "[" + getClass() + "]" + getRawText();\r
     }\r
 \r
+    @Override\r
     public XSLFHyperlink createHyperlink(){\r
-        XSLFHyperlink link = new XSLFHyperlink(_r.getRPr().addNewHlinkClick(), this);\r
-        return link;\r
+        XSLFHyperlink hl = getHyperlink();\r
+        if (hl == null) {\r
+            hl = new XSLFHyperlink(_r.getRPr().addNewHlinkClick(), _p.getParentShape().getSheet());\r
+        }\r
+        return hl;\r
     }\r
 \r
     @Override\r
     public XSLFHyperlink getHyperlink(){\r
         if(!_r.getRPr().isSetHlinkClick()) return null;\r
-\r
-\r
-        return new XSLFHyperlink(_r.getRPr().getHlinkClick(), this);\r
+        return new XSLFHyperlink(_r.getRPr().getHlinkClick(), _p.getParentShape().getSheet());\r
     }\r
 \r
     private boolean fetchCharacterProperty(CharacterPropertyFetcher<?> fetcher){\r
index d78749c750bce4abf16036c44d3f3d8961480096..5aa11f1deb64594f2ee921170d18bad62c703c2e 100644 (file)
@@ -71,10 +71,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape
         return getTextParagraphs().iterator();
     }
 
-    /**
-     *
-     * @return  text contained within this shape or empty string
-     */
+    @Override
     public String getText() {
         StringBuilder out = new StringBuilder();
         for (XSLFTextParagraph p : _paragraphs) {
@@ -95,50 +92,76 @@ public abstract class XSLFTextShape extends XSLFSimpleShape
 
     @Override
     public XSLFTextRun setText(String text) {
-        // copy properties from first paragraph / textrun
+        // calling clearText or setting to a new Array leads to a XmlValueDisconnectedException
+        if (!_paragraphs.isEmpty()) {
+            CTTextBody txBody = getTextBody(false);
+            int cntPs = txBody.sizeOfPArray();
+            for (int i = cntPs; i > 1; i--) {
+                txBody.removeP(i-1);
+                _paragraphs.remove(i-1);
+            }
+            
+            _paragraphs.get(0).clearButKeepProperties();
+        }
+        
+        return appendText(text, false);
+    }
+    
+    @Override
+    public XSLFTextRun appendText(String text, boolean newParagraph) {
+        if (text == null) return null;
+
+        // copy properties from last paragraph / textrun or paragraph end marker
         CTTextParagraphProperties pPr = null;
         CTTextCharacterProperties rPr = null;
-        if (!_paragraphs.isEmpty()) {
-            XSLFTextParagraph p0 = _paragraphs.get(0);
-            pPr = p0.getXmlObject().getPPr();
-            if (!p0.getTextRuns().isEmpty()) {
-                XSLFTextRun r0 = p0.getTextRuns().get(0);
+        
+        boolean firstPara;
+        XSLFTextParagraph para;
+        if (_paragraphs.isEmpty()) {
+            firstPara = false;
+            para = null;
+        } else {
+            firstPara = !newParagraph;
+            para = _paragraphs.get(_paragraphs.size()-1);
+            CTTextParagraph ctp = para.getXmlObject();
+            pPr = ctp.getPPr();
+            List<XSLFTextRun> runs = para.getTextRuns();
+            if (!runs.isEmpty()) {
+                XSLFTextRun r0 = runs.get(runs.size()-1);
                 rPr = r0.getXmlObject().getRPr();
+            } else if (ctp.isSetEndParaRPr()) {
+                rPr = ctp.getEndParaRPr();
             }
         }
-
-        // can't call clearText otherwise we receive a XmlValueDisconnectedException
-        _paragraphs.clear();
-        CTTextBody txBody = getTextBody(true);
-        int cntPs = txBody.sizeOfPArray();
         
-        // split text by paragraph and new line char
-        XSLFTextRun r = null;
-        for (String paraText : text.split("\\r\\n?|\\n")) {
-            XSLFTextParagraph para = addNewTextParagraph();
-            if (pPr != null) {
-                para.getXmlObject().setPPr(pPr);
+        XSLFTextRun run = null;
+        for (String lineTxt : text.split("\\r\\n?|\\n")) {
+            if (!firstPara) {
+                if (para != null && para.getXmlObject().isSetEndParaRPr()) {
+                    para.getXmlObject().unsetEndParaRPr();
+                }
+                para = addNewTextParagraph();
+                if (pPr != null) {
+                    para.getXmlObject().setPPr(pPr);
+                }
             }
-            boolean first = true;
-            for (String runText : paraText.split("[\u000b]")) {
-                if (!first) {
+            boolean firstRun = true;
+            for (String runText : lineTxt.split("[\u000b]")) {
+                if (!firstRun) {
                     para.addLineBreak();
                 }
-                r = para.addNewTextRun();
-                r.setText(runText);
+                run = para.addNewTextRun();
+                run.setText(runText);
                 if (rPr != null) {
-                    r.getXmlObject().setRPr(rPr);
+                    run.getXmlObject().setRPr(rPr);
                 }
-                first = false;
+                firstRun = false;
             }
+            firstPara = false;
         }
         
-        // simply setting a new pArray leads to XmlValueDisconnectedException
-        for (int i = cntPs-1; i >= 0; i--) {
-            txBody.removeP(i);
-        }
-        
-        return r;
+        assert(run != null);
+        return run;
     }
 
     @Override
index 8a2f3c6cc3ab3bf9471f3380788b6003990286af..53c233199c29ee636e822769fa8f3119d81e6d1b 100644 (file)
@@ -19,19 +19,17 @@ package org.apache.poi.xslf.usermodel;
 import static org.junit.Assert.assertEquals;\r
 import static org.junit.Assert.assertNotNull;\r
 \r
+import java.awt.geom.Rectangle2D;\r
 import java.io.IOException;\r
-import java.net.URI;\r
 import java.util.List;\r
 \r
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;\r
 import org.apache.poi.openxml4j.opc.PackageRelationship;\r
 import org.apache.poi.openxml4j.opc.TargetMode;\r
+import org.apache.poi.sl.usermodel.Hyperlink;\r
 import org.apache.poi.xslf.XSLFTestDataSamples;\r
 import org.junit.Test;\r
 \r
-/**\r
- * @author Yegor Kozlov\r
- */\r
 public class TestXSLFHyperlink {\r
 \r
     @Test\r
@@ -45,19 +43,19 @@ public class TestXSLFHyperlink {
         assertEquals("Web Page", cell1.getText());\r
         XSLFHyperlink link1 = cell1.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();\r
         assertNotNull(link1);\r
-        assertEquals(URI.create("http://poi.apache.org/"), link1.getTargetURI());\r
+        assertEquals("http://poi.apache.org/", link1.getAddress());\r
 \r
         XSLFTableCell cell2 = tbl.getRows().get(2).getCells().get(0);\r
         assertEquals("Place in this document", cell2.getText());\r
         XSLFHyperlink link2 = cell2.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();\r
         assertNotNull(link2);\r
-        assertEquals(URI.create("/ppt/slides/slide2.xml"), link2.getTargetURI());\r
+        assertEquals("/ppt/slides/slide2.xml", link2.getAddress());\r
 \r
         XSLFTableCell cell3 = tbl.getRows().get(3).getCells().get(0);\r
         assertEquals("Email", cell3.getText());\r
         XSLFHyperlink link3 = cell3.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();\r
         assertNotNull(link3);\r
-        assertEquals(URI.create("mailto:dev@poi.apache.org?subject=Hi%20There"), link3.getTargetURI());\r
+        assertEquals("mailto:dev@poi.apache.org?subject=Hi%20There", link3.getAddress());\r
         \r
         ppt.close();\r
     }\r
@@ -75,7 +73,7 @@ public class TestXSLFHyperlink {
         r1.setText("Web Page");\r
         XSLFHyperlink link1 = r1.createHyperlink();\r
         link1.setAddress("http://poi.apache.org/");\r
-        assertEquals(URI.create("http://poi.apache.org/"), link1.getTargetURI());\r
+        assertEquals("http://poi.apache.org/", link1.getAddress());\r
         assertEquals(numRel + 1, slide1.getPackagePart().getRelationships().size());\r
 \r
         String id1 = link1.getXmlObject().getId();\r
@@ -90,8 +88,8 @@ public class TestXSLFHyperlink {
         XSLFTextRun r2 = sh2.addNewTextParagraph().addNewTextRun();\r
         r2.setText("Place in this document");\r
         XSLFHyperlink link2 = r2.createHyperlink();\r
-        link2.setAddress(slide2);\r
-        assertEquals(URI.create("/ppt/slides/slide2.xml"), link2.getTargetURI());\r
+        link2.linkToSlide(slide2);\r
+        assertEquals("/ppt/slides/slide2.xml", link2.getAddress());\r
         assertEquals(numRel + 2, slide1.getPackagePart().getRelationships().size());\r
 \r
         String id2 = link2.getXmlObject().getId();\r
@@ -104,4 +102,76 @@ public class TestXSLFHyperlink {
         \r
         ppt.close();\r
     }\r
+\r
+\r
+    @Test\r
+    public void bug47291() throws IOException {\r
+        Rectangle2D anchor = new Rectangle2D.Double(100,100,100,100);\r
+        XMLSlideShow ppt1 = new XMLSlideShow();\r
+        XSLFSlide slide1 = ppt1.createSlide();\r
+        XSLFTextBox tb1 = slide1.createTextBox();\r
+        tb1.setAnchor(anchor);\r
+        XSLFTextRun r1 = tb1.setText("page1");\r
+        XSLFHyperlink hl1 = r1.createHyperlink();\r
+        hl1.linkToEmail("dev@poi.apache.org");\r
+        XSLFTextBox tb2 = ppt1.createSlide().createTextBox();\r
+        tb2.setAnchor(anchor);\r
+        XSLFTextRun r2 = tb2.setText("page2");\r
+        XSLFHyperlink hl2 = r2.createHyperlink();\r
+        hl2.linkToLastSlide();\r
+        XSLFSlide sl3 = ppt1.createSlide();\r
+        XSLFTextBox tb3 = sl3.createTextBox();\r
+        tb3.setAnchor(anchor);\r
+        tb3.setText("text1 ");\r
+        XSLFTextRun r3 = tb3.appendText("lin\u000bk", false);\r
+        tb3.appendText(" text2", false);\r
+        XSLFHyperlink hl3 = r3.createHyperlink();\r
+        hl3.linkToSlide(slide1);\r
+        XSLFTextBox tb4 = ppt1.createSlide().createTextBox();\r
+        tb4.setAnchor(anchor);\r
+        XSLFTextRun r4 = tb4.setText("page4");\r
+        XSLFHyperlink hl4 = r4.createHyperlink();\r
+        hl4.linkToUrl("http://poi.apache.org");\r
+        XSLFTextBox tb5 = ppt1.createSlide().createTextBox();\r
+        tb5.setAnchor(anchor);\r
+        tb5.setText("page5");\r
+        XSLFHyperlink hl5 = tb5.createHyperlink();\r
+        hl5.linkToFirstSlide();\r
+\r
+        XMLSlideShow ppt2 = XSLFTestDataSamples.writeOutAndReadBack(ppt1);\r
+        ppt1.close();\r
+\r
+        List<XSLFSlide> slides = ppt2.getSlides();\r
+        tb1 = (XSLFTextBox)slides.get(0).getShapes().get(0);\r
+        hl1 = tb1.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();\r
+        assertNotNull(hl1);\r
+        assertEquals("dev@poi.apache.org", hl1.getLabel());\r
+        assertEquals(Hyperlink.LINK_EMAIL, hl1.getType());\r
+\r
+        tb2 = (XSLFTextBox)slides.get(1).getShapes().get(0);\r
+        hl2 = tb2.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();\r
+        assertNotNull(hl2);\r
+        assertEquals("lastslide", hl2.getXmlObject().getAction().split("=")[1]);\r
+        assertEquals(Hyperlink.LINK_DOCUMENT, hl2.getType());\r
+\r
+        tb3 = (XSLFTextBox)slides.get(2).getShapes().get(0);\r
+        hl3 = tb3.getTextParagraphs().get(0).getTextRuns().get(3).getHyperlink();\r
+        assertNotNull(hl3);\r
+        assertEquals("/ppt/slides/slide1.xml", hl3.getAddress());\r
+        assertEquals(Hyperlink.LINK_DOCUMENT, hl3.getType());\r
+\r
+        tb4 = (XSLFTextBox)slides.get(3).getShapes().get(0);\r
+        hl4 = tb4.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink();\r
+        assertNotNull(hl4);\r
+        assertEquals("http://poi.apache.org", hl4.getLabel());\r
+        assertEquals(Hyperlink.LINK_URL, hl4.getType());\r
+\r
+        tb5 = (XSLFTextBox)slides.get(4).getShapes().get(0);\r
+        hl5 = tb5.getHyperlink();\r
+        assertNotNull(hl5);\r
+        assertEquals("firstslide", hl5.getXmlObject().getAction().split("=")[1]);\r
+        assertEquals(Hyperlink.LINK_DOCUMENT, hl5.getType());\r
+        \r
+        ppt2.close();\r
+    }\r
 }
\ No newline at end of file
index c0e5948d773d8a79b6b6d3b4705b6b502bad15c8..32780264f23636263289585e4e1e5c032b7e2f4e 100644 (file)
@@ -160,7 +160,7 @@ public final class OLEShape extends HSLFPictureShape {
         if(_exEmbed == null){
             HSLFSlideShow ppt = getSheet().getSlideShow();
 
-            ExObjList lst = ppt.getDocumentRecord().getExObjList();
+            ExObjList lst = ppt.getDocumentRecord().getExObjList(false);
             if(lst == null){
                 logger.log(POILogger.WARN, "ExObjList not found");
                 return null;
index 2cfafa8d2e2746bdf8ae1a1395b714abf2ec7c63..0c74ae2b40a0417832e955ba247ad1343365d48d 100644 (file)
@@ -46,22 +46,33 @@ public final class Document extends PositionDependentRecordContainer
         * Returns the DocumentAtom of this Document
         */
        public DocumentAtom getDocumentAtom() { return documentAtom; }
+       
        /**
         * Returns the Environment of this Notes, which lots of
-        *  settings for the document in it
+        * settings for the document in it
         */
        public Environment getEnvironment() { return environment; }
+       
        /**
         * Returns the PPDrawingGroup, which holds an Escher Structure
-        *  that contains information on pictures in the slides.
+        * that contains information on pictures in the slides.
         */
        public PPDrawingGroup getPPDrawingGroup() { return ppDrawing; }
+       
        /**
         * Returns the ExObjList, which holds the references to
-        *  external objects used in the slides. This may be null, if
-        *  there are no external references.
+        * external objects used in the slides. This may be null, if
+        * there are no external references.
+        *  
+        * @param create if true, create an ExObjList if it doesn't exist
         */
-       public ExObjList getExObjList() { return exObjList; }
+       public ExObjList getExObjList(boolean create) {
+           if (exObjList == null && create) {
+               exObjList = new ExObjList();
+               addChildAfter(exObjList, getDocumentAtom());
+           }
+           return exObjList;
+    }
 
        /**
         * Returns all the SlideListWithTexts that are defined for
index 92245598f6d453fb966e4aa508bab5ed0592001f..ca9d68d788b13b6d4695f591504ebc7ac3f538b6 100644 (file)
@@ -67,9 +67,9 @@ public class ExHyperlink extends RecordContainer {
                        linkDetailsB.setText(url);
                }
        }
-    public void setLinkURL(String url, int options) {
+       
+    public void setLinkOptions(int options) {
         if(linkDetailsB != null) {
-            linkDetailsB.setText(url);
             linkDetailsB.setOptions(options);
         }
     }
@@ -79,7 +79,7 @@ public class ExHyperlink extends RecordContainer {
             linkDetailsA.setText(title);
         }
     }
-
+    
        /**
         * Get the link details (field A)
         */
index f896103b04d95a23e280aa425ca4238ce7da8471..b7b697ac523c5b0b4eb0a2add04fb85c3f0e5786 100644 (file)
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.ListIterator;
 
 import org.apache.poi.hslf.record.ExHyperlink;
+import org.apache.poi.hslf.record.ExHyperlinkAtom;
 import org.apache.poi.hslf.record.ExObjList;
 import org.apache.poi.hslf.record.HSLFEscherClientDataRecord;
 import org.apache.poi.hslf.record.InteractiveInfo;
@@ -30,24 +31,95 @@ import org.apache.poi.hslf.record.InteractiveInfoAtom;
 import org.apache.poi.hslf.record.Record;
 import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
 import org.apache.poi.sl.usermodel.Hyperlink;
+import org.apache.poi.sl.usermodel.Slide;
 
 /**
  * Represents a hyperlink in a PowerPoint document
  */
-public final class HSLFHyperlink implements Hyperlink {
-    public static final byte LINK_NEXTSLIDE = InteractiveInfoAtom.LINK_NextSlide;
-    public static final byte LINK_PREVIOUSSLIDE = InteractiveInfoAtom.LINK_PreviousSlide;
-    public static final byte LINK_FIRSTSLIDE = InteractiveInfoAtom.LINK_FirstSlide;
-    public static final byte LINK_LASTSLIDE = InteractiveInfoAtom.LINK_LastSlide;
-    public static final byte LINK_SLIDENUMBER = InteractiveInfoAtom.LINK_SlideNumber;
-    public static final byte LINK_URL = InteractiveInfoAtom.LINK_Url;
-    public static final byte LINK_NULL = InteractiveInfoAtom.LINK_NULL;
-
-    private int id=-1;
-    private int type;
-    private String address;
-    private String label;
-    private int startIndex, endIndex;
+public final class HSLFHyperlink implements Hyperlink<HSLFShape,HSLFTextParagraph> {
+    private final ExHyperlink exHyper;
+    private final InteractiveInfo info;
+    private TxInteractiveInfoAtom txinfo;
+
+    protected HSLFHyperlink(ExHyperlink exHyper, InteractiveInfo info) {
+        this.info = info;
+        this.exHyper = exHyper;
+    }
+
+    public ExHyperlink getExHyperlink() {
+        return exHyper;
+    }
+    
+    public InteractiveInfo getInfo() {
+        return info;
+    }
+    
+    public TxInteractiveInfoAtom getTextRunInfo() {
+        return txinfo;
+    }
+    
+    protected void setTextRunInfo(TxInteractiveInfoAtom txinfo) {
+        this.txinfo = txinfo;
+    }
+
+    /**
+     * Creates a new Hyperlink and assign it to a shape
+     * This is only a helper method - use {@link HSLFSimpleShape#createHyperlink()} instead!
+     *
+     * @param shape the shape which receives the hyperlink
+     * @return the new hyperlink
+     * 
+     * @see HSLFShape#createHyperlink()
+     */
+    /* package */ static HSLFHyperlink createHyperlink(HSLFSimpleShape shape) {
+        // TODO: check if a hyperlink already exists
+        ExHyperlink exHyper = new ExHyperlink();
+        int linkId = shape.getSheet().getSlideShow().addToObjListAtom(exHyper);
+        ExHyperlinkAtom obj = exHyper.getExHyperlinkAtom();
+        obj.setNumber(linkId);
+        InteractiveInfo info = new InteractiveInfo();
+        info.getInteractiveInfoAtom().setHyperlinkID(linkId);
+        HSLFEscherClientDataRecord cldata = shape.getClientData(true);
+        cldata.addChild(info);
+        HSLFHyperlink hyper = new HSLFHyperlink(exHyper, info);
+        hyper.linkToNextSlide();
+        shape.setHyperlink(hyper);
+        return hyper;
+    }
+
+    /**
+     * Creates a new Hyperlink for a textrun.
+     * This is only a helper method - use {@link HSLFTextRun#createHyperlink()} instead!
+     *
+     * @param run the run which receives the hyperlink
+     * @return the new hyperlink
+     * 
+     * @see HSLFTextRun#createHyperlink()
+     */
+    /* package */ static HSLFHyperlink createHyperlink(HSLFTextRun run) {
+        // TODO: check if a hyperlink already exists
+        ExHyperlink exHyper = new ExHyperlink();
+        int linkId = run.getTextParagraph().getSheet().getSlideShow().addToObjListAtom(exHyper);
+        ExHyperlinkAtom obj = exHyper.getExHyperlinkAtom();
+        obj.setNumber(linkId);
+        InteractiveInfo info = new InteractiveInfo();
+        info.getInteractiveInfoAtom().setHyperlinkID(linkId);
+        // don't add the hyperlink now to text paragraph records
+        // this will be done, when the paragraph is saved
+        HSLFHyperlink hyper = new HSLFHyperlink(exHyper, info);
+        hyper.linkToNextSlide();
+        
+        TxInteractiveInfoAtom txinfo = new TxInteractiveInfoAtom();
+        int startIdx = run.getTextParagraph().getStartIdxOfTextRun(run);
+        int endIdx = startIdx + run.getLength();
+        txinfo.setStartIndex(startIdx);
+        txinfo.setEndIndex(endIdx);
+        hyper.setTextRunInfo(txinfo);
+        
+        run.setHyperlink(hyper);
+        return hyper;
+    }
+    
 
     /**
      * 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<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);
     }
 
-    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<HSLFHyperlink> find(List<HSLFTextParagraph> paragraphs){
+    protected static List<HSLFHyperlink> find(List<HSLFTextParagraph> paragraphs){
         List<HSLFHyperlink> lst = new ArrayList<HSLFHyperlink>();
         if (paragraphs == null || paragraphs.isEmpty()) return lst;
 
         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 <code>null</code>
      */
     @SuppressWarnings("resource")
-    public static HSLFHyperlink find(HSLFShape shape){
+    protected static HSLFHyperlink find(HSLFShape shape){
         HSLFSlideShow ppt = shape.getSheet().getSlideShow();
         //document-level container which stores info about all links in a presentation
-        ExObjList exobj = ppt.getDocumentRecord().getExObjList();
+        ExObjList exobj = ppt.getDocumentRecord().getExObjList(false);
         HSLFEscherClientDataRecord cldata = shape.getClientData(false);
 
         if (exobj != null && cldata != null) {
@@ -228,16 +364,12 @@ public final class HSLFHyperlink implements Hyperlink {
             InteractiveInfo hldr = (InteractiveInfo)r;
             InteractiveInfoAtom info = hldr.getInteractiveInfoAtom();
             int id = info.getHyperlinkID();
-            ExHyperlink linkRecord = exobj.get(id);
-            if (linkRecord == null) {
+            ExHyperlink exHyper = exobj.get(id);
+            if (exHyper == null) {
                 continue;
             }
-            
-            HSLFHyperlink link = new HSLFHyperlink();
-            link.setId(id);
-            link.setType(info.getAction());
-            link.setLabel(linkRecord.getLinkTitle());
-            link.setAddress(linkRecord.getLinkURL());
+
+            HSLFHyperlink link = new HSLFHyperlink(exHyper, hldr);
             out.add(link);
 
             if (iter.hasNext()) {
@@ -246,9 +378,7 @@ public final class HSLFHyperlink implements Hyperlink {
                     iter.previous();
                     continue;
                 }
-                TxInteractiveInfoAtom txinfo = (TxInteractiveInfoAtom)r;
-                link.setStartIndex(txinfo.getStartIndex());
-                link.setEndIndex(txinfo.getEndIndex());
+                link.setTextRunInfo((TxInteractiveInfoAtom)r);
             }
         }
     }
index e4ef3b1b779f4f0e0a50de338dbffb25ef8d05e7..0210a1d8a60b97a354c6475be2e2745e6a7e3ca4 100644 (file)
@@ -28,7 +28,6 @@ import org.apache.poi.ddf.EscherChildAnchorRecord;
 import org.apache.poi.ddf.EscherClientAnchorRecord;
 import org.apache.poi.ddf.EscherColorRef;
 import org.apache.poi.ddf.EscherContainerRecord;
-import org.apache.poi.ddf.EscherOptRecord;
 import org.apache.poi.ddf.EscherProperties;
 import org.apache.poi.ddf.EscherProperty;
 import org.apache.poi.ddf.EscherRecord;
@@ -60,8 +59,6 @@ import org.apache.poi.util.Units;
  *  in points (72 points = 1 inch).
  *  </p>
  * <p>
-  *
-  * @author Yegor Kozlov
  */
 public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
 
@@ -89,7 +86,7 @@ public abstract class HSLFShape implements Shape<HSLFShape,HSLFTextParagraph> {
      * 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<HSLFShape,HSLFTextParagraph> {
         return getFill().getFillStyle();
     }
 
-    /**
-     * Returns the hyperlink assigned to this shape
-     *
-     * @return the hyperlink assigned to this shape
-     * or <code>null</code> if not found.
-     */
-    public HSLFHyperlink getHyperlink(){
-        return HSLFHyperlink.find(this);
-    }
-
     public void draw(Graphics2D graphics){
         logger.log(POILogger.INFO, "Rendering " + getShapeName());
     }
index ee61557734084661e997088c2a86eac91692f56e..72042769d1d116e9faf44fcc218b422c7e8b3b57 100644 (file)
@@ -134,6 +134,7 @@ public abstract class HSLFSheet implements HSLFShapeContainer, Sheet<HSLFShape,H
         if (trs == null) return;
         for (List<HSLFTextParagraph> ltp : trs) {
             HSLFTextParagraph.supplySheet(ltp, this);
+            HSLFTextParagraph.applyHyperlinks(ltp);
         }
     }
 
@@ -171,6 +172,14 @@ public abstract class HSLFSheet implements HSLFShapeContainer, Sheet<HSLFShape,H
             EscherContainerRecord sp = (EscherContainerRecord) it.next();
             HSLFShape sh = HSLFShapeFactory.createShape(sp, null);
             sh.setSheet(this);
+            
+            if (sh instanceof HSLFSimpleShape) {
+                HSLFHyperlink link = HSLFHyperlink.find(sh);
+                if (link != null) {
+                    ((HSLFSimpleShape)sh).setHyperlink(link);
+                }
+            }
+            
             shapeList.add(sh);
         }
 
index 6c905b81f04c48772745c75162c4ef50c128bdcb..1326de19a0ec1e107ad0167bd20ea4f3b2d1a20b 100644 (file)
@@ -32,8 +32,6 @@ import org.apache.poi.ddf.EscherSimpleProperty;
 import org.apache.poi.ddf.EscherSpRecord;
 import org.apache.poi.hslf.exceptions.HSLFException;
 import org.apache.poi.hslf.record.HSLFEscherClientDataRecord;
-import org.apache.poi.hslf.record.InteractiveInfo;
-import org.apache.poi.hslf.record.InteractiveInfoAtom;
 import org.apache.poi.hslf.record.OEPlaceholderAtom;
 import org.apache.poi.hslf.record.Record;
 import org.apache.poi.hslf.record.RoundTripHFPlaceholder12;
@@ -69,6 +67,11 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
 
     public final static double DEFAULT_LINE_WIDTH = 0.75;
 
+    /**
+     * Hyperlink
+     */
+    protected HSLFHyperlink _hyperlink;
+    
     /**
      * Create a SimpleShape object and initialize it from the supplied Record container.
      *
@@ -270,56 +273,6 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
         getFill().setForegroundColor(color);
     }
 
-    public void setHyperlink(HSLFHyperlink link){
-        if(link.getId() == -1){
-            throw new HSLFException("You must call SlideShow.addHyperlink(Hyperlink link) first");
-        }
-
-        InteractiveInfo info = new InteractiveInfo();
-        InteractiveInfoAtom infoAtom = info.getInteractiveInfoAtom();
-
-        switch(link.getType()){
-            case HSLFHyperlink.LINK_FIRSTSLIDE:
-                infoAtom.setAction(InteractiveInfoAtom.ACTION_JUMP);
-                infoAtom.setJump(InteractiveInfoAtom.JUMP_FIRSTSLIDE);
-                infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_FirstSlide);
-                break;
-            case HSLFHyperlink.LINK_LASTSLIDE:
-                infoAtom.setAction(InteractiveInfoAtom.ACTION_JUMP);
-                infoAtom.setJump(InteractiveInfoAtom.JUMP_LASTSLIDE);
-                infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_LastSlide);
-                break;
-            case HSLFHyperlink.LINK_NEXTSLIDE:
-                infoAtom.setAction(InteractiveInfoAtom.ACTION_JUMP);
-                infoAtom.setJump(InteractiveInfoAtom.JUMP_NEXTSLIDE);
-                infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_NextSlide);
-                break;
-            case HSLFHyperlink.LINK_PREVIOUSSLIDE:
-                infoAtom.setAction(InteractiveInfoAtom.ACTION_JUMP);
-                infoAtom.setJump(InteractiveInfoAtom.JUMP_PREVIOUSSLIDE);
-                infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_PreviousSlide);
-                break;
-            case HSLFHyperlink.LINK_URL:
-                infoAtom.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
-                infoAtom.setJump(InteractiveInfoAtom.JUMP_NONE);
-                infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_Url);
-                break;
-            case HSLFHyperlink.LINK_SLIDENUMBER:
-                infoAtom.setAction(InteractiveInfoAtom.ACTION_HYPERLINK);
-                infoAtom.setJump(InteractiveInfoAtom.JUMP_NONE);
-                infoAtom.setHyperlinkType(InteractiveInfoAtom.LINK_SlideNumber);
-                break;
-            default:
-                logger.log(POILogger.WARN, "Ignore unknown hyperlink type : "+link.getLabel());
-                break;
-        }
-
-        infoAtom.setHyperlinkID(link.getId());
-
-        HSLFEscherClientDataRecord cldata = getClientData(true);
-        cldata.addChild(info);
-    }
-
     public Guide getAdjustValue(String name) {
         if (name == null || !name.matches("adj([1-9]|10)?")) {
             throw new IllegalArgumentException("Adjust value '"+name+"' not supported.");
@@ -653,4 +606,26 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H
             }
         }
     }
+
+    @Override
+    public HSLFHyperlink getHyperlink(){
+        return _hyperlink;
+    }
+    
+    @Override
+    public HSLFHyperlink createHyperlink() {
+        if (_hyperlink == null) {
+            _hyperlink = HSLFHyperlink.createHyperlink(this);
+        }
+        return _hyperlink;
+    }
+    
+    /**
+     * Sets the hyperlink - used when the document is parsed
+     *
+     * @param link the hyperlink
+     */
+    protected void setHyperlink(HSLFHyperlink link) {
+        _hyperlink = link;
+    }
 }
\ No newline at end of file
index fae9e205939ee3e7d7d776917daba9cd20d6e7bc..78abb0ab0f5d343583767a3bc2417a5b3fe716ad 100644 (file)
@@ -1010,28 +1010,6 @@ public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagrap
                return objectId;
        }
 
-       /**
-        * Add a hyperlink to this presentation
-        *
-        * @return 0-based index of the hyperlink
-        */
-       public int addHyperlink(HSLFHyperlink link) {
-               ExHyperlink ctrl = new ExHyperlink();
-               ExHyperlinkAtom obj = ctrl.getExHyperlinkAtom();
-        if(link.getType() == HSLFHyperlink.LINK_SLIDENUMBER) {
-            ctrl.setLinkURL(link.getAddress(), 0x30);
-        } else {
-            ctrl.setLinkURL(link.getAddress());
-        }
-               ctrl.setLinkTitle(link.getLabel());
-
-               int objectId = addToObjListAtom(ctrl);
-               link.setId(objectId);
-               obj.setNumber(objectId);
-
-               return objectId;
-       }
-
        /**
         * Add a embedded object to this presentation
         *
@@ -1104,11 +1082,7 @@ public final class HSLFSlideShow implements SlideShow<HSLFShape,HSLFTextParagrap
        }
 
        protected int addToObjListAtom(RecordContainer exObj) {
-               ExObjList lst = (ExObjList) _documentRecord.findFirstOfType(RecordTypes.ExObjList.typeID);
-               if (lst == null) {
-                       lst = new ExObjList();
-                       _documentRecord.addChildAfter(lst, _documentRecord.getDocumentAtom());
-               }
+               ExObjList lst = getDocumentRecord().getExObjList(true);
                ExObjListAtom objAtom = lst.getExObjListAtom();
                // increment the object ID seed
                int objectId = (int) objAtom.getObjectIDSeed() + 1;
index 5a050f5e135d8ba55737c7dad09eee8eb9ce0777..ca76f84ee3276a8c3428e454adc5eaf03f637599 100644 (file)
@@ -22,7 +22,6 @@ import static org.apache.poi.hslf.record.RecordTypes.OutlineTextRefAtom;
 import java.awt.Color;\r
 import java.io.IOException;\r
 import java.util.ArrayList;\r
-import java.util.Arrays;\r
 import java.util.Iterator;\r
 import java.util.List;\r
 \r
@@ -40,6 +39,7 @@ import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType;
 import org.apache.poi.hslf.record.ColorSchemeAtom;\r
 import org.apache.poi.hslf.record.EscherTextboxWrapper;\r
 import org.apache.poi.hslf.record.FontCollection;\r
+import org.apache.poi.hslf.record.InteractiveInfo;\r
 import org.apache.poi.hslf.record.MasterTextPropAtom;\r
 import org.apache.poi.hslf.record.OutlineTextRefAtom;\r
 import org.apache.poi.hslf.record.PPDrawing;\r
@@ -55,6 +55,7 @@ import org.apache.poi.hslf.record.TextCharsAtom;
 import org.apache.poi.hslf.record.TextHeaderAtom;\r
 import org.apache.poi.hslf.record.TextRulerAtom;\r
 import org.apache.poi.hslf.record.TextSpecInfoAtom;\r
+import org.apache.poi.hslf.record.TxInteractiveInfoAtom;\r
 import org.apache.poi.sl.draw.DrawPaint;\r
 import org.apache.poi.sl.usermodel.AutoNumberingScheme;\r
 import org.apache.poi.sl.usermodel.PaintStyle;\r
@@ -102,7 +103,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
     private StyleTextProp9Atom styleTextProp9Atom;\r
 \r
     private boolean _dirty = false;\r
-    \r
+\r
     private final List<HSLFTextParagraph> parentList;\r
 \r
     /**\r
@@ -162,14 +163,14 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
      * Setting a master style reference\r
      *\r
      * @param paragraphStyle the master style reference\r
-     * \r
+     *\r
      * @since POI 3.14-Beta1\r
      */\r
     @Internal\r
     /* package */ void setMasterStyleReference(TextPropCollection paragraphStyle) {\r
         _paragraphStyle = paragraphStyle;\r
     }\r
-    \r
+\r
     /**\r
      * Supply the Sheet we belong to, which might have an assigned SlideShow\r
      * Also passes it on to our child RichTextRuns\r
@@ -183,9 +184,8 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
         }\r
 \r
         assert(sheet.getSlideShow() != null);\r
-        applyHyperlinks(paragraphs);\r
     }\r
-    \r
+\r
     /**\r
      * Supply the Sheet we belong to, which might have an assigned SlideShow\r
      * Also passes it on to our child RichTextRuns\r
@@ -519,7 +519,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
             }\r
         }\r
     }\r
-    \r
+\r
     @Override\r
     public HSLFTextShape getParentShape() {\r
         return _parentShape;\r
@@ -603,12 +603,12 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
             if (_runs.isEmpty()) {\r
                 return null;\r
             }\r
-            \r
+\r
             SolidPaint sp = _runs.get(0).getFontColor();\r
             if(sp == null) {\r
                 return null;\r
             }\r
-            \r
+\r
             return DrawPaint.applyColorTransform(sp.getSolidColor());\r
         }\r
 \r
@@ -709,7 +709,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
      * Fetch the value of the given Paragraph related TextProp. Returns null if\r
      * that TextProp isn't present. If the TextProp isn't present, the value\r
      * from the appropriate Master Sheet will apply.\r
-     * \r
+     *\r
      * The propName can be a comma-separated list, in case multiple equivalent values\r
      * are queried\r
      */\r
@@ -733,12 +733,12 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
         }\r
 \r
         boolean isChar = props.getTextPropType() == TextPropType.character;\r
-        \r
+\r
         for (String pn : propNames) {\r
             TextProp prop = master.getStyleAttribute(txtype, paragraph.getIndentLevel(), pn, isChar);\r
             if (prop != null) return prop;\r
         }\r
-        \r
+\r
         return null;\r
     }\r
 \r
@@ -821,8 +821,21 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
      */\r
     protected static void storeText(List<HSLFTextParagraph> paragraphs) {\r
         fixLineEndings(paragraphs);\r
+        updateTextAtom(paragraphs);\r
+        updateStyles(paragraphs);\r
+        updateHyperlinks(paragraphs);\r
+        refreshRecords(paragraphs);\r
 \r
-        String rawText = toInternalString(getRawText(paragraphs));\r
+        for (HSLFTextParagraph p : paragraphs) {\r
+            p._dirty = false;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Set the correct text atom depending on the multibyte usage\r
+     */\r
+    private static void updateTextAtom(List<HSLFTextParagraph> paragraphs) {\r
+        final String rawText = toInternalString(getRawText(paragraphs));\r
 \r
         // Will it fit in a 8 bit atom?\r
         boolean isUnicode = StringUtil.hasMultibyte(rawText);\r
@@ -888,6 +901,16 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
             }\r
         }\r
 \r
+    }\r
+\r
+    /**\r
+     * Update paragraph and character styles - merges them when subsequential styles match\r
+     */\r
+    private static void updateStyles(List<HSLFTextParagraph> paragraphs) {\r
+        final String rawText = toInternalString(getRawText(paragraphs));\r
+        TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;\r
+        StyleTextPropAtom styleAtom = findStyleAtomPresent(headerAtom, rawText.length());\r
+\r
         // Update the text length for its Paragraph and Character stylings\r
         // * reset the length, to the new string's length\r
         // * add on +1 if the last block\r
@@ -933,7 +956,54 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
                 break;\r
             }\r
         }\r
+    }\r
 \r
+    private static void updateHyperlinks(List<HSLFTextParagraph> paragraphs) {\r
+        TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;\r
+        RecordContainer _txtbox = headerAtom.getParentRecord();\r
+        // remove existing hyperlink records\r
+        for (Record r : _txtbox.getChildRecords()) {\r
+            if (r instanceof InteractiveInfo || r instanceof TxInteractiveInfoAtom) {\r
+                _txtbox.removeChild(r);\r
+            }\r
+        }\r
+        // now go through all the textruns and check for hyperlinks\r
+        HSLFHyperlink lastLink = null;\r
+        for (HSLFTextParagraph para : paragraphs) {\r
+            for (HSLFTextRun run : para) {\r
+                HSLFHyperlink thisLink = run.getHyperlink();\r
+                if (thisLink != null && thisLink == lastLink) {\r
+                    // the hyperlink extends over this text run, increase its length\r
+                    // TODO: the text run might be longer than the hyperlink\r
+                    thisLink.setEndIndex(thisLink.getEndIndex()+run.getLength());\r
+                } else {\r
+                    if (lastLink != null) {\r
+                        InteractiveInfo info = lastLink.getInfo();\r
+                        TxInteractiveInfoAtom txinfo = lastLink.getTextRunInfo();\r
+                        assert(info != null && txinfo != null);\r
+                        _txtbox.appendChildRecord(info);\r
+                        _txtbox.appendChildRecord(txinfo);\r
+                    }\r
+                }\r
+                lastLink = thisLink;\r
+            }\r
+        }\r
+\r
+        if (lastLink != null) {\r
+            InteractiveInfo info = lastLink.getInfo();\r
+            TxInteractiveInfoAtom txinfo = lastLink.getTextRunInfo();\r
+            assert(info != null && txinfo != null);\r
+            _txtbox.appendChildRecord(info);\r
+            _txtbox.appendChildRecord(txinfo);\r
+        }    \r
+    }\r
+    \r
+    /**\r
+     * Writes the textbox records back to the document record \r
+     */\r
+    private static void refreshRecords(List<HSLFTextParagraph> paragraphs) {\r
+        TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;\r
+        RecordContainer _txtbox = headerAtom.getParentRecord();\r
         if (_txtbox instanceof EscherTextboxWrapper) {\r
             try {\r
                 ((EscherTextboxWrapper) _txtbox).writeOut(null);\r
@@ -941,10 +1011,6 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
                 throw new RuntimeException("failed dummy write", e);\r
             }\r
         }\r
-\r
-        for (HSLFTextParagraph p : paragraphs) {\r
-            p._dirty = false;\r
-        }\r
     }\r
 \r
     /**\r
@@ -1043,7 +1109,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
         }\r
         return sb.toString();\r
     }\r
-    \r
+\r
     @Override\r
     public String toString() {\r
         StringBuilder sb = new StringBuilder();\r
@@ -1266,20 +1332,46 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
 \r
     protected static void applyHyperlinks(List<HSLFTextParagraph> paragraphs) {\r
         List<HSLFHyperlink> links = HSLFHyperlink.find(paragraphs);\r
-        \r
+\r
         for (HSLFHyperlink h : links) {\r
             int csIdx = 0;\r
             for (HSLFTextParagraph p : paragraphs) {\r
-                for (HSLFTextRun r : p) {\r
-                    if (h.getStartIndex() <= csIdx && csIdx < h.getEndIndex()) {\r
-                        r.setHyperlinkId(h.getId());\r
+                if (csIdx > h.getEndIndex()) break;\r
+                List<HSLFTextRun> runs = p.getTextRuns();\r
+                for (int rlen=0,rIdx=0; rIdx < runs.size(); csIdx+=rlen, rIdx++) {\r
+                    HSLFTextRun run = runs.get(rIdx);\r
+                    rlen = run.getLength();\r
+                    if (csIdx < h.getEndIndex() && h.getStartIndex() < csIdx+rlen) {\r
+                        String rawText = run.getRawText();\r
+                        int startIdx = h.getStartIndex()-csIdx;\r
+                        if (startIdx > 0) {\r
+                            // hyperlink starts within current textrun\r
+                            HSLFTextRun newRun = new HSLFTextRun(p);\r
+                            newRun.setCharacterStyle(run.getCharacterStyle());\r
+                            newRun.setText(rawText.substring(startIdx));\r
+                            run.setText(rawText.substring(0, startIdx));\r
+                            runs.add(rIdx+1, newRun);\r
+                            rlen = startIdx;\r
+                            continue;\r
+                        }\r
+                        int endIdx = Math.min(rlen, h.getEndIndex()-h.getStartIndex());\r
+                        if (endIdx < rlen) {\r
+                            // hyperlink ends before end of current textrun\r
+                            HSLFTextRun newRun = new HSLFTextRun(p);\r
+                            newRun.setCharacterStyle(run.getCharacterStyle());\r
+                            newRun.setText(rawText.substring(0, endIdx));\r
+                            run.setText(rawText.substring(endIdx));\r
+                            runs.add(rIdx, newRun);\r
+                            rlen = endIdx;\r
+                            run = newRun;\r
+                        }\r
+                        run.setHyperlink(h);\r
                     }\r
-                    csIdx += r.getLength();\r
                 }\r
             }\r
         }\r
     }\r
-    \r
+\r
     protected static void applyCharacterStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> charStyles) {\r
         int paraIdx = 0, runIdx = 0;\r
         HSLFTextRun trun;\r
@@ -1444,7 +1536,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
     public boolean isDirty() {\r
         return _dirty;\r
     }\r
-    \r
+\r
     /**\r
      * Calculates the start index of the given text run\r
      *\r
index 2178d1d85f105d268057c01ae11bdd7cfadba538..427e2d2c5e3aebcc04873e3db6538cb70c609c5a 100644 (file)
@@ -27,10 +27,6 @@ import org.apache.poi.hslf.model.textproperties.CharFlagsTextProp;
 import org.apache.poi.hslf.model.textproperties.TextProp;
 import org.apache.poi.hslf.model.textproperties.TextPropCollection;
 import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType;
-import org.apache.poi.hslf.record.ExHyperlink;
-import org.apache.poi.hslf.record.InteractiveInfo;
-import org.apache.poi.hslf.record.InteractiveInfoAtom;
-import org.apache.poi.hslf.record.Record;
 import org.apache.poi.sl.draw.DrawPaint;
 import org.apache.poi.sl.usermodel.PaintStyle;
 import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
@@ -51,7 +47,7 @@ public final class HSLFTextRun implements TextRun {
        private HSLFTextParagraph parentParagraph;
        private String _runText = "";
        private String _fontFamily;
-       private int hyperlinkId = -1;
+       private HSLFHyperlink link;
        
        /**
         * Our paragraph and character style.
@@ -395,64 +391,27 @@ public final class HSLFTextRun implements TextRun {
     public byte getPitchAndFamily() {
         return 0;
     }
-    
+
     /**
-     * Sets the associated hyperlink id - currently this is only used while parsing and
-     * can't be used for update a ppt
+     * Sets the hyperlink - used when parsing the document
      *
-     * @param hyperlinkId the id or -1 to unset it
+     * @param link the hyperlink
      */
-    public void setHyperlinkId(int hyperlinkId) {
-        this.hyperlinkId = hyperlinkId;
+    protected void setHyperlink(HSLFHyperlink link) {
+        this.link = link;
     }
     
-    /**
-     * Returns the associated hyperlink id
-     *
-     * @return the hyperlink id
-     */
-    public int getHyperlinkId() {
-        return hyperlinkId;
+    @Override
+    public HSLFHyperlink getHyperlink() {
+        return link;
     }
     
-
     @Override
-    public HSLFHyperlink getHyperlink() {
-        if (hyperlinkId == -1) {
-            return null;
-        }
-        
-        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;
+    public HSLFHyperlink createHyperlink() {
+        if (link == null) {
+            link = HSLFHyperlink.createHyperlink(this);
+            parentParagraph.setDirty();
         }
-        
-        // 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;
     }
 }
index c90149854fcb3a4dbccc534457bcf4dcfb8ab0be..4f78bd6c8fa41ec8f4c60fe457c96adde14bfe00 100644 (file)
@@ -35,13 +35,10 @@ import org.apache.poi.ddf.EscherTextboxRecord;
 import org.apache.poi.hslf.exceptions.HSLFException;
 import org.apache.poi.hslf.model.HSLFMetroShape;
 import org.apache.poi.hslf.record.EscherTextboxWrapper;
-import org.apache.poi.hslf.record.InteractiveInfo;
-import org.apache.poi.hslf.record.InteractiveInfoAtom;
 import org.apache.poi.hslf.record.OEPlaceholderAtom;
 import org.apache.poi.hslf.record.PPDrawing;
 import org.apache.poi.hslf.record.RoundTripHFPlaceholder12;
 import org.apache.poi.hslf.record.TextHeaderAtom;
-import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
 import org.apache.poi.sl.draw.DrawFactory;
 import org.apache.poi.sl.draw.DrawTextShape;
 import org.apache.poi.sl.usermodel.Insets2D;
@@ -622,33 +619,6 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
         return getClientDataRecord(RoundTripHFPlaceholder12.typeID);
     }
     
-    /**
-     *
-     * Assigns a hyperlink to this text shape
-     *
-     * @param linkId    id of the hyperlink, @see org.apache.poi.hslf.usermodel.SlideShow#addHyperlink(Hyperlink)
-     * @param      beginIndex   the beginning index, inclusive.
-     * @param      endIndex     the ending index, exclusive.
-     * @see org.apache.poi.hslf.usermodel.HSLFSlideShow#addHyperlink(HSLFHyperlink)
-     */
-    public void setHyperlink(int linkId, int beginIndex, int endIndex){
-        //TODO validate beginIndex and endIndex and throw IllegalArgumentException
-
-        InteractiveInfo info = new InteractiveInfo();
-        InteractiveInfoAtom infoAtom = info.getInteractiveInfoAtom();
-        infoAtom.setAction(org.apache.poi.hslf.record.InteractiveInfoAtom.ACTION_HYPERLINK);
-        infoAtom.setHyperlinkType(org.apache.poi.hslf.record.InteractiveInfoAtom.LINK_Url);
-        infoAtom.setHyperlinkID(linkId);
-
-        _txtbox.appendChildRecord(info);
-
-        TxInteractiveInfoAtom txiatom = new TxInteractiveInfoAtom();
-        txiatom.setStartIndex(beginIndex);
-        txiatom.setEndIndex(endIndex);
-        _txtbox.appendChildRecord(txiatom);
-
-    }
-
     @Override
     public boolean isPlaceholder() {
         OEPlaceholderAtom oep = getPlaceholderAtom();
@@ -729,50 +699,38 @@ implements TextShape<HSLFShape,HSLFTextParagraph> {
         return HSLFTextParagraph.getRawText(getTextParagraphs());
     }
 
-    /**
-     * Returns the text contained in this text frame, which has been made safe
-     * for printing and other use.
-     *
-     * @return the text string for this textbox.
-     */
+    @Override
     public String getText() {
         String rawText = getRawText();
         return HSLFTextParagraph.toExternalString(rawText, getRunType());
     }
 
+    @Override
+    public HSLFTextRun appendText(String text, boolean newParagraph) {
+        // init paragraphs
+        List<HSLFTextParagraph> paras = getTextParagraphs();
+        HSLFTextRun htr = HSLFTextParagraph.appendText(paras, text, newParagraph);
+        setTextId(getRawText().hashCode());
+        return htr; 
+    }
 
-    // Update methods follow
-
-      /**
-       * Adds the supplied text onto the end of the TextParagraphs,
-       * creating a new RichTextRun for it to sit in.
-       *
-       * @param text the text string used by this object.
-       */
-      public HSLFTextRun appendText(String text, boolean newParagraph) {
-          // init paragraphs
-          List<HSLFTextParagraph> paras = getTextParagraphs();
-          return HSLFTextParagraph.appendText(paras, text, newParagraph);
-      }
-
-      @Override
-      public HSLFTextRun setText(String text) {
-          // init paragraphs
-          List<HSLFTextParagraph> paras = getTextParagraphs();
-          HSLFTextRun htr = HSLFTextParagraph.setText(paras, text);
-          setTextId(text.hashCode());
-          return htr;
-      }
-
-      /**
-       * Saves the modified paragraphs/textrun to the records.
-       * Also updates the styles to the correct text length.
-       */
-      protected void storeText() {
-          List<HSLFTextParagraph> paras = getTextParagraphs();
-          HSLFTextParagraph.storeText(paras);
-      }
-      // Accesser methods follow
+    @Override
+    public HSLFTextRun setText(String text) {
+        // init paragraphs
+        List<HSLFTextParagraph> 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<HSLFTextParagraph> paras = getTextParagraphs();
+        HSLFTextParagraph.storeText(paras);
+    }
 
     /**
      * Returns the array of all hyperlinks in this text run
index 715640a1d59419ffda68d857dc90e6bd00e49dac..7810929d789d16480c27da7b480299e178d90d53 100644 (file)
@@ -22,16 +22,25 @@ import static org.apache.poi.hslf.usermodel.HSLFTextParagraph.toExternalString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.poi.POIDataSamples;
-import org.apache.poi.hslf.usermodel.*;
+import org.apache.poi.hslf.HSLFTestDataSamples;
+import org.apache.poi.hslf.record.InteractiveInfoAtom;
+import org.apache.poi.hslf.usermodel.HSLFHyperlink;
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFTextBox;
+import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
+import org.apache.poi.hslf.usermodel.HSLFTextRun;
+import org.apache.poi.sl.usermodel.Hyperlink;
 import org.junit.Test;
 
 /**
  * Test Hyperlink.
- *
- * @author Yegor Kozlov
  */
 public final class TestHyperlink {
     private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance();
@@ -42,7 +51,7 @@ public final class TestHyperlink {
 
         HSLFSlide slide = ppt.getSlides().get(0);
         List<HSLFTextParagraph> 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<HSLFHyperlink> links = HSLFHyperlink.find(para);
-        assertNotNull(links);
+
+        List<HSLFHyperlink> links = findHyperlinks(para);
         assertEquals(2, links.size());
 
         assertEquals("http://jakarta.apache.org/poi/", links.get(0).getLabel());
@@ -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<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;
     }
 }
index bf596faed8d3679eacb9773f121558d1a8521d80..88f6884df481b60afbb23577260163bf2ea82eab 100644 (file)
@@ -38,7 +38,7 @@ public class TestExObjList extends TestCase {
                // Get the document
                Document doc = ss.getDocumentRecord();
                // Get the ExObjList
-               ExObjList exObjList = doc.getExObjList();
+               ExObjList exObjList = doc.getExObjList(false);
                assertNotNull(exObjList);
                assertEquals(1033l, exObjList.getRecordType());