<!-- 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>
<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>
(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>
<!-- 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>
--- /dev/null
+\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.hssf.usermodel.examples;\r
+\r
+import org.apache.poi.hssf.usermodel.*;\r
+import org.apache.poi.hssf.util.HSSFColor;\r
+\r
+import java.io.IOException;\r
+import java.io.FileOutputStream;\r
+\r
+/**\r
+ * Demonstrates how to create hyperlinks.\r
+ *\r
+ * @author Yegor Kozlov (yegor at apach.org)\r
+ */\r
+public class Hyperlinks {\r
+\r
+ public static void main(String[] args) throws IOException {\r
+ HSSFWorkbook wb = new HSSFWorkbook();\r
+\r
+ //cell style for hyperlinks\r
+ //by default hypelrinks are blue and underlined\r
+ HSSFCellStyle hlink_style = wb.createCellStyle();\r
+ HSSFFont hlink_font = wb.createFont();\r
+ hlink_font.setUnderline(HSSFFont.U_SINGLE);\r
+ hlink_font.setColor(HSSFColor.BLUE.index);\r
+ hlink_style.setFont(hlink_font);\r
+\r
+ HSSFCell cell;\r
+ HSSFSheet sheet = wb.createSheet("Hyperlinks");\r
+\r
+ //URL\r
+ cell = sheet.createRow(0).createCell((short)0);\r
+ cell.setCellValue("URL Link");\r
+ HSSFHyperlink link = new HSSFHyperlink(HSSFHyperlink.LINK_URL);\r
+ link.setAddress("http://poi.apache.org/");\r
+ cell.setHyperlink(link);\r
+ cell.setCellStyle(hlink_style);\r
+\r
+ //link to a file in the current directory\r
+ cell = sheet.createRow(1).createCell((short)0);\r
+ cell.setCellValue("File Link");\r
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_FILE);\r
+ link.setAddress("link1.xls");\r
+ cell.setHyperlink(link);\r
+ cell.setCellStyle(hlink_style);\r
+\r
+ //e-mail link\r
+ cell = sheet.createRow(2).createCell((short)0);\r
+ cell.setCellValue("Email Link");\r
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_EMAIL);\r
+ //note, if subject contains white spaces, make sure they are url-encoded\r
+ link.setAddress("mailto:poi@apache.org?subject=Hyperlinks");\r
+ cell.setHyperlink(link);\r
+ cell.setCellStyle(hlink_style);\r
+\r
+ //link to a place in this workbook\r
+\r
+ //create a target sheet and cell\r
+ HSSFSheet sheet2 = wb.createSheet("Target Sheet");\r
+ sheet2.createRow(0).createCell((short)0).setCellValue("Target Cell");\r
+\r
+ cell = sheet.createRow(3).createCell((short)0);\r
+ cell.setCellValue("Worksheet Link");\r
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT);\r
+ link.setAddress("'Target Sheet'!A1");\r
+ cell.setHyperlink(link);\r
+ cell.setCellStyle(hlink_style);\r
+\r
+ FileOutputStream out = new FileOutputStream("hssf-links.xls");\r
+ wb.write(out);\r
+ out.close();\r
+\r
+ }\r
+}\r
/* ====================================================================
- 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
* 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;
}
/**
*/
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;
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()
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;
- }
+
}
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);
}
}
* @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 );
}
}
import java.util.Iterator;\r
\r
/**\r
- * Represents a hyperlink.\r
+ * Represents an Excel hyperlink.\r
*\r
- * @author Yegor Kozlov\r
+ * @author Yegor Kozlov (yegor at apache dot org)\r
*/\r
public class HSSFHyperlink {\r
\r
public static final int LINK_EMAIL = 3;\r
\r
/**\r
- * Unknown type\r
+ * Link to a file\r
*/\r
- public static final int LINK_UNKNOWN = 4;\r
+ public static final int LINK_FILE = 4;\r
\r
/**\r
* Low-level record object that stores the actual hyperlink data\r
*/\r
- private HyperlinkRecord record = null;\r
+ protected HyperlinkRecord record = null;\r
\r
+ /**\r
+ * If we create a new hypelrink remember its type\r
+ */\r
+ protected int link_type;\r
+\r
+ /**\r
+ * Construct a new hyperlink\r
+ *\r
+ * @param type the type of hyperlink to create\r
+ */\r
+ public HSSFHyperlink( int type )\r
+ {\r
+ this.link_type = type;\r
+ record = new HyperlinkRecord();\r
+ switch(type){\r
+ case LINK_URL:\r
+ case LINK_EMAIL:\r
+ record.newUrlLink();\r
+ break;\r
+ case LINK_FILE:\r
+ record.newFileLink();\r
+ break;\r
+ case LINK_DOCUMENT:\r
+ record.newDocumentLink();\r
+ break;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Initialize the hyperlink by a <code>HyperlinkRecord</code> record\r
+ *\r
+ * @param record\r
+ */\r
protected HSSFHyperlink( HyperlinkRecord record )\r
{\r
this.record = record;\r
}\r
\r
/**\r
- * Return the row of the cell that contains the hyperlink\r
+ * Return the row of the first cell that contains the hyperlink\r
*\r
* @return the 0-based row of the cell that contains the hyperlink\r
*/\r
- public int getRow(){\r
- return record.getRow();\r
+ public int getFirstRow(){\r
+ return record.getFirstRow();\r
+ }\r
+\r
+ /**\r
+ * Set the row of the first cell that contains the hyperlink\r
+ *\r
+ * @param row the 0-based row of the first cell that contains the hyperlink\r
+ */\r
+ public void setFirstRow(int row){\r
+ record.setFirstRow(row);\r
+ }\r
+\r
+ /**\r
+ * Return the row of the last cell that contains the hyperlink\r
+ *\r
+ * @return the 0-based row of the last cell that contains the hyperlink\r
+ */\r
+ public int getLastRow(){\r
+ return record.getLastRow();\r
+ }\r
+\r
+ /**\r
+ * Set the row of the last cell that contains the hyperlink\r
+ *\r
+ * @param row the 0-based row of the last cell that contains the hyperlink\r
+ */\r
+ public void setLastRow(int row){\r
+ record.setLastRow(row);\r
+ }\r
+\r
+ /**\r
+ * Return the column of the first cell that contains the hyperlink\r
+ *\r
+ * @return the 0-based column of the first cell that contains the hyperlink\r
+ */\r
+ public short getFirstColumn(){\r
+ return record.getFirstColumn();\r
}\r
\r
/**\r
- * Set the row of the cell that contains the hyperlink\r
+ * Set the column of the first cell that contains the hyperlink\r
*\r
- * @param row the 0-based row of the cell that contains the hyperlink\r
+ * @param col the 0-based column of the first cell that contains the hyperlink\r
*/\r
- public void setRow(int row){\r
- record.setRow(row);\r
+ public void setFirstColumn(short col){\r
+ record.setFirstColumn(col);\r
}\r
\r
/**\r
- * Return the column of the cell that contains the hyperlink\r
+ * Return the column of the last cell that contains the hyperlink\r
*\r
- * @return the 0-based column of the cell that contains the hyperlink\r
+ * @return the 0-based column of the last cell that contains the hyperlink\r
*/\r
- public short getColumn(){\r
- return record.getColumn();\r
+ public short getLastColumn(){\r
+ return record.getLastColumn();\r
}\r
\r
/**\r
- * Set the column of the cell that contains the hyperlink\r
+ * Set the column of the last cell that contains the hyperlink\r
*\r
- * @param col the 0-based column of the cell that contains the hyperlink\r
+ * @param col the 0-based column of the last cell that contains the hyperlink\r
*/\r
- public void setColumn(short col){\r
- record.setColumn(col);\r
+ public void setLastColumn(short col){\r
+ record.setLastColumn(col);\r
}\r
\r
/**\r
- * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, etc.\r
+ * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.\r
*\r
* @return the address of this hyperlink\r
*/\r
public String getAddress(){\r
- return record.getUrlString();\r
+ return record.getAddress();\r
}\r
\r
/**\r
- * Return text to display for this hyperlink\r
+ * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.\r
+ *\r
+ * @param address the address of this hyperlink\r
+ */\r
+ public void setAddress(String address){\r
+ record.setAddress(address);\r
+ }\r
+\r
+ /**\r
+ * Return text label for this hyperlink\r
*\r
* @return text to display\r
*/\r
return record.getLabel();\r
}\r
\r
+ /**\r
+ * Sets text label for this hyperlink\r
+ *\r
+ * @param label text label for this hyperlink\r
+ */\r
+ public void setLabel(String label){\r
+ record.setLabel(label);\r
+ }\r
+\r
/**\r
* Return the type of this hyperlink\r
*\r
* @return the type of this hyperlink\r
*/\r
- public int getType(){\r
- throw new RuntimeException("Not implemented");\r
+ protected int getType(){\r
+ return link_type;\r
}\r
}\r
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());
- }
+
+ }
}
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());
}
/**
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());
}
--- /dev/null
+/* ====================================================================\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.hssf.usermodel;\r
+\r
+import junit.framework.TestCase;\r
+\r
+import java.io.*;\r
+\r
+/**\r
+ * Tests HSSFHyperlink.\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class TestHSSFHyperlink extends TestCase {\r
+ protected String cwd = System.getProperty("HSSF.testdata.path");\r
+\r
+ /**\r
+ * Test that we can read hyperlinks.\r
+ */\r
+ public void testRead() throws Exception {\r
+\r
+ FileInputStream is = new FileInputStream(new File(cwd, "HyperlinksOnManySheets.xls"));\r
+ HSSFWorkbook wb = new HSSFWorkbook(is);\r
+ is.close();\r
+\r
+ HSSFSheet sheet;\r
+ HSSFCell cell;\r
+ HSSFHyperlink link;\r
+\r
+ sheet = wb.getSheet("WebLinks");\r
+ cell = sheet.getRow(4).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("POI", link.getLabel());\r
+ assertEquals("POI", cell.getRichStringCellValue().getString());\r
+ assertEquals("http://poi.apache.org/", link.getAddress());\r
+\r
+ cell = sheet.getRow(8).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("HSSF", link.getLabel());\r
+ assertEquals("HSSF", cell.getRichStringCellValue().getString());\r
+ assertEquals("http://poi.apache.org/hssf/", link.getAddress());\r
+\r
+ sheet = wb.getSheet("Emails");\r
+ cell = sheet.getRow(4).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("dev", link.getLabel());\r
+ assertEquals("dev", cell.getRichStringCellValue().getString());\r
+ assertEquals("mailto:dev@poi.apache.org", link.getAddress());\r
+\r
+ sheet = wb.getSheet("Internal");\r
+ cell = sheet.getRow(4).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("Link To First Sheet", link.getLabel());\r
+ assertEquals("Link To First Sheet", cell.getRichStringCellValue().getString());\r
+ assertEquals("WebLinks!A1", link.getAddress());\r
+ }\r
+\r
+ public void testModify() throws Exception {\r
+ FileInputStream is = new FileInputStream(new File(cwd, "HyperlinksOnManySheets.xls"));\r
+ HSSFWorkbook wb = new HSSFWorkbook(is);\r
+ is.close();\r
+\r
+ HSSFSheet sheet;\r
+ HSSFCell cell;\r
+ HSSFHyperlink link;\r
+\r
+ sheet = wb.getSheet("WebLinks");\r
+ cell = sheet.getRow(4).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ //modify the link\r
+ link.setAddress("www.apache.org");\r
+\r
+ //serialize and read again\r
+ ByteArrayOutputStream out = new ByteArrayOutputStream();\r
+ wb.write(out);\r
+\r
+ wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));\r
+ sheet = wb.getSheet("WebLinks");\r
+ cell = sheet.getRow(4).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("www.apache.org", link.getAddress());\r
+\r
+ }\r
+\r
+ public void testCreate() throws Exception {\r
+ HSSFWorkbook wb = new HSSFWorkbook();\r
+\r
+ HSSFCell cell;\r
+ HSSFSheet sheet = wb.createSheet("Hyperlinks");\r
+\r
+ //URL\r
+ cell = sheet.createRow(0).createCell((short)0);\r
+ cell.setCellValue("URL Link");\r
+ HSSFHyperlink link = new HSSFHyperlink(HSSFHyperlink.LINK_URL);\r
+ link.setAddress("http://poi.apache.org/");\r
+ cell.setHyperlink(link);\r
+\r
+ //link to a file in the current directory\r
+ cell = sheet.createRow(1).createCell((short)0);\r
+ cell.setCellValue("File Link");\r
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_FILE);\r
+ link.setAddress("link1.xls");\r
+ cell.setHyperlink(link);\r
+\r
+ //e-mail link\r
+ cell = sheet.createRow(2).createCell((short)0);\r
+ cell.setCellValue("Email Link");\r
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_EMAIL);\r
+ //note, if subject contains white spaces, make sure they are url-encoded\r
+ link.setAddress("mailto:poi@apache.org?subject=Hyperlinks");\r
+ cell.setHyperlink(link);\r
+\r
+ //link to a place in this workbook\r
+\r
+ //create a target sheet and cell\r
+ HSSFSheet sheet2 = wb.createSheet("Target Sheet");\r
+ sheet2.createRow(0).createCell((short)0).setCellValue("Target Cell");\r
+\r
+ cell = sheet.createRow(3).createCell((short)0);\r
+ cell.setCellValue("Worksheet Link");\r
+ link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT);\r
+ link.setAddress("'Target Sheet'!A1");\r
+ cell.setHyperlink(link);\r
+\r
+ //serialize and read again\r
+ ByteArrayOutputStream out = new ByteArrayOutputStream();\r
+ wb.write(out);\r
+\r
+ wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));\r
+ sheet = wb.getSheet("Hyperlinks");\r
+ cell = sheet.getRow(0).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("http://poi.apache.org/", link.getAddress());\r
+\r
+ cell = sheet.getRow(1).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("link1.xls", link.getAddress());\r
+\r
+ cell = sheet.getRow(2).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("mailto:poi@apache.org?subject=Hyperlinks", link.getAddress());\r
+\r
+ cell = sheet.getRow(3).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("'Target Sheet'!A1", link.getAddress());\r
+ }\r
+\r
+ public void testCloneSheet() throws Exception {\r
+ FileInputStream is = new FileInputStream(new File(cwd, "HyperlinksOnManySheets.xls"));\r
+ HSSFWorkbook wb = new HSSFWorkbook(is);\r
+ is.close();\r
+\r
+ HSSFCell cell;\r
+ HSSFHyperlink link;\r
+\r
+ HSSFSheet sheet = wb.cloneSheet(0);\r
+\r
+ cell = sheet.getRow(4).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("http://poi.apache.org/", link.getAddress());\r
+\r
+ cell = sheet.getRow(8).getCell((short)0);\r
+ link = cell.getHyperlink();\r
+ assertNotNull(link);\r
+ assertEquals("http://poi.apache.org/hssf/", link.getAddress());\r
+ }\r
+}\r