merge down trunk

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hssf_cryptoapi@1755463 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-08-08 01:14:36 +00:00
parent 0bfefdfc04
commit 680683cf77
51 changed files with 1967 additions and 1249 deletions

View File

@ -17,7 +17,6 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
--> -->
<!-- <!--
This build was tested with ant 1.9.4 although it will probably work with 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. 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.output.test.dir" location="build/ooxml-test-classes"/>
<property name="ooxml.testokfile" location="build/ooxml-testokfile.txt"/> <property name="ooxml.testokfile" location="build/ooxml-testokfile.txt"/>
<property name="ooxml.lite.output.dir" location="build/ooxml-lite-classes"/> <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: --> <!-- XSSF/SXSSF subset of OOXML: -->
<property name="ooxml.ss.testokfile" location="build/ooxml-ss-testokfile.txt"/> <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.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.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.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 --> <!-- xml signature libs -->
<property name="dsig.xmlsec.jar" location="${compile.lib}/xmlsec-2.0.6.jar"/> <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"/> value="${repository.m2}/maven2/org/apache/xmlbeans/xmlbeans/2.6.0/xmlbeans-2.6.0.jar"/>
<!-- coverage libs --> <!-- coverage libs -->
<property name="jacoco.zip" location="${main.lib}/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.6.201602180812/jacoco-0.7.6.201602180812.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.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"/> <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> </condition>
<property name="findbugs.version" value="2.0.3" if:set="findbugs.jdk6"/> <property name="findbugs.version" value="2.0.3" if:set="findbugs.jdk6"/>
<property name="findbugs.version" value="3.0.1" unless: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.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"/> <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 --> <!-- this can be overwriten to empty when running with Java 9 -->
<property name="maxpermsize" value="-XX:MaxPermSize=256m"/> <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"> <path id="main.classpath">
<pathelement location="${main.commons-logging.jar}"/> <pathelement location="${main.commons-logging.jar}"/>
@ -310,6 +316,7 @@ under the License.
<pathelement location="${main.log4j.jar}"/> <pathelement location="${main.log4j.jar}"/>
<pathelement location="${main.junit.jar}"/> <pathelement location="${main.junit.jar}"/>
<pathelement location="${main.hamcrest.jar}"/> <pathelement location="${main.hamcrest.jar}"/>
<pathelement location="${main.commons-collections4.jar}"/>
</path> </path>
<path id="scratchpad.classpath"> <path id="scratchpad.classpath">
@ -498,9 +505,7 @@ under the License.
<attribute name="src"/> <attribute name="src"/>
<attribute name="dest"/> <attribute name="dest"/>
<sequential> <sequential>
<local name="exists"/> <!--fail
<available file="@{dest}" property="exists"/>
<!--fail unless:true="${exists}"
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"> 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> <condition>
<and> <and>
@ -509,7 +514,7 @@ under the License.
</and> </and>
</condition> </condition>
</fail--> </fail-->
<get src="@{src}" dest="@{dest}" unless:true="${exists}"/> <get src="@{src}" dest="@{dest}" skipexisting="true"/>
</sequential> </sequential>
</macrodef> </macrodef>
@ -531,12 +536,14 @@ under the License.
<include name="jacoco-0.7.2*"/> <include name="jacoco-0.7.2*"/>
<include name="jacoco-0.7.3*"/> <include name="jacoco-0.7.3*"/>
<include name="jacoco-0.7.4*"/> <include name="jacoco-0.7.4*"/>
<include name="jacoco-0.7.6*"/>
<include name="log4j-1.2.13*"/> <include name="log4j-1.2.13*"/>
<include name="org.jacoco.*-0.6.*"/> <include name="org.jacoco.*-0.6.*"/>
<include name="org.jacoco.*-0.7.1*"/> <include name="org.jacoco.*-0.7.1*"/>
<include name="org.jacoco.*-0.7.2*"/> <include name="org.jacoco.*-0.7.2*"/>
<include name="org.jacoco.*-0.7.3*"/> <include name="org.jacoco.*-0.7.3*"/>
<include name="org.jacoco.*-0.7.4*"/> <include name="org.jacoco.*-0.7.4*"/>
<include name="org.jacoco.*-0.7.6*"/>
<include name="dom4j*"/> <include name="dom4j*"/>
<include name="apache-rat-0.10*"/> <include name="apache-rat-0.10*"/>
<include name="xercesImpl-*.jar"/> <include name="xercesImpl-*.jar"/>
@ -587,6 +594,7 @@ under the License.
<available file="${dsig.bouncycastle-pkix.jar}"/> <available file="${dsig.bouncycastle-pkix.jar}"/>
<available file="${dsig.xmlsec.jar}"/> <available file="${dsig.xmlsec.jar}"/>
<available file="${dsig.sl4j-api.jar}"/> <available file="${dsig.sl4j-api.jar}"/>
<available file="${main.commons-collections4.jar}"/>
</and> </and>
<isset property="disconnected"/> <isset property="disconnected"/>
</or> </or>
@ -605,6 +613,7 @@ under the License.
<downloadfile src="${main.antlauncher.url}" dest="${main.antlauncher.jar}"/> <downloadfile src="${main.antlauncher.url}" dest="${main.antlauncher.jar}"/>
<downloadfile src="${asm.url}" dest="${asm.jar}"/> <downloadfile src="${asm.url}" dest="${asm.jar}"/>
<downloadfile src="${jacoco.url}" dest="${jacoco.zip}"/> <downloadfile src="${jacoco.url}" dest="${jacoco.zip}"/>
<downloadfile src="${main.commons-collections4.url}" dest="${main.commons-collections4.jar}"/>
<unzip src="${jacoco.zip}" dest="."> <unzip src="${jacoco.zip}" dest=".">
<patternset> <patternset>
<include name="lib/*.jar"/> <include name="lib/*.jar"/>
@ -1161,6 +1170,8 @@ under the License.
<group name="Main"> <group name="Main">
<classfiles> <classfiles>
<fileset dir="${main.output.dir}"> <fileset dir="${main.output.dir}">
<!-- exclude some generated classes -->
<exclude name="org/apache/poi/sl/draw/binding/*.class"/>
<!-- exclude large test-class --> <!-- exclude large test-class -->
<exclude name="org/apache/poi/hssf/usermodel/DummyGraphics2d.class"/> <exclude name="org/apache/poi/hssf/usermodel/DummyGraphics2d.class"/>
</fileset> </fileset>
@ -1242,6 +1253,8 @@ under the License.
<jvmarg value="-ea"/> <jvmarg value="-ea"/>
<jvmarg value="-Xmx256m"/> <jvmarg value="-Xmx256m"/>
<!-- jvmarg value="-Duser.timezone=UTC"/ --> <!-- jvmarg value="-Duser.timezone=UTC"/ -->
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<formatter type="plain"/> <formatter type="plain"/>
<formatter type="xml"/> <formatter type="xml"/>
<batchtest todir="${main.reports.test}"> <batchtest todir="${main.reports.test}">
@ -1258,6 +1271,16 @@ under the License.
<antcall target="-test-main-write-testfile"/> <antcall target="-test-main-write-testfile"/>
</target> </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"> <target name="-test-property-check" unless="testcase">
<echo message="Please use -Dtestcase=org.your.testcase to run a single test"/> <echo message="Please use -Dtestcase=org.your.testcase to run a single test"/>
<fail/> <fail/>
@ -1288,6 +1311,8 @@ under the License.
<jvmarg value="-ea"/> <jvmarg value="-ea"/>
<jvmarg value="-Xmx256m"/> <jvmarg value="-Xmx256m"/>
<!-- jvmarg value="-Duser.timezone=UTC"/ --> <!-- jvmarg value="-Duser.timezone=UTC"/ -->
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<formatter type="plain"/> <formatter type="plain"/>
<formatter type="xml"/> <formatter type="xml"/>
<batchtest todir="${main.reports.test}"> <batchtest todir="${main.reports.test}">
@ -1335,6 +1360,8 @@ under the License.
and on Windows with jdk-1.5.22 and on Windows with jdk-1.5.22
--> -->
<jvmarg value="-Xmx256M"/> <jvmarg value="-Xmx256M"/>
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<formatter type="plain"/> <formatter type="plain"/>
<formatter type="xml"/> <formatter type="xml"/>
<batchtest todir="${scratchpad.reports.test}"> <batchtest todir="${scratchpad.reports.test}">
@ -1373,6 +1400,8 @@ under the License.
<jvmarg value="${maxpermsize}"/> <jvmarg value="${maxpermsize}"/>
<jvmarg value="-Xmx768M"/> <jvmarg value="-Xmx768M"/>
<jvmarg value="-ea"/> <jvmarg value="-ea"/>
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<!-- jvmarg value="-Duser.timezone=UTC"/ --> <!-- jvmarg value="-Duser.timezone=UTC"/ -->
<formatter type="plain"/> <formatter type="plain"/>
<formatter type="xml"/> <formatter type="xml"/>
@ -1396,6 +1425,8 @@ under the License.
<syspropertyset refid="junit.properties"/> <syspropertyset refid="junit.properties"/>
<jvmarg value="-Xmx768M"/> <jvmarg value="-Xmx768M"/>
<jvmarg value="-ea"/> <jvmarg value="-ea"/>
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<formatter type="plain"/> <formatter type="plain"/>
<formatter type="xml"/> <formatter type="xml"/>
<batchtest todir="${ooxml.reports.test}"> <batchtest todir="${ooxml.reports.test}">
@ -1439,6 +1470,8 @@ under the License.
<jvmarg value="${maxpermsize}"/> <jvmarg value="${maxpermsize}"/>
<jvmarg value="-Xmx768M"/> <jvmarg value="-Xmx768M"/>
<jvmarg value="-ea"/> <jvmarg value="-ea"/>
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<!-- jvmarg value="-Duser.timezone=UTC"/ --> <!-- jvmarg value="-Duser.timezone=UTC"/ -->
<formatter type="plain"/> <formatter type="plain"/>
<formatter type="xml"/> <formatter type="xml"/>
@ -1485,6 +1518,8 @@ under the License.
<syspropertyset refid="junit.properties"/> <syspropertyset refid="junit.properties"/>
<jvmarg value="-ea"/> <jvmarg value="-ea"/>
<jvmarg value="-Xmx1512M"/> <jvmarg value="-Xmx1512M"/>
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<formatter type="plain"/> <formatter type="plain"/>
<formatter type="xml"/> <formatter type="xml"/>
<batchtest todir="${integration.reports.test}"> <batchtest todir="${integration.reports.test}">
@ -1503,7 +1538,20 @@ under the License.
</target> </target>
<!-- Section: test-ooxml-lite --> <!-- 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"/> <property name="ooxml.lite-merged.dir" location="build/ooxml-lite-merged"/>
<mkdir dir="${ooxml.lite-merged.dir}"/> <mkdir dir="${ooxml.lite-merged.dir}"/>
@ -1522,6 +1570,8 @@ under the License.
<syspropertyset refid="junit.properties"/> <syspropertyset refid="junit.properties"/>
<jvmarg value="${maxpermsize}"/> <jvmarg value="${maxpermsize}"/>
<jvmarg value="-Xmx512m"/> <jvmarg value="-Xmx512m"/>
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<arg value="-ooxml"/> <arg value="-ooxml"/>
<arg value="${ooxml.lite-merged.dir}/ooxml-lite-merged.jar"/> <arg value="${ooxml.lite-merged.dir}/ooxml-lite-merged.jar"/>
<arg value="-test"/> <arg value="-test"/>
@ -1529,6 +1579,8 @@ under the License.
<arg value="-dest"/> <arg value="-dest"/>
<arg value="${ooxml.lite.output.dir}"/> <arg value="${ooxml.lite.output.dir}"/>
</java> </java>
<echo file="${ooxml.lite.testokfile}" append="false" message="testok"/>
</target> </target>
<target name="test-ooxml-lite" depends="jacocotask,compile-ooxml-xsds,compile-ooxml-lite"> <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"/> <classpath refid="test.excelant.classpath"/>
<syspropertyset refid="junit.properties"/> <syspropertyset refid="junit.properties"/>
<jvmarg value="-ea"/> <jvmarg value="-ea"/>
<jvmarg value="${java9addmods}" />
<jvmarg value="${java9addmodsvalue}" />
<formatter type="plain"/> <formatter type="plain"/>
<formatter type="xml"/> <formatter type="xml"/>
<batchtest todir="${excelant.reports.test}"> <batchtest todir="${excelant.reports.test}">
@ -1868,6 +1922,7 @@ under the License.
<fileset dir="${main.lib}"> <fileset dir="${main.lib}">
<include name="commons-codec-*.jar"/> <include name="commons-codec-*.jar"/>
<include name="commons-logging-*.jar"/> <include name="commons-logging-*.jar"/>
<include name="commons-collections4-*.jar"/>
<include name="junit-*.jar"/> <include name="junit-*.jar"/>
<include name="log4j-*.jar"/> <include name="log4j-*.jar"/>
</fileset> </fileset>
@ -2083,7 +2138,7 @@ under the License.
</forbiddenapis> </forbiddenapis>
</target> </target>
<target name="findbugs" depends="assemble"> <target name="findbugs" depends="jar">
<downloadfile src="${findbugs.url}" dest="${findbugs.jar}"/> <downloadfile src="${findbugs.url}" dest="${findbugs.jar}"/>
<property name="findbugs.home" value="build/findbugs" /> <property name="findbugs.home" value="build/findbugs" />
@ -2103,11 +2158,13 @@ under the License.
output="xml:withMessages" output="xml:withMessages"
outputFile="build/findbugs.xml" outputFile="build/findbugs.xml"
effort="max" effort="max"
failOnError="true"
excludeFilter="src/resources/devtools/findbugs-filters.xml"> excludeFilter="src/resources/devtools/findbugs-filters.xml">
<fileset dir="${dist.dir}/maven"> <fileset dir="${dist.dir}/maven">
<include name="poi/poi-${version.id}.jar"/> <include name="poi/poi-${version.id}.jar"/>
<include name="poi-scratchpad/poi-scratchpad-${version.id}.jar"/> <include name="poi-scratchpad/poi-scratchpad-${version.id}.jar"/>
<include name="poi-ooxml/poi-ooxml-${version.id}.jar"/> <include name="poi-ooxml/poi-ooxml-${version.id}.jar"/>
<include name="poi-excelant/poi-excelant-${version.id}.jar"/>
</fileset> </fileset>
<auxClasspath path="${dsig.bouncycastle-pkix.jar}" /> <auxClasspath path="${dsig.bouncycastle-pkix.jar}" />
<auxClasspath path="${dsig.bouncycastle-prov.jar}" /> <auxClasspath path="${dsig.bouncycastle-prov.jar}" />
@ -2117,9 +2174,11 @@ under the License.
<auxClasspath path="${ooxml.security.jar}" /> <auxClasspath path="${ooxml.security.jar}" />
<auxClasspath path="${ooxml.curvesapi.jar}" /> <auxClasspath path="${ooxml.curvesapi.jar}" />
<auxClasspath path="${ooxml.xmlbeans26.jar}" /> <auxClasspath path="${ooxml.xmlbeans26.jar}" />
<auxClasspath path="${main.commons-collections4.jar}" />
<auxClasspath path="${main.commons-codec.jar}" /> <auxClasspath path="${main.commons-codec.jar}" />
<auxClasspath path="${main.commons-logging.jar}" /> <auxClasspath path="${main.commons-logging.jar}" />
<auxClasspath path="${main.junit.jar}" /> <auxClasspath path="${main.junit.jar}" />
<auxClasspath path="${main.ant.jar}" />
<sourcePath path="src/java" /> <sourcePath path="src/java" />
<sourcePath path="src/ooxml/java" /> <sourcePath path="src/ooxml/java" />
<sourcePath path="src/scratchpad/src" /> <sourcePath path="src/scratchpad/src" />

View File

@ -91,6 +91,11 @@
<scope>test</scope> <scope>test</scope>
<version>4.12</version> <version>4.12</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -110,6 +110,11 @@
</build> </build>
<dependencies> <dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>

View File

@ -34,184 +34,184 @@ import org.apache.poi.ss.formula.FormulaShifter;
* @author Jason Height (jheight at chariot dot net dot au) * @author Jason Height (jheight at chariot dot net dot au)
*/ */
public final class RowRecordsAggregate extends RecordAggregate { public final class RowRecordsAggregate extends RecordAggregate {
private int _firstrow = -1; private int _firstrow = -1;
private int _lastrow = -1; private int _lastrow = -1;
private final Map<Integer, RowRecord> _rowRecords; private final Map<Integer, RowRecord> _rowRecords;
private final ValueRecordsAggregate _valuesAgg; private final ValueRecordsAggregate _valuesAgg;
private final List<Record> _unknownRecords; private final List<Record> _unknownRecords;
private final SharedValueManager _sharedValueManager; private final SharedValueManager _sharedValueManager;
// Cache values to speed up performance of // Cache values to speed up performance of
// getStartRowNumberForBlock / getEndRowNumberForBlock, see Bugzilla 47405 // getStartRowNumberForBlock / getEndRowNumberForBlock, see Bugzilla 47405
private RowRecord[] _rowRecordValues = null; private RowRecord[] _rowRecordValues = null;
/** Creates a new instance of ValueRecordsAggregate */ /** Creates a new instance of ValueRecordsAggregate */
public RowRecordsAggregate() { public RowRecordsAggregate() {
this(SharedValueManager.createEmpty()); this(SharedValueManager.createEmpty());
} }
private RowRecordsAggregate(SharedValueManager svm) { private RowRecordsAggregate(SharedValueManager svm) {
if (svm == null) { if (svm == null) {
throw new IllegalArgumentException("SharedValueManager must be provided."); throw new IllegalArgumentException("SharedValueManager must be provided.");
} }
_rowRecords = new TreeMap<Integer, RowRecord>(); _rowRecords = new TreeMap<Integer, RowRecord>();
_valuesAgg = new ValueRecordsAggregate(); _valuesAgg = new ValueRecordsAggregate();
_unknownRecords = new ArrayList<Record>(); _unknownRecords = new ArrayList<Record>();
_sharedValueManager = svm; _sharedValueManager = svm;
} }
/** /**
* @param rs record stream with all {@link SharedFormulaRecord} * @param rs record stream with all {@link SharedFormulaRecord}
* {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed * {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed
* @param svm an initialised {@link SharedValueManager} (from the shared formula, array * @param svm an initialised {@link SharedValueManager} (from the shared formula, array
* and table records of the current sheet). Never <code>null</code>. * and table records of the current sheet). Never <code>null</code>.
*/ */
public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) { public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) {
this(svm); this(svm);
while(rs.hasNext()) { while(rs.hasNext()) {
Record rec = rs.getNext(); Record rec = rs.getNext();
switch (rec.getSid()) { switch (rec.getSid()) {
case RowRecord.sid: case RowRecord.sid:
insertRow((RowRecord) rec); insertRow((RowRecord) rec);
continue; continue;
case DConRefRecord.sid: case DConRefRecord.sid:
addUnknownRecord(rec); addUnknownRecord(rec);
continue; continue;
case DBCellRecord.sid: case DBCellRecord.sid:
// end of 'Row Block'. Should only occur after cell records // end of 'Row Block'. Should only occur after cell records
// ignore DBCELL records because POI generates them upon re-serialization // ignore DBCELL records because POI generates them upon re-serialization
continue; continue;
} }
if (rec instanceof UnknownRecord) { if (rec instanceof UnknownRecord) {
// might need to keep track of where exactly these belong // might need to keep track of where exactly these belong
addUnknownRecord(rec); addUnknownRecord(rec);
while (rs.peekNextSid() == ContinueRecord.sid) { while (rs.peekNextSid() == ContinueRecord.sid) {
addUnknownRecord(rs.getNext()); addUnknownRecord(rs.getNext());
} }
continue; continue;
} }
if (rec instanceof MulBlankRecord) { if (rec instanceof MulBlankRecord) {
_valuesAgg.addMultipleBlanks((MulBlankRecord) rec); _valuesAgg.addMultipleBlanks((MulBlankRecord) rec);
continue; continue;
} }
if (!(rec instanceof CellValueRecordInterface)) { if (!(rec instanceof CellValueRecordInterface)) {
throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")");
} }
_valuesAgg.construct((CellValueRecordInterface)rec, rs, svm); _valuesAgg.construct((CellValueRecordInterface)rec, rs, svm);
} }
} }
/** /**
* Handles UnknownRecords which appear within the row/cell records * Handles UnknownRecords which appear within the row/cell records
*/ */
private void addUnknownRecord(Record rec) { private void addUnknownRecord(Record rec) {
// ony a few distinct record IDs are encountered by the existing POI test cases: // ony a few distinct record IDs are encountered by the existing POI test cases:
// 0x1065 // many // 0x1065 // many
// 0x01C2 // several // 0x01C2 // several
// 0x0034 // few // 0x0034 // few
// No documentation could be found for these // No documentation could be found for these
// keep the unknown records for re-serialization // keep the unknown records for re-serialization
_unknownRecords.add(rec); _unknownRecords.add(rec);
} }
public void insertRow(RowRecord row) { public void insertRow(RowRecord row) {
// Integer integer = Integer.valueOf(row.getRowNumber()); // Integer integer = Integer.valueOf(row.getRowNumber());
_rowRecords.put(Integer.valueOf(row.getRowNumber()), row); _rowRecords.put(Integer.valueOf(row.getRowNumber()), row);
// Clear the cached values // Clear the cached values
_rowRecordValues = null; _rowRecordValues = null;
if ((row.getRowNumber() < _firstrow) || (_firstrow == -1)) { if ((row.getRowNumber() < _firstrow) || (_firstrow == -1)) {
_firstrow = row.getRowNumber(); _firstrow = row.getRowNumber();
} }
if ((row.getRowNumber() > _lastrow) || (_lastrow == -1)) { if ((row.getRowNumber() > _lastrow) || (_lastrow == -1)) {
_lastrow = row.getRowNumber(); _lastrow = row.getRowNumber();
} }
} }
public void removeRow(RowRecord row) { public void removeRow(RowRecord row) {
int rowIndex = row.getRowNumber(); int rowIndex = row.getRowNumber();
_valuesAgg.removeAllCellsValuesForRow(rowIndex); _valuesAgg.removeAllCellsValuesForRow(rowIndex);
Integer key = Integer.valueOf(rowIndex); Integer key = Integer.valueOf(rowIndex);
RowRecord rr = _rowRecords.remove(key); RowRecord rr = _rowRecords.remove(key);
if (rr == null) { if (rr == null) {
throw new RuntimeException("Invalid row index (" + key.intValue() + ")"); throw new RuntimeException("Invalid row index (" + key.intValue() + ")");
} }
if (row != rr) { if (row != rr) {
_rowRecords.put(key, rr); _rowRecords.put(key, rr);
throw new RuntimeException("Attempt to remove row that does not belong to this sheet"); throw new RuntimeException("Attempt to remove row that does not belong to this sheet");
} }
// Clear the cached values // Clear the cached values
_rowRecordValues = null; _rowRecordValues = null;
} }
public RowRecord getRow(int rowIndex) { public RowRecord getRow(int rowIndex) {
int maxrow = SpreadsheetVersion.EXCEL97.getLastRowIndex(); int maxrow = SpreadsheetVersion.EXCEL97.getLastRowIndex();
if (rowIndex < 0 || rowIndex > maxrow) { if (rowIndex < 0 || rowIndex > maxrow) {
throw new IllegalArgumentException("The row number must be between 0 and " + maxrow + ", but had: " + rowIndex); throw new IllegalArgumentException("The row number must be between 0 and " + maxrow + ", but had: " + rowIndex);
} }
return _rowRecords.get(Integer.valueOf(rowIndex)); return _rowRecords.get(Integer.valueOf(rowIndex));
} }
public int getPhysicalNumberOfRows() public int getPhysicalNumberOfRows()
{ {
return _rowRecords.size(); return _rowRecords.size();
} }
public int getFirstRowNum() public int getFirstRowNum()
{ {
return _firstrow; return _firstrow;
} }
public int getLastRowNum() public int getLastRowNum()
{ {
return _lastrow; return _lastrow;
} }
/** Returns the number of row blocks. /** Returns the number of row blocks.
* <p/>The row blocks are goupings of rows that contain the DBCell record * <p/>The row blocks are goupings of rows that contain the DBCell record
* after them * after them
*/ */
public int getRowBlockCount() { public int getRowBlockCount() {
int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE; int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE;
if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0) if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0)
size++; size++;
return size; return size;
} }
private int getRowBlockSize(int block) { private int getRowBlockSize(int block) {
return RowRecord.ENCODED_SIZE * getRowCountForBlock(block); return RowRecord.ENCODED_SIZE * getRowCountForBlock(block);
} }
/** Returns the number of physical rows within a block*/ /** Returns the number of physical rows within a block*/
public int getRowCountForBlock(int block) { public int getRowCountForBlock(int block) {
int startIndex = block * DBCellRecord.BLOCK_SIZE; int startIndex = block * DBCellRecord.BLOCK_SIZE;
int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1; int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1;
if (endIndex >= _rowRecords.size()) if (endIndex >= _rowRecords.size())
endIndex = _rowRecords.size()-1; endIndex = _rowRecords.size()-1;
return endIndex-startIndex+1; return endIndex-startIndex+1;
} }
/** Returns the physical row number of the first row in a block*/ /** Returns the physical row number of the first row in a block*/
private int getStartRowNumberForBlock(int block) { private int getStartRowNumberForBlock(int block) {
int startIndex = block * DBCellRecord.BLOCK_SIZE; int startIndex = block * DBCellRecord.BLOCK_SIZE;
if(_rowRecordValues == null){ if (_rowRecordValues == null) {
_rowRecordValues = _rowRecords.values().toArray(new RowRecord[_rowRecords.size()]); _rowRecordValues = _rowRecords.values().toArray(new RowRecord[_rowRecords.size()]);
} }
try { try {
return _rowRecordValues[startIndex].getRowNumber(); return _rowRecordValues[startIndex].getRowNumber();
} catch(ArrayIndexOutOfBoundsException e) { } 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*/ /** Returns the physical row number of the end row in a block*/
private int getEndRowNumberForBlock(int block) { private int getEndRowNumberForBlock(int block) {
int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1; int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1;
if (endIndex >= _rowRecords.size()) if (endIndex >= _rowRecords.size())
endIndex = _rowRecords.size()-1; endIndex = _rowRecords.size()-1;
if(_rowRecordValues == null){ if (_rowRecordValues == null){
_rowRecordValues = _rowRecords.values().toArray(new RowRecord[_rowRecords.size()]); _rowRecordValues = _rowRecords.values().toArray(new RowRecord[_rowRecords.size()]);
} }
@ -219,287 +219,287 @@ public final class RowRecordsAggregate extends RecordAggregate {
return _rowRecordValues[endIndex].getRowNumber(); return _rowRecordValues[endIndex].getRowNumber();
} catch(ArrayIndexOutOfBoundsException e) { } catch(ArrayIndexOutOfBoundsException e) {
throw new RuntimeException("Did not find end row for block " + block); throw new RuntimeException("Did not find end row for block " + block);
} }
} }
private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) { private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) {
final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE; final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE;
final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE;
Iterator<RowRecord> rowIterator = _rowRecords.values().iterator(); Iterator<RowRecord> rowIterator = _rowRecords.values().iterator();
//Given that we basically iterate through the rows in order, //Given that we basically iterate through the rows in order,
//For a performance improvement, it would be better to return an instance of //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 //an iterator and use that instance throughout, rather than recreating one and
//having to move it to the right position. //having to move it to the right position.
int i=0; int i=0;
for (;i<startIndex;i++) for (;i<startIndex;i++)
rowIterator.next(); rowIterator.next();
int result = 0; int result = 0;
while(rowIterator.hasNext() && (i++ < endIndex)) { while(rowIterator.hasNext() && (i++ < endIndex)) {
Record rec = rowIterator.next(); Record rec = rowIterator.next();
result += rec.getRecordSize(); result += rec.getRecordSize();
rv.visitRecord(rec); rv.visitRecord(rec);
} }
return result; return result;
} }
@Override @Override
public void visitContainedRecords(RecordVisitor rv) { public void visitContainedRecords(RecordVisitor rv) {
PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0); PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0);
//DBCells are serialized before row records. //DBCells are serialized before row records.
final int blockCount = getRowBlockCount(); final int blockCount = getRowBlockCount();
for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) {
// Serialize a block of rows. // Serialize a block of rows.
// Hold onto the position of the first row in the block // Hold onto the position of the first row in the block
int pos=0; int pos=0;
// Hold onto the size of this block that was serialized // Hold onto the size of this block that was serialized
final int rowBlockSize = visitRowRecordsForBlock(blockIndex, rv); final int rowBlockSize = visitRowRecordsForBlock(blockIndex, rv);
pos += rowBlockSize; pos += rowBlockSize;
// Serialize a block of cells for those rows // Serialize a block of cells for those rows
final int startRowNumber = getStartRowNumberForBlock(blockIndex); final int startRowNumber = getStartRowNumberForBlock(blockIndex);
final int endRowNumber = getEndRowNumberForBlock(blockIndex); final int endRowNumber = getEndRowNumberForBlock(blockIndex);
DBCellRecord.Builder dbcrBuilder = new DBCellRecord.Builder(); DBCellRecord.Builder dbcrBuilder = new DBCellRecord.Builder();
// Note: Cell references start from the second row... // Note: Cell references start from the second row...
int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE); int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE);
for (int row = startRowNumber; row <= endRowNumber; row++) { for (int row = startRowNumber; row <= endRowNumber; row++) {
if (_valuesAgg.rowHasCells(row)) { if (_valuesAgg.rowHasCells(row)) {
stv.setPosition(0); stv.setPosition(0);
_valuesAgg.visitCellsForRow(row, stv); _valuesAgg.visitCellsForRow(row, stv);
int rowCellSize = stv.getPosition(); int rowCellSize = stv.getPosition();
pos += rowCellSize; pos += rowCellSize;
// Add the offset to the first cell for the row into the // Add the offset to the first cell for the row into the
// DBCellRecord. // DBCellRecord.
dbcrBuilder.addCellOffset(cellRefOffset); dbcrBuilder.addCellOffset(cellRefOffset);
cellRefOffset = rowCellSize; cellRefOffset = rowCellSize;
} }
} }
// Calculate Offset from the start of a DBCellRecord to the first Row // Calculate Offset from the start of a DBCellRecord to the first Row
rv.visitRecord(dbcrBuilder.build(pos)); rv.visitRecord(dbcrBuilder.build(pos));
} }
for (Record _unknownRecord : _unknownRecords) { for (Record _unknownRecord : _unknownRecords) {
// Potentially breaking the file here since we don't know exactly where to write these records // Potentially breaking the file here since we don't know exactly where to write these records
rv.visitRecord(_unknownRecord); rv.visitRecord(_unknownRecord);
} }
} }
public Iterator<RowRecord> getIterator() { public Iterator<RowRecord> getIterator() {
return _rowRecords.values().iterator(); return _rowRecords.values().iterator();
} }
public int findStartOfRowOutlineGroup(int row) { public int findStartOfRowOutlineGroup(int row) {
// Find the start of the group. // Find the start of the group.
RowRecord rowRecord = this.getRow( row ); RowRecord rowRecord = this.getRow( row );
int level = rowRecord.getOutlineLevel(); int level = rowRecord.getOutlineLevel();
int currentRow = row; int currentRow = row;
while (currentRow >= 0 && this.getRow( currentRow ) != null) { while (currentRow >= 0 && this.getRow( currentRow ) != null) {
rowRecord = this.getRow( currentRow ); rowRecord = this.getRow( currentRow );
if (rowRecord.getOutlineLevel() < level) { if (rowRecord.getOutlineLevel() < level) {
return currentRow + 1; return currentRow + 1;
} }
currentRow--; currentRow--;
} }
return currentRow + 1; return currentRow + 1;
} }
public int findEndOfRowOutlineGroup(int row) { public int findEndOfRowOutlineGroup(int row) {
int level = getRow( row ).getOutlineLevel(); int level = getRow( row ).getOutlineLevel();
int currentRow; int currentRow;
for (currentRow = row; currentRow < getLastRowNum(); currentRow++) { for (currentRow = row; currentRow < getLastRowNum(); currentRow++) {
if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level) { if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level) {
break; break;
} }
} }
return currentRow-1; return currentRow-1;
} }
/** /**
* Hide all rows at or below the current outline level * 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 * @return index of the <em>next<em> row after the last row that gets hidden
*/ */
private int writeHidden(RowRecord pRowRecord, int row) { private int writeHidden(RowRecord pRowRecord, int row) {
int rowIx = row; int rowIx = row;
RowRecord rowRecord = pRowRecord; RowRecord rowRecord = pRowRecord;
int level = rowRecord.getOutlineLevel(); int level = rowRecord.getOutlineLevel();
while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) { while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) {
rowRecord.setZeroHeight(true); rowRecord.setZeroHeight(true);
rowIx++; rowIx++;
rowRecord = getRow(rowIx); rowRecord = getRow(rowIx);
} }
return rowIx; return rowIx;
} }
public void collapseRow(int rowNumber) { public void collapseRow(int rowNumber) {
// Find the start of the group. // Find the start of the group.
int startRow = findStartOfRowOutlineGroup(rowNumber); int startRow = findStartOfRowOutlineGroup(rowNumber);
RowRecord rowRecord = getRow(startRow); RowRecord rowRecord = getRow(startRow);
// Hide all the columns until the end of the group // Hide all the columns until the end of the group
int nextRowIx = writeHidden(rowRecord, startRow); int nextRowIx = writeHidden(rowRecord, startRow);
RowRecord row = getRow(nextRowIx); RowRecord row = getRow(nextRowIx);
if (row == null) { if (row == null) {
row = createRow(nextRowIx); row = createRow(nextRowIx);
insertRow(row); insertRow(row);
} }
// Write collapse field // Write collapse field
row.setColapsed(true); row.setColapsed(true);
} }
/** /**
* Create a row record. * Create a row record.
* *
* @param rowNumber row number * @param rowNumber row number
* @return RowRecord created for the passed in row number * @return RowRecord created for the passed in row number
* @see org.apache.poi.hssf.record.RowRecord * @see org.apache.poi.hssf.record.RowRecord
*/ */
public static RowRecord createRow(int rowNumber) { public static RowRecord createRow(int rowNumber) {
return new RowRecord(rowNumber); return new RowRecord(rowNumber);
} }
public boolean isRowGroupCollapsed(int row) { public boolean isRowGroupCollapsed(int row) {
int collapseRow = findEndOfRowOutlineGroup(row) + 1; int collapseRow = findEndOfRowOutlineGroup(row) + 1;
return getRow(collapseRow) != null && getRow(collapseRow).getColapsed(); return getRow(collapseRow) != null && getRow(collapseRow).getColapsed();
} }
public void expandRow(int rowNumber) { public void expandRow(int rowNumber) {
if (rowNumber == -1) if (rowNumber == -1)
return; return;
// If it is already expanded do nothing. // If it is already expanded do nothing.
if (!isRowGroupCollapsed(rowNumber)) { if (!isRowGroupCollapsed(rowNumber)) {
return; return;
} }
// Find the start of the group. // Find the start of the group.
int startIdx = findStartOfRowOutlineGroup(rowNumber); int startIdx = findStartOfRowOutlineGroup(rowNumber);
RowRecord row = getRow(startIdx); RowRecord row = getRow(startIdx);
// Find the end of the group. // Find the end of the group.
int endIdx = findEndOfRowOutlineGroup(rowNumber); int endIdx = findEndOfRowOutlineGroup(rowNumber);
// expand: // expand:
// collapsed bit must be unset // collapsed bit must be unset
// hidden bit gets unset _if_ surrounding groups are expanded you can determine // 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 // 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 // to look at the start and the end of the current group to determine which
// is the enclosing group // is the enclosing group
// hidden bit only is altered for this outline level. ie. don't un-collapse contained groups // hidden bit only is altered for this outline level. ie. don't un-collapse contained groups
if (!isRowGroupHiddenByParent(rowNumber)) { if (!isRowGroupHiddenByParent(rowNumber)) {
for (int i = startIdx; i <= endIdx; i++) { for (int i = startIdx; i <= endIdx; i++) {
RowRecord otherRow = getRow(i); RowRecord otherRow = getRow(i);
if (row.getOutlineLevel() == otherRow.getOutlineLevel() || !isRowGroupCollapsed(i)) { if (row.getOutlineLevel() == otherRow.getOutlineLevel() || !isRowGroupCollapsed(i)) {
otherRow.setZeroHeight(false); otherRow.setZeroHeight(false);
} }
} }
} }
// Write collapse field // Write collapse field
getRow(endIdx + 1).setColapsed(false); getRow(endIdx + 1).setColapsed(false);
} }
public boolean isRowGroupHiddenByParent(int row) { public boolean isRowGroupHiddenByParent(int row) {
// Look out outline details of end // Look out outline details of end
int endLevel; int endLevel;
boolean endHidden; boolean endHidden;
int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row); int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row);
if (getRow(endOfOutlineGroupIdx + 1) == null) { if (getRow(endOfOutlineGroupIdx + 1) == null) {
endLevel = 0; endLevel = 0;
endHidden = false; endHidden = false;
} else { } else {
endLevel = getRow(endOfOutlineGroupIdx + 1).getOutlineLevel(); endLevel = getRow(endOfOutlineGroupIdx + 1).getOutlineLevel();
endHidden = getRow(endOfOutlineGroupIdx + 1).getZeroHeight(); endHidden = getRow(endOfOutlineGroupIdx + 1).getZeroHeight();
} }
// Look out outline details of start // Look out outline details of start
int startLevel; int startLevel;
boolean startHidden; boolean startHidden;
int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row ); int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row );
if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null) { if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null) {
startLevel = 0; startLevel = 0;
startHidden = false; startHidden = false;
} else { } else {
startLevel = getRow(startOfOutlineGroupIdx - 1).getOutlineLevel(); startLevel = getRow(startOfOutlineGroupIdx - 1).getOutlineLevel();
startHidden = getRow(startOfOutlineGroupIdx - 1).getZeroHeight(); startHidden = getRow(startOfOutlineGroupIdx - 1).getZeroHeight();
} }
if (endLevel > startLevel) { if (endLevel > startLevel) {
return endHidden; return endHidden;
} }
return startHidden; return startHidden;
} }
/** /**
* Returns an iterator for the cell values * Returns an iterator for the cell values
*/ */
public Iterator<CellValueRecordInterface> getCellValueIterator() { public Iterator<CellValueRecordInterface> getCellValueIterator() {
return _valuesAgg.iterator(); return _valuesAgg.iterator();
} }
public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) { public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) {
IndexRecord result = new IndexRecord(); IndexRecord result = new IndexRecord();
result.setFirstRow(_firstrow); result.setFirstRow(_firstrow);
result.setLastRowAdd1(_lastrow + 1); result.setLastRowAdd1(_lastrow + 1);
// Calculate the size of the records from the end of the BOF // Calculate the size of the records from the end of the BOF
// and up to the RowRecordsAggregate... // and up to the RowRecordsAggregate...
// Add the references to the DBCells in the IndexRecord (one for each block) // 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 // Note: The offsets are relative to the Workbook BOF. Assume that this is
// 0 for now..... // 0 for now.....
int blockCount = getRowBlockCount(); int blockCount = getRowBlockCount();
// Calculate the size of this IndexRecord // Calculate the size of this IndexRecord
int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount); int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount);
int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords; int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords;
for (int block = 0; block < blockCount; block++) { for (int block = 0; block < blockCount; block++) {
// each row-block has a DBCELL record. // each row-block has a DBCELL record.
// The offset of each DBCELL record needs to be updated in the INDEX record // The offset of each DBCELL record needs to be updated in the INDEX record
// account for row records in this row-block // account for row records in this row-block
currentOffset += getRowBlockSize(block); currentOffset += getRowBlockSize(block);
// account for cell value records after those // account for cell value records after those
currentOffset += _valuesAgg.getRowCellBlockSize( currentOffset += _valuesAgg.getRowCellBlockSize(
getStartRowNumberForBlock(block), getEndRowNumberForBlock(block)); getStartRowNumberForBlock(block), getEndRowNumberForBlock(block));
// currentOffset is now the location of the DBCELL record for this row-block // currentOffset is now the location of the DBCELL record for this row-block
result.addDbcell(currentOffset); result.addDbcell(currentOffset);
// Add space required to write the DBCELL record (whose reference was just added). // Add space required to write the DBCELL record (whose reference was just added).
currentOffset += (8 + (getRowCountForBlock(block) * 2)); currentOffset += (8 + (getRowCountForBlock(block) * 2));
} }
return result; return result;
} }
public void insertCell(CellValueRecordInterface cvRec) { public void insertCell(CellValueRecordInterface cvRec) {
_valuesAgg.insertCell(cvRec); _valuesAgg.insertCell(cvRec);
} }
public void removeCell(CellValueRecordInterface cvRec) { public void removeCell(CellValueRecordInterface cvRec) {
if (cvRec instanceof FormulaRecordAggregate) { if (cvRec instanceof FormulaRecordAggregate) {
((FormulaRecordAggregate)cvRec).notifyFormulaChanging(); ((FormulaRecordAggregate)cvRec).notifyFormulaChanging();
} }
_valuesAgg.removeCell(cvRec); _valuesAgg.removeCell(cvRec);
} }
public FormulaRecordAggregate createFormula(int row, int col) { public FormulaRecordAggregate createFormula(int row, int col) {
FormulaRecord fr = new FormulaRecord(); FormulaRecord fr = new FormulaRecord();
fr.setRow(row); fr.setRow(row);
fr.setColumn((short) col); fr.setColumn((short) col);
return new FormulaRecordAggregate(fr, null, _sharedValueManager); return new FormulaRecordAggregate(fr, null, _sharedValueManager);
} }
public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) { public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) {
_valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex); _valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex);
} }
public DimensionsRecord createDimensions() { public DimensionsRecord createDimensions() {
DimensionsRecord result = new DimensionsRecord(); DimensionsRecord result = new DimensionsRecord();
result.setFirstRow(_firstrow); result.setFirstRow(_firstrow);
result.setLastRow(_lastrow); result.setLastRow(_lastrow);
result.setFirstCol((short) _valuesAgg.getFirstCellNum()); result.setFirstCol((short) _valuesAgg.getFirstCellNum());
result.setLastCol((short) _valuesAgg.getLastCellNum()); result.setLastCol((short) _valuesAgg.getLastCellNum());
return result; return result;
} }
} }

View File

@ -19,10 +19,10 @@ package org.apache.poi.hssf.usermodel;
import java.util.Map; import java.util.Map;
import org.apache.poi.ss.formula.BaseFormulaEvaluator;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment; import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
import org.apache.poi.ss.formula.IStabilityClassifier; import org.apache.poi.ss.formula.IStabilityClassifier;
import org.apache.poi.ss.formula.WorkbookEvaluator; 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.BoolEval;
import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumericValueEval; 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.CellType;
import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.FormulaEvaluator; 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.ss.usermodel.Workbook;
import org.apache.poi.util.Internal; 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 * cell values. Be sure to call {@link #clearAllCachedResultValues()} if any workbook cells are changed between
* calls to evaluate~ methods on this class. * 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; public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
private final HSSFWorkbook _book; this(workbook, null);
}
public HSSFFormulaEvaluator(HSSFWorkbook workbook) { /**
this(workbook, 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.
*/
public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) {
this(workbook, stabilityClassifier, null);
}
/**
* @param workbook The workbook to perform the formula evaluations in
* @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code>
* for the (conservative) assumption that any cell may have its definition changed after * for the (conservative) assumption that any cell may have its definition changed after
* evaluation begins. * evaluation begins.
* @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) */
*/ public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) {
private HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { this(workbook, stabilityClassifier, null);
_book = workbook; }
_bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder);
}
/** /**
* @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> * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code>
* for the (conservative) assumption that any cell may have its definition changed after * for the (conservative) assumption that any cell may have its definition changed after
* evaluation begins. * evaluation begins.
* @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) * @param udfFinder pass <code>null</code> for default (AnalysisToolPak only)
*/ */
public static HSSFFormulaEvaluator create(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { private HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) {
return new HSSFFormulaEvaluator(workbook, stabilityClassifier, 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);
}
/** /**
* Coordinates several formula evaluators together so that formulas that involve external * Coordinates several formula evaluators together so that formulas that involve external
* references can be evaluated. * references can be evaluated.
* @param workbookNames the simple file names used to identify the workbooks in formulas * @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") * 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. * @param evaluators all evaluators for the full set of workbooks required by the formulas.
*/ */
public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) { public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) {
WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length]; BaseFormulaEvaluator.setupEnvironment(workbookNames, evaluators);
for (int i = 0; i < wbEvals.length; i++) { }
wbEvals[i] = evaluators[i]._bookEvaluator;
}
CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals);
}
@Override @Override
public void setupReferencedWorkbooks(Map<String, FormulaEvaluator> evaluators) { public void setupReferencedWorkbooks(Map<String, FormulaEvaluator> evaluators) {
CollaboratingWorkbooksEnvironment.setupFormulaEvaluator(evaluators); CollaboratingWorkbooksEnvironment.setupFormulaEvaluator(evaluators);
} }
@Override /**
public WorkbookEvaluator _getWorkbookEvaluator() { * Should be called to tell the cell value cache that the specified (value or formula) cell
return _bookEvaluator; * 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 @Override
public void notifyUpdateCell(Cell cell) { public void notifyUpdateCell(Cell cell) {
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell));
} }
/** /**
* Should be called to tell the cell value cache that the specified cell has just been * Should be called to tell the cell value cache that the specified cell has just been
* deleted. * deleted.
* Failure to call this method after changing cell values will cause incorrect behaviour * Failure to call this method after changing cell values will cause incorrect behaviour
* of the evaluate~ methods of this class * of the evaluate~ methods of this class
*/ */
public void notifyDeleteCell(HSSFCell cell) { public void notifyDeleteCell(HSSFCell cell) {
_bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell)); _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell));
} }
@Override @Override
public void notifyDeleteCell(Cell cell) { 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 * Should be called to tell the cell value cache that the specified (value or formula) cell
* has changed. * has changed.
* Failure to call this method after changing cell values will cause incorrect behaviour * Failure to call this method after changing cell values will cause incorrect behaviour
* of the evaluate~ methods of this class * of the evaluate~ methods of this class
*/ */
@Override @Override
public void notifySetFormula(Cell cell) { public void notifySetFormula(Cell cell) {
_bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell));
} }
/** /**
* If cell contains a formula, the formula is evaluated and returned, * If cell contains formula, it evaluates the formula, and saves the result of the formula. The
* else the CellValue simply copies the appropriate cell value from * cell remains as a formula cell. If the cell does not contain formula, rather than throwing an
* the cell and also its cell type. This method should be preferred over * exception, this method returns {@link CellType#_NONE} and leaves the cell unchanged.
* evaluateInCell() when the call should not modify the contents of the *
* original cell. * 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.
* @param cell may be <code>null</code> signifying that the cell is not present (or blank) * <pre>
* @return <code>null</code> if the supplied cell is <code>null</code> or blank * CellType evaluatedCellType = evaluator.evaluateFormulaCell(cell);
*/ * </pre>
@Override * Be aware that your cell will hold both the formula, and the result. If you want the cell
public CellValue evaluate(Cell cell) { * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)}
if (cell == null) { * @param cell The cell to evaluate
return null; * @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();
}
switch (cell.getCellTypeEnum()) { /**
case BOOLEAN: * If cell contains formula, it evaluates the formula, and
return CellValue.valueOf(cell.getBooleanCellValue()); * puts the formula result back into the cell, in place
case ERROR: * of the old formula.
return CellValue.getError(cell.getErrorCellValue()); * Else if cell does not contain formula, this method leaves
case FORMULA: * the cell unchanged.
return evaluateFormulaCellValue(cell); * Note that the same instance of HSSFCell is returned to
case NUMERIC: * allow chained calls like:
return new CellValue(cell.getNumericCellValue()); * <pre>
case STRING: * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
return new CellValue(cell.getRichStringCellValue().getString()); * </pre>
case BLANK: * Be aware that your cell value will be changed to hold the
return null; * result of the formula. If you simply want the formula
default: * value computed for you, use {@link #evaluateFormulaCellEnum(Cell)}}
throw new IllegalStateException("Bad cell type (" + cell.getCellTypeEnum() + ")"); */
} @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 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));
}
/** /**
* If cell contains formula, it evaluates the formula, and saves the result of the formula. The * Loops over all cells in all sheets of the supplied
* cell remains as a formula cell. If the cell does not contain formula, this method returns -1 * workbook.
* and leaves the cell unchanged. * For cells that contain formulas, their formulas are
* * evaluated, and the results are saved. These cells
* Note that the type of the <em>formula result</em> is returned, so you know what kind of * remain as formula cells.
* cached formula result is also stored with the formula. * For cells that do not contain formulas, no changes
* <pre> * are made.
* int evaluatedCellType = evaluator.evaluateFormulaCell(cell); * This is a helpful wrapper around looping over all
* </pre> * cells, and calling evaluateFormulaCell on each one.
* 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)} public static void evaluateAllFormulaCells(Workbook wb) {
* @param cell The cell to evaluate BaseFormulaEvaluator.evaluateAllFormulaCells(wb);
* @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 * Loops over all cells in all sheets of the supplied
* cell remains as a formula cell. If the cell does not contain formula, rather than throwing an * workbook.
* exception, this method returns {@link CellType#_NONE} and leaves the cell unchanged. * For cells that contain formulas, their formulas are
* * evaluated, and the results are saved. These cells
* Note that the type of the <em>formula result</em> is returned, so you know what kind of * remain as formula cells.
* cached formula result is also stored with the formula. * For cells that do not contain formulas, no changes
* <pre> * are made.
* CellType evaluatedCellType = evaluator.evaluateFormulaCell(cell); * This is a helpful wrapper around looping over all
* </pre> * cells, and calling evaluateFormulaCell on each one.
* 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)} @Override
* @param cell The cell to evaluate public void evaluateAll() {
* @return {@link CellType#_NONE} for non-formula cells, or the type of the <em>formula result</em> evaluateAllFormulaCells(_book, this);
* @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 * Returns a CellValue wrapper around the supplied ValueEval instance.
* puts the formula result back into the cell, in place * @param cell
* of the old formula. */
* Else if cell does not contain formula, this method leaves protected CellValue evaluateFormulaCellValue(Cell cell) {
* the cell unchanged. ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell));
* Note that the same instance of HSSFCell is returned to if (eval instanceof BoolEval) {
* allow chained calls like: BoolEval be = (BoolEval) eval;
* <pre> return CellValue.valueOf(be.getBooleanValue());
* int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); }
* </pre> if (eval instanceof NumericValueEval) {
* Be aware that your cell value will be changed to hold the NumericValueEval ne = (NumericValueEval) eval;
* result of the formula. If you simply want the formula return new CellValue(ne.getNumberValue());
* value computed for you, use {@link #evaluateFormulaCellEnum(Cell)}} }
*/ if (eval instanceof StringValueEval) {
@Override StringValueEval ne = (StringValueEval) eval;
public HSSFCell evaluateInCell(Cell cell) { return new CellValue(ne.getStringValue());
if (cell == null) { }
return null; if (eval instanceof ErrorEval) {
} return CellValue.getError(((ErrorEval)eval).getErrorCode());
HSSFCell result = (HSSFCell) cell; }
if (cell.getCellTypeEnum() == CellType.FORMULA) { throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");
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 + ")");
}
}
/**
* 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);
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);
}
/**
* 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() + ")");
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override

View File

@ -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.AggregatingUDFFinder;
import org.apache.poi.ss.formula.udf.IndexedUDFFinder; import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder; 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.Row.MissingCellPolicy;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook; 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). * the 'active' sheet (which is the sheet with focus).
* Unselects sheets that are not in <code>indexes</code>. * 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) { public void setSelectedTabs(int[] indexes) {
Collection<Integer> list = new ArrayList<Integer>(indexes.length); 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). * the 'active' sheet (which is the sheet with focus).
* Unselects sheets that are not in <code>indexes</code>. * 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) { public void setSelectedTabs(Collection<Integer> indexes) {
@ -893,8 +894,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
*/ */
@Override @Override
public Iterator<Sheet> sheetIterator() { public Iterator<Sheet> sheetIterator() {
Iterator<Sheet> result = new SheetIterator<Sheet>(); return new SheetIterator<Sheet>();
return result;
} }
/** /**
@ -1280,9 +1280,9 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
/** /**
* Closes the underlying {@link NPOIFSFileSystem} from which * Closes the underlying {@link NPOIFSFileSystem} from which
* the Workbook was read, if any. Has no effect on Workbooks * the Workbook was read, if any.
* opened from an InputStream, or newly created ones. *
* <p>Once {@link #close()} has been called, no further * <p>Once this has been called, no further
* operations, updates or reads should be performed on the * operations, updates or reads should be performed on the
* Workbook. * Workbook.
*/ */
@ -1531,6 +1531,11 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
return names.get(nameIndex); return names.get(nameIndex);
} }
@Override
public List<HSSFName> getAllNames() {
return Collections.unmodifiableList(names);
}
public NameRecord getNameRecord(int nameIndex) { public NameRecord getNameRecord(int nameIndex) {
return getWorkbook().getNameRecord(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. * @param name the name to remove.
*/ */
void removeName(HSSFName name) { @Override
int index = getNameIndex(name); public void removeName(Name name) {
int index = getNameIndex((HSSFName) name);
removeName(index); removeName(index);
} }

View File

@ -374,21 +374,23 @@ public class CryptoFunctions {
// SET Verifier TO 0x0000 // SET Verifier TO 0x0000
short verifier = 0; short verifier = 0;
// FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER if (!"".equals(password)) {
for (int i = arrByteChars.length-1; i >= 0; i--) { // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER
// SET Verifier TO Intermediate3 BITWISE XOR PasswordByte 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 = 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; return verifier & 0xFFFF;
} }

View File

@ -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);
}
}

View File

@ -27,7 +27,7 @@ import org.apache.poi.ss.util.CellReference;
/** /**
* Provides Lazy Evaluation to a 3D Reference * Provides Lazy Evaluation to a 3D Reference
*/ */
final class LazyRefEval extends RefEvalBase { public final class LazyRefEval extends RefEvalBase {
private final SheetRangeEvaluator _evaluator; private final SheetRangeEvaluator _evaluator;
public LazyRefEval(int rowIndex, int columnIndex, SheetRangeEvaluator sre) { public LazyRefEval(int rowIndex, int columnIndex, SheetRangeEvaluator sre) {
@ -47,14 +47,17 @@ final class LazyRefEval extends RefEvalBase {
return new LazyAreaEval(area, _evaluator); return new LazyAreaEval(area, _evaluator);
} }
public boolean isSubTotal() {
SheetRefEvaluator sheetEvaluator = _evaluator.getSheetEvaluator(getFirstSheetIndex());
return sheetEvaluator.isSubTotal(getRow(), getColumn());
}
public String toString() { public String toString() {
CellReference cr = new CellReference(getRow(), getColumn()); CellReference cr = new CellReference(getRow(), getColumn());
StringBuffer sb = new StringBuffer(); return getClass().getName() + "[" +
sb.append(getClass().getName()).append("["); _evaluator.getSheetNameRange() +
sb.append(_evaluator.getSheetNameRange()); '!' +
sb.append('!'); cr.formatAsString() +
sb.append(cr.formatAsString()); "]";
sb.append("]");
return sb.toString();
} }
} }

View File

@ -19,6 +19,7 @@ package org.apache.poi.ss.formula.functions;
import static org.apache.poi.ss.formula.functions.AggregateFunction.subtotalInstance; 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.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException; import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.NotImplementedException; 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.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval; 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> * Implementation for the Excel function SUBTOTAL<p>
* *
@ -61,7 +67,6 @@ import org.apache.poi.ss.formula.eval.ValueEval;
public class Subtotal implements Function { public class Subtotal implements Function {
private static Function findFunction(int functionCode) throws EvaluationException { private static Function findFunction(int functionCode) throws EvaluationException {
Function func;
switch (functionCode) { switch (functionCode) {
case 1: return subtotalInstance(AggregateFunction.AVERAGE); case 1: return subtotalInstance(AggregateFunction.AVERAGE);
case 2: return Count.subtotalInstance(); case 2: return Count.subtotalInstance();
@ -87,7 +92,7 @@ public class Subtotal implements Function {
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
Function innerFunc; final Function innerFunc;
try { try {
ValueEval ve = OperandResolver.getSingleValue(args[0], srcRowIndex, srcColumnIndex); ValueEval ve = OperandResolver.getSingleValue(args[0], srcRowIndex, srcColumnIndex);
int functionCode = OperandResolver.coerceValueToInt(ve); int functionCode = OperandResolver.coerceValueToInt(ve);
@ -96,9 +101,24 @@ public class Subtotal implements Function {
return e.getErrorEval(); return e.getErrorEval();
} }
ValueEval[] innerArgs = new ValueEval[nInnerArgs]; // ignore the first arg, this is the function-type, we check for the length above
System.arraycopy(args, 1, innerArgs, 0, nInnerArgs); final List<ValueEval> list = new ArrayList<ValueEval>(Arrays.asList(args).subList(1, args.length));
return innerFunc.evaluate(innerArgs, srcRowIndex, srcColumnIndex); 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(list.toArray(new ValueEval[list.size()]), srcRowIndex, srcColumnIndex);
} }
} }

View File

@ -81,7 +81,7 @@ public interface CellStyle {
/** /**
* vertically justified vertical alignment * 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(); static final short VERTICAL_JUSTIFY = 0x3; //VerticalAlignment.JUSTIFY.getCode();

View File

@ -341,9 +341,11 @@ public interface Workbook extends Closeable, Iterable<Sheet> {
/** /**
* Close the underlying input resource (File or Stream), * Close the underlying input resource (File or Stream),
* from which the Workbook was read. After closing, the * from which the Workbook was read.
* Workbook should no longer be used. *
* <p>This will have no effect newly created Workbooks. * <p>Once this has been called, no further
* operations, updates or reads should be performed on the
* Workbook.
*/ */
@Override @Override
void close() throws IOException; void close() throws IOException;
@ -367,6 +369,13 @@ public interface Workbook extends Closeable, Iterable<Sheet> {
*/ */
List<? extends Name> getNames(String name); 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) * @param nameIndex position of the named range (0-based)
* @return the defined name at the specified index * @return the defined name at the specified index
@ -405,6 +414,13 @@ public interface Workbook extends Closeable, Iterable<Sheet> {
*/ */
void removeName(String name); 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 * Adds the linking required to allow formulas referencing
* the specified external workbook to be added to this one. * the specified external workbook to be added to this one.

View File

@ -27,19 +27,13 @@ import org.apache.commons.logging.LogFactory;
* developers to write log calls, while simultaneously making those * developers to write log calls, while simultaneously making those
* calls as cheap as possible by performing lazy evaluation of the log * calls as cheap as possible by performing lazy evaluation of the log
* message.<p> * 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 public class CommonsLogger extends POILogger
{ {
private static LogFactory _creator = LogFactory.getFactory(); private static LogFactory _creator = LogFactory.getFactory();
private Log log = null; private Log log = null;
@Override
public void initialize(final String cat) public void initialize(final String cat)
{ {
this.log = _creator.getInstance(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 level One of DEBUG, INFO, WARN, ERROR, FATAL
* @param obj1 The object to log. * @param obj1 The object to log.
*/ */
@Override
public void log(final int level, final Object obj1) public void log(final int level, final Object obj1)
{ {
if(level==FATAL) 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 obj1 The object to log. This is converted to a string.
* @param exception An exception to be logged * @param exception An exception to be logged
*/ */
@Override
public void log(final int level, final Object obj1, public void log(final int level, final Object obj1,
final Throwable exception) final Throwable exception)
{ {
@ -175,7 +171,7 @@ public class CommonsLogger extends POILogger
* *
* @param level One of DEBUG, INFO, WARN, ERROR, FATAL * @param level One of DEBUG, INFO, WARN, ERROR, FATAL
*/ */
@Override
public boolean check(final int level) public boolean check(final int level)
{ {
if(level==FATAL) if(level==FATAL)

View File

@ -22,14 +22,10 @@ package org.apache.poi.util;
* developers to write log calls, while simultaneously making those * developers to write log calls, while simultaneously making those
* calls as cheap as possible by performing lazy evaluation of the log * calls as cheap as possible by performing lazy evaluation of the log
* message.<p> * 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 { public class NullLogger extends POILogger {
@Override @Override
public void initialize(final String cat){ public void initialize(final String cat) {
// do nothing // do nothing
} }
@ -41,8 +37,7 @@ public class NullLogger extends POILogger {
*/ */
@Override @Override
public void log(final int level, final Object obj1) public void log(final int level, final Object obj1) {
{
// do nothing // do nothing
} }
@ -53,6 +48,7 @@ public class NullLogger extends POILogger {
* @param obj1 The object to log. This is converted to a string. * @param obj1 The object to log. This is converted to a string.
* @param exception An exception to be logged * @param exception An exception to be logged
*/ */
@Override
public void log(int level, Object obj1, final Throwable exception) { public void log(int level, Object obj1, final Throwable exception) {
// do nothing // do nothing
} }

View File

@ -24,15 +24,12 @@ package org.apache.poi.util;
* developers to write log calls, while simultaneously making those * developers to write log calls, while simultaneously making those
* calls as cheap as possible by performing lazy evaluation of the log * calls as cheap as possible by performing lazy evaluation of the log
* message. * 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 public class SystemOutLogger extends POILogger
{ {
private String _cat; private String _cat;
@Override
public void initialize(final String cat) public void initialize(final String cat)
{ {
this._cat=cat; this._cat=cat;
@ -44,7 +41,7 @@ public class SystemOutLogger extends POILogger
* @param level One of DEBUG, INFO, WARN, ERROR, FATAL * @param level One of DEBUG, INFO, WARN, ERROR, FATAL
* @param obj1 The object to log. * @param obj1 The object to log.
*/ */
@Override
public void log(final int level, final Object obj1) public void log(final int level, final Object obj1)
{ {
log(level, obj1, null); 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 obj1 The object to log. This is converted to a string.
* @param exception An exception to be logged * @param exception An exception to be logged
*/ */
@Override
@SuppressForbidden("uses printStackTrace") @SuppressForbidden("uses printStackTrace")
public void log(final int level, final Object obj1, public void log(final int level, final Object obj1,
final Throwable exception) { final Throwable exception) {
@ -78,6 +76,7 @@ public class SystemOutLogger extends POILogger
* @see #ERROR * @see #ERROR
* @see #FATAL * @see #FATAL
*/ */
@Override
public boolean check(final int level) public boolean check(final int level)
{ {
int currentLevel; int currentLevel;

View File

@ -194,6 +194,10 @@ public abstract class POIXMLDocument extends POIXMLDocumentPart implements Close
* Closes the underlying {@link OPCPackage} from which this * Closes the underlying {@link OPCPackage} from which this
* document was read, if there is one * document was read, if there is one
* *
* <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. * @throws IOException for writable packages, if an IO exception occur during the saving process.
*/ */
@Override @Override

View File

@ -382,8 +382,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
} }
// Creates a new package // Creates a new package
OPCPackage pkg = null; OPCPackage pkg = new ZipPackage();
pkg = new ZipPackage();
pkg.originalPackagePath = file.getAbsolutePath(); pkg.originalPackagePath = file.getAbsolutePath();
configurePackage(pkg); configurePackage(pkg);
@ -391,8 +390,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
} }
public static OPCPackage create(OutputStream output) { public static OPCPackage create(OutputStream output) {
OPCPackage pkg = null; OPCPackage pkg = new ZipPackage();
pkg = new ZipPackage();
pkg.originalPackagePath = null; pkg.originalPackagePath = null;
pkg.output = output; pkg.output = output;
@ -542,7 +540,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
// Create the thumbnail part name // Create the thumbnail part name
String contentType = ContentTypes String contentType = ContentTypes
.getContentTypeFromFileExtension(filename); .getContentTypeFromFileExtension(filename);
PackagePartName thumbnailPartName = null; PackagePartName thumbnailPartName;
try { try {
thumbnailPartName = PackagingURIHelper.createPartName("/docProps/" thumbnailPartName = PackagingURIHelper.createPartName("/docProps/"
+ filename); + filename);

View File

@ -29,10 +29,7 @@ import java.util.TreeMap;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException; import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException;
import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.*;
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.util.DocumentHelper; import org.apache.poi.util.DocumentHelper;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -54,7 +51,7 @@ public abstract class ContentTypeManager {
/** /**
* Content type namespace * 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 */ /* Xml elements in content type part */

View File

@ -304,13 +304,13 @@ implements XSLFShapeContainer, GroupShape<XSLFShape,XSLFTextParagraph> {
@Override @Override
public boolean getFlipHorizontal(){ public boolean getFlipHorizontal(){
CTGroupTransform2D xfrm = getXfrm(); CTGroupTransform2D xfrm = getXfrm();
return (xfrm == null || !xfrm.isSetFlipH()) ? false : xfrm.getFlipH(); return !(xfrm == null || !xfrm.isSetFlipH()) && xfrm.getFlipH();
} }
@Override @Override
public boolean getFlipVertical(){ public boolean getFlipVertical(){
CTGroupTransform2D xfrm = getXfrm(); CTGroupTransform2D xfrm = getXfrm();
return (xfrm == null || !xfrm.isSetFlipV()) ? false : xfrm.getFlipV(); return !(xfrm == null || !xfrm.isSetFlipV()) && xfrm.getFlipV();
} }
@Override @Override
@ -333,7 +333,7 @@ implements XSLFShapeContainer, GroupShape<XSLFShape,XSLFTextParagraph> {
// recursively update each shape // recursively update each shape
for(XSLFShape shape : gr.getShapes()) { for(XSLFShape shape : gr.getShapes()) {
XSLFShape newShape = null; XSLFShape newShape;
if (shape instanceof XSLFTextBox) { if (shape instanceof XSLFTextBox) {
newShape = createTextBox(); newShape = createTextBox();
} else if (shape instanceof XSLFAutoShape) { } else if (shape instanceof XSLFAutoShape) {

View File

@ -41,7 +41,6 @@ import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory; import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator; 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.CellType;
import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.util.DocumentHelper; 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.XSSFTable;
import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell; import org.apache.poi.xssf.usermodel.helpers.XSSFSingleXmlCell;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr; 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.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap; import org.w3c.dom.NamedNodeMap;
@ -118,7 +116,6 @@ public class XSSFExportToXml implements Comparator<String>{
* @throws SAXException * @throws SAXException
* @throws ParserConfigurationException * @throws ParserConfigurationException
* @throws TransformerException * @throws TransformerException
* @throws InvalidFormatException
*/ */
public void exportToXML(OutputStream os, String encoding, boolean validate) throws SAXException, ParserConfigurationException, TransformerException{ public void exportToXML(OutputStream os, String encoding, boolean validate) throws SAXException, ParserConfigurationException, TransformerException{
List<XSSFSingleXmlCell> singleXMLCells = map.getRelatedSingleXMLCell(); List<XSSFSingleXmlCell> singleXMLCells = map.getRelatedSingleXMLCell();
@ -128,10 +125,10 @@ public class XSSFExportToXml implements Comparator<String>{
Document doc = DocumentHelper.createDocument(); Document doc = DocumentHelper.createDocument();
Element root = null; final Element root;
if (isNamespaceDeclared()) { if (isNamespaceDeclared()) {
root=doc.createElementNS(getNamespace(),rootElement); root = doc.createElementNS(getNamespace(),rootElement);
} else { } else {
root = doc.createElementNS("", rootElement); root = doc.createElementNS("", rootElement);
} }
@ -152,7 +149,6 @@ public class XSSFExportToXml implements Comparator<String>{
tableMappings.put(commonXPath, table); tableMappings.put(commonXPath, table);
} }
Collections.sort(xpaths,this); Collections.sort(xpaths,this);
for(String xpath : xpaths) { for(String xpath : xpaths) {
@ -167,8 +163,7 @@ public class XSSFExportToXml implements Comparator<String>{
XSSFCell cell = simpleXmlCell.getReferencedCell(); XSSFCell cell = simpleXmlCell.getReferencedCell();
if (cell!=null) { if (cell!=null) {
Node currentNode = getNodeByXPath(xpath,doc.getFirstChild(),doc,false); Node currentNode = getNodeByXPath(xpath,doc.getFirstChild(),doc,false);
STXmlDataType.Enum dataType = simpleXmlCell.getXmlDataType(); mapCellOnNode(cell,currentNode);
mapCellOnNode(cell,currentNode,dataType);
//remove nodes which are empty in order to keep the output xml valid //remove nodes which are empty in order to keep the output xml valid
if("".equals(currentNode.getTextContent()) && currentNode.getParentNode() != null) { if("".equals(currentNode.getTextContent()) && currentNode.getParentNode() != null) {
@ -202,22 +197,15 @@ public class XSSFExportToXml implements Comparator<String>{
XSSFXmlColumnPr pointer = tableColumns.get(j-startColumnIndex); XSSFXmlColumnPr pointer = tableColumns.get(j-startColumnIndex);
String localXPath = pointer.getLocalXPath(); String localXPath = pointer.getLocalXPath();
Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false); Node currentNode = getNodeByXPath(localXPath,tableRootNode,doc,false);
STXmlDataType.Enum dataType = pointer.getXmlDataType();
mapCellOnNode(cell,currentNode);
mapCellOnNode(cell,currentNode,dataType);
} }
} }
} }
} }
} else { } /*else {
// TODO: implement filtering management in xpath // TODO: implement filtering management in xpath
} }*/
} }
boolean isValid = true; boolean isValid = true;
@ -225,8 +213,6 @@ public class XSSFExportToXml implements Comparator<String>{
isValid =isValid(doc); isValid =isValid(doc);
} }
if (isValid) { 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 =""; String value ="";
switch (cell.getCellTypeEnum()) { switch (cell.getCellTypeEnum()) {
@ -349,11 +335,7 @@ public class XSSFExportToXml implements Comparator<String>{
} }
currentNode = selectedNode; currentNode = selectedNode;
} else { } else {
currentNode = createAttribute(doc, currentNode, axisName);
Node attribute = createAttribute(doc, currentNode, axisName);
currentNode = attribute;
} }
} }
return currentNode; return currentNode;
@ -421,12 +403,11 @@ public class XSSFExportToXml implements Comparator<String>{
for(int i =1;i <minLenght; i++) { for(int i =1;i <minLenght; i++) {
String leftElementName =leftTokens[i]; String leftElementName = leftTokens[i];
String rightElementName = rightTokens[i]; String rightElementName = rightTokens[i];
if (leftElementName.equals(rightElementName)) { if (leftElementName.equals(rightElementName)) {
Node complexType = getComplexTypeForElement(leftElementName, xmlSchema,localComplexTypeRootNode); localComplexTypeRootNode = getComplexTypeForElement(leftElementName, xmlSchema, localComplexTypeRootNode);
localComplexTypeRootNode = complexType;
} else { } else {
int leftIndex = indexOfElementInComplexType(leftElementName,localComplexTypeRootNode); int leftIndex = indexOfElementInComplexType(leftElementName,localComplexTypeRootNode);
int rightIndex = indexOfElementInComplexType(rightElementName,localComplexTypeRootNode); int rightIndex = indexOfElementInComplexType(rightElementName,localComplexTypeRootNode);
@ -436,9 +417,9 @@ public class XSSFExportToXml implements Comparator<String>{
}if ( leftIndex > rightIndex) { }if ( leftIndex > rightIndex) {
return 1; return 1;
} }
} else { } /*else {
// NOTE: the xpath doesn't match correctly in the schema // 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 // Note: we expect that all the complex types are defined at root level
Node complexTypeNode = null; Node complexTypeNode = null;
if (!"".equals(complexTypeName)) { if (!"".equals(complexTypeName)) {
complexTypeNode = getComplexTypeNodeFromSchemaChildren(xmlSchema, complexTypeNode, complexTypeName); complexTypeNode = getComplexTypeNodeFromSchemaChildren(xmlSchema, null, complexTypeName);
} }
return complexTypeNode; return complexTypeNode;

View File

@ -338,7 +338,11 @@ public class SXSSFCell implements Cell {
} }
if(_value.getType()==CellType.FORMULA) 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 else
((PlainStringValue)_value).setValue(value); ((PlainStringValue)_value).setValue(value);
} else { } else {
@ -956,6 +960,7 @@ public class SXSSFCell implements Cell {
} }
/*package*/ void setFormulaType(CellType type) /*package*/ void setFormulaType(CellType type)
{ {
Value prevValue = _value;
switch(type) switch(type)
{ {
case NUMERIC: case NUMERIC:
@ -983,7 +988,13 @@ public class SXSSFCell implements Cell {
throw new IllegalArgumentException("Illegal type " + type); 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 //TODO: implement this correctly
@NotImplemented @NotImplemented
/*package*/ CellType computeTypeFromFormula(String formula) /*package*/ CellType computeTypeFromFormula(String formula)

View File

@ -893,8 +893,11 @@ public class SXSSFWorkbook implements Workbook {
/** /**
* Closes the underlying {@link XSSFWorkbook} and {@link OPCPackage} * Closes the underlying {@link XSSFWorkbook} and {@link OPCPackage}
* on which this Workbook is based, if any. Has no effect on Workbooks * on which this Workbook is based, if any.
* created from scratch. *
* <p>Once this has been called, no further
* operations, updates or reads should be performed on the
* Workbook.
*/ */
@Override @Override
public void close() throws IOException { public void close() throws IOException {
@ -1003,12 +1006,25 @@ public class SXSSFWorkbook implements Workbook {
return _wb.getNames(name); 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) * @param nameIndex position of the named range (0-based)
* @return the defined name at the specified index * @return the defined name at the specified index
* @throws IllegalArgumentException if the supplied index is invalid * @throws IllegalArgumentException if the supplied index is invalid
* @deprecated 3.16. New projects should avoid accessing named ranges by index.
*/ */
@Override @Override
@Deprecated
public Name getNameAt(int nameIndex) public Name getNameAt(int nameIndex)
{ {
return _wb.getNameAt(nameIndex); return _wb.getNameAt(nameIndex);
@ -1033,8 +1049,12 @@ public class SXSSFWorkbook implements Workbook {
* *
* @param name the name of the defined name * @param name the name of the defined name
* @return zero based index of the defined name. <code>-1</code> if not found. * @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 @Override
@Deprecated
public int getNameIndex(String name) public int getNameIndex(String name)
{ {
return _wb.getNameIndex(name); return _wb.getNameIndex(name);
@ -1044,8 +1064,11 @@ public class SXSSFWorkbook implements Workbook {
* Remove the defined name at the specified index * Remove the defined name at the specified index
* *
* @param index named range index (0 based) * @param index named range index (0 based)
*
* @deprecated 3.16. New projects should use {@link #removeName(Name)}.
*/ */
@Override @Override
@Deprecated
public void removeName(int index) public void removeName(int index)
{ {
_wb.removeName(index); _wb.removeName(index);
@ -1054,10 +1077,24 @@ public class SXSSFWorkbook implements Workbook {
/** /**
* Remove a defined name by name * 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 @Override
@Deprecated
public void removeName(String name) 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); _wb.removeName(name);
} }

View File

@ -232,7 +232,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
// Otherwise, try it as a named range // Otherwise, try it as a named range
if (sheet == null) { if (sheet == null) {
if (_uBook.getNameIndex(name) > -1) { if (!_uBook.getNames(name).isEmpty()) {
return new NameXPxg(null, name); return new NameXPxg(null, name);
} }
return null; return null;

View File

@ -17,12 +17,9 @@
package org.apache.poi.xssf.usermodel; package org.apache.poi.xssf.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.EvaluationCell; import org.apache.poi.ss.formula.EvaluationCell;
import org.apache.poi.ss.formula.WorkbookEvaluator; 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.BoolEval;
import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumberEval; 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.Cell;
import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
/** /**
* Internal POI use only - parent of XSSF and SXSSF formula evaluators * Internal POI use only - parent of XSSF and SXSSF formula evaluators
*/ */
public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, WorkbookEvaluatorProvider { public abstract class BaseXSSFFormulaEvaluator extends BaseFormulaEvaluator {
private WorkbookEvaluator _bookEvaluator;
protected BaseXSSFFormulaEvaluator(WorkbookEvaluator bookEvaluator) { 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) { public void notifySetFormula(Cell cell) {
_bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell)); _bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell));
} }
@ -63,60 +48,6 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work
_bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell)); _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, * If cell contains formula, it evaluates the formula,
* and saves the result of the formula. The cell * and saves the result of the formula. The cell
@ -164,27 +95,6 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work
setCellValue(cell, cv); 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) { private static void setCellValue(Cell cell, CellValue cv) {
CellType cellType = cv.getCellType(); CellType cellType = cv.getCellType();
@ -218,7 +128,7 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work
/** /**
* Returns a CellValue wrapper around the supplied ValueEval instance. * Returns a CellValue wrapper around the supplied ValueEval instance.
*/ */
private CellValue evaluateFormulaCellValue(Cell cell) { protected CellValue evaluateFormulaCellValue(Cell cell) {
EvaluationCell evalCell = toEvaluationCell(cell); EvaluationCell evalCell = toEvaluationCell(cell);
ValueEval eval = _bookEvaluator.evaluate(evalCell); ValueEval eval = _bookEvaluator.evaluate(evalCell);
if (eval instanceof NumberEval) { if (eval instanceof NumberEval) {
@ -238,22 +148,4 @@ public abstract class BaseXSSFFormulaEvaluator implements FormulaEvaluator, Work
} }
throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); 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);
}
} }

View File

@ -17,7 +17,7 @@
package org.apache.poi.xssf.usermodel; 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.EvaluationCell;
import org.apache.poi.ss.formula.IStabilityClassifier; import org.apache.poi.ss.formula.IStabilityClassifier;
import org.apache.poi.ss.formula.WorkbookEvaluator; import org.apache.poi.ss.formula.WorkbookEvaluator;
@ -88,7 +88,7 @@ public final class XSSFFormulaEvaluator extends BaseXSSFFormulaEvaluator {
* cells, and calling evaluateFormulaCell on each one. * cells, and calling evaluateFormulaCell on each one.
*/ */
public static void evaluateAllFormulaCells(XSSFWorkbook wb) { public static void evaluateAllFormulaCells(XSSFWorkbook wb) {
HSSFFormulaEvaluator.evaluateAllFormulaCells(wb); BaseFormulaEvaluator.evaluateAllFormulaCells(wb);
} }
/** /**
* Loops over all cells in all sheets of the supplied * 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. * cells, and calling evaluateFormulaCell on each one.
*/ */
public void evaluateAll() { public void evaluateAll() {
HSSFFormulaEvaluator.evaluateAllFormulaCells(_book); evaluateAllFormulaCells(_book, this);
} }
/** /**

View File

@ -167,19 +167,18 @@ public final class XSSFName implements Name {
public void setNameName(String name) { public void setNameName(String name) {
validateName(name); validateName(name);
String oldName = getNameName();
int sheetIndex = getSheetIndex(); int sheetIndex = getSheetIndex();
int numberOfNames = _workbook.getNumberOfNames();
//Check to ensure no other names have the same case-insensitive name at the same scope //Check to ensure no other names have the same case-insensitive name at the same scope
for (int i = 0; i < numberOfNames; i++) { for (XSSFName foundName : _workbook.getNames(name)) {
XSSFName nm = _workbook.getNameAt(i); if (foundName.getSheetIndex() == sheetIndex && foundName != this) {
if ((nm != this)
&& name.equalsIgnoreCase(nm.getNameName())
&& (sheetIndex == nm.getSheetIndex())) {
String msg = "The "+(sheetIndex == -1 ? "workbook" : "sheet")+" already contains this name: " + name; String msg = "The "+(sheetIndex == -1 ? "workbook" : "sheet")+" already contains this name: " + name;
throw new IllegalArgumentException(msg); throw new IllegalArgumentException(msg);
} }
} }
_ctName.setName(name); _ctName.setName(name);
//Need to update the name -> named ranges map
_workbook.updateName(this, oldName);
} }
public String getRefersToFormula() { public String getRefersToFormula() {

View File

@ -18,8 +18,8 @@
package org.apache.poi.xssf.usermodel; package org.apache.poi.xssf.usermodel;
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; 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.XSSFPasswordHelper.setPassword;
import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;

View File

@ -18,8 +18,8 @@
package org.apache.poi.xssf.usermodel; package org.apache.poi.xssf.usermodel;
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; 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.XSSFPasswordHelper.setPassword;
import static org.apache.poi.xssf.usermodel.helpers.XSSFPaswordHelper.validatePassword; import static org.apache.poi.xssf.usermodel.helpers.XSSFPasswordHelper.validatePassword;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -29,16 +29,20 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.xml.namespace.QName; 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.POIXMLDocument;
import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.POIXMLException; 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.AggregatingUDFFinder;
import org.apache.poi.ss.formula.udf.IndexedUDFFinder; import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder; 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;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
@ -140,6 +145,11 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
*/ */
private List<XSSFSheet> sheets; 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 * this holds the XSSFName objects attached to this workbook
*/ */
@ -442,6 +452,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
stylesSource.setWorkbook(this); stylesSource.setWorkbook(this);
namedRanges = new ArrayList<XSSFName>(); namedRanges = new ArrayList<XSSFName>();
namedRangesByName = new ArrayListValuedHashMap<String, XSSFName>();
sheets = new ArrayList<XSSFSheet>(); sheets = new ArrayList<XSSFSheet>();
pivotTables = new ArrayList<XSSFPivotTable>(); pivotTables = new ArrayList<XSSFPivotTable>();
} }
@ -733,8 +744,13 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
public XSSFName createName() { public XSSFName createName() {
CTDefinedName ctName = CTDefinedName.Factory.newInstance(); CTDefinedName ctName = CTDefinedName.Factory.newInstance();
ctName.setName(""); ctName.setName("");
return createAndStoreName(ctName);
}
private XSSFName createAndStoreName(CTDefinedName ctName) {
XSSFName name = new XSSFName(ctName, this); XSSFName name = new XSSFName(ctName, this);
namedRanges.add(name); namedRanges.add(name);
namedRangesByName.put(ctName.getName().toLowerCase(Locale.ENGLISH), name);
return name; return name;
} }
@ -938,28 +954,47 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
return stylesSource.getFontAt(idx); 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 @Override
public XSSFName getName(String name) { public XSSFName getName(String name) {
int nameIndex = getNameIndex(name); Collection<XSSFName> list = getNames(name);
if (nameIndex < 0) { if (list.isEmpty()) {
return null; 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 @Override
public List<XSSFName> getNames(String name) { public List<XSSFName> getNames(String name) {
List<XSSFName> names = new ArrayList<XSSFName>(); return Collections.unmodifiableList(namedRangesByName.get(name.toLowerCase(Locale.ENGLISH)));
for(XSSFName nr : namedRanges) {
if(nr.getNameName().equals(name)) {
names.add(nr);
}
}
return names;
} }
/**
* 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 @Override
@Deprecated
public XSSFName getNameAt(int nameIndex) { public XSSFName getNameAt(int nameIndex) {
int nNames = namedRanges.size(); int nNames = namedRanges.size();
if (nNames < 1) { if (nNames < 1) {
@ -973,21 +1008,30 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
} }
/** /**
* Gets the named range index by his name * Get a list of all the named ranges in the workbook.
* <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 in the workbook
* @return named range index
*/ */
@Override @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. <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) { public int getNameIndex(String name) {
int i = 0; XSSFName nm = getName(name);
for(XSSFName nr : namedRanges) { if (nm != null) {
if(nr.getNameName().equals(name)) { return namedRanges.indexOf(nm);
return i;
}
i++;
} }
return -1; return -1;
} }
@ -1258,22 +1302,40 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
return getPackagePart().getContentType().equals(XSSFRelation.MACROS_WORKBOOK.getContentType()); 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 @Override
@Deprecated
public void removeName(int nameIndex) { 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 @Override
@Deprecated
public void removeName(String name) { public void removeName(String name) {
int idx = 0; List<XSSFName> names = namedRangesByName.get(name.toLowerCase(Locale.ENGLISH));
for (XSSFName nm : namedRanges) { if (names.isEmpty()) {
if(nm.getNameName().equalsIgnoreCase(name)) { throw new IllegalArgumentException("Named range was not found: " + name);
removeName(idx);
return;
}
idx++;
} }
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. * (name + sheet index is unique), this method is more accurate.
* *
* @param name the name to remove. * @param name the name to remove.
*
* @throws IllegalArgumentException if the named range is not a part of this XSSFWorkbook
*/ */
void removeName(XSSFName name) { @Override
if (!namedRanges.remove(name)) { 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); 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 * Delete the printarea for the sheet specified
@ -1297,13 +1370,9 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
*/ */
@Override @Override
public void removePrintArea(int sheetIndex) { public void removePrintArea(int sheetIndex) {
int cont = 0; XSSFName name = getBuiltInName(XSSFName.BUILTIN_PRINT_AREA, sheetIndex);
for (XSSFName name : namedRanges) { if (name != null) {
if (name.getNameName().equals(XSSFName.BUILTIN_PRINT_AREA) && name.getSheetIndex() == sheetIndex) { removeName(name);
namedRanges.remove(cont);
break;
}
cont++;
} }
} }
@ -1369,17 +1438,20 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
} }
//adjust indices of names ranges //adjust indices of names ranges
for (Iterator<XSSFName> it = namedRanges.iterator(); it.hasNext();) { List<XSSFName> toRemove = new ArrayList<XSSFName>();
XSSFName nm = it.next(); for (XSSFName nm : namedRanges) {
CTDefinedName ct = nm.getCTName(); CTDefinedName ct = nm.getCTName();
if(!ct.isSetLocalSheetId()) continue; if(!ct.isSetLocalSheetId()) continue;
if (ct.getLocalSheetId() == index) { if (ct.getLocalSheetId() == index) {
it.remove(); toRemove.add(nm);
} else if (ct.getLocalSheetId() > index){ } else if (ct.getLocalSheetId() > index){
// Bump down by one, so still points at the same sheet // Bump down by one, so still points at the same sheet
ct.setLocalSheetId(ct.getLocalSheetId()-1); 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) { XSSFName getBuiltInName(String builtInCode, int sheetNumber) {
for (XSSFName name : namedRanges) { for (XSSFName name : namedRangesByName.get(builtInCode.toLowerCase(Locale.ENGLISH))) {
if (name.getNameName().equalsIgnoreCase(builtInCode) && name.getSheetIndex() == sheetNumber) { if (name.getSheetIndex() == sheetNumber) {
return name; return name;
} }
} }
@ -1537,15 +1609,12 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
nameRecord.setName(builtInName); nameRecord.setName(builtInName);
nameRecord.setLocalSheetId(sheetNumber); nameRecord.setLocalSheetId(sheetNumber);
XSSFName name = new XSSFName(nameRecord, this); if (getBuiltInName(builtInName, sheetNumber) != null) {
for (XSSFName nr : namedRanges) { throw new POIXMLException("Builtin (" + builtInName
if (nr.equals(name)) + ") already exists for sheet (" + sheetNumber + ")");
throw new POIXMLException("Builtin (" + builtInName
+ ") already exists for sheet (" + sheetNumber + ")");
} }
namedRanges.add(name); return createAndStoreName(nameRecord);
return name;
} }
/** /**
@ -1665,10 +1734,11 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
} }
private void reprocessNamedRanges() { private void reprocessNamedRanges() {
namedRangesByName = new ArrayListValuedHashMap<String, XSSFName>();
namedRanges = new ArrayList<XSSFName>(); namedRanges = new ArrayList<XSSFName>();
if(workbook.isSetDefinedNames()) { if(workbook.isSetDefinedNames()) {
for(CTDefinedName ctName : workbook.getDefinedNames().getDefinedNameArray()) { for(CTDefinedName ctName : workbook.getDefinedNames().getDefinedNameArray()) {
namedRanges.add(new XSSFName(ctName, this)); createAndStoreName(ctName);
} }
} }
} }

View File

@ -65,9 +65,7 @@ public final class XSSFFormulaUtils {
*/ */
public void updateSheetName(final int sheetIndex, final String oldName, final String newName) { public void updateSheetName(final int sheetIndex, final String oldName, final String newName) {
// update named ranges // update named ranges
final int numberOfNames = _wb.getNumberOfNames(); for (XSSFName nm : _wb.getAllNames()) {
for (int i = 0; i < numberOfNames; i++) {
XSSFName nm = _wb.getNameAt(i);
if (nm.getSheetIndex() == -1 || nm.getSheetIndex() == sheetIndex) { if (nm.getSheetIndex() == -1 || nm.getSheetIndex() == sheetIndex) {
updateName(nm, oldName, newName); updateName(nm, oldName, newName);
} }

View File

@ -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));
}
}
}

View File

@ -19,18 +19,17 @@
package org.apache.poi.xssf.usermodel.helpers; 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.poifs.crypt.HashAlgorithm;
import org.apache.xmlbeans.XmlCursor; import org.apache.poi.util.Internal;
import org.apache.poi.util.Removal;
import org.apache.xmlbeans.XmlObject; 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 { public class XSSFPaswordHelper {
/** /**
* Sets the XORed or hashed password * Sets the XORed or hashed password
@ -41,41 +40,7 @@ public class XSSFPaswordHelper {
* @param prefix the prefix of the password attributes, may be null * @param prefix the prefix of the password attributes, may be null
*/ */
public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) { public static void setPassword(XmlObject xobj, String password, HashAlgorithm hashAlgo, String prefix) {
XmlCursor cur = xobj.newCursor(); XSSFPasswordHelper.setPassword(xobj, password, hashAlgo, prefix);
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();
} }
/** /**
@ -90,41 +55,6 @@ public class XSSFPaswordHelper {
* @return true, if the hashes match * @return true, if the hashes match
*/ */
public static boolean validatePassword(XmlObject xobj, String password, String prefix) { public static boolean validatePassword(XmlObject xobj, String password, String prefix) {
// TODO: is "velvetSweatshop" the default password? return XSSFPasswordHelper.validatePassword(xobj, password, prefix);
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));
}
} }
} }

View File

@ -83,9 +83,7 @@ public final class XSSFRowShifter extends RowShifter {
public void updateNamedRanges(FormulaShifter shifter) { public void updateNamedRanges(FormulaShifter shifter) {
Workbook wb = sheet.getWorkbook(); Workbook wb = sheet.getWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create((XSSFWorkbook) wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create((XSSFWorkbook) wb);
final int numberOfNames = wb.getNumberOfNames(); for (Name name : wb.getAllNames()) {
for (int i = 0; i < numberOfNames; i++) {
Name name = wb.getNameAt(i);
String formula = name.getRefersToFormula(); String formula = name.getRefersToFormula();
int sheetIndex = name.getSheetIndex(); int sheetIndex = name.getSheetIndex();
final int rowIndex = -1; //don't care, named ranges are not allowed to include structured references final int rowIndex = -1; //don't care, named ranges are not allowed to include structured references

View File

@ -18,6 +18,7 @@
package org.apache.poi.openxml4j.opc.internal; package org.apache.poi.openxml4j.opc.internal;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@ -44,16 +45,21 @@ public final class TestContentTypeManager {
// Retrieves core properties part // Retrieves core properties part
OPCPackage p = OPCPackage.open(filepath, PackageAccess.READ); OPCPackage p = OPCPackage.open(filepath, PackageAccess.READ);
PackageRelationshipCollection rels = p.getRelationshipsByType(PackageRelationshipTypes.CORE_PROPERTIES); try {
PackageRelationship corePropertiesRelationship = rels.getRelationship(0); PackageRelationshipCollection rels = p.getRelationshipsByType(PackageRelationshipTypes.CORE_PROPERTIES);
PackagePart coreDocument = p.getPart(corePropertiesRelationship); PackageRelationship corePropertiesRelationship = rels.getRelationship(0);
PackagePart coreDocument = p.getPart(corePropertiesRelationship);
assertEquals("application/vnd.openxmlformats-package.core-properties+xml", coreDocument.getContentType()); assertEquals("application/vnd.openxmlformats-package.core-properties+xml", coreDocument.getContentType());
// TODO - finish writing this test // TODO - finish writing this test
assumeTrue("finish writing this test", false); assumeTrue("finish writing this test", false);
ContentTypeManager ctm = new ZipContentTypeManager(coreDocument.getInputStream(), p); ContentTypeManager ctm = new ZipContentTypeManager(coreDocument.getInputStream(), p);
assertNotNull(ctm);
} finally {
p.close();
}
} }
/** /**

View File

@ -115,25 +115,25 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues {
assertFalse(wb.isMacroEnabled()); assertFalse(wb.isMacroEnabled());
assertEquals(3, wb.getNumberOfNames()); assertEquals(3, wb.getNumberOfNames());
assertEquals(0, wb.getNameAt(0).getCTName().getLocalSheetId()); assertEquals(0, wb.getName("SheetAA1").getCTName().getLocalSheetId());
assertFalse(wb.getNameAt(0).getCTName().isSetLocalSheetId()); assertFalse(wb.getName("SheetAA1").getCTName().isSetLocalSheetId());
assertEquals("SheetA!$A$1", wb.getNameAt(0).getRefersToFormula()); assertEquals("SheetA!$A$1", wb.getName("SheetAA1").getRefersToFormula());
assertEquals("SheetA", wb.getNameAt(0).getSheetName()); assertEquals("SheetA", wb.getName("SheetAA1").getSheetName());
assertEquals(0, wb.getNameAt(1).getCTName().getLocalSheetId()); assertEquals(0, wb.getName("SheetBA1").getCTName().getLocalSheetId());
assertFalse(wb.getNameAt(1).getCTName().isSetLocalSheetId()); assertFalse(wb.getName("SheetBA1").getCTName().isSetLocalSheetId());
assertEquals("SheetB!$A$1", wb.getNameAt(1).getRefersToFormula()); assertEquals("SheetB!$A$1", wb.getName("SheetBA1").getRefersToFormula());
assertEquals("SheetB", wb.getNameAt(1).getSheetName()); assertEquals("SheetB", wb.getName("SheetBA1").getSheetName());
assertEquals(0, wb.getNameAt(2).getCTName().getLocalSheetId()); assertEquals(0, wb.getName("SheetCA1").getCTName().getLocalSheetId());
assertFalse(wb.getNameAt(2).getCTName().isSetLocalSheetId()); assertFalse(wb.getName("SheetCA1").getCTName().isSetLocalSheetId());
assertEquals("SheetC!$A$1", wb.getNameAt(2).getRefersToFormula()); assertEquals("SheetC!$A$1", wb.getName("SheetCA1").getRefersToFormula());
assertEquals("SheetC", wb.getNameAt(2).getSheetName()); assertEquals("SheetC", wb.getName("SheetCA1").getSheetName());
// Save and re-load, still there // Save and re-load, still there
XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb); XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb);
assertEquals(3, nwb.getNumberOfNames()); assertEquals(3, nwb.getNumberOfNames());
assertEquals("SheetA!$A$1", nwb.getNameAt(0).getRefersToFormula()); assertEquals("SheetA!$A$1", nwb.getName("SheetAA1").getRefersToFormula());
nwb.close(); nwb.close();
wb.close(); wb.close();

View File

@ -154,7 +154,9 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator {
evaluator.evaluate(cXSL_cell); evaluator.evaluate(cXSL_cell);
fail("Without a fix for #56752, shouldn't be able to evaluate a " + fail("Without a fix for #56752, shouldn't be able to evaluate a " +
"reference to a non-provided linked workbook"); "reference to a non-provided linked workbook");
} catch(Exception e) {} } catch(Exception e) {
// expected here
}
// Setup the environment // Setup the environment
Map<String,FormulaEvaluator> evaluators = new HashMap<String, FormulaEvaluator>(); Map<String,FormulaEvaluator> evaluators = new HashMap<String, FormulaEvaluator>();
@ -171,8 +173,19 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator {
evaluator.evaluate(c); evaluator.evaluate(c);
} }
} }
// And evaluate the other way too
evaluator.evaluateAll();
// Evaluate and check results // 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 specific cells and check results
assertEquals("\"Hello!\"", evaluator.evaluate(cXSLX_cell).formatAsString()); assertEquals("\"Hello!\"", evaluator.evaluate(cXSLX_cell).formatAsString());
assertEquals("\"Test A1\"", evaluator.evaluate(cXSLX_sNR).formatAsString()); assertEquals("\"Test A1\"", evaluator.evaluate(cXSLX_sNR).formatAsString());
assertEquals("142.0", evaluator.evaluate(cXSLX_gNR).formatAsString()); assertEquals("142.0", evaluator.evaluate(cXSLX_gNR).formatAsString());
@ -196,7 +209,9 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator {
try { try {
cXSLX_nw_cell.setCellFormula("[alt.xlsx]Sheet1!$A$1"); cXSLX_nw_cell.setCellFormula("[alt.xlsx]Sheet1!$A$1");
fail("New workbook not linked, shouldn't be able to add"); fail("New workbook not linked, shouldn't be able to add");
} catch (Exception e) {} } catch (Exception e) {
// expected here
}
// Link and re-try // Link and re-try
Workbook alt = new XSSFWorkbook(); Workbook alt = new XSSFWorkbook();
@ -651,4 +666,20 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator {
private Cell getCell(Sheet sheet, int rowNo, int column) { private Cell getCell(Sheet sheet, int rowNo, int column) {
return sheet.getRow(rowNo).getCell(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);
}
} }

View File

@ -53,9 +53,8 @@ public final class TestXSSFName extends BaseTestNamedRange {
//sheet.createFreezePane(0, 3); //sheet.createFreezePane(0, 3);
} }
assertEquals(1, wb.getNumberOfNames()); 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()); assertEquals("'First Sheet'!$A:$A,'First Sheet'!$1:$4", nr1.getRefersToFormula());
//remove the columns part //remove the columns part
@ -77,9 +76,8 @@ public final class TestXSSFName extends BaseTestNamedRange {
wb.close(); wb.close();
assertEquals(1, nwb.getNumberOfNames()); 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()); 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 // 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")); sheet2.setRepeatingColumns(CellRangeAddress.valueOf("B:C"));
assertEquals(2, nwb.getNumberOfNames()); 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(XSSFName.BUILTIN_PRINT_TITLE, nr2.getNameName());
assertEquals("SecondSheet!$B:$C,SecondSheet!$1:$1", nr2.getRefersToFormula()); assertEquals("SecondSheet!$B:$C,SecondSheet!$1:$1", nr2.getRefersToFormula());
@ -98,4 +96,38 @@ public final class TestXSSFName extends BaseTestNamedRange {
sheet2.setRepeatingColumns(null); sheet2.setRepeatingColumns(null);
nwb.close(); 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();
}
} }

View File

@ -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.CTXf;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCalcMode; import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCalcMode;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPane; import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPane;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STUnsignedShortHex;
public final class TestXSSFSheet extends BaseTestXSheet { public final class TestXSSFSheet extends BaseTestXSheet {
@ -1099,6 +1100,30 @@ public final class TestXSSFSheet extends BaseTestXSheet {
wb.close(); 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 @Test
public void protectSheet_lowlevel_2013() throws IOException { public void protectSheet_lowlevel_2013() throws IOException {
String password = "test"; String password = "test";

View File

@ -1140,4 +1140,44 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
wb.close(); 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();
}
} }

View File

@ -571,20 +571,39 @@ public final class HWPFDocument extends HWPFDocumentCore {
return _fields; return _fields;
} }
/**
* Warning - not currently implemented for HWPF!
*/
@Override @Override
public void write() throws IOException { public void write() throws IOException {
throw new IllegalStateException("Coming soon!"); // TODO Implement
}
@Override
public void write(File newFile) throws IOException {
throw new IllegalStateException("Coming soon!"); throw new IllegalStateException("Coming soon!");
} }
/** /**
* Writes out the word file that is represented by an instance of this class. * 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 * If the {@link File} exists, it will be replaced, otherwise a new one
* or has a high cost/latency associated with each written byte, * 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 {
NPOIFSFileSystem pfs = POIFSFileSystem.create(newFile);
write(pfs, true);
pfs.writeFilesystem();
}
/**
* Writes out the word file that is represented by an instance of this class.
*
* 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} * consider wrapping the OutputStream in a {@link java.io.BufferedOutputStream}
* to improve write performance. * 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 * @throws IOException If there is an unexpected IOException from the passed
* in OutputStream. * in OutputStream.
*/ */
public void write(OutputStream out) public void write(OutputStream out) throws IOException {
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. // initialize our streams for writing.
HWPFFileSystem docSys = new HWPFFileSystem(); HWPFFileSystem docSys = new HWPFFileSystem();
HWPFOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT); HWPFOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT);
@ -891,7 +913,8 @@ public final class HWPFDocument extends HWPFDocumentCore {
} }
// create new document preserving order of entries // 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 docWritten = false;
boolean dataWritten = false; boolean dataWritten = false;
boolean objectPoolWritten = false; boolean objectPoolWritten = false;
@ -967,7 +990,6 @@ public final class HWPFDocument extends HWPFDocumentCore {
if ( !objectPoolWritten ) if ( !objectPoolWritten )
_objectPool.writeTo( pfs.getRoot() ); _objectPool.writeTo( pfs.getRoot() );
pfs.writeFilesystem( out );
this.directory = pfs.getRoot(); this.directory = pfs.getRoot();
/* /*

View File

@ -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
}

View File

@ -50,14 +50,20 @@ public abstract class BaseTestSlideShow {
@Test @Test
public void addPicture_Stream() throws IOException { public void addPicture_Stream() throws IOException {
SlideShow<?,?> show = createSlideShow(); SlideShow<?,?> show = createSlideShow();
InputStream stream = slTests.openResourceAsStream("clock.jpg"); 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));
assertEquals(0, show.getPictureData().size()); } finally {
PictureData picture = show.addPicture(stream, PictureType.JPEG); stream.close();
assertEquals(1, show.getPictureData().size()); }
assertSame(picture, show.getPictureData().get(0)); } finally {
show.close();
show.close(); }
} }
@Test @Test

View File

@ -27,6 +27,7 @@ import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.eval.ErrorEval; 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.Cell;
import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.CellValue;
@ -105,6 +106,7 @@ public final class TestIndirect {
// non-error cases // non-error cases
confirm(feA, c, "INDIRECT(\"C2\")", 23); confirm(feA, c, "INDIRECT(\"C2\")", 23);
confirm(feA, c, "INDIRECT(\"C2\", TRUE)", 23);
confirm(feA, c, "INDIRECT(\"$C2\")", 23); confirm(feA, c, "INDIRECT(\"$C2\")", 23);
confirm(feA, c, "INDIRECT(\"C$2\")", 23); confirm(feA, c, "INDIRECT(\"C$2\")", 23);
confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref
@ -203,4 +205,9 @@ public final class TestIndirect {
+ "' but got '" + cv.formatAsString() + "'."); + "' but got '" + cv.formatAsString() + "'.");
} }
} }
@Test
public void testInvalidInput() {
assertEquals(ErrorEval.VALUE_INVALID, Indirect.instance.evaluate(new ValueEval[] {}, null));
}
} }

View File

@ -20,9 +20,8 @@ package org.apache.poi.ss.formula.functions;
import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.eval.AreaEval; import org.apache.poi.ss.formula.FormulaParseException;
import org.apache.poi.ss.formula.eval.NumberEval; import org.apache.poi.ss.formula.eval.*;
import org.apache.poi.ss.formula.eval.ValueEval;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.usermodel.*;
@ -75,7 +74,6 @@ public final class TestSubtotal extends TestCase {
} }
public void testAvg(){ public void testAvg(){
Workbook wb = new HSSFWorkbook(); Workbook wb = new HSSFWorkbook();
FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator();
@ -95,16 +93,18 @@ public final class TestSubtotal extends TestCase {
a6.setCellFormula("SUBTOTAL(1,B2:B6)*2 + 2"); a6.setCellFormula("SUBTOTAL(1,B2:B6)*2 + 2");
Cell a7 = sh.createRow(7).createCell(1); Cell a7 = sh.createRow(7).createCell(1);
a7.setCellFormula("SUBTOTAL(1,B2:B7)"); 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(); fe.evaluateAll();
assertEquals(2.0, a3.getNumericCellValue()); assertEquals(2.0, a3.getNumericCellValue());
assertEquals(8.0, a6.getNumericCellValue()); assertEquals(8.0, a6.getNumericCellValue());
assertEquals(3.0, a7.getNumericCellValue()); assertEquals(3.0, a7.getNumericCellValue());
assertEquals(3.0, a8.getNumericCellValue());
} }
public void testSum(){ public void testSum(){
Workbook wb = new HSSFWorkbook(); Workbook wb = new HSSFWorkbook();
FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator();
@ -124,12 +124,15 @@ public final class TestSubtotal extends TestCase {
a6.setCellFormula("SUBTOTAL(9,B2:B6)*2 + 2"); a6.setCellFormula("SUBTOTAL(9,B2:B6)*2 + 2");
Cell a7 = sh.createRow(7).createCell(1); Cell a7 = sh.createRow(7).createCell(1);
a7.setCellFormula("SUBTOTAL(9,B2:B7)"); 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(); fe.evaluateAll();
assertEquals(4.0, a3.getNumericCellValue()); assertEquals(4.0, a3.getNumericCellValue());
assertEquals(26.0, a6.getNumericCellValue()); assertEquals(26.0, a6.getNumericCellValue());
assertEquals(12.0, a7.getNumericCellValue()); assertEquals(12.0, a7.getNumericCellValue());
assertEquals(12.0, a8.getNumericCellValue());
} }
public void testCount(){ public void testCount(){
@ -147,18 +150,21 @@ public final class TestSubtotal extends TestCase {
a3.setCellFormula("SUBTOTAL(2,B2:B3)"); a3.setCellFormula("SUBTOTAL(2,B2:B3)");
Cell a4 = sh.createRow(4).createCell(1); Cell a4 = sh.createRow(4).createCell(1);
a4.setCellValue("POI"); // A4 is string and not counted 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); Cell a6 = sh.createRow(6).createCell(1);
a6.setCellFormula("SUBTOTAL(2,B2:B6)*2 + 2"); a6.setCellFormula("SUBTOTAL(2,B2:B6)*2 + 2");
Cell a7 = sh.createRow(7).createCell(1); Cell a7 = sh.createRow(7).createCell(1);
a7.setCellFormula("SUBTOTAL(2,B2:B7)"); 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(); fe.evaluateAll();
assertEquals(2.0, a3.getNumericCellValue()); assertEquals(2.0, a3.getNumericCellValue());
assertEquals(6.0, a6.getNumericCellValue()); assertEquals(6.0, a6.getNumericCellValue());
assertEquals(2.0, a7.getNumericCellValue()); assertEquals(2.0, a7.getNumericCellValue());
assertEquals(2.0, a8.getNumericCellValue());
} }
public void testCounta(){ public void testCounta(){
@ -176,18 +182,21 @@ public final class TestSubtotal extends TestCase {
a3.setCellFormula("SUBTOTAL(3,B2:B3)"); a3.setCellFormula("SUBTOTAL(3,B2:B3)");
Cell a4 = sh.createRow(4).createCell(1); Cell a4 = sh.createRow(4).createCell(1);
a4.setCellValue("POI"); // A4 is string and not counted 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); Cell a6 = sh.createRow(6).createCell(1);
a6.setCellFormula("SUBTOTAL(3,B2:B6)*2 + 2"); a6.setCellFormula("SUBTOTAL(3,B2:B6)*2 + 2");
Cell a7 = sh.createRow(7).createCell(1); Cell a7 = sh.createRow(7).createCell(1);
a7.setCellFormula("SUBTOTAL(3,B2:B7)"); 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(); fe.evaluateAll();
assertEquals(2.0, a3.getNumericCellValue()); assertEquals(2.0, a3.getNumericCellValue());
assertEquals(8.0, a6.getNumericCellValue()); assertEquals(8.0, a6.getNumericCellValue());
assertEquals(3.0, a7.getNumericCellValue()); assertEquals(3.0, a7.getNumericCellValue());
assertEquals(3.0, a8.getNumericCellValue());
} }
public void testMax(){ public void testMax(){
@ -211,12 +220,15 @@ public final class TestSubtotal extends TestCase {
a6.setCellFormula("SUBTOTAL(4,B2:B6)*2 + 2"); a6.setCellFormula("SUBTOTAL(4,B2:B6)*2 + 2");
Cell a7 = sh.createRow(7).createCell(1); Cell a7 = sh.createRow(7).createCell(1);
a7.setCellFormula("SUBTOTAL(4,B2:B7)"); 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(); fe.evaluateAll();
assertEquals(3.0, a3.getNumericCellValue()); assertEquals(3.0, a3.getNumericCellValue());
assertEquals(16.0, a6.getNumericCellValue()); assertEquals(16.0, a6.getNumericCellValue());
assertEquals(7.0, a7.getNumericCellValue()); assertEquals(7.0, a7.getNumericCellValue());
assertEquals(7.0, a8.getNumericCellValue());
} }
public void testMin(){ public void testMin(){
@ -240,12 +252,15 @@ public final class TestSubtotal extends TestCase {
a6.setCellFormula("SUBTOTAL(5,B2:B6)*2 + 2"); a6.setCellFormula("SUBTOTAL(5,B2:B6)*2 + 2");
Cell a7 = sh.createRow(7).createCell(1); Cell a7 = sh.createRow(7).createCell(1);
a7.setCellFormula("SUBTOTAL(5,B2:B7)"); 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(); fe.evaluateAll();
assertEquals(1.0, a3.getNumericCellValue()); assertEquals(1.0, a3.getNumericCellValue());
assertEquals(4.0, a6.getNumericCellValue()); assertEquals(4.0, a6.getNumericCellValue());
assertEquals(1.0, a7.getNumericCellValue()); assertEquals(1.0, a7.getNumericCellValue());
assertEquals(1.0, a8.getNumericCellValue());
} }
public void testStdev(){ public void testStdev(){
@ -269,12 +284,15 @@ public final class TestSubtotal extends TestCase {
a6.setCellFormula("SUBTOTAL(7,B2:B6)*2 + 2"); a6.setCellFormula("SUBTOTAL(7,B2:B6)*2 + 2");
Cell a7 = sh.createRow(7).createCell(1); Cell a7 = sh.createRow(7).createCell(1);
a7.setCellFormula("SUBTOTAL(7,B2:B7)"); 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(); fe.evaluateAll();
assertEquals(1.41421, a3.getNumericCellValue(), 0.0001); assertEquals(1.41421, a3.getNumericCellValue(), 0.0001);
assertEquals(7.65685, a6.getNumericCellValue(), 0.0001); assertEquals(7.65685, a6.getNumericCellValue(), 0.0001);
assertEquals(2.82842, a7.getNumericCellValue(), 0.0001); assertEquals(2.82842, a7.getNumericCellValue(), 0.0001);
assertEquals(2.82842, a8.getNumericCellValue(), 0.0001);
} }
public void test50209(){ 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(COUNT;B2:B8,C2:C8)", cellC2, 3.0);
confirmExpectedResult(evaluator, "SUBTOTAL(COUNTA;B2:B8,C2:C8)", cellC3, 5.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));
}
} }

View File

@ -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));
}
}

View File

@ -33,9 +33,12 @@ import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute; import java.awt.font.TextAttribute;
import java.awt.font.TextLayout; import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.text.AttributedString; import java.text.AttributedString;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.Assert.*; 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" 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)); 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);
}
}
} }

View File

@ -201,11 +201,7 @@ public abstract class BaseTestNamedRange {
assertEquals("The sheet already contains this name: aaa", e.getMessage()); assertEquals("The sheet already contains this name: aaa", e.getMessage());
} }
int cnt = 0; assertEquals(3, wb.getNames("aaa").size());
for (int i = 0; i < wb.getNumberOfNames(); i++) {
if("aaa".equals(wb.getNameAt(i).getNameName())) cnt++;
}
assertEquals(3, cnt);
wb.close(); wb.close();
} }
@ -250,11 +246,11 @@ public abstract class BaseTestNamedRange {
// Write the workbook to a file // Write the workbook to a file
// Read the Excel file and verify its content // Read the Excel file and verify its content
Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1); 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("Name is "+nm1.getNameName(),"RangeTest1".equals(nm1.getNameName()));
assertTrue("Reference is "+nm1.getRefersToFormula(),(wb2.getSheetName(0)+"!$A$1:$L$41").equals(nm1.getRefersToFormula())); 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("Name is "+nm2.getNameName(),"RangeTest2".equals(nm2.getNameName()));
assertTrue("Reference is "+nm2.getRefersToFormula(),(wb2.getSheetName(1)+"!$A$1:$O$21").equals(nm2.getRefersToFormula())); 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); wb1.getNameAt(0);
Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1); 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("Name is "+nm.getNameName(),"RangeTest".equals(nm.getNameName()));
assertTrue("Reference is "+nm.getRefersToFormula(),(wb2.getSheetName(0)+"!$D$4:$E$8").equals(nm.getRefersToFormula())); 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("Name is "+nm.getNameName(),"AnotherTest".equals(nm.getNameName()));
assertTrue("Reference is "+nm.getRefersToFormula(),newNamedRange2.getRefersToFormula().equals(nm.getRefersToFormula())); assertTrue("Reference is "+nm.getRefersToFormula(),newNamedRange2.getRefersToFormula().equals(nm.getRefersToFormula()));
@ -499,8 +495,7 @@ public abstract class BaseTestNamedRange {
namedCell.setRefersToFormula(reference); namedCell.setRefersToFormula(reference);
// retrieve the newly created named range // retrieve the newly created named range
int namedCellIdx = wb.getNameIndex(cellName); Name aNamedCell = wb.getName(cellName);
Name aNamedCell = wb.getNameAt(namedCellIdx);
assertNotNull(aNamedCell); assertNotNull(aNamedCell);
// retrieve the cell at the named range and test its contents // retrieve the cell at the named range and test its contents
@ -540,8 +535,7 @@ public abstract class BaseTestNamedRange {
namedCell.setRefersToFormula(reference); namedCell.setRefersToFormula(reference);
// retrieve the newly created named range // retrieve the newly created named range
int namedCellIdx = wb.getNameIndex(cname); Name aNamedCell = wb.getName(cname);
Name aNamedCell = wb.getNameAt(namedCellIdx);
assertNotNull(aNamedCell); assertNotNull(aNamedCell);
// retrieve the cell at the named range and test its contents // retrieve the cell at the named range and test its contents

View File

@ -261,17 +261,17 @@ public abstract class BaseTestSheetShiftRows {
name4.setSheetIndex(1); name4.setSheetIndex(1);
sheet1.shiftRows(0, 1, 2); //shift down the top row on Sheet1. 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()); assertEquals("Sheet1!$A$3+Sheet1!$B$3", name1.getRefersToFormula());
name2 = wb.getNameAt(1); name2 = wb.getName("name2");
assertEquals("Sheet1!$A$3", name2.getRefersToFormula()); assertEquals("Sheet1!$A$3", name2.getRefersToFormula());
//name3 and name4 refer to Sheet2 and should not be affected //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()); assertEquals("Sheet2!$A$1", name3.getRefersToFormula());
name4 = wb.getNameAt(3); name4 = wb.getName("name4");
assertEquals("A1", name4.getRefersToFormula()); assertEquals("A1", name4.getRefersToFormula());
wb.close(); wb.close();

View File

@ -78,14 +78,16 @@ public class BaseTestCellUtil {
@Test(expected=RuntimeException.class) @Test(expected=RuntimeException.class)
public void setCellStylePropertyWithInvalidValue() throws IOException { public void setCellStylePropertyWithInvalidValue() throws IOException {
Workbook wb = _testDataProvider.createWorkbook(); Workbook wb = _testDataProvider.createWorkbook();
Sheet s = wb.createSheet(); try {
Row r = s.createRow(0); Sheet s = wb.createSheet();
Cell c = r.createCell(0); Row r = s.createRow(0);
Cell c = r.createCell(0);
// An invalid BorderStyle constant // An invalid BorderStyle constant
CellUtil.setCellStyleProperty(c, CellUtil.BORDER_BOTTOM, 42); CellUtil.setCellStyleProperty(c, CellUtil.BORDER_BOTTOM, 42);
} finally {
wb.close(); wb.close();
}
} }
@Test() @Test()
@ -352,10 +354,8 @@ public class BaseTestCellUtil {
CellUtil.setFont(A1, font2); CellUtil.setFont(A1, font2);
fail("setFont not allowed if font belongs to a different workbook"); fail("setFont not allowed if font belongs to a different workbook");
} catch (final IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
if (e.getMessage().startsWith("Font does not belong to this workbook")) { // one specific message is expected
// expected if (!e.getMessage().startsWith("Font does not belong to this workbook")) {
}
else {
throw e; throw e;
} }
} finally { } finally {
@ -371,7 +371,7 @@ public class BaseTestCellUtil {
*/ */
// bug 55555 // bug 55555
@Test @Test
public void setFillForegroundColorBeforeFillBackgroundColor() { public void setFillForegroundColorBeforeFillBackgroundColor() throws IOException {
Workbook wb1 = _testDataProvider.createWorkbook(); Workbook wb1 = _testDataProvider.createWorkbook();
Cell A1 = wb1.createSheet().createRow(0).createCell(0); Cell A1 = wb1.createSheet().createRow(0).createCell(0);
Map<String, Object> properties = new HashMap<String, Object>(); Map<String, Object> properties = new HashMap<String, Object>();
@ -386,13 +386,14 @@ public class BaseTestCellUtil {
assertEquals("fill pattern", CellStyle.BRICKS, style.getFillPattern()); assertEquals("fill pattern", CellStyle.BRICKS, style.getFillPattern());
assertEquals("fill foreground color", IndexedColors.BLUE, IndexedColors.fromInt(style.getFillForegroundColor())); assertEquals("fill foreground color", IndexedColors.BLUE, IndexedColors.fromInt(style.getFillForegroundColor()));
assertEquals("fill background color", IndexedColors.RED, IndexedColors.fromInt(style.getFillBackgroundColor())); assertEquals("fill background color", IndexedColors.RED, IndexedColors.fromInt(style.getFillBackgroundColor()));
wb1.close();
} }
/** /**
* bug 55555 * bug 55555
* @since POI 3.15 beta 3 * @since POI 3.15 beta 3
*/ */
@Test @Test
public void setFillForegroundColorBeforeFillBackgroundColorEnum() { public void setFillForegroundColorBeforeFillBackgroundColorEnum() throws IOException {
Workbook wb1 = _testDataProvider.createWorkbook(); Workbook wb1 = _testDataProvider.createWorkbook();
Cell A1 = wb1.createSheet().createRow(0).createCell(0); Cell A1 = wb1.createSheet().createRow(0).createCell(0);
Map<String, Object> properties = new HashMap<String, Object>(); Map<String, Object> properties = new HashMap<String, Object>();
@ -407,5 +408,7 @@ public class BaseTestCellUtil {
assertEquals("fill pattern", FillPatternType.BRICKS, style.getFillPatternEnum()); assertEquals("fill pattern", FillPatternType.BRICKS, style.getFillPatternEnum());
assertEquals("fill foreground color", IndexedColors.BLUE, IndexedColors.fromInt(style.getFillForegroundColor())); assertEquals("fill foreground color", IndexedColors.BLUE, IndexedColors.fromInt(style.getFillForegroundColor()));
assertEquals("fill background color", IndexedColors.RED, IndexedColors.fromInt(style.getFillBackgroundColor())); assertEquals("fill background color", IndexedColors.RED, IndexedColors.fromInt(style.getFillBackgroundColor()));
wb1.close();
} }
} }

View File

@ -30,16 +30,20 @@ public class DummyPOILogger extends POILogger {
logged = new ArrayList<String>(); logged = new ArrayList<String>();
} }
@Override
public boolean check(int level) { public boolean check(int level) {
return true; return true;
} }
@Override
public void initialize(String cat) {} public void initialize(String cat) {}
@Override
public void log(int level, Object obj1) { public void log(int level, Object obj1) {
logged.add(level + " - " + obj1); logged.add(level + " - " + obj1);
} }
@Override
public void log(int level, Object obj1, Throwable exception) { public void log(int level, Object obj1, Throwable exception) {
logged.add(level + " - " + obj1 + " - " + exception); logged.add(level + " - " + obj1 + " - " + exception);
} }

View File

@ -26,10 +26,6 @@ import org.junit.Test;
/** /**
* Tests the log class. * 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 { public final class TestPOILogger extends POILogger {
private String lastLog = ""; private String lastLog = "";
@ -62,19 +58,25 @@ public final class TestPOILogger extends POILogger {
} }
} }
// ---------- POI Logger methods implemented for testing ----------
@Override
public void initialize(String cat) { public void initialize(String cat) {
} }
@Override
public void log(int level, Object obj1) { public void log(int level, Object obj1) {
lastLog = (obj1 == null) ? "" : obj1.toString(); lastLog = (obj1 == null) ? "" : obj1.toString();
lastEx = null; lastEx = null;
} }
@Override
public void log(int level, Object obj1, Throwable exception) { public void log(int level, Object obj1, Throwable exception) {
lastLog = (obj1 == null) ? "" : obj1.toString(); lastLog = (obj1 == null) ? "" : obj1.toString();
lastEx = exception; lastEx = exception;
} }
@Override
public boolean check(int level) { public boolean check(int level) {
return true; return true;
} }

Binary file not shown.