]> source.dussan.org Git - poi.git/commitdiff
HSLF: readonly support for hyperlinks
authorYegor Kozlov <yegor@apache.org>
Wed, 30 May 2007 11:56:46 +0000 (11:56 +0000)
committerYegor Kozlov <yegor@apache.org>
Wed, 30 May 2007 11:56:46 +0000 (11:56 +0000)
git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@542804 13f79535-47bb-0310-9956-ffa450edef68

20 files changed:
src/documentation/content/xdocs/hslf/how-to-shapes.xml
src/scratchpad/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hslf/model/Hyperlink.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hslf/model/Shape.java
src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java
src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java
src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java
src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java
src/scratchpad/src/org/apache/poi/hslf/record/EscherTextboxWrapper.java
src/scratchpad/src/org/apache/poi/hslf/record/ExHyperlink.java
src/scratchpad/src/org/apache/poi/hslf/record/ExObjList.java
src/scratchpad/src/org/apache/poi/hslf/record/InteractiveInfoAtom.java
src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java
src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java
src/scratchpad/src/org/apache/poi/hslf/record/TxInteractiveInfoAtom.java [new file with mode: 0644]
src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java [new file with mode: 0644]
src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java
src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfo.java
src/scratchpad/testcases/org/apache/poi/hslf/record/TestInteractiveInfoAtom.java
src/scratchpad/testcases/org/apache/poi/hslf/record/TestTxInteractiveInfoAtom.java [new file with mode: 0644]

index 74e921f8fe192d3ba22f615431dbf644333f4c1a..df40776a53b1b7e759f4bd53918faf633e6bee3a 100644 (file)
@@ -38,6 +38,7 @@
                     <li><link href="#SlideTitle">How to set slide title</link></li>
                     <li><link href="#Fill">How to work with slide/shape background</link></li>
                     <li><link href="#Bullets">How to create bulleted lists</link></li>
+                    <li><link href="#Hyperlinks">Hyperlinks</link></li>
                 </ul>
             </section>
             <section><title>Features</title>
                 <anchor id="Bullets"/>
                 <section><title>How to create bulleted lists</title>
                     <source>
-        SlideShow ppt = new SlideShow();
-
-        Slide slide = ppt.createSlide();
-
-        TextBox shape = new TextBox();
-        RichTextRun rt = shape.getTextRun().getRichTextRuns()[0];
-        shape.setText(
-                "January\r" +
-                "February\r" +
-                "March\r" +
-                "April");
-        rt.setFontSize(42);
-        rt.setBullet(true);
-        rt.setBulletOffset(0);  //bullet offset
-        rt.setTextOffset(50);   //text offset (should be greater than bullet offset)
-        rt.setBulletChar('\u263A'); //bullet character
-        slide.addShape(shape);
+  SlideShow ppt = new SlideShow();
 
-        shape.setAnchor(new java.awt.Rectangle(50, 50, 500, 300));  //position of the text box in the slide
-        slide.addShape(shape);
+  Slide slide = ppt.createSlide();
 
-        FileOutputStream out = new FileOutputStream("bullets.ppt");
-        ppt.write(out);
-        out.close();
-                  </source>
+  TextBox shape = new TextBox();
+  RichTextRun rt = shape.getTextRun().getRichTextRuns()[0];
+  shape.setText(
+          "January\r" +
+          "February\r" +
+          "March\r" +
+          "April");
+  rt.setFontSize(42);
+  rt.setBullet(true);
+  rt.setBulletOffset(0);  //bullet offset
+  rt.setTextOffset(50);   //text offset (should be greater than bullet offset)
+  rt.setBulletChar('\u263A'); //bullet character
+  slide.addShape(shape);
+
+  shape.setAnchor(new java.awt.Rectangle(50, 50, 500, 300));  //position of the text box in the slide
+  slide.addShape(shape);
+
+  FileOutputStream out = new FileOutputStream("bullets.ppt");
+  ppt.write(out);
+  out.close();
+                </source>
+                </section>
+                <anchor id="Hyperlinks"/>
+                <section><title>How to read hyperlinks from a slide show</title>
+                    <source>
+    FileInputStream is = new FileInputStream("slideshow.ppt");
+    SlideShow ppt = new SlideShow(is);
+    is.close();
+
+    Slide[] slide = ppt.getSlides();
+    for (int j = 0; j &lt; slide.length; j++) {
+
+        //read hyperlinks from the text runs
+        TextRun[] txt = slide[j].getTextRuns();
+        for (int k = 0; k &lt; txt.length; k++) {
+            String text = txt[k].getText();
+            Hyperlink[] links = txt[k].getHyperlinks();
+            if(links != null) for (int l = 0; l &lt; links.length; l++) {
+                Hyperlink link = links[l];
+                String title = link.getTitle();
+                String address = link.getAddress();
+                String substring = text.substring(link.getStartIndex(), link.getEndIndex()-1); //in ppt end index is inclusive
+            }
+        }
+
+        //in PowerPoint you can assign a hyperlink to a shape without text,
+        //for example to a Line object. The code below demonstrates how to
+        //read such hyperlinks
+        Shape[] sh = slide[j].getShapes();
+        for (int k = 0; k &lt; sh.length; k++) {
+            Hyperlink link = sh[k].getHyperlink();
+            if(link != null)  {
+                String title = link.getTitle();
+                String address = link.getAddress();
+            }
+        }
+    }
+                </source>
                 </section>
                   
             </section>
diff --git a/src/scratchpad/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java
new file mode 100644 (file)
index 0000000..f919e54
--- /dev/null
@@ -0,0 +1,80 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.hslf.examples;\r
+\r
+import org.apache.poi.hslf.usermodel.SlideShow;\r
+import org.apache.poi.hslf.model.Slide;\r
+import org.apache.poi.hslf.model.TextRun;\r
+import org.apache.poi.hslf.model.Hyperlink;\r
+import org.apache.poi.hslf.model.Shape;\r
+\r
+import java.io.FileInputStream;\r
+\r
+/**\r
+ * Demonstrates how to read hyperlinks from  a presentation\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class Hyperlinks {\r
+\r
+    public static void main(String[] args) throws Exception {\r
+        for (int i = 0; i < args.length; i++) {\r
+            FileInputStream is = new FileInputStream(args[i]);\r
+            SlideShow ppt = new SlideShow(is);\r
+            is.close();\r
+\r
+            Slide[] slide = ppt.getSlides();\r
+            for (int j = 0; j < slide.length; j++) {\r
+                System.out.println("slide " + slide[j].getSlideNumber());\r
+\r
+                //read hyperlinks from the slide's text runs\r
+                System.out.println("reading hyperlinks from the text runs");\r
+                TextRun[] txt = slide[j].getTextRuns();\r
+                for (int k = 0; k < txt.length; k++) {\r
+                    String text = txt[k].getText();\r
+                    Hyperlink[] links = txt[k].getHyperlinks();\r
+                    if(links != null) for (int l = 0; l < links.length; l++) {\r
+                        Hyperlink link = links[l];\r
+                        String title = link.getTitle();\r
+                        String address = link.getAddress();\r
+                        System.out.println("  " + title);\r
+                        System.out.println("  " + address);\r
+                        String substring = text.substring(link.getStartIndex(), link.getEndIndex()-1);//in ppt end index is inclusive\r
+                        System.out.println("  " + substring);\r
+                    }\r
+                }\r
+\r
+                //in PowerPoint you can assign a hyperlink to a shape without text,\r
+                //for example to a Line object. The code below demonstrates how to\r
+                //read such hyperlinks\r
+                System.out.println("  reading hyperlinks from the slide's shapes");\r
+                Shape[] sh = slide[j].getShapes();\r
+                for (int k = 0; k < sh.length; k++) {\r
+                    Hyperlink link = sh[k].getHyperlink();\r
+                    if(link != null)  {\r
+                        String title = link.getTitle();\r
+                        String address = link.getAddress();\r
+                        System.out.println("  " + title);\r
+                        System.out.println("  " + address);\r
+                    }\r
+                }\r
+            }\r
+\r
+        }\r
+\r
+   }\r
+}\r
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Hyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/model/Hyperlink.java
new file mode 100644 (file)
index 0000000..21a4dc5
--- /dev/null
@@ -0,0 +1,168 @@
+\r
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.hslf.model;\r
+\r
+import org.apache.poi.hslf.record.*;\r
+import org.apache.poi.hslf.usermodel.SlideShow;\r
+import org.apache.poi.ddf.EscherContainerRecord;\r
+import org.apache.poi.ddf.EscherRecord;\r
+import org.apache.poi.ddf.EscherClientDataRecord;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Iterator;\r
+\r
+/**\r
+ * Represents a hyperlink in a PowerPoint document\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class Hyperlink {\r
+\r
+    private int type;\r
+    private String address;\r
+    private String title;\r
+    private int startIndex, endIndex;\r
+\r
+    /**\r
+     * Gets the type of the hyperlink action.\r
+     * Must be a <code>ACTION_*</code>  constant defined in <code>InteractiveInfoAtom</code>\r
+     *\r
+     * @return the hyperlink URL\r
+     * @see InteractiveInfoAtom\r
+     */\r
+    public int getType() {\r
+        return type;\r
+    }\r
+\r
+    /**\r
+     * Gets the hyperlink URL\r
+     *\r
+     * @return the hyperlink URL\r
+     */\r
+    public String getAddress() {\r
+        return address;\r
+    }\r
+\r
+    /**\r
+     * Gets the hyperlink user-friendly title (if different from URL)\r
+     *\r
+     * @return the  hyperlink user-friendly title\r
+     */\r
+    public String getTitle() {\r
+        return title;\r
+    }\r
+\r
+    /**\r
+     * Gets the beginning character position\r
+     *\r
+     * @return the beginning character position\r
+     */\r
+    public int getStartIndex() {\r
+        return startIndex;\r
+    }\r
+\r
+    /**\r
+     * Gets the ending character position\r
+     *\r
+     * @return the ending character position\r
+     */\r
+    public int getEndIndex() {\r
+        return endIndex;\r
+    }\r
+\r
+    /**\r
+     * Find hyperlinks in a text run\r
+     *\r
+     * @param run  <code>TextRun</code> to lookup hyperlinks in\r
+     * @return found hyperlinks or <code>null</code> if not found\r
+     */\r
+    protected static Hyperlink[] find(TextRun run){\r
+        ArrayList lst = new ArrayList();\r
+        SlideShow ppt = run.getSheet().getSlideShow();\r
+        //document-level container which stores info about all links in a presentation\r
+        ExObjList exobj = ppt.getDocumentRecord().getExObjList();\r
+        if (exobj == null) {\r
+            return null;\r
+        }\r
+        Record[] records = run._records;\r
+        if(records != null) find(records, exobj, lst);\r
+\r
+        Hyperlink[] links = null;\r
+        if (lst.size() > 0){\r
+            links = new Hyperlink[lst.size()];\r
+            lst.toArray(links);\r
+        }\r
+        return links;\r
+    }\r
+\r
+    /**\r
+     * Find hyperlink assigned to the supplied shape\r
+     *\r
+     * @param shape  <code>Shape</code> to lookup hyperlink in\r
+     * @return found hyperlink or <code>null</code>\r
+     */\r
+    protected static Hyperlink find(Shape shape){\r
+        ArrayList lst = new ArrayList();\r
+        SlideShow ppt = shape.getSheet().getSlideShow();\r
+        //document-level container which stores info about all links in a presentation\r
+        ExObjList exobj = ppt.getDocumentRecord().getExObjList();\r
+        if (exobj == null) {\r
+            return null;\r
+        }\r
+\r
+        EscherContainerRecord spContainer = shape.getSpContainer();\r
+        List spchild = spContainer.getChildRecords();\r
+        for (Iterator it = spchild.iterator(); it.hasNext(); ) {\r
+            EscherRecord obj = (EscherRecord)it.next();\r
+            if (obj.getRecordId() ==  EscherClientDataRecord.RECORD_ID){\r
+                byte[] data = ((EscherContainerRecord)obj).serialize();\r
+                Record[] records = Record.findChildRecords(data, 8, data.length-8);\r
+                if(records != null) find(records, exobj, lst);\r
+            }\r
+        }\r
+\r
+        return lst.size() == 1 ? (Hyperlink)lst.get(0) : null;\r
+    }\r
+\r
+    private static void find(Record[] records, ExObjList exobj, List out){\r
+        for (int i = 0; i < records.length; i++) {\r
+            //see if we have InteractiveInfo in the textrun's records\r
+            if( records[i] instanceof InteractiveInfo){\r
+                InteractiveInfo hldr = (InteractiveInfo)records[i];\r
+                InteractiveInfoAtom info = hldr.getInteractiveInfoAtom();\r
+                int id = info.getHyperlinkID();\r
+                ExHyperlink linkRecord = exobj.get(id);\r
+                if (linkRecord != null){\r
+                    Hyperlink link = new Hyperlink();\r
+                    link.title = linkRecord.getLinkTitle();\r
+                    link.address = linkRecord.getLinkURL();\r
+                    link.type = info.getAction();\r
+\r
+                    if (++i < records.length && records[i] instanceof TxInteractiveInfoAtom){\r
+                        TxInteractiveInfoAtom txinfo = (TxInteractiveInfoAtom)records[i];\r
+                        link.startIndex = txinfo.getStartIndex();\r
+                        link.endIndex = txinfo.getEndIndex();\r
+                    }\r
+                    out.add(link);\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
index 65827bf94d1afcc9d217d02340a636d15dac6f5a..ffe89e03df173109adea2cd97369f6d4ae9fbde2 100644 (file)
@@ -45,7 +45,7 @@ public abstract class Shape {
 
     // For logging
     protected POILogger logger = POILogFactory.getLogger(this.getClass());
-    
+
     /**
      * In Escher absolute distances are specified in
      * English Metric Units (EMUs), occasionally referred to as A units;
@@ -309,4 +309,15 @@ public abstract class Shape {
         return new Fill(this);
     }
 
+
+    /**
+     * Returns the hyperlink assigned to this shape
+     *
+     * @return the hyperlink assigned to this shape
+     * or <code>null</code> if not found.
+     */
+     public Hyperlink getHyperlink(){
+        return Hyperlink.find(this);
+    }
+
 }
index d474a22f6f926585568975e1f78b6e4b910826e5..aeb34763ec6e6297a5f0f81f9d44fcae499ab8d9 100644 (file)
@@ -211,4 +211,13 @@ public class ShapeGroup extends Shape{
         return spRecord.getOptions() >> 4;
     }
 
+    /**
+     * Returns <code>null</code> - shape groups can't have hyperlinks
+     *
+     * @return <code>null</code>.
+     */
+     public Hyperlink getHyperlink(){
+        return null;
+    }
+    
 }
index c53f7f9edddae1c7fd25329abbbedd4b48473c82..b1761b2d9a50b077015434da8c74b8c2a8a444bf 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.poi.hslf.model;
 import org.apache.poi.ddf.EscherContainerRecord;
 import org.apache.poi.ddf.EscherDgRecord;
 import org.apache.poi.ddf.EscherRecord;
+import org.apache.poi.ddf.EscherSpRecord;
 import org.apache.poi.hslf.record.*;
 import org.apache.poi.hslf.usermodel.SlideShow;
 
@@ -127,7 +128,13 @@ public abstract class Sheet {
         Vector runsV = new Vector();
         EscherTextboxWrapper[] wrappers = ppdrawing.getTextboxWrappers();
         for (int i = 0; i < wrappers.length; i++) {
+            int s1 = runsV.size();
             findTextRuns(wrappers[i].getChildRecords(), runsV);
+            int s2 = runsV.size();
+            if (s2 != s1){
+                TextRun t = (TextRun) runsV.get(runsV.size()-1);
+                t.setShapeId(wrappers[i].getShapeId());
+            }
         }
         TextRun[] runs = new TextRun[runsV.size()];
         for (int i = 0; i < runs.length; i++) {
@@ -176,6 +183,15 @@ public abstract class Sheet {
                 }
 
                 if (trun != null) {
+                    ArrayList lst = new ArrayList();
+                    for (int j = i; j < records.length; j++) {
+                        if(j > i && records[j] instanceof TextHeaderAtom) break;
+                        lst.add(records[j]);
+                    }
+                    Record[] recs = new Record[lst.size()];
+                    lst.toArray(recs);
+                    trun._records = recs;
+                    
                     found.add(trun);
                     i++;
                 } else {
@@ -232,6 +248,11 @@ public abstract class Sheet {
         EscherDgRecord dg = (EscherDgRecord) Shape.getEscherChild(dgContainer, EscherDgRecord.RECORD_ID);
         dg.setNumShapes(dg.getNumShapes() + 1);
 
+        int shapeId = dg.getLastMSOSPID()+1;
+        dg.setLastMSOSPID(shapeId);
+
+        EscherSpRecord sp = shape.getSpContainer().getChildById(EscherSpRecord.RECORD_ID);
+        if(sp != null) sp.setShapeId(shapeId);
         shape.setSheet(this);
         shape.afterInsert(this);
 
index 82d9a4de7f369f56ceaa333ac8158368e206be88..2d5c866f775534639fc5b08fbdab89415bae3b2a 100644 (file)
@@ -28,7 +28,6 @@ import java.awt.*;
 import java.awt.font.FontRenderContext;
 import java.awt.font.TextLayout;
 import java.io.IOException;
-import java.util.Vector;
 
 /**
  * Represents a TextFrame shape in PowerPoint.
@@ -170,7 +169,7 @@ public class TextBox extends SimpleShape {
      * @return the text string for this textbox.
      */
      public String getText(){
-        return _txtrun.getText();        
+        return _txtrun == null ? null : _txtrun.getText();
     }
 
     /**
@@ -452,56 +451,37 @@ public class TextBox extends SimpleShape {
     }
 
     private void initTextRun(){
-        TextHeaderAtom tha = null;
-        TextCharsAtom tca = null;
-        TextBytesAtom tba = null;
-        StyleTextPropAtom sta = null;
         OutlineTextRefAtom ota = null;
         
         // Find the interesting child records 
         Record[] child = _txtbox.getChildRecords();
         for (int i = 0; i < child.length; i++) {
-            if (child[i] instanceof TextHeaderAtom) tha = (TextHeaderAtom)child[i];
-            else if (child[i] instanceof TextBytesAtom) tba = (TextBytesAtom)child[i];
-            else if (child[i] instanceof StyleTextPropAtom) sta = (StyleTextPropAtom)child[i];
-            else if (child[i] instanceof OutlineTextRefAtom) ota = (OutlineTextRefAtom)child[i];
-            else if (child[i] instanceof TextCharsAtom) tca = (TextCharsAtom)child[i];
+            if (child[i] instanceof OutlineTextRefAtom) {
+                ota = (OutlineTextRefAtom)child[i];
+                break;
+            }
         }
 
-        // Special handling for cases where there's an OutlineTextRefAtom
+        Sheet sheet = getSheet();
+        TextRun[] runs = sheet.getTextRuns();
         if (ota != null) {
-            // TextHeaderAtom, TextBytesAtom and StyleTextPropAtom are
-               //  stored outside of  EscherContainerRecord
             int idx = ota.getTextIndex();
-            Slide sl = (Slide)getSheet();
-            Record[] rec = sl.getSlideAtomsSet().getSlideRecords();
-            for (int i = 0, j = 0; i < rec.length; i++) {
-                if(rec[i].getRecordType() == RecordTypes.TextHeaderAtom.typeID){
-                    if(j++ == idx) { //we found j-th  TextHeaderAtom, read the text data
-                        for (int k = i; k < rec.length; k++) {
-                            if (rec[k] instanceof TextHeaderAtom) {
-                                if (tha != null) break;
-                                else tha = (TextHeaderAtom)rec[k];
-                            }
-                            else if (rec[k] instanceof TextBytesAtom) tba = (TextBytesAtom)rec[k];
-                            else if (rec[k] instanceof TextCharsAtom) tca = (TextCharsAtom)rec[k];
-                            else if (rec[k] instanceof StyleTextPropAtom) sta = (StyleTextPropAtom)rec[k];
-                        }
-                    }
-                }
+            if(idx < runs.length) _txtrun = runs[idx];
+            if(_txtrun == null) {
+                logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx);
             }
-        }
-        
-        // If we found the records we needed, create a TextRun
-        if(tba != null) {
-               // Bytes based Text Run
-               _txtrun = new TextRun(tha,tba,sta);
-        } else if (tca != null) {
-               // Characters (unicode) based Text Run
-               _txtrun = new TextRun(tha,tca,sta);
         } else {
-               // Empty text box
-               logger.log(POILogger.WARN, "no text records found for TextBox");
+            int shapeId = _escherContainer.getChildById(EscherSpRecord.RECORD_ID).getShapeId();
+            if(runs != null) for (int i = 0; i < runs.length; i++) {
+                if(runs[i].getShapeId() == shapeId){
+                    _txtrun = runs[i];
+                    break;
+                }
+            }
+            if(_txtrun == null) {
+                logger.log(POILogger.WARN, "text run not found for shapeId=" + shapeId);
+            }
         }
+
     }
 }
index 8e6ae61627cfa901b62da229525703e251c0ee8c..7770311477090ca94fb60c1eba999cee10ed34f3 100644 (file)
@@ -49,6 +49,12 @@ public class TextRun
        protected RichTextRun[] _rtRuns;
        private SlideShow slideShow;
     private Sheet sheet;
+    private int shapeId;
+    /**
+     * all text run records that follow TextHeaderAtom.
+     * (there can be misc InteractiveInfo, TxInteractiveInfo and other records)
+     */
+    protected Record[] _records;
 
        /**
        * Constructs a Text Run from a Unicode text block
@@ -517,4 +523,28 @@ public class TextRun
     public Sheet getSheet(){
         return this.sheet;        
     }
+
+    /**
+     * @return  Shape ID
+     */
+    protected int getShapeId(){
+        return shapeId;
+    }
+
+    /**
+     *  @param id Shape ID
+     */
+    protected void setShapeId(int id){
+        shapeId = id;
+    }
+
+    /**
+     * Returns the array of all hyperlinks in this text run
+     *
+     * @return the array of all hyperlinks in this text run
+     * or <code>null</code> if not found.
+     */
+    public Hyperlink[] getHyperlinks(){
+        return Hyperlink.find(this);
+    }
 }
index 4dd0d76ab13453ab400768dc5d361415dc883ff5..142eee88fcc08481b7b91554729be019e97b818a 100644 (file)
@@ -39,6 +39,7 @@ public class EscherTextboxWrapper extends RecordContainer
 {
        private EscherTextboxRecord _escherRecord;
        private long _type;
+    private int shapeId;
 
        /**
         * Returns the underlying DDF Escher Record
@@ -93,4 +94,18 @@ public class EscherTextboxWrapper extends RecordContainer
                // Save in the escher layer
                _escherRecord.setData(data);
        }
+
+    /**
+     * @return  Shape ID
+     */
+    public int getShapeId(){
+        return shapeId;
+    }
+
+    /**
+     *  @param id Shape ID
+     */
+    public void setShapeId(int id){
+        shapeId = id;
+    }
 }
index 8ccc28fb9dd3cf402c99a3f8d2ba9b3745ccc64b..8ba58cdb61efaef31b13eee0da202a0cebd5f01c 100644 (file)
@@ -42,13 +42,22 @@ public class ExHyperlink extends RecordContainer {
        
        /**
         * Returns the URL of the link.
-        * TODO: Figure out which of detailsA or detailsB is the
-        *  one that always holds it 
+     *
+     * @return the URL of the link
         */
        public String getLinkURL() {
-               return linkDetailsA.getText();
+               return linkDetailsB == null ? null : linkDetailsB.getText();
        }
-       
+
+    /**
+     * Returns the hyperlink's user-readable name
+     *
+     * @return the hyperlink's user-readable name
+     */
+    public String getLinkTitle() {
+        return linkDetailsA == null ? null : linkDetailsA.getText();
+    }
+
        /**
         * Sets the URL of the link
         * TODO: Figure out if we should always set both
@@ -66,13 +75,13 @@ public class ExHyperlink extends RecordContainer {
         * Get the link details (field A)
         */
        public String _getDetailsA() {
-               return linkDetailsA.getText();
+               return linkDetailsA == null ? null : linkDetailsA.getText();
        }
        /**
         * Get the link details (field B)
         */
        public String _getDetailsB() {
-               return linkDetailsB.getText();
+               return linkDetailsB == null ? null : linkDetailsB.getText();
        }
 
        /** 
index 95fe5c967c821f93ef015c29bfca303c723b5da9..a511ef609500f12b6968db55640a78dd9f68efaa 100644 (file)
@@ -109,4 +109,21 @@ public class ExObjList extends RecordContainer {
                writeOut(_header[0],_header[1],_type,_children,out);
        }
 
+    /**
+     * Lookup a hyperlink by its unique id
+     *
+     * @param id hyperlink id
+     * @return found <code>ExHyperlink</code> or <code>null</code>
+     */
+    public ExHyperlink get(int id){
+        for(int i=0; i<_children.length; i++) {
+            if(_children[i] instanceof ExHyperlink) {
+                ExHyperlink rec = (ExHyperlink)_children[i];
+                if (rec.getExHyperlinkAtom().getNumber() == id){
+                    return rec;
+                }
+            }
+        }
+        return null;
+    }
 }
index 40200d1301d0556696d19d4d5e1878d40fdee653..8943f882d8b448c3eec0b04e84d091ed67e612fe 100644 (file)
@@ -30,10 +30,37 @@ import org.apache.poi.util.LittleEndian;
  * (The actual link is held Document.ExObjList.ExHyperlink)
  *
  * @author Nick Burch
+ * @author Yegor Kozlov
  */
 
 public class InteractiveInfoAtom extends RecordAtom
 {
+
+    /**
+     * Action Table
+     */
+    public static final int ACTION_NONE = 0;
+    public static final int ACTION_MACRO = 1;
+    public static final int ACTION_RUNPROGRAM = 2;
+    public static final int ACTION_JUMP = 3;
+    public static final int ACTION_HYPERLINK = 4;
+    public static final int ACTION_OLE = 5;
+    public static final int ACTION_MEDIA = 6;
+    public static final int ACTION_CUSTOMSHOW = 7;
+
+    /**
+     *  Jump Table
+     */
+    public static final int JUMP_NONE = 0;
+    public static final int JUMP_NEXTSLIDE = 1;
+    public static final int JUMP_PREVIOUSSLIDE = 2;
+    public static final int JUMP_FIRSTSLIDE = 3;
+    public static final int JUMP_LASTSLIDE = 4;
+    public static final int JUMP_LASTSLIDEVIEWED = 5;
+    public static final int JUMP_ENDSHOW = 6;
+
+
+
     /**
      * Record header.
      */
@@ -90,46 +117,138 @@ public class InteractiveInfoAtom extends RecordAtom
      *  ExHyperlink with this number to get the details.
      * @return the link number
      */
-    public int getNumber() {
+    public int getHyperlinkID() {
         return LittleEndian.getInt(_data,4);
     }
 
     /**
-     * Sets the link number
-     * @param number the link number.
+     * Sets the persistent unique identifier of the link
+     *
+     * @param number the persistent unique identifier of the link
      */
-    public void setNumber(int number) {
+    public void setHyperlinkID(int number) {
         LittleEndian.putInt(_data,4,number);
     }
     
     /**
-     * Get the first number - meaning unknown
+     * a reference to a sound in the sound collection.
      */
-    public int _getNumber1() { 
+    public int getSoundRef() {
         return LittleEndian.getInt(_data,0);
     }
-    protected void _setNumber1(int val) {
+    /**
+     * a reference to a sound in the sound collection.
+     *
+     * @param val a reference to a sound in the sound collection
+     */
+    public void setSoundRef(int val) {
        LittleEndian.putInt(_data, 0, val);
     }
 
     /**
-     * Get the third number - meaning unknown
+     * Hyperlink Action.
+     * <p>
+     * see <code>ACTION_*</code> constants for the list of actions
+     * </p>
+     *
+     * @return hyperlink action.
+     */
+    public byte getAction() {
+        return _data[8];
+    }
+
+    /**
+     * Hyperlink Action
+     * <p>
+     * see <code>ACTION_*</code> constants for the list of actions
+     * </p>
+     *
+     * @param val hyperlink action.
      */
-    public int _getNumber3() { 
-        return LittleEndian.getInt(_data,8);
+    public void setAction(byte val) {
+       _data[8] = val;
     }
-    protected void _setNumber3(int val) {
-       LittleEndian.putInt(_data, 8, val);
+
+    /**
+     * Only valid when action == OLEAction. OLE verb to use, 0 = first verb, 1 = second verb, etc.
+     */
+    public byte getOleVerb() {
+        return _data[9];
     }
 
     /**
-     * Get the fourth number - meaning unknown
+     * Only valid when action == OLEAction. OLE verb to use, 0 = first verb, 1 = second verb, etc.
      */
-    public int _getNumber4() { 
-        return LittleEndian.getInt(_data,12);
+    public void setOleVerb(byte val) {
+       _data[9] = val;
     }
-    protected void _setNumber4(int val) {
-       LittleEndian.putInt(_data, 12, val);
+
+    /**
+     * Jump
+     * <p>
+     * see <code>JUMP_*</code> constants for the list of actions
+     * </p>
+     *
+     * @return jump
+     */
+    public byte getJump() {
+        return _data[10];
+    }
+
+    /**
+     * Jump
+     * <p>
+     * see <code>JUMP_*</code> constants for the list of actions
+     * </p>
+     *
+     * @param val jump
+     */
+    public void setJump(byte val) {
+       _data[10] = val;
+    }
+
+    /**
+     * Flags
+     * <p>
+     * <li> Bit 1: Animated. If 1, then button is animated
+     * <li> Bit 2: Stop sound. If 1, then stop current sound when button is pressed.
+     * <li> Bit 3: CustomShowReturn. If 1, and this is a jump to custom show,
+     *   then return to this slide after custom show.
+     * </p>
+     */
+    public byte getFlags() {
+        return _data[11];
+    }
+
+    /**
+     * Flags
+     * <p>
+     * <li> Bit 1: Animated. If 1, then button is animated
+     * <li> Bit 2: Stop sound. If 1, then stop current sound when button is pressed.
+     * <li> Bit 3: CustomShowReturn. If 1, and this is a jump to custom show,
+     *   then return to this slide after custom show.
+     * </p>
+     */
+    public void setFlags(byte val) {
+       _data[11] = val;
+    }
+
+    /**
+     * hyperlink type
+     *
+     * @return hyperlink type
+     */
+    public byte getHyperlinkType() {
+        return _data[12];
+    }
+
+    /**
+     * hyperlink type
+     *
+     * @param val hyperlink type
+     */
+    public void setHyperlinkType(byte val) {
+       _data[12] = val;
     }
 
     /**
index e34e9d841ffc117b8c0fa0c9c4f3c63a88cfc8e6..db0d434aee673ef1dec18aff1b5723e236e33b44 100644 (file)
@@ -147,6 +147,13 @@ public class PPDrawing extends RecordAtom
                                EscherTextboxRecord tbr = (EscherTextboxRecord)toSearch[i];
                                EscherTextboxWrapper w = new EscherTextboxWrapper(tbr);
                                found.add(w);
+                for (int j = i; j >= 0; j--) {
+                    if(toSearch[j] instanceof EscherSpRecord){
+                        EscherSpRecord sp = (EscherSpRecord)toSearch[j];
+                        w.setShapeId(sp.getShapeId());
+                        break;
+                    }
+                }
                        } else {
                                // If it has children, walk them
                                if(toSearch[i].isContainerRecord()) {
index 3183bd553a705325f35a5b14abafb8d48fd62b6b..3c8d3aa41c9d9201fc5f9b8502723de3bbbaef19 100644 (file)
@@ -112,7 +112,7 @@ public class RecordTypes {
     public static final Type SlideNumberMCAtom = new Type(4056,null);
     public static final Type HeadersFooters = new Type(4057,null);
     public static final Type HeadersFootersAtom = new Type(4058,null);
-    public static final Type TxInteractiveInfoAtom = new Type(4063,null);
+    public static final Type TxInteractiveInfoAtom = new Type(4063,TxInteractiveInfoAtom.class);
     public static final Type CharFormatAtom = new Type(4066,null);
     public static final Type ParaFormatAtom = new Type(4067,null);
     public static final Type RecolorInfoAtom = new Type(4071,null);
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/TxInteractiveInfoAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/TxInteractiveInfoAtom.java
new file mode 100644 (file)
index 0000000..b3db6ba
--- /dev/null
@@ -0,0 +1,122 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.hslf.record;\r
+\r
+import org.apache.poi.util.LittleEndian;\r
+\r
+import java.io.OutputStream;\r
+import java.io.IOException;\r
+\r
+/**\r
+ * Tne atom that holds starting and ending character positions of a hyperlink\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class TxInteractiveInfoAtom extends RecordAtom {\r
+    /**\r
+     * Record header.\r
+     */\r
+    private byte[] _header;\r
+\r
+    /**\r
+     * Record data.\r
+     */\r
+    private byte[] _data;\r
+\r
+    /**\r
+     * Constructs a brand new link related atom record.\r
+     */\r
+    protected TxInteractiveInfoAtom() {\r
+        _header = new byte[8];\r
+        _data = new byte[8];\r
+\r
+        LittleEndian.putShort(_header, 2, (short)getRecordType());\r
+        LittleEndian.putInt(_header, 4, _data.length);\r
+    }\r
+\r
+    /**\r
+     * Constructs the link related atom record from its\r
+     *  source data.\r
+     *\r
+     * @param source the source data as a byte array.\r
+     * @param start the start offset into the byte array.\r
+     * @param len the length of the slice in the byte array.\r
+     */\r
+    protected TxInteractiveInfoAtom(byte[] source, int start, int len) {\r
+        // Get the header.\r
+        _header = new byte[8];\r
+        System.arraycopy(source,start,_header,0,8);\r
+\r
+        // Get the record data.\r
+        _data = new byte[len-8];\r
+        System.arraycopy(source,start+8,_data,0,len-8);\r
+\r
+    }\r
+\r
+    /**\r
+     * Gets the beginning character position\r
+     *\r
+     * @return the beginning character position\r
+     */\r
+    public int getStartIndex() {\r
+        return LittleEndian.getInt(_data, 0);\r
+    }\r
+\r
+    /**\r
+     * Sets the beginning character position\r
+     * @param idx the beginning character position\r
+     */\r
+    public void setStartIndex(int idx) {\r
+        LittleEndian.putInt(_data, 0, idx);\r
+    }\r
+\r
+    /**\r
+     * Gets the ending character position\r
+     *\r
+     * @return the ending character position\r
+     */\r
+    public int getEndIndex() {\r
+        return LittleEndian.getInt(_data, 4);\r
+    }\r
+\r
+    /**\r
+     * Sets the ending character position\r
+     *\r
+     * @param idx the ending character position\r
+     */\r
+    public void setEndIndex(int idx) {\r
+        LittleEndian.putInt(_data, 4, idx);\r
+    }\r
+\r
+    /**\r
+     * Gets the record type.\r
+     * @return the record type.\r
+     */\r
+    public long getRecordType() { return RecordTypes.TxInteractiveInfoAtom.typeID; }\r
+\r
+    /**\r
+     * Write the contents of the record back, so it can be written\r
+     * to disk\r
+     *\r
+     * @param out the output stream to write to.\r
+     * @throws java.io.IOException if an error occurs.\r
+     */\r
+    public void writeOut(OutputStream out) throws IOException {\r
+        out.write(_header);\r
+        out.write(_data);\r
+    }\r
+}\r
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java
new file mode 100644 (file)
index 0000000..9108d3c
--- /dev/null
@@ -0,0 +1,89 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.hslf.model;\r
+\r
+import junit.framework.TestCase;\r
+\r
+import java.io.FileInputStream;\r
+import java.io.File;\r
+\r
+import org.apache.poi.hslf.usermodel.SlideShow;\r
+\r
+/**\r
+ * Test Hyperlink.\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class TestHyperlink extends TestCase {\r
+    protected String cwd = System.getProperty("HSLF.testdata.path");\r
+\r
+    public void testTextRunHyperlinks() throws Exception {\r
+        FileInputStream is = new FileInputStream(new File(cwd, "WithLinks.ppt"));\r
+        SlideShow ppt = new SlideShow(is);\r
+        is.close();\r
+\r
+        TextRun[] run;\r
+        Slide slide;\r
+        slide = ppt.getSlides()[0];\r
+        run = slide.getTextRuns();\r
+        for (int i = 0; i < run.length; i++) {\r
+            String text = run[i].getText();\r
+            if (text.equals(\r
+                    "This page has two links:\n" +\r
+                    "http://jakarta.apache.org/poi/\n" +\r
+                    "\n" +\r
+                    "http://slashdot.org/\n" +\r
+                    "\n" +\r
+                    "In addition, its notes has one link")){\r
+\r
+                Hyperlink[] links = run[i].getHyperlinks();\r
+                assertNotNull(links);\r
+                assertEquals(2, links.length);\r
+\r
+                assertEquals("http://jakarta.apache.org/poi/", links[0].getTitle());\r
+                assertEquals("http://jakarta.apache.org/poi/", links[0].getAddress());\r
+                assertEquals("http://jakarta.apache.org/poi/", text.substring(links[0].getStartIndex(), links[0].getEndIndex()-1));\r
+\r
+                assertEquals("http://slashdot.org/", links[1].getTitle());\r
+                assertEquals("http://slashdot.org/", links[1].getAddress());\r
+                assertEquals("http://slashdot.org/", text.substring(links[1].getStartIndex(), links[1].getEndIndex()-1));\r
+\r
+            }\r
+        }\r
+\r
+        slide = ppt.getSlides()[1];\r
+        run = slide.getTextRuns();\r
+        for (int i = 0; i < run.length; i++) {\r
+            String text = run[i].getText();\r
+            if (text.equals(\r
+                    "I have the one link:\n" +\r
+                    "Jakarta HSSF")){\r
+\r
+                Hyperlink[] links = run[i].getHyperlinks();\r
+                assertNotNull(links);\r
+                assertEquals(1, links.length);\r
+\r
+                assertEquals("http://jakarta.apache.org/poi/hssf/", links[0].getTitle());\r
+                assertEquals("http://jakarta.apache.org/poi/hssf/", links[0].getAddress());\r
+                assertEquals("Jakarta HSSF", text.substring(links[0].getStartIndex(), links[0].getEndIndex()-1));\r
+\r
+            }\r
+        }\r
+\r
+    }\r
+\r
+}\r
index 4bd167898368d0ddebccf2e14b300cf4543b761f..fd637774c08af023cb4d0984a72bd54b4eac8dba 100644 (file)
@@ -161,7 +161,8 @@ public class TestShapes extends TestCase {
         out.close();
 
         ppt = new SlideShow(new HSLFSlideShow(new ByteArrayInputStream(out.toByteArray())));
-
+        sl = ppt.getSlides()[0];
+        
         txtbox = (TextBox)sl.getShapes()[0];
         rt = txtbox.getTextRun().getRichTextRuns()[0];
 
index d3d61fbb9d7ec412aec961708918aa6cba4acc7f..ad48bd1dcdc58d97cff2894c10145ff24cc9c46c 100644 (file)
@@ -48,7 +48,7 @@ public class TestInteractiveInfo extends TestCase {
                InteractiveInfo ii = new InteractiveInfo(data_a, 0, data_a.length);
                InteractiveInfoAtom ia = ii.getInteractiveInfoAtom();
                
-               assertEquals(1, ia.getNumber());
+               assertEquals(1, ia.getHyperlinkID());
     }
     
        public void testWrite() throws Exception {
@@ -69,10 +69,10 @@ public class TestInteractiveInfo extends TestCase {
        InteractiveInfoAtom ia = ii.getInteractiveInfoAtom();
        
        // Set values
-       ia.setNumber(1);
-       ia._setNumber1(0);
-       ia._setNumber3(4);
-       ia._setNumber4(8);
+       ia.setHyperlinkID(1);
+       ia.setSoundRef(0);
+       ia.setAction((byte)4);
+       ia.setHyperlinkType((byte)8);
        
                // Check it's now the same as a
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
index d001ac35de5e01d1666aecf95cccdbaba060116b..df945f5a22a21a198b47bccb4d57d87628270fd2 100644 (file)
@@ -53,22 +53,22 @@ public class TestInteractiveInfoAtom extends TestCase {
                InteractiveInfoAtom ia = new InteractiveInfoAtom(data_a, 0, data_a.length);
                InteractiveInfoAtom ib = new InteractiveInfoAtom(data_b, 0, data_b.length);
                
-               assertEquals(1, ia.getNumber());
-               assertEquals(4, ib.getNumber());
+               assertEquals(1, ia.getHyperlinkID());
+               assertEquals(4, ib.getHyperlinkID());
     }
     
     public void testGetRest() throws Exception {
                InteractiveInfoAtom ia = new InteractiveInfoAtom(data_a, 0, data_a.length);
                InteractiveInfoAtom ib = new InteractiveInfoAtom(data_b, 0, data_b.length);
                
-               assertEquals(0, ia._getNumber1());
-               assertEquals(0, ib._getNumber1());
+               assertEquals(0, ia.getSoundRef());
+               assertEquals(0, ib.getSoundRef());
                
-               assertEquals(4, ia._getNumber3());
-               assertEquals(4, ib._getNumber3());
+               assertEquals(4, ia.getAction());
+               assertEquals(4, ib.getAction());
                
-               assertEquals(8, ia._getNumber4());
-               assertEquals(8, ib._getNumber4());
+               assertEquals(8, ia.getHyperlinkType());
+               assertEquals(8, ib.getHyperlinkType());
     }
     
        public void testWrite() throws Exception {
@@ -88,10 +88,10 @@ public class TestInteractiveInfoAtom extends TestCase {
        InteractiveInfoAtom ia = new InteractiveInfoAtom();
        
        // Set values
-       ia.setNumber(1);
-       ia._setNumber1(0);
-       ia._setNumber3(4);
-       ia._setNumber4(8);
+       ia.setHyperlinkID(1);
+       ia.setSoundRef(0);
+       ia.setAction((byte)4);
+       ia.setHyperlinkType((byte)8);
        
                // Check it's now the same as a
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -109,7 +109,7 @@ public class TestInteractiveInfoAtom extends TestCase {
                InteractiveInfoAtom ia = new InteractiveInfoAtom(data_a, 0, data_a.length);
 
                // Change the number
-               ia.setNumber(4);
+               ia.setHyperlinkID(4);
                
                // Check bytes are now the same
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTxInteractiveInfoAtom.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTxInteractiveInfoAtom.java
new file mode 100644 (file)
index 0000000..9cb53d7
--- /dev/null
@@ -0,0 +1,110 @@
+\r
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+        \r
+\r
+\r
+package org.apache.poi.hslf.record;\r
+\r
+\r
+import junit.framework.TestCase;\r
+import java.io.ByteArrayOutputStream;\r
+import java.text.SimpleDateFormat;\r
+import java.util.Date;\r
+\r
+/**\r
+ * Tests that TxInteractiveInfoAtom works properly.\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class TestTxInteractiveInfoAtom extends TestCase {\r
+       // From WithLinks.ppt \r
+       private byte[] data_a = new byte[] { \r
+               00, 00, (byte)0xDF, 0x0F, 0x08, 00, 00, 00,\r
+               0x19, 00, 00, 00, 0x38, 00, 00, 00\r
+       };\r
+\r
+    private byte[] data_b = new byte[] {\r
+        00, 00, (byte)0xDF, 0x0F, 0x08, 00, 00, 00,\r
+        0x39, 00, 00, 00, 0x4E, 00, 00, 00\r
+    };\r
+\r
+    public void testRead() throws Exception {\r
+        TxInteractiveInfoAtom ia1 = new TxInteractiveInfoAtom(data_a, 0, data_a.length);\r
+\r
+        assertEquals(4063, ia1.getRecordType());\r
+               assertEquals(25, ia1.getStartIndex());\r
+               assertEquals(56, ia1.getEndIndex());\r
+\r
+        TxInteractiveInfoAtom ia2 = new TxInteractiveInfoAtom(data_b, 0, data_b.length);\r
+\r
+        assertEquals(4063, ia2.getRecordType());\r
+               assertEquals(57, ia2.getStartIndex());\r
+               assertEquals(78, ia2.getEndIndex());\r
+    }\r
+    \r
+       public void testWrite() throws Exception {\r
+               TxInteractiveInfoAtom atom = new TxInteractiveInfoAtom(data_a, 0, data_a.length);\r
+               ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
+               atom.writeOut(baos);\r
+               byte[] b = baos.toByteArray();\r
+\r
+               assertEquals(data_a.length, b.length);\r
+               for(int i=0; i<data_a.length; i++) {\r
+                       assertEquals(data_a[i],b[i]);\r
+               }\r
+       }\r
+\r
+       // Create A from scratch\r
+    public void testCreate() throws Exception {\r
+       TxInteractiveInfoAtom ia = new TxInteractiveInfoAtom();\r
+       \r
+       // Set values\r
+        ia.setStartIndex(25);\r
+       ia.setEndIndex(56);\r
+\r
+               // Check it's now the same as a\r
+               ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
+               ia.writeOut(baos);\r
+               byte[] b = baos.toByteArray();\r
+               \r
+               assertEquals(data_a.length, b.length);\r
+               for(int i=0; i<data_a.length; i++) {\r
+                       assertEquals(data_a[i],b[i]);\r
+               }\r
+    }\r
+\r
+       // Try to turn a into b\r
+       public void testChange() throws Exception {\r
+               TxInteractiveInfoAtom ia = new TxInteractiveInfoAtom(data_a, 0, data_a.length);\r
+\r
+               // Change the number\r
+               ia.setStartIndex(57);\r
+        ia.setEndIndex(78);\r
+\r
+               // Check bytes are now the same\r
+               ByteArrayOutputStream baos = new ByteArrayOutputStream();\r
+               ia.writeOut(baos);\r
+               byte[] b = baos.toByteArray();\r
+\r
+               // Should now be the same\r
+               assertEquals(data_b.length, b.length);\r
+               for(int i=0; i<data_b.length; i++) {\r
+                       assertEquals(data_b[i],b[i]);\r
+               }\r
+       }\r
+}\r