diff options
author | Yegor Kozlov <yegor@apache.org> | 2008-02-07 08:56:59 +0000 |
---|---|---|
committer | Yegor Kozlov <yegor@apache.org> | 2008-02-07 08:56:59 +0000 |
commit | 4ab00acc7cf8a56a22153c84ec203707cd413846 (patch) | |
tree | 8c66941a9e915ba84f89505ba945c7d4ff110403 | |
parent | 766dc98ce36338acc4ad9c21d931c0843994ee40 (diff) | |
download | poi-4ab00acc7cf8a56a22153c84ec203707cd413846.tar.gz poi-4ab00acc7cf8a56a22153c84ec203707cd413846.zip |
support for excel hypelrinks
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@619310 13f79535-47bb-0310-9956-ffa450edef68
10 files changed, 1213 insertions, 383 deletions
diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index d2430ca19e..2b017fee7b 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,6 +36,7 @@ <!-- Don't forget to update status.xml too! --> <release version="3.1-beta1" date="2008-??-??"> + <action dev="POI-DEVELOPERS" type="add">37923 - Support for Excel hyperlinks</action> <action dev="POI-DEVELOPERS" type="add">Implement hashCode() and equals(obj) on HSSFFont and HSSFCellStyle</action> <action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action> <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action> diff --git a/src/documentation/content/xdocs/hssf/quick-guide.xml b/src/documentation/content/xdocs/hssf/quick-guide.xml index 69850afe88..66da604892 100644 --- a/src/documentation/content/xdocs/hssf/quick-guide.xml +++ b/src/documentation/content/xdocs/hssf/quick-guide.xml @@ -69,6 +69,7 @@ <li><link href="#NamedRanges">Named Ranges and Named Cells</link></li> <li><link href="#CellComments">How to set cell comments</link></li> <li><link href="#Autofit">How to adjust column width to fit the contents</link></li> + <li><link href="#Hyperlinks">Hyperlinks</link></li> </ul> </section> <section><title>Features</title> @@ -1322,6 +1323,76 @@ Examples: (either via <code>-Djava.awt.headless=true</code> startup parameter or via <code>System.setProperty("java.awt.headless", "true")</code>). </warning> </section> + <anchor id="Hyperlinks"/> + <section><title>How to read hyperlinks</title> + <source> + HSSFSheet sheet = workbook.getSheetAt(0); + + HSSFCell cell = sheet.getRow(0).getCell((short)0); + HSSFHyperlink link = cell.getHyperlink(); + if(link != null){ + System.out.println(link.getAddress()); + } + </source> + </section> + <section><title>How to create hyperlinks</title> + <source> + HSSFWorkbook wb = new HSSFWorkbook(); + + //cell style for hyperlinks + //by default hypelrinks are blue and underlined + HSSFCellStyle hlink_style = wb.createCellStyle(); + HSSFFont hlink_font = wb.createFont(); + hlink_font.setUnderline(HSSFFont.U_SINGLE); + hlink_font.setColor(HSSFColor.BLUE.index); + hlink_style.setFont(hlink_font); + + HSSFCell cell; + HSSFSheet sheet = wb.createSheet("Hyperlinks"); + + //URL + cell = sheet.createRow(0).createCell((short)0); + cell.setCellValue("URL Link"); + HSSFHyperlink link = new HSSFHyperlink(HSSFHyperlink.LINK_URL); + link.setAddress("http://poi.apache.org/"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //link to a file in the current directory + cell = sheet.createRow(1).createCell((short)0); + cell.setCellValue("File Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_FILE); + link.setAddress("link1.xls"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //e-mail link + cell = sheet.createRow(2).createCell((short)0); + cell.setCellValue("Email Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_EMAIL); + //note, if subject contains white spaces, make sure they are url-encoded + link.setAddress("mailto:poi@apache.org?subject=Hyperlinks"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //link to a place in this workbook + + //create a target sheet and cell + HSSFSheet sheet2 = wb.createSheet("Target Sheet"); + sheet2.createRow(0).createCell((short)0).setCellValue("Target Cell"); + + cell = sheet.createRow(3).createCell((short)0); + cell.setCellValue("Worksheet Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT); + link.setAddress("'Target Sheet'!A1"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + FileOutputStream out = new FileOutputStream("hssf-links.xls"); + wb.write(out); + out.close(); + </source> + </section> </body> </document> diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 8d43546e19..feab74a973 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,7 @@ <!-- Don't forget to update changes.xml too! --> <changes> <release version="3.1-beta1" date="2008-??-??"> + <action dev="POI-DEVELOPERS" type="add">37923 - Support for Excel hyperlinks</action> <action dev="POI-DEVELOPERS" type="add">Implement hashCode() and equals(obj) on HSSFFont and HSSFCellStyle</action> <action dev="POI-DEVELOPERS" type="fix">44345 - Implement CountA, CountIf, Index, Rows and Columns functions</action> <action dev="POI-DEVELOPERS" type="fix">44336 - Properly escape sheet names as required when figuring out the text of formulas</action> diff --git a/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java b/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java new file mode 100755 index 0000000000..24b3f186fc --- /dev/null +++ b/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java @@ -0,0 +1,91 @@ +
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.hssf.util.HSSFColor;
+
+import java.io.IOException;
+import java.io.FileOutputStream;
+
+/**
+ * Demonstrates how to create hyperlinks.
+ *
+ * @author Yegor Kozlov (yegor at apach.org)
+ */
+public class Hyperlinks {
+
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+
+ //cell style for hyperlinks
+ //by default hypelrinks are blue and underlined
+ HSSFCellStyle hlink_style = wb.createCellStyle();
+ HSSFFont hlink_font = wb.createFont();
+ hlink_font.setUnderline(HSSFFont.U_SINGLE);
+ hlink_font.setColor(HSSFColor.BLUE.index);
+ hlink_style.setFont(hlink_font);
+
+ HSSFCell cell;
+ HSSFSheet sheet = wb.createSheet("Hyperlinks");
+
+ //URL
+ cell = sheet.createRow(0).createCell((short)0);
+ cell.setCellValue("URL Link");
+ HSSFHyperlink link = new HSSFHyperlink(HSSFHyperlink.LINK_URL);
+ link.setAddress("http://poi.apache.org/");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //link to a file in the current directory
+ cell = sheet.createRow(1).createCell((short)0);
+ cell.setCellValue("File Link");
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_FILE);
+ link.setAddress("link1.xls");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //e-mail link
+ cell = sheet.createRow(2).createCell((short)0);
+ cell.setCellValue("Email Link");
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_EMAIL);
+ //note, if subject contains white spaces, make sure they are url-encoded
+ link.setAddress("mailto:poi@apache.org?subject=Hyperlinks");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //link to a place in this workbook
+
+ //create a target sheet and cell
+ HSSFSheet sheet2 = wb.createSheet("Target Sheet");
+ sheet2.createRow(0).createCell((short)0).setCellValue("Target Cell");
+
+ cell = sheet.createRow(3).createCell((short)0);
+ cell.setCellValue("Worksheet Link");
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT);
+ link.setAddress("'Target Sheet'!A1");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ FileOutputStream out = new FileOutputStream("hssf-links.xls");
+ wb.write(out);
+ out.close();
+
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java index 0dcd45a724..798d4e1ff5 100644 --- a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java +++ b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java @@ -1,29 +1,27 @@ /* ==================================================================== - Copyright 2002-2004 Apache Software Foundation - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ==================================================================== */ + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ package org.apache.poi.hssf.record; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.util.Arrays; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.StringUtil; +import org.apache.poi.util.HexDump; /** * The <code>HyperlinkRecord</code> wraps an HLINK-record @@ -31,146 +29,283 @@ import org.apache.poi.util.StringUtil; * Supports only external links for now (eg http://) * * @author Mark Hissink Muller <a href="mailto:mark@hissinkmuller.nl >mark&064;hissinkmuller.nl</a> + * @author Yegor Kozlov (yegor at apache dot org) */ -public class HyperlinkRecord extends Record implements CellValueRecordInterface -{ - /** Indicates the URL in the Record */ - private static byte[] GUID_OF_URL_MONIKER = - { -32, -55, -22, 121, -7, -70, -50, 17, -116, -126, 0, -86, 0, 75, -87, 11 }; +public class HyperlinkRecord extends Record { + /** + * Link flags + */ + protected static final int HLINK_URL = 0x01; // File link or URL. + protected static final int HLINK_ABS = 0x02; // Absolute path. + protected static final int HLINK_LABEL = 0x14; // Has label. + protected static final int HLINK_PLACE = 0x08; // Place in worksheet. - /** Indicates the STD_LINK in the Record */ - // MHM: to be added when necessary - private static byte[] GUID_OF_STD_LINK = {}; - /** Logger */ - public static final Log log = LogFactory.getLog(HyperlinkRecord.class); + protected final static byte[] STD_MONIKER = {(byte)0xD0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B }; + protected final static byte[] URL_MONIKER = {(byte)0xE0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B }; + protected final static byte[] FILE_MONIKER = {0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}; - // quick and dirty - private static final boolean _DEBUG_ = true; + /** + * Tail of a URL link + */ + protected final static byte[] URL_TAIL = {0x79, 0x58, (byte)0x81, (byte)0xF4, 0x3B, 0x1D, 0x7F, 0x48, (byte)0xAF, 0x2C, + (byte)0x82, 0x5D, (byte)0xC4, (byte)0x85, 0x27, 0x63, 0x00, 0x00, 0x00, + 0x00, (byte)0xA5, (byte)0xAB, 0x00, 0x00}; + /** + * Tail of a file link + */ + protected final static byte[] FILE_TAIL = {(byte)0xFF, (byte)0xFF, (byte)0xAD, (byte)0xDE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; public final static short sid = 0x1b8; - private short field_1_unknown; - private int field_2_row; - private short field_3_column; - private short field_4_xf_index; - private byte[] field_5_unknown; - private int field_6_label_opts; - private int field_7_url_len; - private int field_8_label_len; - private String field_9_label; - private byte[] field_10_unknown; - private int field_11_url_opts; - private String field_12_url; - - /** Blank Constructor */ + /** + * First row of the hyperlink + */ + private int rwFirst; + + /** + * Last row of the hyperlink + */ + private int rwLast; + + /** + * First column of the hyperlink + */ + private short colFirst; + + /** + * Last column of the hyperlink + */ + private short colLast; + + /** + * 16-byte GUID + */ + private byte[] guid; + + /** + * Some sort of options. Seems to always equal 2 + */ + private int label_opts; + + /** + * Some sort of options for file links. + */ + private short file_opts; + + /** + * Link options. Can include any of HLINK_* flags. + */ + private int link_opts; + + /** + * Test label + */ + private String label; + + /** + * Moniker. Makes sense only for URL and file links + */ + private byte[] moniker; + + /** + * Link + */ + private String address; + + /** + * Remaining bytes + */ + private byte[] tail; + + /** + * Create a new hyperlink + */ public HyperlinkRecord() { + } - /** Real Constructor */ + /** + * Read hyperlink from input stream + * + * @param in the stream to read from + */ public HyperlinkRecord(RecordInputStream in) { super(in); } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#getColumn() + /** + * Return the column of the first cell that contains the hyperlink + * + * @return the 0-based column of the first cell that contains the hyperlink */ - public short getColumn() + public short getFirstColumn() { - return field_3_column; + return colFirst; } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#getRow() + /** + * Set the column of the first cell that contains the hyperlink + * + * @param col the 0-based column of the first cell that contains the hyperlink */ - public int getRow() + public void setFirstColumn(short col) { - return field_2_row; + this.colFirst = col; } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#getXFIndex() + /** + * Set the column of the last cell that contains the hyperlink + * + * @return the 0-based column of the last cell that contains the hyperlink + */ + public short getLastColumn() + { + return colLast; + } + + /** + * Set the column of the last cell that contains the hyperlink + * + * @param col the 0-based column of the last cell that contains the hyperlink */ - public short getXFIndex() + public void setLastColumn(short col) { - return field_4_xf_index; + this.colLast = col; } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#isAfter(org.apache.poi.hssf.record.CellValueRecordInterface) + /** + * Return the row of the first cell that contains the hyperlink + * + * @return the 0-based row of the first cell that contains the hyperlink */ - public boolean isAfter(CellValueRecordInterface i) + public int getFirstRow() { - if (this.getRow() < i.getRow()) - { - return false; - } - if ((this.getRow() == i.getRow()) && (this.getColumn() < i.getColumn())) - { - return false; - } - if ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn())) - { - return false; - } - return true; + return rwFirst; } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#isBefore(org.apache.poi.hssf.record.CellValueRecordInterface) + /** + * Set the row of the first cell that contains the hyperlink + * + * @param row the 0-based row of the first cell that contains the hyperlink */ - public boolean isBefore(CellValueRecordInterface i) + public void setFirstRow(int row) { - if (this.getRow() > i.getRow()) - { - return false; - } - if ((this.getRow() == i.getRow()) && (this.getColumn() > i.getColumn())) - { - return false; - } - if ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn())) - { - return false; - } - return true; + this.rwFirst = row; + } + + /** + * Return the row of the last cell that contains the hyperlink + * + * @return the 0-based row of the last cell that contains the hyperlink + */ + public int getLastRow() + { + return rwLast; + } + + /** + * Set the row of the last cell that contains the hyperlink + * + * @param row the 0-based row of the last cell that contains the hyperlink + */ + public void setLastRow(int row) + { + this.rwLast = row; } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#isEqual(org.apache.poi.hssf.record.CellValueRecordInterface) + /** + * Returns a 16-byte guid identifier. Seems to always equal {@link STD_MONIKER} + * + * @return 16-byte guid identifier */ - public boolean isEqual(CellValueRecordInterface i) + public byte[] getGuid() { - return ((this.getRow() == i.getRow()) && (this.getColumn() == i.getColumn())); + return guid; } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#setColumn(short) + /** + * Returns a 16-byte moniker. + * + * @return 16-byte moniker */ - public void setColumn(short col) + public byte[] getMoniker() { - this.field_3_column = col; + return moniker; + } + + /** + * Return text label for this hyperlink + * + * @return text to display + */ + public String getLabel() + { + int idx = label.indexOf('\u0000'); + return idx == -1 ? label : label.substring(0, idx); } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#setRow(int) + /** + * Sets text label for this hyperlink + * + * @param label text label for this hyperlink */ - public void setRow(int row) + public void setLabel(String label) { - this.field_2_row = row; + this.label = label + '\u0000'; + } + /** + * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc. + * + * @return the address of this hyperlink + */ + public String getAddress() + { + int idx = address.indexOf('\u0000'); + return idx == -1 ? address : address.substring(0, idx); } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.CellValueRecordInterface#setXFIndex(short) + /** + * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc. + * + * @param address the address of this hyperlink */ - public void setXFIndex(short xf) + public void setAddress(String address) { - this.field_4_xf_index = xf; + this.address = address + '\u0000'; + } + + /** + * Link options. Must be a combination of HLINK_* constants. + */ + public int getLinkOptions(){ + return link_opts; + } + /** + * Label options + */ + public int getLabelOptions(){ + return label_opts; + } + + /** + * Options for a file link + */ + public int getFileOptions(){ + return file_opts; + } + + public byte[] getTail(){ + return tail; } /** @@ -178,57 +313,56 @@ public class HyperlinkRecord extends Record implements CellValueRecordInterface */ protected void fillFields(RecordInputStream in) { -// System.err.println(in.currentSid); -// System.err.println(in.currentLength); -// for(int i=0; i<300; i++) { -// System.err.println(in.readByte()); -// } -// if(1==1) -// throw new IllegalArgumentException(""); - - field_1_unknown = in.readShort(); - field_2_row = in.readUShort(); - field_3_column = in.readShort(); - field_4_xf_index = in.readShort(); - - // Next up is 16 bytes we don't get - field_5_unknown = new byte[16]; - try { - in.read(field_5_unknown); - } catch(IOException e) { throw new IllegalStateException(e.getMessage()); } - - // Some sort of opts - field_6_label_opts = in.readInt(); - - // Now for lengths, in characters - field_7_url_len = in.readInt(); - field_8_label_len = in.readInt(); - - // Now we have the label, as little endian unicode, - // with a trailing \0 - field_9_label = in.readUnicodeLEString(field_8_label_len); - - // Next up is some more data we can't make sense of - field_10_unknown = new byte[16]; try { - in.read(field_10_unknown); - } catch(IOException e) { throw new IllegalStateException(e.getMessage()); } - - // Might need to nudge the length by one byte - // This is an empirical hack! - field_11_url_opts = in.readInt(); - if(field_11_url_opts == 44) { - field_7_url_len--; + rwFirst = in.readShort(); + rwLast = in.readUShort(); + colFirst = in.readShort(); + colLast = in.readShort(); + + // 16-byte GUID + guid = new byte[16]; + in.read(guid); + + label_opts = in.readInt(); + link_opts = in.readInt(); + + if ((link_opts & HLINK_LABEL) != 0){ + int label_len = in.readInt(); + label = in.readUnicodeLEString(label_len); + } + + if ((link_opts & HLINK_URL) != 0){ + moniker = new byte[16]; + in.read(moniker); + + if(Arrays.equals(URL_MONIKER, moniker)){ + int len = in.readInt(); + + address = in.readUnicodeLEString(len/2); + + tail = in.readRemainder(); + } else if (Arrays.equals(FILE_MONIKER, moniker)){ + file_opts = in.readShort(); + + int len = in.readInt(); + + byte[] path_bytes = new byte[len]; + in.read(path_bytes); + + address = new String(path_bytes); + + tail = in.readRemainder(); + } + } else if((link_opts & HLINK_PLACE) != 0){ + int len = in.readInt(); + address = in.readUnicodeLEString(len); + } + } catch (IOException e){ + throw new RuntimeException(e); } - - // Finally it's the URL - int strlen = field_7_url_len > (in.remaining()/2) ? (in.remaining()/2) : field_7_url_len; - field_12_url = in.readUnicodeLEString(strlen); + } - - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.Record#getSid() - */ + public short getSid() { return HyperlinkRecord.sid; @@ -244,55 +378,75 @@ public class HyperlinkRecord extends Record implements CellValueRecordInterface public int serialize(int offset, byte[] data) { - LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, - ( short )(getRecordSize()-4)); - LittleEndian.putShort(data, 4 + offset, field_1_unknown); - LittleEndian.putUShort(data, 6 + offset, field_2_row); - LittleEndian.putShort(data, 8 + offset, field_3_column); - LittleEndian.putShort(data, 10 + offset, field_4_xf_index); - - offset += 12; - for(int i=0; i<field_5_unknown.length; i++) { - data[offset] = field_5_unknown[i]; - offset++; + int pos = offset; + LittleEndian.putShort(data, pos, sid); pos += 2; + LittleEndian.putShort(data, pos, ( short )(getRecordSize()-4)); pos += 2; + LittleEndian.putUShort(data, pos, rwFirst); pos += 2; + LittleEndian.putUShort(data, pos, rwLast); pos += 2; + LittleEndian.putShort(data, pos, colFirst); pos += 2; + LittleEndian.putShort(data, pos, colLast); pos += 2; + + System.arraycopy(guid, 0, data, pos, guid.length); pos += guid.length; + + LittleEndian.putInt(data, pos, label_opts); pos += 4; + LittleEndian.putInt(data, pos, link_opts); pos += 4; + + if ((link_opts & HLINK_LABEL) != 0){ + LittleEndian.putInt(data, pos, label.length()); pos += 4; + StringUtil.putUnicodeLE(label, data, pos); pos += label.length()*2; } - - LittleEndian.putInt(data, offset, field_6_label_opts); - offset += 4; - LittleEndian.putInt(data, offset, field_7_url_len); - offset += 4; - LittleEndian.putInt(data, offset, field_8_label_len); - offset += 4; - StringUtil.putUnicodeLE(field_9_label, data, offset); - offset += field_9_label.length()*2; - - for(int i=0; i<field_10_unknown.length; i++) { - data[offset] = field_10_unknown[i]; - offset++; + if ((link_opts & HLINK_URL) != 0){ + System.arraycopy(moniker, 0, data, pos, moniker.length); pos += moniker.length; + if(Arrays.equals(URL_MONIKER, moniker)){ + LittleEndian.putInt(data, pos, address.length()*2 + tail.length); pos += 4; + StringUtil.putUnicodeLE(address, data, pos); pos += address.length()*2; + if(tail.length > 0){ + System.arraycopy(tail, 0, data, pos, tail.length); pos += tail.length; + } + } else if (Arrays.equals(FILE_MONIKER, moniker)){ + LittleEndian.putShort(data, pos, file_opts); pos += 2; + LittleEndian.putInt(data, pos, address.length()); pos += 4; + byte[] bytes = address.getBytes(); + System.arraycopy(bytes, 0, data, pos, bytes.length); pos += bytes.length; + if(tail.length > 0){ + System.arraycopy(tail, 0, data, pos, tail.length); pos += tail.length; + } + } + } else if((link_opts & HLINK_PLACE) != 0){ + LittleEndian.putInt(data, pos, address.length()); pos += 4; + StringUtil.putUnicodeLE(address, data, pos); pos += address.length()*2; } - - LittleEndian.putInt(data, offset, field_11_url_opts); - offset += 4; - StringUtil.putUnicodeLE(field_12_url, data, offset); - return getRecordSize(); } public int getRecordSize() { - // We have: - // 4 shorts - // junk - // 3 ints - // label - // junk - // int - // url - return 4 + 4*2 + field_5_unknown.length + - 3*4 + field_9_label.length()*2 + - field_10_unknown.length + 4 + - field_12_url.length()*2; + int size = 4; + size += 2 + 2 + 2 + 2; //rwFirst, rwLast, colFirst, colLast + size += guid.length; + size += 4; //label_opts + size += 4; //link_opts + if ((link_opts & HLINK_LABEL) != 0){ + size += 4; //link length + size += label.length()*2; + } + if ((link_opts & HLINK_URL) != 0){ + size += moniker.length; //moniker length + if(Arrays.equals(URL_MONIKER, moniker)){ + size += 4; //address length + size += address.length()*2; + size += tail.length; + } else if (Arrays.equals(FILE_MONIKER, moniker)){ + size += 2; //file_opts + size += 4; //address length + size += address.length(); + size += tail.length; + } + } else if((link_opts & HLINK_PLACE) != 0){ + size += 4; //address length + size += address.length()*2; + } + return size; } public String toString() @@ -300,71 +454,89 @@ public class HyperlinkRecord extends Record implements CellValueRecordInterface StringBuffer buffer = new StringBuffer(); buffer.append("[HYPERLINK RECORD]\n"); - buffer.append(" .row = ").append(Integer.toHexString(getRow())).append("\n"); - buffer.append(" .column = ").append(Integer.toHexString(getColumn())).append("\n"); - buffer.append(" .xfindex = ").append(Integer.toHexString(getXFIndex())).append("\n"); - buffer.append(" .label = ").append(field_9_label).append("\n"); - buffer.append(" .url = ").append(field_12_url).append("\n"); + buffer.append(" .rwFirst = ").append(Integer.toHexString(getFirstRow())).append("\n"); + buffer.append(" .rwLast = ").append(Integer.toHexString(getLastRow())).append("\n"); + buffer.append(" .colFirst = ").append(Integer.toHexString(getFirstColumn())).append("\n"); + buffer.append(" .colLast = ").append(Integer.toHexString(getLastColumn())).append("\n"); + buffer.append(" .guid = ").append(HexDump.toHex(guid)).append("\n"); + buffer.append(" .label_opts = ").append(label_opts).append("\n"); + buffer.append(" .label = ").append(getLabel()).append("\n"); + if((link_opts & HLINK_URL) != 0){ + buffer.append(" .moniker = ").append(HexDump.toHex(moniker)).append("\n"); + } + buffer.append(" .address = ").append(getAddress()).append("\n"); buffer.append("[/HYPERLINK RECORD]\n"); return buffer.toString(); } /** - * @return Returns the label. + * Initialize a new url link */ - public String getLabel() - { - if(field_9_label.length() == 0) { - return ""; - } else { - // Trim off \0 - return field_9_label.substring(0, field_9_label.length() - 1); - } + public void newUrlLink(){ + rwFirst = 0; + rwLast = 0; + colFirst = 0; + colLast = 0; + guid = STD_MONIKER; + label_opts = 0x2; + link_opts = HLINK_URL | HLINK_ABS | HLINK_LABEL; + label = "" + '\u0000'; + moniker = URL_MONIKER; + address = "" + '\u0000'; + tail = URL_TAIL; } /** - * @param label The label to set. + * Initialize a new file link */ - public void setLabel(String label) - { - this.field_9_label = label + '\u0000'; - this.field_8_label_len = field_9_label.length(); + public void newFileLink(){ + rwFirst = 0; + rwLast = 0; + colFirst = 0; + colLast = 0; + guid = STD_MONIKER; + label_opts = 0x2; + link_opts = HLINK_URL | HLINK_LABEL; + file_opts = 0; + label = "" + '\u0000'; + moniker = FILE_MONIKER; + address = "" + '\0'; + tail = FILE_TAIL; } /** - * @return Returns the Url. + * Initialize a new document link */ - public URL getUrl() throws MalformedURLException - { - return new URL(getUrlString()); - } - public String getUrlString() - { - if(field_12_url.length() == 0) { - return ""; - } else { - // Trim off \0 - return field_12_url.substring(0, field_12_url.length() - 1); - } + public void newDocumentLink(){ + rwFirst = 0; + rwLast = 0; + colFirst = 0; + colLast = 0; + guid = STD_MONIKER; + label_opts = 0x2; + link_opts = HLINK_LABEL | HLINK_PLACE; + label = "" + '\u0000'; + moniker = FILE_MONIKER; + address = "" + '\0'; + tail = new byte[]{}; } - /** - * @param url The url to set. - */ - public void setUrl(URL url) - { - setUrl(url.toString()); - } - /** - * @param url The url to set. - */ - public void setUrl(String url) - { - this.field_12_url = url + '\u0000'; - this.field_7_url_len = field_12_url.length(); + public Object clone() { + HyperlinkRecord rec = new HyperlinkRecord(); + rec.rwFirst = rwFirst; + rec.rwLast = rwLast; + rec.colFirst = colFirst; + rec.colLast = colLast; + rec.guid = guid; + rec.label_opts = label_opts; + rec.link_opts = link_opts; + rec.file_opts = file_opts; + rec.label = label; + rec.address = address; + rec.moniker = moniker; + rec.tail = tail; + return rec; } - public int getOptions(){ - return field_11_url_opts; - } + } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 7b6102b69d..646efe3103 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -1066,7 +1066,7 @@ public class HSSFCell Record rec = ( Record ) it.next(); if (rec instanceof HyperlinkRecord){ HyperlinkRecord link = (HyperlinkRecord)rec; - if(link.getColumn() == record.getColumn() && link.getRow() == record.getRow()){ + if(link.getFirstColumn() == record.getColumn() && link.getFirstRow() == record.getRow()){ return new HSSFHyperlink(link); } } @@ -1080,6 +1080,25 @@ public class HSSFCell * @param link hypelrink associated with this cell */ public void setHyperlink(HSSFHyperlink link){ + link.setFirstRow(record.getRow()); + link.setLastRow(record.getRow()); + link.setFirstColumn(record.getColumn()); + link.setLastColumn(record.getColumn()); + + switch(link.getType()){ + case HSSFHyperlink.LINK_EMAIL: + case HSSFHyperlink.LINK_URL: + link.setLabel("url"); + break; + case HSSFHyperlink.LINK_FILE: + link.setLabel("file"); + break; + case HSSFHyperlink.LINK_DOCUMENT: + link.setLabel("place"); + break; + } + int eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid ); + sheet.getRecords().add( eofLoc, link.record ); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java index e1bd28af6c..7f1c2639c9 100755 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java @@ -27,9 +27,9 @@ import java.util.List; import java.util.Iterator;
/**
- * Represents a hyperlink.
+ * Represents an Excel hyperlink.
*
- * @author Yegor Kozlov
+ * @author Yegor Kozlov (yegor at apache dot org)
*/
public class HSSFHyperlink {
@@ -49,67 +49,145 @@ public class HSSFHyperlink { public static final int LINK_EMAIL = 3;
/**
- * Unknown type
+ * Link to a file
*/
- public static final int LINK_UNKNOWN = 4;
+ public static final int LINK_FILE = 4;
/**
* Low-level record object that stores the actual hyperlink data
*/
- private HyperlinkRecord record = null;
+ protected HyperlinkRecord record = null;
+ /**
+ * If we create a new hypelrink remember its type
+ */
+ protected int link_type;
+
+ /**
+ * Construct a new hyperlink
+ *
+ * @param type the type of hyperlink to create
+ */
+ public HSSFHyperlink( int type )
+ {
+ this.link_type = type;
+ record = new HyperlinkRecord();
+ switch(type){
+ case LINK_URL:
+ case LINK_EMAIL:
+ record.newUrlLink();
+ break;
+ case LINK_FILE:
+ record.newFileLink();
+ break;
+ case LINK_DOCUMENT:
+ record.newDocumentLink();
+ break;
+ }
+ }
+
+ /**
+ * Initialize the hyperlink by a <code>HyperlinkRecord</code> record
+ *
+ * @param record
+ */
protected HSSFHyperlink( HyperlinkRecord record )
{
this.record = record;
}
/**
- * Return the row of the cell that contains the hyperlink
+ * Return the row of the first cell that contains the hyperlink
*
* @return the 0-based row of the cell that contains the hyperlink
*/
- public int getRow(){
- return record.getRow();
+ public int getFirstRow(){
+ return record.getFirstRow();
+ }
+
+ /**
+ * Set the row of the first cell that contains the hyperlink
+ *
+ * @param row the 0-based row of the first cell that contains the hyperlink
+ */
+ public void setFirstRow(int row){
+ record.setFirstRow(row);
+ }
+
+ /**
+ * Return the row of the last cell that contains the hyperlink
+ *
+ * @return the 0-based row of the last cell that contains the hyperlink
+ */
+ public int getLastRow(){
+ return record.getLastRow();
+ }
+
+ /**
+ * Set the row of the last cell that contains the hyperlink
+ *
+ * @param row the 0-based row of the last cell that contains the hyperlink
+ */
+ public void setLastRow(int row){
+ record.setLastRow(row);
+ }
+
+ /**
+ * Return the column of the first cell that contains the hyperlink
+ *
+ * @return the 0-based column of the first cell that contains the hyperlink
+ */
+ public short getFirstColumn(){
+ return record.getFirstColumn();
}
/**
- * Set the row of the cell that contains the hyperlink
+ * Set the column of the first cell that contains the hyperlink
*
- * @param row the 0-based row of the cell that contains the hyperlink
+ * @param col the 0-based column of the first cell that contains the hyperlink
*/
- public void setRow(int row){
- record.setRow(row);
+ public void setFirstColumn(short col){
+ record.setFirstColumn(col);
}
/**
- * Return the column of the cell that contains the hyperlink
+ * Return the column of the last cell that contains the hyperlink
*
- * @return the 0-based column of the cell that contains the hyperlink
+ * @return the 0-based column of the last cell that contains the hyperlink
*/
- public short getColumn(){
- return record.getColumn();
+ public short getLastColumn(){
+ return record.getLastColumn();
}
/**
- * Set the column of the cell that contains the hyperlink
+ * Set the column of the last cell that contains the hyperlink
*
- * @param col the 0-based column of the cell that contains the hyperlink
+ * @param col the 0-based column of the last cell that contains the hyperlink
*/
- public void setColumn(short col){
- record.setColumn(col);
+ public void setLastColumn(short col){
+ record.setLastColumn(col);
}
/**
- * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, etc.
+ * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.
*
* @return the address of this hyperlink
*/
public String getAddress(){
- return record.getUrlString();
+ return record.getAddress();
}
/**
- * Return text to display for this hyperlink
+ * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.
+ *
+ * @param address the address of this hyperlink
+ */
+ public void setAddress(String address){
+ record.setAddress(address);
+ }
+
+ /**
+ * Return text label for this hyperlink
*
* @return text to display
*/
@@ -118,11 +196,20 @@ public class HSSFHyperlink { }
/**
+ * Sets text label for this hyperlink
+ *
+ * @param label text label for this hyperlink
+ */
+ public void setLabel(String label){
+ record.setLabel(label);
+ }
+
+ /**
* Return the type of this hyperlink
*
* @return the type of this hyperlink
*/
- public int getType(){
- throw new RuntimeException("Not implemented");
+ protected int getType(){
+ return link_type;
}
}
diff --git a/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java b/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java index 70548fe95f..3d2ca406ce 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java @@ -18,114 +18,311 @@ package org.apache.poi.hssf.record; import java.io.ByteArrayInputStream; import java.net.URL; +import java.util.Arrays; import junit.framework.TestCase; +/** + * Test HyperlinkRecord + * + * @author Nick Burch + * @author Yegor Kozlov + */ public class TestHyperlinkRecord extends TestCase { - protected void setUp() throws Exception { - super.setUp(); - } - - private byte[] data = new byte[] { - -72, 1, 110, 0, - // ??, Row, col, xf - 6, 0, 3, 0, 2, 0, 2, 0, - - // ?? - -48, -55, -22, 121, -7, -70, -50, 17, - -116, -126, 0, -86, 0, 75, -87, 11, - 2, 0, 0, 0, - - // URL length - 23, 0, 0, 0, - - // Label length - 4, 0, 0, 0, - - // Label - 76, 0, 44, 0, 65, 0, 0, 0, - - // ?? - -32, -55, -22, 121, -7, -70, -50, 17, - -116, -126, 0, -86, 0, 75, -87, 11, - 46, 0, 0, 0, - - // URL - 104, 0, 116, 0, 116, 0, 112, 0, 58, 0, 47, 0, 47, 0, 119, - 0, 119, 0, 119, 0, 46, 0, 108, 0, 97, 0, 107, 0, 105, - 0, 110, 0, 103, 0, 115, 0, 46, 0, 99, 0, 111, 0, - 109, 0, - 0, 0 }; - - private byte[] data2 = new byte[] { - -72, 1, -126, 0, - // ??, Row, col, xf - 2, 0, 2, 0, 4, 0, 4, 0, - - // ?? - -48, -55, -22, 121, -7, -70, -50, 17, - -116, -126, 0, -86, 0, 75, -87, 11, - 2, 0, 0, 0, - - // URL and Label lengths - 23, 0, 0, 0, - 15, 0, 0, 0, - - // Label - 83, 0, 116, 0, 97, 0, 99, 0, 105, 0, - 101, 0, 64, 0, 65, 0, 66, 0, 67, 0, - 46, 0, 99, 0, 111, 0, 109, 0, 0, 0, - - // ?? - -32, -55, -22, 121, -7, -70, -50, 17, - -116, -126, 0, -86, 0, 75, -87, 11, - 44, 0, 0, 0, - - // URL - 109, 0, 97, 0, 105, 0, 108, 0, 116, 0, - 111, 0, 58, 0, 83, 0, 116, 0, 97, 0, - 99, 0, 105, 0, 101, 0, 64, 0, 65, 0, - 66, 0, 67, 0, 46, 0, 99, 0, 111, 0, - 109, 0, 0, 0 }; - - public void testRecordParsing() throws Exception { - RecordInputStream inp = new RecordInputStream( - new ByteArrayInputStream(data) - ); - inp.nextRecord(); - - HyperlinkRecord r = new HyperlinkRecord(inp); - - assertEquals(3, r.getRow()); - assertEquals(2, r.getColumn()); - assertEquals(2, r.getXFIndex()); - - assertEquals("L,A", r.getLabel()); - assertEquals("http://www.lakings.com", r.getUrlString()); - assertEquals(new URL("http://www.lakings.com"), r.getUrl()); - - // Check it serialises as expected - assertEquals(data.length, r.getRecordSize()); - byte[] d = r.serialize(); - assertEquals(data.length, d.length); - for(int i=0; i<data.length; i++) { - assertEquals(data[i], d[i]); + + //link to http://www.lakings.com/ + byte[] data1 = { 0x02, 0x00, //First row of the hyperlink + 0x02, 0x00, //Last row of the hyperlink + 0x00, 0x00, //First column of the hyperlink + 0x00, 0x00, //Last column of the hyperlink + + //16-byte GUID. Seems to be always the same. Does not depend on the hyperlink type + (byte)0xD0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B, + + 0x02, 0x00, 0x00, 0x00, //integer, always 2 + + // flags. Define the type of the hyperlink: + // HyperlinkRecord.HLINK_URL | HyperlinkRecord.HLINK_ABS | HyperlinkRecord.HLINK_LABEL + 0x17, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, //length of the label including the trailing '\0' + + //label: + 0x4D, 0x00, 0x79, 0x00, 0x20, 0x00, 0x4C, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x6B, 0x00, 0x00, 0x00, + + //16-byte link moniker: HyperlinkRecord.URL_MONIKER + (byte)0xE0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B, + + //count of bytes in the address including the tail + 0x48, 0x00, 0x00, 0x00, //integer + + //the actual link, terminated by '\u0000' + 0x68, 0x00, 0x74, 0x00, 0x74, 0x00, 0x70, 0x00, 0x3A, 0x00, 0x2F, 0x00, + 0x2F, 0x00, 0x77, 0x00, 0x77, 0x00, 0x77, 0x00, 0x2E, 0x00, 0x6C, 0x00, + 0x61, 0x00, 0x6B, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x67, 0x00, 0x73, 0x00, + 0x2E, 0x00, 0x63, 0x00, 0x6F, 0x00, 0x6D, 0x00, 0x2F, 0x00, 0x00, 0x00, + + //standard 24-byte tail of a URL link. Seems to always be the same for all URL HLINKs + 0x79, 0x58, (byte)0x81, (byte)0xF4, 0x3B, 0x1D, 0x7F, 0x48, (byte)0xAF, 0x2C, + (byte)0x82, 0x5D, (byte)0xC4, (byte)0x85, 0x27, 0x63, 0x00, 0x00, 0x00, + 0x00, (byte)0xA5, (byte)0xAB, 0x00, 0x00}; + + //link to a file in the current directory: link1.xls + byte[] data2 = {0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + //16-bit GUID. Seems to be always the same. Does not depend on the hyperlink type + (byte)0xD0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B, + + 0x02, 0x00, 0x00, 0x00, //integer, always 2 + + 0x15, 0x00, 0x00, 0x00, //options: HyperlinkRecord.HLINK_URL | HyperlinkRecord.HLINK_LABEL + + 0x05, 0x00, 0x00, 0x00, //length of the label + //label + 0x66, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x00, 0x00, + + //16-byte link moniker: HyperlinkRecord.FILE_MONIKER + 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, + + 0x00, 0x00, //level + 0x0A, 0x00, 0x00, 0x00, //length of the path ) + + //path to the file (plain ISO-8859 bytes, NOT UTF-16LE!) + 0x6C, 0x69, 0x6E, 0x6B, 0x31, 0x2E, 0x78, 0x6C, 0x73, 0x00, + + //standard 28-byte tail of a file link + (byte)0xFF, (byte)0xFF, (byte)0xAD, (byte)0xDE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + // mailto:ebgans@mail.ru?subject=Hello,%20Ebgans! + byte[] data3 = {0x01, 0x00, + 0x01, 0x00, + 0x00, 0x00, + 0x00, 0x00, + + //16-bit GUID. Seems to be always the same. Does not depend on the hyperlink type + (byte)0xD0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B, + + 0x02, 0x00, 0x00, 0x00, //integer, always 2 + + 0x17, 0x00, 0x00, 0x00, //options: HyperlinkRecord.HLINK_URL | HyperlinkRecord.HLINK_ABS | HyperlinkRecord.HLINK_LABEL + + 0x06, 0x00, 0x00, 0x00, //length of the label + 0x65, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x00, 0x00, //label + + //16-byte link moniker: HyperlinkRecord.URL_MONIKER + (byte)0xE0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B, + + //length of the address including the tail. + 0x76, 0x00, 0x00, 0x00, + + //the address is terminated by '\u0000' + 0x6D, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6C, 0x00, 0x74, 0x00, 0x6F, 0x00, + 0x3A, 0x00, 0x65, 0x00, 0x62, 0x00, 0x67, 0x00, 0x61, 0x00, 0x6E, 0x00, + 0x73, 0x00, 0x40, 0x00, 0x6D, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6C, 0x00, + 0x2E, 0x00, 0x72, 0x00, 0x75, 0x00, 0x3F, 0x00, 0x73, 0x00, 0x75, 0x00, + 0x62, 0x00, 0x6A, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x3D, 0x00, + 0x48, 0x00, 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x2C, 0x00, + 0x25, 0x00, 0x32, 0x00, 0x30, 0x00, 0x45, 0x00, 0x62, 0x00, 0x67, 0x00, + 0x61, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x21, 0x00, 0x00, 0x00, + + //standard 24-byte tail of a URL link + 0x79, 0x58, (byte)0x81, (byte)0xF4, 0x3B, 0x1D, 0x7F, 0x48, (byte)0xAF, (byte)0x2C, + (byte)0x82, 0x5D, (byte)0xC4, (byte)0x85, 0x27, 0x63, 0x00, 0x00, 0x00, + 0x00, (byte)0xA5, (byte)0xAB, 0x00, 0x00 + }; + + //link to a place in worksheet: Sheet1!A1 + byte[] data4 = {0x03, 0x00, + 0x03, 0x00, + 0x00, 0x00, + 0x00, 0x00, + + //16-bit GUID. Seems to be always the same. Does not depend on the hyperlink type + (byte)0xD0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B, + + 0x02, 0x00, 0x00, 0x00, //integer, always 2 + + 0x1C, 0x00, 0x00, 0x00, //flags: HyperlinkRecord.HLINK_LABEL | HyperlinkRecord.HLINK_PLACE + + 0x06, 0x00, 0x00, 0x00, //length of the label + + 0x70, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x00, 0x00, //label + + 0x0A, 0x00, 0x00, 0x00, //length of the document link including trailing zero + + //link: Sheet1!A1 + 0x53, 0x00, 0x68, 0x00, 0x65, 0x00, 0x65, 0x00, 0x74, 0x00, 0x31, 0x00, 0x21, + 0x00, 0x41, 0x00, 0x31, 0x00, 0x00, 0x00}; + + public void testReadURLLink(){ + RecordInputStream is = new TestcaseRecordInputStream((short)HyperlinkRecord.sid, (short)data1.length, data1); + HyperlinkRecord link = new HyperlinkRecord(is); + assertEquals(2, link.getFirstRow()); + assertEquals(2, link.getLastRow()); + assertEquals(0, link.getFirstColumn()); + assertEquals(0, link.getLastColumn()); + assertTrue(Arrays.equals(HyperlinkRecord.STD_MONIKER, link.getGuid())); + assertTrue(Arrays.equals(HyperlinkRecord.URL_MONIKER, link.getMoniker())); + assertEquals(2, link.getLabelOptions()); + int opts = HyperlinkRecord.HLINK_URL | HyperlinkRecord.HLINK_ABS | HyperlinkRecord.HLINK_LABEL; + assertEquals(0x17, opts); + assertEquals(opts, link.getLinkOptions()); + assertEquals(0, link.getFileOptions()); + + assertEquals("My Link", link.getLabel()); + assertEquals("http://www.lakings.com/", link.getAddress()); + } + + public void testReadFileLink(){ + RecordInputStream is = new TestcaseRecordInputStream((short)HyperlinkRecord.sid, (short)data2.length, data2); + HyperlinkRecord link = new HyperlinkRecord(is); + assertEquals(0, link.getFirstRow()); + assertEquals(0, link.getLastRow()); + assertEquals(0, link.getFirstColumn()); + assertEquals(0, link.getLastColumn()); + assertTrue(Arrays.equals(HyperlinkRecord.STD_MONIKER, link.getGuid())); + assertTrue(Arrays.equals(HyperlinkRecord.FILE_MONIKER, link.getMoniker())); + assertEquals(2, link.getLabelOptions()); + int opts = HyperlinkRecord.HLINK_URL | HyperlinkRecord.HLINK_LABEL; + assertEquals(0x15, opts); + assertEquals(opts, link.getLinkOptions()); + + assertEquals("file", link.getLabel()); + assertEquals("link1.xls", link.getAddress()); + } + + public void testReadEmailLink(){ + RecordInputStream is = new TestcaseRecordInputStream((short)HyperlinkRecord.sid, (short)data3.length, data3); + HyperlinkRecord link = new HyperlinkRecord(is); + assertEquals(1, link.getFirstRow()); + assertEquals(1, link.getLastRow()); + assertEquals(0, link.getFirstColumn()); + assertEquals(0, link.getLastColumn()); + assertTrue(Arrays.equals(HyperlinkRecord.STD_MONIKER, link.getGuid())); + assertTrue(Arrays.equals(HyperlinkRecord.URL_MONIKER, link.getMoniker())); + assertEquals(2, link.getLabelOptions()); + int opts = HyperlinkRecord.HLINK_URL | HyperlinkRecord.HLINK_ABS | HyperlinkRecord.HLINK_LABEL; + assertEquals(0x17, opts); + assertEquals(opts, link.getLinkOptions()); + + assertEquals("email", link.getLabel()); + assertEquals("mailto:ebgans@mail.ru?subject=Hello,%20Ebgans!", link.getAddress()); + } + + public void testReadDocumentLink(){ + RecordInputStream is = new TestcaseRecordInputStream((short)HyperlinkRecord.sid, (short)data4.length, data4); + HyperlinkRecord link = new HyperlinkRecord(is); + assertEquals(3, link.getFirstRow()); + assertEquals(3, link.getLastRow()); + assertEquals(0, link.getFirstColumn()); + assertEquals(0, link.getLastColumn()); + assertTrue(Arrays.equals(HyperlinkRecord.STD_MONIKER, link.getGuid())); + assertEquals(2, link.getLabelOptions()); + int opts = HyperlinkRecord.HLINK_LABEL | HyperlinkRecord.HLINK_PLACE; + assertEquals(0x1C, opts); + assertEquals(opts, link.getLinkOptions()); + + assertEquals("place", link.getLabel()); + assertEquals("Sheet1!A1", link.getAddress()); + } + + private void serialize(byte[] data){ + RecordInputStream is = new TestcaseRecordInputStream((short)HyperlinkRecord.sid, (short)data.length, data); + HyperlinkRecord link = new HyperlinkRecord(is); + byte[] bytes1 = link.serialize(); + is = new RecordInputStream(new ByteArrayInputStream(bytes1)); + is.nextRecord(); + link = new HyperlinkRecord(is); + byte[] bytes2 = link.serialize(); + assertEquals(bytes1.length, bytes2.length); + assertTrue(Arrays.equals(bytes1, bytes2)); + } + + public void testSerialize(){ + serialize(data1); + serialize(data2); + serialize(data3); + serialize(data4); + } + + public void testCreateURLRecord() throws Exception { + HyperlinkRecord link = new HyperlinkRecord(); + link.newUrlLink(); + link.setFirstRow((short)2); + link.setLastRow((short)2); + link.setLabel("My Link"); + link.setAddress("http://www.lakings.com/"); + + byte[] tmp = link.serialize(); + byte[] ser = new byte[tmp.length-4]; + System.arraycopy(tmp, 4, ser, 0, ser.length); + assertEquals(data1.length, ser.length); + assertTrue(Arrays.equals(data1, ser)); + } + + public void testCreateFileRecord() throws Exception { + HyperlinkRecord link = new HyperlinkRecord(); + link.newFileLink(); + link.setFirstRow((short)0); + link.setLastRow((short)0); + link.setLabel("file"); + link.setAddress("link1.xls"); + + byte[] tmp = link.serialize(); + byte[] ser = new byte[tmp.length-4]; + System.arraycopy(tmp, 4, ser, 0, ser.length); + assertEquals(data2.length, ser.length); + assertTrue(Arrays.equals(data2, ser)); + } + + public void testCreateDocumentRecord() throws Exception { + HyperlinkRecord link = new HyperlinkRecord(); + link.newDocumentLink(); + link.setFirstRow((short)3); + link.setLastRow((short)3); + link.setLabel("place"); + link.setAddress("Sheet1!A1"); + + byte[] tmp = link.serialize(); + byte[] ser = new byte[tmp.length-4]; + System.arraycopy(tmp, 4, ser, 0, ser.length); + assertEquals(data4.length, ser.length); + assertTrue(Arrays.equals(data4, ser)); + } + + public void testCreateEmailtRecord() throws Exception { + HyperlinkRecord link = new HyperlinkRecord(); + link.newUrlLink(); + link.setFirstRow((short)1); + link.setLastRow((short)1); + link.setLabel("email"); + link.setAddress("mailto:ebgans@mail.ru?subject=Hello,%20Ebgans!"); + + byte[] tmp = link.serialize(); + byte[] ser = new byte[tmp.length-4]; + System.arraycopy(tmp, 4, ser, 0, ser.length); + assertEquals(data3.length, ser.length); + assertTrue(Arrays.equals(data3, ser)); + } + + public void testClone() throws Exception { + byte[][] data = {data1, data2, data3, data4}; + for (int i = 0; i < data.length; i++) { + RecordInputStream is = new TestcaseRecordInputStream((short)HyperlinkRecord.sid, (short)data[i].length, data[i]); + HyperlinkRecord link = new HyperlinkRecord(is); + HyperlinkRecord clone = (HyperlinkRecord)link.clone(); + assertTrue(Arrays.equals(link.serialize(), clone.serialize())); } - } - - public void testSecondRecord() throws Exception { - RecordInputStream inp = new RecordInputStream( - new ByteArrayInputStream(data2) - ); - inp.nextRecord(); - - HyperlinkRecord r = new HyperlinkRecord(inp); - - assertEquals(2, r.getRow()); - assertEquals(4, r.getColumn()); - assertEquals(4, r.getXFIndex()); - - assertEquals("Stacie@ABC.com", r.getLabel()); - assertEquals("mailto:Stacie@ABC.com", r.getUrlString()); - } + + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java index 80785ca182..6c604d1b4d 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java @@ -320,8 +320,8 @@ extends TestCase { assertEquals("Foo", link.getLabel()); assertEquals("http://poi.apache.org/", link.getAddress()); - assertEquals(4, link.getRow()); - assertEquals(0, link.getColumn()); + assertEquals(4, link.getFirstRow()); + assertEquals(0, link.getFirstColumn()); } /** @@ -339,16 +339,16 @@ extends TestCase { assertNotNull(link1); assertEquals("Foo", link1.getLabel()); assertEquals("http://poi.apache.org/", link1.getAddress()); - assertEquals(4, link1.getRow()); - assertEquals(0, link1.getColumn()); + assertEquals(4, link1.getFirstRow()); + assertEquals(0, link1.getFirstColumn()); HSSFCell cell2 = sheet.getRow(8).getCell((short)1); HSSFHyperlink link2 = cell2.getHyperlink(); assertNotNull(link2); assertEquals("Bar", link2.getLabel()); - assertEquals("http://poi.apache.org/", link2.getAddress()); - assertEquals(8, link2.getRow()); - assertEquals(1, link2.getColumn()); + assertEquals("http://poi.apache.org/hssf/", link2.getAddress()); + assertEquals(8, link2.getFirstRow()); + assertEquals(1, link2.getFirstColumn()); } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFHyperlink.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFHyperlink.java new file mode 100755 index 0000000000..b87899c343 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFHyperlink.java @@ -0,0 +1,191 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf.usermodel;
+
+import junit.framework.TestCase;
+
+import java.io.*;
+
+/**
+ * Tests HSSFHyperlink.
+ *
+ * @author Yegor Kozlov
+ */
+public class TestHSSFHyperlink extends TestCase {
+ protected String cwd = System.getProperty("HSSF.testdata.path");
+
+ /**
+ * Test that we can read hyperlinks.
+ */
+ public void testRead() throws Exception {
+
+ FileInputStream is = new FileInputStream(new File(cwd, "HyperlinksOnManySheets.xls"));
+ HSSFWorkbook wb = new HSSFWorkbook(is);
+ is.close();
+
+ HSSFSheet sheet;
+ HSSFCell cell;
+ HSSFHyperlink link;
+
+ sheet = wb.getSheet("WebLinks");
+ cell = sheet.getRow(4).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("POI", link.getLabel());
+ assertEquals("POI", cell.getRichStringCellValue().getString());
+ assertEquals("http://poi.apache.org/", link.getAddress());
+
+ cell = sheet.getRow(8).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("HSSF", link.getLabel());
+ assertEquals("HSSF", cell.getRichStringCellValue().getString());
+ assertEquals("http://poi.apache.org/hssf/", link.getAddress());
+
+ sheet = wb.getSheet("Emails");
+ cell = sheet.getRow(4).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("dev", link.getLabel());
+ assertEquals("dev", cell.getRichStringCellValue().getString());
+ assertEquals("mailto:dev@poi.apache.org", link.getAddress());
+
+ sheet = wb.getSheet("Internal");
+ cell = sheet.getRow(4).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("Link To First Sheet", link.getLabel());
+ assertEquals("Link To First Sheet", cell.getRichStringCellValue().getString());
+ assertEquals("WebLinks!A1", link.getAddress());
+ }
+
+ public void testModify() throws Exception {
+ FileInputStream is = new FileInputStream(new File(cwd, "HyperlinksOnManySheets.xls"));
+ HSSFWorkbook wb = new HSSFWorkbook(is);
+ is.close();
+
+ HSSFSheet sheet;
+ HSSFCell cell;
+ HSSFHyperlink link;
+
+ sheet = wb.getSheet("WebLinks");
+ cell = sheet.getRow(4).getCell((short)0);
+ link = cell.getHyperlink();
+ //modify the link
+ link.setAddress("www.apache.org");
+
+ //serialize and read again
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ wb.write(out);
+
+ wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));
+ sheet = wb.getSheet("WebLinks");
+ cell = sheet.getRow(4).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("www.apache.org", link.getAddress());
+
+ }
+
+ public void testCreate() throws Exception {
+ HSSFWorkbook wb = new HSSFWorkbook();
+
+ HSSFCell cell;
+ HSSFSheet sheet = wb.createSheet("Hyperlinks");
+
+ //URL
+ cell = sheet.createRow(0).createCell((short)0);
+ cell.setCellValue("URL Link");
+ HSSFHyperlink link = new HSSFHyperlink(HSSFHyperlink.LINK_URL);
+ link.setAddress("http://poi.apache.org/");
+ cell.setHyperlink(link);
+
+ //link to a file in the current directory
+ cell = sheet.createRow(1).createCell((short)0);
+ cell.setCellValue("File Link");
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_FILE);
+ link.setAddress("link1.xls");
+ cell.setHyperlink(link);
+
+ //e-mail link
+ cell = sheet.createRow(2).createCell((short)0);
+ cell.setCellValue("Email Link");
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_EMAIL);
+ //note, if subject contains white spaces, make sure they are url-encoded
+ link.setAddress("mailto:poi@apache.org?subject=Hyperlinks");
+ cell.setHyperlink(link);
+
+ //link to a place in this workbook
+
+ //create a target sheet and cell
+ HSSFSheet sheet2 = wb.createSheet("Target Sheet");
+ sheet2.createRow(0).createCell((short)0).setCellValue("Target Cell");
+
+ cell = sheet.createRow(3).createCell((short)0);
+ cell.setCellValue("Worksheet Link");
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT);
+ link.setAddress("'Target Sheet'!A1");
+ cell.setHyperlink(link);
+
+ //serialize and read again
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ wb.write(out);
+
+ wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));
+ sheet = wb.getSheet("Hyperlinks");
+ cell = sheet.getRow(0).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("http://poi.apache.org/", link.getAddress());
+
+ cell = sheet.getRow(1).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("link1.xls", link.getAddress());
+
+ cell = sheet.getRow(2).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("mailto:poi@apache.org?subject=Hyperlinks", link.getAddress());
+
+ cell = sheet.getRow(3).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("'Target Sheet'!A1", link.getAddress());
+ }
+
+ public void testCloneSheet() throws Exception {
+ FileInputStream is = new FileInputStream(new File(cwd, "HyperlinksOnManySheets.xls"));
+ HSSFWorkbook wb = new HSSFWorkbook(is);
+ is.close();
+
+ HSSFCell cell;
+ HSSFHyperlink link;
+
+ HSSFSheet sheet = wb.cloneSheet(0);
+
+ cell = sheet.getRow(4).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("http://poi.apache.org/", link.getAddress());
+
+ cell = sheet.getRow(8).getCell((short)0);
+ link = cell.getHyperlink();
+ assertNotNull(link);
+ assertEquals("http://poi.apache.org/hssf/", link.getAddress());
+ }
+}
|