package jxl.write.biff; import common.Assert; import common.Logger; import java.io.File; import java.net.URL; import java.util.ArrayList; import jxl.CellType; import jxl.Hyperlink; import jxl.Range; import jxl.Sheet; import jxl.biff.CellReferenceHelper; import jxl.biff.IntegerHelper; import jxl.biff.SheetRangeImpl; import jxl.biff.StringHelper; import jxl.biff.Type; import jxl.biff.WritableRecordData; import jxl.write.Label; import jxl.write.WritableCell; import jxl.write.WritableSheet; public class HyperlinkRecord extends WritableRecordData { private static Logger logger = Logger.getLogger(HyperlinkRecord.class); private int firstRow; private int lastRow; private int firstColumn; private int lastColumn; private URL url; private File file; private String location; private String contents; private LinkType linkType; private byte[] data; private Range range; private WritableSheet sheet; private boolean modified; private static class LinkType { private LinkType() {} } private static final LinkType urlLink = new LinkType(); private static final LinkType fileLink = new LinkType(); private static final LinkType uncLink = new LinkType(); private static final LinkType workbookLink = new LinkType(); private static final LinkType unknown = new LinkType(); protected HyperlinkRecord(Hyperlink h, WritableSheet s) { super(Type.HLINK); Assert.verify(h instanceof jxl.read.biff.HyperlinkRecord); jxl.read.biff.HyperlinkRecord hl = (jxl.read.biff.HyperlinkRecord)h; this.data = hl.getRecord().getData(); this.sheet = s; this.firstRow = hl.getRow(); this.firstColumn = hl.getColumn(); this.lastRow = hl.getLastRow(); this.lastColumn = hl.getLastColumn(); this.range = (Range)new SheetRangeImpl((Sheet)s, this.firstColumn, this.firstRow, this.lastColumn, this.lastRow); this.linkType = unknown; if (hl.isFile()) { this.linkType = fileLink; this.file = hl.getFile(); } else if (hl.isURL()) { this.linkType = urlLink; this.url = hl.getURL(); } else if (hl.isLocation()) { this.linkType = workbookLink; this.location = hl.getLocation(); } this.modified = false; } protected HyperlinkRecord(int col, int row, int lastcol, int lastrow, URL url, String desc) { super(Type.HLINK); this.firstColumn = col; this.firstRow = row; this.lastColumn = Math.max(this.firstColumn, lastcol); this.lastRow = Math.max(this.firstRow, lastrow); this.url = url; this.contents = desc; this.linkType = urlLink; this.modified = true; } protected HyperlinkRecord(int col, int row, int lastcol, int lastrow, File file, String desc) { super(Type.HLINK); this.firstColumn = col; this.firstRow = row; this.lastColumn = Math.max(this.firstColumn, lastcol); this.lastRow = Math.max(this.firstRow, lastrow); this.contents = desc; this.file = file; if (file.getPath().startsWith("\\\\")) { this.linkType = uncLink; } else { this.linkType = fileLink; } this.modified = true; } protected HyperlinkRecord(int col, int row, int lastcol, int lastrow, String desc, WritableSheet s, int destcol, int destrow, int lastdestcol, int lastdestrow) { super(Type.HLINK); this.firstColumn = col; this.firstRow = row; this.lastColumn = Math.max(this.firstColumn, lastcol); this.lastRow = Math.max(this.firstRow, lastrow); setLocation(s, destcol, destrow, lastdestcol, lastdestrow); this.contents = desc; this.linkType = workbookLink; this.modified = true; } public boolean isFile() { return (this.linkType == fileLink); } public boolean isUNC() { return (this.linkType == uncLink); } public boolean isURL() { return (this.linkType == urlLink); } public boolean isLocation() { return (this.linkType == workbookLink); } public int getRow() { return this.firstRow; } public int getColumn() { return this.firstColumn; } public int getLastRow() { return this.lastRow; } public int getLastColumn() { return this.lastColumn; } public URL getURL() { return this.url; } public File getFile() { return this.file; } public byte[] getData() { if (!this.modified) return this.data; byte[] commonData = new byte[32]; IntegerHelper.getTwoBytes(this.firstRow, commonData, 0); IntegerHelper.getTwoBytes(this.lastRow, commonData, 2); IntegerHelper.getTwoBytes(this.firstColumn, commonData, 4); IntegerHelper.getTwoBytes(this.lastColumn, commonData, 6); commonData[8] = -48; commonData[9] = -55; commonData[10] = -22; commonData[11] = 121; commonData[12] = -7; commonData[13] = -70; commonData[14] = -50; commonData[15] = 17; commonData[16] = -116; commonData[17] = -126; commonData[18] = 0; commonData[19] = -86; commonData[20] = 0; commonData[21] = 75; commonData[22] = -87; commonData[23] = 11; commonData[24] = 2; commonData[25] = 0; commonData[26] = 0; commonData[27] = 0; int optionFlags = 0; if (isURL()) { optionFlags = 3; if (this.contents != null) optionFlags |= 0x14; } else if (isFile()) { optionFlags = 3; if (this.contents == null) optionFlags |= 0x14; } else if (isLocation()) { optionFlags = 8; } else if (isUNC()) { optionFlags = 259; } IntegerHelper.getFourBytes(optionFlags, commonData, 28); if (isURL()) { this.data = getURLData(commonData); } else if (isFile()) { this.data = getFileData(commonData); } else if (isLocation()) { this.data = getLocationData(commonData); } else if (isUNC()) { this.data = getUNCData(commonData); } return this.data; } public String toString() { if (isFile()) return this.file.toString(); if (isURL()) return this.url.toString(); if (isUNC()) return this.file.toString(); return ""; } public Range getRange() { return this.range; } public void setURL(URL url) { this.linkType = urlLink; this.file = null; this.location = null; this.contents = null; this.url = url; this.modified = true; if (this.sheet == null) return; WritableCell wc = this.sheet.getWritableCell(this.firstColumn, this.firstRow); Assert.verify((wc.getType() == CellType.LABEL)); Label l = (Label)wc; l.setString(url.toString()); } public void setFile(File file) { this.linkType = fileLink; this.url = null; this.location = null; this.contents = null; this.file = file; this.modified = true; if (this.sheet == null) return; WritableCell wc = this.sheet.getWritableCell(this.firstColumn, this.firstRow); Assert.verify((wc.getType() == CellType.LABEL)); Label l = (Label)wc; l.setString(file.toString()); } protected void setLocation(String desc, WritableSheet sheet, int destcol, int destrow, int lastdestcol, int lastdestrow) { this.linkType = workbookLink; this.url = null; this.file = null; this.modified = true; this.contents = desc; setLocation(sheet, destcol, destrow, lastdestcol, lastdestrow); if (sheet == null) return; WritableCell wc = sheet.getWritableCell(this.firstColumn, this.firstRow); Assert.verify((wc.getType() == CellType.LABEL)); Label l = (Label)wc; l.setString(desc); } private void setLocation(WritableSheet sheet, int destcol, int destrow, int lastdestcol, int lastdestrow) { StringBuffer sb = new StringBuffer(); sb.append('\''); if (sheet.getName().indexOf('\'') == -1) { sb.append(sheet.getName()); } else { String sheetName = sheet.getName(); int pos = 0; int nextPos = sheetName.indexOf('\'', pos); while (nextPos != -1 && pos < sheetName.length()) { sb.append(sheetName.substring(pos, nextPos)); sb.append("''"); pos = nextPos + 1; nextPos = sheetName.indexOf('\'', pos); } sb.append(sheetName.substring(pos)); } sb.append('\''); sb.append('!'); lastdestcol = Math.max(destcol, lastdestcol); lastdestrow = Math.max(destrow, lastdestrow); CellReferenceHelper.getCellReference(destcol, destrow, sb); sb.append(':'); CellReferenceHelper.getCellReference(lastdestcol, lastdestrow, sb); this.location = sb.toString(); } void insertRow(int r) { Assert.verify((this.sheet != null && this.range != null)); if (r > this.lastRow) return; if (r <= this.firstRow) { this.firstRow++; this.modified = true; } if (r <= this.lastRow) { this.lastRow++; this.modified = true; } if (this.modified) this.range = (Range)new SheetRangeImpl((Sheet)this.sheet, this.firstColumn, this.firstRow, this.lastColumn, this.lastRow); } void insertColumn(int c) { Assert.verify((this.sheet != null && this.range != null)); if (c > this.lastColumn) return; if (c <= this.firstColumn) { this.firstColumn++; this.modified = true; } if (c <= this.lastColumn) { this.lastColumn++; this.modified = true; } if (this.modified) this.range = (Range)new SheetRangeImpl((Sheet)this.sheet, this.firstColumn, this.firstRow, this.lastColumn, this.lastRow); } void removeRow(int r) { Assert.verify((this.sheet != null && this.range != null)); if (r > this.lastRow) return; if (r < this.firstRow) { this.firstRow--; this.modified = true; } if (r < this.lastRow) { this.lastRow--; this.modified = true; } if (this.modified) { Assert.verify((this.range != null)); this.range = (Range)new SheetRangeImpl((Sheet)this.sheet, this.firstColumn, this.firstRow, this.lastColumn, this.lastRow); } } void removeColumn(int c) { Assert.verify((this.sheet != null && this.range != null)); if (c > this.lastColumn) return; if (c < this.firstColumn) { this.firstColumn--; this.modified = true; } if (c < this.lastColumn) { this.lastColumn--; this.modified = true; } if (this.modified) { Assert.verify((this.range != null)); this.range = (Range)new SheetRangeImpl((Sheet)this.sheet, this.firstColumn, this.firstRow, this.lastColumn, this.lastRow); } } private byte[] getURLData(byte[] cd) { String urlString = this.url.toString(); int dataLength = cd.length + 20 + (urlString.length() + 1) * 2; if (this.contents != null) dataLength += 4 + (this.contents.length() + 1) * 2; byte[] d = new byte[dataLength]; System.arraycopy(cd, 0, d, 0, cd.length); int urlPos = cd.length; if (this.contents != null) { IntegerHelper.getFourBytes(this.contents.length() + 1, d, urlPos); StringHelper.getUnicodeBytes(this.contents, d, urlPos + 4); urlPos += (this.contents.length() + 1) * 2 + 4; } d[urlPos] = -32; d[urlPos + 1] = -55; d[urlPos + 2] = -22; d[urlPos + 3] = 121; d[urlPos + 4] = -7; d[urlPos + 5] = -70; d[urlPos + 6] = -50; d[urlPos + 7] = 17; d[urlPos + 8] = -116; d[urlPos + 9] = -126; d[urlPos + 10] = 0; d[urlPos + 11] = -86; d[urlPos + 12] = 0; d[urlPos + 13] = 75; d[urlPos + 14] = -87; d[urlPos + 15] = 11; IntegerHelper.getFourBytes((urlString.length() + 1) * 2, d, urlPos + 16); StringHelper.getUnicodeBytes(urlString, d, urlPos + 20); return d; } private byte[] getUNCData(byte[] cd) { String uncString = this.file.getPath(); byte[] d = new byte[cd.length + uncString.length() * 2 + 2 + 4]; System.arraycopy(cd, 0, d, 0, cd.length); int urlPos = cd.length; int length = uncString.length() + 1; IntegerHelper.getFourBytes(length, d, urlPos); StringHelper.getUnicodeBytes(uncString, d, urlPos + 4); return d; } private byte[] getFileData(byte[] cd) { ArrayList path = new ArrayList(); ArrayList shortFileName = new ArrayList(); path.add(this.file.getName()); shortFileName.add(getShortName(this.file.getName())); File parent = this.file.getParentFile(); while (parent != null) { path.add(parent.getName()); shortFileName.add(getShortName(parent.getName())); parent = parent.getParentFile(); } int upLevelCount = 0; int pos = path.size() - 1; boolean upDir = true; while (upDir) { String s = path.get(pos); if (s.equals("..")) { upLevelCount++; path.remove(pos); shortFileName.remove(pos); } else { upDir = false; } pos--; } StringBuffer filePathSB = new StringBuffer(); StringBuffer shortFilePathSB = new StringBuffer(); if (this.file.getPath().charAt(1) == ':') { char driveLetter = this.file.getPath().charAt(0); if (driveLetter != 'C' && driveLetter != 'c') { filePathSB.append(driveLetter); filePathSB.append(':'); shortFilePathSB.append(driveLetter); shortFilePathSB.append(':'); } } for (int i = path.size() - 1; i >= 0; i--) { filePathSB.append(path.get(i)); shortFilePathSB.append(shortFileName.get(i)); if (i != 0) { filePathSB.append("\\"); shortFilePathSB.append("\\"); } } String filePath = filePathSB.toString(); String shortFilePath = shortFilePathSB.toString(); int dataLength = cd.length + 4 + (shortFilePath.length() + 1) * 2 + 16 + 2 + 4 + filePath.length() + 1 + 4 + 24; if (this.contents != null) dataLength += 4 + (this.contents.length() + 1) * 2; byte[] d = new byte[dataLength]; System.arraycopy(cd, 0, d, 0, cd.length); int filePos = cd.length; if (this.contents != null) { IntegerHelper.getFourBytes(this.contents.length() + 1, d, filePos); StringHelper.getUnicodeBytes(this.contents, d, filePos + 4); filePos += (this.contents.length() + 1) * 2 + 4; } int curPos = filePos; IntegerHelper.getFourBytes(shortFilePath.length() + 1, d, curPos); StringHelper.getUnicodeBytes(shortFilePath, d, curPos + 4); curPos += 4 + (shortFilePath.length() + 1) * 2; d[curPos] = 3; d[curPos + 1] = 3; d[curPos + 2] = 0; d[curPos + 3] = 0; d[curPos + 4] = 0; d[curPos + 5] = 0; d[curPos + 6] = 0; d[curPos + 7] = 0; d[curPos + 8] = -64; d[curPos + 9] = 0; d[curPos + 10] = 0; d[curPos + 11] = 0; d[curPos + 12] = 0; d[curPos + 13] = 0; d[curPos + 14] = 0; d[curPos + 15] = 70; curPos += 16; IntegerHelper.getTwoBytes(upLevelCount, d, curPos); curPos += 2; IntegerHelper.getFourBytes(filePath.length() + 1, d, curPos); curPos += 4; StringHelper.getBytes(filePath, d, curPos); curPos += filePath.length() + 1; d[curPos] = -1; d[curPos + 1] = -1; d[curPos + 2] = -83; d[curPos + 3] = -34; return d; } private String getShortName(String s) { int sep = s.indexOf('.'); String prefix = null; String suffix = null; if (sep == -1) { prefix = s; suffix = ""; } else { prefix = s.substring(0, sep); suffix = s.substring(sep + 1); } if (prefix.length() > 8) { prefix = prefix.substring(0, 6) + "~" + (prefix.length() - 6); prefix = prefix.substring(0, 8); } suffix = suffix.substring(0, Math.min(3, suffix.length())); if (suffix.length() > 0) return prefix + '.' + suffix; return prefix; } private byte[] getLocationData(byte[] cd) { byte[] d = new byte[cd.length + 4 + (this.location.length() + 1) * 2]; System.arraycopy(cd, 0, d, 0, cd.length); int locPos = cd.length; IntegerHelper.getFourBytes(this.location.length() + 1, d, locPos); StringHelper.getUnicodeBytes(this.location, d, locPos + 4); return d; } void initialize(WritableSheet s) { this.sheet = s; this.range = (Range)new SheetRangeImpl((Sheet)s, this.firstColumn, this.firstRow, this.lastColumn, this.lastRow); } String getContents() { return this.contents; } protected void setContents(String desc) { this.contents = desc; this.modified = true; } }