Fixes / additions for reserialization of HyperlinkRecord
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@719546 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
e3df2fb0b0
commit
e796abd3fa
@ -17,11 +17,17 @@
|
|||||||
|
|
||||||
package org.apache.poi.hssf.record;
|
package org.apache.poi.hssf.record;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
import org.apache.poi.util.StringUtil;
|
|
||||||
import org.apache.poi.util.HexDump;
|
import org.apache.poi.util.HexDump;
|
||||||
|
import org.apache.poi.util.HexRead;
|
||||||
|
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
||||||
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
|
import org.apache.poi.util.LittleEndianOutput;
|
||||||
|
import org.apache.poi.util.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The <code>HyperlinkRecord</code> (0x01B8) wraps an HLINK-record
|
* The <code>HyperlinkRecord</code> (0x01B8) wraps an HLINK-record
|
||||||
@ -31,97 +37,210 @@ import org.apache.poi.util.HexDump;
|
|||||||
* @author Mark Hissink Muller <a href="mailto:mark@hissinkmuller.nl >mark&064;hissinkmuller.nl</a>
|
* @author Mark Hissink Muller <a href="mailto:mark@hissinkmuller.nl >mark&064;hissinkmuller.nl</a>
|
||||||
* @author Yegor Kozlov (yegor at apache dot org)
|
* @author Yegor Kozlov (yegor at apache dot org)
|
||||||
*/
|
*/
|
||||||
public final class HyperlinkRecord extends Record {
|
public final class HyperlinkRecord extends StandardRecord {
|
||||||
public final static short sid = 0x01B8;
|
public final static short sid = 0x01B8;
|
||||||
|
|
||||||
|
static final class GUID {
|
||||||
|
/*
|
||||||
|
* this class is currently only used here, but could be moved to a
|
||||||
|
* common package if needed
|
||||||
|
*/
|
||||||
|
private static final int TEXT_FORMAT_LENGTH = 36;
|
||||||
|
|
||||||
|
public static final int ENCODED_SIZE = 16;
|
||||||
|
|
||||||
|
/** 4 bytes - little endian */
|
||||||
|
private final int _d1;
|
||||||
|
/** 2 bytes - little endian */
|
||||||
|
private final int _d2;
|
||||||
|
/** 2 bytes - little endian */
|
||||||
|
private final int _d3;
|
||||||
|
/**
|
||||||
|
* 8 bytes - serialized as big endian, stored with inverted endianness here
|
||||||
|
*/
|
||||||
|
private final long _d4;
|
||||||
|
|
||||||
|
public GUID(LittleEndianInput in) {
|
||||||
|
this(in.readInt(), in.readUShort(), in.readUShort(), in.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GUID(int d1, int d2, int d3, long d4) {
|
||||||
|
_d1 = d1;
|
||||||
|
_d2 = d2;
|
||||||
|
_d3 = d3;
|
||||||
|
_d4 = d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void serialize(LittleEndianOutput out) {
|
||||||
|
out.writeInt(_d1);
|
||||||
|
out.writeShort(_d2);
|
||||||
|
out.writeShort(_d3);
|
||||||
|
out.writeLong(_d4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
GUID other = (GUID) obj;
|
||||||
|
return _d1 == other._d1 && _d2 == other._d2
|
||||||
|
&& _d3 == other._d3 && _d4 == other._d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getD1() {
|
||||||
|
return _d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getD2() {
|
||||||
|
return _d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getD3() {
|
||||||
|
return _d3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getD4() {
|
||||||
|
//
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(8);
|
||||||
|
try {
|
||||||
|
new DataOutputStream(baos).writeLong(_d4);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
byte[] buf = baos.toByteArray();
|
||||||
|
return new LittleEndianByteArrayInputStream(buf).readLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatAsString() {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(36);
|
||||||
|
|
||||||
|
int PREFIX_LEN = "0x".length();
|
||||||
|
sb.append(HexDump.intToHex(_d1), PREFIX_LEN, 8);
|
||||||
|
sb.append("-");
|
||||||
|
sb.append(HexDump.shortToHex(_d2), PREFIX_LEN, 4);
|
||||||
|
sb.append("-");
|
||||||
|
sb.append(HexDump.shortToHex(_d3), PREFIX_LEN, 4);
|
||||||
|
sb.append("-");
|
||||||
|
char[] d4Chars = HexDump.longToHex(getD4());
|
||||||
|
sb.append(d4Chars, PREFIX_LEN, 4);
|
||||||
|
sb.append("-");
|
||||||
|
sb.append(d4Chars, PREFIX_LEN + 4, 12);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder(64);
|
||||||
|
sb.append(getClass().getName()).append(" [");
|
||||||
|
sb.append(formatAsString());
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a GUID in standard text form e.g.<br/>
|
||||||
|
* 13579BDF-0246-8ACE-0123-456789ABCDEF
|
||||||
|
* <br/> -> <br/>
|
||||||
|
* 0x13579BDF, 0x0246, 0x8ACE 0x0123456789ABCDEF
|
||||||
|
*/
|
||||||
|
public static GUID parse(String rep) {
|
||||||
|
char[] cc = rep.toCharArray();
|
||||||
|
if (cc.length != TEXT_FORMAT_LENGTH) {
|
||||||
|
throw new RecordFormatException("supplied text is the wrong length for a GUID");
|
||||||
|
}
|
||||||
|
int d0 = (parseShort(cc, 0) << 16) + (parseShort(cc, 4) << 0);
|
||||||
|
int d1 = parseShort(cc, 9);
|
||||||
|
int d2 = parseShort(cc, 14);
|
||||||
|
for (int i = 23; i > 19; i--) {
|
||||||
|
cc[i] = cc[i - 1];
|
||||||
|
}
|
||||||
|
long d3 = parseLELong(cc, 20);
|
||||||
|
|
||||||
|
return new GUID(d0, d1, d2, d3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long parseLELong(char[] cc, int startIndex) {
|
||||||
|
long acc = 0;
|
||||||
|
for (int i = startIndex + 14; i >= startIndex; i -= 2) {
|
||||||
|
acc <<= 4;
|
||||||
|
acc += parseHexChar(cc[i + 0]);
|
||||||
|
acc <<= 4;
|
||||||
|
acc += parseHexChar(cc[i + 1]);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseShort(char[] cc, int startIndex) {
|
||||||
|
int acc = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
acc <<= 4;
|
||||||
|
acc += parseHexChar(cc[startIndex + i]);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseHexChar(char c) {
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
if (c >= 'A' && c <= 'F') {
|
||||||
|
return c - 'A' + 10;
|
||||||
|
}
|
||||||
|
if (c >= 'a' && c <= 'f') {
|
||||||
|
return c - 'a' + 10;
|
||||||
|
}
|
||||||
|
throw new RecordFormatException("Bad hex char '" + c + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Link flags
|
* Link flags
|
||||||
*/
|
*/
|
||||||
protected static final int HLINK_URL = 0x01; // File link or URL.
|
static final int HLINK_URL = 0x01; // File link or URL.
|
||||||
protected static final int HLINK_ABS = 0x02; // Absolute path.
|
static final int HLINK_ABS = 0x02; // Absolute path.
|
||||||
protected static final int HLINK_LABEL = 0x14; // Has label.
|
static final int HLINK_LABEL = 0x14; // Has label/description.
|
||||||
protected static final int HLINK_PLACE = 0x08; // Place in worksheet.
|
/** Place in worksheet. If set, the {@link #_textMark} field will be present */
|
||||||
|
static final int HLINK_PLACE = 0x08;
|
||||||
|
private static final int HLINK_TARGET_FRAME = 0x80; // has 'target frame'
|
||||||
|
private static final int HLINK_UNC_PATH = 0x100; // has UNC path
|
||||||
|
|
||||||
|
final static GUID STD_MONIKER = GUID.parse("79EAC9D0-BAF9-11CE-8C82-00AA004BA90B");
|
||||||
|
final static GUID URL_MONIKER = GUID.parse("79EAC9E0-BAF9-11CE-8C82-00AA004BA90B");
|
||||||
|
final static GUID FILE_MONIKER = GUID.parse("00000303-0000-0000-C000-000000000046");
|
||||||
|
/** expected Tail of a URL link */
|
||||||
|
private final static byte[] URL_TAIL = HexRead.readFromString("79 58 81 F4 3B 1D 7F 48 AF 2C 82 5D C4 85 27 63 00 00 00 00 A5 AB 00 00");
|
||||||
|
/** expected Tail of a file link */
|
||||||
|
private final static byte[] FILE_TAIL = HexRead.readFromString("FF FF AD DE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00");
|
||||||
|
|
||||||
protected final static byte[] STD_MONIKER = {(byte)0xD0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11,
|
private static final int TAIL_SIZE = FILE_TAIL.length;
|
||||||
(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};
|
|
||||||
|
|
||||||
|
/** cell range of this hyperlink */
|
||||||
|
private CellRangeAddress _range;
|
||||||
|
|
||||||
|
/** 16-byte GUID */
|
||||||
|
private GUID _guid;
|
||||||
|
/** Some sort of options for file links. */
|
||||||
|
private int _fileOpts;
|
||||||
|
/** Link options. Can include any of HLINK_* flags. */
|
||||||
|
private int _linkOpts;
|
||||||
|
/** Test label */
|
||||||
|
private String _label;
|
||||||
|
|
||||||
|
private String _targetFrame;
|
||||||
|
/** Moniker. Makes sense only for URL and file links */
|
||||||
|
private GUID _moniker;
|
||||||
|
/** in 8:3 DOS format No Unicode string header,
|
||||||
|
* always 8-bit characters, zero-terminated */
|
||||||
|
private String _shortFilename;
|
||||||
|
/** Link */
|
||||||
|
private String _address;
|
||||||
/**
|
/**
|
||||||
* Tail of a URL link
|
* Text describing a place in document. In Excel UI, this is appended to the
|
||||||
|
* address, (after a '#' delimiter).<br/>
|
||||||
|
* This field is optional. If present, the {@link #HLINK_PLACE} must be set.
|
||||||
*/
|
*/
|
||||||
protected final static byte[] URL_TAIL = {0x79, 0x58, (byte)0x81, (byte)0xF4, 0x3B, 0x1D, 0x7F, 0x48, (byte)0xAF, 0x2C,
|
private String _textMark;
|
||||||
(byte)0x82, 0x5D, (byte)0xC4, (byte)0x85, 0x27, 0x63, 0x00, 0x00, 0x00,
|
|
||||||
0x00, (byte)0xA5, (byte)0xAB, 0x00, 0x00};
|
|
||||||
|
|
||||||
/**
|
private byte[] _uninterpretedTail;
|
||||||
* 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};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Create a new hyperlink
|
||||||
@ -132,117 +251,100 @@ public final class HyperlinkRecord extends Record {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the column of the first cell that contains the hyperlink
|
* @return the 0-based column of the first cell that contains this hyperlink
|
||||||
*
|
|
||||||
* @return the 0-based column of the first cell that contains the hyperlink
|
|
||||||
*/
|
*/
|
||||||
public short getFirstColumn()
|
public int getFirstColumn() {
|
||||||
{
|
return _range.getFirstColumn();
|
||||||
return colFirst;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the column of the first cell that contains the hyperlink
|
* Set the first column (zero-based)of the range that contains this hyperlink
|
||||||
*
|
|
||||||
* @param col the 0-based column of the first cell that contains the hyperlink
|
|
||||||
*/
|
*/
|
||||||
public void setFirstColumn(short col)
|
public void setFirstColumn(int col) {
|
||||||
{
|
_range.setFirstColumn(col);
|
||||||
this.colFirst = col;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the column of the last cell that contains the hyperlink
|
* @return the 0-based column of the last cell that contains this hyperlink
|
||||||
*
|
|
||||||
* @return the 0-based column of the last cell that contains the hyperlink
|
|
||||||
*/
|
*/
|
||||||
public short getLastColumn()
|
public int getLastColumn() {
|
||||||
{
|
return _range.getLastColumn();
|
||||||
return colLast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the column of the last cell that contains the hyperlink
|
* Set the last column (zero-based)of the range that contains this hyperlink
|
||||||
*
|
|
||||||
* @param col the 0-based column of the last cell that contains the hyperlink
|
|
||||||
*/
|
*/
|
||||||
public void setLastColumn(short col)
|
public void setLastColumn(int col) {
|
||||||
{
|
_range.setLastColumn(col);
|
||||||
this.colLast = col;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the row of the first cell that contains the hyperlink
|
* @return the 0-based row of the first cell that contains this hyperlink
|
||||||
*
|
|
||||||
* @return the 0-based row of the first cell that contains the hyperlink
|
|
||||||
*/
|
*/
|
||||||
public int getFirstRow()
|
public int getFirstRow() {
|
||||||
{
|
return _range.getFirstRow();
|
||||||
return rwFirst;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the row of the first cell that contains the hyperlink
|
* Set the first row (zero-based)of the range that contains this hyperlink
|
||||||
*
|
|
||||||
* @param row the 0-based row of the first cell that contains the hyperlink
|
|
||||||
*/
|
*/
|
||||||
public void setFirstRow(int row)
|
public void setFirstRow(int col) {
|
||||||
{
|
_range.setFirstRow(col);
|
||||||
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 this hyperlink
|
||||||
*
|
|
||||||
* @return the 0-based row of the last cell that contains the hyperlink
|
|
||||||
*/
|
*/
|
||||||
public int getLastRow()
|
public int getLastRow() {
|
||||||
{
|
return _range.getLastRow();
|
||||||
return rwLast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the row of the last cell that contains the hyperlink
|
* Set the last row (zero-based)of the range that contains this hyperlink
|
||||||
*
|
|
||||||
* @param row the 0-based row of the last cell that contains the hyperlink
|
|
||||||
*/
|
*/
|
||||||
public void setLastRow(int row)
|
public void setLastRow(int col) {
|
||||||
{
|
_range.setLastRow(col);
|
||||||
this.rwLast = row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a 16-byte guid identifier. Seems to always equal {@link #STD_MONIKER}
|
* @return 16-byte guid identifier Seems to always equal {@link #STD_MONIKER}
|
||||||
*
|
|
||||||
* @return 16-byte guid identifier
|
|
||||||
*/
|
*/
|
||||||
public byte[] getGuid()
|
GUID getGuid() {
|
||||||
{
|
return _guid;
|
||||||
return guid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a 16-byte moniker.
|
|
||||||
*
|
|
||||||
* @return 16-byte moniker
|
* @return 16-byte moniker
|
||||||
*/
|
*/
|
||||||
public byte[] getMoniker()
|
GUID getMoniker()
|
||||||
{
|
{
|
||||||
return moniker;
|
return _moniker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String cleanString(String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int idx = s.indexOf('\u0000');
|
||||||
|
if (idx < 0) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return s.substring(0, idx);
|
||||||
|
}
|
||||||
|
private static String appendNullTerm(String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return s + '\u0000';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return text label for this hyperlink
|
* Return text label for this hyperlink
|
||||||
*
|
*
|
||||||
* @return text to display
|
* @return text to display
|
||||||
*/
|
*/
|
||||||
public String getLabel()
|
public String getLabel() {
|
||||||
{
|
return cleanString(_label);
|
||||||
if(label == null) return null;
|
|
||||||
|
|
||||||
int idx = label.indexOf('\u0000');
|
|
||||||
return idx == -1 ? label : label.substring(0, idx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -250,205 +352,287 @@ public final class HyperlinkRecord extends Record {
|
|||||||
*
|
*
|
||||||
* @param label text label for this hyperlink
|
* @param label text label for this hyperlink
|
||||||
*/
|
*/
|
||||||
public void setLabel(String label)
|
public void setLabel(String label) {
|
||||||
{
|
_label = appendNullTerm(label);
|
||||||
this.label = label + '\u0000';
|
}
|
||||||
|
public String getTargetFrame() {
|
||||||
|
return cleanString(_targetFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.
|
* Hyperlink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.
|
||||||
*
|
*
|
||||||
* @return the address of this hyperlink
|
* @return the address of this hyperlink
|
||||||
*/
|
*/
|
||||||
public String getAddress()
|
public String getAddress() {
|
||||||
{
|
return cleanString(_address);
|
||||||
if(address == null) return null;
|
|
||||||
|
|
||||||
int idx = address.indexOf('\u0000');
|
|
||||||
return idx == -1 ? address : address.substring(0, idx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.
|
* Hyperlink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.
|
||||||
*
|
*
|
||||||
* @param address the address of this hyperlink
|
* @param address the address of this hyperlink
|
||||||
*/
|
*/
|
||||||
public void setAddress(String address)
|
public void setAddress(String address) {
|
||||||
{
|
_address = appendNullTerm(address);
|
||||||
this.address = address + '\u0000';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getShortFilename() {
|
||||||
|
return cleanString(_shortFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShortFilename(String shortFilename) {
|
||||||
|
_shortFilename = appendNullTerm(shortFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTextMark() {
|
||||||
|
return cleanString(_textMark);
|
||||||
|
}
|
||||||
|
public void setTextMark(String textMark) {
|
||||||
|
_textMark = appendNullTerm(textMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Link options. Must be a combination of HLINK_* constants.
|
* Link options. Must be a combination of HLINK_* constants.
|
||||||
|
* For testing only
|
||||||
*/
|
*/
|
||||||
public int getLinkOptions(){
|
int getLinkOptions(){
|
||||||
return link_opts;
|
return _linkOpts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label options
|
* Label options
|
||||||
*/
|
*/
|
||||||
public int getLabelOptions(){
|
public int getLabelOptions(){
|
||||||
return label_opts;
|
return 2; // always 2
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for a file link
|
* Options for a file link
|
||||||
*/
|
*/
|
||||||
public int getFileOptions(){
|
public int getFileOptions(){
|
||||||
return file_opts;
|
return _fileOpts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getTail(){
|
|
||||||
return tail;
|
public HyperlinkRecord(RecordInputStream in) {
|
||||||
|
_range = new CellRangeAddress(in);
|
||||||
|
|
||||||
|
_guid = new GUID(in);
|
||||||
|
|
||||||
|
if (in.readInt() != 0x00000002) {
|
||||||
|
throw new RecordFormatException("expected "); // TODO
|
||||||
}
|
}
|
||||||
|
_linkOpts = in.readInt();
|
||||||
|
|
||||||
/**
|
if ((_linkOpts & HLINK_LABEL) != 0){
|
||||||
* @param in the RecordInputstream to read the record from
|
|
||||||
*/
|
|
||||||
public HyperlinkRecord(RecordInputStream in)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
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();
|
int label_len = in.readInt();
|
||||||
label = in.readUnicodeLEString(label_len);
|
_label = in.readUnicodeLEString(label_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((link_opts & HLINK_URL) != 0){
|
if ((_linkOpts & HLINK_TARGET_FRAME) != 0){
|
||||||
moniker = new byte[16];
|
|
||||||
in.read(moniker);
|
|
||||||
|
|
||||||
if(Arrays.equals(URL_MONIKER, moniker)){
|
|
||||||
int len = in.readInt();
|
int len = in.readInt();
|
||||||
|
_targetFrame = in.readUnicodeLEString(len);
|
||||||
|
}
|
||||||
|
|
||||||
address = in.readUnicodeLEString(len/2);
|
if ((_linkOpts & HLINK_UNC_PATH) != 0) {
|
||||||
|
_moniker = null;
|
||||||
|
int nChars = in.readInt();
|
||||||
|
_address = in.readUnicodeLEString(nChars);
|
||||||
|
|
||||||
tail = in.readRemainder();
|
} else if ((_linkOpts & HLINK_URL) != 0) {
|
||||||
} else if (Arrays.equals(FILE_MONIKER, moniker)){
|
_moniker = new GUID(in);
|
||||||
file_opts = in.readShort();
|
|
||||||
|
if(URL_MONIKER.equals(_moniker)){
|
||||||
|
int fieldSize = in.readInt();
|
||||||
|
|
||||||
|
if ((_linkOpts & HLINK_TARGET_FRAME) != 0) {
|
||||||
|
int nChars = fieldSize/2;
|
||||||
|
_address = in.readUnicodeLEString(nChars);
|
||||||
|
} else {
|
||||||
|
int nChars = (fieldSize - TAIL_SIZE)/2;
|
||||||
|
_address = in.readUnicodeLEString(nChars);
|
||||||
|
_uninterpretedTail = readTail(URL_TAIL, in);
|
||||||
|
}
|
||||||
|
} else if (FILE_MONIKER.equals(_moniker)) {
|
||||||
|
_fileOpts = in.readShort();
|
||||||
|
|
||||||
|
int len = in.readInt();
|
||||||
|
_shortFilename = StringUtil.readCompressedUnicode(in, len);
|
||||||
|
_uninterpretedTail = readTail(FILE_TAIL, in);
|
||||||
|
int size = in.readInt();
|
||||||
|
if (size > 0) {
|
||||||
|
int charDataSize = in.readInt();
|
||||||
|
if (in.readUShort() != 0x0003) {
|
||||||
|
throw new RecordFormatException("expected "); // TODO
|
||||||
|
}
|
||||||
|
_address = StringUtil.readUnicodeLE(in, charDataSize/2);
|
||||||
|
} else {
|
||||||
|
_address = null; // TODO
|
||||||
|
}
|
||||||
|
} else if (STD_MONIKER.equals(_moniker)) {
|
||||||
|
_fileOpts = in.readShort();
|
||||||
|
|
||||||
int len = in.readInt();
|
int len = in.readInt();
|
||||||
|
|
||||||
byte[] path_bytes = new byte[len];
|
byte[] path_bytes = new byte[len];
|
||||||
in.read(path_bytes);
|
in.readFully(path_bytes);
|
||||||
|
|
||||||
address = new String(path_bytes);
|
_address = new String(path_bytes);
|
||||||
|
|
||||||
tail = in.readRemainder();
|
|
||||||
}
|
}
|
||||||
} else if((link_opts & HLINK_PLACE) != 0){
|
} else {
|
||||||
|
// can happen for a plain link within the current document
|
||||||
|
_moniker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((_linkOpts & HLINK_PLACE) != 0) {
|
||||||
|
|
||||||
int len = in.readInt();
|
int len = in.readInt();
|
||||||
address = in.readUnicodeLEString(len);
|
_textMark = in.readUnicodeLEString(len);
|
||||||
}
|
|
||||||
} catch (IOException e){
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (in.remaining() > 0) {
|
||||||
|
System.out.println(HexDump.toHex(in.readRemainder()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getSid()
|
public void serialize(LittleEndianOutput out) {
|
||||||
{
|
_range.serialize(out);
|
||||||
return HyperlinkRecord.sid;
|
|
||||||
|
_guid.serialize(out);
|
||||||
|
out.writeInt(0x00000002); // TODO const
|
||||||
|
out.writeInt(_linkOpts);
|
||||||
|
|
||||||
|
if ((_linkOpts & HLINK_LABEL) != 0){
|
||||||
|
out.writeInt(_label.length());
|
||||||
|
StringUtil.putUnicodeLE(_label, out);
|
||||||
|
}
|
||||||
|
if ((_linkOpts & HLINK_TARGET_FRAME) != 0){
|
||||||
|
out.writeInt(_targetFrame.length());
|
||||||
|
StringUtil.putUnicodeLE(_targetFrame, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int serialize(int offset, byte[] data)
|
if ((_linkOpts & HLINK_UNC_PATH) != 0) {
|
||||||
{
|
out.writeInt(_address.length());
|
||||||
int pos = offset;
|
StringUtil.putUnicodeLE(_address, out);
|
||||||
LittleEndian.putShort(data, pos, sid); pos += 2;
|
} else if ((_linkOpts & HLINK_URL) != 0){
|
||||||
LittleEndian.putShort(data, pos, ( short )(getRecordSize()-4)); pos += 2;
|
_moniker.serialize(out);
|
||||||
LittleEndian.putUShort(data, pos, rwFirst); pos += 2;
|
if(URL_MONIKER.equals(_moniker)){
|
||||||
LittleEndian.putUShort(data, pos, rwLast); pos += 2;
|
if ((_linkOpts & HLINK_TARGET_FRAME) != 0) {
|
||||||
LittleEndian.putShort(data, pos, colFirst); pos += 2;
|
out.writeInt(_address.length()*2);
|
||||||
LittleEndian.putShort(data, pos, colLast); pos += 2;
|
StringUtil.putUnicodeLE(_address, out);
|
||||||
|
} else {
|
||||||
System.arraycopy(guid, 0, data, pos, guid.length); pos += guid.length;
|
out.writeInt(_address.length()*2 + TAIL_SIZE);
|
||||||
|
StringUtil.putUnicodeLE(_address, out);
|
||||||
LittleEndian.putInt(data, pos, label_opts); pos += 4;
|
writeTail(_uninterpretedTail, out);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
if ((link_opts & HLINK_URL) != 0){
|
} else if (FILE_MONIKER.equals(_moniker)){
|
||||||
System.arraycopy(moniker, 0, data, pos, moniker.length); pos += moniker.length;
|
out.writeShort(_fileOpts);
|
||||||
if(Arrays.equals(URL_MONIKER, moniker)){
|
out.writeInt(_shortFilename.length());
|
||||||
LittleEndian.putInt(data, pos, address.length()*2 + tail.length); pos += 4;
|
StringUtil.putCompressedUnicode(_shortFilename, out);
|
||||||
StringUtil.putUnicodeLE(address, data, pos); pos += address.length()*2;
|
writeTail(_uninterpretedTail, out);
|
||||||
if(tail.length > 0){
|
if (_address == null) {
|
||||||
System.arraycopy(tail, 0, data, pos, tail.length); pos += tail.length;
|
out.writeInt(0);
|
||||||
}
|
} else {
|
||||||
} else if (Arrays.equals(FILE_MONIKER, moniker)){
|
int addrLen = _address.length() * 2;
|
||||||
LittleEndian.putShort(data, pos, file_opts); pos += 2;
|
out.writeInt(addrLen + 6);
|
||||||
LittleEndian.putInt(data, pos, address.length()); pos += 4;
|
out.writeInt(addrLen);
|
||||||
byte[] bytes = address.getBytes();
|
out.writeShort(0x0003); // TODO const
|
||||||
System.arraycopy(bytes, 0, data, pos, bytes.length); pos += bytes.length;
|
StringUtil.putUnicodeLE(_address, out);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
return getRecordSize();
|
if((_linkOpts & HLINK_PLACE) != 0){
|
||||||
|
out.writeInt(_textMark.length());
|
||||||
|
StringUtil.putUnicodeLE(_textMark, out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getDataSize() {
|
protected int getDataSize() {
|
||||||
int size = 0;
|
int size = 0;
|
||||||
size += 2 + 2 + 2 + 2; //rwFirst, rwLast, colFirst, colLast
|
size += 2 + 2 + 2 + 2; //rwFirst, rwLast, colFirst, colLast
|
||||||
size += guid.length;
|
size += GUID.ENCODED_SIZE;
|
||||||
size += 4; //label_opts
|
size += 4; //label_opts
|
||||||
size += 4; //link_opts
|
size += 4; //link_opts
|
||||||
if ((link_opts & HLINK_LABEL) != 0){
|
if ((_linkOpts & HLINK_LABEL) != 0){
|
||||||
size += 4; //link length
|
size += 4; //link length
|
||||||
size += label.length()*2;
|
size += _label.length()*2;
|
||||||
}
|
}
|
||||||
if ((link_opts & HLINK_URL) != 0){
|
if ((_linkOpts & HLINK_TARGET_FRAME) != 0){
|
||||||
size += moniker.length; //moniker length
|
size += 4; // int nChars
|
||||||
if(Arrays.equals(URL_MONIKER, moniker)){
|
size += _targetFrame.length()*2;
|
||||||
|
}
|
||||||
|
if ((_linkOpts & HLINK_UNC_PATH) != 0) {
|
||||||
|
size += 4; // int nChars
|
||||||
|
size += _address.length()*2;
|
||||||
|
} else if ((_linkOpts & HLINK_URL) != 0){
|
||||||
|
size += GUID.ENCODED_SIZE;
|
||||||
|
if(URL_MONIKER.equals(_moniker)){
|
||||||
size += 4; //address length
|
size += 4; //address length
|
||||||
size += address.length()*2;
|
size += _address.length()*2;
|
||||||
size += tail.length;
|
if ((_linkOpts & HLINK_TARGET_FRAME) == 0) {
|
||||||
} else if (Arrays.equals(FILE_MONIKER, moniker)){
|
size += TAIL_SIZE;
|
||||||
|
}
|
||||||
|
} else if (FILE_MONIKER.equals(_moniker)){
|
||||||
size += 2; //file_opts
|
size += 2; //file_opts
|
||||||
size += 4; //address length
|
size += 4; //address length
|
||||||
size += address.length();
|
size += _shortFilename.length();
|
||||||
size += tail.length;
|
size += TAIL_SIZE;
|
||||||
|
size += 4;
|
||||||
|
if (_address != null) {
|
||||||
|
size += 6;
|
||||||
|
size += _address.length() * 2;
|
||||||
}
|
}
|
||||||
} else if((link_opts & HLINK_PLACE) != 0){
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if((_linkOpts & HLINK_PLACE) != 0){
|
||||||
size += 4; //address length
|
size += 4; //address length
|
||||||
size += address.length()*2;
|
size += _textMark.length()*2;
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
|
||||||
{
|
private static byte[] readTail(byte[] expectedTail, LittleEndianInput in) {
|
||||||
|
byte[] result = new byte[TAIL_SIZE];
|
||||||
|
in.readFully(result);
|
||||||
|
if (false) { // Quite a few examples in the unit tests which don't have the exact expected tail
|
||||||
|
for (int i = 0; i < expectedTail.length; i++) {
|
||||||
|
if (expectedTail[i] != result[i]) {
|
||||||
|
System.err.println("Mismatch in tail byte [" + i + "]"
|
||||||
|
+ "expected " + (expectedTail[i] & 0xFF) + " but got " + (result[i] & 0xFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
private static void writeTail(byte[] tail, LittleEndianOutput out) {
|
||||||
|
out.write(tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getSid() {
|
||||||
|
return HyperlinkRecord.sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
StringBuffer buffer = new StringBuffer();
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
|
||||||
buffer.append("[HYPERLINK RECORD]\n");
|
buffer.append("[HYPERLINK RECORD]\n");
|
||||||
buffer.append(" .rwFirst = ").append(Integer.toHexString(getFirstRow())).append("\n");
|
buffer.append(" .range = ").append(_range.formatAsString()).append("\n");
|
||||||
buffer.append(" .rwLast = ").append(Integer.toHexString(getLastRow())).append("\n");
|
buffer.append(" .guid = ").append(_guid.formatAsString()).append("\n");
|
||||||
buffer.append(" .colFirst = ").append(Integer.toHexString(getFirstColumn())).append("\n");
|
buffer.append(" .linkOpts= ").append(HexDump.intToHex(_linkOpts)).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");
|
buffer.append(" .label = ").append(getLabel()).append("\n");
|
||||||
if((link_opts & HLINK_URL) != 0){
|
if ((_linkOpts & HLINK_TARGET_FRAME) != 0) {
|
||||||
buffer.append(" .moniker = ").append(HexDump.toHex(moniker)).append("\n");
|
buffer.append(" .targetFrame= ").append(getTargetFrame()).append("\n");
|
||||||
|
}
|
||||||
|
if((_linkOpts & HLINK_URL) != 0) {
|
||||||
|
buffer.append(" .moniker = ").append(_moniker.formatAsString()).append("\n");
|
||||||
|
}
|
||||||
|
if ((_linkOpts & HLINK_PLACE) != 0) {
|
||||||
|
buffer.append(" .targetFrame= ").append(getTextMark()).append("\n");
|
||||||
}
|
}
|
||||||
buffer.append(" .address = ").append(getAddress()).append("\n");
|
buffer.append(" .address = ").append(getAddress()).append("\n");
|
||||||
buffer.append("[/HYPERLINK RECORD]\n");
|
buffer.append("[/HYPERLINK RECORD]\n");
|
||||||
@ -459,70 +643,56 @@ public final class HyperlinkRecord extends Record {
|
|||||||
* Initialize a new url link
|
* Initialize a new url link
|
||||||
*/
|
*/
|
||||||
public void newUrlLink() {
|
public void newUrlLink() {
|
||||||
rwFirst = 0;
|
_range = new CellRangeAddress(0, 0, 0, 0);
|
||||||
rwLast = 0;
|
_guid = STD_MONIKER;
|
||||||
colFirst = 0;
|
_linkOpts = HLINK_URL | HLINK_ABS | HLINK_LABEL;
|
||||||
colLast = 0;
|
setLabel("");
|
||||||
guid = STD_MONIKER;
|
_moniker = URL_MONIKER;
|
||||||
label_opts = 0x2;
|
setAddress("");
|
||||||
link_opts = HLINK_URL | HLINK_ABS | HLINK_LABEL;
|
_uninterpretedTail = URL_TAIL;
|
||||||
label = "" + '\u0000';
|
|
||||||
moniker = URL_MONIKER;
|
|
||||||
address = "" + '\u0000';
|
|
||||||
tail = URL_TAIL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new file link
|
* Initialize a new file link
|
||||||
*/
|
*/
|
||||||
public void newFileLink() {
|
public void newFileLink() {
|
||||||
rwFirst = 0;
|
_range = new CellRangeAddress(0, 0, 0, 0);
|
||||||
rwLast = 0;
|
_guid = STD_MONIKER;
|
||||||
colFirst = 0;
|
_linkOpts = HLINK_URL | HLINK_LABEL;
|
||||||
colLast = 0;
|
_fileOpts = 0;
|
||||||
guid = STD_MONIKER;
|
setLabel("");
|
||||||
label_opts = 0x2;
|
_moniker = FILE_MONIKER;
|
||||||
link_opts = HLINK_URL | HLINK_LABEL;
|
setAddress(null);
|
||||||
file_opts = 0;
|
setShortFilename("");
|
||||||
label = "" + '\u0000';
|
_uninterpretedTail = FILE_TAIL;
|
||||||
moniker = FILE_MONIKER;
|
|
||||||
address = "" + '\0';
|
|
||||||
tail = FILE_TAIL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new document link
|
* Initialize a new document link
|
||||||
*/
|
*/
|
||||||
public void newDocumentLink() {
|
public void newDocumentLink() {
|
||||||
rwFirst = 0;
|
_range = new CellRangeAddress(0, 0, 0, 0);
|
||||||
rwLast = 0;
|
_guid = STD_MONIKER;
|
||||||
colFirst = 0;
|
_linkOpts = HLINK_LABEL | HLINK_PLACE;
|
||||||
colLast = 0;
|
setLabel("");
|
||||||
guid = STD_MONIKER;
|
_moniker = FILE_MONIKER;
|
||||||
label_opts = 0x2;
|
setAddress("");
|
||||||
link_opts = HLINK_LABEL | HLINK_PLACE;
|
setTextMark("");
|
||||||
label = "" + '\u0000';
|
|
||||||
moniker = FILE_MONIKER;
|
|
||||||
address = "" + '\0';
|
|
||||||
tail = new byte[]{};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object clone() {
|
public Object clone() {
|
||||||
HyperlinkRecord rec = new HyperlinkRecord();
|
HyperlinkRecord rec = new HyperlinkRecord();
|
||||||
rec.rwFirst = rwFirst;
|
rec._range = _range.copy();
|
||||||
rec.rwLast = rwLast;
|
rec._guid = _guid;
|
||||||
rec.colFirst = colFirst;
|
rec._linkOpts = _linkOpts;
|
||||||
rec.colLast = colLast;
|
rec._fileOpts = _fileOpts;
|
||||||
rec.guid = guid;
|
rec._label = _label;
|
||||||
rec.label_opts = label_opts;
|
rec._address = _address;
|
||||||
rec.link_opts = link_opts;
|
rec._moniker = _moniker;
|
||||||
rec.file_opts = file_opts;
|
rec._shortFilename = _shortFilename;
|
||||||
rec.label = label;
|
rec._targetFrame = _targetFrame;
|
||||||
rec.address = address;
|
rec._textMark = _textMark;
|
||||||
rec.moniker = moniker;
|
rec._uninterpretedTail = _uninterpretedTail;
|
||||||
rec.tail = tail;
|
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,18 @@ public class HSSFHyperlink implements Hyperlink {
|
|||||||
public String getAddress(){
|
public String getAddress(){
|
||||||
return record.getAddress();
|
return record.getAddress();
|
||||||
}
|
}
|
||||||
|
public String getTextMark(){
|
||||||
|
return record.getTextMark();
|
||||||
|
}
|
||||||
|
public void setTextMark(String textMark) {
|
||||||
|
record.setTextMark(textMark);
|
||||||
|
}
|
||||||
|
public String getShortFilename(){
|
||||||
|
return record.getShortFilename();
|
||||||
|
}
|
||||||
|
public void setShortFilename(String shortFilename) {
|
||||||
|
record.setShortFilename(shortFilename);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.
|
* Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc.
|
||||||
|
@ -16,11 +16,17 @@
|
|||||||
==================================================================== */
|
==================================================================== */
|
||||||
package org.apache.poi.hssf.record;
|
package org.apache.poi.hssf.record;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.HyperlinkRecord.GUID;
|
||||||
|
import org.apache.poi.util.HexDump;
|
||||||
|
import org.apache.poi.util.HexRead;
|
||||||
|
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
||||||
|
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test HyperlinkRecord
|
* Test HyperlinkRecord
|
||||||
*
|
*
|
||||||
@ -143,7 +149,7 @@ public final class TestHyperlinkRecord extends TestCase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//link to a place in worksheet: Sheet1!A1
|
//link to a place in worksheet: Sheet1!A1
|
||||||
byte[] data4 = {0x03, 0x00,
|
private static final byte[] data4 = {0x03, 0x00,
|
||||||
0x03, 0x00,
|
0x03, 0x00,
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
@ -166,8 +172,79 @@ public final class TestHyperlinkRecord extends TestCase {
|
|||||||
0x53, 0x00, 0x68, 0x00, 0x65, 0x00, 0x65, 0x00, 0x74, 0x00, 0x31, 0x00, 0x21,
|
0x53, 0x00, 0x68, 0x00, 0x65, 0x00, 0x65, 0x00, 0x74, 0x00, 0x31, 0x00, 0x21,
|
||||||
0x00, 0x41, 0x00, 0x31, 0x00, 0x00, 0x00};
|
0x00, 0x41, 0x00, 0x31, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
private void confirmGUID(byte[] expectedGuid, byte[] actualGuid) {
|
private static final byte[] dataLinkToWorkbook = HexRead.readFromString("01 00 01 00 01 00 01 00 " +
|
||||||
assertTrue(Arrays.equals(expectedGuid, actualGuid));
|
"D0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B " +
|
||||||
|
"02 00 00 00 " +
|
||||||
|
"1D 00 00 00 " + // options: LABEL | PLACE | FILE_OR_URL
|
||||||
|
// label: "My Label"
|
||||||
|
"09 00 00 00 " +
|
||||||
|
"4D 00 79 00 20 00 4C 00 61 00 62 00 65 00 6C 00 00 00 " +
|
||||||
|
"03 03 00 00 00 00 00 00 C0 00 00 00 00 00 00 46 " + // file GUID
|
||||||
|
"00 00 " + // file options
|
||||||
|
// shortFileName: "YEARFR~1.XLS"
|
||||||
|
"0D 00 00 00 " +
|
||||||
|
"59 45 41 52 46 52 7E 31 2E 58 4C 53 00 " +
|
||||||
|
// FILE_TAIL - unknown byte sequence
|
||||||
|
"FF FF AD DE 00 00 00 00 " +
|
||||||
|
"00 00 00 00 00 00 00 00 " +
|
||||||
|
"00 00 00 00 00 00 00 00 " +
|
||||||
|
// field len, char data len
|
||||||
|
"2E 00 00 00 " +
|
||||||
|
"28 00 00 00 " +
|
||||||
|
"03 00 " + // unknown ushort
|
||||||
|
// _address: "yearfracExamples.xls"
|
||||||
|
"79 00 65 00 61 00 72 00 66 00 72 00 61 00 63 00 " +
|
||||||
|
"45 00 78 00 61 00 6D 00 70 00 6C 00 65 00 73 00 " +
|
||||||
|
"2E 00 78 00 6C 00 73 00 " +
|
||||||
|
// textMark: "Sheet1!B6"
|
||||||
|
"0A 00 00 00 " +
|
||||||
|
"53 00 68 00 65 00 65 00 74 00 31 00 21 00 42 00 " +
|
||||||
|
"36 00 00 00");
|
||||||
|
|
||||||
|
private static final byte[] dataTargetFrame = HexRead.readFromString("0E 00 0E 00 00 00 00 00 " +
|
||||||
|
"D0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B " +
|
||||||
|
"02 00 00 00 " +
|
||||||
|
"83 00 00 00 " + // options: TARGET_FRAME | ABS | FILE_OR_URL
|
||||||
|
// targetFrame: "_blank"
|
||||||
|
"07 00 00 00 " +
|
||||||
|
"5F 00 62 00 6C 00 61 00 6E 00 6B 00 00 00 " +
|
||||||
|
// url GUID
|
||||||
|
"E0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B " +
|
||||||
|
// address: "http://www.regnow.com/softsell/nph-softsell.cgi?currency=USD&item=7924-37"
|
||||||
|
"94 00 00 00 " +
|
||||||
|
"68 00 74 00 74 00 70 00 3A 00 2F 00 2F 00 77 00 " +
|
||||||
|
"77 00 77 00 2E 00 72 00 65 00 67 00 6E 00 6F 00 " +
|
||||||
|
"77 00 2E 00 63 00 6F 00 6D 00 2F 00 73 00 6F 00 " +
|
||||||
|
"66 00 74 00 73 00 65 00 6C 00 6C 00 2F 00 6E 00 " +
|
||||||
|
"70 00 68 00 2D 00 73 00 6F 00 66 00 74 00 73 00 " +
|
||||||
|
"65 00 6C 00 6C 00 2E 00 63 00 67 00 69 00 3F 00 " +
|
||||||
|
"63 00 75 00 72 00 72 00 65 00 6E 00 63 00 79 00 " +
|
||||||
|
"3D 00 55 00 53 00 44 00 26 00 69 00 74 00 65 00 " +
|
||||||
|
"6D 00 3D 00 37 00 39 00 32 00 34 00 2D 00 33 00 " +
|
||||||
|
"37 00 00 00");
|
||||||
|
|
||||||
|
|
||||||
|
private static final byte[] dataUNC = HexRead.readFromString("01 00 01 00 01 00 01 00 " +
|
||||||
|
"D0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B " +
|
||||||
|
"02 00 00 00 " +
|
||||||
|
"1F 01 00 00 " + // options: UNC_PATH | LABEL | TEXT_MARK | ABS | FILE_OR_URL
|
||||||
|
"09 00 00 00 " + // label: "My Label"
|
||||||
|
"4D 00 79 00 20 00 6C 00 61 00 62 00 65 00 6C 00 00 00 " +
|
||||||
|
// note - no moniker GUID
|
||||||
|
"27 00 00 00 " + // "\\\\MyServer\\my-share\\myDir\\PRODNAME.xls"
|
||||||
|
"5C 00 5C 00 4D 00 79 00 53 00 65 00 72 00 76 00 " +
|
||||||
|
"65 00 72 00 5C 00 6D 00 79 00 2D 00 73 00 68 00 " +
|
||||||
|
"61 00 72 00 65 00 5C 00 6D 00 79 00 44 00 69 00 " +
|
||||||
|
"72 00 5C 00 50 00 52 00 4F 00 44 00 4E 00 41 00 " +
|
||||||
|
"4D 00 45 00 2E 00 78 00 6C 00 73 00 00 00 " +
|
||||||
|
|
||||||
|
"0C 00 00 00 " + // textMark: PRODNAME!C2
|
||||||
|
"50 00 52 00 4F 00 44 00 4E 00 41 00 4D 00 45 00 21 00 " +
|
||||||
|
"43 00 32 00 00 00");
|
||||||
|
|
||||||
|
|
||||||
|
private void confirmGUID(GUID expectedGuid, GUID actualGuid) {
|
||||||
|
assertEquals(expectedGuid, actualGuid);
|
||||||
}
|
}
|
||||||
public void testReadURLLink(){
|
public void testReadURLLink(){
|
||||||
RecordInputStream is = TestcaseRecordInputStream.create(HyperlinkRecord.sid, data1);
|
RecordInputStream is = TestcaseRecordInputStream.create(HyperlinkRecord.sid, data1);
|
||||||
@ -203,7 +280,8 @@ public final class TestHyperlinkRecord extends TestCase {
|
|||||||
assertEquals(opts, link.getLinkOptions());
|
assertEquals(opts, link.getLinkOptions());
|
||||||
|
|
||||||
assertEquals("file", link.getLabel());
|
assertEquals("file", link.getLabel());
|
||||||
assertEquals("link1.xls", link.getAddress());
|
assertEquals("link1.xls", link.getShortFilename());
|
||||||
|
assertEquals(null, link.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadEmailLink(){
|
public void testReadEmailLink(){
|
||||||
@ -238,7 +316,8 @@ public final class TestHyperlinkRecord extends TestCase {
|
|||||||
assertEquals(opts, link.getLinkOptions());
|
assertEquals(opts, link.getLinkOptions());
|
||||||
|
|
||||||
assertEquals("place", link.getLabel());
|
assertEquals("place", link.getLabel());
|
||||||
assertEquals("Sheet1!A1", link.getAddress());
|
assertEquals("Sheet1!A1", link.getTextMark());
|
||||||
|
assertEquals(null, link.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void serialize(byte[] data){
|
private void serialize(byte[] data){
|
||||||
@ -280,7 +359,7 @@ public final class TestHyperlinkRecord extends TestCase {
|
|||||||
link.setFirstRow((short)0);
|
link.setFirstRow((short)0);
|
||||||
link.setLastRow((short)0);
|
link.setLastRow((short)0);
|
||||||
link.setLabel("file");
|
link.setLabel("file");
|
||||||
link.setAddress("link1.xls");
|
link.setShortFilename("link1.xls");
|
||||||
|
|
||||||
byte[] tmp = link.serialize();
|
byte[] tmp = link.serialize();
|
||||||
byte[] ser = new byte[tmp.length-4];
|
byte[] ser = new byte[tmp.length-4];
|
||||||
@ -295,7 +374,7 @@ public final class TestHyperlinkRecord extends TestCase {
|
|||||||
link.setFirstRow((short)3);
|
link.setFirstRow((short)3);
|
||||||
link.setLastRow((short)3);
|
link.setLastRow((short)3);
|
||||||
link.setLabel("place");
|
link.setLabel("place");
|
||||||
link.setAddress("Sheet1!A1");
|
link.setTextMark("Sheet1!A1");
|
||||||
|
|
||||||
byte[] tmp = link.serialize();
|
byte[] tmp = link.serialize();
|
||||||
byte[] ser = new byte[tmp.length-4];
|
byte[] ser = new byte[tmp.length-4];
|
||||||
@ -329,4 +408,70 @@ public final class TestHyperlinkRecord extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testReserializeTargetFrame() {
|
||||||
|
RecordInputStream in = TestcaseRecordInputStream.create(HyperlinkRecord.sid, dataTargetFrame);
|
||||||
|
HyperlinkRecord hr = new HyperlinkRecord(in);
|
||||||
|
byte[] ser = hr.serialize();
|
||||||
|
TestcaseRecordInputStream.confirmRecordEncoding(HyperlinkRecord.sid, dataTargetFrame, ser);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void testReserializeLinkToWorkbook() {
|
||||||
|
|
||||||
|
RecordInputStream in = TestcaseRecordInputStream.create(HyperlinkRecord.sid, dataLinkToWorkbook);
|
||||||
|
HyperlinkRecord hr = new HyperlinkRecord(in);
|
||||||
|
byte[] ser = hr.serialize();
|
||||||
|
TestcaseRecordInputStream.confirmRecordEncoding(HyperlinkRecord.sid, dataLinkToWorkbook, ser);
|
||||||
|
if ("YEARFR~1.XLS".equals(hr.getAddress())) {
|
||||||
|
throw new AssertionFailedError("Identified bug in reading workbook link");
|
||||||
|
}
|
||||||
|
assertEquals("yearfracExamples.xls", hr.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReserializeUNC() {
|
||||||
|
|
||||||
|
RecordInputStream in = TestcaseRecordInputStream.create(HyperlinkRecord.sid, dataUNC);
|
||||||
|
HyperlinkRecord hr = new HyperlinkRecord(in);
|
||||||
|
byte[] ser = hr.serialize();
|
||||||
|
TestcaseRecordInputStream.confirmRecordEncoding(HyperlinkRecord.sid, dataUNC, ser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGUID() {
|
||||||
|
GUID g;
|
||||||
|
g = GUID.parse("3F2504E0-4F89-11D3-9A0C-0305E82C3301");
|
||||||
|
confirmGUID(g, 0x3F2504E0, 0x4F89, 0x11D3, 0x9A0C0305E82C3301L);
|
||||||
|
assertEquals("3F2504E0-4F89-11D3-9A0C-0305E82C3301", g.formatAsString());
|
||||||
|
|
||||||
|
g = GUID.parse("13579BDF-0246-8ACE-0123-456789ABCDEF");
|
||||||
|
confirmGUID(g, 0x13579BDF, 0x0246, 0x8ACE, 0x0123456789ABCDEFL);
|
||||||
|
assertEquals("13579BDF-0246-8ACE-0123-456789ABCDEF", g.formatAsString());
|
||||||
|
|
||||||
|
byte[] buf = new byte[16];
|
||||||
|
g.serialize(new LittleEndianByteArrayOutputStream(buf, 0));
|
||||||
|
String expectedDump = "[DF, 9B, 57, 13, 46, 02, CE, 8A, 01, 23, 45, 67, 89, AB, CD, EF]";
|
||||||
|
assertEquals(expectedDump, HexDump.toHex(buf));
|
||||||
|
|
||||||
|
// STD Moniker
|
||||||
|
g = createFromStreamDump("[D0, C9, EA, 79, F9, BA, CE, 11, 8C, 82, 00, AA, 00, 4B, A9, 0B]");
|
||||||
|
assertEquals("79EAC9D0-BAF9-11CE-8C82-00AA004BA90B", g.formatAsString());
|
||||||
|
// URL Moniker
|
||||||
|
g = createFromStreamDump("[E0, C9, EA, 79, F9, BA, CE, 11, 8C, 82, 00, AA, 00, 4B, A9, 0B]");
|
||||||
|
assertEquals("79EAC9E0-BAF9-11CE-8C82-00AA004BA90B", g.formatAsString());
|
||||||
|
// File Moniker
|
||||||
|
g = createFromStreamDump("[03, 03, 00, 00, 00, 00, 00, 00, C0, 00, 00, 00, 00, 00, 00, 46]");
|
||||||
|
assertEquals("00000303-0000-0000-C000-000000000046", g.formatAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GUID createFromStreamDump(String s) {
|
||||||
|
return new GUID(new LittleEndianByteArrayInputStream(HexRead.readFromString(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmGUID(GUID g, int d1, int d2, int d3, long d4) {
|
||||||
|
assertEquals(new String(HexDump.intToHex(d1)), new String(HexDump.intToHex(g.getD1())));
|
||||||
|
assertEquals(new String(HexDump.shortToHex(d2)), new String(HexDump.shortToHex(g.getD2())));
|
||||||
|
assertEquals(new String(HexDump.shortToHex(d3)), new String(HexDump.shortToHex(g.getD3())));
|
||||||
|
assertEquals(new String(HexDump.longToHex(d4)), new String(HexDump.longToHex(g.getD4())));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ public final class TestHSSFHyperlink extends TestCase {
|
|||||||
assertNotNull(link);
|
assertNotNull(link);
|
||||||
assertEquals("Link To First Sheet", link.getLabel());
|
assertEquals("Link To First Sheet", link.getLabel());
|
||||||
assertEquals("Link To First Sheet", cell.getRichStringCellValue().getString());
|
assertEquals("Link To First Sheet", cell.getRichStringCellValue().getString());
|
||||||
assertEquals("WebLinks!A1", link.getAddress());
|
assertEquals("WebLinks!A1", link.getTextMark());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testModify() throws Exception {
|
public void testModify() throws Exception {
|
||||||
@ -136,7 +136,7 @@ public final class TestHSSFHyperlink extends TestCase {
|
|||||||
cell = sheet.createRow(3).createCell(0);
|
cell = sheet.createRow(3).createCell(0);
|
||||||
cell.setCellValue("Worksheet Link");
|
cell.setCellValue("Worksheet Link");
|
||||||
link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT);
|
link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT);
|
||||||
link.setAddress("'Target Sheet'!A1");
|
link.setTextMark("'Target Sheet'!A1");
|
||||||
cell.setHyperlink(link);
|
cell.setHyperlink(link);
|
||||||
|
|
||||||
//serialize and read again
|
//serialize and read again
|
||||||
@ -163,7 +163,7 @@ public final class TestHSSFHyperlink extends TestCase {
|
|||||||
cell = sheet.getRow(3).getCell(0);
|
cell = sheet.getRow(3).getCell(0);
|
||||||
link = cell.getHyperlink();
|
link = cell.getHyperlink();
|
||||||
assertNotNull(link);
|
assertNotNull(link);
|
||||||
assertEquals("'Target Sheet'!A1", link.getAddress());
|
assertEquals("'Target Sheet'!A1", link.getTextMark());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCloneSheet() {
|
public void testCloneSheet() {
|
||||||
|
Loading…
Reference in New Issue
Block a user