| /* |
| * ==================================================================== |
| * 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.ByteArrayInputStream; |
| import java.util.Arrays; |
| import java.util.Map; |
| import java.util.function.Supplier; |
| |
| import org.apache.poi.util.GenericRecordUtil; |
| import org.apache.poi.util.IOUtils; |
| import org.apache.poi.util.LittleEndianOutput; |
| import org.apache.poi.util.RecordFormatException; |
| import org.apache.poi.util.StringUtil; |
| |
| /** |
| * DConRef records specify a range in a workbook (internal or external) that serves as a data source |
| * for pivot tables or data consolidation. |
| * |
| * Represents a <code>DConRef</code> Structure |
| * <a href="http://msdn.microsoft.com/en-us/library/dd923854(office.12).aspx">[MS-XLS s. |
| * 2.4.86]</a>, and the contained <code>DConFile</code> structure |
| * <a href="http://msdn.microsoft.com/en-us/library/dd950157(office.12).aspx"> |
| * [MS-XLS s. 2.5.69]</a>. This in turn contains a <code>XLUnicodeStringNoCch</code> |
| * <a href="http://msdn.microsoft.com/en-us/library/dd910585(office.12).aspx"> |
| * [MS-XLS s. 2.5.296]</a>. |
| * |
| * <pre> |
| * _______________________________ |
| * | DConRef | |
| *(bytes) +-+-+-+-+-+-+-+-+-+-+...+-+-+-+-+ |
| * | ref |cch| stFile | un| |
| * +-+-+-+-+-+-+-+-+-+-+...+-+-+-+-+ |
| * | |
| * _________|_____________________ |
| * |DConFile / XLUnicodeStringNoCch| |
| * +-+-+-+-+-+-+-+-+-+-+-+...+-+-+-+ |
| * (bits) |h| reserved | rgb | |
| * +-+-+-+-+-+-+-+-+-+-+-+...+-+-+-+ |
| * </pre> |
| * Where |
| * <ul> |
| * <li><code>DConFile.h = 0x00</code> if the characters in<code>rgb</code> are single byte, and |
| * <code>DConFile.h = 0x01</code> if they are double byte.<p> |
| * If they are double byte, then |
| * <ul> |
| * <li> If it exists, the length of <code>DConRef.un = 2</code>. Otherwise it is 1. |
| * <li> The length of <code>DConFile.rgb = (2 * DConRef.cch)</code>. Otherwise it is equal to |
| * <code>DConRef.cch</code>. |
| * </ul> |
| * <li><code>DConRef.rgb</code> starts with <code>0x01</code> if it is an external reference, |
| * and with <code>0x02</code> if it is a self-reference. |
| * </ul> |
| * |
| * At the moment this class is read-only. |
| */ |
| public class DConRefRecord extends StandardRecord { |
| |
| //arbitrarily selected; may need to increase |
| private static final int MAX_RECORD_LENGTH = 100_000; |
| |
| /** |
| * The id of the record type, |
| * <code>sid = {@value}</code> |
| */ |
| public static final short sid = 0x0051; |
| /** |
| * A RefU structure specifying the range of cells if this record is part of an SXTBL. |
| * <a href="http://msdn.microsoft.com/en-us/library/dd920420(office.12).aspx"> |
| * [MS XLS s.2.5.211]</a> |
| */ |
| private int firstRow, lastRow, firstCol, lastCol; |
| /** |
| * the number of chars in the link |
| */ |
| private int charCount; |
| /** |
| * the type of characters (single or double byte) |
| */ |
| private int charType; |
| /** |
| * The link's path string. This is the <code>rgb</code> field of a |
| * <code>XLUnicodeStringNoCch</code>. Therefore it will contain at least one leading special |
| * character (0x01 or 0x02) and probably other ones.<p> |
| * @see <A href="http://msdn.microsoft.com/en-us/library/dd923491(office.12).aspx"> |
| * DConFile [MS-XLS s. 2.5.77]</A> and |
| * <A href="http://msdn.microsoft.com/en-us/library/dd950157(office.12).aspx"> |
| * VirtualPath [MS-XLS s. 2.5.69]</a> |
| * <p> |
| */ |
| private byte[] path; |
| /** |
| * unused bits at the end, must be set to 0. |
| */ |
| private byte[] _unused; |
| |
| public DConRefRecord(DConRefRecord other) { |
| super(other); |
| firstCol = other.firstCol; |
| firstRow = other.firstRow; |
| lastCol = other.lastCol; |
| lastRow = other.lastRow; |
| charCount = other.charCount; |
| charType = other.charType; |
| path = (other.path == null) ? null : other.path.clone(); |
| _unused = (other._unused == null) ? null : other._unused.clone(); |
| } |
| |
| /** |
| * Read constructor. |
| * |
| * @param data byte array containing a DConRef Record, including the header. |
| */ |
| public DConRefRecord(byte[] data) { |
| this(bytesToRIStream(data)); |
| } |
| |
| /** |
| * Read Constructor. |
| * |
| * @param inStream RecordInputStream containing a DConRefRecord structure. |
| */ |
| public DConRefRecord(RecordInputStream inStream) { |
| if (inStream.getSid() != sid) { |
| throw new RecordFormatException("Wrong sid: " + inStream.getSid()); |
| } |
| |
| firstRow = inStream.readUShort(); |
| lastRow = inStream.readUShort(); |
| firstCol = inStream.readUByte(); |
| lastCol = inStream.readUByte(); |
| |
| charCount = inStream.readUShort(); |
| |
| // 7 bits reserved + 1 bit type - first bit only |
| charType = inStream.readUByte() & 0x01; |
| |
| // bytelength is the length of the string in bytes, which depends on whether the string is |
| // made of single- or double-byte chars. This is given by charType, which equals 0 if |
| // single-byte, 1 if double-byte. |
| final int byteLength = charCount * (charType + 1); |
| |
| path = IOUtils.safelyAllocate(byteLength, MAX_RECORD_LENGTH); |
| inStream.readFully(path); |
| |
| // If it's a self reference, the last one or two bytes (depending on char type) are the |
| // unused field. Not sure If i need to bother with this... |
| if (path[0] == 0x02) { |
| _unused = inStream.readRemainder(); |
| } |
| } |
| |
| /* |
| * assuming this wants the number of bytes returned by {@link serialize(LittleEndianOutput)}, |
| * that is, (length - 4). |
| */ |
| @Override |
| protected int getDataSize() |
| { |
| int sz = 9 + path.length; |
| if (path[0] == 0x02) |
| sz += _unused.length; |
| return sz; |
| } |
| |
| @Override |
| protected void serialize(LittleEndianOutput out) |
| { |
| out.writeShort(firstRow); |
| out.writeShort(lastRow); |
| out.writeByte(firstCol); |
| out.writeByte(lastCol); |
| out.writeShort(charCount); |
| out.writeByte(charType); |
| out.write(path); |
| if (path[0] == 0x02) |
| out.write(_unused); |
| } |
| |
| @Override |
| public short getSid() |
| { |
| return sid; |
| } |
| |
| /** |
| * @return The first column of the range. |
| */ |
| public int getFirstColumn() |
| { |
| return firstCol; |
| } |
| |
| /** |
| * @return The first row of the range. |
| */ |
| public int getFirstRow() |
| { |
| return firstRow; |
| } |
| |
| /** |
| * @return The last column of the range. |
| */ |
| public int getLastColumn() |
| { |
| return lastCol; |
| } |
| |
| /** |
| * @return The last row of the range. |
| */ |
| public int getLastRow() |
| { |
| return lastRow; |
| } |
| |
| /** |
| * |
| * @return raw path byte array. |
| */ |
| public byte[] getPath() |
| { |
| return Arrays.copyOf(path, path.length); |
| } |
| |
| /** |
| * @return the link's path, with the special characters stripped/replaced. May be null. |
| * See MS-XLS 2.5.277 (VirtualPath) |
| */ |
| public String getReadablePath() |
| { |
| if (path != null) |
| { |
| //all of the path strings start with either 0x02 or 0x01 followed by zero or |
| //more of 0x01..0x08 |
| int offset = 1; |
| while (offset < path.length && path[offset] < 0x20) { |
| offset++; |
| } |
| String out = new String(Arrays.copyOfRange(path, offset, path.length), StringUtil.UTF8); |
| //UNC paths have \u0003 chars as path separators. |
| out = out.replaceAll("\u0003", "/"); |
| return out; |
| } |
| return null; |
| } |
| |
| /** |
| * Checks if the data source in this reference record is external to this sheet or internal. |
| * |
| * @return true iff this is an external reference. |
| */ |
| public boolean isExternalRef() |
| { |
| return path[0] == 0x01; |
| } |
| |
| @Override |
| public DConRefRecord copy() { |
| return new DConRefRecord(this); |
| } |
| |
| private static RecordInputStream bytesToRIStream(byte[] data) { |
| RecordInputStream ric = new RecordInputStream(new ByteArrayInputStream(data)); |
| ric.nextRecord(); |
| return ric; |
| } |
| |
| @Override |
| public HSSFRecordTypes getGenericRecordType() { |
| return HSSFRecordTypes.DCON_REF; |
| } |
| |
| @Override |
| public Map<String, Supplier<?>> getGenericProperties() { |
| return GenericRecordUtil.getGenericProperties( |
| "firstRow", this::getFirstRow, |
| "lastRow", this::getLastRow, |
| "firstColumn", this::getFirstColumn, |
| "lastColumn", this::getLastColumn, |
| "charCount", () -> charCount, |
| "charType", () -> charType, |
| "path", this::getReadablePath |
| ); |
| } |
| } |