diff --git a/main/HSSF/Record/HeaderFooterBase.cs b/main/HSSF/Record/HeaderFooterBase.cs index 1e53ec4af..f133e50f8 100644 --- a/main/HSSF/Record/HeaderFooterBase.cs +++ b/main/HSSF/Record/HeaderFooterBase.cs @@ -18,13 +18,13 @@ limitations under the License. namespace NPOI.HSSF.Record { using System; -using NPOI.Util; + using NPOI.Util; -/** - * Common header/footer base class - * - * @author Josh Micich - */ + /** + * Common header/footer base class + * + * @author Josh Micich + */ public abstract class HeaderFooterBase : StandardRecord { private bool field_2_hasMultibyte; @@ -40,6 +40,15 @@ protected HeaderFooterBase(RecordInputStream in1) if (in1.Remaining > 0) { int field_1_footer_len = in1.ReadShort(); + //61287 -- if the footer_len == 0, there may not be a multibyte flag + if (field_1_footer_len == 0) + { + field_3_text = ""; + if (in1.Remaining == 0) + { + return; + } + } field_2_hasMultibyte = in1.ReadByte() != 0x00; if (field_2_hasMultibyte) diff --git a/main/HSSF/Record/WriteProtectRecord.cs b/main/HSSF/Record/WriteProtectRecord.cs index 4bcde052f..5841a572b 100644 --- a/main/HSSF/Record/WriteProtectRecord.cs +++ b/main/HSSF/Record/WriteProtectRecord.cs @@ -46,7 +46,10 @@ public WriteProtectRecord() public WriteProtectRecord(RecordInputStream in1) { - + if (in1.Remaining == 2) + { + in1.ReadShort(); + } } public override String ToString() diff --git a/main/POIFS/FileSystem/NDocumentInputStream.cs b/main/POIFS/FileSystem/NDocumentInputStream.cs index 3880eaf7a..a7326e3b4 100644 --- a/main/POIFS/FileSystem/NDocumentInputStream.cs +++ b/main/POIFS/FileSystem/NDocumentInputStream.cs @@ -69,6 +69,10 @@ public NDocumentInputStream(DocumentEntry document) _marked_offset_count = 0; _document_size = document.Size; _closed = false; + if (_document_size < 0) + { + // throw new RecordFormatException("document_size cannot be < 0"); + } DocumentProperty property = (DocumentProperty)doc.Property; _document = new NPOIFSDocument( @@ -277,6 +281,10 @@ private void CheckAvaliable(int requestedSize) public override void ReadFully(byte[] buf, int off, int len) { + if (len < 0) + { + throw new RuntimeException("Can't read negative number of bytes"); + } CheckAvaliable(len); int read = 0; diff --git a/main/POIFS/FileSystem/ODocumentInputStream.cs b/main/POIFS/FileSystem/ODocumentInputStream.cs index b86ede834..0bd64d9df 100644 --- a/main/POIFS/FileSystem/ODocumentInputStream.cs +++ b/main/POIFS/FileSystem/ODocumentInputStream.cs @@ -19,6 +19,7 @@ limitations under the License. using System; using NPOI.POIFS.Storage; using System.IO; +using NPOI.Util; namespace NPOI.POIFS.FileSystem { @@ -71,6 +72,10 @@ public ODocumentInputStream(DocumentEntry document) _current_offset = 0; _marked_offset = 0; _document_size = document.Size; + if (_document_size < 0) + { + throw new RecordFormatException("document_size cannot be < 0"); + } _closed = false; _document = documentNode.Document; _currentBlock = GetDataInputBlock(0); diff --git a/main/SS/Formula/DataValidationEvaluator.cs b/main/SS/Formula/DataValidationEvaluator.cs index 8ca3e96b5..91903f979 100644 --- a/main/SS/Formula/DataValidationEvaluator.cs +++ b/main/SS/Formula/DataValidationEvaluator.cs @@ -1,19 +1,287 @@ -using NPOI.SS.UserModel; +/* ==================================================================== + 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. +==================================================================== */ + + using System; +using System.Collections; using System.Collections.Generic; +using System.IO; using System.Text; namespace NPOI.SS.Formula { + using MathNet.Numerics; + using NPOI.SS.Formula.Eval; + using NPOI.SS.UserModel; + using NPOI.SS.Util; + using System.Net; + + /// + /// + /// Evaluates Data Validation constraints. + /// + /// + /// For performance reasons, this class keeps a cache of all previously retrieved instances. + /// Be sure to call if any workbook validation definitions are + /// added, modified, or deleted. + /// + /// + /// Changing cell values should be fine, as long as the corresponding + /// is called as well. + /// + /// public class DataValidationEvaluator { - /** - * Note that this assumes the cell cached value is up to date and in sync with data edits - * - * @param cell The {@link Cell} to check. - * @param type The {@link CellType} to check for. - * @return true if the cell or cached cell formula result type match the given type - */ + + /// + /// + /// Expensive to compute, so cache them as they are retrieved. + /// + /// + /// Sheets don't implement equals, and since its an interface, + /// there's no guarantee instances won't be recreated on the fly by some implementation. + /// So we use sheet name. + /// + /// + private Dictionary> validations = new Dictionary>(); + + private IWorkbook workbook; + private WorkbookEvaluator workbookEvaluator; + + /// + /// Use the same formula evaluation context used for other operations, so cell value + /// changes are automatically noticed + /// + /// the workbook this operates on + /// provider for formula evaluation + public DataValidationEvaluator(IWorkbook wb, IWorkbookEvaluatorProvider provider) + { + this.workbook = wb; + this.workbookEvaluator = provider.GetWorkbookEvaluator(); + } + + /// + /// + /// evaluator + protected WorkbookEvaluator GetWorkbookEvaluator() + { + return workbookEvaluator; + } + + /// + /// Call this whenever validation structures change, + /// so future results stay in sync with the Workbook state. + /// + public void clearAllCachedValues() + { + validations.Clear(); + } + + /// + /// Lazy load validations by sheet, since reading the CT* types is expensive + /// + /// + /// The s for the sheet + private List GetValidations(ISheet sheet) + { + validations.TryGetValue(sheet.SheetName, out List dvs); + if(dvs == null && !validations.ContainsKey(sheet.SheetName)) + { + dvs = sheet.GetDataValidations(); + validations.Add(sheet.SheetName, dvs); + } + return dvs; + } + + /// + /// Finds and returns the for the cell, if there is + /// one. Lookup is based on the first match from + /// for the cell's sheet. DataValidation + /// regions must be in the same sheet as the DataValidation. Allowed values + /// expressions may reference other sheets, however. + /// + /// reference to check - use this in case the cell does not actually exist yet + /// the DataValidation applicable to the given cell, or null if no + /// validation applies + /// + public IDataValidation GetValidationForCell(CellReference cell) + { + DataValidationContext vc = GetValidationContextForCell(cell); + return vc == null ? null : vc.Validation; + } + + /// + /// Finds and returns the for the cell, if there is + /// one. Lookup is based on the first match from + /// for the cell's sheet. DataValidation + /// regions must be in the same sheet as the DataValidation. Allowed values + /// expressions may reference other sheets, however. + /// + /// reference to check + /// the DataValidationContext applicable to the given cell, or null if no + /// validation applies + /// + public DataValidationContext GetValidationContextForCell(CellReference cell) + { + ISheet sheet = workbook.GetSheet(cell.SheetName); + if(sheet == null) + return null; + List dataValidations = GetValidations(sheet); + if(dataValidations == null) + return null; + foreach(IDataValidation dv in dataValidations) + { + CellRangeAddressList regions = dv.Regions; + if(regions == null) + return null; + // current implementation can't return null + foreach(CellRangeAddressBase range in regions.CellRangeAddresses) + { + if(range.IsInRange(cell)) + { + return new DataValidationContext(dv, this, range, cell); + } + } + } + return null; + } + + /// + /// + /// If returns an instance, and the + /// is , return the valid + /// values, whether they are from a static list or cell range. + /// + /// + /// For all other validation types, or no validation at all, this method + /// returns null. + /// + /// + /// This method could throw an exception if the validation type is not LIST, + /// but since this method is mostly useful in UI contexts, null seems the + /// easier path. + /// + /// + /// reference to check - use this in case the cell does not actually exist yet + /// returns an unmodifiable of s if applicable, or + /// null + /// + public List GetValidationValuesForCell(CellReference cell) + { + DataValidationContext context = GetValidationContextForCell(cell); + + if(context == null) + return null; + + return GetValidationValuesForConstraint(context); + } + + /// + /// static so enums can reference it without creating a whole instance + /// + /// returns an unmodifiable of s, which may be empty + protected static List GetValidationValuesForConstraint(DataValidationContext context) + { + IDataValidationConstraint val = context.Validation.ValidationConstraint; + if(val.GetValidationType() != ValidationType.LIST) + return null; + + string formula = val.Formula1; + + List values = new List(); + + if(val.ExplicitListValues != null && val.ExplicitListValues.Length > 0) + { + // assumes parsing interprets the overloaded property right for XSSF + foreach(string s in val.ExplicitListValues) + { + if(s != null) + values.Add(new StringEval(s)); // constructor throws exception on null + } + } + else if(formula != null) + { + // evaluate formula for cell refs then Get their values + // note this should return the raw formula result, not the "unwrapped" version that returns a single value. + ValueEval eval = context.Evaluator.GetWorkbookEvaluator().EvaluateList(formula, context.Target, context.Region); + // formula is a StringEval if the validation is by a fixed list. Use the explicit list later. + // there is no way from the model to tell if the list is fixed values or formula based. + if(eval is TwoDEval) + { + TwoDEval twod = (TwoDEval) eval; + for(int i = 0; i < twod.Height; i++) + { + ValueEval cellValue = twod.GetValue(i, 0); + values.Add(cellValue); + } + } + } + return values; + } + + /// + /// + /// Use the validation returned by if you + /// want the error display details. This is the validation checked by this + /// method, which attempts to replicate Excel's data validation rules. + /// + /// + /// Note that to properly apply some validations, care must be taken to + /// offset the base validation formula by the relative position of the + /// current cell, or the wrong value is checked. + /// + /// + /// The reference of the cell to evaluate + /// true if the cell has no validation or the cell value passes the + /// defined validation, false if it Assert.Fails + /// + public bool IsValidCell(CellReference cellRef) + { + DataValidationContext context = GetValidationContextForCell(cellRef); + + if(context == null) + return true; + + ICell cell = SheetUtil.GetCell(workbook.GetSheet(cellRef.SheetName), cellRef.Row, cellRef.Col); + + // now we can validate the cell + + // if empty, return not allowed flag + if(cell == null + || IsType(cell, CellType.Blank) + || (IsType(cell, CellType.String) + && (cell.StringCellValue == null || string.IsNullOrEmpty(cell.StringCellValue)) + ) + ) + { + return context.Validation.EmptyCellAllowed; + } + + // cell has a value + + return ValidationEnum.IsValid(cell, context); + } + + /// + /// Note that this assumes the cell cached value is up to date and in sync with data edits + /// + /// + /// + /// true if the cell or cached cell formula result type match the given type public static bool IsType(ICell cell, CellType type) { CellType cellType = cell.CellType; @@ -23,5 +291,477 @@ public static bool IsType(ICell cell, CellType type) ); } + public class ValidationEnum + { + private static readonly ValidationEnum ANY = new ValidationEnum_ANY(); + private static readonly ValidationEnum INTEGER = new ValidationEnum_INTEGER(); + private static readonly ValidationEnum DECIMAL = new ValidationEnum_DECIMAL(); + private static readonly ValidationEnum LIST = new ValidationEnum_LIST(); + private static readonly ValidationEnum DATE = new ValidationEnum_DATE(); + private static readonly ValidationEnum TIME = new ValidationEnum_TIME(); + private static readonly ValidationEnum TEXT_LENGTH = new ValidationEnum_TEXT_LENGTH(); + private static readonly ValidationEnum FORMULA = new ValidationEnum_FORMULA(); + public static Dictionary Values = new Dictionary() + { + { ValidationType.ANY, ANY }, + { ValidationType.INTEGER, INTEGER }, + { ValidationType.DECIMAL, DECIMAL }, + { ValidationType.LIST, LIST }, + { ValidationType.DATE, DATE }, + { ValidationType.TIME, TIME }, + { ValidationType.TEXT_LENGTH, TEXT_LENGTH }, + { ValidationType.FORMULA, FORMULA }, + }; + private class ValidationEnum_ANY : ValidationEnum + { + public override bool IsValidValue(ICell cell, DataValidationContext context) + { + return true; + } + } + + private class ValidationEnum_INTEGER : ValidationEnum + { + public override bool IsValidValue(ICell cell, DataValidationContext context) + { + if(base.IsValidValue(cell, context)) + { + // we know it is a number in the proper range, now check if it is an int + double value = cell.NumericCellValue; // can't Get here without a valid numeric value + return value.CompareTo((int)value) == 0; + } + return false; + } + } + private class ValidationEnum_DECIMAL : ValidationEnum { } + private class ValidationEnum_LIST : ValidationEnum + { + public override bool IsValidValue(ICell cell, DataValidationContext context) + { + List valueList = GetValidationValuesForConstraint(context); + if(valueList == null) + return true; // special case + + // compare cell value to each item + foreach(ValueEval listVal in valueList) + { + ValueEval comp = listVal is RefEval ? ((RefEval) listVal).GetInnerValueEval(context.SheetIndex) : listVal; + + // any value is valid if the list contains a blank value per Excel help + if(comp is BlankEval) + return true; + if(comp is ErrorEval) + continue; // nothing to check + if(comp is BoolEval) + { + if(IsType(cell, CellType.Boolean) && ((BoolEval) comp).BooleanValue == cell.BooleanCellValue) + { + return true; + } + else + { + continue; // check the rest + } + } + if(comp is NumberEval) + { + // could this have trouble with double precision/rounding errors and date/time values? + // do we need to allow a "close enough" double fractional range? + // I see 17 digits After the decimal separator in XSSF files, and for time values, + // there are sometimes discrepancies in the final decimal place. + // I don't have a validation test case yet though. - GW + if(IsType(cell, CellType.Numeric) && ((NumberEval) comp).NumberValue == cell.NumericCellValue) + { + return true; + } + else + { + continue; // check the rest + } + } + if(comp is StringEval) + { + // interestingly, in testing, a validation value of the string "TRUE" or "true" + // did not match a bool cell value of TRUE - so apparently cell type matters + // also, Excel validation is case insensitive - "true" is valid for the list value "TRUE" + if(IsType(cell, CellType.String) && ((StringEval) comp).StringValue.Equals(cell.StringCellValue, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + else + { + continue; // check the rest; + } + } + } + return false; // no matches + } + } + private class ValidationEnum_DATE : ValidationEnum { } + private class ValidationEnum_TIME : ValidationEnum { } + private class ValidationEnum_TEXT_LENGTH : ValidationEnum + { + public override bool IsValidValue(ICell cell, DataValidationContext context) + { + if(!IsType(cell, CellType.String)) + return false; + string v = cell.StringCellValue; + return IsValidNumericValue(v.Length, context); + } + } + private class ValidationEnum_FORMULA : ValidationEnum + { + /** + * Note the formula result must either be a bool result, or anything not in error. + * If bool, value must be true to pass, anything else valid is also passing, errors Assert.Fail. + * @see NPOI.SS.Formula.DataValidationEvaluator.ValidationEnum#isValidValue(NPOI.SS.UserModel.Cell, NPOI.SS.UserModel.DataValidationConstraint, NPOI.SS.Formula.WorkbookEvaluator) + */ + public override bool IsValidValue(ICell cell, DataValidationContext context) + { + // unwrapped single value + ValueEval comp = context.Evaluator.GetWorkbookEvaluator().Evaluate(context.Formula1, context.Target, context.Region); + if(comp is RefEval) + { + comp = ((RefEval) comp).GetInnerValueEval(((RefEval) comp).FirstSheetIndex); + } + + if(comp is BlankEval) + return true; + if(comp is ErrorEval) + return false; + if(comp is BoolEval) + { + return ((BoolEval) comp).BooleanValue; + } + // empirically tested in Excel - 0=false, any other number = true/valid + // see test file DataValidationEvaluations.xlsx + if(comp is NumberEval) + { + return ((NumberEval) comp).NumberValue != 0; + } + return false; // anything else is false, such as text + } + } + + public virtual bool IsValidValue(ICell cell, DataValidationContext context) + { + return IsValidNumericCell(cell, context); + } + + /** + * Uses the cell value, which may be the cached formula result value. + * We won't re-evaluate cells here. This validation would be After the cell value was updated externally. + * Excel allows invalid values through methods like copy/paste, and only validates them when the user + * interactively edits the cell. + * @return if the cell is a valid numeric cell for the validation or not + */ + protected bool IsValidNumericCell(ICell cell, DataValidationContext context) + { + if(!IsType(cell, CellType.Numeric)) + return false; + + Double value = cell.NumericCellValue; + return IsValidNumericValue(value, context); + } + + /** + * Is the number a valid option for the validation? + */ + protected bool IsValidNumericValue(Double value, DataValidationContext context) + { + try + { + Double? t1 = EvalOrConstant(context.Formula1, context); + // per Excel, a blank value for a numeric validation constraint formula validates true + if(t1 == null) + return true; + Double? t2 = null; + if(context.Operator == OperatorType.BETWEEN || context.Operator == OperatorType.NOT_BETWEEN) + { + t2 = EvalOrConstant(context.Formula2, context); + // per Excel, a blank value for a numeric validation constraint formula validates true + if(t2 == null) + return true; + } + return OperatorEnum.Values[context.Operator].IsValid(value, t1.Value, t2.Value); + } + catch(FormatException e) + { + // one or both formulas are in error, not evaluating to a number, so the validation is false per Excel's behavior. + return false; + } + } + + /** + * Evaluate a numeric formula value as either a constant or numeric expression. + * Note that Excel treats validations with constraint formulas that evaluate to null as valid, + * but evaluations in error or non-numeric are marked invalid. + * @param formula + * @param context + * @return numeric value or null if not defined or the formula evaluates to an empty/missing cell. + * @throws NumberFormatException if the formula is non-numeric when it should be + */ + private static Double? EvalOrConstant(string formula, DataValidationContext context) + { + if(formula == null || string.IsNullOrEmpty(formula.Trim())) + return null; // shouldn't happen, but just in case + try + { + return Double.Parse(formula); + } + catch(FormatException e) + { + // must be an expression, then. Overloading by Excel in the file formats. + } + // note the call to the "unwrapped" version, which returns a single value + ValueEval eval = context.Evaluator.GetWorkbookEvaluator().Evaluate(formula, context.Target, context.Region); + if(eval is RefEval) + { + eval = ((RefEval) eval).GetInnerValueEval(((RefEval) eval).FirstSheetIndex); + } + if(eval is BlankEval) + return null; + if(eval is NumberEval) + return ((NumberEval) eval).NumberValue; + if(eval is StringEval) + { + string value = ((StringEval) eval).StringValue; + if(value == null || string.IsNullOrEmpty(value.Trim())) + return null; + // try to parse the cell value as a double and return it + return Double.Parse(value); + } + throw new FormatException("Formula '" + formula + "' evaluates to something other than a number"); + } + /// + /// Validates against the type defined in context, as an index of the enum values array. + /// + /// Cell to check validity of + /// The Data Validation to check against + /// true if validation passes + /// if the constraint type is an invalid index + public static bool IsValid(ICell cell, DataValidationContext context) + { + return Values[context.Validation.ValidationConstraint.GetValidationType()].IsValidValue(cell, context); + } + } + + public class OperatorEnum + { + public static readonly OperatorEnum BETWEEN = new Operator_BETWEEN(); + public static readonly OperatorEnum NOT_BETWEEN = new Operator_NOT_BETWEEN(); + public static readonly OperatorEnum EQUAL = new Operator_EQUAL(); + public static readonly OperatorEnum NOT_EQUAL = new Operator_NOT_EQUAL(); + public static readonly OperatorEnum GREATER_THAN = new Operator_GREATER_THAN(); + public static readonly OperatorEnum LESS_THAN = new Operator_LESS_THAN(); + public static readonly OperatorEnum GREATER_OR_EQUAL = new Operator_GREATER_OR_EQUAL(); + public static readonly OperatorEnum LESS_OR_EQUAL = new Operator_LESS_OR_EQUAL(); + + public static readonly OperatorEnum IGNORED = BETWEEN; + + public static readonly Dictionary Values = new Dictionary() + { + { OperatorType.BETWEEN, BETWEEN }, + { OperatorType.NOT_BETWEEN, NOT_BETWEEN }, + { OperatorType.EQUAL, EQUAL }, + { OperatorType.NOT_EQUAL, NOT_EQUAL }, + { OperatorType.GREATER_THAN, GREATER_THAN }, + { OperatorType.LESS_THAN, LESS_THAN }, + { OperatorType.GREATER_OR_EQUAL, GREATER_OR_EQUAL }, + { OperatorType.LESS_OR_EQUAL, LESS_OR_EQUAL }, + }; + /** + * Evaluates comparison using operator instance rules + * @param cellValue won't be null, assumption is previous checks handled that + * @param v1 if null, value assumed invalid, anything passes, per Excel behavior + * @param v2 null if not needed. If null when needed, assume anything passes, per Excel behavior + * @return true if the comparison is valid + */ + public virtual bool IsValid(Double cellValue, Double v1, Double v2) + { + throw new NotImplementedException(); + } + + private class Operator_BETWEEN : OperatorEnum + { + public override bool IsValid(Double cellValue, Double v1, Double v2) + { + return cellValue.CompareTo(v1) >= 0 && cellValue.CompareTo(v2) <= 0; + } + } + + private class Operator_NOT_BETWEEN : OperatorEnum + { + public override bool IsValid(Double cellValue, Double v1, Double v2) + { + return cellValue.CompareTo(v1) < 0 || cellValue.CompareTo(v2) > 0; + } + } + + private class Operator_EQUAL : OperatorEnum + { + public override bool IsValid(Double cellValue, Double v1, Double v2) + { + return cellValue.CompareTo(v1) == 0; + } + } + + private class Operator_NOT_EQUAL : OperatorEnum + { + public override bool IsValid(Double cellValue, Double v1, Double v2) + { + return cellValue.CompareTo(v1) != 0; + } + } + private class Operator_GREATER_THAN : OperatorEnum + { + public override bool IsValid(Double cellValue, Double v1, Double v2) + { + return cellValue.CompareTo(v1) > 0; + } + } + private class Operator_LESS_THAN : OperatorEnum + { + public override bool IsValid(Double cellValue, Double v1, Double v2) + { + return cellValue.CompareTo(v1) < 0; + } + } + private class Operator_GREATER_OR_EQUAL : OperatorEnum + { + public override bool IsValid(Double cellValue, Double v1, Double v2) + { + return cellValue.CompareTo(v1) >= 0; + } + } + private class Operator_LESS_OR_EQUAL : OperatorEnum + { + public override bool IsValid(Double cellValue, Double v1, Double v2) + { + return cellValue.CompareTo(v1) <= 0; + } + } + } + + + /** + * This class organizes and encapsulates all the pieces of information related to a single + * data validation configuration for a single cell. It cleanly separates the validation region, + * the cells it applies to, the specific cell this instance references, and the validation + * configuration and current values if applicable. + */ + public class DataValidationContext + { + private IDataValidation dv; + private DataValidationEvaluator dve; + private CellRangeAddressBase region; + private CellReference target; + + /** + * + * @param dv + * @param dve + * @param region + * @param target + */ + public DataValidationContext(IDataValidation dv, DataValidationEvaluator dve, CellRangeAddressBase region, CellReference target) + { + this.dv = dv; + this.dve = dve; + this.region = region; + this.target = target; + } + /** + * @return the dv + */ + public IDataValidation Validation + { + get + { + return dv; + } + } + /** + * @return the dve + */ + public DataValidationEvaluator Evaluator + { + get + { + return dve; + } + } + /** + * @return the region + */ + public CellRangeAddressBase Region + { + get + { + return region; + } + } + /** + * @return the target + */ + public CellReference Target + { + get + { + return target; + } + } + + public int OffsetColumns + { + get + { + return target.Col - region.FirstColumn; + } + } + + public int OffsetRows + { + get + { + return target.Row - region.FirstRow; + } + } + + public int SheetIndex + { + get + { + return dve.GetWorkbookEvaluator().GetSheetIndex(target.SheetName); + } + } + + public string Formula1 + { + get + { + return dv.ValidationConstraint.Formula1; + } + } + + public string Formula2 + { + get + { + return dv.ValidationConstraint.Formula2; + } + } + + public int Operator + { + get + { + return dv.ValidationConstraint.Operator; + } + + } + + } } + } diff --git a/main/SS/Formula/WorkbookEvaluator.cs b/main/SS/Formula/WorkbookEvaluator.cs index 08a37f069..0985fffae 100644 --- a/main/SS/Formula/WorkbookEvaluator.cs +++ b/main/SS/Formula/WorkbookEvaluator.cs @@ -293,6 +293,26 @@ public ValueEval Evaluate(String formula, CellReference target, CellRangeAddress { return Evaluate(formula, target, region, FormulaType.Cell); } + + + /** + * Some expressions need to be evaluated in terms of an offset from the top left corner of a region, + * such as some data validation and conditional format expressions, when those constraints apply + * to contiguous cells. When a relative formula is used, it must be evaluated by shifting by the target + * offset position relative to the top left of the range. + *

+ * Returns a ValueEval that may be one or more values, such as the allowed values for a data validation constraint. + * + * @param formula + * @param target cell context for the operation + * @param region containing the cell + * @return ValueEval for one or more values + * @throws IllegalArgumentException if target does not define a sheet name to evaluate the formula on. + */ + public ValueEval EvaluateList(String formula, CellReference target, CellRangeAddressBase region) { + return Evaluate(formula, target, region, FormulaType.DataValidationList); + } + private ValueEval Evaluate(String formula, CellReference target, CellRangeAddressBase region, FormulaType formulaType) { String sheetName = target == null ? null : target.SheetName; @@ -461,6 +481,8 @@ private ValueEval EvaluateAny(IEvaluationCell srcCell, int sheetIndex, //return cce.GetValue(); return result; } + + /** * Adds the current cell reference to the exception for easier debugging. * Would be nice to get the formula text as well, but that seems to require @@ -709,6 +731,7 @@ public ValueEval EvaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) { throw new InvalidOperationException("evaluation stack not empty"); } + // "unwrap" result to just the value relevant for the source cell if needed ValueEval result; if (ec.IsSingleValue) { @@ -784,14 +807,14 @@ public static ValueEval DereferenceResult(ValueEval evaluationResult, int srcRow return value; } /** - * Dereferences a single value from any AreaEval or RefEval evaluation - * result. If the supplied evaluationResult is just a plain value, it is - * returned as-is. - * - * @return a {@link NumberEval}, {@link StringEval}, {@link BoolEval}, or - * {@link ErrorEval}. Never null. {@link BlankEval} is - * converted to {@link NumberEval#ZERO} - */ + * Dereferences a single value from any AreaEval or RefEval evaluation + * result. If the supplied evaluationResult is just a plain value, it is + * returned as-is. + * + * @return a {@link NumberEval}, {@link StringEval}, {@link BoolEval}, or + * {@link ErrorEval}. Never null. {@link BlankEval} is + * converted to {@link NumberEval#ZERO} + */ public static ValueEval DereferenceResult(ValueEval evaluationResult, OperationEvaluationContext ec) { ValueEval value; diff --git a/main/Util/DocumentFormatException.cs b/main/Util/DocumentFormatException.cs new file mode 100644 index 000000000..442abc15d --- /dev/null +++ b/main/Util/DocumentFormatException.cs @@ -0,0 +1,69 @@ + +/* ==================================================================== + 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. +==================================================================== */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace NPOI.Util +{ + ///

+ /// This is similar to , except this is thrown + /// when there's a higher order problem with parsing a document beyond individual records. + /// + public class DocumentFormatException : RuntimeException + { + + public DocumentFormatException(string exception) + : base(exception) + { + + } + + public DocumentFormatException(string exception, Exception ex) + : base(exception, ex) + { + + } + + public DocumentFormatException(Exception thr) + : base(thr) + { + + } + + /// + /// Syntactic sugar to check whether a DocumentFormatException should + /// be thrown. If assertTrue is false, this will throw this + /// exception with the message. + /// + /// + /// + public static void Check(bool assertTrue, string message) + { + if(!assertTrue) + { + throw new DocumentFormatException(message); + } + } + } +} + + diff --git a/main/Util/IOUtils.cs b/main/Util/IOUtils.cs index 542767196..76eace27a 100644 --- a/main/Util/IOUtils.cs +++ b/main/Util/IOUtils.cs @@ -373,6 +373,10 @@ public static void Copy(Stream inp, Stream out1) int count; while ((count = inp.Read(buff, 0, buff.Length)) >0) { + if (count < -1) + { + throw new RecordFormatException("Can't have read < -1 bytes"); + } out1.Write(buff, 0, count); } } diff --git a/main/Util/RecordFormatException.cs b/main/Util/RecordFormatException.cs index b0e21c643..bb1bb8cd2 100644 --- a/main/Util/RecordFormatException.cs +++ b/main/Util/RecordFormatException.cs @@ -45,6 +45,22 @@ public RecordFormatException(Exception ex): base(ex) { } + + /** + * Syntactic sugar to check whether a RecordFormatException should + * be thrown. If assertTrue is false, this will throw this + * exception with the message. + * + * @param assertTrue + * @param message + */ + public static void Check(bool assertTrue, String message) + { + if (! assertTrue) + { + throw new RecordFormatException(message); + } + } } } \ No newline at end of file diff --git a/ooxml/Util/PackageHelper.cs b/ooxml/Util/PackageHelper.cs index df29d079a..e0c339721 100644 --- a/ooxml/Util/PackageHelper.cs +++ b/ooxml/Util/PackageHelper.cs @@ -134,7 +134,7 @@ private static void Copy(OPCPackage pkg, PackagePart part, OPCPackage tgt, Packa * @param src source properties * @param tgt target properties */ - private static void CopyProperties(PackageProperties src, PackageProperties tgt) + private static void CopyProperties(IPackageProperties src, IPackageProperties tgt) { tgt.SetCategoryProperty(src.GetCategoryProperty()); tgt.SetContentStatusProperty(src.GetContentStatusProperty()); diff --git a/ooxml/XSSF/Util/EvilUnclosedBRFixingInputStream.cs b/ooxml/XSSF/Util/EvilUnclosedBRFixingInputStream.cs index 80dbb41ab..4f58ce4e1 100644 --- a/ooxml/XSSF/Util/EvilUnclosedBRFixingInputStream.cs +++ b/ooxml/XSSF/Util/EvilUnclosedBRFixingInputStream.cs @@ -20,11 +20,6 @@ limitations under the License. using NPOI.Util; namespace NPOI.XSSF.Util { - - - - - /** * This is a seriously sick fix for the fact that some .xlsx * files contain raw bits of HTML, without being escaped @@ -46,272 +41,6 @@ public EvilUnclosedBRFixingInputStream(InputStream source) } } - //public class EvilUnclosedBRFixingInputStream : Stream - //{ - // private Stream source; - // private byte[] spare; - - // private static byte[] detect = new byte[] { - // (byte)'<', (byte)'b', (byte)'r', (byte)'>' - // }; - - // public EvilUnclosedBRFixingInputStream(Stream source) - // { - // this.source = source; - // } - - // /** - // * Warning - doesn't fix! - // */ - - // public int Read() - // { - // return source.ReadByte(); - // } - - - // public override int Read(byte[] b, int off, int len) - // { - // // Grab any data left from last time - // int readA = ReadFromSpare(b, off, len); - - // // Now read from the stream - // int readB = source.Read(b, off + readA, len - readA); - - // // Figure out how much we've done - // int read; - // if (readB == -1 || readB == 0) - // { - // if (readA == 0) - // { - // return readB; - // } - // read = readA; - // } - // else - // { - // read = readA + readB; - // } - - // // Fix up our data - // if (read > 0) - // { - // read = fixUp(b, off, read); - // } - - // // All done - // return read; - // } - - - // public int Read(byte[] b) - // { - // return this.Read(b, 0, b.Length); - // } - - // /** - // * Reads into the buffer from the spare bytes - // */ - // private int ReadFromSpare(byte[] b, int offset, int len) - // { - // if (spare == null) return 0; - // if (len == 0) throw new ArgumentException("Asked to read 0 bytes"); - - // if (spare.Length <= len) - // { - // // All fits, good - // Array.Copy(spare, 0, b, offset, spare.Length); - // int read = spare.Length; - // spare = null; - // return read; - // } - // else - // { - // // We have more spare than they can copy with... - // byte[] newspare = new byte[spare.Length - len]; - // Array.Copy(spare, 0, b, offset, len); - // Array.Copy(spare, len, newspare, 0, newspare.Length); - // spare = newspare; - // return len; - // } - // } - // private void AddToSpare(byte[] b, int offset, int len, bool atTheEnd) - // { - // if (spare == null) - // { - // spare = new byte[len]; - // Array.Copy(b, offset, spare, 0, len); - // } - // else - // { - // byte[] newspare = new byte[spare.Length + len]; - // if (atTheEnd) - // { - // Array.Copy(spare, 0, newspare, 0, spare.Length); - // Array.Copy(b, offset, newspare, spare.Length, len); - // } - // else - // { - // Array.Copy(b, offset, newspare, 0, len); - // Array.Copy(spare, 0, newspare, len, spare.Length); - // } - // spare = newspare; - // } - // } - - // private int fixUp(byte[] b, int offset, int read) - // { - // // Do we have any potential overhanging ones? - // for (int i = 0; i < detect.Length - 1; i++) - // { - // int base1 = offset + read - 1 - i; - // if (base1 < 0) continue; - - // bool going = true; - // for (int j = 0; j <= i && going; j++) - // { - // if (b[base1 + j] == detect[j]) - // { - // // Matches - // } - // else - // { - // going = false; - // } - // } - // if (going) - // { - // // There could be a
handing over the end, eg fixAt = new List(); - // for (int i = offset; i <= offset + read - detect.Length; i++) - // { - // bool going = true; - // for (int j = 0; j < detect.Length && going; j++) - // { - // if (b[i + j] != detect[j]) - // { - // going = false; - // } - // } - // if (going) - // { - // fixAt.Add(i); - // } - // } - - // if (fixAt.Count == 0) - // { - // return read; - // } - - // // If there isn't space in the buffer to contain - // // all the fixes, then save the overshoot for next time - // int needed = offset + read + fixAt.Count; - // int overshoot = needed - b.Length; - // if (overshoot > 0) - // { - // // Make sure we don't loose part of a
! - // int fixes = 0; - // foreach (int at in fixAt) - // { - // if (at > offset + read - detect.Length - overshoot - fixes) - // { - // overshoot = needed - at - 1 - fixes; - // break; - // } - // fixes++; - // } - - // AddToSpare(b, offset + read - overshoot, overshoot, false); - // read -= overshoot; - // } - - // // Fix them, in reverse order so the - // // positions are valid - // for (int j = fixAt.Count - 1; j >= 0; j--) - // { - // int i = fixAt[j]; - // if (i >= read + offset) - // { - // // This one has Moved into the overshoot - // continue; - // } - // if (i > read - 3) - // { - // // This one has Moved into the overshoot - // continue; - // } - - // byte[] tmp = new byte[read - i - 3]; - // Array.Copy(b, i + 3, tmp, 0, tmp.Length); - // b[i + 3] = (byte)'/'; - // Array.Copy(tmp, 0, b, i + 4, tmp.Length); - // // It got one longer - // read++; - // } - // return read; - // } - - // public override bool CanRead - // { - // get { return true; } - // } - - // public override bool CanSeek - // { - // get { return true; } - // } - - // public override bool CanWrite - // { - // get { return false; } - // } - - // public override void Flush() - // { - - // } - - // public override long Length - // { - // get { return source.Length; } - // } - - // public override long Position - // { - // get - // { - // return source.Position; - // } - // set - // { - // source.Position = value; - // } - // } - - // public override long Seek(long offset, SeekOrigin origin) - // { - // return source.Seek(offset, origin); - // } - - // public override void SetLength(long value) - // { - // throw new InvalidOperationException(); - // } - - // public override void Write(byte[] buffer, int offset, int count) - // { - // throw new InvalidOperationException(); - // } - //} } diff --git a/ooxml/XWPF/Usermodel/XWPFRelation.cs b/ooxml/XWPF/Usermodel/XWPFRelation.cs index da00ab65e..e621ffb6f 100644 --- a/ooxml/XWPF/Usermodel/XWPFRelation.cs +++ b/ooxml/XWPF/Usermodel/XWPFRelation.cs @@ -17,6 +17,7 @@ limitations under the License. namespace NPOI.XWPF.UserModel { + using NPOI.OpenXml4Net.OPC; using System; using System.Collections.Generic; /** @@ -32,26 +33,26 @@ public class XWPFRelation : POIXMLRelation public static XWPFRelation DOCUMENT = new XWPFRelation( - "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", + PackageRelationshipTypes.CORE_DOCUMENT, "/word/document.xml", - null + null ); public static XWPFRelation TEMPLATE = new XWPFRelation( - "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml", - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", - "/word/document.xml", - null + "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml", + PackageRelationshipTypes.CORE_DOCUMENT, + "/word/document.xml", + null ); public static XWPFRelation MACRO_DOCUMENT = new XWPFRelation( - "application/vnd.ms-word.document.macroEnabled.main+xml", - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", + "application/vnd.ms-word.document.macroEnabled.main+xml", + PackageRelationshipTypes.CORE_DOCUMENT, "/word/document.xml", - null + null ); public static XWPFRelation MACRO_TEMPLATE_DOCUMENT = new XWPFRelation( - "application/vnd.ms-word.template.macroEnabledTemplate.main+xml", - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", + "application/vnd.ms-word.template.macroEnabledTemplate.main+xml", + PackageRelationshipTypes.CORE_DOCUMENT, "/word/document.xml", null ); diff --git a/openxml4Net/OPC/Internal/PackagePropertiesPart.cs b/openxml4Net/OPC/Internal/PackagePropertiesPart.cs index 405004b5f..5aeff6098 100644 --- a/openxml4Net/OPC/Internal/PackagePropertiesPart.cs +++ b/openxml4Net/OPC/Internal/PackagePropertiesPart.cs @@ -17,15 +17,13 @@ namespace NPOI.OpenXml4Net.OPC.Internal * @author Julien Chable * @version 1.0 */ - public class PackagePropertiesPart : PackagePart, PackageProperties + public class PackagePropertiesPart : PackagePart, IPackageProperties { - public static String NAMESPACE_DC = "http://purl.org/dc/elements/1.1/"; + public static String NAMESPACE_DC_URI = PackageProperties.NAMESPACE_DC; - public static String NAMESPACE_DC_URI = "http://purl.org/dc/elements/1.1/"; + public static String NAMESPACE_CP_URI = PackageNamespaces.CORE_PROPERTIES; - public static String NAMESPACE_CP_URI = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; - - public static String NAMESPACE_DCTERMS_URI = "http://purl.org/dc/terms/"; + public static String NAMESPACE_DCTERMS_URI = PackageProperties.NAMESPACE_DCTERMS; public static String NAMESPACE_XSI_URI = "http://www.w3.org/2001/XMLSchema-instance"; private static String DEFAULT_DATEFORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; diff --git a/openxml4Net/OPC/OPCPackage.cs b/openxml4Net/OPC/OPCPackage.cs index d1afa4938..d53b3ea0d 100644 --- a/openxml4Net/OPC/OPCPackage.cs +++ b/openxml4Net/OPC/OPCPackage.cs @@ -604,7 +604,7 @@ internal void ThrowExceptionIfWriteOnly() * * @return The PackageProperties part of this package. */ - public PackageProperties GetPackageProperties() + public IPackageProperties GetPackageProperties() { this.ThrowExceptionIfWriteOnly(); // If no properties part has been found then we Create one diff --git a/openxml4Net/OPC/PackageProperties.cs b/openxml4Net/OPC/PackageProperties.cs index 31ad2ed39..3d6d5c3a5 100644 --- a/openxml4Net/OPC/PackageProperties.cs +++ b/openxml4Net/OPC/PackageProperties.cs @@ -11,7 +11,7 @@ namespace NPOI.OpenXml4Net.OPC * @version 1.0 * @see org.apache.poi.OpenXml4Net.opc.OPCPackage */ - public interface PackageProperties + public interface IPackageProperties { /* Getters and Setters */ @@ -198,4 +198,17 @@ public interface PackageProperties */ void SetVersionProperty(String version); } + + public class PackageProperties + { + /** + * Dublin Core Terms URI. + */ + public static String NAMESPACE_DCTERMS = "http://purl.org/dc/terms/"; + + /** + * Dublin Core namespace URI. + */ + public static String NAMESPACE_DC = "http://purl.org/dc/elements/1.1/"; + } } diff --git a/testcases/main/HSSF/UserModel/TestBugs.cs b/testcases/main/HSSF/UserModel/TestBugs.cs index af2fa4eec..8d3386719 100644 --- a/testcases/main/HSSF/UserModel/TestBugs.cs +++ b/testcases/main/HSSF/UserModel/TestBugs.cs @@ -46,6 +46,7 @@ namespace TestCases.HSSF.UserModel using NPOI.HSSF; using System.Net; using SixLabors.ImageSharp; + using NPOI.HPSF; /** * Testcases for bugs entered in bugzilla @@ -2369,8 +2370,8 @@ public void Test47847() ClassicAssert.AreEqual(3, wb.NumberOfSheets); // Find the SST record - UnicodeString withExt = wb.Workbook.GetSSTString(0); - UnicodeString withoutExt = wb.Workbook.GetSSTString(31); + NPOI.HSSF.Record.UnicodeString withExt = wb.Workbook.GetSSTString(0); + NPOI.HSSF.Record.UnicodeString withoutExt = wb.Workbook.GetSSTString(31); ClassicAssert.AreEqual("O:Alloc:Qty", withExt.String); ClassicAssert.IsTrue((withExt.OptionFlags & 0x0004) == 0x0004); @@ -3500,6 +3501,29 @@ public void Test45353b() wb.Close(); } + [Test] + public void Test61287() + { + IWorkbook wb = HSSFTestDataSamples.OpenSampleWorkbook("61287.xls"); + ExcelExtractor ex = new ExcelExtractor((HSSFWorkbook)wb); + String text = ex.Text; + POITestCase.AssertContains(text, "\u8D44\u4EA7\u8D1F\u503A\u8868"); + wb.Close(); + } + + [Test] + public void Test61300() + { + ClassicAssert.Throws(()=>{ + NPOIFSFileSystem npoifs = new NPOIFSFileSystem(HSSFTestDataSamples.OpenSampleFileStream("61300.xls")); + + DocumentEntry entry = + (DocumentEntry) npoifs.Root.GetEntry(SummaryInformation.DEFAULT_STREAM_NAME); + PropertySet properties = + new PropertySet(new DocumentInputStream(entry)); + }); + } + // follow https://svn.apache.org/viewvc?view=revision&revision=1896552 to write a unit test for this fix. [Test] public void Test52447() diff --git a/testcases/main/HSSF/UserModel/TestPOIFSProperties.cs b/testcases/main/HSSF/UserModel/TestPOIFSProperties.cs index 68724bbd7..d81df04ef 100644 --- a/testcases/main/HSSF/UserModel/TestPOIFSProperties.cs +++ b/testcases/main/HSSF/UserModel/TestPOIFSProperties.cs @@ -20,11 +20,13 @@ namespace TestCases.HSSF.UserModel using System; using System.IO; using NPOI.HSSF.UserModel; - using NUnit.Framework;using NUnit.Framework.Legacy; + using NUnit.Framework; + using NUnit.Framework.Legacy; using TestCases.HSSF; using NPOI.HPSF; using NPOI.POIFS.FileSystem; + using NPOI.Util; /** * Old-style setting of POIFS properties doesn't work with POI 3.0.2 @@ -37,57 +39,88 @@ public class TestPOIFSProperties private static String title = "Testing POIFS properties"; - public void TestFail() + [Test] + public void TestFail() { - Stream is1 = HSSFTestDataSamples.OpenSampleFileStream("Simple.xls"); - POIFSFileSystem fs = new POIFSFileSystem(is1); - - HSSFWorkbook wb = new HSSFWorkbook(fs); + MemoryStream out1 = new MemoryStream(); + { + // read the workbook, adjust the SummaryInformation and write the data to a byte array + POIFSFileSystem fs = OpenFileSystem(); - //set POIFS properties after constructing HSSFWorkbook - //(a piece of code that used to work up to POI 3.0.2) - SummaryInformation summary1 = (SummaryInformation)PropertySetFactory.Create(fs.CreateDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME)); - summary1.Title=(title); - //Write the modified property back to POIFS - fs.Root.GetEntry(SummaryInformation.DEFAULT_STREAM_NAME).Delete(); - fs.CreateDocument(summary1.ToInputStream(), SummaryInformation.DEFAULT_STREAM_NAME); + HSSFWorkbook wb = new HSSFWorkbook(fs); - //save the workbook and read the property - MemoryStream out1 = new MemoryStream(); - wb.Write(out1); - out1.Close(); + //set POIFS properties After constructing HSSFWorkbook + //(a piece of code that used to work up to POI 3.0.2) + SetTitle(fs); - POIFSFileSystem fs2 = new POIFSFileSystem(new MemoryStream(out1.ToArray())); - SummaryInformation summary2 = (SummaryInformation)PropertySetFactory.Create(fs2.CreateDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME)); + //save the workbook and read the property + wb.Write(out1); + out1.Close(); + wb.Close(); + } - //Assert.Failing assertion - ClassicAssert.AreEqual(title, summary2.Title); + // process the byte array + CheckFromByteArray(out1.ToArray()); } [Test] public void TestOK() + { + MemoryStream out1 = new MemoryStream(); + { + // read the workbook, adjust the SummaryInformation and write the data to a byte array + POIFSFileSystem fs = OpenFileSystem(); + + //set POIFS properties before constructing HSSFWorkbook + SetTitle(fs); + + HSSFWorkbook wb = new HSSFWorkbook(fs); + + wb.Write(out1); + out1.Close(); + wb.Close(); + } + + // process the byte array + CheckFromByteArray(out1.ToArray()); + } + + private POIFSFileSystem OpenFileSystem() { Stream is1 = HSSFTestDataSamples.OpenSampleFileStream("Simple.xls"); POIFSFileSystem fs = new POIFSFileSystem(is1); + is1.Close(); + return fs; + } - //set POIFS properties before constructing HSSFWorkbook - SummaryInformation summary1 = (SummaryInformation)PropertySetFactory.Create(fs.CreateDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME)); - summary1.Title = (title); + private void SetTitle(POIFSFileSystem fs) + { + SummaryInformation summary1 = (SummaryInformation) PropertySetFactory.Create(fs.CreateDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME)); + ClassicAssert.IsNotNull(summary1); + summary1.Title = title; + //write the modified property back to POIFS fs.Root.GetEntry(SummaryInformation.DEFAULT_STREAM_NAME).Delete(); fs.CreateDocument(summary1.ToInputStream(), SummaryInformation.DEFAULT_STREAM_NAME); - HSSFWorkbook wb = new HSSFWorkbook(fs); + // check that the information was added successfully to the filesystem object + SummaryInformation summaryCheck = (SummaryInformation) PropertySetFactory.Create(fs.CreateDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME)); + ClassicAssert.IsNotNull(summaryCheck); + } - MemoryStream out1 = new MemoryStream(); - wb.Write(out1); - out1.Close(); + private void CheckFromByteArray(byte[] bytes) + { + // on some environments in CI we see strange Assert.Failures, let's verify that the size is exactly right + // this can be removed again After the problem is identified + // 5120 + ClassicAssert.AreEqual(9216, bytes.Length, "Had: " + HexDump.ToHex(bytes)); - //read the property - POIFSFileSystem fs2 = new POIFSFileSystem(new MemoryStream(out1.ToArray())); - SummaryInformation summary2 = (SummaryInformation)PropertySetFactory.Create(fs2.CreateDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME)); - ClassicAssert.AreEqual(title, summary2.Title); + POIFSFileSystem fs2 = new POIFSFileSystem(new MemoryStream(bytes)); + SummaryInformation summary2 = (SummaryInformation) PropertySetFactory.Create(fs2.CreateDocumentInputStream(SummaryInformation.DEFAULT_STREAM_NAME)); + ClassicAssert.IsNotNull(summary2); + ClassicAssert.AreEqual(title, summary2.Title); + fs2.Close(); } } } \ No newline at end of file diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFDataValidation.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFDataValidation.cs index cc73636b9..bec97c596 100644 --- a/testcases/ooxml/XSSF/UserModel/TestXSSFDataValidation.cs +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFDataValidation.cs @@ -17,12 +17,15 @@ limitations under the License. using TestCases.SS.UserModel; using NPOI.SS.UserModel; using System.Collections.Generic; -using NUnit.Framework;using NUnit.Framework.Legacy; +using NUnit.Framework; +using NUnit.Framework.Legacy; using NPOI.SS.Util; using System; using System.Text; using NPOI.XSSF; using NPOI.XSSF.UserModel; +using NPOI.SS.Formula; +using NPOI.SS.Formula.Eval; namespace TestCases.XSSF.UserModel { @@ -411,5 +414,14 @@ private XSSFDataValidation CreateValidation(XSSFSheet sheet) return validation; } + [Test] + public void TestTableBasedValidationList() + { + XSSFWorkbook wb = XSSFTestDataSamples.OpenSampleWorkbook("dataValidationTableRange.xlsx"); + XSSFFormulaEvaluator fEval = wb.GetCreationHelper().CreateFormulaEvaluator() as XSSFFormulaEvaluator; + DataValidationEvaluator dve = new DataValidationEvaluator(wb, fEval); + List values = dve.GetValidationValuesForCell(new CellReference("County Ranking", 8, 6, false, false)); + ClassicAssert.AreEqual(32, values.Count, "wrong # of valid values"); + } } } diff --git a/testcases/openxml4net/TestPackageCoreProperties.cs b/testcases/openxml4net/TestPackageCoreProperties.cs index bb929a3c7..b0c25a1b3 100644 --- a/testcases/openxml4net/TestPackageCoreProperties.cs +++ b/testcases/openxml4net/TestPackageCoreProperties.cs @@ -66,7 +66,7 @@ public void TestSetProperties() SimpleDateFormat msdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.fff'Z'"); msdf.TimeZone = TimeZoneInfo.Utc; - PackageProperties props = p.GetPackageProperties(); + IPackageProperties props = p.GetPackageProperties(); //test various date formats props.SetCreatedProperty("2007-05-12T08:00:00Z"); @@ -133,7 +133,7 @@ private void CompareProperties(OPCPackage p) DateTime expectedDate = df.Parse("2007-05-12T08:00:00Z"); // Gets the core properties - PackageProperties props = p.GetPackageProperties(); + IPackageProperties props = p.GetPackageProperties(); ClassicAssert.AreEqual("MyCategory", props.GetCategoryProperty()); ClassicAssert.AreEqual("MyContentStatus", props.GetContentStatusProperty() ); @@ -215,7 +215,7 @@ public void TestGetPropertiesLO() { // Open the namespace OPCPackage pkg1 = OPCPackage.Open(OpenXml4NetTestDataSamples.OpenSampleStream("51444.xlsx")); - PackageProperties props1 = pkg1.GetPackageProperties(); + IPackageProperties props1 = pkg1.GetPackageProperties(); ClassicAssert.AreEqual(null, props1.GetTitleProperty()); props1.SetTitleProperty("Bug 51444 fixed"); MemoryStream out1 = new MemoryStream(); @@ -224,7 +224,7 @@ public void TestGetPropertiesLO() pkg1.Close(); OPCPackage pkg2 = OPCPackage.Open(new MemoryStream(out1.ToArray())); - PackageProperties props2 = pkg2.GetPackageProperties(); + IPackageProperties props2 = pkg2.GetPackageProperties(); props2.SetTitleProperty("Bug 51444 fixed"); pkg2.Close(); } diff --git a/testcases/openxml4net/TestRelationships.cs b/testcases/openxml4net/TestRelationships.cs index b9896d5b8..eef7f2228 100644 --- a/testcases/openxml4net/TestRelationships.cs +++ b/testcases/openxml4net/TestRelationships.cs @@ -258,8 +258,7 @@ public void TestCreateRelationsFromScratch() { partB.GetRelationship("rId1").TargetUri.ToString()); // Check core too ClassicAssert.AreEqual("/docProps/core.xml", - pkg.GetRelationshipsByType( - "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties").GetRelationship(0).TargetUri.ToString()); + pkg.GetRelationshipsByType(PackageRelationshipTypes.CORE_PROPERTIES).GetRelationship(0).TargetUri.ToString()); // Add some more partB.AddExternalRelationship("http://poi.apache.org/new", "http://example/poi/new"); diff --git a/testcases/test-data/spreadsheet/61287.xls b/testcases/test-data/spreadsheet/61287.xls new file mode 100644 index 000000000..4d35295e3 Binary files /dev/null and b/testcases/test-data/spreadsheet/61287.xls differ diff --git a/testcases/test-data/spreadsheet/61300.xls b/testcases/test-data/spreadsheet/61300.xls new file mode 100644 index 000000000..0b54c8cff Binary files /dev/null and b/testcases/test-data/spreadsheet/61300.xls differ diff --git a/testcases/test-data/spreadsheet/dataValidationTableRange.xlsx b/testcases/test-data/spreadsheet/dataValidationTableRange.xlsx new file mode 100644 index 000000000..76e24344e Binary files /dev/null and b/testcases/test-data/spreadsheet/dataValidationTableRange.xlsx differ