200 lines
7.1 KiB
Java
200 lines
7.1 KiB
Java
/* ====================================================================
|
|
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.ss.formula;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import org.apache.poi.ss.formula.ptg.ExpPtg;
|
|
import org.apache.poi.ss.formula.ptg.Ptg;
|
|
import org.apache.poi.ss.formula.ptg.TblPtg;
|
|
import org.apache.poi.ss.util.CellReference;
|
|
import org.apache.poi.util.LittleEndian;
|
|
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
|
import org.apache.poi.util.LittleEndianInput;
|
|
import org.apache.poi.util.LittleEndianOutput;
|
|
|
|
/**
|
|
* Encapsulates an encoded formula token array.
|
|
*
|
|
* @author Josh Micich
|
|
*/
|
|
public class Formula {
|
|
|
|
private static final Formula EMPTY = new Formula(new byte[0], 0, new Ptg[0]);
|
|
|
|
/** immutable */
|
|
private final byte[] _byteEncoding;
|
|
private final int _encodedTokenLen;
|
|
private Ptg[] _cache;
|
|
|
|
private Formula(byte[] byteEncoding, int encodedTokenLen, Ptg[] cache) {
|
|
_byteEncoding = byteEncoding.clone();
|
|
_encodedTokenLen = encodedTokenLen;
|
|
_cache = cache;
|
|
// if (false) { // set to true to eagerly check Ptg decoding
|
|
// LittleEndianByteArrayInputStream in = new LittleEndianByteArrayInputStream(byteEncoding);
|
|
// Ptg.readTokens(encodedTokenLen, in);
|
|
// int nUnusedBytes = _byteEncoding.length - in.getReadIndex();
|
|
// if (nUnusedBytes > 0) {
|
|
// // TODO - this seems to occur when IntersectionPtg is present
|
|
// // This example file "IntersectionPtg.xls"
|
|
// // used by test: TestIntersectionPtg.testReading()
|
|
// // has 10 bytes unused at the end of the formula
|
|
// // 10 extra bytes are just 0x01 and 0x00
|
|
// System.out.println(nUnusedBytes + " unused bytes at end of formula");
|
|
// }
|
|
// }
|
|
}
|
|
/**
|
|
* Convenience method for {@link #read(int, LittleEndianInput, int)}
|
|
*/
|
|
public static Formula read(int encodedTokenLen, LittleEndianInput in) {
|
|
return read(encodedTokenLen, in, encodedTokenLen);
|
|
}
|
|
/**
|
|
* When there are no array constants present, <tt>encodedTokenLen</tt>==<tt>totalEncodedLen</tt>
|
|
* @param encodedTokenLen number of bytes in the stream taken by the plain formula tokens
|
|
* @param totalEncodedLen the total number of bytes in the formula (includes trailing encoding
|
|
* for array constants, but does not include 2 bytes for initial <tt>ushort encodedTokenLen</tt> field.
|
|
* @return A new formula object as read from the stream. Possibly empty, never <code>null</code>.
|
|
*/
|
|
public static Formula read(int encodedTokenLen, LittleEndianInput in, int totalEncodedLen) {
|
|
byte[] byteEncoding = new byte[totalEncodedLen];
|
|
in.readFully(byteEncoding);
|
|
return new Formula(byteEncoding, encodedTokenLen, null);
|
|
}
|
|
|
|
public Ptg[] getTokens() {
|
|
if(_cache != null)
|
|
return _cache;
|
|
LittleEndianInput in = new LittleEndianByteArrayInputStream(_byteEncoding);
|
|
return _cache = Ptg.readTokens(_encodedTokenLen, in);
|
|
}
|
|
/**
|
|
* Writes The formula encoding is includes:
|
|
* <ul>
|
|
* <li>ushort tokenDataLen</li>
|
|
* <li>tokenData</li>
|
|
* <li>arrayConstantData (if present)</li>
|
|
* </ul>
|
|
*/
|
|
public void serialize(LittleEndianOutput out) {
|
|
out.writeShort(_encodedTokenLen);
|
|
out.write(_byteEncoding);
|
|
}
|
|
|
|
public void serializeTokens(LittleEndianOutput out) {
|
|
out.write(_byteEncoding, 0, _encodedTokenLen);
|
|
}
|
|
public void serializeArrayConstantData(LittleEndianOutput out) {
|
|
int len = _byteEncoding.length-_encodedTokenLen;
|
|
out.write(_byteEncoding, _encodedTokenLen, len);
|
|
}
|
|
|
|
|
|
/**
|
|
* @return total formula encoding length. The formula encoding includes:
|
|
* <ul>
|
|
* <li>ushort tokenDataLen</li>
|
|
* <li>tokenData</li>
|
|
* <li>arrayConstantData (optional)</li>
|
|
* </ul>
|
|
* Note - this value is different to <tt>tokenDataLength</tt>
|
|
*/
|
|
public int getEncodedSize() {
|
|
return 2 + _byteEncoding.length;
|
|
}
|
|
/**
|
|
* This method is often used when the formula length does not appear immediately before
|
|
* the encoded token data.
|
|
*
|
|
* @return the encoded length of the plain formula tokens. This does <em>not</em> include
|
|
* the leading ushort field, nor any trailing array constant data.
|
|
*/
|
|
public int getEncodedTokenSize() {
|
|
return _encodedTokenLen;
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link Formula} object from a supplied {@link Ptg} array.
|
|
* Handles <code>null</code>s OK.
|
|
* @param ptgs may be <code>null</code>
|
|
* @return Never <code>null</code> (Possibly empty if the supplied <tt>ptgs</tt> is <code>null</code>)
|
|
*/
|
|
public static Formula create(Ptg[] ptgs) {
|
|
if (ptgs == null || ptgs.length < 1) {
|
|
return EMPTY;
|
|
}
|
|
int totalSize = Ptg.getEncodedSize(ptgs);
|
|
byte[] encodedData = new byte[totalSize];
|
|
Ptg.serializePtgs(ptgs, encodedData, 0);
|
|
int encodedTokenLen = Ptg.getEncodedSizeWithoutArrayData(ptgs);
|
|
return new Formula(encodedData, encodedTokenLen, ptgs); // todo: copy ptgs? don't think we need to
|
|
}
|
|
/**
|
|
* Gets the {@link Ptg} array from the supplied {@link Formula}.
|
|
* Handles <code>null</code>s OK.
|
|
*
|
|
* @param formula may be <code>null</code>
|
|
* @return possibly <code>null</code> (if the supplied <tt>formula</tt> is <code>null</code>)
|
|
*/
|
|
public static Ptg[] getTokens(Formula formula) {
|
|
if (formula == null) {
|
|
return null;
|
|
}
|
|
return formula.getTokens();
|
|
}
|
|
|
|
public Formula copy() {
|
|
// OK to return this because immutable
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Gets the locator for the corresponding {@link org.apache.poi.hssf.record.SharedFormulaRecord},
|
|
* {@link org.apache.poi.hssf.record.ArrayRecord} or {@link org.apache.poi.hssf.record.TableRecord}
|
|
* if this formula belongs to such a grouping. The {@link CellReference}
|
|
* returned by this method will match the top left corner of the range of that grouping.
|
|
* The return value is usually not the same as the location of the cell containing this formula.
|
|
*
|
|
* @return the firstRow & firstColumn of an array formula or shared formula that this formula
|
|
* belongs to. <code>null</code> if this formula is not part of an array or shared formula.
|
|
*/
|
|
public CellReference getExpReference() {
|
|
byte[] data = _byteEncoding;
|
|
if (data.length != 5) {
|
|
// tExp and tTbl are always 5 bytes long, and the only ptg in the formula
|
|
return null;
|
|
}
|
|
switch (data[0]) {
|
|
case ExpPtg.sid:
|
|
break;
|
|
case TblPtg.sid:
|
|
break;
|
|
default:
|
|
return null;
|
|
}
|
|
int firstRow = LittleEndian.getUShort(data, 1);
|
|
int firstColumn = LittleEndian.getUShort(data, 3);
|
|
return new CellReference(firstRow, firstColumn);
|
|
}
|
|
public boolean isSame(Formula other) {
|
|
return Arrays.equals(_byteEncoding, other._byteEncoding);
|
|
}
|
|
}
|