git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hssf_cryptoapi@1755463 13f79535-47bb-0310-9956-ffa450edef68pull/38/head
@@ -17,7 +17,6 @@ KIND, either express or implied. See the License for the | |||
specific language governing permissions and limitations | |||
under the License. | |||
--> | |||
<!-- | |||
This build was tested with ant 1.9.4 although it will probably work with | |||
other versions, however at least 1.8.0 is required. | |||
@@ -133,6 +132,7 @@ under the License. | |||
<property name="ooxml.output.test.dir" location="build/ooxml-test-classes"/> | |||
<property name="ooxml.testokfile" location="build/ooxml-testokfile.txt"/> | |||
<property name="ooxml.lite.output.dir" location="build/ooxml-lite-classes"/> | |||
<property name="ooxml.lite.testokfile" location="build/ooxml-lite-testokfile.txt"/> | |||
<!-- XSSF/SXSSF subset of OOXML: --> | |||
<property name="ooxml.ss.testokfile" location="build/ooxml-ss-testokfile.txt"/> | |||
@@ -169,6 +169,9 @@ under the License. | |||
<property name="main.ant.url" value="${repository.m2}/maven2/org/apache/ant/ant/1.9.4/ant-1.9.4.jar"/> | |||
<property name="main.antlauncher.jar" location="${main.lib}/ant-launcher-1.9.4.jar"/> | |||
<property name="main.antlauncher.url" value="${repository.m2}/maven2/org/apache/ant/ant-launcher/1.9.4/ant-launcher-1.9.4.jar"/> | |||
<property name="main.commons-collections4.jar" location="${main.lib}/commons-collections4-4.1.jar"/> | |||
<property name="main.commons-collections4.url" | |||
value="${repository.m2}/maven2/org/apache/commons/commons-collections4/4.1/commons-collections4-4.1.jar"/> | |||
<!-- xml signature libs --> | |||
<property name="dsig.xmlsec.jar" location="${compile.lib}/xmlsec-2.0.6.jar"/> | |||
@@ -192,8 +195,8 @@ under the License. | |||
value="${repository.m2}/maven2/org/apache/xmlbeans/xmlbeans/2.6.0/xmlbeans-2.6.0.jar"/> | |||
<!-- coverage libs --> | |||
<property name="jacoco.zip" location="${main.lib}/jacoco-0.7.6.201602180812.zip"/> | |||
<property name="jacoco.url" value="${repository.m2}/maven2/org/jacoco/jacoco/0.7.6.201602180812/jacoco-0.7.6.201602180812.zip"/> | |||
<property name="jacoco.zip" location="${main.lib}/jacoco-0.7.7.201606060606.zip"/> | |||
<property name="jacoco.url" value="${repository.m2}/maven2/org/jacoco/jacoco/0.7.7.201606060606/jacoco-0.7.7.201606060606.zip"/> | |||
<property name="asm.jar" location="${main.lib}/asm-all-5.0.3.jar"/> | |||
<property name="asm.url" value="${repository.m2}/maven2/org/ow2/asm/asm-all/5.0.3/asm-all-5.0.3.jar"/> | |||
@@ -285,6 +288,7 @@ under the License. | |||
</condition> | |||
<property name="findbugs.version" value="2.0.3" if:set="findbugs.jdk6"/> | |||
<property name="findbugs.version" value="3.0.1" unless:set="findbugs.jdk6"/> | |||
<echo message="Findbugs-Version: ${findbugs.version} for Java ${ant.java.version}"/> | |||
<property name="findbugs.url" value="http://prdownloads.sourceforge.net/findbugs/findbugs-noUpdateChecks-${findbugs.version}.zip?download"/> | |||
<property name="findbugs.jar" location="${main.lib}/findbugs-noUpdateChecks-${findbugs.version}.zip"/> | |||
@@ -303,6 +307,8 @@ under the License. | |||
<!-- this can be overwriten to empty when running with Java 9 --> | |||
<property name="maxpermsize" value="-XX:MaxPermSize=256m"/> | |||
<property name="java9addmods" value="-Dthis.is.a.dummy=true"/> | |||
<property name="java9addmodsvalue" value="-Dthis.is.a.dummy=true"/> | |||
<path id="main.classpath"> | |||
<pathelement location="${main.commons-logging.jar}"/> | |||
@@ -310,6 +316,7 @@ under the License. | |||
<pathelement location="${main.log4j.jar}"/> | |||
<pathelement location="${main.junit.jar}"/> | |||
<pathelement location="${main.hamcrest.jar}"/> | |||
<pathelement location="${main.commons-collections4.jar}"/> | |||
</path> | |||
<path id="scratchpad.classpath"> | |||
@@ -498,9 +505,7 @@ under the License. | |||
<attribute name="src"/> | |||
<attribute name="dest"/> | |||
<sequential> | |||
<local name="exists"/> | |||
<available file="@{dest}" property="exists"/> | |||
<!--fail unless:true="${exists}" | |||
<!--fail | |||
message="Java version might be uncapable to download https URLs - see https://stackoverflow.com/questions/6851461/java-why-does-ssl-handshake-give-could-not-generate-dh-keypair-exception"> | |||
<condition> | |||
<and> | |||
@@ -509,7 +514,7 @@ under the License. | |||
</and> | |||
</condition> | |||
</fail--> | |||
<get src="@{src}" dest="@{dest}" unless:true="${exists}"/> | |||
<get src="@{src}" dest="@{dest}" skipexisting="true"/> | |||
</sequential> | |||
</macrodef> | |||
@@ -531,12 +536,14 @@ under the License. | |||
<include name="jacoco-0.7.2*"/> | |||
<include name="jacoco-0.7.3*"/> | |||
<include name="jacoco-0.7.4*"/> | |||
<include name="jacoco-0.7.6*"/> | |||
<include name="log4j-1.2.13*"/> | |||
<include name="org.jacoco.*-0.6.*"/> | |||
<include name="org.jacoco.*-0.7.1*"/> | |||
<include name="org.jacoco.*-0.7.2*"/> | |||
<include name="org.jacoco.*-0.7.3*"/> | |||
<include name="org.jacoco.*-0.7.4*"/> | |||
<include name="org.jacoco.*-0.7.6*"/> | |||
<include name="dom4j*"/> | |||
<include name="apache-rat-0.10*"/> | |||
<include name="xercesImpl-*.jar"/> | |||
@@ -587,6 +594,7 @@ under the License. | |||
<available file="${dsig.bouncycastle-pkix.jar}"/> | |||
<available file="${dsig.xmlsec.jar}"/> | |||
<available file="${dsig.sl4j-api.jar}"/> | |||
<available file="${main.commons-collections4.jar}"/> | |||
</and> | |||
<isset property="disconnected"/> | |||
</or> | |||
@@ -605,6 +613,7 @@ under the License. | |||
<downloadfile src="${main.antlauncher.url}" dest="${main.antlauncher.jar}"/> | |||
<downloadfile src="${asm.url}" dest="${asm.jar}"/> | |||
<downloadfile src="${jacoco.url}" dest="${jacoco.zip}"/> | |||
<downloadfile src="${main.commons-collections4.url}" dest="${main.commons-collections4.jar}"/> | |||
<unzip src="${jacoco.zip}" dest="."> | |||
<patternset> | |||
<include name="lib/*.jar"/> | |||
@@ -1161,6 +1170,8 @@ under the License. | |||
<group name="Main"> | |||
<classfiles> | |||
<fileset dir="${main.output.dir}"> | |||
<!-- exclude some generated classes --> | |||
<exclude name="org/apache/poi/sl/draw/binding/*.class"/> | |||
<!-- exclude large test-class --> | |||
<exclude name="org/apache/poi/hssf/usermodel/DummyGraphics2d.class"/> | |||
</fileset> | |||
@@ -1242,6 +1253,8 @@ under the License. | |||
<jvmarg value="-ea"/> | |||
<jvmarg value="-Xmx256m"/> | |||
<!-- jvmarg value="-Duser.timezone=UTC"/ --> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<formatter type="plain"/> | |||
<formatter type="xml"/> | |||
<batchtest todir="${main.reports.test}"> | |||
@@ -1258,6 +1271,16 @@ under the License. | |||
<antcall target="-test-main-write-testfile"/> | |||
</target> | |||
<target name="test-report" depends="init"> | |||
<mkdir dir="build/report"/> | |||
<junitreport todir="build/report"> | |||
<fileset dir="build"> | |||
<include name="*results/**/TEST-*.xml"/> | |||
</fileset> | |||
<report format="frames" todir="build/report"/> | |||
</junitreport> | |||
</target> | |||
<target name="-test-property-check" unless="testcase"> | |||
<echo message="Please use -Dtestcase=org.your.testcase to run a single test"/> | |||
<fail/> | |||
@@ -1288,6 +1311,8 @@ under the License. | |||
<jvmarg value="-ea"/> | |||
<jvmarg value="-Xmx256m"/> | |||
<!-- jvmarg value="-Duser.timezone=UTC"/ --> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<formatter type="plain"/> | |||
<formatter type="xml"/> | |||
<batchtest todir="${main.reports.test}"> | |||
@@ -1335,6 +1360,8 @@ under the License. | |||
and on Windows with jdk-1.5.22 | |||
--> | |||
<jvmarg value="-Xmx256M"/> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<formatter type="plain"/> | |||
<formatter type="xml"/> | |||
<batchtest todir="${scratchpad.reports.test}"> | |||
@@ -1373,6 +1400,8 @@ under the License. | |||
<jvmarg value="${maxpermsize}"/> | |||
<jvmarg value="-Xmx768M"/> | |||
<jvmarg value="-ea"/> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<!-- jvmarg value="-Duser.timezone=UTC"/ --> | |||
<formatter type="plain"/> | |||
<formatter type="xml"/> | |||
@@ -1396,6 +1425,8 @@ under the License. | |||
<syspropertyset refid="junit.properties"/> | |||
<jvmarg value="-Xmx768M"/> | |||
<jvmarg value="-ea"/> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<formatter type="plain"/> | |||
<formatter type="xml"/> | |||
<batchtest todir="${ooxml.reports.test}"> | |||
@@ -1439,6 +1470,8 @@ under the License. | |||
<jvmarg value="${maxpermsize}"/> | |||
<jvmarg value="-Xmx768M"/> | |||
<jvmarg value="-ea"/> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<!-- jvmarg value="-Duser.timezone=UTC"/ --> | |||
<formatter type="plain"/> | |||
<formatter type="xml"/> | |||
@@ -1485,6 +1518,8 @@ under the License. | |||
<syspropertyset refid="junit.properties"/> | |||
<jvmarg value="-ea"/> | |||
<jvmarg value="-Xmx1512M"/> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<formatter type="plain"/> | |||
<formatter type="xml"/> | |||
<batchtest todir="${integration.reports.test}"> | |||
@@ -1503,7 +1538,20 @@ under the License. | |||
</target> | |||
<!-- Section: test-ooxml-lite --> | |||
<target name="compile-ooxml-lite" depends="compile-ooxml"> | |||
<target name="-compile-ooxml-lite-check"> | |||
<uptodate property="ooxml.lite.test.notRequired" targetfile="${ooxml.lite.testokfile}"> | |||
<srcfiles dir="${ooxml.src}"/> | |||
<srcfiles dir="${ooxml.src.test}"/> | |||
<srcfiles file="${ooxml.xsds.jar}"/> | |||
<srcfiles file="${ooxml.security.jar}"/> | |||
</uptodate> | |||
</target> | |||
<target name="compile-ooxml-lite" depends="-compile-ooxml-lite-check,compile-ooxml" | |||
unless="ooxml.lite.test.notRequired"> | |||
<delete file="${ooxml.lite.testokfile}"/> | |||
<echo message="Running ooxml-lite generator"/> | |||
<property name="ooxml.lite-merged.dir" location="build/ooxml-lite-merged"/> | |||
<mkdir dir="${ooxml.lite-merged.dir}"/> | |||
@@ -1522,6 +1570,8 @@ under the License. | |||
<syspropertyset refid="junit.properties"/> | |||
<jvmarg value="${maxpermsize}"/> | |||
<jvmarg value="-Xmx512m"/> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<arg value="-ooxml"/> | |||
<arg value="${ooxml.lite-merged.dir}/ooxml-lite-merged.jar"/> | |||
<arg value="-test"/> | |||
@@ -1529,6 +1579,8 @@ under the License. | |||
<arg value="-dest"/> | |||
<arg value="${ooxml.lite.output.dir}"/> | |||
</java> | |||
<echo file="${ooxml.lite.testokfile}" append="false" message="testok"/> | |||
</target> | |||
<target name="test-ooxml-lite" depends="jacocotask,compile-ooxml-xsds,compile-ooxml-lite"> | |||
@@ -1557,6 +1609,8 @@ under the License. | |||
<classpath refid="test.excelant.classpath"/> | |||
<syspropertyset refid="junit.properties"/> | |||
<jvmarg value="-ea"/> | |||
<jvmarg value="${java9addmods}" /> | |||
<jvmarg value="${java9addmodsvalue}" /> | |||
<formatter type="plain"/> | |||
<formatter type="xml"/> | |||
<batchtest todir="${excelant.reports.test}"> | |||
@@ -1868,6 +1922,7 @@ under the License. | |||
<fileset dir="${main.lib}"> | |||
<include name="commons-codec-*.jar"/> | |||
<include name="commons-logging-*.jar"/> | |||
<include name="commons-collections4-*.jar"/> | |||
<include name="junit-*.jar"/> | |||
<include name="log4j-*.jar"/> | |||
</fileset> | |||
@@ -2083,7 +2138,7 @@ under the License. | |||
</forbiddenapis> | |||
</target> | |||
<target name="findbugs" depends="assemble"> | |||
<target name="findbugs" depends="jar"> | |||
<downloadfile src="${findbugs.url}" dest="${findbugs.jar}"/> | |||
<property name="findbugs.home" value="build/findbugs" /> | |||
@@ -2103,11 +2158,13 @@ under the License. | |||
output="xml:withMessages" | |||
outputFile="build/findbugs.xml" | |||
effort="max" | |||
failOnError="true" | |||
excludeFilter="src/resources/devtools/findbugs-filters.xml"> | |||
<fileset dir="${dist.dir}/maven"> | |||
<include name="poi/poi-${version.id}.jar"/> | |||
<include name="poi-scratchpad/poi-scratchpad-${version.id}.jar"/> | |||
<include name="poi-ooxml/poi-ooxml-${version.id}.jar"/> | |||
<include name="poi-excelant/poi-excelant-${version.id}.jar"/> | |||
</fileset> | |||
<auxClasspath path="${dsig.bouncycastle-pkix.jar}" /> | |||
<auxClasspath path="${dsig.bouncycastle-prov.jar}" /> | |||
@@ -2117,9 +2174,11 @@ under the License. | |||
<auxClasspath path="${ooxml.security.jar}" /> | |||
<auxClasspath path="${ooxml.curvesapi.jar}" /> | |||
<auxClasspath path="${ooxml.xmlbeans26.jar}" /> | |||
<auxClasspath path="${main.commons-collections4.jar}" /> | |||
<auxClasspath path="${main.commons-codec.jar}" /> | |||
<auxClasspath path="${main.commons-logging.jar}" /> | |||
<auxClasspath path="${main.junit.jar}" /> | |||
<auxClasspath path="${main.ant.jar}" /> | |||
<sourcePath path="src/java" /> | |||
<sourcePath path="src/ooxml/java" /> | |||
<sourcePath path="src/scratchpad/src" /> |
@@ -91,6 +91,11 @@ | |||
<scope>test</scope> | |||
<version>4.12</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.apache.commons</groupId> | |||
<artifactId>commons-collections4</artifactId> | |||
<version>4.1</version> | |||
</dependency> | |||
</dependencies> | |||
</project> |
@@ -110,6 +110,11 @@ | |||
</build> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.apache.commons</groupId> | |||
<artifactId>commons-collections4</artifactId> | |||
<version>4.1</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-codec</groupId> | |||
<artifactId>commons-codec</artifactId> |
@@ -34,184 +34,184 @@ import org.apache.poi.ss.formula.FormulaShifter; | |||
* @author Jason Height (jheight at chariot dot net dot au) | |||
*/ | |||
public final class RowRecordsAggregate extends RecordAggregate { | |||
private int _firstrow = -1; | |||
private int _lastrow = -1; | |||
private final Map<Integer, RowRecord> _rowRecords; | |||
private final ValueRecordsAggregate _valuesAgg; | |||
private final List<Record> _unknownRecords; | |||
private final SharedValueManager _sharedValueManager; | |||
// Cache values to speed up performance of | |||
private int _firstrow = -1; | |||
private int _lastrow = -1; | |||
private final Map<Integer, RowRecord> _rowRecords; | |||
private final ValueRecordsAggregate _valuesAgg; | |||
private final List<Record> _unknownRecords; | |||
private final SharedValueManager _sharedValueManager; | |||
// Cache values to speed up performance of | |||
// getStartRowNumberForBlock / getEndRowNumberForBlock, see Bugzilla 47405 | |||
private RowRecord[] _rowRecordValues = null; | |||
/** Creates a new instance of ValueRecordsAggregate */ | |||
public RowRecordsAggregate() { | |||
this(SharedValueManager.createEmpty()); | |||
} | |||
private RowRecordsAggregate(SharedValueManager svm) { | |||
if (svm == null) { | |||
throw new IllegalArgumentException("SharedValueManager must be provided."); | |||
} | |||
_rowRecords = new TreeMap<Integer, RowRecord>(); | |||
_valuesAgg = new ValueRecordsAggregate(); | |||
_unknownRecords = new ArrayList<Record>(); | |||
_sharedValueManager = svm; | |||
} | |||
/** | |||
* @param rs record stream with all {@link SharedFormulaRecord} | |||
* {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed | |||
* @param svm an initialised {@link SharedValueManager} (from the shared formula, array | |||
* and table records of the current sheet). Never <code>null</code>. | |||
*/ | |||
public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) { | |||
this(svm); | |||
while(rs.hasNext()) { | |||
Record rec = rs.getNext(); | |||
switch (rec.getSid()) { | |||
case RowRecord.sid: | |||
insertRow((RowRecord) rec); | |||
continue; | |||
/** Creates a new instance of ValueRecordsAggregate */ | |||
public RowRecordsAggregate() { | |||
this(SharedValueManager.createEmpty()); | |||
} | |||
private RowRecordsAggregate(SharedValueManager svm) { | |||
if (svm == null) { | |||
throw new IllegalArgumentException("SharedValueManager must be provided."); | |||
} | |||
_rowRecords = new TreeMap<Integer, RowRecord>(); | |||
_valuesAgg = new ValueRecordsAggregate(); | |||
_unknownRecords = new ArrayList<Record>(); | |||
_sharedValueManager = svm; | |||
} | |||
/** | |||
* @param rs record stream with all {@link SharedFormulaRecord} | |||
* {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed | |||
* @param svm an initialised {@link SharedValueManager} (from the shared formula, array | |||
* and table records of the current sheet). Never <code>null</code>. | |||
*/ | |||
public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) { | |||
this(svm); | |||
while(rs.hasNext()) { | |||
Record rec = rs.getNext(); | |||
switch (rec.getSid()) { | |||
case RowRecord.sid: | |||
insertRow((RowRecord) rec); | |||
continue; | |||
case DConRefRecord.sid: | |||
addUnknownRecord(rec); | |||
continue; | |||
case DBCellRecord.sid: | |||
// end of 'Row Block'. Should only occur after cell records | |||
// ignore DBCELL records because POI generates them upon re-serialization | |||
continue; | |||
} | |||
if (rec instanceof UnknownRecord) { | |||
// might need to keep track of where exactly these belong | |||
addUnknownRecord(rec); | |||
while (rs.peekNextSid() == ContinueRecord.sid) { | |||
addUnknownRecord(rs.getNext()); | |||
} | |||
continue; | |||
} | |||
if (rec instanceof MulBlankRecord) { | |||
_valuesAgg.addMultipleBlanks((MulBlankRecord) rec); | |||
continue; | |||
} | |||
if (!(rec instanceof CellValueRecordInterface)) { | |||
throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); | |||
} | |||
_valuesAgg.construct((CellValueRecordInterface)rec, rs, svm); | |||
} | |||
} | |||
/** | |||
* Handles UnknownRecords which appear within the row/cell records | |||
*/ | |||
private void addUnknownRecord(Record rec) { | |||
// ony a few distinct record IDs are encountered by the existing POI test cases: | |||
// 0x1065 // many | |||
// 0x01C2 // several | |||
// 0x0034 // few | |||
// No documentation could be found for these | |||
// keep the unknown records for re-serialization | |||
_unknownRecords.add(rec); | |||
} | |||
public void insertRow(RowRecord row) { | |||
// Integer integer = Integer.valueOf(row.getRowNumber()); | |||
_rowRecords.put(Integer.valueOf(row.getRowNumber()), row); | |||
// Clear the cached values | |||
_rowRecordValues = null; | |||
if ((row.getRowNumber() < _firstrow) || (_firstrow == -1)) { | |||
_firstrow = row.getRowNumber(); | |||
} | |||
if ((row.getRowNumber() > _lastrow) || (_lastrow == -1)) { | |||
_lastrow = row.getRowNumber(); | |||
} | |||
} | |||
public void removeRow(RowRecord row) { | |||
int rowIndex = row.getRowNumber(); | |||
_valuesAgg.removeAllCellsValuesForRow(rowIndex); | |||
Integer key = Integer.valueOf(rowIndex); | |||
RowRecord rr = _rowRecords.remove(key); | |||
if (rr == null) { | |||
throw new RuntimeException("Invalid row index (" + key.intValue() + ")"); | |||
} | |||
if (row != rr) { | |||
_rowRecords.put(key, rr); | |||
throw new RuntimeException("Attempt to remove row that does not belong to this sheet"); | |||
} | |||
// Clear the cached values | |||
_rowRecordValues = null; | |||
} | |||
public RowRecord getRow(int rowIndex) { | |||
// end of 'Row Block'. Should only occur after cell records | |||
// ignore DBCELL records because POI generates them upon re-serialization | |||
continue; | |||
} | |||
if (rec instanceof UnknownRecord) { | |||
// might need to keep track of where exactly these belong | |||
addUnknownRecord(rec); | |||
while (rs.peekNextSid() == ContinueRecord.sid) { | |||
addUnknownRecord(rs.getNext()); | |||
} | |||
continue; | |||
} | |||
if (rec instanceof MulBlankRecord) { | |||
_valuesAgg.addMultipleBlanks((MulBlankRecord) rec); | |||
continue; | |||
} | |||
if (!(rec instanceof CellValueRecordInterface)) { | |||
throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); | |||
} | |||
_valuesAgg.construct((CellValueRecordInterface)rec, rs, svm); | |||
} | |||
} | |||
/** | |||
* Handles UnknownRecords which appear within the row/cell records | |||
*/ | |||
private void addUnknownRecord(Record rec) { | |||
// ony a few distinct record IDs are encountered by the existing POI test cases: | |||
// 0x1065 // many | |||
// 0x01C2 // several | |||
// 0x0034 // few | |||
// No documentation could be found for these | |||
// keep the unknown records for re-serialization | |||
_unknownRecords.add(rec); | |||
} | |||
public void insertRow(RowRecord row) { | |||
// Integer integer = Integer.valueOf(row.getRowNumber()); | |||
_rowRecords.put(Integer.valueOf(row.getRowNumber()), row); | |||
// Clear the cached values | |||
_rowRecordValues = null; | |||
if ((row.getRowNumber() < _firstrow) || (_firstrow == -1)) { | |||
_firstrow = row.getRowNumber(); | |||
} | |||
if ((row.getRowNumber() > _lastrow) || (_lastrow == -1)) { | |||
_lastrow = row.getRowNumber(); | |||
} | |||
} | |||
public void removeRow(RowRecord row) { | |||
int rowIndex = row.getRowNumber(); | |||
_valuesAgg.removeAllCellsValuesForRow(rowIndex); | |||
Integer key = Integer.valueOf(rowIndex); | |||
RowRecord rr = _rowRecords.remove(key); | |||
if (rr == null) { | |||
throw new RuntimeException("Invalid row index (" + key.intValue() + ")"); | |||
} | |||
if (row != rr) { | |||
_rowRecords.put(key, rr); | |||
throw new RuntimeException("Attempt to remove row that does not belong to this sheet"); | |||
} | |||
// Clear the cached values | |||
_rowRecordValues = null; | |||
} | |||
public RowRecord getRow(int rowIndex) { | |||
int maxrow = SpreadsheetVersion.EXCEL97.getLastRowIndex(); | |||
if (rowIndex < 0 || rowIndex > maxrow) { | |||
throw new IllegalArgumentException("The row number must be between 0 and " + maxrow + ", but had: " + rowIndex); | |||
} | |||
return _rowRecords.get(Integer.valueOf(rowIndex)); | |||
} | |||
public int getPhysicalNumberOfRows() | |||
{ | |||
return _rowRecords.size(); | |||
} | |||
public int getFirstRowNum() | |||
{ | |||
return _firstrow; | |||
} | |||
public int getLastRowNum() | |||
{ | |||
return _lastrow; | |||
} | |||
/** Returns the number of row blocks. | |||
* <p/>The row blocks are goupings of rows that contain the DBCell record | |||
* after them | |||
*/ | |||
public int getRowBlockCount() { | |||
int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE; | |||
if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0) | |||
size++; | |||
return size; | |||
} | |||
private int getRowBlockSize(int block) { | |||
return RowRecord.ENCODED_SIZE * getRowCountForBlock(block); | |||
} | |||
/** Returns the number of physical rows within a block*/ | |||
public int getRowCountForBlock(int block) { | |||
int startIndex = block * DBCellRecord.BLOCK_SIZE; | |||
int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1; | |||
if (endIndex >= _rowRecords.size()) | |||
endIndex = _rowRecords.size()-1; | |||
return endIndex-startIndex+1; | |||
} | |||
/** Returns the physical row number of the first row in a block*/ | |||
private int getStartRowNumberForBlock(int block) { | |||
int startIndex = block * DBCellRecord.BLOCK_SIZE; | |||
if(_rowRecordValues == null){ | |||
throw new IllegalArgumentException("The row number must be between 0 and " + maxrow + ", but had: " + rowIndex); | |||
} | |||
return _rowRecords.get(Integer.valueOf(rowIndex)); | |||
} | |||
public int getPhysicalNumberOfRows() | |||
{ | |||
return _rowRecords.size(); | |||
} | |||
public int getFirstRowNum() | |||
{ | |||
return _firstrow; | |||
} | |||
public int getLastRowNum() | |||
{ | |||
return _lastrow; | |||
} | |||
/** Returns the number of row blocks. | |||
* <p/>The row blocks are goupings of rows that contain the DBCell record | |||
* after them | |||
*/ | |||
public int getRowBlockCount() { | |||
int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE; | |||
if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0) | |||
size++; | |||
return size; | |||
} | |||
private int getRowBlockSize(int block) { | |||
return RowRecord.ENCODED_SIZE * getRowCountForBlock(block); | |||
} | |||
/** Returns the number of physical rows within a block*/ | |||
public int getRowCountForBlock(int block) { | |||
int startIndex = block * DBCellRecord.BLOCK_SIZE; | |||
int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1; | |||
if (endIndex >= _rowRecords.size()) | |||
endIndex = _rowRecords.size()-1; | |||
return endIndex-startIndex+1; | |||
} | |||
/** Returns the physical row number of the first row in a block*/ | |||
private int getStartRowNumberForBlock(int block) { | |||
int startIndex = block * DBCellRecord.BLOCK_SIZE; | |||
if (_rowRecordValues == null) { | |||
_rowRecordValues = _rowRecords.values().toArray(new RowRecord[_rowRecords.size()]); | |||
} | |||
try { | |||
return _rowRecordValues[startIndex].getRowNumber(); | |||
} catch(ArrayIndexOutOfBoundsException e) { | |||
throw new RuntimeException("Did not find start row for block " + block); | |||
} | |||
} | |||
throw new RuntimeException("Did not find start row for block " + block); | |||
} | |||
} | |||
/** Returns the physical row number of the end row in a block*/ | |||
private int getEndRowNumberForBlock(int block) { | |||
int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1; | |||
if (endIndex >= _rowRecords.size()) | |||
endIndex = _rowRecords.size()-1; | |||
/** Returns the physical row number of the end row in a block*/ | |||
private int getEndRowNumberForBlock(int block) { | |||
int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1; | |||
if (endIndex >= _rowRecords.size()) | |||
endIndex = _rowRecords.size()-1; | |||
if(_rowRecordValues == null){ | |||
if (_rowRecordValues == null){ | |||
_rowRecordValues = _rowRecords.values().toArray(new RowRecord[_rowRecords.size()]); | |||
} | |||
@@ -219,287 +219,287 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
return _rowRecordValues[endIndex].getRowNumber(); | |||
} catch(ArrayIndexOutOfBoundsException e) { | |||
throw new RuntimeException("Did not find end row for block " + block); | |||
} | |||
} | |||
private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) { | |||
final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE; | |||
final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; | |||
Iterator<RowRecord> rowIterator = _rowRecords.values().iterator(); | |||
//Given that we basically iterate through the rows in order, | |||
//For a performance improvement, it would be better to return an instance of | |||
//an iterator and use that instance throughout, rather than recreating one and | |||
//having to move it to the right position. | |||
int i=0; | |||
for (;i<startIndex;i++) | |||
rowIterator.next(); | |||
int result = 0; | |||
while(rowIterator.hasNext() && (i++ < endIndex)) { | |||
Record rec = rowIterator.next(); | |||
result += rec.getRecordSize(); | |||
rv.visitRecord(rec); | |||
} | |||
return result; | |||
} | |||
} | |||
} | |||
private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) { | |||
final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE; | |||
final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; | |||
Iterator<RowRecord> rowIterator = _rowRecords.values().iterator(); | |||
//Given that we basically iterate through the rows in order, | |||
//For a performance improvement, it would be better to return an instance of | |||
//an iterator and use that instance throughout, rather than recreating one and | |||
//having to move it to the right position. | |||
int i=0; | |||
for (;i<startIndex;i++) | |||
rowIterator.next(); | |||
int result = 0; | |||
while(rowIterator.hasNext() && (i++ < endIndex)) { | |||
Record rec = rowIterator.next(); | |||
result += rec.getRecordSize(); | |||
rv.visitRecord(rec); | |||
} | |||
return result; | |||
} | |||
@Override | |||
public void visitContainedRecords(RecordVisitor rv) { | |||
PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0); | |||
//DBCells are serialized before row records. | |||
final int blockCount = getRowBlockCount(); | |||
for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { | |||
// Serialize a block of rows. | |||
// Hold onto the position of the first row in the block | |||
int pos=0; | |||
// Hold onto the size of this block that was serialized | |||
final int rowBlockSize = visitRowRecordsForBlock(blockIndex, rv); | |||
pos += rowBlockSize; | |||
// Serialize a block of cells for those rows | |||
final int startRowNumber = getStartRowNumberForBlock(blockIndex); | |||
final int endRowNumber = getEndRowNumberForBlock(blockIndex); | |||
DBCellRecord.Builder dbcrBuilder = new DBCellRecord.Builder(); | |||
// Note: Cell references start from the second row... | |||
int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE); | |||
for (int row = startRowNumber; row <= endRowNumber; row++) { | |||
if (_valuesAgg.rowHasCells(row)) { | |||
stv.setPosition(0); | |||
_valuesAgg.visitCellsForRow(row, stv); | |||
int rowCellSize = stv.getPosition(); | |||
pos += rowCellSize; | |||
// Add the offset to the first cell for the row into the | |||
// DBCellRecord. | |||
dbcrBuilder.addCellOffset(cellRefOffset); | |||
cellRefOffset = rowCellSize; | |||
} | |||
} | |||
// Calculate Offset from the start of a DBCellRecord to the first Row | |||
rv.visitRecord(dbcrBuilder.build(pos)); | |||
} | |||
for (Record _unknownRecord : _unknownRecords) { | |||
// Potentially breaking the file here since we don't know exactly where to write these records | |||
rv.visitRecord(_unknownRecord); | |||
} | |||
} | |||
public Iterator<RowRecord> getIterator() { | |||
return _rowRecords.values().iterator(); | |||
} | |||
public int findStartOfRowOutlineGroup(int row) { | |||
// Find the start of the group. | |||
RowRecord rowRecord = this.getRow( row ); | |||
int level = rowRecord.getOutlineLevel(); | |||
int currentRow = row; | |||
while (currentRow >= 0 && this.getRow( currentRow ) != null) { | |||
rowRecord = this.getRow( currentRow ); | |||
if (rowRecord.getOutlineLevel() < level) { | |||
return currentRow + 1; | |||
} | |||
currentRow--; | |||
} | |||
return currentRow + 1; | |||
} | |||
public int findEndOfRowOutlineGroup(int row) { | |||
int level = getRow( row ).getOutlineLevel(); | |||
int currentRow; | |||
for (currentRow = row; currentRow < getLastRowNum(); currentRow++) { | |||
if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level) { | |||
break; | |||
} | |||
} | |||
return currentRow-1; | |||
} | |||
/** | |||
* Hide all rows at or below the current outline level | |||
* @return index of the <em>next<em> row after the last row that gets hidden | |||
*/ | |||
private int writeHidden(RowRecord pRowRecord, int row) { | |||
int rowIx = row; | |||
RowRecord rowRecord = pRowRecord; | |||
int level = rowRecord.getOutlineLevel(); | |||
while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) { | |||
rowRecord.setZeroHeight(true); | |||
rowIx++; | |||
rowRecord = getRow(rowIx); | |||
} | |||
return rowIx; | |||
} | |||
public void collapseRow(int rowNumber) { | |||
// Find the start of the group. | |||
int startRow = findStartOfRowOutlineGroup(rowNumber); | |||
RowRecord rowRecord = getRow(startRow); | |||
// Hide all the columns until the end of the group | |||
int nextRowIx = writeHidden(rowRecord, startRow); | |||
RowRecord row = getRow(nextRowIx); | |||
if (row == null) { | |||
row = createRow(nextRowIx); | |||
insertRow(row); | |||
} | |||
// Write collapse field | |||
row.setColapsed(true); | |||
} | |||
/** | |||
* Create a row record. | |||
* | |||
* @param rowNumber row number | |||
* @return RowRecord created for the passed in row number | |||
* @see org.apache.poi.hssf.record.RowRecord | |||
*/ | |||
public static RowRecord createRow(int rowNumber) { | |||
return new RowRecord(rowNumber); | |||
} | |||
public boolean isRowGroupCollapsed(int row) { | |||
int collapseRow = findEndOfRowOutlineGroup(row) + 1; | |||
return getRow(collapseRow) != null && getRow(collapseRow).getColapsed(); | |||
} | |||
public void expandRow(int rowNumber) { | |||
if (rowNumber == -1) | |||
return; | |||
// If it is already expanded do nothing. | |||
if (!isRowGroupCollapsed(rowNumber)) { | |||
return; | |||
} | |||
// Find the start of the group. | |||
int startIdx = findStartOfRowOutlineGroup(rowNumber); | |||
RowRecord row = getRow(startIdx); | |||
// Find the end of the group. | |||
int endIdx = findEndOfRowOutlineGroup(rowNumber); | |||
// expand: | |||
// collapsed bit must be unset | |||
// hidden bit gets unset _if_ surrounding groups are expanded you can determine | |||
// this by looking at the hidden bit of the enclosing group. You will have | |||
// to look at the start and the end of the current group to determine which | |||
// is the enclosing group | |||
// hidden bit only is altered for this outline level. ie. don't un-collapse contained groups | |||
if (!isRowGroupHiddenByParent(rowNumber)) { | |||
for (int i = startIdx; i <= endIdx; i++) { | |||
RowRecord otherRow = getRow(i); | |||
if (row.getOutlineLevel() == otherRow.getOutlineLevel() || !isRowGroupCollapsed(i)) { | |||
otherRow.setZeroHeight(false); | |||
} | |||
} | |||
} | |||
// Write collapse field | |||
getRow(endIdx + 1).setColapsed(false); | |||
} | |||
public boolean isRowGroupHiddenByParent(int row) { | |||
// Look out outline details of end | |||
int endLevel; | |||
boolean endHidden; | |||
int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row); | |||
if (getRow(endOfOutlineGroupIdx + 1) == null) { | |||
endLevel = 0; | |||
endHidden = false; | |||
} else { | |||
endLevel = getRow(endOfOutlineGroupIdx + 1).getOutlineLevel(); | |||
endHidden = getRow(endOfOutlineGroupIdx + 1).getZeroHeight(); | |||
} | |||
// Look out outline details of start | |||
int startLevel; | |||
boolean startHidden; | |||
int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row ); | |||
if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null) { | |||
startLevel = 0; | |||
startHidden = false; | |||
} else { | |||
startLevel = getRow(startOfOutlineGroupIdx - 1).getOutlineLevel(); | |||
startHidden = getRow(startOfOutlineGroupIdx - 1).getZeroHeight(); | |||
} | |||
if (endLevel > startLevel) { | |||
return endHidden; | |||
} | |||
return startHidden; | |||
} | |||
/** | |||
* Returns an iterator for the cell values | |||
*/ | |||
public Iterator<CellValueRecordInterface> getCellValueIterator() { | |||
return _valuesAgg.iterator(); | |||
} | |||
public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) { | |||
IndexRecord result = new IndexRecord(); | |||
result.setFirstRow(_firstrow); | |||
result.setLastRowAdd1(_lastrow + 1); | |||
// Calculate the size of the records from the end of the BOF | |||
// and up to the RowRecordsAggregate... | |||
// Add the references to the DBCells in the IndexRecord (one for each block) | |||
// Note: The offsets are relative to the Workbook BOF. Assume that this is | |||
// 0 for now..... | |||
int blockCount = getRowBlockCount(); | |||
// Calculate the size of this IndexRecord | |||
int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount); | |||
int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords; | |||
for (int block = 0; block < blockCount; block++) { | |||
// each row-block has a DBCELL record. | |||
// The offset of each DBCELL record needs to be updated in the INDEX record | |||
// account for row records in this row-block | |||
currentOffset += getRowBlockSize(block); | |||
// account for cell value records after those | |||
currentOffset += _valuesAgg.getRowCellBlockSize( | |||
getStartRowNumberForBlock(block), getEndRowNumberForBlock(block)); | |||
// currentOffset is now the location of the DBCELL record for this row-block | |||
result.addDbcell(currentOffset); | |||
// Add space required to write the DBCELL record (whose reference was just added). | |||
currentOffset += (8 + (getRowCountForBlock(block) * 2)); | |||
} | |||
return result; | |||
} | |||
public void insertCell(CellValueRecordInterface cvRec) { | |||
_valuesAgg.insertCell(cvRec); | |||
} | |||
public void removeCell(CellValueRecordInterface cvRec) { | |||
if (cvRec instanceof FormulaRecordAggregate) { | |||
((FormulaRecordAggregate)cvRec).notifyFormulaChanging(); | |||
} | |||
_valuesAgg.removeCell(cvRec); | |||
} | |||
public FormulaRecordAggregate createFormula(int row, int col) { | |||
FormulaRecord fr = new FormulaRecord(); | |||
fr.setRow(row); | |||
fr.setColumn((short) col); | |||
return new FormulaRecordAggregate(fr, null, _sharedValueManager); | |||
} | |||
public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) { | |||
_valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex); | |||
} | |||
public DimensionsRecord createDimensions() { | |||
DimensionsRecord result = new DimensionsRecord(); | |||
result.setFirstRow(_firstrow); | |||
result.setLastRow(_lastrow); | |||
result.setFirstCol((short) _valuesAgg.getFirstCellNum()); | |||
result.setLastCol((short) _valuesAgg.getLastCellNum()); | |||
return result; | |||
} | |||
PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0); | |||
//DBCells are serialized before row records. | |||
final int blockCount = getRowBlockCount(); | |||
for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { | |||
// Serialize a block of rows. | |||
// Hold onto the position of the first row in the block | |||
int pos=0; | |||
// Hold onto the size of this block that was serialized | |||
final int rowBlockSize = visitRowRecordsForBlock(blockIndex, rv); | |||
pos += rowBlockSize; | |||
// Serialize a block of cells for those rows | |||
final int startRowNumber = getStartRowNumberForBlock(blockIndex); | |||
final int endRowNumber = getEndRowNumberForBlock(blockIndex); | |||
DBCellRecord.Builder dbcrBuilder = new DBCellRecord.Builder(); | |||
// Note: Cell references start from the second row... | |||
int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE); | |||
for (int row = startRowNumber; row <= endRowNumber; row++) { | |||
if (_valuesAgg.rowHasCells(row)) { | |||
stv.setPosition(0); | |||
_valuesAgg.visitCellsForRow(row, stv); | |||
int rowCellSize = stv.getPosition(); | |||
pos += rowCellSize; | |||
// Add the offset to the first cell for the row into the | |||
// DBCellRecord. | |||
dbcrBuilder.addCellOffset(cellRefOffset); | |||
cellRefOffset = rowCellSize; | |||
} | |||
} | |||
// Calculate Offset from the start of a DBCellRecord to the first Row | |||
rv.visitRecord(dbcrBuilder.build(pos)); | |||
} | |||
for (Record _unknownRecord : _unknownRecords) { | |||
// Potentially breaking the file here since we don't know exactly where to write these records | |||
rv.visitRecord(_unknownRecord); | |||
} | |||
} | |||
public Iterator<RowRecord> getIterator() { | |||
return _rowRecords.values().iterator(); | |||
} | |||
public int findStartOfRowOutlineGroup(int row) { | |||
// Find the start of the group. | |||
RowRecord rowRecord = this.getRow( row ); | |||
int level = rowRecord.getOutlineLevel(); | |||
int currentRow = row; | |||
while (currentRow >= 0 && this.getRow( currentRow ) != null) { | |||
rowRecord = this.getRow( currentRow ); | |||
if (rowRecord.getOutlineLevel() < level) { | |||
return currentRow + 1; | |||
} | |||
currentRow--; | |||
} | |||
return currentRow + 1; | |||
} | |||
public int findEndOfRowOutlineGroup(int row) { | |||
int level = getRow( row ).getOutlineLevel(); | |||
int currentRow; | |||
for (currentRow = row; currentRow < getLastRowNum(); currentRow++) { | |||
if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level) { | |||
break; | |||
} | |||
} | |||
return currentRow-1; | |||
} | |||
/** | |||
* Hide all rows at or below the current outline level | |||
* @return index of the <em>next<em> row after the last row that gets hidden | |||
*/ | |||
private int writeHidden(RowRecord pRowRecord, int row) { | |||
int rowIx = row; | |||
RowRecord rowRecord = pRowRecord; | |||
int level = rowRecord.getOutlineLevel(); | |||
while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) { | |||
rowRecord.setZeroHeight(true); | |||
rowIx++; | |||
rowRecord = getRow(rowIx); | |||
} | |||
return rowIx; | |||
} | |||
public void collapseRow(int rowNumber) { | |||
// Find the start of the group. | |||
int startRow = findStartOfRowOutlineGroup(rowNumber); | |||
RowRecord rowRecord = getRow(startRow); | |||
// Hide all the columns until the end of the group | |||
int nextRowIx = writeHidden(rowRecord, startRow); | |||
RowRecord row = getRow(nextRowIx); | |||
if (row == null) { | |||
row = createRow(nextRowIx); | |||
insertRow(row); | |||
} | |||
// Write collapse field | |||
row.setColapsed(true); | |||
} | |||
/** | |||
* Create a row record. | |||
* | |||
* @param rowNumber row number | |||
* @return RowRecord created for the passed in row number | |||
* @see org.apache.poi.hssf.record.RowRecord | |||
*/ | |||
public static RowRecord createRow(int rowNumber) { | |||
return new RowRecord(rowNumber); | |||
} | |||
public boolean isRowGroupCollapsed(int row) { | |||
int collapseRow = findEndOfRowOutlineGroup(row) + 1; | |||
return getRow(collapseRow) != null && getRow(collapseRow).getColapsed(); | |||
} | |||
public void expandRow(int rowNumber) { | |||
if (rowNumber == -1) | |||
return; | |||
// If it is already expanded do nothing. | |||
if (!isRowGroupCollapsed(rowNumber)) { | |||
return; | |||
} | |||
// Find the start of the group. | |||
int startIdx = findStartOfRowOutlineGroup(rowNumber); | |||
RowRecord row = getRow(startIdx); | |||
// Find the end of the group. | |||
int endIdx = findEndOfRowOutlineGroup(rowNumber); | |||
// expand: | |||
// collapsed bit must be unset | |||
// hidden bit gets unset _if_ surrounding groups are expanded you can determine | |||
// this by looking at the hidden bit of the enclosing group. You will have | |||
// to look at the start and the end of the current group to determine which | |||
// is the enclosing group | |||
// hidden bit only is altered for this outline level. ie. don't un-collapse contained groups | |||
if (!isRowGroupHiddenByParent(rowNumber)) { | |||
for (int i = startIdx; i <= endIdx; i++) { | |||
RowRecord otherRow = getRow(i); | |||
if (row.getOutlineLevel() == otherRow.getOutlineLevel() || !isRowGroupCollapsed(i)) { | |||
otherRow.setZeroHeight(false); | |||
} | |||
} | |||
} | |||
// Write collapse field | |||
getRow(endIdx + 1).setColapsed(false); | |||
} | |||
public boolean isRowGroupHiddenByParent(int row) { | |||
// Look out outline details of end | |||
int endLevel; | |||
boolean endHidden; | |||
int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row); | |||
if (getRow(endOfOutlineGroupIdx + 1) == null) { | |||
endLevel = 0; | |||
endHidden = false; | |||
} else { | |||
endLevel = getRow(endOfOutlineGroupIdx + 1).getOutlineLevel(); | |||
endHidden = getRow(endOfOutlineGroupIdx + 1).getZeroHeight(); | |||
} | |||
// Look out outline details of start | |||
int startLevel; | |||
boolean startHidden; | |||
int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row ); | |||
if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null) { | |||
startLevel = 0; | |||
startHidden = false; | |||
} else { | |||
startLevel = getRow(startOfOutlineGroupIdx - 1).getOutlineLevel(); | |||
startHidden = getRow(startOfOutlineGroupIdx - 1).getZeroHeight(); | |||
} | |||
if (endLevel > startLevel) { | |||
return endHidden; | |||
} | |||
return startHidden; | |||
} | |||
/** | |||
* Returns an iterator for the cell values | |||
*/ | |||
public Iterator<CellValueRecordInterface> getCellValueIterator() { | |||
return _valuesAgg.iterator(); | |||
} | |||
public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) { | |||
IndexRecord result = new IndexRecord(); | |||
result.setFirstRow(_firstrow); | |||
result.setLastRowAdd1(_lastrow + 1); | |||
// Calculate the size of the records from the end of the BOF | |||
// and up to the RowRecordsAggregate... | |||
// Add the references to the DBCells in the IndexRecord (one for each block) | |||
// Note: The offsets are relative to the Workbook BOF. Assume that this is | |||
// 0 for now..... | |||
int blockCount = getRowBlockCount(); | |||
// Calculate the size of this IndexRecord | |||
int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount); | |||
int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords; | |||
for (int block = 0; block < blockCount; block++) { | |||
// each row-block has a DBCELL record. | |||
// The offset of each DBCELL record needs to be updated in the INDEX record | |||
// account for row records in this row-block | |||
currentOffset += getRowBlockSize(block); | |||
// account for cell value records after those | |||
currentOffset += _valuesAgg.getRowCellBlockSize( | |||
getStartRowNumberForBlock(block), getEndRowNumberForBlock(block)); | |||
// currentOffset is now the location of the DBCELL record for this row-block | |||
result.addDbcell(currentOffset); | |||
// Add space required to write the DBCELL record (whose reference was just added). | |||
currentOffset += (8 + (getRowCountForBlock(block) * 2)); | |||
} | |||
return result; | |||
} | |||
public void insertCell(CellValueRecordInterface cvRec) { | |||
_valuesAgg.insertCell(cvRec); | |||
} | |||
public void removeCell(CellValueRecordInterface cvRec) { | |||
if (cvRec instanceof FormulaRecordAggregate) { | |||
((FormulaRecordAggregate)cvRec).notifyFormulaChanging(); | |||
} | |||
_valuesAgg.removeCell(cvRec); | |||
} | |||
public FormulaRecordAggregate createFormula(int row, int col) { | |||
FormulaRecord fr = new FormulaRecord(); | |||
fr.setRow(row); | |||
fr.setColumn((short) col); | |||
return new FormulaRecordAggregate(fr, null, _sharedValueManager); | |||
} | |||
public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) { | |||
_valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex); | |||
} | |||
public DimensionsRecord createDimensions() { | |||
DimensionsRecord result = new DimensionsRecord(); | |||
result.setFirstRow(_firstrow); | |||
result.setLastRow(_lastrow); | |||
result.setFirstCol((short) _valuesAgg.getFirstCellNum()); | |||
result.setLastCol((short) _valuesAgg.getLastCellNum()); | |||
return result; | |||
} | |||
} |
@@ -19,10 +19,10 @@ package org.apache.poi.hssf.usermodel; | |||
import java.util.Map; | |||
import org.apache.poi.ss.formula.BaseFormulaEvaluator; | |||
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment; | |||
import org.apache.poi.ss.formula.IStabilityClassifier; | |||
import org.apache.poi.ss.formula.WorkbookEvaluator; | |||
import org.apache.poi.ss.formula.WorkbookEvaluatorProvider; | |||
import org.apache.poi.ss.formula.eval.BoolEval; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.NumericValueEval; | |||
@@ -33,8 +33,6 @@ import org.apache.poi.ss.usermodel.Cell; | |||
import org.apache.poi.ss.usermodel.CellType; | |||
import org.apache.poi.ss.usermodel.CellValue; | |||
import org.apache.poi.ss.usermodel.FormulaEvaluator; | |||
import org.apache.poi.ss.usermodel.Row; | |||
import org.apache.poi.ss.usermodel.Sheet; | |||
import org.apache.poi.ss.usermodel.Workbook; | |||
import org.apache.poi.util.Internal; | |||
@@ -45,362 +43,251 @@ import org.apache.poi.util.Internal; | |||
* cell values. Be sure to call {@link #clearAllCachedResultValues()} if any workbook cells are changed between | |||
* calls to evaluate~ methods on this class. | |||
*/ | |||
public class HSSFFormulaEvaluator implements FormulaEvaluator, WorkbookEvaluatorProvider { | |||
public class HSSFFormulaEvaluator extends BaseFormulaEvaluator { | |||
private final HSSFWorkbook _book; | |||
private final WorkbookEvaluator _bookEvaluator; | |||
private final HSSFWorkbook _book; | |||
public HSSFFormulaEvaluator(HSSFWorkbook workbook) { | |||
this(workbook, null); | |||
} | |||
/** | |||
* @param workbook The workbook to perform the formula evaluations in | |||
* @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> | |||
* for the (conservative) assumption that any cell may have its definition changed after | |||
* evaluation begins. | |||
*/ | |||
public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) { | |||
this(workbook, stabilityClassifier, null); | |||
} | |||
public HSSFFormulaEvaluator(HSSFWorkbook workbook) { | |||
this(workbook, null); | |||
} | |||
/** | |||
* @param workbook The workbook to perform the formula evaluations in | |||
* @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> | |||
* for the (conservative) assumption that any cell may have its definition changed after | |||
* evaluation begins. | |||
*/ | |||
public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) { | |||
this(workbook, stabilityClassifier, null); | |||
} | |||
/** | |||
* @param workbook The workbook to perform the formula evaluations in | |||
/** | |||
* @param workbook The workbook to perform the formula evaluations in | |||
* @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> | |||
* for the (conservative) assumption that any cell may have its definition changed after | |||
* evaluation begins. | |||
* @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) | |||
*/ | |||
private HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { | |||
_book = workbook; | |||
_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder); | |||
} | |||
* @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) | |||
*/ | |||
private HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { | |||
super(new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder)); | |||
_book = workbook; | |||
} | |||
/** | |||
* @param workbook The workbook to perform the formula evaluations in | |||
* @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> | |||
* for the (conservative) assumption that any cell may have its definition changed after | |||
* evaluation begins. | |||
* @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) | |||
*/ | |||
public static HSSFFormulaEvaluator create(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { | |||
return new HSSFFormulaEvaluator(workbook, stabilityClassifier, udfFinder); | |||
} | |||
/** | |||
* @param workbook The workbook to perform the formula evaluations in | |||
* @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> | |||
* for the (conservative) assumption that any cell may have its definition changed after | |||
* evaluation begins. | |||
* @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) | |||
*/ | |||
public static HSSFFormulaEvaluator create(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { | |||
return new HSSFFormulaEvaluator(workbook, stabilityClassifier, udfFinder); | |||
} | |||
/** | |||
* Coordinates several formula evaluators together so that formulas that involve external | |||
* references can be evaluated. | |||
* @param workbookNames the simple file names used to identify the workbooks in formulas | |||
* with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") | |||
* @param evaluators all evaluators for the full set of workbooks required by the formulas. | |||
*/ | |||
public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) { | |||
WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length]; | |||
for (int i = 0; i < wbEvals.length; i++) { | |||
wbEvals[i] = evaluators[i]._bookEvaluator; | |||
} | |||
CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals); | |||
} | |||
/** | |||
* Coordinates several formula evaluators together so that formulas that involve external | |||
* references can be evaluated. | |||
* @param workbookNames the simple file names used to identify the workbooks in formulas | |||
* with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") | |||
* @param evaluators all evaluators for the full set of workbooks required by the formulas. | |||
*/ | |||
public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) { | |||
BaseFormulaEvaluator.setupEnvironment(workbookNames, evaluators); | |||
} | |||
@Override | |||
@Override | |||
public void setupReferencedWorkbooks(Map<String, FormulaEvaluator> evaluators) { | |||
CollaboratingWorkbooksEnvironment.setupFormulaEvaluator(evaluators); | |||
} | |||
@Override | |||
public WorkbookEvaluator _getWorkbookEvaluator() { | |||
return _bookEvaluator; | |||
/** | |||
* Should be called to tell the cell value cache that the specified (value or formula) cell | |||
* has changed. | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
public void notifyUpdateCell(HSSFCell cell) { | |||
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell)); | |||
} | |||
/** | |||
* Should be called whenever there are major changes (e.g. moving sheets) to input cells | |||
* in the evaluated workbook. If performance is not critical, a single call to this method | |||
* may be used instead of many specific calls to the notify~ methods. | |||
* | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
@Override | |||
public void clearAllCachedResultValues() { | |||
_bookEvaluator.clearAllCachedResultValues(); | |||
} | |||
/** | |||
* Should be called to tell the cell value cache that the specified (value or formula) cell | |||
* has changed. | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
public void notifyUpdateCell(HSSFCell cell) { | |||
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell)); | |||
} | |||
@Override | |||
public void notifyUpdateCell(Cell cell) { | |||
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); | |||
} | |||
/** | |||
* Should be called to tell the cell value cache that the specified cell has just been | |||
* deleted. | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
public void notifyDeleteCell(HSSFCell cell) { | |||
_bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell)); | |||
} | |||
@Override | |||
/** | |||
* Should be called to tell the cell value cache that the specified cell has just been | |||
* deleted. | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
public void notifyDeleteCell(HSSFCell cell) { | |||
_bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell)); | |||
} | |||
@Override | |||
public void notifyDeleteCell(Cell cell) { | |||
_bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell((HSSFCell)cell)); | |||
} | |||
_bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell((HSSFCell)cell)); | |||
} | |||
/** | |||
* Should be called to tell the cell value cache that the specified (value or formula) cell | |||
* has changed. | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
@Override | |||
/** | |||
* Should be called to tell the cell value cache that the specified (value or formula) cell | |||
* has changed. | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
@Override | |||
public void notifySetFormula(Cell cell) { | |||
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); | |||
} | |||
/** | |||
* If cell contains a formula, the formula is evaluated and returned, | |||
* else the CellValue simply copies the appropriate cell value from | |||
* the cell and also its cell type. This method should be preferred over | |||
* evaluateInCell() when the call should not modify the contents of the | |||
* original cell. | |||
* | |||
* @param cell may be <code>null</code> signifying that the cell is not present (or blank) | |||
* @return <code>null</code> if the supplied cell is <code>null</code> or blank | |||
*/ | |||
@Override | |||
public CellValue evaluate(Cell cell) { | |||
if (cell == null) { | |||
return null; | |||
} | |||
switch (cell.getCellTypeEnum()) { | |||
case BOOLEAN: | |||
return CellValue.valueOf(cell.getBooleanCellValue()); | |||
case ERROR: | |||
return CellValue.getError(cell.getErrorCellValue()); | |||
case FORMULA: | |||
return evaluateFormulaCellValue(cell); | |||
case NUMERIC: | |||
return new CellValue(cell.getNumericCellValue()); | |||
case STRING: | |||
return new CellValue(cell.getRichStringCellValue().getString()); | |||
case BLANK: | |||
return null; | |||
default: | |||
throw new IllegalStateException("Bad cell type (" + cell.getCellTypeEnum() + ")"); | |||
} | |||
} | |||
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); | |||
} | |||
/** | |||
* If cell contains formula, it evaluates the formula, and saves the result of the formula. The | |||
* cell remains as a formula cell. If the cell does not contain formula, rather than throwing an | |||
* exception, this method returns {@link CellType#_NONE} and leaves the cell unchanged. | |||
* | |||
* Note that the type of the <em>formula result</em> is returned, so you know what kind of | |||
* cached formula result is also stored with the formula. | |||
* <pre> | |||
* CellType evaluatedCellType = evaluator.evaluateFormulaCell(cell); | |||
* </pre> | |||
* Be aware that your cell will hold both the formula, and the result. If you want the cell | |||
* replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} | |||
* @param cell The cell to evaluate | |||
* @return {@link CellType#_NONE} for non-formula cells, or the type of the <em>formula result</em> | |||
* @since POI 3.15 beta 3 | |||
* @deprecated POI 3.15 beta 3. Will be deleted when we make the CellType enum transition. See bug 59791. | |||
*/ | |||
@Internal | |||
@Override | |||
public CellType evaluateFormulaCellEnum(Cell cell) { | |||
if (cell == null || cell.getCellTypeEnum() != CellType.FORMULA) { | |||
return CellType._NONE; | |||
} | |||
CellValue cv = evaluateFormulaCellValue(cell); | |||
// cell remains a formula cell, but the cached value is changed | |||
setCellValue(cell, cv); | |||
return cv.getCellType(); | |||
} | |||
/** | |||
* If cell contains formula, it evaluates the formula, and saves the result of the formula. The | |||
* cell remains as a formula cell. If the cell does not contain formula, this method returns -1 | |||
* and leaves the cell unchanged. | |||
* | |||
* Note that the type of the <em>formula result</em> is returned, so you know what kind of | |||
* cached formula result is also stored with the formula. | |||
* <pre> | |||
* int evaluatedCellType = evaluator.evaluateFormulaCell(cell); | |||
* </pre> | |||
* Be aware that your cell will hold both the formula, and the result. If you want the cell | |||
* replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} | |||
* @param cell The cell to evaluate | |||
* @return -1 for non-formula cells, or the type of the <em>formula result</em> | |||
*/ | |||
@Override | |||
public int evaluateFormulaCell(Cell cell) { | |||
return evaluateFormulaCellEnum(cell).getCode(); | |||
} | |||
/** | |||
* If cell contains formula, it evaluates the formula, and saves the result of the formula. The | |||
* cell remains as a formula cell. If the cell does not contain formula, rather than throwing an | |||
* exception, this method returns {@link CellType#_NONE} and leaves the cell unchanged. | |||
* | |||
* Note that the type of the <em>formula result</em> is returned, so you know what kind of | |||
* cached formula result is also stored with the formula. | |||
* <pre> | |||
* CellType evaluatedCellType = evaluator.evaluateFormulaCell(cell); | |||
* </pre> | |||
* Be aware that your cell will hold both the formula, and the result. If you want the cell | |||
* replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} | |||
* @param cell The cell to evaluate | |||
* @return {@link CellType#_NONE} for non-formula cells, or the type of the <em>formula result</em> | |||
* @since POI 3.15 beta 3 | |||
* @deprecated POI 3.15 beta 3. Will be deleted when we make the CellType enum transition. See bug 59791. | |||
*/ | |||
@Internal | |||
@Override | |||
public CellType evaluateFormulaCellEnum(Cell cell) { | |||
if (cell == null || cell.getCellTypeEnum() != CellType.FORMULA) { | |||
return CellType._NONE; | |||
} | |||
CellValue cv = evaluateFormulaCellValue(cell); | |||
// cell remains a formula cell, but the cached value is changed | |||
setCellValue(cell, cv); | |||
return cv.getCellType(); | |||
} | |||
/** | |||
* If cell contains formula, it evaluates the formula, and | |||
* puts the formula result back into the cell, in place | |||
* of the old formula. | |||
* Else if cell does not contain formula, this method leaves | |||
* the cell unchanged. | |||
* Note that the same instance of HSSFCell is returned to | |||
* allow chained calls like: | |||
* <pre> | |||
* int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); | |||
* </pre> | |||
* Be aware that your cell value will be changed to hold the | |||
* result of the formula. If you simply want the formula | |||
* value computed for you, use {@link #evaluateFormulaCellEnum(Cell)}} | |||
*/ | |||
@Override | |||
public HSSFCell evaluateInCell(Cell cell) { | |||
if (cell == null) { | |||
return null; | |||
} | |||
HSSFCell result = (HSSFCell) cell; | |||
if (cell.getCellTypeEnum() == CellType.FORMULA) { | |||
CellValue cv = evaluateFormulaCellValue(cell); | |||
setCellValue(cell, cv); | |||
setCellType(cell, cv); // cell will no longer be a formula cell | |||
} | |||
return result; | |||
} | |||
/** | |||
* If cell contains formula, it evaluates the formula, and | |||
* puts the formula result back into the cell, in place | |||
* of the old formula. | |||
* Else if cell does not contain formula, this method leaves | |||
* the cell unchanged. | |||
* Note that the same instance of HSSFCell is returned to | |||
* allow chained calls like: | |||
* <pre> | |||
* int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); | |||
* </pre> | |||
* Be aware that your cell value will be changed to hold the | |||
* result of the formula. If you simply want the formula | |||
* value computed for you, use {@link #evaluateFormulaCellEnum(Cell)}} | |||
*/ | |||
@Override | |||
public HSSFCell evaluateInCell(Cell cell) { | |||
if (cell == null) { | |||
return null; | |||
} | |||
HSSFCell result = (HSSFCell) cell; | |||
if (cell.getCellTypeEnum() == CellType.FORMULA) { | |||
CellValue cv = evaluateFormulaCellValue(cell); | |||
setCellValue(cell, cv); | |||
setCellType(cell, cv); // cell will no longer be a formula cell | |||
} | |||
return result; | |||
} | |||
private static void setCellType(Cell cell, CellValue cv) { | |||
CellType cellType = cv.getCellType(); | |||
switch (cellType) { | |||
case BOOLEAN: | |||
case ERROR: | |||
case NUMERIC: | |||
case STRING: | |||
cell.setCellType(cellType); | |||
return; | |||
case BLANK: | |||
// never happens - blanks eventually get translated to zero | |||
case FORMULA: | |||
// this will never happen, we have already evaluated the formula | |||
default: | |||
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); | |||
} | |||
} | |||
private static void setCellValue(Cell cell, CellValue cv) { | |||
CellType cellType = cv.getCellType(); | |||
switch (cellType) { | |||
case BOOLEAN: | |||
cell.setCellValue(cv.getBooleanValue()); | |||
break; | |||
case ERROR: | |||
cell.setCellErrorValue(cv.getErrorValue()); | |||
break; | |||
case NUMERIC: | |||
cell.setCellValue(cv.getNumberValue()); | |||
break; | |||
case STRING: | |||
cell.setCellValue(new HSSFRichTextString(cv.getStringValue())); | |||
break; | |||
case BLANK: | |||
// never happens - blanks eventually get translated to zero | |||
case FORMULA: | |||
// this will never happen, we have already evaluated the formula | |||
default: | |||
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); | |||
} | |||
} | |||
private static void setCellValue(Cell cell, CellValue cv) { | |||
CellType cellType = cv.getCellType(); | |||
switch (cellType) { | |||
case BOOLEAN: | |||
cell.setCellValue(cv.getBooleanValue()); | |||
break; | |||
case ERROR: | |||
cell.setCellErrorValue(cv.getErrorValue()); | |||
break; | |||
case NUMERIC: | |||
cell.setCellValue(cv.getNumberValue()); | |||
break; | |||
case STRING: | |||
cell.setCellValue(new HSSFRichTextString(cv.getStringValue())); | |||
break; | |||
case BLANK: | |||
// never happens - blanks eventually get translated to zero | |||
case FORMULA: | |||
// this will never happen, we have already evaluated the formula | |||
default: | |||
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); | |||
} | |||
} | |||
/** | |||
* Loops over all cells in all sheets of the supplied | |||
* workbook. | |||
* For cells that contain formulas, their formulas are | |||
* evaluated, and the results are saved. These cells | |||
* remain as formula cells. | |||
* For cells that do not contain formulas, no changes | |||
* are made. | |||
* This is a helpful wrapper around looping over all | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
public static void evaluateAllFormulaCells(HSSFWorkbook wb) { | |||
evaluateAllFormulaCells(wb, new HSSFFormulaEvaluator(wb)); | |||
} | |||
/** | |||
* Loops over all cells in all sheets of the supplied | |||
* workbook. | |||
* For cells that contain formulas, their formulas are | |||
* evaluated, and the results are saved. These cells | |||
* remain as formula cells. | |||
* For cells that do not contain formulas, no changes | |||
* are made. | |||
* This is a helpful wrapper around looping over all | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
public static void evaluateAllFormulaCells(HSSFWorkbook wb) { | |||
evaluateAllFormulaCells(wb, new HSSFFormulaEvaluator(wb)); | |||
} | |||
/** | |||
* Loops over all cells in all sheets of the supplied | |||
* workbook. | |||
* For cells that contain formulas, their formulas are | |||
* evaluated, and the results are saved. These cells | |||
* remain as formula cells. | |||
* For cells that do not contain formulas, no changes | |||
* are made. | |||
* This is a helpful wrapper around looping over all | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
public static void evaluateAllFormulaCells(Workbook wb) { | |||
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); | |||
evaluateAllFormulaCells(wb, evaluator); | |||
} | |||
private static void evaluateAllFormulaCells(Workbook wb, FormulaEvaluator evaluator) { | |||
for(int i=0; i<wb.getNumberOfSheets(); i++) { | |||
Sheet sheet = wb.getSheetAt(i); | |||
/** | |||
* Loops over all cells in all sheets of the supplied | |||
* workbook. | |||
* For cells that contain formulas, their formulas are | |||
* evaluated, and the results are saved. These cells | |||
* remain as formula cells. | |||
* For cells that do not contain formulas, no changes | |||
* are made. | |||
* This is a helpful wrapper around looping over all | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
public static void evaluateAllFormulaCells(Workbook wb) { | |||
BaseFormulaEvaluator.evaluateAllFormulaCells(wb); | |||
} | |||
for(Row r : sheet) { | |||
for (Cell c : r) { | |||
if (c.getCellTypeEnum() == CellType.FORMULA) { | |||
evaluator.evaluateFormulaCellEnum(c); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Loops over all cells in all sheets of the supplied | |||
* workbook. | |||
* For cells that contain formulas, their formulas are | |||
* evaluated, and the results are saved. These cells | |||
* remain as formula cells. | |||
* For cells that do not contain formulas, no changes | |||
* are made. | |||
* This is a helpful wrapper around looping over all | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
@Override | |||
public void evaluateAll() { | |||
evaluateAllFormulaCells(_book, this); | |||
} | |||
/** | |||
* Loops over all cells in all sheets of the supplied | |||
* workbook. | |||
* For cells that contain formulas, their formulas are | |||
* evaluated, and the results are saved. These cells | |||
* remain as formula cells. | |||
* For cells that do not contain formulas, no changes | |||
* are made. | |||
* This is a helpful wrapper around looping over all | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
@Override | |||
public void evaluateAll() { | |||
evaluateAllFormulaCells(_book, this); | |||
} | |||
/** | |||
* Returns a CellValue wrapper around the supplied ValueEval instance. | |||
* @param cell | |||
*/ | |||
private CellValue evaluateFormulaCellValue(Cell cell) { | |||
ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell)); | |||
if (eval instanceof BoolEval) { | |||
BoolEval be = (BoolEval) eval; | |||
return CellValue.valueOf(be.getBooleanValue()); | |||
} | |||
if (eval instanceof NumericValueEval) { | |||
NumericValueEval ne = (NumericValueEval) eval; | |||
return new CellValue(ne.getNumberValue()); | |||
} | |||
if (eval instanceof StringValueEval) { | |||
StringValueEval ne = (StringValueEval) eval; | |||
return new CellValue(ne.getStringValue()); | |||
} | |||
if (eval instanceof ErrorEval) { | |||
return CellValue.getError(((ErrorEval)eval).getErrorCode()); | |||
} | |||
throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); | |||
} | |||
/** | |||
* Returns a CellValue wrapper around the supplied ValueEval instance. | |||
* @param cell | |||
*/ | |||
protected CellValue evaluateFormulaCellValue(Cell cell) { | |||
ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell)); | |||
if (eval instanceof BoolEval) { | |||
BoolEval be = (BoolEval) eval; | |||
return CellValue.valueOf(be.getBooleanValue()); | |||
} | |||
if (eval instanceof NumericValueEval) { | |||
NumericValueEval ne = (NumericValueEval) eval; | |||
return new CellValue(ne.getNumberValue()); | |||
} | |||
if (eval instanceof StringValueEval) { | |||
StringValueEval ne = (StringValueEval) eval; | |||
return new CellValue(ne.getStringValue()); | |||
} | |||
if (eval instanceof ErrorEval) { | |||
return CellValue.getError(((ErrorEval)eval).getErrorCode()); | |||
} | |||
throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override |
@@ -92,6 +92,7 @@ import org.apache.poi.ss.formula.SheetNameFormatter; | |||
import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; | |||
import org.apache.poi.ss.formula.udf.IndexedUDFFinder; | |||
import org.apache.poi.ss.formula.udf.UDFFinder; | |||
import org.apache.poi.ss.usermodel.Name; | |||
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; | |||
import org.apache.poi.ss.usermodel.Sheet; | |||
import org.apache.poi.ss.usermodel.Workbook; | |||
@@ -548,7 +549,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss | |||
* the 'active' sheet (which is the sheet with focus). | |||
* Unselects sheets that are not in <code>indexes</code>. | |||
* | |||
* @param indexes | |||
* @param indexes Array of sheets to select, the index is 0-based. | |||
*/ | |||
public void setSelectedTabs(int[] indexes) { | |||
Collection<Integer> list = new ArrayList<Integer>(indexes.length); | |||
@@ -563,7 +564,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss | |||
* the 'active' sheet (which is the sheet with focus). | |||
* Unselects sheets that are not in <code>indexes</code>. | |||
* | |||
* @param indexes | |||
* @param indexes Collection of sheets to select, the index is 0-based. | |||
*/ | |||
public void setSelectedTabs(Collection<Integer> indexes) { | |||
@@ -893,8 +894,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss | |||
*/ | |||
@Override | |||
public Iterator<Sheet> sheetIterator() { | |||
Iterator<Sheet> result = new SheetIterator<Sheet>(); | |||
return result; | |||
return new SheetIterator<Sheet>(); | |||
} | |||
/** | |||
@@ -1280,9 +1280,9 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss | |||
/** | |||
* Closes the underlying {@link NPOIFSFileSystem} from which | |||
* the Workbook was read, if any. Has no effect on Workbooks | |||
* opened from an InputStream, or newly created ones. | |||
* <p>Once {@link #close()} has been called, no further | |||
* the Workbook was read, if any. | |||
* | |||
* <p>Once this has been called, no further | |||
* operations, updates or reads should be performed on the | |||
* Workbook. | |||
*/ | |||
@@ -1531,6 +1531,11 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss | |||
return names.get(nameIndex); | |||
} | |||
@Override | |||
public List<HSSFName> getAllNames() { | |||
return Collections.unmodifiableList(names); | |||
} | |||
public NameRecord getNameRecord(int nameIndex) { | |||
return getWorkbook().getNameRecord(nameIndex); | |||
} | |||
@@ -1702,8 +1707,9 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss | |||
* | |||
* @param name the name to remove. | |||
*/ | |||
void removeName(HSSFName name) { | |||
int index = getNameIndex(name); | |||
@Override | |||
public void removeName(Name name) { | |||
int index = getNameIndex((HSSFName) name); | |||
removeName(index); | |||
} | |||
@@ -374,20 +374,22 @@ public class CryptoFunctions { | |||
// SET Verifier TO 0x0000 | |||
short verifier = 0; | |||
// FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER | |||
for (int i = arrByteChars.length-1; i >= 0; i--) { | |||
// SET Verifier TO Intermediate3 BITWISE XOR PasswordByte | |||
if (!"".equals(password)) { | |||
// FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER | |||
for (int i = arrByteChars.length-1; i >= 0; i--) { | |||
// SET Verifier TO Intermediate3 BITWISE XOR PasswordByte | |||
verifier = rotateLeftBase15Bit(verifier); | |||
verifier ^= arrByteChars[i]; | |||
} | |||
// as we haven't prepended the password length into the input array | |||
// we need to do it now separately ... | |||
verifier = rotateLeftBase15Bit(verifier); | |||
verifier ^= arrByteChars[i]; | |||
verifier ^= arrByteChars.length; | |||
// RETURN Verifier BITWISE XOR 0xCE4B | |||
verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K') | |||
} | |||
// as we haven't prepended the password length into the input array | |||
// we need to do it now separately ... | |||
verifier = rotateLeftBase15Bit(verifier); | |||
verifier ^= arrByteChars.length; | |||
// RETURN Verifier BITWISE XOR 0xCE4B | |||
verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K') | |||
return verifier & 0xFFFF; | |||
} |
@@ -0,0 +1,194 @@ | |||
/* ==================================================================== | |||
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.Map; | |||
import org.apache.poi.ss.usermodel.Cell; | |||
import org.apache.poi.ss.usermodel.CellType; | |||
import org.apache.poi.ss.usermodel.CellValue; | |||
import org.apache.poi.ss.usermodel.FormulaEvaluator; | |||
import org.apache.poi.ss.usermodel.Row; | |||
import org.apache.poi.ss.usermodel.Sheet; | |||
import org.apache.poi.ss.usermodel.Workbook; | |||
/** | |||
* Common functionality across file formats for evaluating formula cells.<p/> | |||
*/ | |||
public abstract class BaseFormulaEvaluator implements FormulaEvaluator, WorkbookEvaluatorProvider { | |||
protected final WorkbookEvaluator _bookEvaluator; | |||
protected BaseFormulaEvaluator(WorkbookEvaluator bookEvaluator) { | |||
this._bookEvaluator = bookEvaluator; | |||
} | |||
/** | |||
* Coordinates several formula evaluators together so that formulas that involve external | |||
* references can be evaluated. | |||
* @param workbookNames the simple file names used to identify the workbooks in formulas | |||
* with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") | |||
* @param evaluators all evaluators for the full set of workbooks required by the formulas. | |||
*/ | |||
public static void setupEnvironment(String[] workbookNames, BaseFormulaEvaluator[] evaluators) { | |||
WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length]; | |||
for (int i = 0; i < wbEvals.length; i++) { | |||
wbEvals[i] = evaluators[i]._bookEvaluator; | |||
} | |||
CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals); | |||
} | |||
@Override | |||
public void setupReferencedWorkbooks(Map<String, FormulaEvaluator> evaluators) { | |||
CollaboratingWorkbooksEnvironment.setupFormulaEvaluator(evaluators); | |||
} | |||
@Override | |||
public WorkbookEvaluator _getWorkbookEvaluator() { | |||
return _bookEvaluator; | |||
} | |||
/** | |||
* Should be called whenever there are major changes (e.g. moving sheets) to input cells | |||
* in the evaluated workbook. If performance is not critical, a single call to this method | |||
* may be used instead of many specific calls to the notify~ methods. | |||
* | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
@Override | |||
public void clearAllCachedResultValues() { | |||
_bookEvaluator.clearAllCachedResultValues(); | |||
} | |||
/** | |||
* If cell contains a formula, the formula is evaluated and returned, | |||
* else the CellValue simply copies the appropriate cell value from | |||
* the cell and also its cell type. This method should be preferred over | |||
* evaluateInCell() when the call should not modify the contents of the | |||
* original cell. | |||
* | |||
* @param cell may be <code>null</code> signifying that the cell is not present (or blank) | |||
* @return <code>null</code> if the supplied cell is <code>null</code> or blank | |||
*/ | |||
@Override | |||
public CellValue evaluate(Cell cell) { | |||
if (cell == null) { | |||
return null; | |||
} | |||
switch (cell.getCellTypeEnum()) { | |||
case BOOLEAN: | |||
return CellValue.valueOf(cell.getBooleanCellValue()); | |||
case ERROR: | |||
return CellValue.getError(cell.getErrorCellValue()); | |||
case FORMULA: | |||
return evaluateFormulaCellValue(cell); | |||
case NUMERIC: | |||
return new CellValue(cell.getNumericCellValue()); | |||
case STRING: | |||
return new CellValue(cell.getRichStringCellValue().getString()); | |||
case BLANK: | |||
return null; | |||
default: | |||
throw new IllegalStateException("Bad cell type (" + cell.getCellTypeEnum() + ")"); | |||
} | |||
} | |||
protected abstract CellValue evaluateFormulaCellValue(Cell cell); | |||
/** | |||
* If cell contains formula, it evaluates the formula, and saves the result of the formula. The | |||
* cell remains as a formula cell. If the cell does not contain formula, this method returns -1 | |||
* and leaves the cell unchanged. | |||
* | |||
* Note that the type of the <em>formula result</em> is returned, so you know what kind of | |||
* cached formula result is also stored with the formula. | |||
* <pre> | |||
* int evaluatedCellType = evaluator.evaluateFormulaCell(cell); | |||
* </pre> | |||
* Be aware that your cell will hold both the formula, and the result. If you want the cell | |||
* replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} | |||
* @param cell The cell to evaluate | |||
* @return -1 for non-formula cells, or the type of the <em>formula result</em> | |||
*/ | |||
@Override | |||
public int evaluateFormulaCell(Cell cell) { | |||
return evaluateFormulaCellEnum(cell).getCode(); | |||
} | |||
protected static void setCellType(Cell cell, CellValue cv) { | |||
CellType cellType = cv.getCellType(); | |||
switch (cellType) { | |||
case BOOLEAN: | |||
case ERROR: | |||
case NUMERIC: | |||
case STRING: | |||
cell.setCellType(cellType); | |||
return; | |||
case BLANK: | |||
// never happens - blanks eventually get translated to zero | |||
throw new IllegalArgumentException("This should never happen. Blanks eventually get translated to zero."); | |||
case FORMULA: | |||
// this will never happen, we have already evaluated the formula | |||
throw new IllegalArgumentException("This should never happen. Formulas should have already been evaluated."); | |||
default: | |||
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); | |||
} | |||
} | |||
/** | |||
* Loops over all cells in all sheets of the supplied | |||
* workbook. | |||
* For cells that contain formulas, their formulas are | |||
* evaluated, and the results are saved. These cells | |||
* remain as formula cells. | |||
* For cells that do not contain formulas, no changes | |||
* are made. | |||
* This is a helpful wrapper around looping over all | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
public static void evaluateAllFormulaCells(Workbook wb) { | |||
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); | |||
evaluateAllFormulaCells(wb, evaluator); | |||
} | |||
protected static void evaluateAllFormulaCells(Workbook wb, FormulaEvaluator evaluator) { | |||
for(int i=0; i<wb.getNumberOfSheets(); i++) { | |||
Sheet sheet = wb.getSheetAt(i); | |||
for(Row r : sheet) { | |||
for (Cell c : r) { | |||
if (c.getCellTypeEnum() == CellType.FORMULA) { | |||
evaluator.evaluateFormulaCellEnum(c); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public void setIgnoreMissingWorkbooks(boolean ignore){ | |||
_bookEvaluator.setIgnoreMissingWorkbooks(ignore); | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public void setDebugEvaluationOutputForNextEval(boolean value){ | |||
_bookEvaluator.setDebugEvaluationOutputForNextEval(value); | |||
} | |||
} |
@@ -27,7 +27,7 @@ import org.apache.poi.ss.util.CellReference; | |||
/** | |||
* Provides Lazy Evaluation to a 3D Reference | |||
*/ | |||
final class LazyRefEval extends RefEvalBase { | |||
public final class LazyRefEval extends RefEvalBase { | |||
private final SheetRangeEvaluator _evaluator; | |||
public LazyRefEval(int rowIndex, int columnIndex, SheetRangeEvaluator sre) { | |||
@@ -47,14 +47,17 @@ final class LazyRefEval extends RefEvalBase { | |||
return new LazyAreaEval(area, _evaluator); | |||
} | |||
public boolean isSubTotal() { | |||
SheetRefEvaluator sheetEvaluator = _evaluator.getSheetEvaluator(getFirstSheetIndex()); | |||
return sheetEvaluator.isSubTotal(getRow(), getColumn()); | |||
} | |||
public String toString() { | |||
CellReference cr = new CellReference(getRow(), getColumn()); | |||
StringBuffer sb = new StringBuffer(); | |||
sb.append(getClass().getName()).append("["); | |||
sb.append(_evaluator.getSheetNameRange()); | |||
sb.append('!'); | |||
sb.append(cr.formatAsString()); | |||
sb.append("]"); | |||
return sb.toString(); | |||
return getClass().getName() + "[" + | |||
_evaluator.getSheetNameRange() + | |||
'!' + | |||
cr.formatAsString() + | |||
"]"; | |||
} | |||
} |
@@ -19,6 +19,7 @@ package org.apache.poi.ss.formula.functions; | |||
import static org.apache.poi.ss.formula.functions.AggregateFunction.subtotalInstance; | |||
import org.apache.poi.ss.formula.LazyRefEval; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.NotImplementedException; | |||
@@ -26,6 +27,11 @@ import org.apache.poi.ss.formula.eval.NotImplementedFunctionException; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
/** | |||
* Implementation for the Excel function SUBTOTAL<p> | |||
* | |||
@@ -61,7 +67,6 @@ import org.apache.poi.ss.formula.eval.ValueEval; | |||
public class Subtotal implements Function { | |||
private static Function findFunction(int functionCode) throws EvaluationException { | |||
Function func; | |||
switch (functionCode) { | |||
case 1: return subtotalInstance(AggregateFunction.AVERAGE); | |||
case 2: return Count.subtotalInstance(); | |||
@@ -87,7 +92,7 @@ public class Subtotal implements Function { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
Function innerFunc; | |||
final Function innerFunc; | |||
try { | |||
ValueEval ve = OperandResolver.getSingleValue(args[0], srcRowIndex, srcColumnIndex); | |||
int functionCode = OperandResolver.coerceValueToInt(ve); | |||
@@ -96,9 +101,24 @@ public class Subtotal implements Function { | |||
return e.getErrorEval(); | |||
} | |||
ValueEval[] innerArgs = new ValueEval[nInnerArgs]; | |||
System.arraycopy(args, 1, innerArgs, 0, nInnerArgs); | |||
// ignore the first arg, this is the function-type, we check for the length above | |||
final List<ValueEval> list = new ArrayList<ValueEval>(Arrays.asList(args).subList(1, args.length)); | |||
Iterator<ValueEval> it = list.iterator(); | |||
// See https://support.office.com/en-us/article/SUBTOTAL-function-7b027003-f060-4ade-9040-e478765b9939 | |||
// "If there are other subtotals within ref1, ref2,... (or nested subtotals), these nested subtotals are ignored to avoid double counting." | |||
// For array references it is handled in other evaluation steps, but we need to handle this here for references to subtotal-functions | |||
while(it.hasNext()) { | |||
ValueEval eval = it.next(); | |||
if(eval instanceof LazyRefEval) { | |||
LazyRefEval lazyRefEval = (LazyRefEval) eval; | |||
if(lazyRefEval.isSubTotal()) { | |||
it.remove(); | |||
} | |||
} | |||
} | |||
return innerFunc.evaluate(innerArgs, srcRowIndex, srcColumnIndex); | |||
return innerFunc.evaluate(list.toArray(new ValueEval[list.size()]), srcRowIndex, srcColumnIndex); | |||
} | |||
} |
@@ -81,7 +81,7 @@ public interface CellStyle { | |||
/** | |||
* vertically justified vertical alignment | |||
* @deprecated POI 3.15 beta 3. Use {@link VerticalAlignment#TOP} instead. | |||
* @deprecated POI 3.15 beta 3. Use {@link VerticalAlignment#JUSTIFY} instead. | |||
*/ | |||
static final short VERTICAL_JUSTIFY = 0x3; //VerticalAlignment.JUSTIFY.getCode(); | |||
@@ -341,9 +341,11 @@ public interface Workbook extends Closeable, Iterable<Sheet> { | |||
/** | |||
* Close the underlying input resource (File or Stream), | |||
* from which the Workbook was read. After closing, the | |||
* Workbook should no longer be used. | |||
* <p>This will have no effect newly created Workbooks. | |||
* from which the Workbook was read. | |||
* | |||
* <p>Once this has been called, no further | |||
* operations, updates or reads should be performed on the | |||
* Workbook. | |||
*/ | |||
@Override | |||
void close() throws IOException; | |||
@@ -367,6 +369,13 @@ public interface Workbook extends Closeable, Iterable<Sheet> { | |||
*/ | |||
List<? extends Name> getNames(String name); | |||
/** | |||
* Returns all defined names. | |||
* | |||
* @return a list of the defined names. An empty list is returned if none is found. | |||
*/ | |||
List<? extends Name> getAllNames(); | |||
/** | |||
* @param nameIndex position of the named range (0-based) | |||
* @return the defined name at the specified index | |||
@@ -405,6 +414,13 @@ public interface Workbook extends Closeable, Iterable<Sheet> { | |||
*/ | |||
void removeName(String name); | |||
/** | |||
* Remove a defined name | |||
* | |||
* @param name the name of the defined name | |||
*/ | |||
void removeName(Name name); | |||
/** | |||
* Adds the linking required to allow formulas referencing | |||
* the specified external workbook to be added to this one. |
@@ -27,19 +27,13 @@ import org.apache.commons.logging.LogFactory; | |||
* developers to write log calls, while simultaneously making those | |||
* calls as cheap as possible by performing lazy evaluation of the log | |||
* message.<p> | |||
* | |||
* @author Marc Johnson (mjohnson at apache dot org) | |||
* @author Glen Stampoultzis (glens at apache.org) | |||
* @author Nicola Ken Barozzi (nicolaken at apache.org) | |||
*/ | |||
public class CommonsLogger extends POILogger | |||
{ | |||
private static LogFactory _creator = LogFactory.getFactory(); | |||
private Log log = null; | |||
@Override | |||
public void initialize(final String cat) | |||
{ | |||
this.log = _creator.getInstance(cat); | |||
@@ -51,6 +45,7 @@ public class CommonsLogger extends POILogger | |||
* @param level One of DEBUG, INFO, WARN, ERROR, FATAL | |||
* @param obj1 The object to log. | |||
*/ | |||
@Override | |||
public void log(final int level, final Object obj1) | |||
{ | |||
if(level==FATAL) | |||
@@ -104,6 +99,7 @@ public class CommonsLogger extends POILogger | |||
* @param obj1 The object to log. This is converted to a string. | |||
* @param exception An exception to be logged | |||
*/ | |||
@Override | |||
public void log(final int level, final Object obj1, | |||
final Throwable exception) | |||
{ | |||
@@ -175,7 +171,7 @@ public class CommonsLogger extends POILogger | |||
* | |||
* @param level One of DEBUG, INFO, WARN, ERROR, FATAL | |||
*/ | |||
@Override | |||
public boolean check(final int level) | |||
{ | |||
if(level==FATAL) |
@@ -22,14 +22,10 @@ package org.apache.poi.util; | |||
* developers to write log calls, while simultaneously making those | |||
* calls as cheap as possible by performing lazy evaluation of the log | |||
* message.<p> | |||
* | |||
* @author Marc Johnson (mjohnson at apache dot org) | |||
* @author Glen Stampoultzis (glens at apache.org) | |||
* @author Nicola Ken Barozzi (nicolaken at apache.org) | |||
*/ | |||
public class NullLogger extends POILogger { | |||
@Override | |||
public void initialize(final String cat){ | |||
public void initialize(final String cat) { | |||
// do nothing | |||
} | |||
@@ -41,8 +37,7 @@ public class NullLogger extends POILogger { | |||
*/ | |||
@Override | |||
public void log(final int level, final Object obj1) | |||
{ | |||
public void log(final int level, final Object obj1) { | |||
// do nothing | |||
} | |||
@@ -53,6 +48,7 @@ public class NullLogger extends POILogger { | |||
* @param obj1 The object to log. This is converted to a string. | |||
* @param exception An exception to be logged | |||
*/ | |||
@Override | |||
public void log(int level, Object obj1, final Throwable exception) { | |||
// do nothing | |||
} |
@@ -24,15 +24,12 @@ package org.apache.poi.util; | |||
* developers to write log calls, while simultaneously making those | |||
* calls as cheap as possible by performing lazy evaluation of the log | |||
* message. | |||
* | |||
* @author Marc Johnson (mjohnson at apache dot org) | |||
* @author Glen Stampoultzis (glens at apache.org) | |||
* @author Nicola Ken Barozzi (nicolaken at apache.org) | |||
*/ | |||
public class SystemOutLogger extends POILogger | |||
{ | |||
private String _cat; | |||
@Override | |||
public void initialize(final String cat) | |||
{ | |||
this._cat=cat; | |||
@@ -44,7 +41,7 @@ public class SystemOutLogger extends POILogger | |||
* @param level One of DEBUG, INFO, WARN, ERROR, FATAL | |||
* @param obj1 The object to log. | |||
*/ | |||
@Override | |||
public void log(final int level, final Object obj1) | |||
{ | |||
log(level, obj1, null); | |||
@@ -57,6 +54,7 @@ public class SystemOutLogger extends POILogger | |||
* @param obj1 The object to log. This is converted to a string. | |||
* @param exception An exception to be logged | |||
*/ | |||
@Override | |||
@SuppressForbidden("uses printStackTrace") | |||
public void log(final int level, final Object obj1, | |||
final Throwable exception) { | |||
@@ -78,6 +76,7 @@ public class SystemOutLogger extends POILogger | |||
* @see #ERROR | |||
* @see #FATAL | |||
*/ | |||
@Override | |||
public boolean check(final int level) | |||
{ | |||
int currentLevel; |
@@ -193,8 +193,12 @@ public abstract class POIXMLDocument extends POIXMLDocumentPart implements Close | |||
/** | |||
* Closes the underlying {@link OPCPackage} from which this | |||
* document was read, if there is one | |||
* | |||
* @throws IOException for writable packages, if an IO exception occur during the saving process. | |||
* | |||
* <p>Once this has been called, no further | |||
* operations, updates or reads should be performed on the | |||
* document. | |||
* | |||
* @throws IOException for writable packages, if an IO exception occur during the saving process. | |||
*/ | |||
@Override | |||
public void close() throws IOException { |
@@ -382,8 +382,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable { | |||
} | |||
// Creates a new package | |||
OPCPackage pkg = null; | |||
pkg = new ZipPackage(); | |||
OPCPackage pkg = new ZipPackage(); | |||
pkg.originalPackagePath = file.getAbsolutePath(); | |||
configurePackage(pkg); | |||
@@ -391,8 +390,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable { | |||
} | |||
public static OPCPackage create(OutputStream output) { | |||
OPCPackage pkg = null; | |||
pkg = new ZipPackage(); | |||
OPCPackage pkg = new ZipPackage(); | |||
pkg.originalPackagePath = null; | |||
pkg.output = output; | |||
@@ -542,7 +540,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable { | |||
// Create the thumbnail part name | |||
String contentType = ContentTypes | |||
.getContentTypeFromFileExtension(filename); | |||
PackagePartName thumbnailPartName = null; | |||
PackagePartName thumbnailPartName; | |||
try { | |||
thumbnailPartName = PackagingURIHelper.createPartName("/docProps/" | |||
+ filename); |
@@ -29,10 +29,7 @@ import java.util.TreeMap; | |||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; | |||
import org.apache.poi.openxml4j.exceptions.InvalidOperationException; | |||
import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException; | |||
import org.apache.poi.openxml4j.opc.OPCPackage; | |||
import org.apache.poi.openxml4j.opc.PackagePart; | |||
import org.apache.poi.openxml4j.opc.PackagePartName; | |||
import org.apache.poi.openxml4j.opc.PackagingURIHelper; | |||
import org.apache.poi.openxml4j.opc.*; | |||
import org.apache.poi.util.DocumentHelper; | |||
import org.w3c.dom.Document; | |||
import org.w3c.dom.Element; | |||
@@ -54,7 +51,7 @@ public abstract class ContentTypeManager { | |||
/** | |||
* Content type namespace | |||
*/ | |||
public static final String TYPES_NAMESPACE_URI = "http://schemas.openxmlformats.org/package/2006/content-types"; | |||
public static final String TYPES_NAMESPACE_URI = PackageNamespaces.CONTENT_TYPES; | |||
/* Xml elements in content type part */ | |||
@@ -304,13 +304,13 @@ implements XSLFShapeContainer, GroupShape<XSLFShape,XSLFTextParagraph> { | |||
@Override | |||
public boolean getFlipHorizontal(){ | |||
CTGroupTransform2D xfrm = getXfrm(); | |||
return (xfrm == null || !xfrm.isSetFlipH()) ? false : xfrm.getFlipH(); | |||
return !(xfrm == null || !xfrm.isSetFlipH()) && xfrm.getFlipH(); | |||
} | |||
@Override | |||
public boolean getFlipVertical(){ | |||
CTGroupTransform2D xfrm = getXfrm(); | |||
return (xfrm == null || !xfrm.isSetFlipV()) ? false : xfrm.getFlipV(); | |||
return !(xfrm == null || !xfrm.isSetFlipV()) && xfrm.getFlipV(); | |||
} | |||
@Override | |||
@@ -333,7 +333,7 @@ implements XSLFShapeContainer, GroupShape<XSLFShape,XSLFTextParagraph> { | |||
// recursively update each shape | |||
for(XSLFShape shape : gr.getShapes()) { | |||
XSLFShape newShape = null; | |||
XSLFShape newShape; | |||
if (shape instanceof XSLFTextBox) { | |||
newShape = createTextBox(); | |||
} else if (shape instanceof XSLFAutoShape) { |
@@ -41,7 +41,6 @@ import javax.xml.validation.Schema; | |||
import javax.xml.validation.SchemaFactory; | |||
import javax.xml.validation.Validator; | |||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; | |||
import org.apache.poi.ss.usermodel.CellType; | |||
import org.apache.poi.ss.usermodel.DateUtil; | |||
import org.apache.poi.util.DocumentHelper; | |||
@@ -55,7 +54,6 @@ import org.apache.poi.xssf.usermodel.XSSFSheet; | |||
import org.apache.poi.xssf.usermodel.XSSFTable; | |||
import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell; | |||
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr; | |||
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STXmlDataType; | |||
import org.w3c.dom.Document; | |||
import org.w3c.dom.Element; | |||
import org.w3c.dom.NamedNodeMap; | |||
@@ -117,8 +115,7 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
* @param validate if true, validates the XML againts the XML Schema | |||
* @throws SAXException | |||
* @throws ParserConfigurationException | |||
* @throws TransformerException | |||
* @throws InvalidFormatException | |||
* @throws TransformerException | |||
*/ | |||
public void exportToXML(OutputStream os, String encoding, boolean validate) throws SAXException, ParserConfigurationException, TransformerException{ | |||
List<XSSFSingleXmlCell> singleXMLCells = map.getRelatedSingleXMLCell(); | |||
@@ -128,10 +125,10 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
Document doc = DocumentHelper.createDocument(); | |||
Element root = null; | |||
final Element root; | |||
if (isNamespaceDeclared()) { | |||
root=doc.createElementNS(getNamespace(),rootElement); | |||
root = doc.createElementNS(getNamespace(),rootElement); | |||
} else { | |||
root = doc.createElementNS("", rootElement); | |||
} | |||
@@ -152,7 +149,6 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
tableMappings.put(commonXPath, table); | |||
} | |||
Collections.sort(xpaths,this); | |||
for(String xpath : xpaths) { | |||
@@ -167,8 +163,7 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
XSSFCell cell = simpleXmlCell.getReferencedCell(); | |||
if (cell!=null) { | |||
Node currentNode = getNodeByXPath(xpath,doc.getFirstChild(),doc,false); | |||
STXmlDataType.Enum dataType = simpleXmlCell.getXmlDataType(); | |||
mapCellOnNode(cell,currentNode,dataType); | |||
mapCellOnNode(cell,currentNode); | |||
//remove nodes which are empty in order to keep the output xml valid | |||
if("".equals(currentNode.getTextContent()) && currentNode.getParentNode() != null) { | |||
@@ -202,22 +197,15 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
XSSFXmlColumnPr pointer = tableColumns.get(j-startColumnIndex); | |||
String localXPath = pointer.getLocalXPath(); | |||
Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false); | |||
STXmlDataType.Enum dataType = pointer.getXmlDataType(); | |||
mapCellOnNode(cell,currentNode,dataType); | |||
mapCellOnNode(cell,currentNode); | |||
} | |||
} | |||
} | |||
} | |||
} else { | |||
} /*else { | |||
// TODO: implement filtering management in xpath | |||
} | |||
}*/ | |||
} | |||
boolean isValid = true; | |||
@@ -225,8 +213,6 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
isValid =isValid(doc); | |||
} | |||
if (isValid) { | |||
///////////////// | |||
@@ -275,7 +261,7 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
} | |||
private void mapCellOnNode(XSSFCell cell, Node node, STXmlDataType.Enum outputDataType) { | |||
private void mapCellOnNode(XSSFCell cell, Node node) { | |||
String value =""; | |||
switch (cell.getCellTypeEnum()) { | |||
@@ -349,11 +335,7 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
} | |||
currentNode = selectedNode; | |||
} else { | |||
Node attribute = createAttribute(doc, currentNode, axisName); | |||
currentNode = attribute; | |||
currentNode = createAttribute(doc, currentNode, axisName); | |||
} | |||
} | |||
return currentNode; | |||
@@ -421,12 +403,11 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
for(int i =1;i <minLenght; i++) { | |||
String leftElementName =leftTokens[i]; | |||
String leftElementName = leftTokens[i]; | |||
String rightElementName = rightTokens[i]; | |||
if (leftElementName.equals(rightElementName)) { | |||
Node complexType = getComplexTypeForElement(leftElementName, xmlSchema,localComplexTypeRootNode); | |||
localComplexTypeRootNode = complexType; | |||
localComplexTypeRootNode = getComplexTypeForElement(leftElementName, xmlSchema, localComplexTypeRootNode); | |||
} else { | |||
int leftIndex = indexOfElementInComplexType(leftElementName,localComplexTypeRootNode); | |||
int rightIndex = indexOfElementInComplexType(rightElementName,localComplexTypeRootNode); | |||
@@ -436,9 +417,9 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
}if ( leftIndex > rightIndex) { | |||
return 1; | |||
} | |||
} else { | |||
} /*else { | |||
// NOTE: the xpath doesn't match correctly in the schema | |||
} | |||
}*/ | |||
} | |||
} | |||
@@ -483,7 +464,7 @@ public class XSSFExportToXml implements Comparator<String>{ | |||
// Note: we expect that all the complex types are defined at root level | |||
Node complexTypeNode = null; | |||
if (!"".equals(complexTypeName)) { | |||
complexTypeNode = getComplexTypeNodeFromSchemaChildren(xmlSchema, complexTypeNode, complexTypeName); | |||
complexTypeNode = getComplexTypeNodeFromSchemaChildren(xmlSchema, null, complexTypeName); | |||
} | |||
return complexTypeNode; |
@@ -338,7 +338,11 @@ public class SXSSFCell implements Cell { | |||
} | |||
if(_value.getType()==CellType.FORMULA) | |||
((StringFormulaValue)_value).setPreEvaluatedValue(value); | |||
if(_value instanceof NumericFormulaValue) { | |||
((NumericFormulaValue) _value).setPreEvaluatedValue(Double.parseDouble(value)); | |||
} else { | |||
((StringFormulaValue) _value).setPreEvaluatedValue(value); | |||
} | |||
else | |||
((PlainStringValue)_value).setValue(value); | |||
} else { | |||
@@ -956,6 +960,7 @@ public class SXSSFCell implements Cell { | |||
} | |||
/*package*/ void setFormulaType(CellType type) | |||
{ | |||
Value prevValue = _value; | |||
switch(type) | |||
{ | |||
case NUMERIC: | |||
@@ -983,7 +988,13 @@ public class SXSSFCell implements Cell { | |||
throw new IllegalArgumentException("Illegal type " + type); | |||
} | |||
} | |||
// if we had a Formula before, we should copy over the _value of the formula | |||
if(prevValue instanceof FormulaValue) { | |||
((FormulaValue)_value)._value = ((FormulaValue)prevValue)._value; | |||
} | |||
} | |||
//TODO: implement this correctly | |||
@NotImplemented | |||
/*package*/ CellType computeTypeFromFormula(String formula) |
@@ -893,8 +893,11 @@ public class SXSSFWorkbook implements Workbook { | |||
/** | |||
* Closes the underlying {@link XSSFWorkbook} and {@link OPCPackage} | |||
* on which this Workbook is based, if any. Has no effect on Workbooks | |||
* created from scratch. | |||
* on which this Workbook is based, if any. | |||
* | |||
* <p>Once this has been called, no further | |||
* operations, updates or reads should be performed on the | |||
* Workbook. | |||
*/ | |||
@Override | |||
public void close() throws IOException { | |||
@@ -1003,12 +1006,25 @@ public class SXSSFWorkbook implements Workbook { | |||
return _wb.getNames(name); | |||
} | |||
/** | |||
* Returns all defined names | |||
* | |||
* @return all defined names | |||
*/ | |||
@Override | |||
public List<? extends Name> getAllNames() | |||
{ | |||
return _wb.getAllNames(); | |||
} | |||
/** | |||
* @param nameIndex position of the named range (0-based) | |||
* @return the defined name at the specified index | |||
* @throws IllegalArgumentException if the supplied index is invalid | |||
* @deprecated 3.16. New projects should avoid accessing named ranges by index. | |||
*/ | |||
@Override | |||
@Deprecated | |||
public Name getNameAt(int nameIndex) | |||
{ | |||
return _wb.getNameAt(nameIndex); | |||
@@ -1033,8 +1049,12 @@ public class SXSSFWorkbook implements Workbook { | |||
* | |||
* @param name the name of the defined name | |||
* @return zero based index of the defined name. <code>-1</code> if not found. | |||
* | |||
* @deprecated 3.16. New projects should avoid accessing named ranges by index. | |||
* Use {@link #getName(String)} instead. | |||
*/ | |||
@Override | |||
@Deprecated | |||
public int getNameIndex(String name) | |||
{ | |||
return _wb.getNameIndex(name); | |||
@@ -1044,8 +1064,11 @@ public class SXSSFWorkbook implements Workbook { | |||
* Remove the defined name at the specified index | |||
* | |||
* @param index named range index (0 based) | |||
* | |||
* @deprecated 3.16. New projects should use {@link #removeName(Name)}. | |||
*/ | |||
@Override | |||
@Deprecated | |||
public void removeName(int index) | |||
{ | |||
_wb.removeName(index); | |||
@@ -1054,10 +1077,24 @@ public class SXSSFWorkbook implements Workbook { | |||
/** | |||
* Remove a defined name by name | |||
* | |||
* @param name the name of the defined name | |||
* @param name the name of the defined name | |||
* | |||
* @deprecated 3.16. New projects should use {@link #removeName(Name)}. | |||
*/ | |||
@Override | |||
@Deprecated | |||
public void removeName(String name) | |||
{ | |||
_wb.removeName(name); | |||
} | |||
/** | |||
* Remove the given defined name | |||
* | |||
* @param name the name to remove | |||
*/ | |||
@Override | |||
public void removeName(Name name) | |||
{ | |||
_wb.removeName(name); | |||
} |
@@ -232,7 +232,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork | |||
// Otherwise, try it as a named range | |||
if (sheet == null) { | |||
if (_uBook.getNameIndex(name) > -1) { | |||
if (!_uBook.getNames(name).isEmpty()) { | |||
return new NameXPxg(null, name); | |||
} | |||
return null; |
@@ -17,12 +17,9 @@ | |||
package org.apache.poi.xssf.usermodel; | |||
import java.util.Map; | |||
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment; | |||
import org.apache.poi.ss.formula.BaseFormulaEvaluator; | |||
import org.apache.poi.ss.formula.EvaluationCell; | |||
import org.apache.poi.ss.formula.WorkbookEvaluator; | |||
import org.apache.poi.ss.formula.WorkbookEvaluatorProvider; | |||
import org.apache.poi.ss.formula.eval.BoolEval; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.NumberEval; | |||
@@ -31,28 +28,16 @@ import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.usermodel.Cell; | |||
import org.apache.poi.ss.usermodel.CellType; | |||
import org.apache.poi.ss.usermodel.CellValue; | |||
import org.apache.poi.ss.usermodel.FormulaEvaluator; | |||
import org.apache.poi.util.Internal; | |||
/** | |||
* Internal POI use only - parent of XSSF and SXSSF formula evaluators | |||
*/ | |||
public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, WorkbookEvaluatorProvider { | |||
private WorkbookEvaluator _bookEvaluator; | |||
public abstract class BaseXSSFFormulaEvaluator extends BaseFormulaEvaluator { | |||
protected BaseXSSFFormulaEvaluator(WorkbookEvaluator bookEvaluator) { | |||
_bookEvaluator = bookEvaluator; | |||
super(bookEvaluator); | |||
} | |||
/** | |||
* Should be called whenever there are major changes (e.g. moving sheets) to input cells | |||
* in the evaluated workbook. | |||
* Failure to call this method after changing cell values will cause incorrect behaviour | |||
* of the evaluate~ methods of this class | |||
*/ | |||
public void clearAllCachedResultValues() { | |||
_bookEvaluator.clearAllCachedResultValues(); | |||
} | |||
public void notifySetFormula(Cell cell) { | |||
_bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell)); | |||
} | |||
@@ -63,60 +48,6 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work | |||
_bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell)); | |||
} | |||
/** | |||
* If cell contains a formula, the formula is evaluated and returned, | |||
* else the CellValue simply copies the appropriate cell value from | |||
* the cell and also its cell type. This method should be preferred over | |||
* evaluateInCell() when the call should not modify the contents of the | |||
* original cell. | |||
* @param cell | |||
*/ | |||
public CellValue evaluate(Cell cell) { | |||
if (cell == null) { | |||
return null; | |||
} | |||
switch (cell.getCellTypeEnum()) { | |||
case BOOLEAN: | |||
return CellValue.valueOf(cell.getBooleanCellValue()); | |||
case ERROR: | |||
return CellValue.getError(cell.getErrorCellValue()); | |||
case FORMULA: | |||
return evaluateFormulaCellValue(cell); | |||
case NUMERIC: | |||
return new CellValue(cell.getNumericCellValue()); | |||
case STRING: | |||
return new CellValue(cell.getRichStringCellValue().getString()); | |||
case BLANK: | |||
return null; | |||
default: | |||
throw new IllegalStateException("Bad cell type (" + cell.getCellTypeEnum() + ")"); | |||
} | |||
} | |||
/** | |||
* If cell contains formula, it evaluates the formula, | |||
* and saves the result of the formula. The cell | |||
* remains as a formula cell. | |||
* Else if cell does not contain formula, this method leaves | |||
* the cell unchanged. | |||
* Note that the type of the formula result is returned, | |||
* so you know what kind of value is also stored with | |||
* the formula. | |||
* <pre> | |||
* int evaluatedCellType = evaluator.evaluateFormulaCell(cell); | |||
* </pre> | |||
* Be aware that your cell will hold both the formula, | |||
* and the result. If you want the cell replaced with | |||
* the result of the formula, use {@link #evaluate(org.apache.poi.ss.usermodel.Cell)} } | |||
* @param cell The cell to evaluate | |||
* @return The type of the formula result (the cell's type remains as CellType.FORMULA however) | |||
*/ | |||
public int evaluateFormulaCell(Cell cell) { | |||
return evaluateFormulaCellEnum(cell).getCode(); | |||
} | |||
/** | |||
* If cell contains formula, it evaluates the formula, | |||
* and saves the result of the formula. The cell | |||
@@ -164,27 +95,6 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work | |||
setCellValue(cell, cv); | |||
} | |||
} | |||
private static void setCellType(Cell cell, CellValue cv) { | |||
CellType cellType = cv.getCellType(); | |||
switch (cellType) { | |||
case BOOLEAN: | |||
case ERROR: | |||
case NUMERIC: | |||
case STRING: | |||
cell.setCellType(cellType); | |||
return; | |||
case BLANK: | |||
// never happens - blanks eventually get translated to zero | |||
throw new IllegalArgumentException("This should never happen. Blanks eventually get translated to zero."); | |||
case FORMULA: | |||
// this will never happen, we have already evaluated the formula | |||
throw new IllegalArgumentException("This should never happen. Formulas should have already been evaluated."); | |||
default: | |||
throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); | |||
} | |||
} | |||
private static void setCellValue(Cell cell, CellValue cv) { | |||
CellType cellType = cv.getCellType(); | |||
@@ -218,7 +128,7 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work | |||
/** | |||
* Returns a CellValue wrapper around the supplied ValueEval instance. | |||
*/ | |||
private CellValue evaluateFormulaCellValue(Cell cell) { | |||
protected CellValue evaluateFormulaCellValue(Cell cell) { | |||
EvaluationCell evalCell = toEvaluationCell(cell); | |||
ValueEval eval = _bookEvaluator.evaluate(evalCell); | |||
if (eval instanceof NumberEval) { | |||
@@ -238,22 +148,4 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work | |||
} | |||
throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); | |||
} | |||
public void setupReferencedWorkbooks(Map<String, FormulaEvaluator> evaluators) { | |||
CollaboratingWorkbooksEnvironment.setupFormulaEvaluator(evaluators); | |||
} | |||
public WorkbookEvaluator _getWorkbookEvaluator() { | |||
return _bookEvaluator; | |||
} | |||
/** {@inheritDoc} */ | |||
public void setIgnoreMissingWorkbooks(boolean ignore){ | |||
_bookEvaluator.setIgnoreMissingWorkbooks(ignore); | |||
} | |||
/** {@inheritDoc} */ | |||
public void setDebugEvaluationOutputForNextEval(boolean value){ | |||
_bookEvaluator.setDebugEvaluationOutputForNextEval(value); | |||
} | |||
} |
@@ -17,7 +17,7 @@ | |||
package org.apache.poi.xssf.usermodel; | |||
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; | |||
import org.apache.poi.ss.formula.BaseFormulaEvaluator; | |||
import org.apache.poi.ss.formula.EvaluationCell; | |||
import org.apache.poi.ss.formula.IStabilityClassifier; | |||
import org.apache.poi.ss.formula.WorkbookEvaluator; | |||
@@ -88,7 +88,7 @@ public final class XSSFFormulaEvaluator extends BaseXSSFFormulaEvaluator { | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
public static void evaluateAllFormulaCells(XSSFWorkbook wb) { | |||
HSSFFormulaEvaluator.evaluateAllFormulaCells(wb); | |||
BaseFormulaEvaluator.evaluateAllFormulaCells(wb); | |||
} | |||
/** | |||
* Loops over all cells in all sheets of the supplied | |||
@@ -102,7 +102,7 @@ public final class XSSFFormulaEvaluator extends BaseXSSFFormulaEvaluator { | |||
* cells, and calling evaluateFormulaCell on each one. | |||
*/ | |||
public void evaluateAll() { | |||
HSSFFormulaEvaluator.evaluateAllFormulaCells(_book); | |||
evaluateAllFormulaCells(_book, this); | |||
} | |||
/** |
@@ -167,19 +167,18 @@ public final class XSSFName implements Name { | |||
public void setNameName(String name) { | |||
validateName(name); | |||
String oldName = getNameName(); | |||
int sheetIndex = getSheetIndex(); | |||
int numberOfNames = _workbook.getNumberOfNames(); | |||
//Check to ensure no other names have the same case-insensitive name at the same scope | |||
for (int i = 0; i < numberOfNames; i++) { | |||
XSSFName nm = _workbook.getNameAt(i); | |||
if ((nm != this) | |||
&& name.equalsIgnoreCase(nm.getNameName()) | |||
&& (sheetIndex == nm.getSheetIndex())) { | |||
for (XSSFName foundName : _workbook.getNames(name)) { | |||
if (foundName.getSheetIndex() == sheetIndex && foundName != this) { | |||
String msg = "The "+(sheetIndex == -1 ? "workbook" : "sheet")+" already contains this name: " + name; | |||
throw new IllegalArgumentException(msg); | |||
} | |||
} | |||
_ctName.setName(name); | |||
//Need to update the name -> named ranges map | |||
_workbook.updateName(this, oldName); | |||
} | |||
public String getRefersToFormula() { |
@@ -18,8 +18,8 @@ | |||
package org.apache.poi.xssf.usermodel; | |||
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; | |||
import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.setPassword; | |||
import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; | |||
import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.setPassword; | |||
import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword; | |||
import java.io.IOException; | |||
import java.io.InputStream; |
@@ -18,8 +18,8 @@ | |||
package org.apache.poi.xssf.usermodel; | |||
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; | |||
import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.setPassword; | |||
import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; | |||
import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.setPassword; | |||
import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
@@ -29,16 +29,20 @@ import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.Iterator; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.Map; | |||
import java.util.NoSuchElementException; | |||
import java.util.regex.Pattern; | |||
import javax.xml.namespace.QName; | |||
import org.apache.commons.collections4.ListValuedMap; | |||
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; | |||
import org.apache.poi.POIXMLDocument; | |||
import org.apache.poi.POIXMLDocumentPart; | |||
import org.apache.poi.POIXMLException; | |||
@@ -59,6 +63,7 @@ import org.apache.poi.ss.formula.SheetNameFormatter; | |||
import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; | |||
import org.apache.poi.ss.formula.udf.IndexedUDFFinder; | |||
import org.apache.poi.ss.formula.udf.UDFFinder; | |||
import org.apache.poi.ss.usermodel.Name; | |||
import org.apache.poi.ss.usermodel.Row; | |||
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; | |||
import org.apache.poi.ss.usermodel.Sheet; | |||
@@ -140,6 +145,11 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
*/ | |||
private List<XSSFSheet> sheets; | |||
/** | |||
* this holds the XSSFName objects attached to this workbook, keyed by lower-case name | |||
*/ | |||
private ListValuedMap<String, XSSFName> namedRangesByName; | |||
/** | |||
* this holds the XSSFName objects attached to this workbook | |||
*/ | |||
@@ -442,6 +452,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
stylesSource.setWorkbook(this); | |||
namedRanges = new ArrayList<XSSFName>(); | |||
namedRangesByName = new ArrayListValuedHashMap<String, XSSFName>(); | |||
sheets = new ArrayList<XSSFSheet>(); | |||
pivotTables = new ArrayList<XSSFPivotTable>(); | |||
} | |||
@@ -733,8 +744,13 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
public XSSFName createName() { | |||
CTDefinedName ctName = CTDefinedName.Factory.newInstance(); | |||
ctName.setName(""); | |||
return createAndStoreName(ctName); | |||
} | |||
private XSSFName createAndStoreName(CTDefinedName ctName) { | |||
XSSFName name = new XSSFName(ctName, this); | |||
namedRanges.add(name); | |||
namedRangesByName.put(ctName.getName().toLowerCase(Locale.ENGLISH), name); | |||
return name; | |||
} | |||
@@ -938,28 +954,47 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
return stylesSource.getFontAt(idx); | |||
} | |||
/** | |||
* Get the first named range with the given name. | |||
* | |||
* Note: names of named ranges are not unique as they are scoped by sheet. | |||
* {@link #getNames(String name)} returns all named ranges with the given name. | |||
* | |||
* @param name named range name | |||
* @return XSSFName with the given name. <code>null</code> is returned no named range could be found. | |||
*/ | |||
@Override | |||
public XSSFName getName(String name) { | |||
int nameIndex = getNameIndex(name); | |||
if (nameIndex < 0) { | |||
Collection<XSSFName> list = getNames(name); | |||
if (list.isEmpty()) { | |||
return null; | |||
} | |||
return namedRanges.get(nameIndex); | |||
return list.iterator().next(); | |||
} | |||
/** | |||
* Get the named ranges with the given name. | |||
* <i>Note:</i>Excel named ranges are case-insensitive and | |||
* this method performs a case-insensitive search. | |||
* | |||
* @param name named range name | |||
* @return list of XSSFNames with the given name. An empty list if no named ranges could be found | |||
*/ | |||
@Override | |||
public List<XSSFName> getNames(String name) { | |||
List<XSSFName> names = new ArrayList<XSSFName>(); | |||
for(XSSFName nr : namedRanges) { | |||
if(nr.getNameName().equals(name)) { | |||
names.add(nr); | |||
} | |||
} | |||
return names; | |||
return Collections.unmodifiableList(namedRangesByName.get(name.toLowerCase(Locale.ENGLISH))); | |||
} | |||
/** | |||
* Get the named range at the given index. | |||
* | |||
* @param nameIndex the index of the named range | |||
* @return the XSSFName at the given index | |||
* | |||
* @deprecated 3.16. New projects should avoid accessing named ranges by index. | |||
*/ | |||
@Override | |||
@Deprecated | |||
public XSSFName getNameAt(int nameIndex) { | |||
int nNames = namedRanges.size(); | |||
if (nNames < 1) { | |||
@@ -973,21 +1008,30 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
} | |||
/** | |||
* Gets the named range index by his name | |||
* <i>Note:</i>Excel named ranges are case-insensitive and | |||
* this method performs a case-insensitive search. | |||
* Get a list of all the named ranges in the workbook. | |||
* | |||
* @return list of XSSFNames in the workbook | |||
*/ | |||
@Override | |||
public List<XSSFName> getAllNames() { | |||
return Collections.unmodifiableList(namedRanges); | |||
} | |||
/** | |||
* Gets the named range index by name. | |||
* | |||
* @param name named range name | |||
* @return named range index | |||
* @return named range index. <code>-1</code> is returned if no named ranges could be found. | |||
* | |||
* @deprecated 3.16. New projects should avoid accessing named ranges by index. | |||
* Use {@link #getName(String)} instead. | |||
*/ | |||
@Override | |||
@Deprecated | |||
public int getNameIndex(String name) { | |||
int i = 0; | |||
for(XSSFName nr : namedRanges) { | |||
if(nr.getNameName().equals(name)) { | |||
return i; | |||
} | |||
i++; | |||
XSSFName nm = getName(name); | |||
if (nm != null) { | |||
return namedRanges.indexOf(nm); | |||
} | |||
return -1; | |||
} | |||
@@ -1258,22 +1302,40 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
return getPackagePart().getContentType().equals(XSSFRelation.MACROS_WORKBOOK.getContentType()); | |||
} | |||
/** | |||
* Remove the named range at the given index. | |||
* | |||
* @param nameIndex the index of the named range name to remove | |||
* | |||
* @deprecated 3.16. New projects should use {@link #removeName(Name)}. | |||
*/ | |||
@Override | |||
@Deprecated | |||
public void removeName(int nameIndex) { | |||
namedRanges.remove(nameIndex); | |||
removeName(getNameAt(nameIndex)); | |||
} | |||
/** | |||
* Remove the first named range found with the given name. | |||
* | |||
* Note: names of named ranges are not unique (name + sheet | |||
* index is unique), so {@link #removeName(Name)} should | |||
* be used if possible. | |||
* | |||
* @param name the named range name to remove | |||
* | |||
* @throws IllegalArgumentException if no named range could be found | |||
* | |||
* @deprecated 3.16. New projects should use {@link #removeName(Name)}. | |||
*/ | |||
@Override | |||
@Deprecated | |||
public void removeName(String name) { | |||
int idx = 0; | |||
for (XSSFName nm : namedRanges) { | |||
if(nm.getNameName().equalsIgnoreCase(name)) { | |||
removeName(idx); | |||
return; | |||
} | |||
idx++; | |||
List<XSSFName> names = namedRangesByName.get(name.toLowerCase(Locale.ENGLISH)); | |||
if (names.isEmpty()) { | |||
throw new IllegalArgumentException("Named range was not found: " + name); | |||
} | |||
throw new IllegalArgumentException("Named range was not found: " + name); | |||
removeName(names.get(0)); | |||
} | |||
@@ -1282,13 +1344,24 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
* (name + sheet index is unique), this method is more accurate. | |||
* | |||
* @param name the name to remove. | |||
* | |||
* @throws IllegalArgumentException if the named range is not a part of this XSSFWorkbook | |||
*/ | |||
void removeName(XSSFName name) { | |||
if (!namedRanges.remove(name)) { | |||
@Override | |||
public void removeName(Name name) { | |||
if (!namedRangesByName.removeMapping(name.getNameName().toLowerCase(Locale.ENGLISH), name) | |||
|| !namedRanges.remove(name)) { | |||
throw new IllegalArgumentException("Name was not found: " + name); | |||
} | |||
} | |||
void updateName(XSSFName name, String oldName) { | |||
if (!namedRangesByName.removeMapping(oldName.toLowerCase(Locale.ENGLISH), name)) { | |||
throw new IllegalArgumentException("Name was not found: " + name); | |||
} | |||
namedRangesByName.put(name.getNameName().toLowerCase(Locale.ENGLISH), name); | |||
} | |||
/** | |||
* Delete the printarea for the sheet specified | |||
@@ -1297,13 +1370,9 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
*/ | |||
@Override | |||
public void removePrintArea(int sheetIndex) { | |||
int cont = 0; | |||
for (XSSFName name : namedRanges) { | |||
if (name.getNameName().equals(XSSFName.BUILTIN_PRINT_AREA) && name.getSheetIndex() == sheetIndex) { | |||
namedRanges.remove(cont); | |||
break; | |||
} | |||
cont++; | |||
XSSFName name = getBuiltInName(XSSFName.BUILTIN_PRINT_AREA, sheetIndex); | |||
if (name != null) { | |||
removeName(name); | |||
} | |||
} | |||
@@ -1369,17 +1438,20 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
} | |||
//adjust indices of names ranges | |||
for (Iterator<XSSFName> it = namedRanges.iterator(); it.hasNext();) { | |||
XSSFName nm = it.next(); | |||
List<XSSFName> toRemove = new ArrayList<XSSFName>(); | |||
for (XSSFName nm : namedRanges) { | |||
CTDefinedName ct = nm.getCTName(); | |||
if(!ct.isSetLocalSheetId()) continue; | |||
if (ct.getLocalSheetId() == index) { | |||
it.remove(); | |||
toRemove.add(nm); | |||
} else if (ct.getLocalSheetId() > index){ | |||
// Bump down by one, so still points at the same sheet | |||
ct.setLocalSheetId(ct.getLocalSheetId()-1); | |||
} | |||
} | |||
for (XSSFName nm : toRemove) { | |||
removeName(nm); | |||
} | |||
} | |||
/** | |||
@@ -1514,8 +1586,8 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
} | |||
XSSFName getBuiltInName(String builtInCode, int sheetNumber) { | |||
for (XSSFName name : namedRanges) { | |||
if (name.getNameName().equalsIgnoreCase(builtInCode) && name.getSheetIndex() == sheetNumber) { | |||
for (XSSFName name : namedRangesByName.get(builtInCode.toLowerCase(Locale.ENGLISH))) { | |||
if (name.getSheetIndex() == sheetNumber) { | |||
return name; | |||
} | |||
} | |||
@@ -1537,15 +1609,12 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
nameRecord.setName(builtInName); | |||
nameRecord.setLocalSheetId(sheetNumber); | |||
XSSFName name = new XSSFName(nameRecord, this); | |||
for (XSSFName nr : namedRanges) { | |||
if (nr.equals(name)) | |||
throw new POIXMLException("Builtin (" + builtInName | |||
+ ") already exists for sheet (" + sheetNumber + ")"); | |||
if (getBuiltInName(builtInName, sheetNumber) != null) { | |||
throw new POIXMLException("Builtin (" + builtInName | |||
+ ") already exists for sheet (" + sheetNumber + ")"); | |||
} | |||
namedRanges.add(name); | |||
return name; | |||
return createAndStoreName(nameRecord); | |||
} | |||
/** | |||
@@ -1665,10 +1734,11 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { | |||
} | |||
private void reprocessNamedRanges() { | |||
namedRangesByName = new ArrayListValuedHashMap<String, XSSFName>(); | |||
namedRanges = new ArrayList<XSSFName>(); | |||
if(workbook.isSetDefinedNames()) { | |||
for(CTDefinedName ctName : workbook.getDefinedNames().getDefinedNameArray()) { | |||
namedRanges.add(new XSSFName(ctName, this)); | |||
createAndStoreName(ctName); | |||
} | |||
} | |||
} |
@@ -65,9 +65,7 @@ public final class XSSFFormulaUtils { | |||
*/ | |||
public void updateSheetName(final int sheetIndex, final String oldName, final String newName) { | |||
// update named ranges | |||
final int numberOfNames = _wb.getNumberOfNames(); | |||
for (int i = 0; i < numberOfNames; i++) { | |||
XSSFName nm = _wb.getNameAt(i); | |||
for (XSSFName nm : _wb.getAllNames()) { | |||
if (nm.getSheetIndex() == -1 || nm.getSheetIndex() == sheetIndex) { | |||
updateName(nm, oldName, newName); | |||
} |
@@ -0,0 +1,136 @@ | |||
/* | |||
* ==================================================================== | |||
* 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.xssf.usermodel.helpers; | |||
import java.security.SecureRandom; | |||
import java.util.Arrays; | |||
import java.util.Locale; | |||
import javax.xml.bind.DatatypeConverter; | |||
import javax.xml.namespace.QName; | |||
import org.apache.poi.poifs.crypt.CryptoFunctions; | |||
import org.apache.poi.poifs.crypt.HashAlgorithm; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.xmlbeans.XmlCursor; | |||
import org.apache.xmlbeans.XmlObject; | |||
@Internal(since="3.15 beta 3") | |||
public final class XSSFPasswordHelper { | |||
private XSSFPasswordHelper() { | |||
// no instances of this static class | |||
} | |||
/** | |||
* Sets the XORed or hashed password | |||
* | |||
* @param xobj the xmlbeans object which contains the password attributes | |||
* @param password the password, if null, the password attributes will be removed | |||
* @param hashAlgo the hash algorithm, if null the password will be XORed | |||
* @param prefix the prefix of the password attributes, may be null | |||
*/ | |||
public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) { | |||
XmlCursor cur = xobj.newCursor(); | |||
if (password == null) { | |||
cur.removeAttribute(getAttrName(prefix, "password")); | |||
cur.removeAttribute(getAttrName(prefix, "algorithmName")); | |||
cur.removeAttribute(getAttrName(prefix, "hashValue")); | |||
cur.removeAttribute(getAttrName(prefix, "saltValue")); | |||
cur.removeAttribute(getAttrName(prefix, "spinCount")); | |||
return; | |||
} | |||
cur.toFirstContentToken(); | |||
if (hashAlgo == null) { | |||
int hash = CryptoFunctions.createXorVerifier1(password); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "password"), | |||
String.format(Locale.ROOT, "%04X", hash).toUpperCase(Locale.ROOT)); | |||
} else { | |||
SecureRandom random = new SecureRandom(); | |||
byte salt[] = random.generateSeed(16); | |||
// Iterations specifies the number of times the hashing function shall be iteratively run (using each | |||
// iteration's result as the input for the next iteration). | |||
int spinCount = 100000; | |||
// Implementation Notes List: | |||
// --> In this third stage, the reversed byte order legacy hash from the second stage shall | |||
// be converted to Unicode hex string representation | |||
byte hash[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCount, false); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "algorithmName"), hashAlgo.jceId); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "hashValue"), DatatypeConverter.printBase64Binary(hash)); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "saltValue"), DatatypeConverter.printBase64Binary(salt)); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "spinCount"), ""+spinCount); | |||
} | |||
cur.dispose(); | |||
} | |||
/** | |||
* Validates the password, i.e. | |||
* calculates the hash of the given password and compares it against the stored hash | |||
* | |||
* @param xobj the xmlbeans object which contains the password attributes | |||
* @param password the password, if null the method will always return false, | |||
* even if there's no password set | |||
* @param prefix the prefix of the password attributes, may be null | |||
* | |||
* @return true, if the hashes match | |||
*/ | |||
public static boolean validatePassword(XmlObject xobj, String password, String prefix) { | |||
// TODO: is "velvetSweatshop" the default password? | |||
if (password == null) return false; | |||
XmlCursor cur = xobj.newCursor(); | |||
String xorHashVal = cur.getAttributeText(getAttrName(prefix, "password")); | |||
String algoName = cur.getAttributeText(getAttrName(prefix, "algorithmName")); | |||
String hashVal = cur.getAttributeText(getAttrName(prefix, "hashValue")); | |||
String saltVal = cur.getAttributeText(getAttrName(prefix, "saltValue")); | |||
String spinCount = cur.getAttributeText(getAttrName(prefix, "spinCount")); | |||
cur.dispose(); | |||
if (xorHashVal != null) { | |||
int hash1 = Integer.parseInt(xorHashVal, 16); | |||
int hash2 = CryptoFunctions.createXorVerifier1(password); | |||
return hash1 == hash2; | |||
} else { | |||
if (hashVal == null || algoName == null || saltVal == null || spinCount == null) { | |||
return false; | |||
} | |||
byte hash1[] = DatatypeConverter.parseBase64Binary(hashVal); | |||
HashAlgorithm hashAlgo = HashAlgorithm.fromString(algoName); | |||
byte salt[] = DatatypeConverter.parseBase64Binary(saltVal); | |||
int spinCnt = Integer.parseInt(spinCount); | |||
byte hash2[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCnt, false); | |||
return Arrays.equals(hash1, hash2); | |||
} | |||
} | |||
private static QName getAttrName(String prefix, String name) { | |||
if (prefix == null || "".equals(prefix)) { | |||
return new QName(name); | |||
} else { | |||
return new QName(prefix+Character.toUpperCase(name.charAt(0))+name.substring(1)); | |||
} | |||
} | |||
} |
@@ -1,130 +1,60 @@ | |||
/* | |||
* ==================================================================== | |||
* 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.xssf.usermodel.helpers; | |||
import java.security.SecureRandom; | |||
import java.util.Arrays; | |||
import java.util.Locale; | |||
import javax.xml.bind.DatatypeConverter; | |||
import javax.xml.namespace.QName; | |||
import org.apache.poi.poifs.crypt.CryptoFunctions; | |||
import org.apache.poi.poifs.crypt.HashAlgorithm; | |||
import org.apache.xmlbeans.XmlCursor; | |||
import org.apache.xmlbeans.XmlObject; | |||
public class XSSFPaswordHelper { | |||
/** | |||
* Sets the XORed or hashed password | |||
* | |||
* @param xobj the xmlbeans object which contains the password attributes | |||
* @param password the password, if null, the password attributes will be removed | |||
* @param hashAlgo the hash algorithm, if null the password will be XORed | |||
* @param prefix the prefix of the password attributes, may be null | |||
*/ | |||
public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) { | |||
XmlCursor cur = xobj.newCursor(); | |||
if (password == null) { | |||
cur.removeAttribute(getAttrName(prefix, "password")); | |||
cur.removeAttribute(getAttrName(prefix, "algorithmName")); | |||
cur.removeAttribute(getAttrName(prefix, "hashValue")); | |||
cur.removeAttribute(getAttrName(prefix, "saltValue")); | |||
cur.removeAttribute(getAttrName(prefix, "spinCount")); | |||
return; | |||
} | |||
cur.toFirstContentToken(); | |||
if (hashAlgo == null) { | |||
int hash = CryptoFunctions.createXorVerifier1(password); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "password"), | |||
Integer.toHexString(hash).toUpperCase(Locale.ROOT)); | |||
} else { | |||
SecureRandom random = new SecureRandom(); | |||
byte salt[] = random.generateSeed(16); | |||
// Iterations specifies the number of times the hashing function shall be iteratively run (using each | |||
// iteration's result as the input for the next iteration). | |||
int spinCount = 100000; | |||
// Implementation Notes List: | |||
// --> In this third stage, the reversed byte order legacy hash from the second stage shall | |||
// be converted to Unicode hex string representation | |||
byte hash[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCount, false); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "algorithmName"), hashAlgo.jceId); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "hashValue"), DatatypeConverter.printBase64Binary(hash)); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "saltValue"), DatatypeConverter.printBase64Binary(salt)); | |||
cur.insertAttributeWithValue(getAttrName(prefix, "spinCount"), ""+spinCount); | |||
} | |||
cur.dispose(); | |||
} | |||
/** | |||
* Validates the password, i.e. | |||
* calculates the hash of the given password and compares it against the stored hash | |||
* | |||
* @param xobj the xmlbeans object which contains the password attributes | |||
* @param password the password, if null the method will always return false, | |||
* even if there's no password set | |||
* @param prefix the prefix of the password attributes, may be null | |||
* | |||
* @return true, if the hashes match | |||
*/ | |||
public static boolean validatePassword(XmlObject xobj, String password, String prefix) { | |||
// TODO: is "velvetSweatshop" the default password? | |||
if (password == null) return false; | |||
XmlCursor cur = xobj.newCursor(); | |||
String xorHashVal = cur.getAttributeText(getAttrName(prefix, "password")); | |||
String algoName = cur.getAttributeText(getAttrName(prefix, "algorithmName")); | |||
String hashVal = cur.getAttributeText(getAttrName(prefix, "hashValue")); | |||
String saltVal = cur.getAttributeText(getAttrName(prefix, "saltValue")); | |||
String spinCount = cur.getAttributeText(getAttrName(prefix, "spinCount")); | |||
cur.dispose(); | |||
if (xorHashVal != null) { | |||
int hash1 = Integer.parseInt(xorHashVal, 16); | |||
int hash2 = CryptoFunctions.createXorVerifier1(password); | |||
return hash1 == hash2; | |||
} else { | |||
if (hashVal == null || algoName == null || saltVal == null || spinCount == null) { | |||
return false; | |||
} | |||
byte hash1[] = DatatypeConverter.parseBase64Binary(hashVal); | |||
HashAlgorithm hashAlgo = HashAlgorithm.fromString(algoName); | |||
byte salt[] = DatatypeConverter.parseBase64Binary(saltVal); | |||
int spinCnt = Integer.parseInt(spinCount); | |||
byte hash2[] = CryptoFunctions.hashPassword(password, hashAlgo, salt, spinCnt, false); | |||
return Arrays.equals(hash1, hash2); | |||
} | |||
} | |||
private static QName getAttrName(String prefix, String name) { | |||
if (prefix == null || "".equals(prefix)) { | |||
return new QName(name); | |||
} else { | |||
return new QName(prefix+Character.toUpperCase(name.charAt(0))+name.substring(1)); | |||
} | |||
} | |||
} | |||
/* | |||
* ==================================================================== | |||
* 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.xssf.usermodel.helpers; | |||
import org.apache.poi.poifs.crypt.HashAlgorithm; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.Removal; | |||
import org.apache.xmlbeans.XmlObject; | |||
/** | |||
* @deprecated POI 3.15 beta 3. Use {@link XSSFPasswordHelper} instead. | |||
*/ | |||
@Internal(since="3.15 beta 3") | |||
@Deprecated | |||
@Removal(version="3.17") | |||
public class XSSFPaswordHelper { | |||
/** | |||
* Sets the XORed or hashed password | |||
* | |||
* @param xobj the xmlbeans object which contains the password attributes | |||
* @param password the password, if null, the password attributes will be removed | |||
* @param hashAlgo the hash algorithm, if null the password will be XORed | |||
* @param prefix the prefix of the password attributes, may be null | |||
*/ | |||
public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) { | |||
XSSFPasswordHelper.setPassword(xobj, password, hashAlgo, prefix); | |||
} | |||
/** | |||
* Validates the password, i.e. | |||
* calculates the hash of the given password and compares it against the stored hash | |||
* | |||
* @param xobj the xmlbeans object which contains the password attributes | |||
* @param password the password, if null the method will always return false, | |||
* even if there's no password set | |||
* @param prefix the prefix of the password attributes, may be null | |||
* | |||
* @return true, if the hashes match | |||
*/ | |||
public static boolean validatePassword(XmlObject xobj, String password, String prefix) { | |||
return XSSFPasswordHelper.validatePassword(xobj, password, prefix); | |||
} | |||
} |
@@ -83,9 +83,7 @@ public final class XSSFRowShifter extends RowShifter { | |||
public void updateNamedRanges(FormulaShifter shifter) { | |||
Workbook wb = sheet.getWorkbook(); | |||
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create((XSSFWorkbook) wb); | |||
final int numberOfNames = wb.getNumberOfNames(); | |||
for (int i = 0; i < numberOfNames; i++) { | |||
Name name = wb.getNameAt(i); | |||
for (Name name : wb.getAllNames()) { | |||
String formula = name.getRefersToFormula(); | |||
int sheetIndex = name.getSheetIndex(); | |||
final int rowIndex = -1; //don't care, named ranges are not allowed to include structured references |
@@ -18,6 +18,7 @@ | |||
package org.apache.poi.openxml4j.opc.internal; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.fail; | |||
import static org.junit.Assume.assumeTrue; | |||
@@ -44,16 +45,21 @@ public final class TestContentTypeManager { | |||
// Retrieves core properties part | |||
OPCPackage p = OPCPackage.open(filepath, PackageAccess.READ); | |||
PackageRelationshipCollection rels = p.getRelationshipsByType(PackageRelationshipTypes.CORE_PROPERTIES); | |||
PackageRelationship corePropertiesRelationship = rels.getRelationship(0); | |||
PackagePart coreDocument = p.getPart(corePropertiesRelationship); | |||
assertEquals("application/vnd.openxmlformats-package.core-properties+xml", coreDocument.getContentType()); | |||
// TODO - finish writing this test | |||
assumeTrue("finish writing this test", false); | |||
ContentTypeManager ctm = new ZipContentTypeManager(coreDocument.getInputStream(), p); | |||
try { | |||
PackageRelationshipCollection rels = p.getRelationshipsByType(PackageRelationshipTypes.CORE_PROPERTIES); | |||
PackageRelationship corePropertiesRelationship = rels.getRelationship(0); | |||
PackagePart coreDocument = p.getPart(corePropertiesRelationship); | |||
assertEquals("application/vnd.openxmlformats-package.core-properties+xml", coreDocument.getContentType()); | |||
// TODO - finish writing this test | |||
assumeTrue("finish writing this test", false); | |||
ContentTypeManager ctm = new ZipContentTypeManager(coreDocument.getInputStream(), p); | |||
assertNotNull(ctm); | |||
} finally { | |||
p.close(); | |||
} | |||
} | |||
/** |
@@ -115,25 +115,25 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues { | |||
assertFalse(wb.isMacroEnabled()); | |||
assertEquals(3, wb.getNumberOfNames()); | |||
assertEquals(0, wb.getNameAt(0).getCTName().getLocalSheetId()); | |||
assertFalse(wb.getNameAt(0).getCTName().isSetLocalSheetId()); | |||
assertEquals("SheetA!$A$1", wb.getNameAt(0).getRefersToFormula()); | |||
assertEquals("SheetA", wb.getNameAt(0).getSheetName()); | |||
assertEquals(0, wb.getName("SheetAA1").getCTName().getLocalSheetId()); | |||
assertFalse(wb.getName("SheetAA1").getCTName().isSetLocalSheetId()); | |||
assertEquals("SheetA!$A$1", wb.getName("SheetAA1").getRefersToFormula()); | |||
assertEquals("SheetA", wb.getName("SheetAA1").getSheetName()); | |||
assertEquals(0, wb.getNameAt(1).getCTName().getLocalSheetId()); | |||
assertFalse(wb.getNameAt(1).getCTName().isSetLocalSheetId()); | |||
assertEquals("SheetB!$A$1", wb.getNameAt(1).getRefersToFormula()); | |||
assertEquals("SheetB", wb.getNameAt(1).getSheetName()); | |||
assertEquals(0, wb.getName("SheetBA1").getCTName().getLocalSheetId()); | |||
assertFalse(wb.getName("SheetBA1").getCTName().isSetLocalSheetId()); | |||
assertEquals("SheetB!$A$1", wb.getName("SheetBA1").getRefersToFormula()); | |||
assertEquals("SheetB", wb.getName("SheetBA1").getSheetName()); | |||
assertEquals(0, wb.getNameAt(2).getCTName().getLocalSheetId()); | |||
assertFalse(wb.getNameAt(2).getCTName().isSetLocalSheetId()); | |||
assertEquals("SheetC!$A$1", wb.getNameAt(2).getRefersToFormula()); | |||
assertEquals("SheetC", wb.getNameAt(2).getSheetName()); | |||
assertEquals(0, wb.getName("SheetCA1").getCTName().getLocalSheetId()); | |||
assertFalse(wb.getName("SheetCA1").getCTName().isSetLocalSheetId()); | |||
assertEquals("SheetC!$A$1", wb.getName("SheetCA1").getRefersToFormula()); | |||
assertEquals("SheetC", wb.getName("SheetCA1").getSheetName()); | |||
// Save and re-load, still there | |||
XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb); | |||
assertEquals(3, nwb.getNumberOfNames()); | |||
assertEquals("SheetA!$A$1", nwb.getNameAt(0).getRefersToFormula()); | |||
assertEquals("SheetA!$A$1", nwb.getName("SheetAA1").getRefersToFormula()); | |||
nwb.close(); | |||
wb.close(); |
@@ -154,7 +154,9 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { | |||
evaluator.evaluate(cXSL_cell); | |||
fail("Without a fix for #56752, shouldn't be able to evaluate a " + | |||
"reference to a non-provided linked workbook"); | |||
} catch(Exception e) {} | |||
} catch(Exception e) { | |||
// expected here | |||
} | |||
// Setup the environment | |||
Map<String,FormulaEvaluator> evaluators = new HashMap<String, FormulaEvaluator>(); | |||
@@ -171,8 +173,19 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { | |||
evaluator.evaluate(c); | |||
} | |||
} | |||
// And evaluate the other way too | |||
evaluator.evaluateAll(); | |||
// Static evaluator won't work, as no references passed in | |||
try { | |||
XSSFFormulaEvaluator.evaluateAllFormulaCells(wb); | |||
fail("Static method lacks references, shouldn't work"); | |||
} catch(Exception e) { | |||
// expected here | |||
} | |||
// Evaluate and check results | |||
// Evaluate specific cells and check results | |||
assertEquals("\"Hello!\"", evaluator.evaluate(cXSLX_cell).formatAsString()); | |||
assertEquals("\"Test A1\"", evaluator.evaluate(cXSLX_sNR).formatAsString()); | |||
assertEquals("142.0", evaluator.evaluate(cXSLX_gNR).formatAsString()); | |||
@@ -196,7 +209,9 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { | |||
try { | |||
cXSLX_nw_cell.setCellFormula("[alt.xlsx]Sheet1!$A$1"); | |||
fail("New workbook not linked, shouldn't be able to add"); | |||
} catch (Exception e) {} | |||
} catch (Exception e) { | |||
// expected here | |||
} | |||
// Link and re-try | |||
Workbook alt = new XSSFWorkbook(); | |||
@@ -651,4 +666,20 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { | |||
private Cell getCell(Sheet sheet, int rowNo, int column) { | |||
return sheet.getRow(rowNo).getCell(column); | |||
} | |||
@Test | |||
public void test59736() { | |||
Workbook wb = XSSFTestDataSamples.openSampleWorkbook("59736.xlsx"); | |||
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); | |||
Cell cell = wb.getSheetAt(0).getRow(0).getCell(0); | |||
assertEquals(1, cell.getNumericCellValue(), 0.001); | |||
cell = wb.getSheetAt(0).getRow(1).getCell(0); | |||
CellValue value = evaluator.evaluate(cell); | |||
assertEquals(1, value.getNumberValue(), 0.001); | |||
cell = wb.getSheetAt(0).getRow(2).getCell(0); | |||
value = evaluator.evaluate(cell); | |||
assertEquals(1, value.getNumberValue(), 0.001); | |||
} | |||
} |
@@ -53,9 +53,8 @@ public final class TestXSSFName extends BaseTestNamedRange { | |||
//sheet.createFreezePane(0, 3); | |||
} | |||
assertEquals(1, wb.getNumberOfNames()); | |||
XSSFName nr1 = wb.getNameAt(0); | |||
XSSFName nr1 = wb.getName(XSSFName.BUILTIN_PRINT_TITLE); | |||
assertEquals(XSSFName.BUILTIN_PRINT_TITLE, nr1.getNameName()); | |||
assertEquals("'First Sheet'!$A:$A,'First Sheet'!$1:$4", nr1.getRefersToFormula()); | |||
//remove the columns part | |||
@@ -77,9 +76,8 @@ public final class TestXSSFName extends BaseTestNamedRange { | |||
wb.close(); | |||
assertEquals(1, nwb.getNumberOfNames()); | |||
nr1 = nwb.getNameAt(0); | |||
nr1 = nwb.getName(XSSFName.BUILTIN_PRINT_TITLE); | |||
assertEquals(XSSFName.BUILTIN_PRINT_TITLE, nr1.getNameName()); | |||
assertEquals("'First Sheet'!$A:$A,'First Sheet'!$1:$4", nr1.getRefersToFormula()); | |||
// check that setting RR&C on a second sheet causes a new Print_Titles built-in | |||
@@ -89,7 +87,7 @@ public final class TestXSSFName extends BaseTestNamedRange { | |||
sheet2.setRepeatingColumns(CellRangeAddress.valueOf("B:C")); | |||
assertEquals(2, nwb.getNumberOfNames()); | |||
XSSFName nr2 = nwb.getNameAt(1); | |||
XSSFName nr2 = nwb.getNames(XSSFName.BUILTIN_PRINT_TITLE).get(1); | |||
assertEquals(XSSFName.BUILTIN_PRINT_TITLE, nr2.getNameName()); | |||
assertEquals("SecondSheet!$B:$C,SecondSheet!$1:$1", nr2.getRefersToFormula()); | |||
@@ -98,4 +96,38 @@ public final class TestXSSFName extends BaseTestNamedRange { | |||
sheet2.setRepeatingColumns(null); | |||
nwb.close(); | |||
} | |||
@Test | |||
public void testSetNameName() throws Exception { | |||
// Test that renaming named ranges doesn't break our new named range map | |||
XSSFWorkbook wb = new XSSFWorkbook(); | |||
wb.createSheet("First Sheet"); | |||
// Two named ranges called "name1", one scoped to sheet1 and one globally | |||
XSSFName nameSheet1 = wb.createName(); | |||
nameSheet1.setNameName("name1"); | |||
nameSheet1.setRefersToFormula("'First Sheet'!$A$1"); | |||
nameSheet1.setSheetIndex(0); | |||
XSSFName nameGlobal = wb.createName(); | |||
nameGlobal.setNameName("name1"); | |||
nameGlobal.setRefersToFormula("'First Sheet'!$B$1"); | |||
// Rename sheet-scoped name to "name2", check everything is updated properly | |||
// and that the other name is unaffected | |||
nameSheet1.setNameName("name2"); | |||
assertEquals(1, wb.getNames("name1").size()); | |||
assertEquals(1, wb.getNames("name2").size()); | |||
assertEquals(nameGlobal, wb.getName("name1")); | |||
assertEquals(nameSheet1, wb.getName("name2")); | |||
// Rename the other name to "name" and check everything again | |||
nameGlobal.setNameName("name2"); | |||
assertEquals(0, wb.getNames("name1").size()); | |||
assertEquals(2, wb.getNames("name2").size()); | |||
assertTrue(wb.getNames("name2").contains(nameGlobal)); | |||
assertTrue(wb.getNames("name2").contains(nameSheet1)); | |||
wb.close(); | |||
} | |||
} |
@@ -80,6 +80,7 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet; | |||
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXf; | |||
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCalcMode; | |||
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPane; | |||
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STUnsignedShortHex; | |||
public final class TestXSSFSheet extends BaseTestXSheet { | |||
@@ -1099,6 +1100,30 @@ public final class TestXSSFSheet extends BaseTestXSheet { | |||
wb.close(); | |||
} | |||
@Test | |||
public void protectSheet_emptyPassword() throws IOException { | |||
XSSFWorkbook wb = new XSSFWorkbook(); | |||
XSSFSheet sheet = wb.createSheet(); | |||
CTSheetProtection pr = sheet.getCTWorksheet().getSheetProtection(); | |||
assertNull("CTSheetProtection should be null by default", pr); | |||
String password = ""; | |||
sheet.protectSheet(password); | |||
pr = sheet.getCTWorksheet().getSheetProtection(); | |||
assertNotNull("CTSheetProtection should be not null", pr); | |||
assertTrue("sheet protection should be on", pr.isSetSheet()); | |||
assertTrue("object protection should be on", pr.isSetObjects()); | |||
assertTrue("scenario protection should be on", pr.isSetScenarios()); | |||
int hashVal = CryptoFunctions.createXorVerifier1(password); | |||
STUnsignedShortHex xpassword = pr.xgetPassword(); | |||
int actualVal = Integer.parseInt(xpassword.getStringValue(),16); | |||
assertEquals("well known value for top secret hash should match", hashVal, actualVal); | |||
sheet.protectSheet(null); | |||
assertNull("protectSheet(null) should unset CTSheetProtection", sheet.getCTWorksheet().getSheetProtection()); | |||
wb.close(); | |||
} | |||
@Test | |||
public void protectSheet_lowlevel_2013() throws IOException { | |||
String password = "test"; |
@@ -1140,4 +1140,44 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { | |||
wb.close(); | |||
} | |||
@Test | |||
public void testRemoveSheet() throws IOException { | |||
// Test removing a sheet maintains the named ranges correctly | |||
XSSFWorkbook wb = new XSSFWorkbook(); | |||
wb.createSheet("Sheet1"); | |||
wb.createSheet("Sheet2"); | |||
XSSFName sheet1Name = wb.createName(); | |||
sheet1Name.setNameName("name1"); | |||
sheet1Name.setSheetIndex(0); | |||
sheet1Name.setRefersToFormula("Sheet1!$A$1"); | |||
XSSFName sheet2Name = wb.createName(); | |||
sheet2Name.setNameName("name1"); | |||
sheet2Name.setSheetIndex(1); | |||
sheet2Name.setRefersToFormula("Sheet2!$A$1"); | |||
assertTrue(wb.getAllNames().contains(sheet1Name)); | |||
assertTrue(wb.getAllNames().contains(sheet2Name)); | |||
assertEquals(2, wb.getNames("name1").size()); | |||
assertEquals(sheet1Name, wb.getNames("name1").get(0)); | |||
assertEquals(sheet2Name, wb.getNames("name1").get(1)); | |||
// Remove sheet1, we should only have sheet2Name now | |||
wb.removeSheetAt(0); | |||
assertFalse(wb.getAllNames().contains(sheet1Name)); | |||
assertTrue(wb.getAllNames().contains(sheet2Name)); | |||
assertEquals(1, wb.getNames("name1").size()); | |||
assertEquals(sheet2Name, wb.getNames("name1").get(0)); | |||
// Check by index as well for sanity | |||
assertEquals(1, wb.getNumberOfNames()); | |||
assertEquals(0, wb.getNameIndex("name1")); | |||
assertEquals(sheet2Name, wb.getNameAt(0)); | |||
wb.close(); | |||
} | |||
} |
@@ -571,20 +571,39 @@ public final class HWPFDocument extends HWPFDocumentCore { | |||
return _fields; | |||
} | |||
/** | |||
* Warning - not currently implemented for HWPF! | |||
*/ | |||
@Override | |||
public void write() throws IOException { | |||
// TODO Implement | |||
throw new IllegalStateException("Coming soon!"); | |||
} | |||
/** | |||
* Writes out the word file that is represented by an instance of this class. | |||
* | |||
* If the {@link File} exists, it will be replaced, otherwise a new one | |||
* will be created | |||
* | |||
* @param newFile The File to write to. | |||
* @throws IOException If there is an unexpected IOException from writing | |||
* to the File. | |||
* | |||
* @since 3.15 beta 3 | |||
*/ | |||
@Override | |||
public void write(File newFile) throws IOException { | |||
throw new IllegalStateException("Coming soon!"); | |||
NPOIFSFileSystem pfs = POIFSFileSystem.create(newFile); | |||
write(pfs, true); | |||
pfs.writeFilesystem(); | |||
} | |||
/** | |||
* Writes out the word file that is represented by an instance of this class. | |||
* | |||
* If {@code stream} is a {@link java.io.FileOutputStream} on a networked drive | |||
* or has a high cost/latency associated with each written byte, | |||
* For better performance when writing to files, use {@link #write(File)}. | |||
* If {@code stream} has a high cost/latency associated with each written byte, | |||
* consider wrapping the OutputStream in a {@link java.io.BufferedOutputStream} | |||
* to improve write performance. | |||
* | |||
@@ -592,9 +611,12 @@ public final class HWPFDocument extends HWPFDocumentCore { | |||
* @throws IOException If there is an unexpected IOException from the passed | |||
* in OutputStream. | |||
*/ | |||
public void write(OutputStream out) | |||
throws IOException | |||
{ | |||
public void write(OutputStream out) throws IOException { | |||
NPOIFSFileSystem pfs = new NPOIFSFileSystem(); | |||
write(pfs, true); | |||
pfs.writeFilesystem( out ); | |||
} | |||
private void write(NPOIFSFileSystem pfs, boolean copyOtherEntries) throws IOException { | |||
// initialize our streams for writing. | |||
HWPFFileSystem docSys = new HWPFFileSystem(); | |||
HWPFOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT); | |||
@@ -891,7 +913,8 @@ public final class HWPFDocument extends HWPFDocumentCore { | |||
} | |||
// create new document preserving order of entries | |||
NPOIFSFileSystem pfs = new NPOIFSFileSystem(); | |||
// TODO Check "copyOtherEntries" and tweak behaviour based on that | |||
// TODO That's needed for in-place write | |||
boolean docWritten = false; | |||
boolean dataWritten = false; | |||
boolean objectPoolWritten = false; | |||
@@ -967,7 +990,6 @@ public final class HWPFDocument extends HWPFDocumentCore { | |||
if ( !objectPoolWritten ) | |||
_objectPool.writeTo( pfs.getRoot() ); | |||
pfs.writeFilesystem( out ); | |||
this.directory = pfs.getRoot(); | |||
/* |
@@ -0,0 +1,81 @@ | |||
/* ==================================================================== | |||
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.hwpf.usermodel; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import org.apache.poi.hwpf.HWPFDocument; | |||
import org.apache.poi.hwpf.HWPFTestCase; | |||
import org.apache.poi.hwpf.HWPFTestDataSamples; | |||
import org.apache.poi.poifs.filesystem.POIFSFileSystem; | |||
import org.apache.poi.util.TempFile; | |||
/** | |||
* Test various write situations | |||
*/ | |||
public final class TestHWPFWrite extends HWPFTestCase { | |||
/** | |||
* Write to a stream | |||
*/ | |||
public void testWriteStream() throws Exception { | |||
HWPFDocument doc = HWPFTestDataSamples.openSampleFile("SampleDoc.doc"); | |||
Range r = doc.getRange(); | |||
assertEquals("I am a test document\r", r.getParagraph(0).text()); | |||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |||
doc.write(baos); | |||
doc.close(); | |||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); | |||
doc = new HWPFDocument(bais); | |||
r = doc.getRange(); | |||
assertEquals("I am a test document\r", r.getParagraph(0).text()); | |||
doc.close(); | |||
} | |||
/** | |||
* Write to a new file | |||
*/ | |||
public void testWriteNewFile() throws Exception { | |||
HWPFDocument doc = HWPFTestDataSamples.openSampleFile("SampleDoc.doc"); | |||
Range r = doc.getRange(); | |||
assertEquals("I am a test document\r", r.getParagraph(0).text()); | |||
File file = TempFile.createTempFile("TestDocument", ".doc"); | |||
doc.write(file); | |||
doc.close(); | |||
// Check reading from File and Stream | |||
doc = new HWPFDocument(new FileInputStream(file)); | |||
r = doc.getRange(); | |||
assertEquals("I am a test document\r", r.getParagraph(0).text()); | |||
doc.close(); | |||
doc = new HWPFDocument(new POIFSFileSystem(file)); | |||
r = doc.getRange(); | |||
assertEquals("I am a test document\r", r.getParagraph(0).text()); | |||
doc.close(); | |||
} | |||
// TODO In-place write positive and negative checks | |||
} |
@@ -50,14 +50,20 @@ public abstract class BaseTestSlideShow { | |||
@Test | |||
public void addPicture_Stream() throws IOException { | |||
SlideShow<?,?> show = createSlideShow(); | |||
InputStream stream = slTests.openResourceAsStream("clock.jpg"); | |||
assertEquals(0, show.getPictureData().size()); | |||
PictureData picture = show.addPicture(stream, PictureType.JPEG); | |||
assertEquals(1, show.getPictureData().size()); | |||
assertSame(picture, show.getPictureData().get(0)); | |||
show.close(); | |||
try { | |||
InputStream stream = slTests.openResourceAsStream("clock.jpg"); | |||
try { | |||
assertEquals(0, show.getPictureData().size()); | |||
PictureData picture = show.addPicture(stream, PictureType.JPEG); | |||
assertEquals(1, show.getPictureData().size()); | |||
assertSame(picture, show.getPictureData().get(0)); | |||
} finally { | |||
stream.close(); | |||
} | |||
} finally { | |||
show.close(); | |||
} | |||
} | |||
@Test |
@@ -27,6 +27,7 @@ import org.apache.poi.hssf.usermodel.HSSFRow; | |||
import org.apache.poi.hssf.usermodel.HSSFSheet; | |||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.usermodel.Cell; | |||
import org.apache.poi.ss.usermodel.CellType; | |||
import org.apache.poi.ss.usermodel.CellValue; | |||
@@ -105,6 +106,7 @@ public final class TestIndirect { | |||
// non-error cases | |||
confirm(feA, c, "INDIRECT(\"C2\")", 23); | |||
confirm(feA, c, "INDIRECT(\"C2\", TRUE)", 23); | |||
confirm(feA, c, "INDIRECT(\"$C2\")", 23); | |||
confirm(feA, c, "INDIRECT(\"C$2\")", 23); | |||
confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref | |||
@@ -149,7 +151,7 @@ public final class TestIndirect { | |||
// confirm(feA, c, "INDIRECT(\"Sheet1!A65537\")", ErrorEval.REF_INVALID); // bad row | |||
// } | |||
confirm(feA, c, "INDIRECT(\"Sheet1!A 1\")", ErrorEval.REF_INVALID); // space in cell ref | |||
wbA.close(); | |||
} | |||
@@ -203,4 +205,9 @@ public final class TestIndirect { | |||
+ "' but got '" + cv.formatAsString() + "'."); | |||
} | |||
} | |||
@Test | |||
public void testInvalidInput() { | |||
assertEquals(ErrorEval.VALUE_INVALID, Indirect.instance.evaluate(new ValueEval[] {}, null)); | |||
} | |||
} |
@@ -20,9 +20,8 @@ package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.hssf.HSSFTestDataSamples; | |||
import org.apache.poi.hssf.usermodel.HSSFSheet; | |||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||
import org.apache.poi.ss.formula.eval.AreaEval; | |||
import org.apache.poi.ss.formula.eval.NumberEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.FormulaParseException; | |||
import org.apache.poi.ss.formula.eval.*; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.ss.usermodel.*; | |||
@@ -75,7 +74,6 @@ public final class TestSubtotal extends TestCase { | |||
} | |||
public void testAvg(){ | |||
Workbook wb = new HSSFWorkbook(); | |||
FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); | |||
@@ -95,16 +93,18 @@ public final class TestSubtotal extends TestCase { | |||
a6.setCellFormula("SUBTOTAL(1,B2:B6)*2 + 2"); | |||
Cell a7 = sh.createRow(7).createCell(1); | |||
a7.setCellFormula("SUBTOTAL(1,B2:B7)"); | |||
Cell a8 = sh.createRow(8).createCell(1); | |||
a8.setCellFormula("SUBTOTAL(1,B2,B3,B4,B5,B6,B7,B8)"); | |||
fe.evaluateAll(); | |||
assertEquals(2.0, a3.getNumericCellValue()); | |||
assertEquals(8.0, a6.getNumericCellValue()); | |||
assertEquals(3.0, a7.getNumericCellValue()); | |||
assertEquals(3.0, a8.getNumericCellValue()); | |||
} | |||
public void testSum(){ | |||
Workbook wb = new HSSFWorkbook(); | |||
FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); | |||
@@ -124,12 +124,15 @@ public final class TestSubtotal extends TestCase { | |||
a6.setCellFormula("SUBTOTAL(9,B2:B6)*2 + 2"); | |||
Cell a7 = sh.createRow(7).createCell(1); | |||
a7.setCellFormula("SUBTOTAL(9,B2:B7)"); | |||
Cell a8 = sh.createRow(8).createCell(1); | |||
a8.setCellFormula("SUBTOTAL(9,B2,B3,B4,B5,B6,B7,B8)"); | |||
fe.evaluateAll(); | |||
assertEquals(4.0, a3.getNumericCellValue()); | |||
assertEquals(26.0, a6.getNumericCellValue()); | |||
assertEquals(12.0, a7.getNumericCellValue()); | |||
assertEquals(12.0, a8.getNumericCellValue()); | |||
} | |||
public void testCount(){ | |||
@@ -147,18 +150,21 @@ public final class TestSubtotal extends TestCase { | |||
a3.setCellFormula("SUBTOTAL(2,B2:B3)"); | |||
Cell a4 = sh.createRow(4).createCell(1); | |||
a4.setCellValue("POI"); // A4 is string and not counted | |||
Cell a5 = sh.createRow(5).createCell(1); // A5 is blank and not counted | |||
/*Cell a5 =*/ sh.createRow(5).createCell(1); // A5 is blank and not counted | |||
Cell a6 = sh.createRow(6).createCell(1); | |||
a6.setCellFormula("SUBTOTAL(2,B2:B6)*2 + 2"); | |||
Cell a7 = sh.createRow(7).createCell(1); | |||
a7.setCellFormula("SUBTOTAL(2,B2:B7)"); | |||
Cell a8 = sh.createRow(8).createCell(1); | |||
a8.setCellFormula("SUBTOTAL(2,B2,B3,B4,B5,B6,B7,B8)"); | |||
fe.evaluateAll(); | |||
assertEquals(2.0, a3.getNumericCellValue()); | |||
assertEquals(6.0, a6.getNumericCellValue()); | |||
assertEquals(2.0, a7.getNumericCellValue()); | |||
assertEquals(2.0, a8.getNumericCellValue()); | |||
} | |||
public void testCounta(){ | |||
@@ -176,18 +182,21 @@ public final class TestSubtotal extends TestCase { | |||
a3.setCellFormula("SUBTOTAL(3,B2:B3)"); | |||
Cell a4 = sh.createRow(4).createCell(1); | |||
a4.setCellValue("POI"); // A4 is string and not counted | |||
Cell a5 = sh.createRow(5).createCell(1); // A5 is blank and not counted | |||
/*Cell a5 =*/ sh.createRow(5).createCell(1); // A5 is blank and not counted | |||
Cell a6 = sh.createRow(6).createCell(1); | |||
a6.setCellFormula("SUBTOTAL(3,B2:B6)*2 + 2"); | |||
Cell a7 = sh.createRow(7).createCell(1); | |||
a7.setCellFormula("SUBTOTAL(3,B2:B7)"); | |||
Cell a8 = sh.createRow(8).createCell(1); | |||
a8.setCellFormula("SUBTOTAL(3,B2,B3,B4,B5,B6,B7,B8)"); | |||
fe.evaluateAll(); | |||
assertEquals(2.0, a3.getNumericCellValue()); | |||
assertEquals(8.0, a6.getNumericCellValue()); | |||
assertEquals(3.0, a7.getNumericCellValue()); | |||
assertEquals(3.0, a8.getNumericCellValue()); | |||
} | |||
public void testMax(){ | |||
@@ -211,12 +220,15 @@ public final class TestSubtotal extends TestCase { | |||
a6.setCellFormula("SUBTOTAL(4,B2:B6)*2 + 2"); | |||
Cell a7 = sh.createRow(7).createCell(1); | |||
a7.setCellFormula("SUBTOTAL(4,B2:B7)"); | |||
Cell a8 = sh.createRow(8).createCell(1); | |||
a8.setCellFormula("SUBTOTAL(4,B2,B3,B4,B5,B6,B7,B8)"); | |||
fe.evaluateAll(); | |||
assertEquals(3.0, a3.getNumericCellValue()); | |||
assertEquals(16.0, a6.getNumericCellValue()); | |||
assertEquals(7.0, a7.getNumericCellValue()); | |||
assertEquals(7.0, a8.getNumericCellValue()); | |||
} | |||
public void testMin(){ | |||
@@ -240,12 +252,15 @@ public final class TestSubtotal extends TestCase { | |||
a6.setCellFormula("SUBTOTAL(5,B2:B6)*2 + 2"); | |||
Cell a7 = sh.createRow(7).createCell(1); | |||
a7.setCellFormula("SUBTOTAL(5,B2:B7)"); | |||
Cell a8 = sh.createRow(8).createCell(1); | |||
a8.setCellFormula("SUBTOTAL(5,B2,B3,B4,B5,B6,B7,B8)"); | |||
fe.evaluateAll(); | |||
assertEquals(1.0, a3.getNumericCellValue()); | |||
assertEquals(4.0, a6.getNumericCellValue()); | |||
assertEquals(1.0, a7.getNumericCellValue()); | |||
assertEquals(1.0, a8.getNumericCellValue()); | |||
} | |||
public void testStdev(){ | |||
@@ -269,12 +284,15 @@ public final class TestSubtotal extends TestCase { | |||
a6.setCellFormula("SUBTOTAL(7,B2:B6)*2 + 2"); | |||
Cell a7 = sh.createRow(7).createCell(1); | |||
a7.setCellFormula("SUBTOTAL(7,B2:B7)"); | |||
Cell a8 = sh.createRow(8).createCell(1); | |||
a8.setCellFormula("SUBTOTAL(7,B2,B3,B4,B5,B6,B7,B8)"); | |||
fe.evaluateAll(); | |||
assertEquals(1.41421, a3.getNumericCellValue(), 0.0001); | |||
assertEquals(7.65685, a6.getNumericCellValue(), 0.0001); | |||
assertEquals(2.82842, a7.getNumericCellValue(), 0.0001); | |||
assertEquals(2.82842, a8.getNumericCellValue(), 0.0001); | |||
} | |||
public void test50209(){ | |||
@@ -328,4 +346,69 @@ public final class TestSubtotal extends TestCase { | |||
confirmExpectedResult(evaluator, "SUBTOTAL(COUNT;B2:B8,C2:C8)", cellC2, 3.0); | |||
confirmExpectedResult(evaluator, "SUBTOTAL(COUNTA;B2:B8,C2:C8)", cellC3, 5.0); | |||
} | |||
public void testUnimplemented(){ | |||
Workbook wb = new HSSFWorkbook(); | |||
FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); | |||
Sheet sh = wb.createSheet(); | |||
Cell a3 = sh.createRow(3).createCell(1); | |||
a3.setCellFormula("SUBTOTAL(8,B2:B3)"); | |||
try { | |||
fe.evaluateAll(); | |||
fail("Should catch an NotImplementedFunctionException here, adjust these tests if it was actually implemented"); | |||
} catch (NotImplementedException e) { | |||
// expected here | |||
} | |||
a3.setCellFormula("SUBTOTAL(10,B2:B3)"); | |||
try { | |||
fe.evaluateAll(); | |||
fail("Should catch an NotImplementedFunctionException here, adjust these tests if it was actually implemented"); | |||
} catch (NotImplementedException e) { | |||
// expected here | |||
} | |||
a3.setCellFormula("SUBTOTAL(11,B2:B3)"); | |||
try { | |||
fe.evaluateAll(); | |||
fail("Should catch an NotImplementedFunctionException here, adjust these tests if it was actually implemented"); | |||
} catch (NotImplementedException e) { | |||
// expected here | |||
} | |||
a3.setCellFormula("SUBTOTAL(107,B2:B3)"); | |||
try { | |||
fe.evaluateAll(); | |||
fail("Should catch an NotImplementedFunctionException here, adjust these tests if it was actually implemented"); | |||
} catch (NotImplementedException e) { | |||
// expected here | |||
} | |||
a3.setCellFormula("SUBTOTAL(0,B2:B3)"); | |||
fe.evaluateAll(); | |||
assertEquals(FormulaError.VALUE.getCode(), a3.getErrorCellValue()); | |||
try { | |||
a3.setCellFormula("SUBTOTAL(9)"); | |||
fail("Should catch an exception here"); | |||
} catch (FormulaParseException e) { | |||
// expected here | |||
} | |||
try { | |||
a3.setCellFormula("SUBTOTAL()"); | |||
fail("Should catch an exception here"); | |||
} catch (FormulaParseException e) { | |||
// expected here | |||
} | |||
Subtotal subtotal = new Subtotal(); | |||
assertEquals(ErrorEval.VALUE_INVALID, subtotal.evaluate(new ValueEval[] {}, 0, 0)); | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
/* ==================================================================== | |||
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.functions; | |||
import org.apache.poi.ss.formula.eval.*; | |||
import org.junit.Test; | |||
import static org.junit.Assert.*; | |||
public class TestWeekdayFunc { | |||
@Test | |||
public void testEvaluate() throws Exception { | |||
assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(1.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(1.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(2.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(0.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(3.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(1.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(11.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(7.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(12.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(6.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(13.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(5.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(14.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(4.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(15.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(3.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(16.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(17.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(3.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(3.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(1.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(2.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(1.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(3.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(2.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(11.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(1.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(12.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(7.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(13.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(6.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(14.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(5.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(15.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(4.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(16.0)}, 0, 0)).getNumberValue(), 0.001); | |||
assertEquals(3.0, ((NumberEval)WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(39448.0), new NumberEval(17.0)}, 0, 0)).getNumberValue(), 0.001); | |||
} | |||
@Test | |||
public void testEvaluateInvalid() throws Exception { | |||
assertEquals(ErrorEval.VALUE_INVALID, WeekdayFunc.instance.evaluate(new ValueEval[]{}, 0, 0)); | |||
assertEquals(ErrorEval.VALUE_INVALID, WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(1.0), new NumberEval(1.0)}, 0, 0)); | |||
assertEquals(ErrorEval.NUM_ERROR, WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(-1.0)}, 0, 0)); | |||
assertEquals(ErrorEval.VALUE_INVALID, WeekdayFunc.instance.evaluate(new ValueEval[]{new StringEval("")}, 0, 0)); | |||
assertEquals(ErrorEval.VALUE_INVALID, WeekdayFunc.instance.evaluate(new ValueEval[]{new StringEval("1"), new StringEval("")}, 0, 0)); | |||
assertEquals(ErrorEval.NUM_ERROR, WeekdayFunc.instance.evaluate(new ValueEval[]{new StringEval("2"), BlankEval.instance}, 0, 0)); | |||
assertEquals(ErrorEval.NUM_ERROR, WeekdayFunc.instance.evaluate(new ValueEval[]{new StringEval("3"), MissingArgEval.instance}, 0, 0)); | |||
assertEquals(ErrorEval.NUM_ERROR, WeekdayFunc.instance.evaluate(new ValueEval[]{new NumberEval(1.0), new NumberEval(18.0)}, 0, 0)); | |||
} | |||
} |
@@ -33,9 +33,12 @@ import java.awt.font.FontRenderContext; | |||
import java.awt.font.TextAttribute; | |||
import java.awt.font.TextLayout; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.text.AttributedString; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import static org.junit.Assert.*; | |||
@@ -1606,4 +1609,78 @@ public abstract class BaseTestBugzillaIssues { | |||
assertNull("Sheet0 after write", wb2.getPrintArea(0)); // CURRENTLY FAILS with "Sheet0!$A$1:$C$6" | |||
assertEquals("Sheet1 after write", "Sheet1!$A$1:$A$1", wb2.getPrintArea(1)); | |||
} | |||
} | |||
@Test | |||
public void test55384() throws Exception { | |||
Workbook wb = _testDataProvider.createWorkbook(); | |||
try { | |||
Sheet sh = wb.createSheet(); | |||
for (int rownum = 0; rownum < 10; rownum++) { | |||
org.apache.poi.ss.usermodel.Row row = sh.createRow(rownum); | |||
for (int cellnum = 0; cellnum < 3; cellnum++) { | |||
Cell cell = row.createCell(cellnum); | |||
cell.setCellValue(rownum + cellnum); | |||
} | |||
} | |||
Row row = sh.createRow(10); | |||
// setting no precalculated value works just fine. | |||
Cell cell1 = row.createCell(0); | |||
cell1.setCellFormula("SUM(A1:A10)"); | |||
// but setting a precalculated STRING value fails totally in SXSSF | |||
Cell cell2 = row.createCell(1); | |||
cell2.setCellFormula("SUM(B1:B10)"); | |||
cell2.setCellValue("55"); | |||
// setting a precalculated int value works as expected | |||
Cell cell3 = row.createCell(2); | |||
cell3.setCellFormula("SUM(C1:C10)"); | |||
cell3.setCellValue(65); | |||
assertEquals(CellType.FORMULA, cell1.getCellTypeEnum()); | |||
assertEquals(CellType.FORMULA, cell2.getCellTypeEnum()); | |||
assertEquals(CellType.FORMULA, cell3.getCellTypeEnum()); | |||
assertEquals("SUM(A1:A10)", cell1.getCellFormula()); | |||
assertEquals("SUM(B1:B10)", cell2.getCellFormula()); | |||
assertEquals("SUM(C1:C10)", cell3.getCellFormula()); | |||
/*String name = wb.getClass().getCanonicalName(); | |||
String ext = (wb instanceof HSSFWorkbook) ? ".xls" : ".xlsx"; | |||
OutputStream output = new FileOutputStream("/tmp" + name + ext); | |||
try { | |||
wb.write(output); | |||
} finally { | |||
output.close(); | |||
}*/ | |||
Workbook wbBack = _testDataProvider.writeOutAndReadBack(wb); | |||
checkFormulaPreevaluatedString(wbBack); | |||
wbBack.close(); | |||
} finally { | |||
wb.close(); | |||
} | |||
} | |||
private void checkFormulaPreevaluatedString(Workbook readFile) { | |||
Sheet sheet = readFile.getSheetAt(0); | |||
Row row = sheet.getRow(sheet.getLastRowNum()); | |||
assertEquals(10, row.getRowNum()); | |||
for (Cell cell : row) { | |||
String cellValue = null; | |||
switch (cell.getCellTypeEnum()) { | |||
case STRING: | |||
cellValue = cell.getRichStringCellValue().getString(); | |||
break; | |||
case FORMULA: | |||
cellValue = cell.getCellFormula(); | |||
break; | |||
} | |||
assertNotNull(cellValue); | |||
cellValue = cellValue.isEmpty() ? null : cellValue; | |||
assertNotNull(cellValue); | |||
} | |||
} | |||
} |
@@ -201,11 +201,7 @@ public abstract class BaseTestNamedRange { | |||
assertEquals("The sheet already contains this name: aaa", e.getMessage()); | |||
} | |||
int cnt = 0; | |||
for (int i = 0; i < wb.getNumberOfNames(); i++) { | |||
if("aaa".equals(wb.getNameAt(i).getNameName())) cnt++; | |||
} | |||
assertEquals(3, cnt); | |||
assertEquals(3, wb.getNames("aaa").size()); | |||
wb.close(); | |||
} | |||
@@ -250,11 +246,11 @@ public abstract class BaseTestNamedRange { | |||
// Write the workbook to a file | |||
// Read the Excel file and verify its content | |||
Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1); | |||
Name nm1 = wb2.getNameAt(wb2.getNameIndex("RangeTest1")); | |||
Name nm1 = wb2.getName("RangeTest1"); | |||
assertTrue("Name is "+nm1.getNameName(),"RangeTest1".equals(nm1.getNameName())); | |||
assertTrue("Reference is "+nm1.getRefersToFormula(),(wb2.getSheetName(0)+"!$A$1:$L$41").equals(nm1.getRefersToFormula())); | |||
Name nm2 = wb2.getNameAt(wb2.getNameIndex("RangeTest2")); | |||
Name nm2 = wb2.getName("RangeTest2"); | |||
assertTrue("Name is "+nm2.getNameName(),"RangeTest2".equals(nm2.getNameName())); | |||
assertTrue("Reference is "+nm2.getRefersToFormula(),(wb2.getSheetName(1)+"!$A$1:$O$21").equals(nm2.getRefersToFormula())); | |||
@@ -466,11 +462,11 @@ public abstract class BaseTestNamedRange { | |||
wb1.getNameAt(0); | |||
Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1); | |||
Name nm =wb2.getNameAt(wb2.getNameIndex("RangeTest")); | |||
Name nm =wb2.getName("RangeTest"); | |||
assertTrue("Name is "+nm.getNameName(),"RangeTest".equals(nm.getNameName())); | |||
assertTrue("Reference is "+nm.getRefersToFormula(),(wb2.getSheetName(0)+"!$D$4:$E$8").equals(nm.getRefersToFormula())); | |||
nm = wb2.getNameAt(wb2.getNameIndex("AnotherTest")); | |||
nm = wb2.getName("AnotherTest"); | |||
assertTrue("Name is "+nm.getNameName(),"AnotherTest".equals(nm.getNameName())); | |||
assertTrue("Reference is "+nm.getRefersToFormula(),newNamedRange2.getRefersToFormula().equals(nm.getRefersToFormula())); | |||
@@ -499,8 +495,7 @@ public abstract class BaseTestNamedRange { | |||
namedCell.setRefersToFormula(reference); | |||
// retrieve the newly created named range | |||
int namedCellIdx = wb.getNameIndex(cellName); | |||
Name aNamedCell = wb.getNameAt(namedCellIdx); | |||
Name aNamedCell = wb.getName(cellName); | |||
assertNotNull(aNamedCell); | |||
// retrieve the cell at the named range and test its contents | |||
@@ -540,8 +535,7 @@ public abstract class BaseTestNamedRange { | |||
namedCell.setRefersToFormula(reference); | |||
// retrieve the newly created named range | |||
int namedCellIdx = wb.getNameIndex(cname); | |||
Name aNamedCell = wb.getNameAt(namedCellIdx); | |||
Name aNamedCell = wb.getName(cname); | |||
assertNotNull(aNamedCell); | |||
// retrieve the cell at the named range and test its contents |
@@ -261,17 +261,17 @@ public abstract class BaseTestSheetShiftRows { | |||
name4.setSheetIndex(1); | |||
sheet1.shiftRows(0, 1, 2); //shift down the top row on Sheet1. | |||
name1 = wb.getNameAt(0); | |||
name1 = wb.getName("name1"); | |||
assertEquals("Sheet1!$A$3+Sheet1!$B$3", name1.getRefersToFormula()); | |||
name2 = wb.getNameAt(1); | |||
name2 = wb.getName("name2"); | |||
assertEquals("Sheet1!$A$3", name2.getRefersToFormula()); | |||
//name3 and name4 refer to Sheet2 and should not be affected | |||
name3 = wb.getNameAt(2); | |||
name3 = wb.getName("name3"); | |||
assertEquals("Sheet2!$A$1", name3.getRefersToFormula()); | |||
name4 = wb.getNameAt(3); | |||
name4 = wb.getName("name4"); | |||
assertEquals("A1", name4.getRefersToFormula()); | |||
wb.close(); |
@@ -78,14 +78,16 @@ public class BaseTestCellUtil { | |||
@Test(expected=RuntimeException.class) | |||
public void setCellStylePropertyWithInvalidValue() throws IOException { | |||
Workbook wb = _testDataProvider.createWorkbook(); | |||
Sheet s = wb.createSheet(); | |||
Row r = s.createRow(0); | |||
Cell c = r.createCell(0); | |||
try { | |||
Sheet s = wb.createSheet(); | |||
Row r = s.createRow(0); | |||
Cell c = r.createCell(0); | |||
// An invalid BorderStyle constant | |||
CellUtil.setCellStyleProperty(c, CellUtil.BORDER_BOTTOM, 42); | |||
wb.close(); | |||
// An invalid BorderStyle constant | |||
CellUtil.setCellStyleProperty(c, CellUtil.BORDER_BOTTOM, 42); | |||
} finally { | |||
wb.close(); | |||
} | |||
} | |||
@Test() | |||
@@ -352,10 +354,8 @@ public class BaseTestCellUtil { | |||
CellUtil.setFont(A1, font2); | |||
fail("setFont not allowed if font belongs to a different workbook"); | |||
} catch (final IllegalArgumentException e) { | |||
if (e.getMessage().startsWith("Font does not belong to this workbook")) { | |||
// expected | |||
} | |||
else { | |||
// one specific message is expected | |||
if (!e.getMessage().startsWith("Font does not belong to this workbook")) { | |||
throw e; | |||
} | |||
} finally { | |||
@@ -371,7 +371,7 @@ public class BaseTestCellUtil { | |||
*/ | |||
// bug 55555 | |||
@Test | |||
public void setFillForegroundColorBeforeFillBackgroundColor() { | |||
public void setFillForegroundColorBeforeFillBackgroundColor() throws IOException { | |||
Workbook wb1 = _testDataProvider.createWorkbook(); | |||
Cell A1 = wb1.createSheet().createRow(0).createCell(0); | |||
Map<String, Object> properties = new HashMap<String, Object>(); | |||
@@ -386,13 +386,14 @@ public class BaseTestCellUtil { | |||
assertEquals("fill pattern", CellStyle.BRICKS, style.getFillPattern()); | |||
assertEquals("fill foreground color", IndexedColors.BLUE, IndexedColors.fromInt(style.getFillForegroundColor())); | |||
assertEquals("fill background color", IndexedColors.RED, IndexedColors.fromInt(style.getFillBackgroundColor())); | |||
wb1.close(); | |||
} | |||
/** | |||
* bug 55555 | |||
* @since POI 3.15 beta 3 | |||
*/ | |||
@Test | |||
public void setFillForegroundColorBeforeFillBackgroundColorEnum() { | |||
public void setFillForegroundColorBeforeFillBackgroundColorEnum() throws IOException { | |||
Workbook wb1 = _testDataProvider.createWorkbook(); | |||
Cell A1 = wb1.createSheet().createRow(0).createCell(0); | |||
Map<String, Object> properties = new HashMap<String, Object>(); | |||
@@ -407,5 +408,7 @@ public class BaseTestCellUtil { | |||
assertEquals("fill pattern", FillPatternType.BRICKS, style.getFillPatternEnum()); | |||
assertEquals("fill foreground color", IndexedColors.BLUE, IndexedColors.fromInt(style.getFillForegroundColor())); | |||
assertEquals("fill background color", IndexedColors.RED, IndexedColors.fromInt(style.getFillBackgroundColor())); | |||
wb1.close(); | |||
} | |||
} |
@@ -30,16 +30,20 @@ public class DummyPOILogger extends POILogger { | |||
logged = new ArrayList<String>(); | |||
} | |||
@Override | |||
public boolean check(int level) { | |||
return true; | |||
} | |||
@Override | |||
public void initialize(String cat) {} | |||
@Override | |||
public void log(int level, Object obj1) { | |||
logged.add(level + " - " + obj1); | |||
} | |||
@Override | |||
public void log(int level, Object obj1, Throwable exception) { | |||
logged.add(level + " - " + obj1 + " - " + exception); | |||
} |
@@ -26,10 +26,6 @@ import org.junit.Test; | |||
/** | |||
* Tests the log class. | |||
* | |||
* @author Glen Stampoultzis (glens at apache.org) | |||
* @author Marc Johnson (mjohnson at apache dot org) | |||
* @author Nicola Ken Barozzi (nicolaken at apache.org) | |||
*/ | |||
public final class TestPOILogger extends POILogger { | |||
private String lastLog = ""; | |||
@@ -61,20 +57,26 @@ public final class TestPOILogger extends POILogger { | |||
POILogFactory._loggerClassName = oldLCN; | |||
} | |||
} | |||
// ---------- POI Logger methods implemented for testing ---------- | |||
@Override | |||
public void initialize(String cat) { | |||
} | |||
@Override | |||
public void log(int level, Object obj1) { | |||
lastLog = (obj1 == null) ? "" : obj1.toString(); | |||
lastEx = null; | |||
} | |||
@Override | |||
public void log(int level, Object obj1, Throwable exception) { | |||
lastLog = (obj1 == null) ? "" : obj1.toString(); | |||
lastEx = exception; | |||
} | |||
@Override | |||
public boolean check(int level) { | |||
return true; | |||
} |