diff --git a/ClosedXML/Attributes/XLColumnAttribute.cs b/ClosedXML/Attributes/XLColumnAttribute.cs index 41b541b..526dcb0 100644 --- a/ClosedXML/Attributes/XLColumnAttribute.cs +++ b/ClosedXML/Attributes/XLColumnAttribute.cs @@ -23,7 +23,7 @@ { var attribute = GetXLColumnAttribute(mi); if (attribute == null) return null; - return XLHelper.IsNullOrWhiteSpace(attribute.Header) ? null : attribute.Header; + return String.IsNullOrWhiteSpace(attribute.Header) ? null : attribute.Header; } internal static Int32 GetOrder(MemberInfo mi) diff --git a/ClosedXML/ClosedXML.csproj b/ClosedXML/ClosedXML.csproj index 564d1ec..f4d6ebf 100644 --- a/ClosedXML/ClosedXML.csproj +++ b/ClosedXML/ClosedXML.csproj @@ -65,9 +65,18 @@ + + + + + + + + + @@ -84,6 +93,8 @@ + + diff --git a/ClosedXML/Excel/CalcEngine/CalcEngine.cs b/ClosedXML/Excel/CalcEngine/CalcEngine.cs index 71e3d72..c1a1aa1 100644 --- a/ClosedXML/Excel/CalcEngine/CalcEngine.cs +++ b/ClosedXML/Excel/CalcEngine/CalcEngine.cs @@ -77,18 +77,20 @@ // skip leading equals sign if (_len > 0 && _expr[0] == '=') - { _ptr++; - } + + // skip leading +'s + while (_len > _ptr && _expr[_ptr] == '+') + _ptr++; // parse the expression var expr = ParseExpression(); // check for errors - if (_token.ID != TKID.END) - { - Throw(); - } + if (_token.ID == TKID.OPEN) + Throw("Unknown function: " + expr.LastParseItem); + else if (_token.ID != TKID.END) + Throw("Expected end of expression"); // optimize expression if (_optimize) @@ -113,10 +115,9 @@ /// public object Evaluate(string expression) { - var x = //Parse(expression); - _cache != null - ? _cache[expression] - : Parse(expression); + var x = _cache != null + ? _cache[expression] + : Parse(expression); return x.Evaluate(); } @@ -252,6 +253,18 @@ #region ** token/keyword tables + private static readonly IDictionary ErrorMap = new Dictionary() + { + ["#REF!"] = ErrorExpression.ExpressionErrorType.CellReference, + ["#VALUE!"] = ErrorExpression.ExpressionErrorType.CellValue, + ["#DIV/0!"] = ErrorExpression.ExpressionErrorType.DivisionByZero, + ["#NAME?"] = ErrorExpression.ExpressionErrorType.NameNotRecognized, + ["#N/A"] = ErrorExpression.ExpressionErrorType.NoValueAvailable, + ["#NULL!"] = ErrorExpression.ExpressionErrorType.NullValue, + ["#NUM!"] = ErrorExpression.ExpressionErrorType.NumberInvalid + }; + + // build/get static token table private Dictionary GetSymbolTable() { @@ -413,11 +426,11 @@ var pCnt = p == null ? 0 : p.Count; if (fnDef.ParmMin != -1 && pCnt < fnDef.ParmMin) { - Throw("Too few parameters."); + Throw(string.Format("Too few parameters for function '{0}'. Expected a minimum of {1} and a maximum of {2}.", id, fnDef.ParmMin, fnDef.ParmMax)); } if (fnDef.ParmMax != -1 && pCnt > fnDef.ParmMax) { - Throw("Too many parameters."); + Throw(string.Format("Too many parameters for function '{0}'.Expected a minimum of {1} and a maximum of {2}.", id, fnDef.ParmMin, fnDef.ParmMax)); } x = new FunctionExpression(fnDef, p); break; @@ -462,6 +475,10 @@ } break; + + case TKTYPE.ERROR: + x = new ErrorExpression((ErrorExpression.ExpressionErrorType)_token.Value); + break; } // make sure we got something... @@ -662,26 +679,12 @@ return; } - // parse dates (review) - if (c == '#') + // parse #REF! (and other errors) in formula + if (c == '#' && ErrorMap.Any(pair => _len > _ptr+pair.Key.Length && _expr.Substring(_ptr, pair.Key.Length).Equals(pair.Key, StringComparison.OrdinalIgnoreCase))) { - // look for end # - for (i = 1; i + _ptr < _len; i++) - { - c = _expr[_ptr + i]; - if (c == '#') break; - } - - // check that we got the end of the date - if (c != '#') - { - Throw("Can't find final date delimiter ('#')."); - } - - // end of date - var lit = _expr.Substring(_ptr + 1, i - 1); - _ptr += i + 1; - _token = new Token(DateTime.Parse(lit, _ci), TKID.ATOM, TKTYPE.LITERAL); + var errorPair = ErrorMap.Single(pair => _len > _ptr + pair.Key.Length && _expr.Substring(_ptr, pair.Key.Length).Equals(pair.Key, StringComparison.OrdinalIgnoreCase)); + _ptr += errorPair.Key.Length; + _token = new Token(errorPair.Value, TKID.ATOM, TKTYPE.ERROR); return; } @@ -713,7 +716,7 @@ if (isEnclosed && disallowedSymbols.Contains(c)) break; - var allowedSymbols = new List() { '_' }; + var allowedSymbols = new List() { '_', '.' }; if (!isLetter && !isDigit && !(isEnclosed || allowedSymbols.Contains(c)) @@ -771,10 +774,10 @@ } // make sure the list was closed correctly - if (_token.ID != TKID.CLOSE) - { - Throw(); - } + if (_token.ID == TKID.OPEN) + Throw("Unknown function: " + expr.LastParseItem); + else if (_token.ID != TKID.CLOSE) + Throw("Syntax error: expected ')'"); // done return parms; @@ -816,7 +819,7 @@ private static void Throw(string msg) { - throw new Exception(msg); + throw new ExpressionParseException(msg); } #endregion ** static helpers diff --git a/ClosedXML/Excel/CalcEngine/Exceptions/CalcEngineException.cs b/ClosedXML/Excel/CalcEngine/Exceptions/CalcEngineException.cs new file mode 100644 index 0000000..962b1c6 --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/Exceptions/CalcEngineException.cs @@ -0,0 +1,18 @@ +using System; + +namespace ClosedXML.Excel.CalcEngine.Exceptions +{ + internal abstract class CalcEngineException : ArgumentException + { + protected CalcEngineException() + : base() + { } + protected CalcEngineException(string message) + : base(message) + { } + + protected CalcEngineException(string message, Exception innerException) + : base(message, innerException) + { } + } +} diff --git a/ClosedXML/Excel/CalcEngine/Exceptions/CellReferenceException.cs b/ClosedXML/Excel/CalcEngine/Exceptions/CellReferenceException.cs new file mode 100644 index 0000000..f0fd88c --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/Exceptions/CellReferenceException.cs @@ -0,0 +1,27 @@ +using System; + +namespace ClosedXML.Excel.CalcEngine.Exceptions +{ + /// + /// This error occurs when you delete a cell referred to in the + /// formula or if you paste cells over the ones referred to in the + /// formula. + /// Corresponds to the #REF! error in Excel + /// + /// + internal class CellReferenceException : CalcEngineException + { + public CellReferenceException() + : base() + { } + + public CellReferenceException(string message) + : base(message) + { } + + public CellReferenceException(string message, Exception innerException) + : base(message, innerException) + { } + + } +} diff --git a/ClosedXML/Excel/CalcEngine/Exceptions/CellValueException.cs b/ClosedXML/Excel/CalcEngine/Exceptions/CellValueException.cs new file mode 100644 index 0000000..0716353 --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/Exceptions/CellValueException.cs @@ -0,0 +1,26 @@ +using System; + +namespace ClosedXML.Excel.CalcEngine.Exceptions +{ + /// + /// This error is most often the result of specifying a + /// mathematical operation with one or more cells that contain + /// text. + /// Corresponds to the #VALUE! error in Excel + /// + /// + internal class CellValueException : CalcEngineException + { + public CellValueException() + : base() + { } + + public CellValueException(string message) + : base(message) + { } + + public CellValueException(string message, Exception innerException) + : base(message, innerException) + { } + } +} diff --git a/ClosedXML/Excel/CalcEngine/Exceptions/DivisionByZeroException.cs b/ClosedXML/Excel/CalcEngine/Exceptions/DivisionByZeroException.cs new file mode 100644 index 0000000..53e2ed1 --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/Exceptions/DivisionByZeroException.cs @@ -0,0 +1,26 @@ +using System; + +namespace ClosedXML.Excel.CalcEngine.Exceptions +{ + /// + /// The division operation in your formula refers to a cell that + /// contains the value 0 or is blank. + /// Corresponds to the #DIV/0! error in Excel + /// + /// + internal class DivisionByZeroException : CalcEngineException + { + public DivisionByZeroException() + : base() + { } + + public DivisionByZeroException(string message) + : base(message) + { } + + public DivisionByZeroException(string message, Exception innerException) + : base(message, innerException) + { } + + } +} \ No newline at end of file diff --git a/ClosedXML/Excel/CalcEngine/Exceptions/NameNotRecognizedException.cs b/ClosedXML/Excel/CalcEngine/Exceptions/NameNotRecognizedException.cs new file mode 100644 index 0000000..0f51e56 --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/Exceptions/NameNotRecognizedException.cs @@ -0,0 +1,27 @@ +using System; + +namespace ClosedXML.Excel.CalcEngine.Exceptions +{ + /// + /// This error value appears when you incorrectly type the range + /// name, refer to a deleted range name, or forget to put quotation + /// marks around a text string in a formula. + /// Corresponds to the #NAME? error in Excel + /// + /// + internal class NameNotRecognizedException : CalcEngineException + { + public NameNotRecognizedException() + : base() + { } + + public NameNotRecognizedException(string message) + : base(message) + { } + + public NameNotRecognizedException(string message, Exception innerException) + : base(message, innerException) + { } + + } +} diff --git a/ClosedXML/Excel/CalcEngine/Exceptions/NoValueAvailableException.cs b/ClosedXML/Excel/CalcEngine/Exceptions/NoValueAvailableException.cs new file mode 100644 index 0000000..0e97fe5 --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/Exceptions/NoValueAvailableException.cs @@ -0,0 +1,27 @@ +using System; + +namespace ClosedXML.Excel.CalcEngine.Exceptions +{ + /// + /// Technically, this is not an error value but a special value + /// that you can manually enter into a cell to indicate that you + /// don’t yet have a necessary value. + /// Corresponds to the #N/A error in Excel. + /// + /// + internal class NoValueAvailableException : CalcEngineException + { + public NoValueAvailableException() + : base() + { } + + public NoValueAvailableException(string message) + : base(message) + { } + + public NoValueAvailableException(string message, Exception innerException) + : base(message, innerException) + { } + + } +} \ No newline at end of file diff --git a/ClosedXML/Excel/CalcEngine/Exceptions/NullValueException.cs b/ClosedXML/Excel/CalcEngine/Exceptions/NullValueException.cs new file mode 100644 index 0000000..d3153d7 --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/Exceptions/NullValueException.cs @@ -0,0 +1,27 @@ +using System; + +namespace ClosedXML.Excel.CalcEngine.Exceptions +{ + /// + /// Because a space indicates an intersection, this error will + /// occur if you insert a space instead of a comma(the union operator) + /// between ranges used in function arguments. + /// Corresponds to the #NULL! error in Excel + /// + /// + internal class NullValueException : CalcEngineException + { + public NullValueException() + : base() + { } + + public NullValueException(string message) + : base(message) + { } + + public NullValueException(string message, Exception innerException) + : base(message, innerException) + { } + + } +} diff --git a/ClosedXML/Excel/CalcEngine/Exceptions/NumberException.cs b/ClosedXML/Excel/CalcEngine/Exceptions/NumberException.cs new file mode 100644 index 0000000..4ce87d0 --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/Exceptions/NumberException.cs @@ -0,0 +1,27 @@ +using System; + +namespace ClosedXML.Excel.CalcEngine.Exceptions +{ + /// + /// This error can be caused by an invalid argument in an Excel + /// function or a formula that produces a number too large or too small + /// to be represented in the worksheet. + /// Corresponds to the #NUM! error in Excel + /// + /// + internal class NumberException : CalcEngineException + { + public NumberException() + : base() + { } + + public NumberException(string message) + : base(message) + { } + + public NumberException(string message, Exception innerException) + : base(message, innerException) + { } + + } +} diff --git a/ClosedXML/Excel/CalcEngine/Expression.cs b/ClosedXML/Excel/CalcEngine/Expression.cs index dcac3c0..b9a29ca 100644 --- a/ClosedXML/Excel/CalcEngine/Expression.cs +++ b/ClosedXML/Excel/CalcEngine/Expression.cs @@ -1,11 +1,18 @@ +using ClosedXML.Excel.CalcEngine.Exceptions; using System; -using System.Threading; using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Threading; namespace ClosedXML.Excel.CalcEngine { + internal abstract class ExpressionBase + { + public abstract string LastParseItem { get; } + } + /// /// Base class that represents parsed expressions. /// @@ -16,34 +23,39 @@ /// object val = expr.Evaluate(); /// /// - internal class Expression : IComparable + internal class Expression : ExpressionBase, IComparable { //--------------------------------------------------------------------------- + #region ** fields - internal Token _token; + internal readonly Token _token; - #endregion + #endregion ** fields //--------------------------------------------------------------------------- + #region ** ctors internal Expression() { _token = new Token(null, TKID.ATOM, TKTYPE.IDENTIFIER); } + internal Expression(object value) { _token = new Token(value, TKID.ATOM, TKTYPE.LITERAL); } + internal Expression(Token tk) { _token = tk; } - #endregion + #endregion ** ctors //--------------------------------------------------------------------------- + #region ** object model public virtual object Evaluate() @@ -54,23 +66,32 @@ } return _token.Value; } + public virtual Expression Optimize() { return this; } - #endregion + #endregion ** object model //--------------------------------------------------------------------------- + #region ** implicit converters public static implicit operator string(Expression x) { + if (x is ErrorExpression) + (x as ErrorExpression).ThrowApplicableException(); + var v = x.Evaluate(); return v == null ? string.Empty : v.ToString(); } + public static implicit operator double(Expression x) { + if (x is ErrorExpression) + (x as ErrorExpression).ThrowApplicableException(); + // evaluate var v = x.Evaluate(); @@ -102,8 +123,12 @@ CultureInfo _ci = Thread.CurrentThread.CurrentCulture; return (double)Convert.ChangeType(v, typeof(double), _ci); } + public static implicit operator bool(Expression x) { + if (x is ErrorExpression) + (x as ErrorExpression).ThrowApplicableException(); + // evaluate var v = x.Evaluate(); @@ -128,8 +153,12 @@ // handle everything else return (double)x == 0 ? false : true; } + public static implicit operator DateTime(Expression x) { + if (x is ErrorExpression) + (x as ErrorExpression).ThrowApplicableException(); + // evaluate var v = x.Evaluate(); @@ -150,9 +179,10 @@ return (DateTime)Convert.ChangeType(v, typeof(DateTime), _ci); } - #endregion + #endregion ** implicit converters //--------------------------------------------------------------------------- + #region ** IComparable public int CompareTo(Expression other) @@ -197,15 +227,27 @@ return c1.CompareTo(c2); } - #endregion + #endregion ** IComparable + + //--------------------------------------------------------------------------- + + #region ** ExpressionBase + + public override string LastParseItem + { + get { return _token?.Value?.ToString() ?? "Unknown value"; } + } + + #endregion ** ExpressionBase } + /// /// Unary expression, e.g. +123 /// - class UnaryExpression : Expression + internal class UnaryExpression : Expression { // ** fields - Expression _expr; + private Expression _expr; // ** ctor public UnaryExpression(Token tk, Expression expr) : base(tk) @@ -220,11 +262,13 @@ { case TKID.ADD: return +(double)_expr; + case TKID.SUB: return -(double)_expr; } throw new ArgumentException("Bad expression."); } + public override Expression Optimize() { _expr = _expr.Optimize(); @@ -232,20 +276,27 @@ ? new Expression(this.Evaluate()) : this; } + + public override string LastParseItem + { + get { return _expr.LastParseItem; } + } } + /// /// Binary expression, e.g. 1+2 /// - class BinaryExpression : Expression + internal class BinaryExpression : Expression { // ** fields - Expression _lft; - Expression _rgt; + private Expression _lft; + + private Expression _rgt; // ** ctor public BinaryExpression(Token tk, Expression exprLeft, Expression exprRight) : base(tk) { - _lft = exprLeft; + _lft = exprLeft; _rgt = exprRight; } @@ -272,18 +323,25 @@ { case TKID.CONCAT: return (string)_lft + (string)_rgt; + case TKID.ADD: return (double)_lft + (double)_rgt; + case TKID.SUB: return (double)_lft - (double)_rgt; + case TKID.MUL: return (double)_lft * (double)_rgt; + case TKID.DIV: return (double)_lft / (double)_rgt; + case TKID.DIVINT: return (double)(int)((double)_lft / (double)_rgt); + case TKID.MOD: return (double)(int)((double)_lft % (double)_rgt); + case TKID.POWER: var a = (double)_lft; var b = (double)_rgt; @@ -297,6 +355,7 @@ } throw new ArgumentException("Bad expression."); } + public override Expression Optimize() { _lft = _lft.Optimize(); @@ -305,20 +364,27 @@ ? new Expression(this.Evaluate()) : this; } + + public override string LastParseItem + { + get { return _rgt.LastParseItem; } + } } + /// /// Function call expression, e.g. sin(0.5) /// - class FunctionExpression : Expression + internal class FunctionExpression : Expression { // ** fields - FunctionDefinition _fn; - List _parms; + private readonly FunctionDefinition _fn; + + private readonly List _parms; // ** ctor internal FunctionExpression() - { - } + { } + public FunctionExpression(FunctionDefinition function, List parms) { _fn = function; @@ -330,6 +396,7 @@ { return _fn.Function(_parms); } + public override Expression Optimize() { bool allLits = true; @@ -349,33 +416,46 @@ ? new Expression(this.Evaluate()) : this; } + + public override string LastParseItem + { + get { return _parms.Last().LastParseItem; } + } } + /// /// Simple variable reference. /// - class VariableExpression : Expression + internal class VariableExpression : Expression { - Dictionary _dct; - string _name; + private readonly Dictionary _dct; + private readonly string _name; public VariableExpression(Dictionary dct, string name) { _dct = dct; _name = name; } + public override object Evaluate() { return _dct[_name]; } + + public override string LastParseItem + { + get { return _name; } + } } + /// /// Expression that represents an external object. /// - class XObjectExpression : + internal class XObjectExpression : Expression, IEnumerable { - object _value; + private readonly object _value; // ** ctor internal XObjectExpression(object value) @@ -398,18 +478,75 @@ // return raw object return _value; } + public IEnumerator GetEnumerator() { return (_value as IEnumerable).GetEnumerator(); } + + public override string LastParseItem + { + get { return Value.ToString(); } + } } /// /// Expression that represents an omitted parameter. /// - class EmptyValueExpression : Expression + internal class EmptyValueExpression : Expression { internal EmptyValueExpression() { } + + public override string LastParseItem + { + get { return ""; } + } + } + + internal class ErrorExpression : Expression + { + internal enum ExpressionErrorType + { + CellReference, + CellValue, + DivisionByZero, + NameNotRecognized, + NoValueAvailable, + NullValue, + NumberInvalid + } + + internal ErrorExpression(ExpressionErrorType eet) + : base(new Token(eet, TKID.ATOM, TKTYPE.ERROR)) + { } + + public override object Evaluate() + { + return this._token.Value; + } + + public void ThrowApplicableException() + { + var eet = (ExpressionErrorType)_token.Value; + switch (eet) + { + // TODO: include last token in exception message + case ExpressionErrorType.CellReference: + throw new CellReferenceException(); + case ExpressionErrorType.CellValue: + throw new CellValueException(); + case ExpressionErrorType.DivisionByZero: + throw new DivisionByZeroException(); + case ExpressionErrorType.NameNotRecognized: + throw new NameNotRecognizedException(); + case ExpressionErrorType.NoValueAvailable: + throw new NoValueAvailableException(); + case ExpressionErrorType.NullValue: + throw new NullValueException(); + case ExpressionErrorType.NumberInvalid: + throw new NumberException(); + } + } } /// diff --git a/ClosedXML/Excel/CalcEngine/ExpressionParseException.cs b/ClosedXML/Excel/CalcEngine/ExpressionParseException.cs new file mode 100644 index 0000000..2de2293 --- /dev/null +++ b/ClosedXML/Excel/CalcEngine/ExpressionParseException.cs @@ -0,0 +1,21 @@ +using System; +using System.Text; + +namespace ClosedXML.Excel.CalcEngine +{ + /// + /// The exception that is thrown when the strings to be parsed to an expression is invalid. + /// + public class ExpressionParseException : Exception + { + /// + /// Initializes a new instance of the ExpressionParseException class with a + /// specified error message. + /// + /// The message that describes the error. + public ExpressionParseException(string message) + : base(message) + { + } + } +} diff --git a/ClosedXML/Excel/CalcEngine/Functions/Information.cs b/ClosedXML/Excel/CalcEngine/Functions/Information.cs index 6df1a1e..6b19dfb 100644 --- a/ClosedXML/Excel/CalcEngine/Functions/Information.cs +++ b/ClosedXML/Excel/CalcEngine/Functions/Information.cs @@ -1,3 +1,4 @@ +using ClosedXML.Excel.CalcEngine.Exceptions; using System; using System.Collections.Generic; using System.Globalization; @@ -9,27 +10,42 @@ public static void Register(CalcEngine ce) { //TODO: Add documentation - ce.RegisterFunction("ERRORTYPE",1,ErrorType); - ce.RegisterFunction("ISBLANK", 1,int.MaxValue, IsBlank); - ce.RegisterFunction("ISERR",1, int.MaxValue, IsErr); - ce.RegisterFunction("ISERROR",1, int.MaxValue, IsError); - ce.RegisterFunction("ISEVEN",1, IsEven); - ce.RegisterFunction("ISLOGICAL",1,int.MaxValue,IsLogical); - ce.RegisterFunction("ISNA",1, int.MaxValue, IsNa); - ce.RegisterFunction("ISNONTEXT",1, int.MaxValue, IsNonText); - ce.RegisterFunction("ISNUMBER",1, int.MaxValue, IsNumber); - ce.RegisterFunction("ISODD",1,IsOdd); - ce.RegisterFunction("ISREF",1, int.MaxValue, IsRef); + ce.RegisterFunction("ERRORTYPE", 1, ErrorType); + ce.RegisterFunction("ISBLANK", 1, int.MaxValue, IsBlank); + ce.RegisterFunction("ISERR", 1, int.MaxValue, IsErr); + ce.RegisterFunction("ISERROR", 1, int.MaxValue, IsError); + ce.RegisterFunction("ISEVEN", 1, IsEven); + ce.RegisterFunction("ISLOGICAL", 1, int.MaxValue, IsLogical); + ce.RegisterFunction("ISNA", 1, int.MaxValue, IsNa); + ce.RegisterFunction("ISNONTEXT", 1, int.MaxValue, IsNonText); + ce.RegisterFunction("ISNUMBER", 1, int.MaxValue, IsNumber); + ce.RegisterFunction("ISODD", 1, IsOdd); + ce.RegisterFunction("ISREF", 1, int.MaxValue, IsRef); ce.RegisterFunction("ISTEXT", 1, int.MaxValue, IsText); - ce.RegisterFunction("N",1,N); - ce.RegisterFunction("NA",0,NA); - ce.RegisterFunction("TYPE",1,Type); + ce.RegisterFunction("N", 1, N); + ce.RegisterFunction("NA", 0, NA); + ce.RegisterFunction("TYPE", 1, Type); } + static IDictionary errorTypes = new Dictionary() + { + [ErrorExpression.ExpressionErrorType.NullValue] = 1, + [ErrorExpression.ExpressionErrorType.DivisionByZero] = 2, + [ErrorExpression.ExpressionErrorType.CellValue] = 3, + [ErrorExpression.ExpressionErrorType.CellReference] = 4, + [ErrorExpression.ExpressionErrorType.NameNotRecognized] = 5, + [ErrorExpression.ExpressionErrorType.NumberInvalid] = 6, + [ErrorExpression.ExpressionErrorType.NoValueAvailable] = 7 + }; + static object ErrorType(List p) { - //TODO: Write Code - throw new NotSupportedException();; + var v = p[0].Evaluate(); + + if (v is ErrorExpression.ExpressionErrorType) + return errorTypes[(ErrorExpression.ExpressionErrorType)v]; + else + throw new NoValueAvailableException(); } static object IsBlank(List p) @@ -46,17 +62,19 @@ return isBlank; } - //TODO: Support for Error Values static object IsErr(List p) { - //TODO: Write Code - throw new NotSupportedException(); + var v = p[0].Evaluate(); + + return v is ErrorExpression.ExpressionErrorType + && ((ErrorExpression.ExpressionErrorType)v) != ErrorExpression.ExpressionErrorType.NoValueAvailable; } - + static object IsError(List p) { - //TODO: Write Code - throw new NotSupportedException(); + var v = p[0].Evaluate(); + + return v is ErrorExpression.ExpressionErrorType; } static object IsEven(List p) @@ -74,7 +92,7 @@ { var v = p[0].Evaluate(); var isLogical = v is bool; - + if (isLogical && p.Count > 1) { var sublist = p.GetRange(1, p.Count); @@ -86,8 +104,10 @@ static object IsNa(List p) { - //TODO: Write Code - throw new NotSupportedException();; + var v = p[0].Evaluate(); + + return v is ErrorExpression.ExpressionErrorType + && ((ErrorExpression.ExpressionErrorType)v) == ErrorExpression.ExpressionErrorType.NoValueAvailable; } static object IsNonText(List p) @@ -110,16 +130,16 @@ try { var stringValue = (string) v; - double.Parse(stringValue.TrimEnd('%', ' '), NumberStyles.Any); - isNumber = true; + double dv; + return double.TryParse(stringValue.TrimEnd('%', ' '), NumberStyles.Any, null, out dv); } catch (Exception) { isNumber = false; } } - - if (isNumber && p.Count > 1) + + if (isNumber && p.Count > 1) { var sublist = p.GetRange(1, p.Count); isNumber = (bool)IsNumber(sublist); @@ -135,8 +155,13 @@ static object IsRef(List p) { - //TODO: Write Code - throw new NotSupportedException();; + var oe = p[0] as XObjectExpression; + if (oe == null) + return false; + + var crr = oe.Value as CellRangeReference; + + return crr != null; } static object IsText(List p) @@ -161,8 +186,7 @@ static object NA(List p) { - //TODO: Write Code - throw new NotSupportedException();; + return ErrorExpression.ExpressionErrorType.NoValueAvailable; } static object Type(List p) @@ -190,4 +214,4 @@ return null; } } -} \ No newline at end of file +} diff --git a/ClosedXML/Excel/CalcEngine/Functions/Lookup.cs b/ClosedXML/Excel/CalcEngine/Functions/Lookup.cs index 8a9ac19..801a195 100644 --- a/ClosedXML/Excel/CalcEngine/Functions/Lookup.cs +++ b/ClosedXML/Excel/CalcEngine/Functions/Lookup.cs @@ -1,3 +1,4 @@ +using ClosedXML.Excel.CalcEngine.Exceptions; using System; using System.Collections.Generic; using System.Linq; @@ -15,7 +16,7 @@ //ce.RegisterFunction("COLUMNS", , Columns); // Returns the number of columns in a reference //ce.RegisterFunction("FORMULATEXT", , Formulatext); // Returns the formula at the given reference as text //ce.RegisterFunction("GETPIVOTDATA", , Getpivotdata); // Returns data stored in a PivotTable report - ce.RegisterFunction("HLOOKUP", 4, Hlookup); // Looks in the top row of an array and returns the value of the indicated cell + ce.RegisterFunction("HLOOKUP", 3, 4, Hlookup); // Looks in the top row of an array and returns the value of the indicated cell //ce.RegisterFunction("HYPERLINK", , Hyperlink); // Creates a shortcut or jump that opens a document stored on a network server, an intranet, or the Internet //ce.RegisterFunction("INDEX", , Index); // Uses an index to choose a value from a reference or array //ce.RegisterFunction("INDIRECT", , Indirect); // Returns a reference indicated by a text value @@ -26,7 +27,7 @@ //ce.RegisterFunction("ROWS", , Rows); // Returns the number of rows in a reference //ce.RegisterFunction("RTD", , Rtd); // Retrieves real-time data from a program that supports COM automation //ce.RegisterFunction("TRANSPOSE", , Transpose); // Returns the transpose of an array - ce.RegisterFunction("VLOOKUP", 4, Vlookup); // Looks in the first column of an array and moves across the row to return the value of a cell + ce.RegisterFunction("VLOOKUP", 3, 4, Vlookup); // Looks in the first column of an array and moves across the row to return the value of a cell } private static object Hlookup(List p) @@ -34,20 +35,25 @@ var lookup_value = p[0]; var table_array = p[1] as XObjectExpression; + if (table_array == null) + throw new NoValueAvailableException("table_array has to be a range"); + var range_reference = table_array.Value as CellRangeReference; + if (range_reference == null) + throw new NoValueAvailableException("table_array has to be a range"); + var range = range_reference.Range; var row_index_num = (int)(p[2]); - var range_lookup = p.Count < 4 || (bool)(p[3]); - - if (table_array == null || range_reference == null) - throw new ApplicationException("table_array has to be a range"); + var range_lookup = p.Count < 4 + || p[3] is EmptyValueExpression + || (bool)(p[3]); if (row_index_num < 1) - throw new ApplicationException("col_index_num has to be positive"); + throw new CellReferenceException("Row index has to be positive"); if (row_index_num > range.RowCount()) - throw new ApplicationException("col_index_num must be smaller or equal to the number of rows in the table array"); + throw new CellReferenceException("Row index has to be positive"); IXLRangeColumn matching_column; matching_column = range.FindColumn(c => !c.Cell(1).IsEmpty() && new Expression(c.Cell(1).Value).CompareTo(lookup_value) == 0); @@ -67,7 +73,7 @@ } if (matching_column == null) - throw new ApplicationException("No matches found."); + throw new NoValueAvailableException("No matches found."); return matching_column .Cell(row_index_num) @@ -79,23 +85,35 @@ var lookup_value = p[0]; var table_array = p[1] as XObjectExpression; + if (table_array == null) + throw new NoValueAvailableException("table_array has to be a range"); + var range_reference = table_array.Value as CellRangeReference; + if (range_reference == null) + throw new NoValueAvailableException("table_array has to be a range"); + var range = range_reference.Range; var col_index_num = (int)(p[2]); - var range_lookup = p.Count < 4 || (bool)(p[3]); - - if (table_array == null || range_reference == null) - throw new ApplicationException("table_array has to be a range"); + var range_lookup = p.Count < 4 + || p[3] is EmptyValueExpression + || (bool)(p[3]); if (col_index_num < 1) - throw new ApplicationException("col_index_num has to be positive"); + throw new CellReferenceException("Column index has to be positive"); if (col_index_num > range.ColumnCount()) - throw new ApplicationException("col_index_num must be smaller or equal to the number of columns in the table array"); + throw new CellReferenceException("Colum index must be smaller or equal to the number of columns in the table array"); IXLRangeRow matching_row; - matching_row = range.FindRow(r => !r.Cell(1).IsEmpty() && new Expression(r.Cell(1).Value).CompareTo(lookup_value) == 0); + try + { + matching_row = range.FindRow(r => !r.Cell(1).IsEmpty() && new Expression(r.Cell(1).Value).CompareTo(lookup_value) == 0); + } + catch (Exception ex) + { + throw new NoValueAvailableException("No matches found", ex); + } if (range_lookup && matching_row == null) { var first_row = range.FirstRow().RowNumber(); @@ -112,7 +130,7 @@ } if (matching_row == null) - throw new ApplicationException("No matches found."); + throw new NoValueAvailableException("No matches found."); return matching_row .Cell(col_index_num) diff --git a/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs b/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs index c4e86ca..12e0995 100644 --- a/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs +++ b/ClosedXML/Excel/CalcEngine/Functions/MathTrig.cs @@ -1,8 +1,8 @@ +using ClosedXML.Excel.CalcEngine.Functions; using System; using System.Collections; using System.Collections.Generic; using System.Linq; -using ClosedXML.Excel.CalcEngine.Functions; namespace ClosedXML.Excel.CalcEngine { @@ -29,7 +29,8 @@ ce.RegisterFunction("EXP", 1, Exp); ce.RegisterFunction("FACT", 1, Fact); ce.RegisterFunction("FACTDOUBLE", 1, FactDouble); - ce.RegisterFunction("FLOOR", 1, Floor); + ce.RegisterFunction("FLOOR", 1, 2, Floor); + ce.RegisterFunction("FLOOR.MATH", 1, 3, FloorMath); ce.RegisterFunction("GCD", 1, 255, Gcd); ce.RegisterFunction("INT", 1, Int); ce.RegisterFunction("LCM", 1, 255, Lcm); @@ -120,12 +121,44 @@ private static object Floor(List p) { - return Math.Floor(p[0]); + double number = p[0]; + double significance = 1; + if (p.Count > 1) + significance = p[1]; + + if (significance < 0) + { + number = -number; + significance = -significance; + + return -Math.Floor(number / significance) * significance; + } + else if (significance == 1) + return Math.Floor(number); + else + return Math.Floor(number / significance) * significance; + } + + private static object FloorMath(List p) + { + double number = p[0]; + double significance = 1; + if (p.Count > 1) significance = p[1]; + + double mode = 0; + if (p.Count > 2) mode = p[2]; + + if (number >= 0) + return Math.Floor(number / Math.Abs(significance)) * Math.Abs(significance); + else if (mode >= 0) + return Math.Floor(number / Math.Abs(significance)) * Math.Abs(significance); + else + return -Math.Floor(-number / Math.Abs(significance)) * Math.Abs(significance); } private static object Int(List p) { - return (int) ((double) p[0]); + return (int)((double)p[0]); } private static object Ln(List p) @@ -135,7 +168,7 @@ private static object Log(List p) { - var lbase = p.Count > 1 ? (double) p[1] : 10; + var lbase = p.Count > 1 ? (double)p[1] : 10; return Math.Log(p[0], lbase); } @@ -161,7 +194,7 @@ private static object RandBetween(List p) { - return _rnd.Next((int) (double) p[0], (int) (double) p[1]); + return _rnd.Next((int)(double)p[0], (int)(double)p[1]); } private static object Sign(List p) @@ -240,42 +273,42 @@ private static object Trunc(List p) { - return (double) (int) ((double) p[0]); + return (double)(int)((double)p[0]); } public static double DegreesToRadians(double degrees) { - return (Math.PI/180.0)*degrees; + return (Math.PI / 180.0) * degrees; } public static double RadiansToDegrees(double radians) { - return (180.0/Math.PI)*radians; + return (180.0 / Math.PI) * radians; } public static double GradsToRadians(double grads) { - return (grads/200.0)*Math.PI; + return (grads / 200.0) * Math.PI; } public static double RadiansToGrads(double radians) { - return (radians/Math.PI)*200.0; + return (radians / Math.PI) * 200.0; } public static double DegreesToGrads(double degrees) { - return (degrees/9.0)*10.0; + return (degrees / 9.0) * 10.0; } public static double GradsToDegrees(double grads) { - return (grads/10.0)*9.0; + return (grads / 10.0) * 9.0; } public static double ASinh(double x) { - return (Math.Log(x + Math.Sqrt(x*x + 1.0))); + return (Math.Log(x + Math.Sqrt(x * x + 1.0))); } private static object Acosh(List p) @@ -295,8 +328,8 @@ private static object Combin(List p) { - Int32 n = (int) p[0]; - Int32 k = (int) p[1]; + Int32 n = (int)p[0]; + Int32 k = (int)p[1]; return XLMath.Combin(n, k); } @@ -305,8 +338,6 @@ return p[0] * (180.0 / Math.PI); } - - private static object Fact(List p) { var num = Math.Floor(p[0]); @@ -348,15 +379,15 @@ private static int Lcm(int a, int b) { if (a == 0 || b == 0) return 0; - return a * ( b / Gcd(a, b)); + return a * (b / Gcd(a, b)); } private static object Mod(List p) { - Int32 n = (int)Math.Abs(p[0]); - Int32 d = (int)p[1]; - var ret = n % d; - return d < 0 ? ret * -1 : ret; + double number = p[0]; + double divisor = p[1]; + + return number - Math.Floor(number / divisor) * divisor; } private static object MRound(List p) @@ -479,7 +510,6 @@ temp = Math.Round(temp, 0, MidpointRounding.AwayFromZero); return temp * Math.Pow(10, digits); } - } private static object RoundDown(List p) @@ -512,7 +542,7 @@ var obj = p[3] as XObjectExpression; if (obj == null) - return p[3] * Math.Pow(x , n); + return p[3] * Math.Pow(x, n); Double total = 0; Int32 i = 0; @@ -540,26 +570,37 @@ { case 1: return tally.Average(); + case 2: return tally.Count(true); + case 3: return tally.Count(false); + case 4: return tally.Max(); + case 5: return tally.Min(); + case 6: return tally.Product(); + case 7: return tally.Std(); + case 8: return tally.StdP(); + case 9: return tally.Sum(); + case 10: return tally.Var(); + case 11: return tally.VarP(); + default: throw new ArgumentException("Function not supported."); } @@ -591,19 +632,18 @@ } } - return C; } private static double[,] GetArray(Expression expression) { var oExp1 = expression as XObjectExpression; - if (oExp1 == null) return new [,]{{(Double)expression}}; + if (oExp1 == null) return new[,] { { (Double)expression } }; var range = (oExp1.Value as CellRangeReference).Range; var rowCount = range.RowCount(); var columnCount = range.ColumnCount(); - var arr = new double[rowCount,columnCount]; + var arr = new double[rowCount, columnCount]; for (int row = 0; row < rowCount; row++) { diff --git a/ClosedXML/Excel/CalcEngine/Functions/Text.cs b/ClosedXML/Excel/CalcEngine/Functions/Text.cs index cb66322..8ff1da3 100644 --- a/ClosedXML/Excel/CalcEngine/Functions/Text.cs +++ b/ClosedXML/Excel/CalcEngine/Functions/Text.cs @@ -1,3 +1,4 @@ +using ClosedXML.Excel.CalcEngine.Exceptions; using System; using System.Collections.Generic; using System.Globalization; @@ -44,7 +45,9 @@ private static object _Char(List p) { var i = (int)p[0]; - if (i < 1 || i > 255) throw new IndexOutOfRangeException(); + if (i < 1 || i > 255) + throw new CellValueException(string.Format("The number {0} is out of the required range (1 to 255)", i)); + var c = (char)i; return c.ToString(); } @@ -76,7 +79,7 @@ } var index = text.IndexOf(srch, start, StringComparison.Ordinal); if (index == -1) - throw new Exception("String not found."); + throw new ArgumentException("String not found."); else return index + 1; } @@ -192,7 +195,7 @@ var search = WildcardToRegex((string)p[0]); var text = (string)p[1]; - if ("" == text) throw new Exception("Invalid input string."); + if ("" == text) throw new ArgumentException("Invalid input string."); var start = 0; if (p.Count > 2) @@ -203,12 +206,12 @@ Regex r = new Regex(search, RegexOptions.Compiled | RegexOptions.IgnoreCase); var match = r.Match(text.Substring(start)); if (!match.Success) - throw new Exception("Search failed."); + throw new ArgumentException("Search failed."); else return match.Index + start + 1; //var index = text.IndexOf(search, start, StringComparison.OrdinalIgnoreCase); //if (index == -1) - // throw new Exception("String not found."); + // throw new ArgumentException("String not found."); //else // return index + 1; } @@ -233,7 +236,7 @@ int index = (int)p[3]; if (index < 1) { - throw new Exception("Invalid index in Substitute."); + throw new ArgumentException("Invalid index in Substitute."); } int pos = text.IndexOf(oldText); while (pos > -1 && index > 1) diff --git a/ClosedXML/Excel/CalcEngine/Functions/XLMatrix.cs b/ClosedXML/Excel/CalcEngine/Functions/XLMatrix.cs index 80d2663..79d6a70 100644 --- a/ClosedXML/Excel/CalcEngine/Functions/XLMatrix.cs +++ b/ClosedXML/Excel/CalcEngine/Functions/XLMatrix.cs @@ -59,7 +59,7 @@ public void MakeLU() // Function for LU decomposition { - if (!IsSquare()) throw new Exception("The matrix is not square!"); + if (!IsSquare()) throw new InvalidOperationException("The matrix is not square!"); L = IdentityMatrix(rows, cols); U = Duplicate(); @@ -79,8 +79,8 @@ k0 = i; } } - if (p == 0) - throw new Exception("The matrix is singular!"); + if (p == 0) + throw new InvalidOperationException("The matrix is singular!"); var pom1 = pi[k]; pi[k] = pi[k0]; @@ -115,8 +115,8 @@ public XLMatrix SolveWith(XLMatrix v) // Function solves Ax = v in confirmity with solution vector "v" { - if (rows != cols) throw new Exception("The matrix is not square!"); - if (rows != v.rows) throw new Exception("Wrong number of results in solution vector!"); + if (rows != cols) throw new InvalidOperationException("The matrix is not square!"); + if (rows != v.rows) throw new ArgumentException("Wrong number of results in solution vector!"); if (L == null) MakeLU(); var b = new XLMatrix(rows, 1); @@ -242,9 +242,9 @@ for (var j = 0; j < nums.Length; j++) matrix[i, j] = double.Parse(nums[j]); } } - catch (FormatException) + catch (FormatException fe) { - throw new Exception("Wrong input format!"); + throw new FormatException("Wrong input format!", fe); } return matrix; } @@ -345,7 +345,7 @@ private static XLMatrix StrassenMultiply(XLMatrix A, XLMatrix B) // Smart matrix multiplication { - if (A.cols != B.rows) throw new Exception("Wrong dimension of matrix!"); + if (A.cols != B.rows) throw new ArgumentException("Wrong dimension of matrix!"); XLMatrix R; @@ -513,7 +513,7 @@ public static XLMatrix StupidMultiply(XLMatrix m1, XLMatrix m2) // Stupid matrix multiplication { - if (m1.cols != m2.rows) throw new Exception("Wrong dimensions of matrix!"); + if (m1.cols != m2.rows) throw new ArgumentException("Wrong dimensions of matrix!"); var result = ZeroMatrix(m1.rows, m2.cols); for (var i = 0; i < result.rows; i++) @@ -535,7 +535,7 @@ private static XLMatrix Add(XLMatrix m1, XLMatrix m2) { if (m1.rows != m2.rows || m1.cols != m2.cols) - throw new Exception("Matrices must have the same dimensions!"); + throw new ArgumentException("Matrices must have the same dimensions!"); var r = new XLMatrix(m1.rows, m1.cols); for (var i = 0; i < r.rows; i++) for (var j = 0; j < r.cols; j++) diff --git a/ClosedXML/Excel/CalcEngine/Token.cs b/ClosedXML/Excel/CalcEngine/Token.cs index 67351df..4766a51 100644 --- a/ClosedXML/Excel/CalcEngine/Token.cs +++ b/ClosedXML/Excel/CalcEngine/Token.cs @@ -1,12 +1,13 @@ namespace ClosedXML.Excel.CalcEngine { /// - /// Represents a node in the expression tree. + /// Represents a node in the expression tree. /// internal class Token - { + { // ** fields - public TKID ID; + public TKID ID; + public TKTYPE Type; public object Value; @@ -15,22 +16,25 @@ { Value = value; ID = id; - Type = type; - } + Type = type; + } } + /// /// Token types (used when building expressions, sequence defines operator priority) /// internal enum TKTYPE { - COMPARE, // < > = <= >= - ADDSUB, // + - - MULDIV, // * / - POWER, // ^ - GROUP, // ( ) , . - LITERAL, // 123.32, "Hello", etc. - IDENTIFIER // functions, external objects, bindings + COMPARE, // < > = <= >= + ADDSUB, // + - + MULDIV, // * / + POWER, // ^ + GROUP, // ( ) , . + LITERAL, // 123.32, "Hello", etc. + IDENTIFIER, // functions, external objects, bindings + ERROR // e.g. #REF! } + /// /// Token ID (used when evaluating expressions) /// diff --git a/ClosedXML/Excel/CalcEngine/XLCalcEngine.cs b/ClosedXML/Excel/CalcEngine/XLCalcEngine.cs index 33011e2..108ea40 100644 --- a/ClosedXML/Excel/CalcEngine/XLCalcEngine.cs +++ b/ClosedXML/Excel/CalcEngine/XLCalcEngine.cs @@ -92,13 +92,13 @@ { if (_evaluating) { - throw new Exception("Circular Reference"); + throw new InvalidOperationException("Circular Reference"); } try { _evaluating = true; var f = cell.FormulaA1; - if (XLHelper.IsNullOrWhiteSpace(f)) + if (String.IsNullOrWhiteSpace(f)) return cell.Value; else return new XLCalcEngine(cell.Worksheet).Evaluate(f); diff --git a/ClosedXML/Excel/Cells/IXLCell.cs b/ClosedXML/Excel/Cells/IXLCell.cs index d685041..fc3724e 100644 --- a/ClosedXML/Excel/Cells/IXLCell.cs +++ b/ClosedXML/Excel/Cells/IXLCell.cs @@ -7,6 +7,8 @@ { public enum XLCellValues { Text, Number, Boolean, DateTime, TimeSpan } + public enum XLTableCellType { None, Header, Data, Total } + public enum XLClearOptions { ContentsAndFormats, @@ -30,7 +32,7 @@ /// Gets this cell's address, relative to the worksheet. /// The cell's address. - IXLAddress Address { get; } + IXLAddress Address { get; } /// /// Gets or sets the type of this cell's data. @@ -115,8 +117,6 @@ Boolean TryGetValue(out T value); - - Boolean HasHyperlink { get; } /// @@ -152,7 +152,6 @@ /// IXLRange AsRange(); - /// /// Gets or sets the cell's style. /// @@ -173,6 +172,21 @@ IXLRange InsertData(IEnumerable data); /// + /// Inserts the IEnumerable data elements and returns the range it occupies. + /// + /// The IEnumerable data. + /// if set to true the data will be transposed before inserting. + /// + IXLRange InsertData(IEnumerable data, Boolean tranpose); + + /// + /// Inserts the data of a data table. + /// + /// The data table. + /// The range occupied by the inserted data + IXLRange InsertData(DataTable dataTable); + + /// /// Inserts the IEnumerable data elements as a table and returns it. /// The new table will receive a generic name: Table# /// @@ -208,7 +222,6 @@ /// IXLTable InsertTable(IEnumerable data, String tableName, Boolean createTable); - /// /// Inserts the DataTable data elements as a table and returns it. /// The new table will receive a generic name: Table# @@ -245,22 +258,26 @@ /// IXLTable InsertTable(DataTable data, String tableName, Boolean createTable); + XLTableCellType TableCellType(); XLHyperlink Hyperlink { get; set; } IXLWorksheet Worksheet { get; } IXLDataValidation DataValidation { get; } IXLDataValidation NewDataValidation { get; } + IXLDataValidation SetDataValidation(); - IXLCells InsertCellsAbove(int numberOfRows); + IXLCells InsertCellsBelow(int numberOfRows); + IXLCells InsertCellsAfter(int numberOfColumns); + IXLCells InsertCellsBefore(int numberOfColumns); /// - /// Creates a named range out of this cell. + /// Creates a named range out of this cell. /// If the named range exists, it will add this range to that named range. /// The default scope for the named range is Workbook. /// @@ -268,7 +285,7 @@ IXLCell AddToNamed(String rangeName); /// - /// Creates a named range out of this cell. + /// Creates a named range out of this cell. /// If the named range exists, it will add this range to that named range. /// Name of the range. /// The scope for the named range. @@ -276,7 +293,7 @@ IXLCell AddToNamed(String rangeName, XLScope scope); /// - /// Creates a named range out of this cell. + /// Creates a named range out of this cell. /// If the named range exists, it will add this range to that named range. /// Name of the range. /// The scope for the named range. @@ -285,8 +302,11 @@ IXLCell AddToNamed(String rangeName, XLScope scope, String comment); IXLCell CopyFrom(IXLCell otherCell); + IXLCell CopyFrom(String otherCell); + IXLCell CopyTo(IXLCell target); + IXLCell CopyTo(String target); String ValueCached { get; } @@ -297,19 +317,29 @@ Boolean HasComment { get; } Boolean IsMerged(); + Boolean IsEmpty(); + Boolean IsEmpty(Boolean includeFormats); IXLCell CellAbove(); + IXLCell CellAbove(Int32 step); + IXLCell CellBelow(); + IXLCell CellBelow(Int32 step); + IXLCell CellLeft(); + IXLCell CellLeft(Int32 step); + IXLCell CellRight(); + IXLCell CellRight(Int32 step); IXLColumn WorksheetColumn(); + IXLRow WorksheetRow(); Boolean HasDataValidation { get; } @@ -319,9 +349,11 @@ void Select(); Boolean Active { get; set; } + IXLCell SetActive(Boolean value = true); Boolean HasFormula { get; } + Boolean HasArrayFormula { get; } IXLRangeAddress FormulaReference { get; set; } } diff --git a/ClosedXML/Excel/Cells/XLCell.cs b/ClosedXML/Excel/Cells/XLCell.cs index faf129b..b0d8fa3 100644 --- a/ClosedXML/Excel/Cells/XLCell.cs +++ b/ClosedXML/Excel/Cells/XLCell.cs @@ -1,4 +1,4 @@ -using FastMember; +using FastMember; using System; using System.Collections; using System.Collections.Generic; @@ -15,6 +15,7 @@ using Attributes; using ClosedXML.Extensions; + [DebuggerDisplay("{Address}")] internal class XLCell : IXLCell, IXLStylized { public static readonly DateTime BaseDate = new DateTime(1899, 12, 30); @@ -73,49 +74,7 @@ internal XLCellValues _dataType; private XLHyperlink _hyperlink; private XLRichText _richText; - - #endregion Fields - - #region Constructor - - private Int32 _styleCacheId; - - public XLCell(XLWorksheet worksheet, XLAddress address, Int32 styleId) - { - Address = address; - ShareString = true; - _worksheet = worksheet; - SetStyle(styleId); - } - - private IXLStyle GetStyleForRead() - { - return Worksheet.Workbook.GetStyleById(GetStyleId()); - } - - public Int32 GetStyleId() - { - if (StyleChanged) - SetStyle(Style); - - return _styleCacheId; - } - - private void SetStyle(IXLStyle styleToUse) - { - _styleCacheId = Worksheet.Workbook.GetStyleId(styleToUse); - _style = null; - StyleChanged = false; - } - - private void SetStyle(Int32 styleId) - { - _styleCacheId = styleId; - _style = null; - StyleChanged = false; - } - - #endregion Constructor + private Int32? _styleCacheId; public bool SettingHyperlink; public int SharedStringId; @@ -123,6 +82,25 @@ private string _formulaR1C1; private IXLStyle _style; + #endregion Fields + + #region Constructor + + public XLCell(XLWorksheet worksheet, XLAddress address, Int32 styleId) + : this(worksheet, address) + { + SetStyle(styleId); + } + + public XLCell(XLWorksheet worksheet, XLAddress address) + { + Address = address; + ShareString = true; + _worksheet = worksheet; + } + + #endregion Constructor + public XLWorksheet Worksheet { get { return _worksheet; } @@ -221,8 +199,23 @@ public IXLCell SetValue(T value) { + return SetValue(value, true); + } + + internal IXLCell SetValue(T value, bool setTableHeader) + { + if (value == null) + return this.Clear(XLClearOptions.Contents); + FormulaA1 = String.Empty; _richText = null; + + if (setTableHeader) + { + if (SetTableHeaderValue(value)) return this; + if (SetTableTotalsRowLabel(value)) return this; + } + var style = GetStyleForRead(); if (value is String || value is char) { @@ -281,7 +274,7 @@ if (TryGetValue(out retVal)) return retVal; - throw new Exception("Cannot convert cell value to " + typeof(T)); + throw new FormatException("Cannot convert cell value to " + typeof(T)); } public string GetString() @@ -365,7 +358,7 @@ get { var fA1 = FormulaA1; - if (!XLHelper.IsNullOrWhiteSpace(fA1)) + if (!String.IsNullOrWhiteSpace(fA1)) { if (fA1[0] == '{') fA1 = fA1.Substring(1, fA1.Length - 2); @@ -396,8 +389,7 @@ var retValEnumerable = retVal as IEnumerable; if (retValEnumerable != null && !(retVal is String)) - foreach (var v in retValEnumerable) - return v; + return retValEnumerable.Cast().First(); return retVal; } @@ -436,12 +428,16 @@ { FormulaA1 = String.Empty; - if (value as XLCells != null) throw new ArgumentException("Cannot assign IXLCells object to the cell value."); + if (value is XLCells) throw new ArgumentException("Cannot assign IXLCells object to the cell value."); + + if (SetTableHeaderValue(value)) return; if (SetRangeRows(value)) return; if (SetRangeColumns(value)) return; + if (SetDataTable(value)) return; + if (SetEnumerable(value)) return; if (SetRange(value)) return; @@ -449,7 +445,7 @@ if (!SetRichText(value)) SetValue(value); - if (_cellValue.Length > 32767) throw new ArgumentException("Cells can only hold 32,767 characters."); + if (_cellValue.Length > 32767) throw new ArgumentException("Cells can hold only 32,767 characters."); } } @@ -470,7 +466,10 @@ public IXLTable InsertTable(IEnumerable data, string tableName, bool createTable) { - if (data != null && data.GetType() != typeof(String)) + if (createTable && this.Worksheet.Tables.Any(t => t.Contains(this))) + throw new InvalidOperationException(String.Format("This cell '{0}' is already part of a table.", this.Address.ToString())); + + if (data != null && !(data is String)) { var ro = Address.RowNumber + 1; var fRo = Address.RowNumber; @@ -496,15 +495,15 @@ if (!hasTitles) { var fieldName = XLColumnAttribute.GetHeader(itemType); - if (XLHelper.IsNullOrWhiteSpace(fieldName)) + if (String.IsNullOrWhiteSpace(fieldName)) fieldName = itemType.Name; - SetValue(fieldName, fRo, co); + _worksheet.SetValue(fieldName, fRo, co); hasTitles = true; co = Address.ColumnNumber; } - SetValue(o, ro, co); + _worksheet.SetValue(o, ro, co); co++; if (co > maxCo) @@ -515,7 +514,7 @@ } else { - const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; var memberCache = new Dictionary>(); var accessorCache = new Dictionary(); IEnumerable members = null; @@ -561,7 +560,7 @@ { foreach (var item in (m as Array)) { - SetValue(item, ro, co); + _worksheet.SetValue(item, ro, co); co++; } } @@ -574,11 +573,11 @@ if (!hasTitles) { foreach (var fieldName in from DataColumn column in row.Table.Columns - select XLHelper.IsNullOrWhiteSpace(column.Caption) + select String.IsNullOrWhiteSpace(column.Caption) ? column.ColumnName : column.Caption) { - SetValue(fieldName, fRo, co); + _worksheet.SetValue(fieldName, fRo, co); co++; } @@ -588,7 +587,7 @@ foreach (var item in row.ItemArray) { - SetValue(item, ro, co); + _worksheet.SetValue(item, ro, co); co++; } } @@ -604,7 +603,7 @@ { for (var i = 0; i < fieldCount; i++) { - SetValue(record.GetName(i), fRo, co); + _worksheet.SetValue(record.GetName(i), fRo, co); co++; } @@ -614,7 +613,7 @@ for (var i = 0; i < fieldCount; i++) { - SetValue(record[i], ro, co); + _worksheet.SetValue(record[i], ro, co); co++; } } @@ -624,13 +623,13 @@ { foreach (var mi in members) { - if ((mi as IEnumerable) == null) + if (!(mi is IEnumerable)) { var fieldName = XLColumnAttribute.GetHeader(mi); - if (XLHelper.IsNullOrWhiteSpace(fieldName)) + if (String.IsNullOrWhiteSpace(fieldName)) fieldName = mi.Name; - SetValue(fieldName, fRo, co); + _worksheet.SetValue(fieldName, fRo, co); } co++; @@ -642,7 +641,13 @@ foreach (var mi in members) { - SetValue(accessor[m, mi.Name], ro, co); + if (mi.MemberType == MemberTypes.Property && (mi as PropertyInfo).GetGetMethod().IsStatic) + _worksheet.SetValue((mi as PropertyInfo).GetValue(null), ro, co); + else if (mi.MemberType == MemberTypes.Field && (mi as FieldInfo).IsStatic) + _worksheet.SetValue((mi as FieldInfo).GetValue(null), ro, co); + else + _worksheet.SetValue(accessor[m, mi.Name], ro, co); + co++; } } @@ -688,14 +693,16 @@ { if (data == null) return null; - if (data.Rows.Count > 0) return InsertTable(data.AsEnumerable(), tableName, createTable); + if (createTable && this.Worksheet.Tables.Any(t => t.Contains(this))) + throw new InvalidOperationException(String.Format("This cell '{0}' is already part of a table.", this.Address.ToString())); + if (data.Rows.Cast().Any()) return InsertTable(data.Rows.Cast(), tableName, createTable); var ro = Address.RowNumber; var co = Address.ColumnNumber; foreach (DataColumn col in data.Columns) { - SetValue(col.ColumnName, ro, co); + _worksheet.SetValue(col.ColumnName, ro, co); co++; } @@ -711,16 +718,35 @@ return tableName == null ? range.AsTable() : range.AsTable(tableName); } + public XLTableCellType TableCellType() + { + var table = this.Worksheet.Tables.FirstOrDefault(t => t.AsRange().Contains(this)); + if (table == null) return XLTableCellType.None; + + if (table.ShowHeaderRow && table.HeadersRow().RowNumber().Equals(this.Address.RowNumber)) return XLTableCellType.Header; + if (table.ShowTotalsRow && table.TotalsRow().RowNumber().Equals(this.Address.RowNumber)) return XLTableCellType.Total; + + return XLTableCellType.Data; + } + public IXLRange InsertData(IEnumerable data) { - if (data != null && data.GetType() != typeof(String)) + return InsertData(data, false); + } + + public IXLRange InsertData(IEnumerable data, Boolean transpose) + { + if (data != null && !(data is String)) { - var ro = Address.RowNumber; - var maxCo = 0; + var rowNumber = Address.RowNumber; + var columnNumber = Address.ColumnNumber; + + var maxColumnNumber = 0; + var maxRowNumber = 0; var isDataTable = false; var isDataReader = false; - const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; var memberCache = new Dictionary>(); var accessorCache = new Dictionary(); IEnumerable members = null; @@ -729,36 +755,31 @@ foreach (var m in data) { var itemType = m.GetType(); - if (!memberCache.ContainsKey(itemType)) - { - var _accessor = TypeAccessor.Create(itemType); - var _members = itemType.GetFields(bindingFlags).Cast() - .Concat(itemType.GetProperties(bindingFlags)) - .Where(mi => !XLColumnAttribute.IgnoreMember(mi)) - .OrderBy(mi => XLColumnAttribute.GetOrder(mi)); - - memberCache.Add(itemType, _members); - accessorCache.Add(itemType, _accessor); - } - - members = memberCache[itemType]; - accessor = accessorCache[itemType]; - - var co = Address.ColumnNumber; + if (transpose) + rowNumber = Address.RowNumber; + else + columnNumber = Address.ColumnNumber; if (itemType.IsPrimitive || itemType == typeof(String) || itemType == typeof(DateTime) || itemType.IsNumber()) { - SetValue(m, ro, co); - co++; + _worksheet.SetValue(m, rowNumber, columnNumber); + + if (transpose) + rowNumber++; + else + columnNumber++; } else if (itemType.IsArray) { - // dynamic arr = m; foreach (var item in (Array)m) { - SetValue(item, ro, co); - co++; + _worksheet.SetValue(item, rowNumber, columnNumber); + + if (transpose) + rowNumber++; + else + columnNumber++; } } else if (isDataTable || m is DataRow) @@ -768,8 +789,12 @@ foreach (var item in (m as DataRow).ItemArray) { - SetValue(item, ro, co); - co++; + _worksheet.SetValue(item, rowNumber, columnNumber); + + if (transpose) + rowNumber++; + else + columnNumber++; } } else if (isDataReader || m is IDataRecord) @@ -782,36 +807,76 @@ var fieldCount = record.FieldCount; for (var i = 0; i < fieldCount; i++) { - SetValue(record[i], ro, co); - co++; + _worksheet.SetValue(record[i], rowNumber, columnNumber); + + if (transpose) + rowNumber++; + else + columnNumber++; } } else { + if (!memberCache.ContainsKey(itemType)) + { + var _accessor = TypeAccessor.Create(itemType); + + var _members = itemType.GetFields(bindingFlags).Cast() + .Concat(itemType.GetProperties(bindingFlags)) + .Where(mi => !XLColumnAttribute.IgnoreMember(mi)) + .OrderBy(mi => XLColumnAttribute.GetOrder(mi)); + + memberCache.Add(itemType, _members); + accessorCache.Add(itemType, _accessor); + } + + accessor = accessorCache[itemType]; + members = memberCache[itemType]; + foreach (var mi in members) { - SetValue(accessor[m, mi.Name], ro, co); - co++; + if (mi.MemberType == MemberTypes.Property && (mi as PropertyInfo).GetGetMethod().IsStatic) + _worksheet.SetValue((mi as PropertyInfo).GetValue(null), rowNumber, columnNumber); + else if (mi.MemberType == MemberTypes.Field && (mi as FieldInfo).IsStatic) + _worksheet.SetValue((mi as FieldInfo).GetValue(null), rowNumber, columnNumber); + else + _worksheet.SetValue(accessor[m, mi.Name], rowNumber, columnNumber); + + if (transpose) + rowNumber++; + else + columnNumber++; } } - if (co > maxCo) - maxCo = co; + if (transpose) + columnNumber++; + else + rowNumber++; - ro++; + if (columnNumber > maxColumnNumber) + maxColumnNumber = columnNumber; + + if (rowNumber > maxRowNumber) + maxRowNumber = rowNumber; } ClearMerged(); return _worksheet.Range( Address.RowNumber, Address.ColumnNumber, - ro - 1, - maxCo - 1); + maxRowNumber - 1, + maxColumnNumber - 1); } return null; } + public IXLRange InsertData(DataTable dataTable) + { + return InsertData(dataTable.Rows); + } + public IXLStyle Style { get { return GetStyle(); } @@ -968,9 +1033,9 @@ { get { - if (XLHelper.IsNullOrWhiteSpace(_formulaA1)) + if (String.IsNullOrWhiteSpace(_formulaA1)) { - if (!XLHelper.IsNullOrWhiteSpace(_formulaR1C1)) + if (!String.IsNullOrWhiteSpace(_formulaR1C1)) { _formulaA1 = GetFormulaA1(_formulaR1C1); return FormulaA1; @@ -990,7 +1055,7 @@ set { - _formulaA1 = XLHelper.IsNullOrWhiteSpace(value) ? null : value; + _formulaA1 = String.IsNullOrWhiteSpace(value) ? null : value; _formulaR1C1 = null; } @@ -1000,7 +1065,7 @@ { get { - if (XLHelper.IsNullOrWhiteSpace(_formulaR1C1)) + if (String.IsNullOrWhiteSpace(_formulaR1C1)) _formulaR1C1 = GetFormulaR1C1(FormulaA1); return _formulaR1C1; @@ -1008,7 +1073,7 @@ set { - _formulaR1C1 = XLHelper.IsNullOrWhiteSpace(value) ? null : value; + _formulaR1C1 = String.IsNullOrWhiteSpace(value) ? null : value; } } @@ -1131,10 +1196,15 @@ public Boolean IsEmpty(Boolean includeFormats) { + return IsEmpty(includeFormats, includeFormats); + } + + public Boolean IsEmpty(Boolean includeNormalFormats, Boolean includeConditionalFormats) + { if (InnerText.Length > 0) return false; - if (includeFormats) + if (includeNormalFormats) { if (!Style.Equals(Worksheet.Style) || IsMerged() || HasComment || HasDataValidation) return false; @@ -1149,10 +1219,12 @@ if (Worksheet.Internals.ColumnsCollection.TryGetValue(Address.ColumnNumber, out column) && !column.Style.Equals(Worksheet.Style)) return false; } - - if (Worksheet.ConditionalFormats.Any(cf => cf.Range.Contains(this))) - return false; } + + if (includeConditionalFormats + && Worksheet.ConditionalFormats.Any(cf => cf.Range.Contains(this))) + return false; + return true; } @@ -1450,6 +1522,43 @@ #endregion IXLStylized Members + private Boolean SetTableHeaderValue(object value) + { + foreach (var table in Worksheet.Tables.Where(t => t.ShowHeaderRow)) + { + var cells = table.HeadersRow().CellsUsed(c => c.Address.Equals(this.Address)); + if (cells.Any()) + { + var oldName = cells.First().GetString(); + var field = table.Field(oldName); + field.Name = value.ToString(); + return true; + } + } + + return false; + } + + private Boolean SetTableTotalsRowLabel(object value) + { + foreach (var table in Worksheet.Tables.Where(t => t.ShowTotalsRow)) + { + var cells = table.TotalsRow().Cells(c => c.Address.Equals(this.Address)); + if (cells.Any()) + { + var cell = cells.First(); + var field = table.Fields.First(f => f.Column.ColumnNumber() == cell.WorksheetColumn().ColumnNumber()); + field.TotalsRowFunction = XLTotalsRowFunction.None; + field.TotalsRowLabel = value.ToString(); + this._cellValue = value.ToString(); + this.DataType = XLCellValues.Text; + return true; + } + } + + return false; + } + private bool SetRangeColumns(object value) { var columns = value as XLRangeColumns; @@ -1515,14 +1624,57 @@ return _worksheet.Range(Address, Address); } + #region Styles + private IXLStyle GetStyle() { if (_style != null) return _style; - return _style = new XLStyle(this, Worksheet.Workbook.GetStyleById(_styleCacheId)); + return _style = new XLStyle(this, Worksheet.Workbook.GetStyleById(StyleCacheId())); } + private IXLStyle GetStyleForRead() + { + return Worksheet.Workbook.GetStyleById(GetStyleId()); + } + + public Int32 GetStyleId() + { + if (StyleChanged) + SetStyle(Style); + + return StyleCacheId(); + } + + private void SetStyle(IXLStyle styleToUse) + { + _styleCacheId = Worksheet.Workbook.GetStyleId(styleToUse); + _style = null; + StyleChanged = false; + } + + private void SetStyle(Int32 styleId) + { + _styleCacheId = styleId; + _style = null; + StyleChanged = false; + } + + public Int32 StyleCacheId() + { + if (!_styleCacheId.HasValue) + _styleCacheId = Worksheet.GetStyleId(); + return _styleCacheId.Value; + } + + public Boolean IsDefaultWorksheetStyle() + { + return !_styleCacheId.HasValue && !StyleChanged || GetStyleId() == Worksheet.GetStyleId(); + } + + #endregion Styles + public void DeleteComment() { _comment = null; @@ -1532,7 +1684,7 @@ { var style = GetStyleForRead(); return _dataType == XLCellValues.Number - && XLHelper.IsNullOrWhiteSpace(style.NumberFormat.Format) + && String.IsNullOrWhiteSpace(style.NumberFormat.Format) && ((style.NumberFormat.NumberFormatId >= 14 && style.NumberFormat.NumberFormatId <= 22) || (style.NumberFormat.NumberFormatId >= 45 @@ -1543,7 +1695,7 @@ { var format = String.Empty; var style = GetStyleForRead(); - if (XLHelper.IsNullOrWhiteSpace(style.NumberFormat.Format)) + if (String.IsNullOrWhiteSpace(style.NumberFormat.Format)) { var formatCodes = GetFormatCodes(); if (formatCodes.ContainsKey(style.NumberFormat.NumberFormatId)) @@ -1617,10 +1769,17 @@ return false; } + private bool SetDataTable(object o) + { + var dataTable = o as DataTable; + if (dataTable == null) return false; + return InsertData(dataTable) != null; + } + private bool SetEnumerable(object collectionObject) { // IXLRichText implements IEnumerable, but we don't want to handle this here. - if ((collectionObject as IXLRichText) != null) return false; + if (collectionObject is IXLRichText) return false; var asEnumerable = collectionObject as IEnumerable; return InsertData(asEnumerable) != null; @@ -1635,19 +1794,6 @@ mergeToDelete.ForEach(m => Worksheet.Internals.MergedRanges.Remove(m)); } - private void SetValue(T value, int ro, int co) where T : class - { - if (value == null) - _worksheet.Cell(ro, co).SetValue(String.Empty); - else - { - if (value is IConvertible) - _worksheet.Cell(ro, co).SetValue((T)Convert.ChangeType(value, typeof(T))); - else - _worksheet.Cell(ro, co).SetValue(value); - } - } - private void SetValue(object value) { FormulaA1 = String.Empty; @@ -1725,6 +1871,10 @@ } } if (val.Length > 32767) throw new ArgumentException("Cells can only hold 32,767 characters."); + + if (SetTableHeaderValue(val)) return; + if (SetTableTotalsRowLabel(val)) return; + _cellValue = val; } @@ -1783,7 +1933,7 @@ private string GetFormula(string strValue, FormulaConversionType conversionType, int rowsToShift, int columnsToShift) { - if (XLHelper.IsNullOrWhiteSpace(strValue)) + if (String.IsNullOrWhiteSpace(strValue)) return String.Empty; var value = ">" + strValue + "<"; @@ -2000,7 +2150,8 @@ CopyValuesFrom(source); - SetStyle(source._style ?? source.Worksheet.Workbook.GetStyleById(source._styleCacheId)); + if (source._styleCacheId.HasValue) + SetStyle(source._style ?? source.Worksheet.Workbook.GetStyleById(source._styleCacheId.Value)); var conditionalFormats = source.Worksheet.ConditionalFormats.Where(c => c.Range.Contains(source)).ToList(); foreach (var cf in conditionalFormats) @@ -2057,7 +2208,7 @@ internal static String ShiftFormulaRows(String formulaA1, XLWorksheet worksheetInAction, XLRange shiftedRange, int rowsShifted) { - if (XLHelper.IsNullOrWhiteSpace(formulaA1)) return String.Empty; + if (String.IsNullOrWhiteSpace(formulaA1)) return String.Empty; var value = formulaA1; // ">" + formulaA1 + "<"; @@ -2262,7 +2413,7 @@ internal static String ShiftFormulaColumns(String formulaA1, XLWorksheet worksheetInAction, XLRange shiftedRange, int columnsShifted) { - if (XLHelper.IsNullOrWhiteSpace(formulaA1)) return String.Empty; + if (String.IsNullOrWhiteSpace(formulaA1)) return String.Empty; var value = formulaA1; // ">" + formulaA1 + "<"; @@ -2590,7 +2741,9 @@ #endregion XLCell Right - public Boolean HasFormula { get { return !XLHelper.IsNullOrWhiteSpace(FormulaA1); } } + public Boolean HasFormula { get { return !String.IsNullOrWhiteSpace(FormulaA1); } } + + public Boolean HasArrayFormula { get { return FormulaA1.StartsWith("{"); } } public IXLRangeAddress FormulaReference { get; set; } } diff --git a/ClosedXML/Excel/Cells/XLCells.cs b/ClosedXML/Excel/Cells/XLCells.cs index 5f49b98..c8fef10 100644 --- a/ClosedXML/Excel/Cells/XLCells.cs +++ b/ClosedXML/Excel/Cells/XLCells.cs @@ -52,16 +52,18 @@ { if (oneRange) { - var cellRange = range.Worksheet.Internals.CellsCollection - .GetCells( - range.FirstAddress.RowNumber, - range.FirstAddress.ColumnNumber, - range.LastAddress.RowNumber, - range.LastAddress.ColumnNumber) - .Where(c => - !c.IsEmpty(_includeFormats) - && (_predicate == null || _predicate(c)) - ); + var cellRange = range + .Worksheet + .Internals + .CellsCollection + .GetCells( + range.FirstAddress.RowNumber, + range.FirstAddress.ColumnNumber, + range.LastAddress.RowNumber, + range.LastAddress.ColumnNumber) + .Where(c => !c.IsEmpty(_includeFormats) + && (_predicate == null || _predicate(c)) + ); foreach(var cell in cellRange) { @@ -121,13 +123,16 @@ { if (_usedCellsOnly) { - var cellRange = cellsInRanges.SelectMany( - cir => - cir.Value.Select(a => cir.Key.Internals.CellsCollection.GetCell(a)).Where( - cell => cell != null && ( - !cell.IsEmpty(_includeFormats) - && (_predicate == null || _predicate(cell)) - ))); + var cellRange = cellsInRanges + .SelectMany( + cir => + cir.Value.Select(a => cir.Key.Internals.CellsCollection.GetCell(a)).Where( + cell => + cell != null + && !cell.IsEmpty(_includeFormats) + && (_predicate == null || _predicate(cell)) + ) + ); foreach (var cell in cellRange) { @@ -277,4 +282,4 @@ cell.Select(); } } -} \ No newline at end of file +} diff --git a/ClosedXML/Excel/Cells/XLCellsCollection.cs b/ClosedXML/Excel/Cells/XLCellsCollection.cs index 32a89aa..8f11a98 100644 --- a/ClosedXML/Excel/Cells/XLCellsCollection.cs +++ b/ClosedXML/Excel/Cells/XLCellsCollection.cs @@ -95,7 +95,7 @@ Count--; DecrementUsage(RowsUsed, row); DecrementUsage(ColumnsUsed, row); - + HashSet delHash; if (deleted.TryGetValue(row, out delHash)) { @@ -119,7 +119,7 @@ } } - + } internal IEnumerable GetCells(Int32 rowStart, Int32 columnStart, @@ -148,14 +148,22 @@ { HashSet ids = new HashSet(); ids.Add(initial); - foreach (var row in rowsCollection) + foreach (var row in rowsCollection.Values) { - foreach (var column in row.Value) + foreach (var cell in row.Values) { - var id = column.Value.GetStyleId(); - if (!ids.Contains(id)) + Int32? id = null; + + if (cell.StyleChanged) + id = cell.GetStyleId(); + else if (cell.StyleCacheId() != cell.Worksheet.GetStyleId()) { - ids.Add(id); + id = cell.GetStyleId(); + } + + if (id.HasValue && !ids.Contains(id.Value)) + { + ids.Add(id.Value); } } } @@ -494,4 +502,4 @@ return GetCells(row, 1, row, MaxColumnUsed); } } -} \ No newline at end of file +} diff --git a/ClosedXML/Excel/Columns/XLColumn.cs b/ClosedXML/Excel/Columns/XLColumn.cs index b8036d4..ff70d31 100644 --- a/ClosedXML/Excel/Columns/XLColumn.cs +++ b/ClosedXML/Excel/Columns/XLColumn.cs @@ -774,17 +774,27 @@ #endregion - public new Boolean IsEmpty() + public override Boolean IsEmpty() { return IsEmpty(false); } - public new Boolean IsEmpty(Boolean includeFormats) + public override Boolean IsEmpty(Boolean includeFormats) { if (includeFormats && !Style.Equals(Worksheet.Style)) return false; return base.IsEmpty(includeFormats); } + + public override Boolean IsEntireRow() + { + return false; + } + + public override Boolean IsEntireColumn() + { + return true; + } } } diff --git a/ClosedXML/Excel/Coordinates/IXLAddress.cs b/ClosedXML/Excel/Coordinates/IXLAddress.cs index bc70a90..6158251 100644 --- a/ClosedXML/Excel/Coordinates/IXLAddress.cs +++ b/ClosedXML/Excel/Coordinates/IXLAddress.cs @@ -5,18 +5,27 @@ { public interface IXLAddress : IEqualityComparer, IEquatable { - IXLWorksheet Worksheet { get; } - Int32 RowNumber { get; } - Int32 ColumnNumber { get; } String ColumnLetter { get; } - Boolean FixedRow { get; } + Int32 ColumnNumber { get; } Boolean FixedColumn { get; } - String ToStringRelative(); - String ToStringRelative(Boolean includeSheet); - String ToStringFixed(); - String ToStringFixed(XLReferenceStyle referenceStyle); - String ToStringFixed(XLReferenceStyle referenceStyle, Boolean includeSheet); - String ToString(XLReferenceStyle referenceStyle); + Boolean FixedRow { get; } + Int32 RowNumber { get; } String UniqueId { get; } + IXLWorksheet Worksheet { get; } + + String ToString(XLReferenceStyle referenceStyle); + + String ToString(XLReferenceStyle referenceStyle, Boolean includeSheet); + + String ToStringFixed(); + + String ToStringFixed(XLReferenceStyle referenceStyle); + + String ToStringFixed(XLReferenceStyle referenceStyle, Boolean includeSheet); + + String ToStringRelative(); + + + String ToStringRelative(Boolean includeSheet); } } diff --git a/ClosedXML/Excel/Coordinates/XLAddress.cs b/ClosedXML/Excel/Coordinates/XLAddress.cs index b1d7ff8..81f4977 100644 --- a/ClosedXML/Excel/Coordinates/XLAddress.cs +++ b/ClosedXML/Excel/Coordinates/XLAddress.cs @@ -7,6 +7,7 @@ internal class XLAddress : IXLAddress { #region Static + /// /// Create address without worksheet. For calculation only! /// @@ -72,22 +73,32 @@ } return new XLAddress(worksheet, rowNumber, columnLetter, fixedRow, fixedColumn); } - #endregion + + #endregion Static + #region Private fields + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private bool _fixedRow; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private bool _fixedColumn; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private string _columnLetter; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly int _rowNumber; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly int _columnNumber; + private string _trimmedAddress; - #endregion + + #endregion Private fields + #region Constructors + /// /// Initializes a new struct using a mixed notation. Attention: without worksheet for calculation only! /// @@ -99,6 +110,7 @@ : this(null, rowNumber, columnLetter, fixedRow, fixedColumn) { } + /// /// Initializes a new struct using a mixed notation. /// @@ -124,6 +136,7 @@ : this(null, rowNumber, columnNumber, fixedRow, fixedColumn) { } + /// /// Initializes a new struct using R1C1 notation. /// @@ -142,12 +155,14 @@ _columnLetter = null; _fixedColumn = fixedColumn; _fixedRow = fixedRow; - - } - #endregion + + #endregion Constructors + #region Properties + public XLWorksheet Worksheet { get; internal set; } + IXLWorksheet IXLAddress.Worksheet { [DebuggerStepThrough] @@ -195,8 +210,11 @@ { get { return _columnLetter ?? (_columnLetter = XLHelper.GetColumnLetterFromNumber(_columnNumber)); } } - #endregion + + #endregion Properties + #region Overrides + public override string ToString() { String retVal = ColumnLetter; @@ -214,31 +232,45 @@ public string ToString(XLReferenceStyle referenceStyle) { - if (referenceStyle == XLReferenceStyle.A1) - { - return ColumnLetter + _rowNumber.ToInvariantString(); - } - if (referenceStyle == XLReferenceStyle.R1C1) - { - return String.Format("R{0}C{1}", _rowNumber.ToInvariantString(), ColumnNumber); - } - if (HasWorksheet && Worksheet.Workbook.ReferenceStyle == XLReferenceStyle.R1C1) - { - return String.Format("R{0}C{1}", _rowNumber.ToInvariantString(), ColumnNumber); - } - return ColumnLetter + _rowNumber.ToInvariantString(); + return ToString(referenceStyle, false); } - #endregion + + public string ToString(XLReferenceStyle referenceStyle, bool includeSheet) + { + string address = string.Empty; + if (referenceStyle == XLReferenceStyle.A1) + + address = ColumnLetter + _rowNumber.ToInvariantString(); + else if (referenceStyle == XLReferenceStyle.R1C1) + + address = String.Format("R{0}C{1}", _rowNumber.ToInvariantString(), ColumnNumber); + else if (HasWorksheet && Worksheet.Workbook.ReferenceStyle == XLReferenceStyle.R1C1) + + address = String.Format("R{0}C{1}", _rowNumber.ToInvariantString(), ColumnNumber); + else + address = ColumnLetter + _rowNumber.ToInvariantString(); + + if (includeSheet) + return String.Format("{0}!{1}", + Worksheet.Name.WrapSheetNameInQuotesIfRequired(), + address); + + return address; + } + + #endregion Overrides + #region Methods + public string GetTrimmedAddress() { return _trimmedAddress ?? (_trimmedAddress = ColumnLetter + _rowNumber.ToInvariantString()); } + #endregion Methods - - #endregion #region Operator Overloads + public static XLAddress operator +(XLAddress left, XLAddress right) { return new XLAddress(left.Worksheet, @@ -288,9 +320,13 @@ { return !(left == right); } - #endregion + + #endregion Operator Overloads + #region Interface Requirements + #region IEqualityComparer Members + public Boolean Equals(IXLAddress x, IXLAddress y) { return x == y; @@ -315,8 +351,11 @@ { return _rowNumber ^ _columnNumber; } - #endregion + + #endregion IEqualityComparer Members + #region IEquatable Members + public bool Equals(IXLAddress other) { var right = other as XLAddress; @@ -329,10 +368,12 @@ public override Boolean Equals(Object other) { - return Equals((XLAddress) other); + return Equals((XLAddress)other); } - #endregion - #endregion + + #endregion IEquatable Members + + #endregion Interface Requirements public String ToStringRelative() { diff --git a/ClosedXML/Excel/DataValidation/XLDataValidation.cs b/ClosedXML/Excel/DataValidation/XLDataValidation.cs index 321e6d8..17164c0 100644 --- a/ClosedXML/Excel/DataValidation/XLDataValidation.cs +++ b/ClosedXML/Excel/DataValidation/XLDataValidation.cs @@ -41,9 +41,9 @@ return AllowedValues != XLAllowedValues.AnyValue || (ShowInputMessage && - (!XLHelper.IsNullOrWhiteSpace(InputTitle) || !XLHelper.IsNullOrWhiteSpace(InputMessage))) + (!String.IsNullOrWhiteSpace(InputTitle) || !String.IsNullOrWhiteSpace(InputMessage))) ||(ShowErrorMessage && - (!XLHelper.IsNullOrWhiteSpace(ErrorTitle) || !XLHelper.IsNullOrWhiteSpace(ErrorMessage))); + (!String.IsNullOrWhiteSpace(ErrorTitle) || !String.IsNullOrWhiteSpace(ErrorMessage))); } diff --git a/ClosedXML/Excel/Drawings/IXLPicture.cs b/ClosedXML/Excel/Drawings/IXLPicture.cs index 2c2f750..cf167d2 100644 --- a/ClosedXML/Excel/Drawings/IXLPicture.cs +++ b/ClosedXML/Excel/Drawings/IXLPicture.cs @@ -16,6 +16,8 @@ Int32 Height { get; set; } + Int32 Id { get; } + MemoryStream ImageStream { get; } Int32 Left { get; set; } diff --git a/ClosedXML/Excel/Drawings/XLPicture.cs b/ClosedXML/Excel/Drawings/XLPicture.cs index 86d45ae..9266215 100644 --- a/ClosedXML/Excel/Drawings/XLPicture.cs +++ b/ClosedXML/Excel/Drawings/XLPicture.cs @@ -16,6 +16,7 @@ private static IDictionary FormatMap; private readonly IXLWorksheet _worksheet; private Int32 height; + private Int32 id; private String name = string.Empty; private Int32 width; @@ -32,7 +33,7 @@ } internal XLPicture(IXLWorksheet worksheet, Stream stream) - : this(worksheet) + : this(worksheet) { if (stream == null) throw new ArgumentNullException(nameof(stream)); @@ -67,11 +68,8 @@ using (var bitmap = new Bitmap(ImageStream)) { - if (FormatMap.ContainsKey(this.Format)) - { - if (FormatMap[this.Format].Guid != bitmap.RawFormat.Guid) - throw new ArgumentException("The picture format in the stream and the parameter don't match"); - } + if (FormatMap.ContainsKey(this.Format) && FormatMap[this.Format].Guid != bitmap.RawFormat.Guid) + throw new ArgumentException("The picture format in the stream and the parameter don't match"); DeduceDimensionsFromBitmap(bitmap); } @@ -105,6 +103,13 @@ [XLMarkerPosition.TopLeft] = null, [XLMarkerPosition.BottomRight] = null }; + + // Calculate default picture ID + var allPictures = worksheet.Workbook.Worksheets.SelectMany(ws => ws.Pictures); + if (allPictures.Any()) + this.id = allPictures.Max(p => p.Id) + 1; + else + this.id = 1; } public IXLAddress BottomRightCellAddress @@ -135,6 +140,16 @@ } } + public Int32 Id + { + get { return id; } + internal set + { + if ((_worksheet.Pictures.FirstOrDefault(p => p.Id.Equals(value)) ?? this) != this) + throw new ArgumentException($"The picture ID '{value}' already exists."); + } + } + public MemoryStream ImageStream { get; private set; } public Int32 Left @@ -156,19 +171,10 @@ { if (name == value) return; - if (value.IndexOfAny(InvalidNameChars.ToCharArray()) != -1) - throw new ArgumentException($"Picture names cannot contain any of the following characters: {InvalidNameChars}"); - - if (XLHelper.IsNullOrWhiteSpace(value)) - throw new ArgumentException("Picture names cannot be empty"); - - if (value.Length > 31) - throw new ArgumentException("Picture names cannot be more than 31 characters"); - if ((_worksheet.Pictures.FirstOrDefault(p => p.Name.Equals(value, StringComparison.OrdinalIgnoreCase)) ?? this) != this) throw new ArgumentException($"The picture name '{value}' already exists."); - name = value; + SetName(value); } } @@ -323,6 +329,20 @@ return this; } + internal void SetName(string value) + { + if (value.IndexOfAny(InvalidNameChars.ToCharArray()) != -1) + throw new ArgumentException($"Picture names cannot contain any of the following characters: {InvalidNameChars}"); + + if (String.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Picture names cannot be empty"); + + if (value.Length > 31) + throw new ArgumentException("Picture names cannot be more than 31 characters"); + + name = value; + } + private static ImageFormat FromMimeType(string mimeType) { var guid = ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase))?.FormatID; diff --git a/ClosedXML/Excel/Drawings/XLPictures.cs b/ClosedXML/Excel/Drawings/XLPictures.cs index 3381309..6a102dd 100644 --- a/ClosedXML/Excel/Drawings/XLPictures.cs +++ b/ClosedXML/Excel/Drawings/XLPictures.cs @@ -39,7 +39,7 @@ return picture; } - public Drawings.IXLPicture Add(Stream stream, XLPictureFormat format) + public IXLPicture Add(Stream stream, XLPictureFormat format) { var picture = new XLPicture(_worksheet, stream, format); _pictures.Add(picture); @@ -127,13 +127,21 @@ var matches = _pictures.Where(p => p.Name.Equals(pictureName, StringComparison.OrdinalIgnoreCase)); if (matches.Any()) { - picture = matches.Single(); + picture = matches.First(); return true; } picture = null; return false; } + internal IXLPicture Add(Stream stream, string name, int Id) + { + var picture = Add(stream) as XLPicture; + picture.SetName(name); + picture.Id = Id; + return picture; + } + private String GetNextPictureName() { var pictureNumber = this.Count; diff --git a/ClosedXML/Excel/Hyperlinks/XLHyperlink_Internal.cs b/ClosedXML/Excel/Hyperlinks/XLHyperlink_Internal.cs index 2e5bebe..caf81d2 100644 --- a/ClosedXML/Excel/Hyperlinks/XLHyperlink_Internal.cs +++ b/ClosedXML/Excel/Hyperlinks/XLHyperlink_Internal.cs @@ -5,7 +5,7 @@ public partial class XLHyperlink { internal XLHyperlink() - { + { } @@ -36,7 +36,7 @@ else { _internalAddress = address; - IsExternal = false; + IsExternal = false; } } } @@ -51,14 +51,14 @@ internal void SetValues(IXLCell cell, String tooltip) { Tooltip = tooltip; - _internalAddress = cell.Address.ToString(); + _internalAddress = cell.Address.ToString(XLReferenceStyle.A1, true); IsExternal = false; } internal void SetValues(IXLRangeBase range, String tooltip) { Tooltip = tooltip; - _internalAddress = range.RangeAddress.ToString(); + _internalAddress = range.RangeAddress.ToString(XLReferenceStyle.A1, true); IsExternal = false; } diff --git a/ClosedXML/Excel/Misc/XLFormula.cs b/ClosedXML/Excel/Misc/XLFormula.cs index 34cea84..8027667 100644 --- a/ClosedXML/Excel/Misc/XLFormula.cs +++ b/ClosedXML/Excel/Misc/XLFormula.cs @@ -44,7 +44,7 @@ else { _value = value.Trim(); - IsFormula = !XLHelper.IsNullOrWhiteSpace(_value) && _value.TrimStart()[0] == '=' ; + IsFormula = !String.IsNullOrWhiteSpace(_value) && _value.TrimStart()[0] == '=' ; if (IsFormula) _value = _value.Substring(1); } diff --git a/ClosedXML/Excel/NamedRanges/XLNamedRanges.cs b/ClosedXML/Excel/NamedRanges/XLNamedRanges.cs index a7b191f..f3f7304 100644 --- a/ClosedXML/Excel/NamedRanges/XLNamedRanges.cs +++ b/ClosedXML/Excel/NamedRanges/XLNamedRanges.cs @@ -69,10 +69,16 @@ if (!match.Success) { - if (Worksheet == null || !XLHelper.NamedRangeReferenceRegex.Match(Worksheet.Range(rangeAddress).ToString()).Success) - throw new ArgumentException("For named ranges in the workbook scope, specify the sheet name in the reference."); + var range = Worksheet?.Range(rangeAddress) ?? Workbook.Range(rangeAddress); + if (range == null) + throw new ArgumentException(string.Format("The range address '{0}' for the named range '{1}' is not a valid range.", rangeAddress, rangeName)); else - rangeAddress = Worksheet.Range(rangeAddress).ToString(); + { + if (Worksheet == null || !XLHelper.NamedRangeReferenceRegex.Match(range.ToString()).Success) + throw new ArgumentException("For named ranges in the workbook scope, specify the sheet name in the reference."); + else + rangeAddress = Worksheet.Range(rangeAddress).ToString(); + } } } diff --git a/ClosedXML/Excel/PivotTables/IXLPivotFields.cs b/ClosedXML/Excel/PivotTables/IXLPivotFields.cs index 08c5312..df5a497 100644 --- a/ClosedXML/Excel/PivotTables/IXLPivotFields.cs +++ b/ClosedXML/Excel/PivotTables/IXLPivotFields.cs @@ -1,17 +1,22 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace ClosedXML.Excel { - public interface IXLPivotFields: IEnumerable + public interface IXLPivotFields : IEnumerable { IXLPivotField Add(String sourceName); - IXLPivotField Add(String sourceName, String customName); - void Clear(); - void Remove(String sourceName); - int IndexOf(IXLPivotField pf); + IXLPivotField Add(String sourceName, String customName); + + void Clear(); + + Boolean Contains(String sourceName); + + IXLPivotField Get(String sourceName); + + Int32 IndexOf(IXLPivotField pf); + + void Remove(String sourceName); } } diff --git a/ClosedXML/Excel/PivotTables/XLPivotFields.cs b/ClosedXML/Excel/PivotTables/XLPivotFields.cs index c1a52f2..19accb7 100644 --- a/ClosedXML/Excel/PivotTables/XLPivotFields.cs +++ b/ClosedXML/Excel/PivotTables/XLPivotFields.cs @@ -1,13 +1,40 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace ClosedXML.Excel { - public class XLPivotFields: IXLPivotFields + public class XLPivotFields : IXLPivotFields { private readonly Dictionary _pivotFields = new Dictionary(); + + public IXLPivotField Add(String sourceName) + { + return Add(sourceName, sourceName); + } + + public IXLPivotField Add(String sourceName, String customName) + { + var pivotField = new XLPivotField(sourceName) { CustomName = customName }; + _pivotFields.Add(sourceName, pivotField); + return pivotField; + } + + public void Clear() + { + _pivotFields.Clear(); + } + + public Boolean Contains(String sourceName) + { + return _pivotFields.ContainsKey(sourceName); + } + + public IXLPivotField Get(string sourceName) + { + return _pivotFields[sourceName]; + } + public IEnumerator GetEnumerator() { return _pivotFields.Values.GetEnumerator(); @@ -18,32 +45,17 @@ return GetEnumerator(); } - public IXLPivotField Add(String sourceName) + public Int32 IndexOf(IXLPivotField pf) { - return Add(sourceName, sourceName); - } - public IXLPivotField Add(String sourceName, String customName) - { - var pivotField = new XLPivotField(sourceName) {CustomName = customName}; - _pivotFields.Add(sourceName, pivotField); - return pivotField; - } - - public void Clear() - { - _pivotFields.Clear(); - } - public void Remove(String sourceName) - { - _pivotFields.Remove(sourceName); - } - - public int IndexOf(IXLPivotField pf) - { - var selectedItem = _pivotFields.Select((item, index) => new {Item = item, Position = index}).FirstOrDefault(i => i.Item.Key == pf.SourceName); + var selectedItem = _pivotFields.Select((item, index) => new { Item = item, Position = index }).FirstOrDefault(i => i.Item.Key == pf.SourceName); if (selectedItem == null) throw new IndexOutOfRangeException("Invalid field name."); return selectedItem.Position; } + + public void Remove(String sourceName) + { + _pivotFields.Remove(sourceName); + } } } diff --git a/ClosedXML/Excel/Ranges/IXLRange.cs b/ClosedXML/Excel/Ranges/IXLRange.cs index fbbe958..945c0c9 100644 --- a/ClosedXML/Excel/Ranges/IXLRange.cs +++ b/ClosedXML/Excel/Ranges/IXLRange.cs @@ -1,12 +1,14 @@ using System; - namespace ClosedXML.Excel { public enum XLShiftDeletedCells { ShiftCellsUp, ShiftCellsLeft } + public enum XLTransposeOptions { MoveCells, ReplaceCells } + public enum XLSearchContents { Values, Formulas, ValuesAndFormulas } - public interface IXLRange: IXLRangeBase + + public interface IXLRange : IXLRangeBase { /// /// Gets the cell at the specified row and column. @@ -28,6 +30,7 @@ /// The cell's row. /// The cell's column. IXLCell Cell(int row, string column); + /// Gets the cell at the specified address. /// The cell address is relative to the parent range. /// The cell address in the parent range. @@ -36,79 +39,100 @@ /// /// Gets the specified column of the range. /// - /// The range column. - IXLRangeColumn Column(int column); + /// The column number. + /// + IXLRangeColumn Column(int columnNumber); + /// /// Gets the specified column of the range. /// - /// The range column. - IXLRangeColumn Column(string column); + /// Column letter. + IXLRangeColumn Column(string columnLetter); + /// /// Gets the first column of the range. /// IXLRangeColumn FirstColumn(Func predicate = null); + /// /// Gets the first column of the range that contains a cell with a value. /// IXLRangeColumn FirstColumnUsed(Boolean includeFormats, Func predicate = null); + IXLRangeColumn FirstColumnUsed(Func predicate = null); + /// /// Gets the last column of the range. /// IXLRangeColumn LastColumn(Func predicate = null); + /// /// Gets the last column of the range that contains a cell with a value. /// IXLRangeColumn LastColumnUsed(Boolean includeFormats, Func predicate = null); + IXLRangeColumn LastColumnUsed(Func predicate = null); + /// /// Gets a collection of all columns in this range. /// IXLRangeColumns Columns(Func predicate = null); + /// /// Gets a collection of the specified columns in this range. /// /// The first column to return. /// The last column to return. IXLRangeColumns Columns(int firstColumn, int lastColumn); + /// /// Gets a collection of the specified columns in this range. /// /// The first column to return. /// The last column to return. IXLRangeColumns Columns(string firstColumn, string lastColumn); + /// /// Gets a collection of the specified columns in this range, separated by commas. /// e.g. Columns("G:H"), Columns("10:11,13:14"), Columns("P:Q,S:T"), Columns("V") /// /// The columns to return. IXLRangeColumns Columns(string columns); + /// /// Returns the first row that matches the given predicate /// IXLRangeColumn FindColumn(Func predicate); + /// /// Returns the first row that matches the given predicate /// IXLRangeRow FindRow(Func predicate); + /// /// Gets the first row of the range. /// IXLRangeRow FirstRow(Func predicate = null); + /// /// Gets the first row of the range that contains a cell with a value. /// IXLRangeRow FirstRowUsed(Boolean includeFormats, Func predicate = null); + IXLRangeRow FirstRowUsed(Func predicate = null); + /// /// Gets the last row of the range. /// IXLRangeRow LastRow(Func predicate = null); + /// /// Gets the last row of the range that contains a cell with a value. /// IXLRangeRow LastRowUsed(Boolean includeFormats, Func predicate = null); + IXLRangeRow LastRowUsed(Func predicate = null); + /// /// Gets the specified row of the range. /// @@ -124,6 +148,7 @@ /// The last row to return. /// IXLRangeRows Rows(int firstRow, int lastRow); + /// /// Gets a collection of the specified rows in this range, separated by commas. /// e.g. Rows("4:5"), Rows("7:8,10:11"), Rows("13") @@ -182,27 +207,34 @@ /// /// Number of columns to insert. IXLRangeColumns InsertColumnsAfter(int numberOfColumns); + IXLRangeColumns InsertColumnsAfter(int numberOfColumns, Boolean expandRange); + /// /// Inserts X number of columns to the left of this range. /// This range and all cells to the right of this range will be shifted X number of columns. /// /// Number of columns to insert. IXLRangeColumns InsertColumnsBefore(int numberOfColumns); + IXLRangeColumns InsertColumnsBefore(int numberOfColumns, Boolean expandRange); + /// /// Inserts X number of rows on top of this range. /// This range and all cells below this range will be shifted X number of rows. /// /// Number of rows to insert. IXLRangeRows InsertRowsAbove(int numberOfRows); + IXLRangeRows InsertRowsAbove(int numberOfRows, Boolean expandRange); + /// /// Inserts X number of rows below this range. /// All cells below this range will be shifted X number of rows. /// /// Number of rows to insert. IXLRangeRows InsertRowsBelow(int numberOfRows); + IXLRangeRows InsertRowsBelow(int numberOfRows, Boolean expandRange); /// @@ -218,13 +250,17 @@ void Transpose(XLTransposeOptions transposeOption); IXLTable AsTable(); + IXLTable AsTable(String name); + IXLTable CreateTable(); + IXLTable CreateTable(String name); IXLRange RangeUsed(); IXLRange CopyTo(IXLCell target); + IXLRange CopyTo(IXLRangeBase target); IXLSortElements SortRows { get; } @@ -233,9 +269,10 @@ IXLRange Sort(); IXLRange Sort(String columnsToSortBy, XLSortOrder sortOrder = XLSortOrder.Ascending, Boolean matchCase = false, Boolean ignoreBlanks = true); + IXLRange Sort(Int32 columnToSortBy, XLSortOrder sortOrder = XLSortOrder.Ascending, Boolean matchCase = false, Boolean ignoreBlanks = true); + IXLRange SortLeftToRight(XLSortOrder sortOrder = XLSortOrder.Ascending, Boolean matchCase = false, Boolean ignoreBlanks = true); - IXLRange SetDataType(XLCellValues dataType); @@ -246,9 +283,11 @@ new IXLRange Clear(XLClearOptions clearOptions = XLClearOptions.ContentsAndFormats); IXLRangeRows RowsUsed(Boolean includeFormats, Func predicate = null); + IXLRangeRows RowsUsed(Func predicate = null); + IXLRangeColumns ColumnsUsed(Boolean includeFormats, Func predicate = null); + IXLRangeColumns ColumnsUsed(Func predicate = null); } } - diff --git a/ClosedXML/Excel/Ranges/IXLRangeAddress.cs b/ClosedXML/Excel/Ranges/IXLRangeAddress.cs index 9da98e4..86ed2b1 100644 --- a/ClosedXML/Excel/Ranges/IXLRangeAddress.cs +++ b/ClosedXML/Excel/Ranges/IXLRangeAddress.cs @@ -4,8 +4,6 @@ { public interface IXLRangeAddress { - IXLWorksheet Worksheet { get; } - /// /// Gets or sets the first address in the range. /// @@ -13,13 +11,7 @@ /// The first address. /// IXLAddress FirstAddress { get; set; } - /// - /// Gets or sets the last address in the range. - /// - /// - /// The last address. - /// - IXLAddress LastAddress { get; set; } + /// /// Gets or sets a value indicating whether this range is invalid. /// @@ -28,10 +20,28 @@ /// Boolean IsInvalid { get; set; } - String ToStringRelative(); - String ToStringRelative(Boolean includeSheet); + /// + /// Gets or sets the last address in the range. + /// + /// + /// The last address. + /// + IXLAddress LastAddress { get; set; } + + IXLWorksheet Worksheet { get; } + + String ToString(XLReferenceStyle referenceStyle); + + String ToString(XLReferenceStyle referenceStyle, Boolean includeSheet); + String ToStringFixed(); + String ToStringFixed(XLReferenceStyle referenceStyle); + String ToStringFixed(XLReferenceStyle referenceStyle, Boolean includeSheet); + + String ToStringRelative(); + + String ToStringRelative(Boolean includeSheet); } } diff --git a/ClosedXML/Excel/Ranges/IXLRangeBase.cs b/ClosedXML/Excel/Ranges/IXLRangeBase.cs index 76902d0..064d975 100644 --- a/ClosedXML/Excel/Ranges/IXLRangeBase.cs +++ b/ClosedXML/Excel/Ranges/IXLRangeBase.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace ClosedXML.Excel { @@ -8,7 +9,7 @@ Worksheet } - public interface IXLRangeBase: IDisposable + public interface IXLRangeBase : IDisposable { IXLWorksheet Worksheet { get; } @@ -64,7 +65,6 @@ IXLHyperlinks Hyperlinks { get; } - /// /// Returns the collection of cells. /// @@ -94,6 +94,15 @@ IXLCells CellsUsed(Boolean includeFormats, Func predicate); /// + /// Searches the cells' contents for a given piece of text + /// + /// The search text. + /// The compare options. + /// if set to true search formulae instead of cell values. + /// + IXLCells Search(String searchText, CompareOptions compareOptions = CompareOptions.Ordinal, Boolean searchFormulae = false); + + /// /// Returns the first cell of this range. /// IXLCell FirstCell(); @@ -236,15 +245,19 @@ IXLRange AsRange(); Boolean IsMerged(); + Boolean IsEmpty(); + Boolean IsEmpty(Boolean includeFormats); + Boolean IsEntireRow(); + + Boolean IsEntireColumn(); IXLPivotTable CreatePivotTable(IXLCell targetCell); + IXLPivotTable CreatePivotTable(IXLCell targetCell, String name); - - //IXLChart CreateChart(Int32 firstRow, Int32 firstColumn, Int32 lastRow, Int32 lastColumn); IXLAutoFilter SetAutoFilter(); diff --git a/ClosedXML/Excel/Ranges/XLRange.cs b/ClosedXML/Excel/Ranges/XLRange.cs index 94d53e3..1991c89 100644 --- a/ClosedXML/Excel/Ranges/XLRange.cs +++ b/ClosedXML/Excel/Ranges/XLRange.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; - namespace ClosedXML.Excel { internal class XLRange : XLRangeBase, IXLRange @@ -23,29 +22,28 @@ SetStyle(xlRangeParameters.DefaultStyle); } - #endregion + #endregion Constructor public XLRangeParameters RangeParameters { get; private set; } #region IXLRange Members - IXLRangeRow IXLRange.Row(Int32 row) { return Row(row); } - IXLRangeColumn IXLRange.Column(Int32 column) + IXLRangeColumn IXLRange.Column(Int32 columnNumber) { - return Column(column); + return Column(columnNumber); } - IXLRangeColumn IXLRange.Column(String column) + IXLRangeColumn IXLRange.Column(String columnLetter) { - return Column(column); + return Column(columnLetter); } - public IXLRangeColumns Columns(Func predicate = null) + public virtual IXLRangeColumns Columns(Func predicate = null) { var retVal = new XLRangeColumns(); Int32 columnCount = ColumnCount(); @@ -69,13 +67,13 @@ return retVal; } - public IXLRangeColumns Columns(String firstColumn, String lastColumn) + public virtual IXLRangeColumns Columns(String firstColumn, String lastColumn) { return Columns(XLHelper.GetColumnNumberFromLetter(firstColumn), XLHelper.GetColumnNumberFromLetter(lastColumn)); } - public IXLRangeColumns Columns(String columns) + public virtual IXLRangeColumns Columns(String columns) { var retVal = new XLRangeColumns(); var columnPairs = columns.Split(','); @@ -274,6 +272,7 @@ { return CreateTable(); } + public XLTable CreateTable() { return new XLTable(this, true, true); @@ -283,6 +282,7 @@ { return CreateTable(name); } + public XLTable CreateTable(String name) { return new XLTable(this, name, true, true); @@ -333,27 +333,27 @@ return this; } - public new IXLRange Sort() { return base.Sort().AsRange(); } - + public new IXLRange Sort(String columnsToSortBy, XLSortOrder sortOrder = XLSortOrder.Ascending, Boolean matchCase = false, Boolean ignoreBlanks = true) { return base.Sort(columnsToSortBy, sortOrder, matchCase, ignoreBlanks).AsRange(); } + public new IXLRange Sort(Int32 columnToSortBy, XLSortOrder sortOrder = XLSortOrder.Ascending, Boolean matchCase = false, Boolean ignoreBlanks = true) { return base.Sort(columnToSortBy, sortOrder, matchCase, ignoreBlanks).AsRange(); } + public new IXLRange SortLeftToRight(XLSortOrder sortOrder = XLSortOrder.Ascending, Boolean matchCase = false, Boolean ignoreBlanks = true) { return base.SortLeftToRight(sortOrder, matchCase, ignoreBlanks).AsRange(); } - - #endregion + #endregion IXLRange Members private void WorksheetRangeShiftedColumns(XLRange range, int columnsShifted) { @@ -362,13 +362,14 @@ private void WorksheetRangeShiftedRows(XLRange range, int rowsShifted) { - ShiftRows(RangeAddress, range, rowsShifted); + ShiftRows(RangeAddress, range, rowsShifted); } IXLRangeColumn IXLRange.FirstColumn(Func predicate) { return FirstColumn(predicate); } + public XLRangeColumn FirstColumn(Func predicate = null) { if (predicate == null) @@ -390,6 +391,7 @@ { return LastColumn(predicate); } + public XLRangeColumn LastColumn(Func predicate = null) { Int32 columnCount = ColumnCount(); @@ -407,10 +409,11 @@ return null; } - IXLRangeColumn IXLRange.FirstColumnUsed(Func predicate ) + IXLRangeColumn IXLRange.FirstColumnUsed(Func predicate) { return FirstColumnUsed(false, predicate); } + public XLRangeColumn FirstColumnUsed(Func predicate = null) { return FirstColumnUsed(false, predicate); @@ -420,6 +423,7 @@ { return FirstColumnUsed(includeFormats, predicate); } + public XLRangeColumn FirstColumnUsed(Boolean includeFormats, Func predicate = null) { if (predicate == null) @@ -450,6 +454,7 @@ { return LastColumnUsed(false, predicate); } + public XLRangeColumn LastColumnUsed(Func predicate = null) { return LastColumnUsed(false, predicate); @@ -459,6 +464,7 @@ { return LastColumnUsed(includeFormats, predicate); } + public XLRangeColumn LastColumnUsed(Boolean includeFormats, Func predicate = null) { if (predicate == null) @@ -489,6 +495,7 @@ { return FirstRow(predicate); } + public XLRangeRow FirstRow(Func predicate = null) { if (predicate == null) @@ -510,6 +517,7 @@ { return LastRow(predicate); } + public XLRangeRow LastRow(Func predicate = null) { Int32 rowCount = RowCount(); @@ -531,6 +539,7 @@ { return FirstRowUsed(false, predicate); } + public XLRangeRow FirstRowUsed(Func predicate = null) { return FirstRowUsed(false, predicate); @@ -540,6 +549,7 @@ { return FirstRowUsed(includeFormats, predicate); } + public XLRangeRow FirstRowUsed(Boolean includeFormats, Func predicate = null) { if (predicate == null) @@ -572,6 +582,7 @@ { return LastRowUsed(false, predicate); } + public XLRangeRow LastRowUsed(Func predicate = null) { return LastRowUsed(false, predicate); @@ -581,6 +592,7 @@ { return LastRowUsed(includeFormats, predicate); } + public XLRangeRow LastRowUsed(Boolean includeFormats, Func predicate = null) { if (predicate == null) @@ -607,11 +619,11 @@ return null; } - IXLRangeRows IXLRange.RowsUsed(Boolean includeFormats, Func predicate) { return RowsUsed(includeFormats, predicate); } + public XLRangeRows RowsUsed(Boolean includeFormats, Func predicate = null) { XLRangeRows rows = new XLRangeRows(); @@ -627,19 +639,23 @@ } return rows; } + IXLRangeRows IXLRange.RowsUsed(Func predicate) { return RowsUsed(predicate); } + public XLRangeRows RowsUsed(Func predicate = null) { return RowsUsed(false, predicate); } + IXLRangeColumns IXLRange.ColumnsUsed(Boolean includeFormats, Func predicate) { return ColumnsUsed(includeFormats, predicate); } - public XLRangeColumns ColumnsUsed(Boolean includeFormats, Func predicate = null) + + public virtual XLRangeColumns ColumnsUsed(Boolean includeFormats, Func predicate = null) { XLRangeColumns columns = new XLRangeColumns(); Int32 columnCount = ColumnCount(); @@ -654,11 +670,13 @@ } return columns; } + IXLRangeColumns IXLRange.ColumnsUsed(Func predicate) { return ColumnsUsed(predicate); } - public XLRangeColumns ColumnsUsed(Func predicate = null) + + public virtual XLRangeColumns ColumnsUsed(Func predicate = null) { return ColumnsUsed(false, predicate); } @@ -682,34 +700,30 @@ new XLRangeParameters(new XLRangeAddress(firstCellAddress, lastCellAddress), Worksheet.Style), false); } - - - public XLRangeColumn Column(Int32 column) + public virtual XLRangeColumn Column(Int32 columnNumber) { - if (column <= 0 || column > XLHelper.MaxColumnNumber) + if (columnNumber <= 0 || columnNumber > XLHelper.MaxColumnNumber) throw new IndexOutOfRangeException(String.Format("Column number must be between 1 and {0}", XLHelper.MaxColumnNumber)); var firstCellAddress = new XLAddress(Worksheet, RangeAddress.FirstAddress.RowNumber, - RangeAddress.FirstAddress.ColumnNumber + column - 1, + RangeAddress.FirstAddress.ColumnNumber + columnNumber - 1, false, false); var lastCellAddress = new XLAddress(Worksheet, RangeAddress.LastAddress.RowNumber, - RangeAddress.FirstAddress.ColumnNumber + column - 1, + RangeAddress.FirstAddress.ColumnNumber + columnNumber - 1, false, false); return new XLRangeColumn( new XLRangeParameters(new XLRangeAddress(firstCellAddress, lastCellAddress), Worksheet.Style), false); } - public XLRangeColumn Column(String column) + public virtual XLRangeColumn Column(String columnLetter) { - return Column(XLHelper.GetColumnNumberFromLetter(column)); + return Column(XLHelper.GetColumnNumberFromLetter(columnLetter)); } - - private void TransposeRange(int squareSide) { var cellsToInsert = new Dictionary(); @@ -728,7 +742,7 @@ { var oldCell = rngToTranspose.Cell(ro, co); var newKey = rngToTranspose.Cell(co, ro).Address; - // new XLAddress(Worksheet, c.Address.ColumnNumber, c.Address.RowNumber); + // new XLAddress(Worksheet, c.Address.ColumnNumber, c.Address.RowNumber); var newCell = new XLCell(Worksheet, newKey, oldCell.GetStyleId()); newCell.CopyFrom(oldCell, true); cellsToInsert.Add(new XLSheetPoint(newKey.RowNumber, newKey.ColumnNumber), newCell); diff --git a/ClosedXML/Excel/Ranges/XLRangeAddress.cs b/ClosedXML/Excel/Ranges/XLRangeAddress.cs index df631ec..13f986a 100644 --- a/ClosedXML/Excel/Ranges/XLRangeAddress.cs +++ b/ClosedXML/Excel/Ranges/XLRangeAddress.cs @@ -1,7 +1,6 @@ using ClosedXML.Extensions; using System; using System.Diagnostics; -using System.Globalization; using System.Linq; namespace ClosedXML.Excel @@ -10,16 +9,18 @@ { #region Private fields - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private XLAddress _firstAddress; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private XLAddress _lastAddress; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private XLAddress _firstAddress; - #endregion + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private XLAddress _lastAddress; + + #endregion Private fields #region Constructor - public XLRangeAddress(XLRangeAddress rangeAddress): this(rangeAddress.FirstAddress, rangeAddress.LastAddress) + public XLRangeAddress(XLRangeAddress rangeAddress) : this(rangeAddress.FirstAddress, rangeAddress.LastAddress) { - } public XLRangeAddress(XLAddress firstAddress, XLAddress lastAddress) @@ -73,7 +74,7 @@ Worksheet = worksheet; } - #endregion + #endregion Constructor #region Public properties @@ -84,7 +85,7 @@ get { if (IsInvalid) - throw new Exception("Range is invalid."); + throw new InvalidOperationException("Range is invalid."); return _firstAddress; } @@ -96,7 +97,7 @@ get { if (IsInvalid) - throw new Exception("Range is an invalid state."); + throw new InvalidOperationException("Range is an invalid state."); return _lastAddress; } @@ -122,10 +123,9 @@ set { LastAddress = value as XLAddress; } } - public bool IsInvalid { get; set; } - #endregion + #endregion Public properties #region Public methods @@ -168,7 +168,20 @@ public override string ToString() { - return _firstAddress + ":" + _lastAddress; + return String.Concat(_firstAddress, ':', _lastAddress); + } + + public string ToString(XLReferenceStyle referenceStyle) + { + return ToString(referenceStyle, false); + } + + public string ToString(XLReferenceStyle referenceStyle, bool includeSheet) + { + if (referenceStyle == XLReferenceStyle.R1C1) + return ToStringFixed(referenceStyle, true); + else + return ToStringRelative(includeSheet); } public override bool Equals(object obj) @@ -187,6 +200,6 @@ ^ LastAddress.GetHashCode(); } - #endregion + #endregion Public methods } } diff --git a/ClosedXML/Excel/Ranges/XLRangeBase.cs b/ClosedXML/Excel/Ranges/XLRangeBase.cs index 6e34637..190d473 100644 --- a/ClosedXML/Excel/Ranges/XLRangeBase.cs +++ b/ClosedXML/Excel/Ranges/XLRangeBase.cs @@ -2,36 +2,39 @@ using ClosedXML.Extensions; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; - - namespace ClosedXML.Excel { internal abstract class XLRangeBase : IXLRangeBase, IXLStylized { public Boolean StyleChanged { get; set; } + #region Fields private IXLStyle _style; private XLSortElements _sortRows; private XLSortElements _sortColumns; - #endregion + #endregion Fields private Int32 _styleCacheId; + protected void SetStyle(IXLStyle styleToUse) { _styleCacheId = Worksheet.Workbook.GetStyleId(styleToUse); _style = null; StyleChanged = false; } + protected void SetStyle(Int32 styleId) { _styleCacheId = styleId; _style = null; StyleChanged = false; } + public Int32 GetStyleId() { if (StyleChanged) @@ -39,6 +42,7 @@ return _styleCacheId; } + protected IXLStyle GetStyle() { return _style ?? (_style = new XLStyle(this, Worksheet.Workbook.GetStyleById(_styleCacheId))); @@ -46,18 +50,17 @@ #region Constructor - static Int32 IdCounter = 0; - readonly Int32 Id; + private static Int32 IdCounter = 0; + private readonly Int32 Id; protected XLRangeBase(XLRangeAddress rangeAddress) { - Id = ++IdCounter; RangeAddress = new XLRangeAddress(rangeAddress); } - #endregion + #endregion Constructor private XLCallbackAction _shiftedRowsAction; @@ -71,6 +74,7 @@ } private XLCallbackAction _shiftedColumnsAction; + protected void SubscribeToShiftedColumns(Action action) { if (Worksheet == null || !Worksheet.EventTrackingEnabled) return; @@ -82,9 +86,8 @@ #region Public properties - //public XLRangeAddress RangeAddress { get; protected set; } - private XLRangeAddress _rangeAddress; + public XLRangeAddress RangeAddress { get { return _rangeAddress; } @@ -243,8 +246,6 @@ } } - - public Object Value { set { Cells().ForEach(c => c.Value = value); } @@ -255,7 +256,7 @@ set { Cells().ForEach(c => c.DataType = value); } } - #endregion + #endregion IXLRangeBase Members #region IXLStylized Members @@ -268,9 +269,9 @@ } } - #endregion + #endregion IXLStylized Members - #endregion + #endregion Public properties #region IXLRangeBase Members @@ -457,17 +458,17 @@ get { return GetStyle(); } set { Cells().ForEach(c => c.Style = value); } } + IXLRange IXLRangeBase.AsRange() { return AsRange(); } + public virtual XLRange AsRange() { return Worksheet.Range(RangeAddress.FirstAddress, RangeAddress.LastAddress); } - - public IXLRange AddToNamed(String rangeName) { return AddToNamed(rangeName, XLScope.Workbook); @@ -505,18 +506,30 @@ return Cells().Any(c => c.IsMerged()); } - public Boolean IsEmpty() + public virtual Boolean IsEmpty() { return !CellsUsed().Any() || CellsUsed().Any(c => c.IsEmpty()); } - public Boolean IsEmpty(Boolean includeFormats) + public virtual Boolean IsEmpty(Boolean includeFormats) { return !CellsUsed(includeFormats).Cast().Any() || CellsUsed(includeFormats).Cast().Any(c => c.IsEmpty(includeFormats)); } - #endregion + public virtual Boolean IsEntireRow() + { + return RangeAddress.FirstAddress.ColumnNumber == 1 + && RangeAddress.LastAddress.ColumnNumber == XLHelper.MaxColumnNumber; + } + + public virtual Boolean IsEntireColumn() + { + return RangeAddress.FirstAddress.RowNumber == 1 + && RangeAddress.LastAddress.RowNumber == XLHelper.MaxRowNumber; + } + + #endregion IXLRangeBase Members #region IXLStylized Members @@ -539,7 +552,28 @@ set { SetStyle(value); } } - #endregion + #endregion IXLStylized Members + + public IXLCells Search(String searchText, CompareOptions compareOptions = CompareOptions.Ordinal, Boolean searchFormulae = false) + { + var culture = CultureInfo.CurrentCulture; + return this.CellsUsed(false, c => + { + try + { + if (searchFormulae) + return c.HasFormula + && culture.CompareInfo.IndexOf(c.FormulaA1, searchText, compareOptions) >= 0 + || culture.CompareInfo.IndexOf(c.Value.ToString(), searchText, compareOptions) >= 0; + else + return culture.CompareInfo.IndexOf(c.GetFormattedString(), searchText, compareOptions) >= 0; + } + catch + { + return false; + } + }); + } public XLCell FirstCell() { @@ -642,7 +676,6 @@ } } - if (sp.Row > 0) return Worksheet.Cell(sp.Row, sp.Column); @@ -734,7 +767,6 @@ } } - if (sp.Row > 0) return Worksheet.Cell(sp.Row, sp.Column); @@ -748,7 +780,6 @@ public XLCell Cell(String cellAddressInRange) { - if (XLHelper.IsValidA1Address(cellAddressInRange)) return Cell(XLAddress.Create(Worksheet, cellAddressInRange)); @@ -810,12 +841,14 @@ Int32 newCellStyleId = styleId; - // If the default style for this range base is empty, but the worksheet + XLCell newCell; + // If the default style for this range base is empty, but the worksheet // has a default style, use the worksheet's default style - if (styleId == 0 && worksheetStyleId != 0) - newCellStyleId = worksheetStyleId; + if (styleId == 0 && worksheetStyleId != 0 || styleId == worksheetStyleId) + newCell = new XLCell(Worksheet, absoluteAddress); + else + newCell = new XLCell(Worksheet, absoluteAddress, newCellStyleId); - var newCell = new XLCell(Worksheet, absoluteAddress, newCellStyleId); Worksheet.Internals.CellsCollection.Add(absRow, absColumn, newCell); return newCell; } @@ -904,7 +937,6 @@ public XLRange Range(IXLRangeAddress rangeAddress) { - var newFirstCellAddress = new XLAddress((XLWorksheet)rangeAddress.FirstAddress.Worksheet, rangeAddress.FirstAddress.RowNumber + RangeAddress.FirstAddress.RowNumber - 1, rangeAddress.FirstAddress.ColumnNumber + RangeAddress.FirstAddress.ColumnNumber - 1, @@ -1072,12 +1104,11 @@ { foreach (XLWorksheet ws in Worksheet.Workbook.WorksheetsInternal) { - foreach (XLCell cell in ws.Internals.CellsCollection.GetCells(c => !XLHelper.IsNullOrWhiteSpace(c.FormulaA1))) + foreach (XLCell cell in ws.Internals.CellsCollection.GetCells(c => !String.IsNullOrWhiteSpace(c.FormulaA1))) using (var asRange = AsRange()) cell.ShiftFormulaColumns(asRange, numberOfColumns); } - var cellsDataValidations = new Dictionary(); var cellsToInsert = new Dictionary(); var cellsToDelete = new List(); @@ -1092,10 +1123,10 @@ { for (int co = lastColumn; co >= firstColumn; co--) { + int newColumn = co + numberOfColumns; for (int ro = lastRow; ro >= firstRow; ro--) { var oldKey = new XLAddress(Worksheet, ro, co, false, false); - int newColumn = co + numberOfColumns; var newKey = new XLAddress(Worksheet, ro, newColumn, false, false); var oldCell = Worksheet.Internals.CellsCollection.GetCell(ro, co) ?? Worksheet.Cell(oldKey); @@ -1106,6 +1137,11 @@ cellsToInsert.Add(newKey, newCell); cellsToDelete.Add(oldKey); } + + if (this.IsEntireColumn()) + { + Worksheet.Column(newColumn).Width = Worksheet.Column(co).Width; + } } } } @@ -1193,7 +1229,6 @@ : Worksheet.Style; rangeToReturn.Row(ro).Style = styleToUse; } - } } @@ -1284,15 +1319,17 @@ return retVal; } - struct DataValidationToCopy + private struct DataValidationToCopy { public XLAddress SourceAddress; public XLDataValidation DataValidation; } + public void InsertRowsAboveVoid(Boolean onlyUsedCells, Int32 numberOfRows, Boolean formatFromAbove = true) { InsertRowsAboveInternal(onlyUsedCells, numberOfRows, formatFromAbove, nullReturn: true); } + public IXLRangeRows InsertRowsAbove(Boolean onlyUsedCells, Int32 numberOfRows, Boolean formatFromAbove = true) { return InsertRowsAboveInternal(onlyUsedCells, numberOfRows, formatFromAbove, nullReturn: false); @@ -1303,7 +1340,7 @@ using (var asRange = AsRange()) foreach (XLWorksheet ws in Worksheet.Workbook.WorksheetsInternal) { - foreach (XLCell cell in ws.Internals.CellsCollection.GetCells(c => !XLHelper.IsNullOrWhiteSpace(c.FormulaA1))) + foreach (XLCell cell in ws.Internals.CellsCollection.GetCells(c => !String.IsNullOrWhiteSpace(c.FormulaA1))) cell.ShiftFormulaRows(asRange, numberOfRows); } @@ -1323,10 +1360,11 @@ { for (int ro = lastRow; ro >= firstRow; ro--) { + int newRow = ro + numberOfRows; + for (int co = lastColumn; co >= firstColumn; co--) { var oldKey = new XLAddress(Worksheet, ro, co, false, false); - int newRow = ro + numberOfRows; var newKey = new XLAddress(Worksheet, newRow, co, false, false); var oldCell = Worksheet.Internals.CellsCollection.GetCell(ro, co); if (oldCell != null) @@ -1338,6 +1376,10 @@ cellsToDelete.Add(oldKey); } } + if (this.IsEntireRow()) + { + Worksheet.Row(newRow).Height = Worksheet.Row(ro).Height; + } } } } @@ -1363,7 +1405,6 @@ newCell.FormulaA1 = c.FormulaA1; cellsToInsert.Add(newKey, newCell); cellsToDelete.Add(c.Address); - } } @@ -1381,7 +1422,6 @@ cellsToDelete.ForEach(c => Worksheet.Internals.CellsCollection.Remove(c.RowNumber, c.ColumnNumber)); cellsToInsert.ForEach(c => Worksheet.Internals.CellsCollection.Add(c.Key.RowNumber, c.Key.ColumnNumber, c.Value)); - Int32 firstRowReturn = RangeAddress.FirstAddress.RowNumber; Int32 lastRowReturn = RangeAddress.FirstAddress.RowNumber + numberOfRows - 1; Int32 firstColumnReturn = RangeAddress.FirstAddress.ColumnNumber; @@ -1472,12 +1512,11 @@ RangeAddress.LastAddress.RowNumber, RangeAddress.LastAddress.ColumnNumber); - foreach ( XLCell cell in Worksheet.Workbook.Worksheets.Cast().SelectMany( xlWorksheet => (xlWorksheet).Internals.CellsCollection.GetCells( - c => !XLHelper.IsNullOrWhiteSpace(c.FormulaA1)))) + c => !String.IsNullOrWhiteSpace(c.FormulaA1)))) { if (shiftDeleteCells == XLShiftDeletedCells.ShiftCellsUp) cell.ShiftFormulaRows((XLRange)shiftedRangeFormula, numberOfRows * -1); @@ -1501,7 +1540,6 @@ Worksheet.Internals.CellsCollection.MaxRowUsed, RangeAddress.LastAddress.ColumnNumber); - int columnModifier = shiftDeleteCells == XLShiftDeletedCells.ShiftCellsLeft ? ColumnCount() : 0; int rowModifier = shiftDeleteCells == XLShiftDeletedCells.ShiftCellsUp ? RowCount() : 0; var cellsQuery = shiftDeleteCells == XLShiftDeletedCells.ShiftCellsLeft ? shiftLeftQuery : shiftUpQuery; @@ -1523,7 +1561,6 @@ cellsToInsert.Add(newKey, newCell); } - cellsToDelete.ForEach(c => Worksheet.Internals.CellsCollection.Remove(c.RowNumber, c.ColumnNumber)); cellsToInsert.ForEach( c => Worksheet.Internals.CellsCollection.Add(c.Key.RowNumber, c.Key.ColumnNumber, c.Value)); @@ -1698,17 +1735,16 @@ // return chart; //} - IXLPivotTable IXLRangeBase.CreatePivotTable(IXLCell targetCell) { return CreatePivotTable(targetCell); } + IXLPivotTable IXLRangeBase.CreatePivotTable(IXLCell targetCell, String name) { return CreatePivotTable(targetCell, name); } - public XLPivotTable CreatePivotTable(IXLCell targetCell) { return CreatePivotTable(targetCell, Guid.NewGuid().ToString()); @@ -1760,7 +1796,7 @@ public IXLRangeBase Sort(String columnsToSortBy, XLSortOrder sortOrder = XLSortOrder.Ascending, Boolean matchCase = false, Boolean ignoreBlanks = true) { SortColumns.Clear(); - if (XLHelper.IsNullOrWhiteSpace(columnsToSortBy)) + if (String.IsNullOrWhiteSpace(columnsToSortBy)) { columnsToSortBy = String.Empty; Int32 maxColumn = ColumnCount(); @@ -1821,7 +1857,6 @@ return this; } - #region Sort Rows private void SortRangeRows() @@ -1886,7 +1921,7 @@ SortingRangeRows(pivot + 1, end); } - #endregion + #endregion Sort Rows #region Sort Columns @@ -1951,9 +1986,9 @@ SortingRangeColumns(pivot + 1, end); } - #endregion + #endregion Sort Columns - #endregion + #endregion Sort public XLRangeColumn ColumnQuick(Int32 column) { @@ -2017,7 +2052,6 @@ } } - internal IXLConditionalFormat AddConditionalFormat(IXLConditionalFormat source) { using (var asRange = AsRange()) diff --git a/ClosedXML/Excel/Ranges/XLRangeColumn.cs b/ClosedXML/Excel/Ranges/XLRangeColumn.cs index 7449d3c..34710ea 100644 --- a/ClosedXML/Excel/Ranges/XLRangeColumn.cs +++ b/ClosedXML/Excel/Ranges/XLRangeColumn.cs @@ -1,9 +1,8 @@ +using System; +using System.Linq; + namespace ClosedXML.Excel { - using System; - using System.Linq; - - internal class XLRangeColumn : XLRangeBase, IXLRangeColumn { #region Constructor @@ -13,18 +12,24 @@ { if (quickLoad) return; - SubscribeToShiftedRows((range, rowsShifted) => this.WorksheetRangeShiftedRows(range, rowsShifted)); - SubscribeToShiftedColumns((range, columnsShifted) => this.WorksheetRangeShiftedColumns(range, columnsShifted)); + SubscribeToShiftedRows((range, rowsShifted) => this.WorksheetRangeShiftedRows(range, rowsShifted)); + SubscribeToShiftedColumns((range, columnsShifted) => this.WorksheetRangeShiftedColumns(range, columnsShifted)); SetStyle(rangeParameters.DefaultStyle); } - #endregion + public XLRangeColumn(XLRangeParameters rangeParameters, bool quickLoad, IXLTable table) + : this(rangeParameters, quickLoad) + { + this.Table = table; + } + + #endregion Constructor #region IXLRangeColumn Members - IXLCell IXLRangeColumn.Cell(int row) + IXLCell IXLRangeColumn.Cell(int rowNumber) { - return Cell(row); + return Cell(rowNumber); } public new IXLCells Cells(string cellsInColumn) @@ -43,6 +48,22 @@ public void Delete() { + Delete(true); + } + + internal void Delete(Boolean deleteTableField) + { + if (deleteTableField && IsTableColumn()) + { + var table = Table as XLTable; + var firstCellValue = Cell(1).Value.ToString(); + if (!table.FieldNames.ContainsKey(firstCellValue)) + throw new ArgumentException(string.Format("Field {0} not found.", firstCellValue)); + + var field = table.Fields.Cast().Single(f => f.Name == firstCellValue); + field.Delete(false); + } + Delete(XLShiftDeletedCells.ShiftCellsLeft); } @@ -77,7 +98,6 @@ return this; } - public new IXLRangeColumn CopyTo(IXLCell target) { base.CopyTo(target); @@ -166,7 +186,7 @@ return Worksheet.Column(RangeAddress.FirstAddress.ColumnNumber); } - #endregion + #endregion IXLRangeColumn Members public XLCell Cell(int row) { @@ -289,7 +309,7 @@ return ColumnShift(step * -1); } - #endregion + #endregion XLRangeColumn Left #region XLRangeColumn Right @@ -313,29 +333,40 @@ return ColumnShift(step); } - #endregion - + #endregion XLRangeColumn Right public IXLTable AsTable() { + if (IsTableColumn()) + throw new InvalidOperationException("This column is already part of a table."); + using (var asRange = AsRange()) - return asRange.AsTable(); + return asRange.AsTable(); } public IXLTable AsTable(string name) { + if (IsTableColumn()) + throw new InvalidOperationException("This column is already part of a table."); + using (var asRange = AsRange()) return asRange.AsTable(name); } public IXLTable CreateTable() { + if (IsTableColumn()) + throw new InvalidOperationException("This column is already part of a table."); + using (var asRange = AsRange()) return asRange.CreateTable(); } public IXLTable CreateTable(string name) { + if (IsTableColumn()) + throw new InvalidOperationException("This column is already part of a table."); + using (var asRange = AsRange()) return asRange.CreateTable(name); } @@ -351,5 +382,11 @@ return Column(FirstCellUsed(includeFormats), LastCellUsed(includeFormats)); } + internal IXLTable Table { get; set; } + + public Boolean IsTableColumn() + { + return Table != null; + } } } diff --git a/ClosedXML/Excel/Rows/IXLRow.cs b/ClosedXML/Excel/Rows/IXLRow.cs index 3bec732..70816fb 100644 --- a/ClosedXML/Excel/Rows/IXLRow.cs +++ b/ClosedXML/Excel/Rows/IXLRow.cs @@ -15,6 +15,11 @@ Double Height { get; set; } /// + /// Clears the height for the row and defaults it to the spreadsheet row height. + /// + void ClearHeight(); + + /// /// Deletes this row and shifts the rows below this one accordingly. /// void Delete(); diff --git a/ClosedXML/Excel/Rows/XLRow.cs b/ClosedXML/Excel/Rows/XLRow.cs index 944cb17..a847178 100644 --- a/ClosedXML/Excel/Rows/XLRow.cs +++ b/ClosedXML/Excel/Rows/XLRow.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Drawing; - +using System.Linq; namespace ClosedXML.Excel { @@ -15,7 +14,7 @@ private Boolean _isHidden; private Int32 _outlineLevel; - #endregion + #endregion Private fields #region Constructor @@ -44,7 +43,7 @@ _height = row._height; IsReference = row.IsReference; if (IsReference) - SubscribeToShiftedRows((range, rowShifted) => this.WorksheetRangeShiftedRows(range, rowShifted)); + SubscribeToShiftedRows((range, rowShifted) => this.WorksheetRangeShiftedRows(range, rowShifted)); _collapsed = row._collapsed; _isHidden = row._isHidden; @@ -53,7 +52,7 @@ SetStyle(row.GetStyleId()); } - #endregion + #endregion Constructor public Boolean IsReference { get; private set; } @@ -108,6 +107,7 @@ #region IXLRow Members private Boolean _loading; + public Boolean Loading { get { return IsReference ? Worksheet.Internals.RowsCollection[RowNumber()].Loading : _loading; } @@ -121,6 +121,7 @@ } public Boolean HeightChanged { get; private set; } + public Double Height { get { return IsReference ? Worksheet.Internals.RowsCollection[RowNumber()].Height : _height; } @@ -136,6 +137,12 @@ } } + public void ClearHeight() + { + Height = Worksheet.RowHeight; + HeightChanged = false; + } + public void Delete() { int rowNumber = RowNumber(); @@ -291,7 +298,7 @@ foreach (IXLRichString rt in c.RichText) { String formattedString = rt.Text; - var arr = formattedString.Split(new[] {Environment.NewLine}, StringSplitOptions.None); + var arr = formattedString.Split(new[] { Environment.NewLine }, StringSplitOptions.None); Int32 arrCount = arr.Count(); for (Int32 i = 0; i < arrCount; i++) { @@ -305,7 +312,7 @@ else { String formattedString = c.GetFormattedString(); - var arr = formattedString.Split(new[] {Environment.NewLine}, StringSplitOptions.None); + var arr = formattedString.Split(new[] { Environment.NewLine }, StringSplitOptions.None); Int32 arrCount = arr.Count(); for (Int32 i = 0; i < arrCount; i++) { @@ -338,7 +345,7 @@ } } else - thisHeight = c.Style.Font.GetHeight( fontCache); + thisHeight = c.Style.Font.GetHeight(fontCache); if (thisHeight >= maxHeight) { @@ -520,15 +527,15 @@ IXLRangeRow IXLRow.CopyTo(IXLCell target) { using (var asRange = AsRange()) - using (var copy = asRange.CopyTo(target)) - return copy.Row(1); + using (var copy = asRange.CopyTo(target)) + return copy.Row(1); } IXLRangeRow IXLRow.CopyTo(IXLRangeBase target) { using (var asRange = AsRange()) - using (var copy = asRange.CopyTo(target)) - return copy.Row(1); + using (var copy = asRange.CopyTo(target)) + return copy.Row(1); } public IXLRow CopyTo(IXLRow row) @@ -581,7 +588,7 @@ return Row(FirstCellUsed(includeFormats), LastCellUsed(includeFormats)); } - #endregion + #endregion IXLRow Members public override XLRange AsRange() { @@ -676,7 +683,7 @@ return RowShift(step * -1); } - #endregion + #endregion XLRow Above #region XLRow Below @@ -700,14 +707,14 @@ return RowShift(step); } - #endregion + #endregion XLRow Below - public new Boolean IsEmpty() + public override Boolean IsEmpty() { return IsEmpty(false); } - public new Boolean IsEmpty(Boolean includeFormats) + public override Boolean IsEmpty(Boolean includeFormats) { if (includeFormats && !Style.Equals(Worksheet.Style)) return false; @@ -715,6 +722,14 @@ return base.IsEmpty(includeFormats); } + public override Boolean IsEntireRow() + { + return true; + } + public override Boolean IsEntireColumn() + { + return false; + } } } diff --git a/ClosedXML/Excel/SaveOptions.cs b/ClosedXML/Excel/SaveOptions.cs new file mode 100644 index 0000000..7dcd9a1 --- /dev/null +++ b/ClosedXML/Excel/SaveOptions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ClosedXML.Excel +{ + public sealed class SaveOptions + { + public SaveOptions() + { +#if DEBUG + this.ValidatePackage = true; +#else + this.ValidatePackage = false; +#endif + + this.EvaluateFormulasBeforeSaving = false; + this.GenerateCalculationChain = true; + } + public Boolean ValidatePackage; + public Boolean EvaluateFormulasBeforeSaving; + public Boolean GenerateCalculationChain; + } +} diff --git a/ClosedXML/Excel/Style/Colors/XLColor_Public.cs b/ClosedXML/Excel/Style/Colors/XLColor_Public.cs index 6feefe3..4d0d9d3 100644 --- a/ClosedXML/Excel/Style/Colors/XLColor_Public.cs +++ b/ClosedXML/Excel/Style/Colors/XLColor_Public.cs @@ -55,7 +55,7 @@ get { if (_colorType == XLColorType.Theme) - throw new Exception("Cannot convert theme color to Color."); + throw new InvalidOperationException("Cannot convert theme color to Color."); if (_colorType == XLColorType.Indexed) if (_indexed == TOOLTIPCOLORINDEX) @@ -72,12 +72,12 @@ get { if (ColorType == XLColorType.Theme) - throw new Exception("Cannot convert theme color to indexed color."); + throw new InvalidOperationException("Cannot convert theme color to indexed color."); if (ColorType == XLColorType.Indexed) return _indexed; - throw new Exception("Cannot convert Color to indexed color."); + throw new InvalidOperationException("Cannot convert Color to indexed color."); } } @@ -89,9 +89,9 @@ return _themeColor; if (ColorType == XLColorType.Indexed) - throw new Exception("Cannot convert indexed color to theme color."); + throw new InvalidOperationException("Cannot convert indexed color to theme color."); - throw new Exception("Cannot convert Color to theme color."); + throw new InvalidOperationException("Cannot convert Color to theme color."); } } @@ -103,7 +103,7 @@ return _themeTint; if (ColorType == XLColorType.Indexed) - throw new Exception("Cannot extract theme tint from an indexed color."); + throw new InvalidOperationException("Cannot extract theme tint from an indexed color."); return _color.A/255.0; } diff --git a/ClosedXML/Excel/Style/XLPredefinedFormat.cs b/ClosedXML/Excel/Style/XLPredefinedFormat.cs new file mode 100644 index 0000000..32c6e1a --- /dev/null +++ b/ClosedXML/Excel/Style/XLPredefinedFormat.cs @@ -0,0 +1,165 @@ +namespace ClosedXML.Excel +{ + /// + /// Reference point of date/number formats available. + /// See more at: https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.numberingformat.aspx + /// + public static class XLPredefinedFormat + { + public enum Number + { + /// + /// General + /// + General = 0, + + /// + /// 0 + /// + Integer = 1, + + /// + /// 0.00 + /// + Precision2 = 2, + + /// + /// #,##0 + /// + IntegerWithSeparator = 3, + + /// + /// #,##0.00 + /// + Precision2WithSeparator = 4, + + /// + /// 0% + /// + PercentInteger = 9, + + /// + /// 0.00% + /// + PercentPrecision2 = 10, + + /// + /// 0.00E+00 + /// + ScientificPrecision2 = 11, + + /// + /// # ?/? + /// + FractionPrecision1 = 12, + + /// + /// # ??/?? + /// + FractionPrecision2 = 13, + + /// + /// #,##0 ,(#,##0) + /// + IntegerWithSeparatorAndParens = 37, + + /// + /// #,##0 ,[Red](#,##0) + /// + IntegerWithSeparatorAndParensRed = 38, + + /// + /// #,##0.00,(#,##0.00) + /// + Precision2WithSeparatorAndParens = 39, + + /// + /// #,##0.00,[Red](#,##0.00) + /// + Precision2WithSeparatorAndParensRed = 40, + + /// + /// ##0.0E+0 + /// + ScientificUpToHundredsAndPrecision1 = 48, + + /// + /// @ + /// + Text = 49 + } + + public enum DateTime + { + /// + /// General + /// + General = 0, + + /// + /// d/m/yyyy + /// + DayMonthYear4WithSlashes = 14, + + /// + /// d-mmm-yy + /// + DayMonthAbbrYear2WithDashes = 15, + + /// + /// d-mmm + /// + DayMonthAbbrWithDash = 16, + + /// + /// mmm-yy + /// + MonthAbbrYear2WithDash = 17, + + /// + /// h:mm tt + /// + Hour12MinutesAmPm = 18, + + /// + /// h:mm:ss tt + /// + Hour12MinutesSecondsAmPm = 19, + + /// + /// H:mm + /// + Hour24Minutes = 20, + + /// + /// H:mm:ss + /// + Hour24MinutesSeconds = 21, + + /// + /// m/d/yyyy H:mm + /// + MonthDayYear4WithDashesHour24Minutes = 22, + + /// + /// mm:ss + /// + MinutesSeconds = 45, + + /// + /// [h]:mm:ss + /// + Hour12MinutesSeconds = 46, + + /// + /// mmss.0 + /// + MinutesSecondsMillis1 = 47, + + /// + /// @ + /// + Text = 49 + } + } +} diff --git a/ClosedXML/Excel/Tables/IXLTable.cs b/ClosedXML/Excel/Tables/IXLTable.cs index 386c669..1030c02 100644 --- a/ClosedXML/Excel/Tables/IXLTable.cs +++ b/ClosedXML/Excel/Tables/IXLTable.cs @@ -1,31 +1,22 @@ using System; using System.Collections.Generic; + namespace ClosedXML.Excel { public interface IXLTable : IXLRange { - string Name { get; set; } + IXLBaseAutoFilter AutoFilter { get; } + IXLTableRange DataRange { get; } Boolean EmphasizeFirstColumn { get; set; } Boolean EmphasizeLastColumn { get; set; } - Boolean ShowRowStripes { get; set; } - Boolean ShowColumnStripes { get; set; } - Boolean ShowTotalsRow { get; set; } - Boolean ShowAutoFilter { get; set; } - XLTableTheme Theme { get; set; } - IXLRangeRow HeadersRow(); - IXLRangeRow TotalsRow(); - IXLTableField Field(string fieldName); - IXLTableField Field(int fieldIndex); IEnumerable Fields { get; } - - - - IXLTable SetEmphasizeFirstColumn(); IXLTable SetEmphasizeFirstColumn(Boolean value); - IXLTable SetEmphasizeLastColumn(); IXLTable SetEmphasizeLastColumn(Boolean value); - IXLTable SetShowRowStripes(); IXLTable SetShowRowStripes(Boolean value); - IXLTable SetShowColumnStripes(); IXLTable SetShowColumnStripes(Boolean value); - IXLTable SetShowTotalsRow(); IXLTable SetShowTotalsRow(Boolean value); - IXLTable SetShowAutoFilter(); IXLTable SetShowAutoFilter(Boolean value); + string Name { get; set; } + Boolean ShowAutoFilter { get; set; } + Boolean ShowColumnStripes { get; set; } + Boolean ShowHeaderRow { get; set; } + Boolean ShowRowStripes { get; set; } + Boolean ShowTotalsRow { get; set; } + XLTableTheme Theme { get; set; } /// /// Clears the contents of this table. @@ -33,13 +24,97 @@ /// Specify what you want to clear. new IXLTable Clear(XLClearOptions clearOptions = XLClearOptions.ContentsAndFormats); - IXLBaseAutoFilter AutoFilter { get; } + IXLTableField Field(string fieldName); + + IXLTableField Field(int fieldIndex); + + IXLRangeRow HeadersRow(); + + /// + /// Resizes the table to the specified range. + /// + /// The new table range. + /// + IXLTable Resize(IXLRange range); + + /// + /// Resizes the table to the specified range address. + /// + /// The range boundaries. + /// + IXLTable Resize(IXLRangeAddress rangeAddress); + + /// + /// Resizes the table to the specified range address. + /// + /// The range boundaries. + /// + IXLTable Resize(string rangeAddress); + + /// + /// Resizes the table to the specified range. + /// + /// The first cell in the range. + /// The last cell in the range. + /// + IXLTable Resize(IXLCell firstCell, IXLCell lastCell); + + /// + /// Resizes the table to the specified range. + /// + /// The first cell address in the worksheet. + /// The last cell address in the worksheet. + /// + IXLTable Resize(string firstCellAddress, string lastCellAddress); + + /// + /// Resizes the table to the specified range. + /// + /// The first cell address in the worksheet. + /// The last cell address in the worksheet. + /// + IXLTable Resize(IXLAddress firstCellAddress, IXLAddress lastCellAddress); + + /// + /// Resizes the table to the specified range. + /// + /// The first cell's row of the range to return. + /// The first cell's column of the range to return. + /// The last cell's row of the range to return. + /// The last cell's column of the range to return. + /// + IXLTable Resize(int firstCellRow, int firstCellColumn, int lastCellRow, int lastCellColumn); new IXLBaseAutoFilter SetAutoFilter(); - Boolean ShowHeaderRow { get; set; } - IXLTable SetShowHeaderRow(); IXLTable SetShowHeaderRow(Boolean value); + IXLTable SetEmphasizeFirstColumn(); - IXLTableRange DataRange { get; } + IXLTable SetEmphasizeFirstColumn(Boolean value); + + IXLTable SetEmphasizeLastColumn(); + + IXLTable SetEmphasizeLastColumn(Boolean value); + + IXLTable SetShowAutoFilter(); + + IXLTable SetShowAutoFilter(Boolean value); + + IXLTable SetShowColumnStripes(); + + IXLTable SetShowColumnStripes(Boolean value); + + IXLTable SetShowHeaderRow(); + + IXLTable SetShowHeaderRow(Boolean value); + + IXLTable SetShowRowStripes(); + + IXLTable SetShowRowStripes(Boolean value); + + IXLTable SetShowTotalsRow(); + + IXLTable SetShowTotalsRow(Boolean value); + + IXLRangeRow TotalsRow(); } -} \ No newline at end of file +} diff --git a/ClosedXML/Excel/Tables/IXLTableField.cs b/ClosedXML/Excel/Tables/IXLTableField.cs index 5bc695c..91ba4e8 100644 --- a/ClosedXML/Excel/Tables/IXLTableField.cs +++ b/ClosedXML/Excel/Tables/IXLTableField.cs @@ -18,11 +18,14 @@ public interface IXLTableField { + IXLRangeColumn Column { get; } Int32 Index { get; } String Name { get; set; } - String TotalsRowLabel { get; set; } String TotalsRowFormulaA1 { get; set; } String TotalsRowFormulaR1C1 { get; set; } XLTotalsRowFunction TotalsRowFunction { get; set; } + String TotalsRowLabel { get; set; } + + void Delete(); } } diff --git a/ClosedXML/Excel/Tables/XLTable.cs b/ClosedXML/Excel/Tables/XLTable.cs index 51bfac7..c468d76 100644 --- a/ClosedXML/Excel/Tables/XLTable.cs +++ b/ClosedXML/Excel/Tables/XLTable.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -13,12 +13,12 @@ internal bool _showTotalsRow; internal HashSet _uniqueNames; - #endregion + #endregion Private fields #region Constructor public XLTable(XLRange range, Boolean addToTables, Boolean setAutofilter = true) - : base(new XLRangeParameters(range.RangeAddress, range.Style )) + : base(new XLRangeParameters(range.RangeAddress, range.Style)) { InitializeValues(setAutofilter); @@ -45,53 +45,85 @@ AddToTables(range, addToTables); } - #endregion + #endregion Constructor private IXLRangeAddress _lastRangeAddress; private Dictionary _fieldNames = null; + public Dictionary FieldNames { get { - if (_fieldNames != null && _lastRangeAddress != null && _lastRangeAddress.Equals(RangeAddress)) return _fieldNames; + if (_fieldNames != null && _lastRangeAddress != null && _lastRangeAddress.Equals(RangeAddress)) + return _fieldNames; - _fieldNames = new Dictionary(); - _lastRangeAddress = RangeAddress; - - if (ShowHeaderRow) + if (_fieldNames == null) { - var headersRow = HeadersRow(); - Int32 cellPos = 0; - foreach (var cell in headersRow.Cells()) - { - var name = cell.GetString(); - if (XLHelper.IsNullOrWhiteSpace(name)) - { - name = "Column" + (cellPos + 1); - cell.SetValue(name); - } - if (_fieldNames.ContainsKey(name)) - throw new ArgumentException("The header row contains more than one field name '" + name + "'."); - - _fieldNames.Add(name, new XLTableField(this, name) {Index = cellPos++ }); - } + _fieldNames = new Dictionary(); + _lastRangeAddress = RangeAddress; + HeadersRow(); } else { - if (_fieldNames == null) _fieldNames = new Dictionary(); + HeadersRow(false); + } - Int32 colCount = ColumnCount(); - for (Int32 i = 1; i <= colCount; i++) + RescanFieldNames(); + + _lastRangeAddress = RangeAddress; + + return _fieldNames; + } + } + + private void RescanFieldNames() + { + if (ShowHeaderRow) + { + var detectedFieldNames = new Dictionary(); + var headersRow = HeadersRow(false); + Int32 cellPos = 0; + foreach (var cell in headersRow.Cells()) + { + var name = cell.GetString(); + if (_fieldNames.ContainsKey(name) && _fieldNames[name].Column.ColumnNumber() == cell.Address.ColumnNumber) { - if (!_fieldNames.Values.Any(f => f.Index == i - 1)) - { - var name = "Column" + i; + (_fieldNames[name] as XLTableField).Index = cellPos; + detectedFieldNames.Add(name, _fieldNames[name]); + cellPos++; + continue; + } - _fieldNames.Add(name, new XLTableField(this, name) {Index = i - 1 }); - } + if (String.IsNullOrWhiteSpace(name)) + { + name = GetUniqueName("Column", cellPos + 1, true); + cell.SetValue(name); + cell.DataType = XLCellValues.Text; + } + if (_fieldNames.ContainsKey(name)) + throw new ArgumentException("The header row contains more than one field name '" + name + "'."); + + _fieldNames.Add(name, new XLTableField(this, name) { Index = cellPos++ }); + detectedFieldNames.Add(name, _fieldNames[name]); + } + + _fieldNames.Keys + .Where(key => !detectedFieldNames.ContainsKey(key)) + .ToArray() + .ForEach(key => _fieldNames.Remove(key)); + } + else + { + Int32 colCount = ColumnCount(); + for (Int32 i = 1; i <= colCount; i++) + { + if (!_fieldNames.Values.Any(f => f.Index == i - 1)) + { + var name = "Column" + i; + + _fieldNames.Add(name, new XLTableField(this, name) { Index = i - 1 }); } } - return _fieldNames; } } @@ -100,12 +132,21 @@ _fieldNames = new Dictionary(); Int32 cellPos = 0; - foreach(var name in fieldNames) + foreach (var name in fieldNames) { _fieldNames.Add(name, new XLTableField(this, name) { Index = cellPos++ }); } } + internal void RenameField(String oldName, String newName) + { + if (!_fieldNames.ContainsKey(oldName)) + throw new ArgumentException("The field does not exist in this table", "oldName"); + + var field = _fieldNames[oldName]; + _fieldNames.Remove(oldName); + _fieldNames.Add(newName, field); + } internal String RelId { get; set; } @@ -114,14 +155,11 @@ get { XLRange range; - //var ws = Worksheet; - //var tracking = ws.EventTrackingEnabled; - //ws.EventTrackingEnabled = false; if (_showHeaderRow) { range = _showTotalsRow - ? Range(2, 1,RowCount() - 1,ColumnCount()) + ? Range(2, 1, RowCount() - 1, ColumnCount()) : Range(2, 1, RowCount(), ColumnCount()); } else @@ -130,12 +168,13 @@ ? Range(1, 1, RowCount() - 1, ColumnCount()) : Range(1, 1, RowCount(), ColumnCount()); } - //ws.EventTrackingEnabled = tracking; + return new XLTableRange(range, this); } } private XLAutoFilter _autoFilter; + public XLAutoFilter AutoFilter { get @@ -164,10 +203,13 @@ public Boolean ShowColumnStripes { get; set; } private Boolean _showAutoFilter; - public Boolean ShowAutoFilter { + + public Boolean ShowAutoFilter + { get { return _showHeaderRow && _showAutoFilter; } set { _showAutoFilter = value; } - } + } + public XLTableTheme Theme { get; set; } public String Name @@ -210,9 +252,18 @@ public IXLRangeRow HeadersRow() { + return HeadersRow(true); + } + + internal IXLRangeRow HeadersRow(Boolean scanForNewFieldsNames) + { if (!ShowHeaderRow) return null; - var m = FieldNames; + if (scanForNewFieldsNames) + { + var tempResult = FieldNames; + } + return FirstRow(); } @@ -241,6 +292,124 @@ } } + public IXLTable Resize(IXLRangeAddress rangeAddress) + { + return Resize(Worksheet.Range(RangeAddress)); + } + + public IXLTable Resize(string rangeAddress) + { + return Resize(Worksheet.Range(RangeAddress)); + } + + public IXLTable Resize(IXLCell firstCell, IXLCell lastCell) + { + return Resize(Worksheet.Range(firstCell, lastCell)); + } + + public IXLTable Resize(string firstCellAddress, string lastCellAddress) + { + return Resize(Worksheet.Range(firstCellAddress, lastCellAddress)); + } + + public IXLTable Resize(IXLAddress firstCellAddress, IXLAddress lastCellAddress) + { + return Resize(Worksheet.Range(firstCellAddress, lastCellAddress)); + } + + public IXLTable Resize(int firstCellRow, int firstCellColumn, int lastCellRow, int lastCellColumn) + { + return Resize(Worksheet.Range(firstCellRow, firstCellColumn, lastCellRow, lastCellColumn)); + } + + public IXLTable Resize(IXLRange range) + { + if (!this.ShowHeaderRow) + throw new NotImplementedException("Resizing of tables with no headers not supported yet."); + + if (this.Worksheet != range.Worksheet) + throw new InvalidOperationException("You cannot resize a table to a range on a different sheet."); + + var totalsRowChanged = this.ShowTotalsRow ? range.LastRow().RowNumber() - this.TotalsRow().RowNumber() : 0; + var oldTotalsRowNumber = this.ShowTotalsRow ? this.TotalsRow().RowNumber() : -1; + + var existingHeaders = this.FieldNames.Keys; + var newHeaders = new HashSet(); + var tempArray = this.Fields.Select(f => f.Column).ToArray(); + + var firstRow = range.Row(1); + if (!firstRow.FirstCell().Address.Equals(this.HeadersRow().FirstCell().Address) + || !firstRow.LastCell().Address.Equals(this.HeadersRow().LastCell().Address)) + { + _uniqueNames.Clear(); + var co = 1; + foreach (var c in firstRow.Cells()) + { + if (String.IsNullOrWhiteSpace(((XLCell)c).InnerText)) + c.Value = GetUniqueName("Column", co, true); + + var header = c.GetString(); + _uniqueNames.Add(header); + + if (!existingHeaders.Contains(header)) + newHeaders.Add(header); + + co++; + } + } + + if (totalsRowChanged < 0) + { + range.Rows(r => r.RowNumber().Equals(this.TotalsRow().RowNumber() + totalsRowChanged)).Single().InsertRowsAbove(1); + range = Worksheet.Range(range.FirstCell(), range.LastCell().CellAbove()); + oldTotalsRowNumber++; + } + else if (totalsRowChanged > 0) + { + this.TotalsRow().RowBelow(totalsRowChanged + 1).InsertRowsAbove(1); + this.TotalsRow().AsRange().Delete(XLShiftDeletedCells.ShiftCellsUp); + } + + this.RangeAddress = range.RangeAddress as XLRangeAddress; + RescanFieldNames(); + + if (this.ShowTotalsRow) + { + foreach (var f in this._fieldNames.Values) + { + var c = this.TotalsRow().Cell(f.Index + 1); + if (!c.IsEmpty() && newHeaders.Contains(f.Name)) + { + f.TotalsRowLabel = c.GetFormattedString(); + c.DataType = XLCellValues.Text; + } + } + + if (totalsRowChanged != 0) + { + foreach (var f in this._fieldNames.Values.Cast()) + { + f.UpdateUnderlyingCellFormula(); + var c = this.TotalsRow().Cell(f.Index + 1); + if (!String.IsNullOrWhiteSpace(f.TotalsRowLabel)) + { + c.DataType = XLCellValues.Text; + + //Remove previous row's label + var oldTotalsCell = this.Worksheet.Cell(oldTotalsRowNumber, f.Column.ColumnNumber()); + if (oldTotalsCell.Value.ToString() == f.TotalsRowLabel) + oldTotalsCell.Value = null; + } + + if (f.TotalsRowFunction != XLTotalsRowFunction.None) + c.DataType = XLCellValues.Number; + } + } + } + + return this; + } + public IXLTable SetEmphasizeFirstColumn() { EmphasizeFirstColumn = true; @@ -364,9 +533,7 @@ base.Dispose(); } - #endregion - - + #endregion IXLTable Members private void InitializeValues(Boolean setAutofilter) { @@ -376,7 +543,7 @@ if (setAutofilter) InitializeAutoFilter(); - HeadersRow().DataType = XLCellValues.Text; + AsRange().Row(1).DataType = XLCellValues.Text; if (RowCount() == 1) InsertRowsBelow(1); @@ -395,21 +562,20 @@ Int32 co = 1; foreach (IXLCell c in range.Row(1).Cells()) { - if (XLHelper.IsNullOrWhiteSpace(((XLCell)c).InnerText)) - c.Value = GetUniqueName("Column" + co.ToInvariantString()); + if (String.IsNullOrWhiteSpace(((XLCell)c).InnerText)) + c.Value = GetUniqueName("Column", co, true); _uniqueNames.Add(c.GetString()); co++; } Worksheet.Tables.Add(this); } - - private String GetUniqueName(String originalName) + private String GetUniqueName(String originalName, Int32 initialOffset, Boolean enforceOffset) { - String name = originalName; - if (_uniqueNames.Contains(name)) + String name = String.Concat(originalName, enforceOffset ? initialOffset.ToInvariantString() : string.Empty); + if (_uniqueNames?.Contains(name) ?? false) { - Int32 i = 1; + Int32 i = initialOffset; name = originalName + i.ToInvariantString(); while (_uniqueNames.Contains(name)) { @@ -418,12 +584,15 @@ } } - _uniqueNames.Add(name); return name; } public Int32 GetFieldIndex(String name) { + // There is a discrepancy in the way headers with line breaks are stored. + // The entry in the table definition will contain \r\n + // but the shared string value of the actual cell will contain only \n + name = name.Replace("\r\n", "\n"); if (FieldNames.ContainsKey(name)) return FieldNames[name].Index; @@ -431,6 +600,7 @@ } internal Boolean _showHeaderRow; + public Boolean ShowHeaderRow { get { return _showHeaderRow; } @@ -445,8 +615,8 @@ Int32 co = 1; foreach (IXLCell c in headersRow.Cells()) { - if (XLHelper.IsNullOrWhiteSpace(((XLCell)c).InnerText)) - c.Value = GetUniqueName("Column" + co.ToInvariantString()); + if (String.IsNullOrWhiteSpace(((XLCell)c).InnerText)) + c.Value = GetUniqueName("Column", co, true); _uniqueNames.Add(c.GetString()); co++; } @@ -461,59 +631,59 @@ } else { - using(var asRange = Worksheet.Range( - RangeAddress.FirstAddress.RowNumber - 1 , + using (var asRange = Worksheet.Range( + RangeAddress.FirstAddress.RowNumber - 1, RangeAddress.FirstAddress.ColumnNumber, RangeAddress.LastAddress.RowNumber, RangeAddress.LastAddress.ColumnNumber )) - using (var firstRow = asRange.FirstRow()) - { - IXLRangeRow rangeRow; - if (firstRow.IsEmpty(true)) - { - rangeRow = firstRow; - RangeAddress.FirstAddress = new XLAddress(Worksheet, - RangeAddress.FirstAddress.RowNumber - 1, - RangeAddress.FirstAddress.ColumnNumber, - RangeAddress.FirstAddress.FixedRow, - RangeAddress.FirstAddress.FixedColumn); - } - else - { - var fAddress = RangeAddress.FirstAddress; - var lAddress = RangeAddress.LastAddress; + using (var firstRow = asRange.FirstRow()) + { + IXLRangeRow rangeRow; + if (firstRow.IsEmpty(true)) + { + rangeRow = firstRow; + RangeAddress.FirstAddress = new XLAddress(Worksheet, + RangeAddress.FirstAddress.RowNumber - 1, + RangeAddress.FirstAddress.ColumnNumber, + RangeAddress.FirstAddress.FixedRow, + RangeAddress.FirstAddress.FixedColumn); + } + else + { + var fAddress = RangeAddress.FirstAddress; + var lAddress = RangeAddress.LastAddress; - rangeRow = firstRow.InsertRowsBelow(1, false).First(); + rangeRow = firstRow.InsertRowsBelow(1, false).First(); + RangeAddress.FirstAddress = new XLAddress(Worksheet, fAddress.RowNumber, + fAddress.ColumnNumber, + fAddress.FixedRow, + fAddress.FixedColumn); - RangeAddress.FirstAddress = new XLAddress(Worksheet, fAddress.RowNumber, - fAddress.ColumnNumber, - fAddress.FixedRow, - fAddress.FixedColumn); + RangeAddress.LastAddress = new XLAddress(Worksheet, lAddress.RowNumber + 1, + lAddress.ColumnNumber, + lAddress.FixedRow, + lAddress.FixedColumn); + } - RangeAddress.LastAddress = new XLAddress(Worksheet, lAddress.RowNumber + 1, - lAddress.ColumnNumber, - lAddress.FixedRow, - lAddress.FixedColumn); - } - - Int32 co = 1; - foreach (var name in FieldNames.Values.Select(f => f.Name)) - { - rangeRow.Cell(co).SetValue(name); - co++; - } - - } + Int32 co = 1; + foreach (var name in FieldNames.Values.Select(f => f.Name)) + { + rangeRow.Cell(co).SetValue(name); + co++; + } + } } _showHeaderRow = value; } } + public IXLTable SetShowHeaderRow() { return SetShowHeaderRow(true); } + public IXLTable SetShowHeaderRow(Boolean value) { ShowHeaderRow = value; @@ -528,5 +698,60 @@ RangeAddress.LastAddress.FixedColumn); } + public override XLRangeColumn Column(int columnNumber) + { + var column = base.Column(columnNumber); + column.Table = this; + return column; + } + + public override XLRangeColumn Column(string columnName) + { + var column = base.Column(columnName); + column.Table = this; + return column; + } + + public override IXLRangeColumns Columns(int firstColumn, int lastColumn) + { + var columns = base.Columns(firstColumn, lastColumn); + columns.Cast().ForEach(column => column.Table = this); + return columns; + } + + public override IXLRangeColumns Columns(Func predicate = null) + { + var columns = base.Columns(predicate); + columns.Cast().ForEach(column => column.Table = this); + return columns; + } + + public override IXLRangeColumns Columns(string columns) + { + var cols = base.Columns(columns); + cols.Cast().ForEach(column => column.Table = this); + return cols; + } + + public override IXLRangeColumns Columns(string firstColumn, string lastColumn) + { + var columns = base.Columns(firstColumn, lastColumn); + columns.Cast().ForEach(column => column.Table = this); + return columns; + } + + public override XLRangeColumns ColumnsUsed(bool includeFormats, Func predicate = null) + { + var columns = base.ColumnsUsed(includeFormats, predicate); + columns.Cast().ForEach(column => column.Table = this); + return columns; + } + + public override XLRangeColumns ColumnsUsed(Func predicate = null) + { + var columns = base.ColumnsUsed(predicate); + columns.Cast().ForEach(column => column.Table = this); + return columns; + } } } diff --git a/ClosedXML/Excel/Tables/XLTableField.cs b/ClosedXML/Excel/Tables/XLTableField.cs index 744700f..c737adc 100644 --- a/ClosedXML/Excel/Tables/XLTableField.cs +++ b/ClosedXML/Excel/Tables/XLTableField.cs @@ -1,19 +1,43 @@ -using System; +using System; +using System.Diagnostics; +using System.Linq; namespace ClosedXML.Excel { - internal class XLTableField: IXLTableField + [DebuggerDisplay("{Name}")] + internal class XLTableField : IXLTableField { - private XLTable table; + internal XLTotalsRowFunction totalsRowFunction; + internal String totalsRowLabel; + private readonly XLTable table; + + private String name; + public XLTableField(XLTable table, String name) { this.table = table; this.name = name; } - public Int32 Index { get; internal set; } + private IXLRangeColumn _column; - private String name; + public IXLRangeColumn Column + { + get { return _column ?? (_column = table.HeadersRow(false).Cell(this.Index + 1).AsRange().Columns().Single()); } + } + + private Int32 index; + + public Int32 Index + { + get { return index; } + internal set + { + if (index == value) return; + index = value; + _column = null; + } + } public String Name { @@ -24,24 +48,13 @@ set { if (table.ShowHeaderRow) - table.HeadersRow().Cell(Index + 1).SetValue(value); + (table.HeadersRow(false).Cell(Index + 1) as XLCell).SetValue(value, false); + table.RenameField(name, value); name = value; } } - internal String totalsRowLabel; - public String TotalsRowLabel - { - get { return totalsRowLabel; } - set - { - totalsRowFunction = XLTotalsRowFunction.None; - table.TotalsRow().Cell(Index + 1).SetValue(value); - totalsRowLabel = value; - } - } - public String TotalsRowFormulaA1 { get { return table.TotalsRow().Cell(Index + 1).FormulaA1; } @@ -51,6 +64,7 @@ table.TotalsRow().Cell(Index + 1).FormulaA1 = value; } } + public String TotalsRowFormulaR1C1 { get { return table.TotalsRow().Cell(Index + 1).FormulaR1C1; } @@ -61,38 +75,72 @@ } } - internal XLTotalsRowFunction totalsRowFunction; public XLTotalsRowFunction TotalsRowFunction { get { return totalsRowFunction; } set { - if (value != XLTotalsRowFunction.None && value != XLTotalsRowFunction.Custom) - { - var cell = table.TotalsRow().Cell(Index + 1); - String formula = String.Empty; - switch (value) - { - case XLTotalsRowFunction.Sum: formula = "109"; break; - case XLTotalsRowFunction.Minimum: formula = "105"; break; - case XLTotalsRowFunction.Maximum: formula = "104"; break; - case XLTotalsRowFunction.Average: formula = "101"; break; - case XLTotalsRowFunction.Count: formula = "103"; break; - case XLTotalsRowFunction.CountNumbers: formula = "102"; break; - case XLTotalsRowFunction.StandardDeviation: formula = "107"; break; - case XLTotalsRowFunction.Variance: formula = "110"; break; - } - - cell.FormulaA1 = "SUBTOTAL(" + formula + ",[" + Name + "])"; - var lastCell = table.LastRow().Cell(Index + 1); - if (lastCell.DataType != XLCellValues.Text) - { - cell.DataType = lastCell.DataType; - cell.Style.NumberFormat = lastCell.Style.NumberFormat; - } - } totalsRowFunction = value; + UpdateUnderlyingCellFormula(); } } + + internal void UpdateUnderlyingCellFormula() + { + if (TotalsRowFunction != XLTotalsRowFunction.None && TotalsRowFunction != XLTotalsRowFunction.Custom) + { + var cell = table.TotalsRow().Cell(Index + 1); + String formula = String.Empty; + switch (TotalsRowFunction) + { + case XLTotalsRowFunction.Sum: formula = "109"; break; + case XLTotalsRowFunction.Minimum: formula = "105"; break; + case XLTotalsRowFunction.Maximum: formula = "104"; break; + case XLTotalsRowFunction.Average: formula = "101"; break; + case XLTotalsRowFunction.Count: formula = "103"; break; + case XLTotalsRowFunction.CountNumbers: formula = "102"; break; + case XLTotalsRowFunction.StandardDeviation: formula = "107"; break; + case XLTotalsRowFunction.Variance: formula = "110"; break; + } + + cell.FormulaA1 = "SUBTOTAL(" + formula + ",[" + Name + "])"; + var lastCell = table.LastRow().Cell(Index + 1); + if (lastCell.DataType != XLCellValues.Text) + { + cell.DataType = lastCell.DataType; + cell.Style.NumberFormat = lastCell.Style.NumberFormat; + } + } + } + + public String TotalsRowLabel + { + get { return totalsRowLabel; } + set + { + totalsRowFunction = XLTotalsRowFunction.None; + (table.TotalsRow().Cell(Index + 1) as XLCell).SetValue(value, false); + totalsRowLabel = value; + } + } + + public void Delete() + { + Delete(true); + } + + internal void Delete(Boolean deleteUnderlyingRangeColumn) + { + var fields = table.Fields.Cast().ToArray(); + + if (deleteUnderlyingRangeColumn) + { + table.AsRange().ColumnQuick(this.Index + 1).Delete(); + // (this.Column as XLRangeColumn).Delete(false); + } + + fields.Where(f => f.Index > this.Index).ForEach(f => f.Index--); + table.FieldNames.Remove(this.Name); + } } -} +} \ No newline at end of file diff --git a/ClosedXML/Excel/XLWorkbook.cs b/ClosedXML/Excel/XLWorkbook.cs index 9e86a67..c014def 100644 --- a/ClosedXML/Excel/XLWorkbook.cs +++ b/ClosedXML/Excel/XLWorkbook.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.Data; +using System.Globalization; using System.IO; using System.Linq; namespace ClosedXML.Excel { - public enum XLEventTracking { Enabled, Disabled } + public enum XLCalculateMode { Auto, @@ -58,14 +59,11 @@ Italic = false, Underline = XLFontUnderlineValues.None, Strikethrough = false, - VerticalAlignment = - XLFontVerticalTextAlignmentValues. - Baseline, + VerticalAlignment = XLFontVerticalTextAlignmentValues.Baseline, FontSize = 11, FontColor = XLColor.FromArgb(0, 0, 0), FontName = "Calibri", - FontFamilyNumbering = - XLFontFamilyNumberingValues.Swiss + FontFamilyNumbering = XLFontFamilyNumberingValues.Swiss }, Fill = new XLFill(null) { @@ -75,10 +73,8 @@ }, Border = new XLBorder(null, null) { - BottomBorder = - XLBorderStyleValues.None, - DiagonalBorder = - XLBorderStyleValues.None, + BottomBorder = XLBorderStyleValues.None, + DiagonalBorder = XLBorderStyleValues.None, DiagonalDown = false, DiagonalUp = false, LeftBorder = XLBorderStyleValues.None, @@ -90,24 +86,17 @@ RightBorderColor = XLColor.Black, TopBorderColor = XLColor.Black }, - NumberFormat = - new XLNumberFormat(null, null) {NumberFormatId = 0}, + NumberFormat = new XLNumberFormat(null, null) { NumberFormatId = 0 }, Alignment = new XLAlignment(null) { Indent = 0, - Horizontal = - XLAlignmentHorizontalValues. - General, + Horizontal = XLAlignmentHorizontalValues.General, JustifyLastLine = false, - ReadingOrder = - XLAlignmentReadingOrderValues. - ContextDependent, + ReadingOrder = XLAlignmentReadingOrderValues.ContextDependent, RelativeIndent = 0, ShrinkToFit = false, TextRotation = 0, - Vertical = - XLAlignmentVerticalValues. - Bottom, + Vertical = XLAlignmentVerticalValues.Bottom, WrapText = false }, Protection = new XLProtection(null) @@ -166,7 +155,12 @@ /// public static XLCellSetValueBehavior CellSetValueBehavior { get; set; } - #endregion + public static XLWorkbook OpenFromTemplate(String path) + { + return new XLWorkbook(path, true); + } + + #endregion Static internal readonly List UnsupportedSheets = new List(); @@ -203,7 +197,7 @@ Stream }; - #endregion + #endregion Nested Type: XLLoadSource internal XLWorksheets WorksheetsInternal { get; private set; } @@ -265,7 +259,6 @@ /// public XLCalculateMode CalculateMode { get; set; } - public Boolean CalculationOnSave { get; set; } public Boolean ForceFullCalculation { get; set; } public Boolean FullCalculationOnLoad { get; set; } @@ -352,24 +345,34 @@ { case XLThemeColor.Text1: return Theme.Text1; + case XLThemeColor.Background1: return Theme.Background1; + case XLThemeColor.Text2: return Theme.Text2; + case XLThemeColor.Background2: return Theme.Background2; + case XLThemeColor.Accent1: return Theme.Accent1; + case XLThemeColor.Accent2: return Theme.Accent2; + case XLThemeColor.Accent3: return Theme.Accent3; + case XLThemeColor.Accent4: return Theme.Accent4; + case XLThemeColor.Accent5: return Theme.Accent5; + case XLThemeColor.Accent6: return Theme.Accent6; + default: throw new ArgumentException("Invalid theme color"); } @@ -422,7 +425,6 @@ return null; } - /// /// Saves the current workbook. /// @@ -440,16 +442,26 @@ /// public void Save(Boolean validate, Boolean evaluateFormulae = false) { + Save(new SaveOptions + { + ValidatePackage = validate, + EvaluateFormulasBeforeSaving = evaluateFormulae, + GenerateCalculationChain = true + }); + } + + public void Save(SaveOptions options) + { checkForWorksheetsPresent(); if (_loadSource == XLLoadSource.New) - throw new Exception("This is a new file, please use one of the SaveAs methods."); + throw new InvalidOperationException("This is a new file. Please use one of the 'SaveAs' methods."); if (_loadSource == XLLoadSource.Stream) { - CreatePackage(_originalStream, false, _spreadsheetDocumentType, validate, evaluateFormulae); + CreatePackage(_originalStream, false, _spreadsheetDocumentType, options); } else - CreatePackage(_originalFile, _spreadsheetDocumentType, validate, evaluateFormulae); + CreatePackage(_originalFile, _spreadsheetDocumentType, options); } /// @@ -469,6 +481,16 @@ /// public void SaveAs(String file, Boolean validate, Boolean evaluateFormulae = false) { + SaveAs(file, new SaveOptions + { + ValidatePackage = validate, + EvaluateFormulasBeforeSaving = evaluateFormulae, + GenerateCalculationChain = true + }); + } + + public void SaveAs(String file, SaveOptions options) + { checkForWorksheetsPresent(); PathHelper.CreateDirectory(Path.GetDirectoryName(file)); if (_loadSource == XLLoadSource.New) @@ -476,14 +498,14 @@ if (File.Exists(file)) File.Delete(file); - CreatePackage(file, GetSpreadsheetDocumentType(file), validate, evaluateFormulae); + CreatePackage(file, GetSpreadsheetDocumentType(file), options); } else if (_loadSource == XLLoadSource.File) { if (String.Compare(_originalFile.Trim(), file.Trim(), true) != 0) File.Copy(_originalFile, file, true); - CreatePackage(file, GetSpreadsheetDocumentType(file), validate, evaluateFormulae); + CreatePackage(file, GetSpreadsheetDocumentType(file), options); } else if (_loadSource == XLLoadSource.Stream) { @@ -492,17 +514,20 @@ using (var fileStream = File.Create(file)) { CopyStream(_originalStream, fileStream); - //fileStream.Position = 0; - CreatePackage(fileStream, false, _spreadsheetDocumentType, validate, evaluateFormulae); + CreatePackage(fileStream, false, _spreadsheetDocumentType, options); fileStream.Close(); } } + + _loadSource = XLLoadSource.File; + _originalFile = file; } private static SpreadsheetDocumentType GetSpreadsheetDocumentType(string filePath) { var extension = Path.GetExtension(filePath); - if (extension == null) throw new Exception("Empty extension is not supported."); + + if (extension == null) throw new ArgumentException("Empty extension is not supported."); extension = extension.Substring(1).ToLowerInvariant(); switch (extension) @@ -510,19 +535,20 @@ case "xlsm": case "xltm": return SpreadsheetDocumentType.MacroEnabledWorkbook; + case "xlsx": case "xltx": return SpreadsheetDocumentType.Workbook; + default: throw new ArgumentException(String.Format("Extension '{0}' is not supported. Supported extensions are '.xlsx', '.xslm', '.xltx' and '.xltm'.", extension)); - } } private void checkForWorksheetsPresent() { if (Worksheets.Count() == 0) - throw new Exception("Workbooks need at least one worksheet."); + throw new InvalidOperationException("Workbooks need at least one worksheet."); } /// @@ -542,6 +568,16 @@ /// public void SaveAs(Stream stream, Boolean validate, Boolean evaluateFormulae = false) { + SaveAs(stream, new SaveOptions + { + ValidatePackage = validate, + EvaluateFormulasBeforeSaving = evaluateFormulae, + GenerateCalculationChain = true + }); + } + + public void SaveAs(Stream stream, SaveOptions options) + { checkForWorksheetsPresent(); if (_loadSource == XLLoadSource.New) { @@ -552,13 +588,13 @@ if (stream.CanRead && stream.CanSeek && stream.CanWrite) { // all is fine the package can be created in a direct way - CreatePackage(stream, true, _spreadsheetDocumentType, validate, evaluateFormulae); + CreatePackage(stream, true, _spreadsheetDocumentType, options); } else { // the harder way MemoryStream ms = new MemoryStream(); - CreatePackage(ms, true, _spreadsheetDocumentType, validate, evaluateFormulae); + CreatePackage(ms, true, _spreadsheetDocumentType, options); // not really nessesary, because I changed CopyStream too. // but for better understanding and if somebody in the future // provide an changed version of CopyStream @@ -573,7 +609,7 @@ CopyStream(fileStream, stream); fileStream.Close(); } - CreatePackage(stream, false, _spreadsheetDocumentType, validate, evaluateFormulae); + CreatePackage(stream, false, _spreadsheetDocumentType, options); } else if (_loadSource == XLLoadSource.Stream) { @@ -581,8 +617,11 @@ if (_originalStream != stream) CopyStream(_originalStream, stream); - CreatePackage(stream, false, _spreadsheetDocumentType, validate, evaluateFormulae); + CreatePackage(stream, false, _spreadsheetDocumentType, options); } + + _loadSource = XLLoadSource.Stream; + _originalStream = stream; } internal static void CopyStream(Stream input, Stream output) @@ -596,7 +635,6 @@ output.Write(buffer, 0, len); // dm 20130422, and flushing the output after write output.Flush(); - } public IXLWorksheet Worksheet(String name) @@ -650,24 +688,44 @@ return columns; } + /// + /// Searches the cells' contents for a given piece of text + /// + /// The search text. + /// The compare options. + /// if set to true search formulae instead of cell values. + /// + public IEnumerable Search(String searchText, CompareOptions compareOptions = CompareOptions.Ordinal, Boolean searchFormulae = false) + { + foreach (var ws in WorksheetsInternal) + { + foreach (var cell in ws.Search(searchText, compareOptions, searchFormulae)) + yield return cell; + } + } + #region Fields - private readonly XLLoadSource _loadSource = XLLoadSource.New; - private readonly String _originalFile; - private readonly Stream _originalStream; + private XLLoadSource _loadSource = XLLoadSource.New; + private String _originalFile; + private Stream _originalStream; -#endregion + #endregion Fields #region Constructor - /// /// Creates a new Excel workbook. /// public XLWorkbook() :this(XLEventTracking.Enabled) { + } + internal XLWorkbook(String file, Boolean asTemplate) + : this(XLEventTracking.Enabled) + { + LoadSheetsFromTemplate(file); } public XLWorkbook(XLEventTracking eventTracking) @@ -706,7 +764,6 @@ public XLWorkbook(String file) : this(file, XLEventTracking.Enabled) { - } public XLWorkbook(String file, XLEventTracking eventTracking) @@ -718,15 +775,12 @@ Load(file); } - - /// /// Opens an existing workbook from a stream. /// /// The stream to open. public XLWorkbook(Stream stream):this(stream, XLEventTracking.Enabled) { - } public XLWorkbook(Stream stream, XLEventTracking eventTracking) @@ -737,7 +791,7 @@ Load(stream); } -#endregion + #endregion Constructor #region Nested type: UnsupportedSheet @@ -748,7 +802,7 @@ public Int32 Position; } -#endregion + #endregion Nested type: UnsupportedSheet public IXLCell Cell(String namedCell) { @@ -790,13 +844,13 @@ internal XLIdManager ShapeIdManager { get; private set; } - public void Dispose() { Worksheets.ForEach(w => w.Dispose()); } public Boolean Use1904DateSystem { get; set; } + public XLWorkbook SetUse1904DateSystem() { return SetUse1904DateSystem(true); @@ -817,10 +871,12 @@ { return Worksheets.Add(sheetName, position); } + public IXLWorksheet AddWorksheet(DataTable dataTable) { return Worksheets.Add(dataTable); } + public void AddWorksheet(DataSet dataSet) { Worksheets.Add(dataSet); @@ -837,10 +893,12 @@ } private XLCalcEngine _calcEngine; + private XLCalcEngine CalcEngine { get { return _calcEngine ?? (_calcEngine = new XLCalcEngine(this)); } } + public Object Evaluate(String expression) { return CalcEngine.Evaluate(expression); @@ -853,6 +911,7 @@ { get { return _calcEngineExpr ?? (_calcEngineExpr = new XLCalcEngine()); } } + public static Object EvaluateExpr(String expression) { return CalcEngineExpr.Evaluate(expression); @@ -861,12 +920,16 @@ public String Author { get; set; } public Boolean LockStructure { get; set; } + public XLWorkbook SetLockStructure(Boolean value) { LockStructure = value; return this; } + public Boolean LockWindows { get; set; } + public XLWorkbook SetLockWindows(Boolean value) { LockWindows = value; return this; } + internal HexBinaryValue LockPassword { get; set; } public Boolean IsPasswordProtected { get { return LockPassword != null; } } - + public void Protect(Boolean lockStructure, Boolean lockWindows, String workbookPassword) { if (IsPasswordProtected && workbookPassword == null) @@ -886,7 +949,6 @@ LockPassword = null; } - if (!IsPasswordProtected && hashPassword != null && (lockStructure || lockWindows)) { //Protect workbook using password. @@ -896,7 +958,7 @@ LockStructure = lockStructure; LockWindows = lockWindows; } - + public void Protect() { Protect(true); @@ -927,4 +989,4 @@ Protect(false, false, workbookPassword); } } -} \ No newline at end of file +} diff --git a/ClosedXML/Excel/XLWorkbook_ImageHandling.cs b/ClosedXML/Excel/XLWorkbook_ImageHandling.cs index 37e85da..9dbaf3e 100644 --- a/ClosedXML/Excel/XLWorkbook_ImageHandling.cs +++ b/ClosedXML/Excel/XLWorkbook_ImageHandling.cs @@ -43,7 +43,13 @@ if (!IsAllowedAnchor(anchor)) return null; - return anchor + var picture = anchor + .Descendants() + .FirstOrDefault(); + + if (picture == null) return null; + + return picture .Descendants() .FirstOrDefault(); } diff --git a/ClosedXML/Excel/XLWorkbook_Load.cs b/ClosedXML/Excel/XLWorkbook_Load.cs index ec3a6b2..9db3a92 100644 --- a/ClosedXML/Excel/XLWorkbook_Load.cs +++ b/ClosedXML/Excel/XLWorkbook_Load.cs @@ -55,11 +55,17 @@ LoadSpreadsheetDocument(dSpreadsheet); } + private void LoadSheetsFromTemplate(String fileName) + { + using (var dSpreadsheet = SpreadsheetDocument.CreateFromTemplate(fileName)) + LoadSpreadsheetDocument(dSpreadsheet); + } + private void LoadSpreadsheetDocument(SpreadsheetDocument dSpreadsheet) { ShapeIdManager = new XLIdManager(); SetProperties(dSpreadsheet); - //var sharedStrings = dSpreadsheet.WorkbookPart.SharedStringTablePart.SharedStringTable.Elements(); + SharedStringItem[] sharedStrings = null; if (dSpreadsheet.WorkbookPart.GetPartsOfType().Count() > 0) { @@ -267,11 +273,15 @@ #region LoadTables - foreach (TableDefinitionPart tablePart in wsPart.TableDefinitionParts) + foreach (var tablePart in wsPart.TableDefinitionParts) { var dTable = tablePart.Table; - string reference = dTable.Reference.Value; - XLTable xlTable = ws.Range(reference).CreateTable(dTable.Name, false) as XLTable; + String reference = dTable.Reference.Value; + String tableName = dTable?.Name ?? dTable.DisplayName ?? string.Empty; + if (String.IsNullOrWhiteSpace(tableName)) + throw new InvalidDataException("The table name is missing."); + + XLTable xlTable = ws.Range(reference).CreateTable(tableName, false) as XLTable; if (dTable.HeaderRowCount != null && dTable.HeaderRowCount == 0) { xlTable._showHeaderRow = false; @@ -502,6 +512,14 @@ if (pivotTableDefinition.ShowError != null && pivotTableDefinition.ErrorCaption != null) pt.ErrorValueReplacement = pivotTableDefinition.ErrorCaption.Value; + // Subtotal configuration + if (pivotTableDefinition.PivotFields.Cast().All(pf => pf.SubtotalTop != null && pf.SubtotalTop.HasValue && pf.SubtotalTop.Value)) + pt.SetSubtotals(XLPivotSubtotals.AtTop); + else if (pivotTableDefinition.PivotFields.Cast().All(pf => pf.SubtotalTop != null && pf.SubtotalTop.HasValue && !pf.SubtotalTop.Value)) + pt.SetSubtotals(XLPivotSubtotals.AtBottom); + else + pt.SetSubtotals(XLPivotSubtotals.DoNotShow); + // Row labels if (pivotTableDefinition.RowFields != null) { @@ -642,7 +660,7 @@ { var vsdp = GetPropertiesFromAnchor(anchor); - var picture = ws.AddPicture(stream, vsdp.Name) as XLPicture; + var picture = (ws as XLWorksheet).AddPicture(stream, vsdp.Name, Convert.ToInt32(vsdp.Id.Value)) as XLPicture; picture.RelId = imgId; Xdr.ShapeProperties spPr = anchor.Descendants().First(); @@ -727,7 +745,7 @@ if (shape != null) break; } - if (xdoc == null) throw new Exception("Could not load comments file"); + if (xdoc == null) throw new ArgumentException("Could not load comments file"); return xdoc; } @@ -1064,7 +1082,7 @@ else { if (!Worksheet(Int32.Parse(localSheetId) + 1).NamedRanges.Any(nr => nr.Name == name)) - Worksheet(Int32.Parse(localSheetId) + 1).NamedRanges.Add(name, text, comment).Visible = visible; + (Worksheet(Int32.Parse(localSheetId) + 1).NamedRanges as XLNamedRanges).Add(name, text, comment, true).Visible = visible; } } } @@ -1075,7 +1093,6 @@ private IEnumerable validateDefinedNames(IEnumerable definedNames) { - var fixedNames = new List(); var sb = new StringBuilder(); foreach (string testName in definedNames) { @@ -1177,7 +1194,11 @@ formula = cell.CellFormula.Text; if (cell.CellFormula.Reference != null) + { + // Parent cell of shared formulas + // Child cells will use this shared index to set its R1C1 style formula xlCell.FormulaReference = ws.Range(cell.CellFormula.Reference.Value).RangeAddress; + } xlCell.FormulaA1 = formula; sharedFormulasR1C1.Add(cell.CellFormula.SharedIndex.Value, xlCell.FormulaR1C1); @@ -1189,7 +1210,7 @@ { if (cell.CellFormula.SharedIndex != null) xlCell.FormulaR1C1 = sharedFormulasR1C1[cell.CellFormula.SharedIndex.Value]; - else + else if (!String.IsNullOrWhiteSpace(cell.CellFormula.Text)) { String formula; if (cell.CellFormula.FormulaType != null && cell.CellFormula.FormulaType == CellFormulaValues.Array) @@ -1201,7 +1222,16 @@ } if (cell.CellFormula.Reference != null) - xlCell.FormulaReference = ws.Range(cell.CellFormula.Reference.Value).RangeAddress; + { + foreach (var childCell in ws.Range(cell.CellFormula.Reference.Value).Cells(c => c.FormulaReference == null || !c.HasFormula)) + { + if (childCell.FormulaReference == null) + childCell.FormulaReference = ws.Range(cell.CellFormula.Reference.Value).RangeAddress; + + if (!childCell.HasFormula) + childCell.FormulaA1 = xlCell.FormulaA1; + } + } if (cell.CellValue != null) xlCell.ValueCached = cell.CellValue.Text; @@ -1225,7 +1255,7 @@ } else if (cell.DataType == CellValues.SharedString) { - if (cell.CellValue != null && !XLHelper.IsNullOrWhiteSpace(cell.CellValue.Text)) + if (cell.CellValue != null && !String.IsNullOrWhiteSpace(cell.CellValue.Text)) { var sharedString = sharedStrings[Int32.Parse(cell.CellValue.Text, XLHelper.NumberStyle, XLHelper.ParseCulture)]; ParseCellValue(sharedString, xlCell); @@ -1237,7 +1267,7 @@ } else if (cell.DataType == CellValues.Date) { - if (cell.CellValue != null && !XLHelper.IsNullOrWhiteSpace(cell.CellValue.Text)) + if (cell.CellValue != null && !String.IsNullOrWhiteSpace(cell.CellValue.Text)) xlCell._cellValue = Double.Parse(cell.CellValue.Text, XLHelper.NumberStyle, XLHelper.ParseCulture).ToInvariantString(); xlCell._dataType = XLCellValues.DateTime; } @@ -1249,7 +1279,7 @@ } else if (cell.DataType == CellValues.Number) { - if (cell.CellValue != null && !XLHelper.IsNullOrWhiteSpace(cell.CellValue.Text)) + if (cell.CellValue != null && !String.IsNullOrWhiteSpace(cell.CellValue.Text)) xlCell._cellValue = Double.Parse(cell.CellValue.Text, XLHelper.NumberStyle, XLHelper.ParseCulture).ToInvariantString(); if (s == null) @@ -1267,7 +1297,7 @@ else { var numberFormatId = ((CellFormat)(s.CellFormats).ElementAt(styleIndex)).NumberFormatId; - if (!XLHelper.IsNullOrWhiteSpace(cell.CellValue.Text)) + if (!String.IsNullOrWhiteSpace(cell.CellValue.Text)) xlCell._cellValue = Double.Parse(cell.CellValue.Text, CultureInfo.InvariantCulture).ToInvariantString(); if (s.NumberingFormats != null && @@ -1559,7 +1589,7 @@ return XLCellValues.Text; else { - if (!XLHelper.IsNullOrWhiteSpace(numberFormat.Format)) + if (!String.IsNullOrWhiteSpace(numberFormat.Format)) { var dataType = GetDataTypeFromFormat(numberFormat.Format); return dataType.HasValue ? dataType.Value : XLCellValues.Number; @@ -1774,7 +1804,7 @@ foreach (DataValidation dvs in dataValidations.Elements()) { String txt = dvs.SequenceOfReferences.InnerText; - if (XLHelper.IsNullOrWhiteSpace(txt)) continue; + if (String.IsNullOrWhiteSpace(txt)) continue; foreach (var dvt in txt.Split(' ').Select(rangeAddress => ws.Range(rangeAddress).DataValidation)) { if (dvs.AllowBlank != null) dvt.IgnoreBlanks = dvs.AllowBlank; @@ -1822,7 +1852,7 @@ if (conditionalFormat.ConditionalFormatType == XLConditionalFormatType.CellIs && fr.Operator != null) conditionalFormat.Operator = fr.Operator.Value.ToClosedXml(); - if (fr.Text != null && !XLHelper.IsNullOrWhiteSpace(fr.Text)) + if (fr.Text != null && !String.IsNullOrWhiteSpace(fr.Text)) conditionalFormat.Values.Add(GetFormula(fr.Text.Value)); if (conditionalFormat.ConditionalFormatType == XLConditionalFormatType.Top10) @@ -2435,4 +2465,4 @@ return false; } } -} \ No newline at end of file +} diff --git a/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/Excel/XLWorkbook_Save.cs index bcd2dc1..50510ce 100644 --- a/ClosedXML/Excel/XLWorkbook_Save.cs +++ b/ClosedXML/Excel/XLWorkbook_Save.cs @@ -87,7 +87,7 @@ } } - private bool Validate(SpreadsheetDocument package) + private Boolean Validate(SpreadsheetDocument package) { var backupCulture = Thread.CurrentThread.CurrentCulture; @@ -111,7 +111,7 @@ return true; } - private void CreatePackage(String filePath, SpreadsheetDocumentType spreadsheetDocumentType, bool validate, bool evaluateFormulae) + private void CreatePackage(String filePath, SpreadsheetDocumentType spreadsheetDocumentType, SaveOptions options) { PathHelper.CreateDirectory(Path.GetDirectoryName(filePath)); var package = File.Exists(filePath) @@ -120,12 +120,12 @@ using (package) { - CreateParts(package, evaluateFormulae); - if (validate) Validate(package); + CreateParts(package, options); + if (options.ValidatePackage) Validate(package); } } - private void CreatePackage(Stream stream, bool newStream, SpreadsheetDocumentType spreadsheetDocumentType, bool validate, bool evaluateFormulae) + private void CreatePackage(Stream stream, bool newStream, SpreadsheetDocumentType spreadsheetDocumentType, SaveOptions options) { var package = newStream ? SpreadsheetDocument.Create(stream, spreadsheetDocumentType) @@ -133,8 +133,8 @@ using (package) { - CreateParts(package, evaluateFormulae); - if (validate) Validate(package); + CreateParts(package, options); + if (options.ValidatePackage) Validate(package); } } @@ -142,9 +142,9 @@ private void DeleteSheetAndDependencies(WorkbookPart wbPart, string sheetId) { //Get the SheetToDelete from workbook.xml - Sheet worksheet = wbPart.Workbook.Descendants().Where(s => s.Id == sheetId).FirstOrDefault(); + Sheet worksheet = wbPart.Workbook.Descendants().FirstOrDefault(s => s.Id == sheetId); if (worksheet == null) - { } + return; string sheetName = worksheet.Name; // Get the pivot Table Parts @@ -154,8 +154,8 @@ { PivotCacheDefinition pvtCacheDef = Item.PivotCacheDefinition; //Check if this CacheSource is linked to SheetToDelete - var pvtCahce = pvtCacheDef.Descendants().Where(s => s.WorksheetSource.Sheet == sheetName); - if (pvtCahce.Count() > 0) + var pvtCache = pvtCacheDef.Descendants().Where(s => s.WorksheetSource.Sheet == sheetName); + if (pvtCache.Any()) { pvtTableCacheDefinationPart.Add(Item, Item.ToString()); } @@ -178,7 +178,7 @@ { List defNamesToDelete = new List(); - foreach (DefinedName Item in definedNames) + foreach (var Item in definedNames.OfType()) { // This condition checks to delete only those names which are part of Sheet in question if (Item.Text.Contains(worksheet.Name + "!")) @@ -201,24 +201,18 @@ var calChainEntries = calChainPart.CalculationChain.Descendants().Where(c => c.SheetId == sheetId); List calcsToDelete = new List(); foreach (CalculationCell Item in calChainEntries) - { calcsToDelete.Add(Item); - } foreach (CalculationCell Item in calcsToDelete) - { Item.Remove(); - } - if (calChainPart.CalculationChain.Count() == 0) - { + if (!calChainPart.CalculationChain.Any()) wbPart.DeletePart(calChainPart); - } } } // Adds child parts and generates content of the specified part. - private void CreateParts(SpreadsheetDocument document, bool evaluateFormulae) + private void CreateParts(SpreadsheetDocument document, SaveOptions options) { var context = new SaveContext(); @@ -241,12 +235,12 @@ // Ensure all RelId's have been added to the context context.RelIdGenerator.AddValues(workbookPart.Parts.Select(p => p.RelationshipId), RelType.Workbook); - context.RelIdGenerator.AddValues(WorksheetsInternal.Cast().Where(ws => !XLHelper.IsNullOrWhiteSpace(ws.RelId)).Select(ws => ws.RelId), RelType.Workbook); - context.RelIdGenerator.AddValues(WorksheetsInternal.Cast().Where(ws => !XLHelper.IsNullOrWhiteSpace(ws.LegacyDrawingId)).Select(ws => ws.LegacyDrawingId), RelType.Workbook); + context.RelIdGenerator.AddValues(WorksheetsInternal.Cast().Where(ws => !String.IsNullOrWhiteSpace(ws.RelId)).Select(ws => ws.RelId), RelType.Workbook); + context.RelIdGenerator.AddValues(WorksheetsInternal.Cast().Where(ws => !String.IsNullOrWhiteSpace(ws.LegacyDrawingId)).Select(ws => ws.LegacyDrawingId), RelType.Workbook); context.RelIdGenerator.AddValues(WorksheetsInternal .Cast() .SelectMany(ws => ws.Tables.Cast()) - .Where(t => !XLHelper.IsNullOrWhiteSpace(t.RelId)) + .Where(t => !String.IsNullOrWhiteSpace(t.RelId)) .Select(t => t.RelId), RelType.Workbook); var extendedFilePropertiesPart = document.ExtendedFilePropertiesPart ?? @@ -303,7 +297,7 @@ var vmlDrawingPart = worksheetPart.VmlDrawingParts.FirstOrDefault(); if (vmlDrawingPart == null) { - if (XLHelper.IsNullOrWhiteSpace(worksheet.LegacyDrawingId)) + if (String.IsNullOrWhiteSpace(worksheet.LegacyDrawingId)) { worksheet.LegacyDrawingId = context.RelIdGenerator.GetNext(RelType.Workbook); worksheet.LegacyDrawingIsNew = true; @@ -314,7 +308,7 @@ GenerateVmlDrawingPartContent(vmlDrawingPart, worksheet, context); } - GenerateWorksheetPartContent(worksheetPart, worksheet, evaluateFormulae, context); + GenerateWorksheetPartContent(worksheetPart, worksheet, options.EvaluateFormulasBeforeSaving, context); if (worksheet.PivotTables.Any()) { @@ -333,7 +327,10 @@ if (workbookPart.Workbook.PivotCaches != null && !workbookPart.Workbook.PivotCaches.Any()) workbookPart.Workbook.RemoveChild(workbookPart.Workbook.PivotCaches); - GenerateCalculationChainPartContent(workbookPart, context); + if (options.GenerateCalculationChain) + GenerateCalculationChainPartContent(workbookPart, context); + else + DeleteCalculationChainPartContent(workbookPart, context); if (workbookPart.ThemePart == null) { @@ -350,6 +347,9 @@ GenerateCustomFilePropertiesPartContent(customFilePropertiesPart); } SetPackageProperties(document); + + // Clear list of deleted worksheets to prevent errors on multiple saves + worksheets.Deleted.Clear(); } private void DeleteComments(WorksheetPart worksheetPart, XLWorksheet worksheet, SaveContext context) @@ -473,7 +473,7 @@ if (Properties.Manager != null) { - if (!XLHelper.IsNullOrWhiteSpace(Properties.Manager)) + if (!String.IsNullOrWhiteSpace(Properties.Manager)) { if (properties.Manager == null) properties.Manager = new Manager(); @@ -486,7 +486,7 @@ if (Properties.Company == null) return; - if (!XLHelper.IsNullOrWhiteSpace(Properties.Company)) + if (!String.IsNullOrWhiteSpace(Properties.Company)) { if (properties.Company == null) properties.Company = new Company(); @@ -590,7 +590,7 @@ workbook.WorkbookProtection = null; } - #endregion + #endregion WorkbookProtection if (workbook.BookViews == null) workbook.BookViews = new BookViews(); @@ -616,7 +616,7 @@ foreach (var xlSheet in WorksheetsInternal.Cast().OrderBy(w => w.Position)) { string rId; - if (xlSheet.SheetId == 0 && XLHelper.IsNullOrWhiteSpace(xlSheet.RelId)) + if (xlSheet.SheetId == 0 && String.IsNullOrWhiteSpace(xlSheet.RelId)) { rId = context.RelIdGenerator.GetNext(RelType.Workbook); @@ -628,7 +628,7 @@ } else { - if (XLHelper.IsNullOrWhiteSpace(xlSheet.RelId)) + if (String.IsNullOrWhiteSpace(xlSheet.RelId)) { rId = String.Format("rId{0}", xlSheet.SheetId); context.RelIdGenerator.AddValues(new List { rId }, RelType.Workbook); @@ -672,6 +672,8 @@ var xlSheet = Worksheet(sheet.Name); if (xlSheet.Visibility != XLWorksheetVisibility.Visible) sheet.State = xlSheet.Visibility.ToOpenXml(); + else + sheet.State = null; if (foundVisible) continue; @@ -773,7 +775,7 @@ if (!nr.Visible) definedName.Hidden = BooleanValue.FromBoolean(true); - if (!XLHelper.IsNullOrWhiteSpace(nr.Comment)) + if (!String.IsNullOrWhiteSpace(nr.Comment)) definedName.Comment = nr.Comment; definedNames.AppendChild(definedName); } @@ -827,7 +829,7 @@ if (!nr.Visible) definedName.Hidden = BooleanValue.FromBoolean(true); - if (!XLHelper.IsNullOrWhiteSpace(nr.Comment)) + if (!String.IsNullOrWhiteSpace(nr.Comment)) definedName.Comment = nr.Comment; definedNames.AppendChild(definedName); } @@ -873,7 +875,7 @@ w.Internals.CellsCollection.GetCells( c => ((c.DataType == XLCellValues.Text && c.ShareString) || c.HasRichText) && (c as XLCell).InnerText.Length > 0 - && XLHelper.IsNullOrWhiteSpace(c.FormulaA1) + && String.IsNullOrWhiteSpace(c.FormulaA1) ))) { c.DataType = XLCellValues.Text; @@ -1002,11 +1004,16 @@ return run; } + private void DeleteCalculationChainPartContent(WorkbookPart workbookPart, SaveContext context) + { + if (workbookPart.CalculationChainPart != null) + workbookPart.DeletePart(workbookPart.CalculationChainPart); + } + private void GenerateCalculationChainPartContent(WorkbookPart workbookPart, SaveContext context) { - var thisRelId = context.RelIdGenerator.GetNext(RelType.Workbook); if (workbookPart.CalculationChainPart == null) - workbookPart.AddNewPart(thisRelId); + workbookPart.AddNewPart(context.RelIdGenerator.GetNext(RelType.Workbook)); if (workbookPart.CalculationChainPart.CalculationChain == null) workbookPart.CalculationChainPart.CalculationChain = new CalculationChain(); @@ -1019,29 +1026,37 @@ var cellsWithoutFormulas = new HashSet(); foreach (var c in worksheet.Internals.CellsCollection.GetCells()) { - if (XLHelper.IsNullOrWhiteSpace(c.FormulaA1)) + if (String.IsNullOrWhiteSpace(c.FormulaA1)) cellsWithoutFormulas.Add(c.Address.ToStringRelative()); else { - if (c.FormulaA1.StartsWith("{")) + if (c.HasArrayFormula) { - var cc = new CalculationCell - { - CellReference = c.Address.ToString(), - SheetId = worksheet.SheetId - }; - - if (c.FormulaReference == null) - c.FormulaReference = c.AsRange().RangeAddress; if (c.FormulaReference.FirstAddress.Equals(c.Address)) { + var cc = new CalculationCell + { + CellReference = c.Address.ToString(), + SheetId = worksheet.SheetId + }; + + if (c.FormulaReference == null) + c.FormulaReference = c.AsRange().RangeAddress; + cc.Array = true; calculationChain.AppendChild(cc); - calculationChain.AppendChild(new CalculationCell { CellReference = c.Address.ToString(), InChildChain = true }); - } - else - { - calculationChain.AppendChild(cc); + + foreach (var childCell in worksheet.Range(c.FormulaReference.ToString()).Cells()) + { + calculationChain.AppendChild( + new CalculationCell + { + CellReference = childCell.Address.ToString(), + SheetId = worksheet.SheetId, + InChildChain = true + } + ); + } } } else @@ -1055,17 +1070,34 @@ } } - //var cCellsToRemove = new List(); - var m = from cc in calculationChain.Elements() - where !(cc.SheetId != null || cc.InChildChain != null) - && calculationChain.Elements() - .Where(c1 => c1.SheetId != null) - .Select(c1 => c1.CellReference.Value) - .Contains(cc.CellReference.Value) - || cellsWithoutFormulas.Contains(cc.CellReference.Value) - select cc; - //m.ToList().ForEach(cc => cCellsToRemove.Add(cc)); - m.ToList().ForEach(cc => calculationChain.RemoveChild(cc)); + // This part shouldn't be necessary anymore, but I'm keeping it in the DEBUG configuration until I'm 100% sure. + +#if DEBUG + var sheetCellReferences = calculationChain.Elements() + .Where(cc1 => cc1.SheetId != null) + .Select(cc1 => cc1.CellReference.Value) + .ToList(); + + // Remove orphaned calc chain cells + var cellsToRemove = calculationChain.Elements() + .Where(cc => + { + return cc.SheetId == worksheet.SheetId + && cellsWithoutFormulas.Contains(cc.CellReference.Value) + || cc.SheetId == null + && cc.InChildChain == null + && sheetCellReferences.Contains(cc.CellReference.Value); + }) + .ToArray(); + + // This shouldn't happen, because the calc chain should be correctly generated + System.Diagnostics.Debug.Assert(!cellsToRemove.Any()); + + foreach (var cc in cellsToRemove) + { + calculationChain.RemoveChild(cc); + } +#endif } if (!calculationChain.Any()) @@ -1810,7 +1842,7 @@ tableColumn1.TotalsRowFormula = new TotalsRowFormula(xlField.TotalsRowFormulaA1); } - if (!XLHelper.IsNullOrWhiteSpace(xlField.TotalsRowLabel)) + if (!String.IsNullOrWhiteSpace(xlField.TotalsRowLabel)) tableColumn1.TotalsRowLabel = xlField.TotalsRowLabel; } tableColumns1.AppendChild(tableColumn1); @@ -1872,7 +1904,7 @@ var workbookCacheRelId = pt.WorkbookCacheRelId; PivotCache pivotCache; PivotTableCacheDefinitionPart pivotTableCacheDefinitionPart; - if (!XLHelper.IsNullOrWhiteSpace(pt.WorkbookCacheRelId)) + if (!String.IsNullOrWhiteSpace(pt.WorkbookCacheRelId)) { pivotCache = pivotCaches.Cast().Single(pc => pc.Id.Value == pt.WorkbookCacheRelId); pivotTableCacheDefinitionPart = workbookPart.GetPartById(pt.WorkbookCacheRelId) as PivotTableCacheDefinitionPart; @@ -1886,18 +1918,18 @@ GeneratePivotTableCacheDefinitionPartContent(pivotTableCacheDefinitionPart, pt); - if (XLHelper.IsNullOrWhiteSpace(pt.WorkbookCacheRelId)) + if (String.IsNullOrWhiteSpace(pt.WorkbookCacheRelId)) pivotCaches.AppendChild(pivotCache); PivotTablePart pivotTablePart; - if (XLHelper.IsNullOrWhiteSpace(pt.RelId)) + if (String.IsNullOrWhiteSpace(pt.RelId)) pivotTablePart = worksheetPart.AddNewPart(context.RelIdGenerator.GetNext(RelType.Workbook)); else pivotTablePart = worksheetPart.GetPartById(pt.RelId) as PivotTablePart; GeneratePivotTablePartContent(pivotTablePart, pt, pivotCache.CacheId, context); - if (XLHelper.IsNullOrWhiteSpace(pt.RelId)) + if (String.IsNullOrWhiteSpace(pt.RelId)) pivotTablePart.AddPart(pivotTableCacheDefinitionPart, context.RelIdGenerator.GetNext(RelType.Workbook)); } } @@ -1931,7 +1963,11 @@ { var columnNumber = c.ColumnNumber(); var columnName = c.FirstCell().Value.ToString(); - var xlpf = pt.Fields.Add(columnName); + IXLPivotField xlpf; + if (pt.Fields.Contains(columnName)) + xlpf = pt.Fields.Get(columnName); + else + xlpf = pt.Fields.Add(columnName); var field = pt.RowLabels.Union(pt.ColumnLabels).Union(pt.ReportFilters).FirstOrDefault(f => f.SourceName == columnName); @@ -2112,6 +2148,23 @@ IXLPivotField labelField = null; var pf = new PivotField { ShowAll = false, Name = xlpf.CustomName }; + switch (pt.Subtotals) + { + case XLPivotSubtotals.DoNotShow: + pf.DefaultSubtotal = false; + break; + + case XLPivotSubtotals.AtBottom: + pf.DefaultSubtotal = true; + pf.SubtotalTop = false; + break; + + case XLPivotSubtotals.AtTop: + pf.DefaultSubtotal = true; + pf.SubtotalTop = true; + break; + } + if (pt.RowLabels.Any(p => p.SourceName == xlpf.SourceName)) { labelField = pt.RowLabels.Single(p => p.SourceName == xlpf.SourceName); @@ -2135,7 +2188,7 @@ var fieldItems = new Items(); - if (xlpf.SharedStrings.Count > 0) + if (xlpf.SharedStrings.Any()) { for (uint i = 0; i < xlpf.SharedStrings.Count; i++) { @@ -2146,7 +2199,7 @@ } } - if (xlpf.Subtotals.Count > 0) + if (xlpf.Subtotals.Any()) { foreach (var subtotal in xlpf.Subtotals) { @@ -2211,13 +2264,17 @@ fieldItems.AppendChild(itemSubtotal); } } - else + // If the field itself doesn't have subtotals, but the pivot table is set to show pivot tables, add the default item + else if (pt.Subtotals != XLPivotSubtotals.DoNotShow) { fieldItems.AppendChild(new Item { ItemType = ItemValues.Default }); } - fieldItems.Count = Convert.ToUInt32(fieldItems.Count()); - pf.AppendChild(fieldItems); + if (fieldItems.Any()) + { + fieldItems.Count = Convert.ToUInt32(fieldItems.Count()); + pf.AppendChild(fieldItems); + } pivotFields.AppendChild(pf); } @@ -2494,7 +2551,7 @@ StrokeWeight = String.Format(CultureInfo.InvariantCulture, "{0}pt", c.Comment.Style.ColorsAndLines.LineWeight), InsetMode = c.Comment.Style.Margins.Automatic ? InsetMarginValues.Auto : InsetMarginValues.Custom }; - if (!XLHelper.IsNullOrWhiteSpace(c.Comment.Style.Web.AlternateText)) + if (!String.IsNullOrWhiteSpace(c.Comment.Style.Web.AlternateText)) shape.Alternate = c.Comment.Style.Web.AlternateText; return shape; @@ -3048,7 +3105,7 @@ { var differentialFormat = new DifferentialFormat(); differentialFormat.Append(GetNewFont(new FontInfo { Font = cf.Style.Font as XLFont }, false)); - if (!XLHelper.IsNullOrWhiteSpace(cf.Style.NumberFormat.Format)) + if (!String.IsNullOrWhiteSpace(cf.Style.NumberFormat.Format)) { var numberFormat = new NumberingFormat { @@ -3692,7 +3749,7 @@ { var newXLNumberFormat = new XLNumberFormat(); - if (nf.FormatCode != null && !XLHelper.IsNullOrWhiteSpace(nf.FormatCode.Value)) + if (nf.FormatCode != null && !String.IsNullOrWhiteSpace(nf.FormatCode.Value)) newXLNumberFormat.Format = nf.FormatCode.Value; else if (nf.NumberFormatId != null) newXLNumberFormat.NumberFormatId = (Int32)nf.NumberFormatId.Value; @@ -4092,15 +4149,15 @@ cm.SetElement(XLWSContentManager.XLWSContents.SheetData, sheetData); var lastRow = 0; - var sheetDataRows = + var existingSheetDataRows = sheetData.Elements().ToDictionary(r => r.RowIndex == null ? ++lastRow : (Int32)r.RowIndex.Value, r => r); foreach ( var r in - xlWorksheet.Internals.RowsCollection.Deleted.Where(r => sheetDataRows.ContainsKey(r.Key))) + xlWorksheet.Internals.RowsCollection.Deleted.Where(r => existingSheetDataRows.ContainsKey(r.Key))) { - sheetData.RemoveChild(sheetDataRows[r.Key]); - sheetDataRows.Remove(r.Key); + sheetData.RemoveChild(existingSheetDataRows[r.Key]); + existingSheetDataRows.Remove(r.Key); xlWorksheet.Internals.CellsCollection.deleted.Remove(r.Key); } @@ -4109,28 +4166,10 @@ foreach (var distinctRow in distinctRows.OrderBy(r => r)) { Row row; - if (sheetDataRows.ContainsKey(distinctRow)) - row = sheetDataRows[distinctRow]; + if (existingSheetDataRows.ContainsKey(distinctRow)) + row = existingSheetDataRows[distinctRow]; else - { row = new Row { RowIndex = (UInt32)distinctRow }; - if (noRows) - { - sheetData.AppendChild(row); - noRows = false; - } - else - { - if (sheetDataRows.Any(r => r.Key > row.RowIndex.Value)) - { - var minRow = sheetDataRows.Where(r => r.Key > (Int32)row.RowIndex.Value).Min(r => r.Key); - var rowBeforeInsert = sheetDataRows[minRow]; - sheetData.InsertBefore(row, rowBeforeInsert); - } - else - sheetData.AppendChild(row); - } - } if (maxColumn > 0) row.Spans = new ListValue { InnerText = "1:" + maxColumn.ToInvariantString() }; @@ -4184,104 +4223,158 @@ xlWorksheet.Internals.CellsCollection.deleted.Remove(kpDel.Key); } - if (!xlWorksheet.Internals.CellsCollection.RowsCollection.ContainsKey(distinctRow)) continue; - - var isNewRow = !row.Elements().Any(); - lastCell = 0; - var mRows = row.Elements().ToDictionary(c => XLHelper.GetColumnNumberFromAddress(c.CellReference == null - ? (XLHelper.GetColumnLetterFromNumber(++lastCell) + distinctRow) : c.CellReference.Value), c => c); - foreach (var xlCell in xlWorksheet.Internals.CellsCollection.RowsCollection[distinctRow].Values - .OrderBy(c => c.Address.ColumnNumber) - .Select(c => c)) + if (xlWorksheet.Internals.CellsCollection.RowsCollection.ContainsKey(distinctRow)) { - var styleId = context.SharedStyles[xlCell.GetStyleId()].StyleId; - var cellReference = (xlCell.Address).GetTrimmedAddress(); - var isEmpty = xlCell.IsEmpty(true); - - Cell cell = null; - if (cellsByReference.ContainsKey(cellReference)) + var isNewRow = !row.Elements().Any(); + lastCell = 0; + var mRows = row.Elements().ToDictionary(c => XLHelper.GetColumnNumberFromAddress(c.CellReference == null + ? (XLHelper.GetColumnLetterFromNumber(++lastCell) + distinctRow) : c.CellReference.Value), c => c); + foreach (var xlCell in xlWorksheet.Internals.CellsCollection.RowsCollection[distinctRow].Values + .OrderBy(c => c.Address.ColumnNumber) + .Select(c => c)) { - cell = cellsByReference[cellReference]; - if (isEmpty) - { - cell.Remove(); - } - } + XLTableField field = null; - if (!isEmpty) - { - if (cell == null) - { - cell = new Cell(); - cell.CellReference = new StringValue(cellReference); + var styleId = context.SharedStyles[xlCell.GetStyleId()].StyleId; + var cellReference = (xlCell.Address).GetTrimmedAddress(); - if (isNewRow) - row.AppendChild(cell); - else + // For saving cells to file, ignore conditional formatting. They just bloat the file + var isEmpty = xlCell.IsEmpty(true, false); + + Cell cell = null; + if (cellsByReference.ContainsKey(cellReference)) + { + cell = cellsByReference[cellReference]; + if (isEmpty) { - var newColumn = XLHelper.GetColumnNumberFromAddress(cellReference); + cell.Remove(); + } + } - Cell cellBeforeInsert = null; - int[] lastCo = { Int32.MaxValue }; - foreach (var c in mRows.Where(kp => kp.Key > newColumn).Where(c => lastCo[0] > c.Key)) - { - cellBeforeInsert = c.Value; - lastCo[0] = c.Key; - } - if (cellBeforeInsert == null) + if (!isEmpty) + { + if (cell == null) + { + cell = new Cell(); + cell.CellReference = new StringValue(cellReference); + + if (isNewRow) row.AppendChild(cell); else - row.InsertBefore(cell, cellBeforeInsert); - } - } - - cell.StyleIndex = styleId; - var formula = xlCell.FormulaA1; - if (xlCell.HasFormula) - { - if (formula.StartsWith("{")) - { - formula = formula.Substring(1, formula.Length - 2); - var f = new CellFormula { FormulaType = CellFormulaValues.Array }; - - if (xlCell.FormulaReference == null) - xlCell.FormulaReference = xlCell.AsRange().RangeAddress; - if (xlCell.FormulaReference.FirstAddress.Equals(xlCell.Address)) { - f.Text = formula; - f.Reference = xlCell.FormulaReference.ToStringRelative(); + var newColumn = XLHelper.GetColumnNumberFromAddress(cellReference); + + Cell cellBeforeInsert = null; + int[] lastCo = { Int32.MaxValue }; + foreach (var c in mRows.Where(kp => kp.Key > newColumn).Where(c => lastCo[0] > c.Key)) + { + cellBeforeInsert = c.Value; + lastCo[0] = c.Key; + } + if (cellBeforeInsert == null) + row.AppendChild(cell); + else + row.InsertBefore(cell, cellBeforeInsert); + } + } + + cell.StyleIndex = styleId; + if (xlCell.HasFormula) + { + var formula = xlCell.FormulaA1; + if (xlCell.HasArrayFormula) + { + formula = formula.Substring(1, formula.Length - 2); + var f = new CellFormula { FormulaType = CellFormulaValues.Array }; + + if (xlCell.FormulaReference == null) + xlCell.FormulaReference = xlCell.AsRange().RangeAddress; + + if (xlCell.FormulaReference.FirstAddress.Equals(xlCell.Address)) + { + f.Text = formula; + f.Reference = xlCell.FormulaReference.ToStringRelative(); + } + + cell.CellFormula = f; + } + else + { + cell.CellFormula = new CellFormula(); + cell.CellFormula.Text = formula; } - cell.CellFormula = f; + cell.CellValue = null; + } + else if (xlCell.TableCellType() == XLTableCellType.Total) + { + var table = xlWorksheet.Tables.First(t => t.AsRange().Contains(xlCell)); + field = table.Fields.First(f => f.Column.ColumnNumber() == xlCell.Address.ColumnNumber) as XLTableField; + + if (!String.IsNullOrWhiteSpace(field.TotalsRowLabel)) + { + cell.DataType = XLWorkbook.CvSharedString; + } + else + { + cell.DataType = null; + } + cell.CellFormula = null; } else { - cell.CellFormula = new CellFormula(); - cell.CellFormula.Text = formula; + cell.CellFormula = null; + cell.DataType = xlCell.DataType == XLCellValues.DateTime ? null : GetCellValueType(xlCell); } - cell.CellValue = null; + if (evaluateFormulae || field != null || !xlCell.HasFormula) + SetCellValue(xlCell, field, cell); + } + } + xlWorksheet.Internals.CellsCollection.deleted.Remove(distinctRow); + } + + // If we're adding a new row (not in sheet already and it's not "empty" + if (!existingSheetDataRows.ContainsKey(distinctRow)) + { + var invalidRow = row.Height == null + && row.CustomHeight == null + && row.Hidden == null + && row.StyleIndex == null + && row.CustomFormat == null + && row.Collapsed == null + && row.OutlineLevel == null + && !row.Elements().Any(); + + if (!invalidRow) + { + if (noRows) + { + sheetData.AppendChild(row); + noRows = false; } else { - cell.CellFormula = null; - cell.DataType = xlCell.DataType == XLCellValues.DateTime ? null : GetCellValueType(xlCell); + if (existingSheetDataRows.Any(r => r.Key > row.RowIndex.Value)) + { + var minRow = existingSheetDataRows.Where(r => r.Key > (Int32)row.RowIndex.Value).Min(r => r.Key); + var rowBeforeInsert = existingSheetDataRows[minRow]; + sheetData.InsertBefore(row, rowBeforeInsert); + } + else + sheetData.AppendChild(row); } - - if (!xlCell.HasFormula || evaluateFormulae) - SetCellValue(xlCell, cell); - } } - xlWorksheet.Internals.CellsCollection.deleted.Remove(distinctRow); } + foreach ( var r in xlWorksheet.Internals.CellsCollection.deleted.Keys.Where( - sheetDataRows.ContainsKey)) + existingSheetDataRows.ContainsKey)) { - sheetData.RemoveChild(sheetDataRows[r]); - sheetDataRows.Remove(r); + sheetData.RemoveChild(existingSheetDataRows[r]); + existingSheetDataRows.Remove(r); } #endregion SheetData @@ -4301,7 +4394,7 @@ var protection = xlWorksheet.Protection; sheetProtection.Sheet = protection.Protected; - if (!XLHelper.IsNullOrWhiteSpace(protection.PasswordHash)) + if (!String.IsNullOrWhiteSpace(protection.PasswordHash)) sheetProtection.Password = protection.PasswordHash; sheetProtection.FormatCells = GetBooleanValue(!protection.FormatCells, true); sheetProtection.FormatColumns = GetBooleanValue(!protection.FormatColumns, true); @@ -4553,7 +4646,7 @@ Display = hl.Cell.GetFormattedString() }; } - if (!XLHelper.IsNullOrWhiteSpace(hl.Tooltip)) + if (!String.IsNullOrWhiteSpace(hl.Tooltip)) hyperlink.Tooltip = hl.Tooltip; hyperlinks.AppendChild(hyperlink); } @@ -4810,7 +4903,7 @@ { worksheetPart.Worksheet.RemoveAllChildren(); { - if (!XLHelper.IsNullOrWhiteSpace(xlWorksheet.LegacyDrawingId)) + if (!String.IsNullOrWhiteSpace(xlWorksheet.LegacyDrawingId)) { var previousElement = cm.GetPreviousElementFor(XLWSContentManager.XLWSContents.LegacyDrawing); worksheetPart.Worksheet.InsertAfter(new LegacyDrawing { Id = xlWorksheet.LegacyDrawingId }, @@ -4837,8 +4930,25 @@ #endregion LegacyDrawingHeaderFooter } - private static void SetCellValue(XLCell xlCell, Cell openXmlCell) + private static void SetCellValue(XLCell xlCell, XLTableField field, Cell openXmlCell) { + if (field != null) + { + if (!String.IsNullOrWhiteSpace(field.TotalsRowLabel)) + { + var cellValue = new CellValue(); + cellValue.Text = xlCell.SharedStringId.ToString(); + openXmlCell.DataType = CvSharedString; + openXmlCell.CellValue = cellValue; + } + else if (field.TotalsRowFunction == XLTotalsRowFunction.None) + { + openXmlCell.DataType = CvSharedString; + openXmlCell.CellValue = null; + } + return; + } + if (xlCell.HasFormula) { var cellValue = new CellValue(); @@ -4890,7 +5000,7 @@ } else if (dataType == XLCellValues.DateTime || dataType == XLCellValues.Number) { - if (!XLHelper.IsNullOrWhiteSpace(xlCell.InnerText)) + if (!String.IsNullOrWhiteSpace(xlCell.InnerText)) { var cellValue = new CellValue(); cellValue.Text = Double.Parse(xlCell.InnerText, XLHelper.NumberStyle, XLHelper.ParseCulture).ToInvariantString(); diff --git a/ClosedXML/Excel/XLWorksheet.cs b/ClosedXML/Excel/XLWorksheet.cs index c28ef98..6b5348a 100644 --- a/ClosedXML/Excel/XLWorksheet.cs +++ b/ClosedXML/Excel/XLWorksheet.cs @@ -176,7 +176,7 @@ throw new ArgumentException("Worksheet names cannot contain any of the following characters: " + InvalidNameChars); - if (XLHelper.IsNullOrWhiteSpace(value)) + if (String.IsNullOrWhiteSpace(value)) throw new ArgumentException("Worksheet names cannot be empty"); if (value.Length > 31) @@ -606,11 +606,18 @@ } } - foreach (IXLNamedRange r in NamedRanges) + foreach (var nr in NamedRanges) { var ranges = new XLRanges(); - r.Ranges.ForEach(ranges.Add); - targetSheet.NamedRanges.Add(r.Name, ranges); + foreach (var r in nr.Ranges) + { + if (this == r.Worksheet) + // Named ranges on the source worksheet have to point to the new destination sheet + ranges.Add(targetSheet.Range(r.RangeAddress.FirstAddress.RowNumber, r.RangeAddress.FirstAddress.ColumnNumber, r.RangeAddress.LastAddress.RowNumber, r.RangeAddress.LastAddress.ColumnNumber)); + else + ranges.Add(r); + } + targetSheet.NamedRanges.Add(nr.Name, ranges); } foreach (XLTable t in Tables.Cast()) @@ -651,7 +658,7 @@ private String ReplaceRelativeSheet(string newSheetName, String value) { - if (XLHelper.IsNullOrWhiteSpace(value)) return value; + if (String.IsNullOrWhiteSpace(value)) return value; var newValue = new StringBuilder(); var addresses = value.Split(','); @@ -1523,6 +1530,11 @@ public String Author { get; set; } + public override string ToString() + { + return this.Name; + } + public IXLPictures Pictures { get; private set; } public IXLPicture Picture(string pictureName) @@ -1540,7 +1552,12 @@ return Pictures.Add(stream, name); } - public Drawings.IXLPicture AddPicture(Stream stream, XLPictureFormat format) + internal IXLPicture AddPicture(Stream stream, string name, int Id) + { + return (Pictures as XLPictures).Add(stream, name, Id); + } + + public IXLPicture AddPicture(Stream stream, XLPictureFormat format) { return Pictures.Add(stream, format); } @@ -1569,5 +1586,25 @@ { return Pictures.Add(imageFile, name); } + public override Boolean IsEntireRow() + { + return true; + } + + public override Boolean IsEntireColumn() + { + return true; + } + + internal void SetValue(T value, int ro, int co) where T : class + { + if (value == null) + this.Cell(ro, co).SetValue(String.Empty); + else if (value is IConvertible) + this.Cell(ro, co).SetValue((T)Convert.ChangeType(value, typeof(T))); + else + this.Cell(ro, co).SetValue(value); + } + } } diff --git a/ClosedXML/Excel/XLWorksheets.cs b/ClosedXML/Excel/XLWorksheets.cs index 4d14cb7..9061e1d 100644 --- a/ClosedXML/Excel/XLWorksheets.cs +++ b/ClosedXML/Excel/XLWorksheets.cs @@ -77,18 +77,18 @@ if (wss.Any()) return wss.First().Value; - throw new Exception("There isn't a worksheet named '" + sheetName + "'."); + throw new ArgumentException("There isn't a worksheet named '" + sheetName + "'."); } public IXLWorksheet Worksheet(Int32 position) { int wsCount = _worksheets.Values.Count(w => w.Position == position); if (wsCount == 0) - throw new Exception("There isn't a worksheet associated with that position."); + throw new ArgumentException("There isn't a worksheet associated with that position."); if (wsCount > 1) { - throw new Exception( + throw new ArgumentException( "Can't retrieve a worksheet because there are multiple worksheets associated with that position."); } @@ -130,14 +130,14 @@ { int wsCount = _worksheets.Values.Count(w => w.Position == position); if (wsCount == 0) - throw new Exception("There isn't a worksheet associated with that index."); + throw new ArgumentException("There isn't a worksheet associated with that index."); if (wsCount > 1) - throw new Exception( + throw new ArgumentException( "Can't delete the worksheet because there are multiple worksheets associated with that index."); var ws = _worksheets.Values.Single(w => w.Position == position); - if (!XLHelper.IsNullOrWhiteSpace(ws.RelId) && !Deleted.Contains(ws.RelId)) + if (!String.IsNullOrWhiteSpace(ws.RelId) && !Deleted.Contains(ws.RelId)) Deleted.Add(ws.RelId); _worksheets.RemoveAll(w => w.Position == position); @@ -178,9 +178,10 @@ public void Rename(String oldSheetName, String newSheetName) { - if (XLHelper.IsNullOrWhiteSpace(oldSheetName) || !_worksheets.ContainsKey(oldSheetName)) return; + if (String.IsNullOrWhiteSpace(oldSheetName) || !_worksheets.ContainsKey(oldSheetName)) return; - if (_worksheets.Any(ws1 => ws1.Key.Equals(newSheetName, StringComparison.OrdinalIgnoreCase))) + if (!oldSheetName.Equals(newSheetName, StringComparison.OrdinalIgnoreCase) + && _worksheets.Any(ws1 => ws1.Key.Equals(newSheetName, StringComparison.OrdinalIgnoreCase))) throw new ArgumentException(String.Format("A worksheet with the same name ({0}) has already been added.", newSheetName), nameof(newSheetName)); var ws = _worksheets[oldSheetName]; diff --git a/ClosedXML/Extensions.cs b/ClosedXML/Extensions.cs index 7655168..699d251 100644 --- a/ClosedXML/Extensions.cs +++ b/ClosedXML/Extensions.cs @@ -221,7 +221,7 @@ public static Double GetWidth(this IXLFontBase fontBase, String text, Dictionary fontCache) { - if (XLHelper.IsNullOrWhiteSpace(text)) + if (String.IsNullOrWhiteSpace(text)) return 0; var font = GetCachedFont(fontBase, fontCache); diff --git a/ClosedXML/Properties/AssemblyVersionInfo.cs b/ClosedXML/Properties/AssemblyVersionInfo.cs index d9a81ab..57466c5 100644 --- a/ClosedXML/Properties/AssemblyVersionInfo.cs +++ b/ClosedXML/Properties/AssemblyVersionInfo.cs @@ -7,6 +7,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("0.88.0.0")] -[assembly: AssemblyFileVersion("0.88.0.0")] -[assembly: AssemblyInformationalVersion("0.88.0-beta")] +[assembly: AssemblyVersion("0.89.0.0")] +[assembly: AssemblyFileVersion("0.89.0.0")] +[assembly: AssemblyInformationalVersion("0.89.0-beta1")] diff --git a/ClosedXML/XLHelper.cs b/ClosedXML/XLHelper.cs index 585b8ca..dd08d44 100644 --- a/ClosedXML/XLHelper.cs +++ b/ClosedXML/XLHelper.cs @@ -117,7 +117,7 @@ public static bool IsValidColumn(string column) { var length = column.Length; - if (IsNullOrWhiteSpace(column) || length > 3) + if (String.IsNullOrWhiteSpace(column) || length > 3) return false; var theColumn = column.ToUpper(); @@ -153,7 +153,7 @@ public static bool IsValidA1Address(string address) { - if (IsNullOrWhiteSpace(address)) + if (String.IsNullOrWhiteSpace(address)) return false; address = address.Replace("$", ""); @@ -226,26 +226,6 @@ return rows; } - public static bool IsNullOrWhiteSpace(string value) - { -#if NET4 - return String.IsNullOrWhiteSpace(value); -#else - if (value != null) - { - var length = value.Length; - for (int i = 0; i < length; i++) - { - if (!char.IsWhiteSpace(value[i])) - { - return false; - } - } - } - return true; -#endif - } - private static readonly Regex A1RegexRelative = new Regex( @"(?<=\W)(?\$?[a-zA-Z]{1,3}\$?\d{1,7})(?=\W)" // A1 + @"|(?<=\W)(?\$?\d{1,7}:\$?\d{1,7})(?=\W)" // 1:1 diff --git a/ClosedXML_Examples/ClosedXML_Examples.csproj b/ClosedXML_Examples/ClosedXML_Examples.csproj index 9f4871b..a870a50 100644 --- a/ClosedXML_Examples/ClosedXML_Examples.csproj +++ b/ClosedXML_Examples/ClosedXML_Examples.csproj @@ -105,7 +105,6 @@ - @@ -118,7 +117,6 @@ - @@ -169,6 +167,9 @@ + + + diff --git a/ClosedXML_Examples/Creating/CreateFiles.cs b/ClosedXML_Examples/Creating/CreateFiles.cs index 1e639dd..8f1978f 100644 --- a/ClosedXML_Examples/Creating/CreateFiles.cs +++ b/ClosedXML_Examples/Creating/CreateFiles.cs @@ -4,6 +4,7 @@ using ClosedXML_Examples.Ranges; using ClosedXML_Examples.Rows; using ClosedXML_Examples.Styles; +using ClosedXML_Examples.Tables; using System.IO; namespace ClosedXML_Examples @@ -67,6 +68,7 @@ new RowCells().Create(Path.Combine(path, "RowCells.xlsx")); new FreezePanes().Create(Path.Combine(path, "FreezePanes.xlsx")); new UsingTables().Create(Path.Combine(path, "UsingTables.xlsx")); + new ResizingTables().Create(Path.Combine(path, "ResizingTables.xlsx")); new AddingRowToTables().Create(Path.Combine(path, "AddingRowToTables.xlsx")); new RightToLeft().Create(Path.Combine(path, "RightToLeft.xlsx")); new ShowCase().Create(Path.Combine(path, "ShowCase.xlsx")); diff --git a/ClosedXML_Examples/Misc/AddingDataSet.cs b/ClosedXML_Examples/Misc/AddingDataSet.cs index 11c4b9c..a8d1e68 100644 --- a/ClosedXML_Examples/Misc/AddingDataSet.cs +++ b/ClosedXML_Examples/Misc/AddingDataSet.cs @@ -1,45 +1,11 @@ +using ClosedXML.Excel; using System; using System.Data; -using ClosedXML.Excel; namespace ClosedXML_Examples.Misc { public class AddingDataSet : IXLExample { - #region Variables - - // Public - - // Private - - - #endregion - - #region Properties - - // Public - - // Private - - // Override - - - #endregion - - #region Events - - // Public - - // Private - - // Override - - - #endregion - - #region Methods - - // Public public void Create(String filePath) { var wb = new XLWorkbook(); @@ -52,7 +18,6 @@ wb.SaveAs(filePath); } - // Private private DataSet GetDataSet() { var ds = new DataSet(); @@ -78,9 +43,5 @@ table.Rows.Add(100, "Dilantin", "Melanie", new DateTime(2000, 1, 5)); return table; } - // Override - - - #endregion } } diff --git a/ClosedXML_Examples/Misc/Collections.cs b/ClosedXML_Examples/Misc/Collections.cs index 39b416d..a9b9fe5 100644 --- a/ClosedXML_Examples/Misc/Collections.cs +++ b/ClosedXML_Examples/Misc/Collections.cs @@ -68,7 +68,7 @@ var dataTable = GetTable(); ws.Cell(6, 1).Value = "DataTable"; ws.Range(6, 1, 6, 4).Merge().AddToNamed("Titles"); - ws.Cell(7, 1).Value = dataTable.AsEnumerable(); + ws.Cell(7, 1).Value = dataTable; // From a query var list = new List(); @@ -83,8 +83,7 @@ ws.Cell(6, 6).Value = "Query"; ws.Range(6, 6, 6, 8).Merge().AddToNamed("Titles"); - ws.Cell(7, 6).Value = people.AsEnumerable(); // Very Important to call the AsEnumerable method - // otherwise it won't be copied. + ws.Cell(7, 6).Value = people; // Prepare the style for the titles @@ -92,7 +91,7 @@ titlesStyle.Font.Bold = true; titlesStyle.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; titlesStyle.Fill.BackgroundColor = XLColor.Cyan; - + // Format all titles in one shot wb.NamedRanges.NamedRange("Titles").Ranges.Style = titlesStyle; diff --git a/ClosedXML_Examples/Misc/CopyingWorksheets.cs b/ClosedXML_Examples/Misc/CopyingWorksheets.cs index b0843eb..e06cd66 100644 --- a/ClosedXML_Examples/Misc/CopyingWorksheets.cs +++ b/ClosedXML_Examples/Misc/CopyingWorksheets.cs @@ -1,6 +1,6 @@ -using System.IO; using ClosedXML.Excel; -using ClosedXML_Examples.Ranges; +using ClosedXML_Examples.Tables; +using System.IO; namespace ClosedXML_Examples.Misc { @@ -18,7 +18,7 @@ var wsSource = wb.Worksheet(1); // Copy the worksheet to a new sheet in this workbook wsSource.CopyTo("Copy"); - + // We're going to open another workbook to show that you can // copy a sheet from one workbook to another: new BasicTable().Create(tempFile2); @@ -40,6 +40,5 @@ } } } - } } diff --git a/ClosedXML_Examples/Misc/Formulas.cs b/ClosedXML_Examples/Misc/Formulas.cs index 729cb9e..8066a81 100644 --- a/ClosedXML_Examples/Misc/Formulas.cs +++ b/ClosedXML_Examples/Misc/Formulas.cs @@ -54,6 +54,7 @@ // Just put the formula between curly braces ws.Cell("A6").Value = "Array Formula: "; ws.Cell("B6").FormulaA1 = "{A2+A3}"; + ws.Range("C6:D6").FormulaA1 = "{TRANSPOSE(A2:A3)}"; ws.Range(1, 1, 1, 7).Style.Fill.BackgroundColor = XLColor.Cyan; ws.Range(1, 1, 1, 7).Style.Font.Bold = true; diff --git a/ClosedXML_Examples/Misc/InsertingData.cs b/ClosedXML_Examples/Misc/InsertingData.cs index d0ade19..626a9ec 100644 --- a/ClosedXML_Examples/Misc/InsertingData.cs +++ b/ClosedXML_Examples/Misc/InsertingData.cs @@ -1,8 +1,8 @@ +using ClosedXML.Excel; using System; using System.Collections.Generic; using System.Data; using System.Linq; -using ClosedXML.Excel; namespace ClosedXML_Examples.Misc { @@ -13,72 +13,82 @@ // Public public void Create(String filePath) { - var wb = new XLWorkbook(); - var ws = wb.Worksheets.Add("Inserting Data"); + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Inserting Data"); - // From a list of strings - var listOfStrings = new List(); - listOfStrings.Add("House"); - listOfStrings.Add("001"); - ws.Cell(1, 1).Value = "From Strings"; - ws.Cell(1, 1).AsRange().AddToNamed("Titles"); - ws.Cell(2, 1).InsertData(listOfStrings); + // From a list of strings + var listOfStrings = new List(); + listOfStrings.Add("House"); + listOfStrings.Add("001"); + ws.Cell(1, 1).Value = "From Strings"; + ws.Cell(1, 1).AsRange().AddToNamed("Titles"); + ws.Cell(2, 1).InsertData(listOfStrings); - // From a list of arrays - var listOfArr = new List(); - listOfArr.Add(new Int32[] { 1, 2, 3 }); - listOfArr.Add(new Int32[] { 1 }); - listOfArr.Add(new Int32[] { 1, 2, 3, 4, 5, 6 }); - ws.Cell(1, 3).Value = "From Arrays"; - ws.Range(1, 3, 1, 8).Merge().AddToNamed("Titles"); - ws.Cell(2, 3).InsertData(listOfArr); + // From a list of arrays + var listOfArr = new List(); + listOfArr.Add(new Int32[] { 1, 2, 3 }); + listOfArr.Add(new Int32[] { 1 }); + listOfArr.Add(new Int32[] { 1, 2, 3, 4, 5, 6 }); + ws.Cell(1, 3).Value = "From Arrays"; + ws.Range(1, 3, 1, 8).Merge().AddToNamed("Titles"); + ws.Cell(2, 3).InsertData(listOfArr); - // From a DataTable - var dataTable = GetTable(); - ws.Cell(6, 1).Value = "From DataTable"; - ws.Range(6, 1, 6, 4).Merge().AddToNamed("Titles"); - ws.Cell(7, 1).InsertData(dataTable.AsEnumerable()); + // From a DataTable + var dataTable = GetTable(); + ws.Cell(6, 1).Value = "From DataTable"; + ws.Range(6, 1, 6, 4).Merge().AddToNamed("Titles"); + ws.Cell(7, 1).InsertData(dataTable); - // From a query - var list = new List(); - list.Add(new Person() { Name = "John", Age = 30, House = "On Elm St." }); - list.Add(new Person() { Name = "Mary", Age = 15, House = "On Main St." }); - list.Add(new Person() { Name = "Luis", Age = 21, House = "On 23rd St." }); - list.Add(new Person() { Name = "Henry", Age = 45, House = "On 5th Ave." }); + // From a query + var list = new List(); + list.Add(new Person() { Name = "John", Age = 30, House = "On Elm St." }); + list.Add(new Person() { Name = "Mary", Age = 15, House = "On Main St." }); + list.Add(new Person() { Name = "Luis", Age = 21, House = "On 23rd St." }); + list.Add(new Person() { Name = "Henry", Age = 45, House = "On 5th Ave." }); - var people = from p in list - where p.Age >= 21 - select new { p.Name, p.House, p.Age }; + var people = from p in list + where p.Age >= 21 + select new { p.Name, p.House, p.Age }; - ws.Cell(6, 6).Value = "From Query"; - ws.Range(6, 6, 6, 8).Merge().AddToNamed("Titles"); - ws.Cell(7, 6).InsertData(people.AsEnumerable()); + ws.Cell(6, 6).Value = "From Query"; + ws.Range(6, 6, 6, 8).Merge().AddToNamed("Titles"); + ws.Cell(7, 6).InsertData(people); - // Prepare the style for the titles - var titlesStyle = wb.Style; - titlesStyle.Font.Bold = true; - titlesStyle.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; - titlesStyle.Fill.BackgroundColor = XLColor.Cyan; - - // Format all titles in one shot - wb.NamedRanges.NamedRange("Titles").Ranges.Style = titlesStyle; + ws.Cell(11, 6).Value = "From List"; + ws.Range(11, 6, 11, 9).Merge().AddToNamed("Titles"); + ws.Cell(12, 6).InsertData(list); - ws.Columns().AdjustToContents(); + ws.Cell("A13").Value = "Transposed"; + ws.Range(13, 1, 13, 3).Merge().AddToNamed("Titles"); + ws.Cell("A14").InsertData(people.AsEnumerable(), true); - wb.SaveAs(filePath); + // Prepare the style for the titles + var titlesStyle = wb.Style; + titlesStyle.Font.Bold = true; + titlesStyle.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + titlesStyle.Fill.BackgroundColor = XLColor.Cyan; + + // Format all titles in one shot + wb.NamedRanges.NamedRange("Titles").Ranges.Style = titlesStyle; + + ws.Columns().AdjustToContents(); + + wb.SaveAs(filePath); + } } - class Person + private class Person { public String House { get; set; } public String Name { get; set; } public Int32 Age { get; set; } + public static String ClassType { get { return nameof(Person); } } } // Private private DataTable GetTable() { - DataTable table = new DataTable(); table.Columns.Add("Dosage", typeof(int)); table.Columns.Add("Drug", typeof(string)); @@ -92,9 +102,9 @@ table.Rows.Add(100, "Dilantin", "Melanie", new DateTime(2000, 1, 5)); return table; } + // Override - - #endregion + #endregion Methods } } diff --git a/ClosedXML_Examples/Misc/InsertingTables.cs b/ClosedXML_Examples/Misc/InsertingTables.cs deleted file mode 100644 index 50be801..0000000 --- a/ClosedXML_Examples/Misc/InsertingTables.cs +++ /dev/null @@ -1,102 +0,0 @@ -using ClosedXML.Attributes; -using ClosedXML.Excel; -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; - -namespace ClosedXML_Examples.Misc -{ - public class InsertingTables : IXLExample - { - #region Methods - - // Public - public void Create(String filePath) - { - var wb = new XLWorkbook(); - var ws = wb.Worksheets.Add("Inserting Tables"); - - // From a list of strings - var listOfStrings = new List(); - listOfStrings.Add("House"); - listOfStrings.Add("Car"); - ws.Cell(1, 1).Value = "From Strings"; - ws.Cell(1, 1).AsRange().AddToNamed("Titles"); - ws.Cell(2, 1).InsertTable(listOfStrings); - - // From a list of arrays - var listOfArr = new List(); - listOfArr.Add(new Int32[] { 1, 2, 3 }); - listOfArr.Add(new Int32[] { 1 }); - listOfArr.Add(new Int32[] { 1, 2, 3, 4, 5, 6 }); - ws.Cell(1, 3).Value = "From Arrays"; - ws.Range(1, 3, 1, 8).Merge().AddToNamed("Titles"); - ws.Cell(2, 3).InsertTable(listOfArr); - - // From a DataTable - var dataTable = GetTable(); - ws.Cell(7, 1).Value = "From DataTable"; - ws.Range(7, 1, 7, 4).Merge().AddToNamed("Titles"); - ws.Cell(8, 1).InsertTable(dataTable.AsEnumerable()); - - // From a query - var list = new List(); - list.Add(new Person() { Name = "John", Age = 30, House = "On Elm St." }); - list.Add(new Person() { Name = "Mary", Age = 15, House = "On Main St." }); - list.Add(new Person() { Name = "Luis", Age = 21, House = "On 23rd St." }); - list.Add(new Person() { Name = "Henry", Age = 45, House = "On 5th Ave." }); - - var people = from p in list - where p.Age >= 21 - select p; - - ws.Cell(7, 6).Value = "From Query"; - ws.Range(7, 6, 7, 8).Merge().AddToNamed("Titles"); - ws.Cell(8, 6).InsertTable(people.AsEnumerable()); - - // Prepare the style for the titles - var titlesStyle = wb.Style; - titlesStyle.Font.Bold = true; - titlesStyle.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; - titlesStyle.Fill.BackgroundColor = XLColor.Cyan; - - // Format all titles in one shot - wb.NamedRanges.NamedRange("Titles").Ranges.Style = titlesStyle; - - ws.Columns().AdjustToContents(); - - wb.SaveAs(filePath); - } - - private class Person - { - [XLColumn(Header = "House Street")] - public String House { get; set; } - - public String Name { get; set; } - public Int32 Age { get; set; } - } - - // Private - private DataTable GetTable() - { - DataTable table = new DataTable(); - table.Columns.Add("Dosage", typeof(int)); - table.Columns.Add("Drug", typeof(string)); - table.Columns.Add("Patient", typeof(string)); - table.Columns.Add("Date", typeof(DateTime)); - - table.Rows.Add(25, "Indocin", "David", new DateTime(2000, 1, 1)); - table.Rows.Add(50, "Enebrel", "Sam", new DateTime(2000, 1, 2)); - table.Rows.Add(10, "Hydralazine", "Christoff", new DateTime(2000, 1, 3)); - table.Rows.Add(21, "Combivent", "Janet", new DateTime(2000, 1, 4)); - table.Rows.Add(100, "Dilantin", "Melanie", new DateTime(2000, 1, 5)); - return table; - } - - // Override - - #endregion Methods - } -} diff --git a/ClosedXML_Examples/PivotTables/PivotTables.cs b/ClosedXML_Examples/PivotTables/PivotTables.cs index 9eb24ea..fa479da 100644 --- a/ClosedXML_Examples/PivotTables/PivotTables.cs +++ b/ClosedXML_Examples/PivotTables/PivotTables.cs @@ -60,6 +60,8 @@ IXLWorksheet ptSheet; IXLPivotTable pt; + #region Pivots + for (int i = 1; i <= 3; i++) { // Add a new sheet for our pivot table @@ -95,7 +97,10 @@ ptSheet.Columns().AdjustToContents(); } - // Different kind of pivot + #endregion Pivots + + #region Different kind of pivot + ptSheet = wb.Worksheets.Add("pvtNoColumnLabels"); pt = ptSheet.PivotTables.AddNew("pvtNoColumnLabels", ptSheet.Cell(1, 1), dataRange); @@ -105,8 +110,10 @@ pt.Values.Add("NumberOfOrders").SetSummaryFormula(XLPivotSummary.Sum); pt.Values.Add("Quality").SetSummaryFormula(XLPivotSummary.Sum); + #endregion Different kind of pivot - // Pivot table with collapsed fields + #region Pivot table with collapsed fields + ptSheet = wb.Worksheets.Add("pvtCollapsedFields"); pt = ptSheet.PivotTables.AddNew("pvtCollapsedFields", ptSheet.Cell(1, 1), dataRange); @@ -116,8 +123,10 @@ pt.Values.Add("NumberOfOrders").SetSummaryFormula(XLPivotSummary.Sum); pt.Values.Add("Quality").SetSummaryFormula(XLPivotSummary.Sum); + #endregion Pivot table with collapsed fields - // Pivot table with a field both as a value and as a row/column/filter label + #region Pivot table with a field both as a value and as a row/column/filter label + ptSheet = wb.Worksheets.Add("pvtFieldAsValueAndLabel"); pt = ptSheet.PivotTables.AddNew("pvtFieldAsValueAndLabel", ptSheet.Cell(1, 1), dataRange); @@ -126,6 +135,37 @@ pt.Values.Add("Name").SetSummaryFormula(XLPivotSummary.Count);//.NumberFormat.Format = "#0.00"; + #endregion Pivot table with a field both as a value and as a row/column/filter label + + #region Pivot table with subtotals disabled + + ptSheet = wb.Worksheets.Add("pvtHideSubTotals"); + + // Create the pivot table, using the data from the "PastrySalesData" table + pt = ptSheet.PivotTables.AddNew("pvtHidesubTotals", ptSheet.Cell(1, 1), dataRange); + + // The rows in our pivot table will be the names of the pastries + pt.RowLabels.Add(XLConstants.PivotTableValuesSentinalLabel); + + // The columns will be the months + pt.ColumnLabels.Add("Month"); + pt.ColumnLabels.Add("Name"); + + // The values in our table will come from the "NumberOfOrders" field + // The default calculation setting is a total of each row/column + pt.Values.Add("NumberOfOrders", "NumberOfOrdersPercentageOfBearclaw") + .ShowAsPercentageFrom("Name").And("Bearclaw") + .NumberFormat.Format = "0%"; + + pt.Values.Add("Quality", "Sum of Quality") + .NumberFormat.SetFormat("#,##0.00"); + + pt.Subtotals = XLPivotSubtotals.DoNotShow; + + ptSheet.Columns().AdjustToContents(); + + #endregion Pivot table with subtotals disabled + wb.SaveAs(filePath); } } diff --git a/ClosedXML_Examples/Ranges/UsingTables.cs b/ClosedXML_Examples/Ranges/UsingTables.cs deleted file mode 100644 index e5c24b2..0000000 --- a/ClosedXML_Examples/Ranges/UsingTables.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.IO; -using ClosedXML.Excel; -using System.Linq; - - -namespace ClosedXML_Examples.Ranges -{ - public class UsingTables : IXLExample - { - #region Methods - - // Public - public void Create(String filePath) - { - string tempFile = ExampleHelper.GetTempFilePath(filePath); - try - { - new BasicTable().Create(tempFile); - var wb = new XLWorkbook(tempFile); - var ws = wb.Worksheet(1); - ws.Name = "Contacts Table"; - var firstCell = ws.FirstCellUsed(); - var lastCell = ws.LastCellUsed(); - var range = ws.Range(firstCell.Address, lastCell.Address); - range.FirstRow().Delete(); // Deleting the "Contacts" header (we don't need it for our purposes) - - // We want to use a theme for table, not the hard coded format of the BasicTable - range.Clear(XLClearOptions.Formats); - // Put back the date and number formats - range.Column(4).Style.NumberFormat.NumberFormatId = 15; - range.Column(5).Style.NumberFormat.Format = "$ #,##0"; - - var table = range.CreateTable(); // You can also use range.AsTable() if you want to - // manipulate the range as a table but don't want - // to create the table in the worksheet. - - // Let's activate the Totals row and add the sum of Income - table.ShowTotalsRow = true; - table.Field("Income").TotalsRowFunction = XLTotalsRowFunction.Sum; - // Just for fun let's add the text "Sum Of Income" to the totals row - table.Field(0).TotalsRowLabel = "Sum Of Income"; - - // Copy all the headers - Int32 columnWithHeaders = lastCell.Address.ColumnNumber + 2; - Int32 currentRow = table.RangeAddress.FirstAddress.RowNumber; - ws.Cell(currentRow, columnWithHeaders).Value = "Table Headers"; - foreach (var cell in table.HeadersRow().Cells()) - { - currentRow++; - ws.Cell(currentRow, columnWithHeaders).Value = cell.Value; - } - - // Format the headers as a table with a different style and no autofilters - var htFirstCell = ws.Cell(table.RangeAddress.FirstAddress.RowNumber, columnWithHeaders); - var htLastCell = ws.Cell(currentRow, columnWithHeaders); - var headersTable = ws.Range(htFirstCell, htLastCell).CreateTable("Headers"); - headersTable.Theme = XLTableTheme.TableStyleLight10; - headersTable.ShowAutoFilter = false; - - // Add a custom formula to the headersTable - headersTable.ShowTotalsRow = true; - headersTable.Field(0).TotalsRowFormulaA1 = "CONCATENATE(\"Count: \", CountA(Headers[Table Headers]))"; - - // Copy the names - Int32 columnWithNames = columnWithHeaders + 2; - currentRow = table.RangeAddress.FirstAddress.RowNumber; // reset the currentRow - ws.Cell(currentRow, columnWithNames).Value = "Names"; - foreach (var row in table.DataRange.Rows()) - { - currentRow++; - var fName = row.Field("FName").GetString(); // Notice how we're calling the cell by field name - var lName = row.Field("LName").GetString(); // Notice how we're calling the cell by field name - var name = String.Format("{0} {1}", fName, lName); - ws.Cell(currentRow, columnWithNames).Value = name; - } - - // Format the names as a table with a different style and no autofilters - var ntFirstCell = ws.Cell(table.RangeAddress.FirstAddress.RowNumber, columnWithNames); - var ntLastCell = ws.Cell(currentRow, columnWithNames); - var namesTable = ws.Range(ntFirstCell, ntLastCell).CreateTable(); - namesTable.Theme = XLTableTheme.TableStyleLight12; - namesTable.ShowAutoFilter = false; - - ws.Columns().AdjustToContents(); - ws.Columns("A,G,I").Width = 3; - - wb.SaveAs(filePath); - } - finally - { - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - } - } - - // Private - - // Override - - - #endregion - } -} diff --git a/ClosedXML_Examples/Styles/UsingPhonetics.cs b/ClosedXML_Examples/Styles/UsingPhonetics.cs index eaf2e32..f6c6483 100644 --- a/ClosedXML_Examples/Styles/UsingPhonetics.cs +++ b/ClosedXML_Examples/Styles/UsingPhonetics.cs @@ -23,8 +23,10 @@ // And then we add the phonetics cell.RichText.Phonetics.SetFontSize(8); - cell.RichText.Phonetics.Add("げん", 7, 1); - cell.RichText.Phonetics.Add("き", 8, 1); + cell.RichText.Phonetics.Add("げん", 7, 8); + cell.RichText.Phonetics.Add("き", 8, 9); + + //TODO: I'm looking for someone who understands Japanese to confirm the validity of the above code. wb.SaveAs(filePath); } diff --git a/ClosedXML_Examples/Tables/InsertingTables.cs b/ClosedXML_Examples/Tables/InsertingTables.cs new file mode 100644 index 0000000..176eed1 --- /dev/null +++ b/ClosedXML_Examples/Tables/InsertingTables.cs @@ -0,0 +1,111 @@ +using ClosedXML.Attributes; +using ClosedXML.Excel; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; + +namespace ClosedXML_Examples.Tables +{ + public class InsertingTables : IXLExample + { + #region Methods + + // Public + public void Create(String filePath) + { + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Inserting Tables"); + + // From a list of strings + var listOfStrings = new List(); + listOfStrings.Add("House"); + listOfStrings.Add("Car"); + ws.Cell(1, 1).Value = "From Strings"; + ws.Cell(1, 1).AsRange().AddToNamed("Titles"); + ws.Cell(2, 1).InsertTable(listOfStrings); + + // From a list of arrays + var listOfArr = new List(); + listOfArr.Add(new Int32[] { 1, 2, 3 }); + listOfArr.Add(new Int32[] { 1 }); + listOfArr.Add(new Int32[] { 1, 2, 3, 4, 5, 6 }); + ws.Cell(1, 3).Value = "From Arrays"; + ws.Range(1, 3, 1, 8).Merge().AddToNamed("Titles"); + ws.Cell(2, 3).InsertTable(listOfArr); + + // From a DataTable + var dataTable = GetTable(); + ws.Cell(7, 1).Value = "From DataTable"; + ws.Range(7, 1, 7, 4).Merge().AddToNamed("Titles"); + ws.Cell(8, 1).InsertTable(dataTable); + + // From a query + var list = new List(); + list.Add(new Person() { Name = "John", Age = 30, House = "On Elm St." }); + list.Add(new Person() { Name = "Mary", Age = 15, House = "On Main St." }); + list.Add(new Person() { Name = "Luis", Age = 21, House = "On 23rd St." }); + list.Add(new Person() { Name = "Henry", Age = 45, House = "On 5th Ave." }); + + var people = from p in list + where p.Age >= 21 + select p; + + ws.Cell(7, 6).Value = "From Query"; + ws.Range(7, 6, 7, 9).Merge().AddToNamed("Titles"); + ws.Cell(8, 6).InsertTable(people); + + ws.Cell(15, 6).Value = "From List"; + ws.Range(15, 6, 15, 9).Merge().AddToNamed("Titles"); + ws.Cell(16, 6).InsertTable(people); + + // Prepare the style for the titles + var titlesStyle = wb.Style; + titlesStyle.Font.Bold = true; + titlesStyle.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + titlesStyle.Fill.BackgroundColor = XLColor.Cyan; + + // Format all titles in one shot + wb.NamedRanges.NamedRange("Titles").Ranges.Style = titlesStyle; + + ws.Columns().AdjustToContents(); + + wb.SaveAs(filePath); + } + } + + private class Person + { + [XLColumn(Header = "House Street")] + public String House { get; set; } + + public String Name { get; set; } + public Int32 Age { get; set; } + + [XLColumn(Header = "Class Type")] + public static String ClassType { get { return nameof(Person); } } + } + + // Private + private DataTable GetTable() + { + DataTable table = new DataTable(); + table.Columns.Add("Dosage", typeof(int)); + table.Columns.Add("Drug", typeof(string)); + table.Columns.Add("Patient", typeof(string)); + table.Columns.Add("Date", typeof(DateTime)); + + table.Rows.Add(25, "Indocin", "David", new DateTime(2000, 1, 1)); + table.Rows.Add(50, "Enebrel", "Sam", new DateTime(2000, 1, 2)); + table.Rows.Add(10, "Hydralazine", "Christoff", new DateTime(2000, 1, 3)); + table.Rows.Add(21, "Combivent", "Janet", new DateTime(2000, 1, 4)); + table.Rows.Add(100, "Dilantin", "Melanie", new DateTime(2000, 1, 5)); + return table; + } + + // Override + + #endregion Methods + } +} diff --git a/ClosedXML_Examples/Tables/ResizingTables.cs b/ClosedXML_Examples/Tables/ResizingTables.cs new file mode 100644 index 0000000..64ad34e --- /dev/null +++ b/ClosedXML_Examples/Tables/ResizingTables.cs @@ -0,0 +1,47 @@ +using ClosedXML.Excel; +using System; +using System.Linq; + +// TODO: Add example to Wiki + +namespace ClosedXML_Examples.Tables +{ + public class ResizingTables : IXLExample + { + public void Create(string filePath) + { + using (var wb = new XLWorkbook()) + { + var ws1 = wb.AddWorksheet("Sheet1"); + + var data1 = Enumerable.Range(1, 10) + .Select(i => + new + { + Index = i, + Character = Convert.ToChar(64 + i), + String = new String('a', i), + Integer = 64 + i + }); + + var table1 = ws1.Cell("B2").InsertTable(data1, true) + .SetShowHeaderRow() + .SetShowTotalsRow(); + + table1.Fields.First().TotalsRowLabel = "Sum of Integer"; + table1.Fields.Last().TotalsRowFunction = XLTotalsRowFunction.Sum; + + var ws2 = ws1.CopyTo("Sheet2"); + var table2 = ws2.Tables.First(); + table2.Resize(table2.FirstCell(), table2.LastCell().CellLeft().CellAbove(3)); + + var ws3 = ws2.CopyTo("Sheet3"); + var table3 = ws3.Tables.First(); + table3.Resize(table3.FirstCell().CellLeft(), table3.LastCell().CellRight().CellBelow(1)); + + wb.Worksheets.ForEach(ws => ws.Columns().AdjustToContents()); + wb.SaveAs(filePath); + } + } + } +} diff --git a/ClosedXML_Examples/Tables/UsingTables.cs b/ClosedXML_Examples/Tables/UsingTables.cs new file mode 100644 index 0000000..639a0a0 --- /dev/null +++ b/ClosedXML_Examples/Tables/UsingTables.cs @@ -0,0 +1,105 @@ +using ClosedXML.Excel; +using System; +using System.IO; + +namespace ClosedXML_Examples.Tables +{ + public class UsingTables : IXLExample + { + #region Methods + + // Public + public void Create(String filePath) + { + string tempFile = ExampleHelper.GetTempFilePath(filePath); + try + { + new BasicTable().Create(tempFile); + using (var wb = new XLWorkbook(tempFile)) + { + var ws = wb.Worksheet(1); + ws.Name = "Contacts Table"; + var firstCell = ws.FirstCellUsed(); + var lastCell = ws.LastCellUsed(); + var range = ws.Range(firstCell.Address, lastCell.Address); + range.FirstRow().Delete(); // Deleting the "Contacts" header (we don't need it for our purposes) + + // We want to use a theme for table, not the hard coded format of the BasicTable + range.Clear(XLClearOptions.Formats); + // Put back the date and number formats + range.Column(4).Style.NumberFormat.NumberFormatId = 15; + range.Column(5).Style.NumberFormat.Format = "$ #,##0"; + + var table = range.CreateTable(); // You can also use range.AsTable() if you want to + // manipulate the range as a table but don't want + // to create the table in the worksheet. + + // Let's activate the Totals row and add the sum of Income + table.ShowTotalsRow = true; + table.Field("Income").TotalsRowFunction = XLTotalsRowFunction.Sum; + // Just for fun let's add the text "Sum Of Income" to the totals row + table.Field(0).TotalsRowLabel = "Sum Of Income"; + + // Copy all the headers + Int32 columnWithHeaders = lastCell.Address.ColumnNumber + 2; + Int32 currentRow = table.RangeAddress.FirstAddress.RowNumber; + ws.Cell(currentRow, columnWithHeaders).Value = "Table Headers"; + foreach (var cell in table.HeadersRow().Cells()) + { + currentRow++; + ws.Cell(currentRow, columnWithHeaders).Value = cell.Value; + } + + // Format the headers as a table with a different style and no autofilters + var htFirstCell = ws.Cell(table.RangeAddress.FirstAddress.RowNumber, columnWithHeaders); + var htLastCell = ws.Cell(currentRow, columnWithHeaders); + var headersTable = ws.Range(htFirstCell, htLastCell).CreateTable("Headers"); + headersTable.Theme = XLTableTheme.TableStyleLight10; + headersTable.ShowAutoFilter = false; + + // Add a custom formula to the headersTable + headersTable.ShowTotalsRow = true; + headersTable.Field(0).TotalsRowFormulaA1 = "CONCATENATE(\"Count: \", CountA(Headers[Table Headers]))"; + + // Copy the names + Int32 columnWithNames = columnWithHeaders + 2; + currentRow = table.RangeAddress.FirstAddress.RowNumber; // reset the currentRow + ws.Cell(currentRow, columnWithNames).Value = "Names"; + foreach (var row in table.DataRange.Rows()) + { + currentRow++; + var fName = row.Field("FName").GetString(); // Notice how we're calling the cell by field name + var lName = row.Field("LName").GetString(); // Notice how we're calling the cell by field name + var name = String.Format("{0} {1}", fName, lName); + ws.Cell(currentRow, columnWithNames).Value = name; + } + + // Format the names as a table with a different style and no autofilters + var ntFirstCell = ws.Cell(table.RangeAddress.FirstAddress.RowNumber, columnWithNames); + var ntLastCell = ws.Cell(currentRow, columnWithNames); + var namesTable = ws.Range(ntFirstCell, ntLastCell).CreateTable(); + namesTable.Theme = XLTableTheme.TableStyleLight12; + namesTable.ShowAutoFilter = false; + + ws.Columns().AdjustToContents(); + ws.Columns("A,G,I").Width = 3; + + wb.SaveAs(filePath); + } + } + finally + { + if (File.Exists(tempFile)) + { + File.Delete(tempFile); + } + } + } + + // Private + + // Override + + #endregion Methods + } +} diff --git a/ClosedXML_Tests/ClosedXML_Tests.csproj b/ClosedXML_Tests/ClosedXML_Tests.csproj index 6879598..28a64f7 100644 --- a/ClosedXML_Tests/ClosedXML_Tests.csproj +++ b/ClosedXML_Tests/ClosedXML_Tests.csproj @@ -76,14 +76,19 @@ + + + + + @@ -91,6 +96,7 @@ + @@ -169,7 +175,6 @@ - @@ -219,7 +224,6 @@ - @@ -285,7 +289,13 @@ - + + + + + + + diff --git a/ClosedXML_Tests/Examples/MiscTests.cs b/ClosedXML_Tests/Examples/MiscTests.cs index 4f14453..d74a9fc 100644 --- a/ClosedXML_Tests/Examples/MiscTests.cs +++ b/ClosedXML_Tests/Examples/MiscTests.cs @@ -133,11 +133,6 @@ TestHelper.RunTestExample(@"Misc\InsertingData.xlsx"); } - [Test] - public void InsertingTables() - { - TestHelper.RunTestExample(@"Misc\InsertingTables.xlsx"); - } [Test] public void LambdaExpressions() @@ -204,11 +199,11 @@ { TestHelper.RunTestExample(@"Misc\WorkbookProperties.xlsx"); } - + [Test] public void WorkbookProtection() { TestHelper.RunTestExample(@"Misc\WorkbookProtection.xlsx"); } } -} \ No newline at end of file +} diff --git a/ClosedXML_Tests/Examples/RangesTests.cs b/ClosedXML_Tests/Examples/RangesTests.cs index fe3e1e2..59cd62b 100644 --- a/ClosedXML_Tests/Examples/RangesTests.cs +++ b/ClosedXML_Tests/Examples/RangesTests.cs @@ -99,12 +99,6 @@ } [Test] - public void UsingTables() - { - TestHelper.RunTestExample(@"Ranges\UsingTables.xlsx"); - } - - [Test] public void AddingRowToTables() { TestHelper.RunTestExample(@"Ranges\AddingRowToTables.xlsx"); @@ -116,4 +110,4 @@ TestHelper.RunTestExample(@"Ranges\WalkingRanges.xlsx"); } } -} \ No newline at end of file +} diff --git a/ClosedXML_Tests/Examples/StylesTests.cs b/ClosedXML_Tests/Examples/StylesTests.cs index 4ecfb15..c400a81 100644 --- a/ClosedXML_Tests/Examples/StylesTests.cs +++ b/ClosedXML_Tests/Examples/StylesTests.cs @@ -67,9 +67,15 @@ } [Test] + public void UsingPhonetics() + { + TestHelper.RunTestExample(@"Styles\UsingPhonetics.xlsx"); + } + + [Test] public void UsingRichText() { TestHelper.RunTestExample(@"Styles\UsingRichText.xlsx"); } } -} \ No newline at end of file +} diff --git a/ClosedXML_Tests/Examples/TablesTests.cs b/ClosedXML_Tests/Examples/TablesTests.cs new file mode 100644 index 0000000..934bf65 --- /dev/null +++ b/ClosedXML_Tests/Examples/TablesTests.cs @@ -0,0 +1,27 @@ +using ClosedXML_Examples.Tables; +using NUnit.Framework; + +namespace ClosedXML_Tests.Examples +{ + [TestFixture] + public class TablesTests + { + [Test] + public void InsertingTables() + { + TestHelper.RunTestExample(@"Tables\InsertingTables.xlsx"); + } + + [Test] + public void ResizingTables() + { + TestHelper.RunTestExample(@"Tables\ResizingTables.xlsx"); + } + + [Test] + public void UsingTables() + { + TestHelper.RunTestExample(@"Tables\UsingTables.xlsx"); + } + } +} diff --git a/ClosedXML_Tests/Excel/AutoFilters/AutoFilterTests.cs b/ClosedXML_Tests/Excel/AutoFilters/AutoFilterTests.cs index c630f5a..25dff84 100644 --- a/ClosedXML_Tests/Excel/AutoFilters/AutoFilterTests.cs +++ b/ClosedXML_Tests/Excel/AutoFilters/AutoFilterTests.cs @@ -29,7 +29,7 @@ listOfArr.Add(6); table.DataRange.InsertRowsBelow(listOfArr.Count - table.DataRange.RowCount()); - table.DataRange.FirstCell().InsertData(listOfArr.AsEnumerable()); + table.DataRange.FirstCell().InsertData(listOfArr); Assert.AreEqual("A1:A5", table.AutoFilter.Range.RangeAddress.ToStringRelative()); } @@ -73,4 +73,4 @@ Assert.That(!ws.AutoFilter.Enabled); } } -} \ No newline at end of file +} diff --git a/ClosedXML_Tests/Excel/CalcEngine/CalcEngineExceptionTests.cs b/ClosedXML_Tests/Excel/CalcEngine/CalcEngineExceptionTests.cs new file mode 100644 index 0000000..b1be35f --- /dev/null +++ b/ClosedXML_Tests/Excel/CalcEngine/CalcEngineExceptionTests.cs @@ -0,0 +1,29 @@ +using ClosedXML.Excel; +using ClosedXML.Excel.CalcEngine.Exceptions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; + +namespace ClosedXML_Tests.Excel.CalcEngine +{ + [TestFixture] + public class CalcEngineExceptionTests + { + [OneTimeSetUp] + public void SetCultureInfo() + { + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); + } + + [Test] + public void InvalidCharNumber() + { + Assert.Throws(() => XLWorkbook.EvaluateExpr("CHAR(-2)")); + Assert.Throws(() => XLWorkbook.EvaluateExpr("CHAR(270)")); + } + } +} diff --git a/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs b/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs index 3fca48e..3ad7f8e 100644 --- a/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs +++ b/ClosedXML_Tests/Excel/CalcEngine/FunctionsTests.cs @@ -620,7 +620,7 @@ Assert.AreEqual(0, cell.Value); cell = wb.Worksheet(1).Cell(3, 1).SetFormulaA1("=SUM(D1,D2)"); Assert.AreEqual(0, cell.Value); - Assert.That(() => wb.Worksheet(1).Cell(3, 1).SetFormulaA1("=AVERAGE(D1,D2)").Value, Throws.Exception); + Assert.That(() => wb.Worksheet(1).Cell(3, 1).SetFormulaA1("=AVERAGE(D1,D2)").Value, Throws.TypeOf()); } [Test] diff --git a/ClosedXML_Tests/Excel/CalcEngine/InformationTests.cs b/ClosedXML_Tests/Excel/CalcEngine/InformationTests.cs index 93ad203..19de6ca 100644 --- a/ClosedXML_Tests/Excel/CalcEngine/InformationTests.cs +++ b/ClosedXML_Tests/Excel/CalcEngine/InformationTests.cs @@ -158,6 +158,17 @@ } #endregion IsLogical Tests + [Test] + public void IsNA() + { + object actual; + actual = XLWorkbook.EvaluateExpr("ISNA(#N/A)"); + Assert.AreEqual(true, actual); + + actual = XLWorkbook.EvaluateExpr("ISNA(#REF!)"); + Assert.AreEqual(false, actual); + } + #region IsNotText Tests [Test] @@ -288,6 +299,30 @@ } #endregion IsOdd Test + [Test] + public void IsRef() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet"); + ws.Cell("A1").Value = "123"; + + ws.Cell("B1").FormulaA1 = "ISREF(A1)"; + ws.Cell("B2").FormulaA1 = "ISREF(5)"; + ws.Cell("B3").FormulaA1 = "ISREF(YEAR(TODAY()))"; + + bool actual; + actual = ws.Cell("B1").GetValue(); + Assert.AreEqual(true, actual); + + actual = ws.Cell("B2").GetValue(); + Assert.AreEqual(false, actual); + + actual = ws.Cell("B3").GetValue(); + Assert.AreEqual(false, actual); + } + } + #region IsText Tests [Test] diff --git a/ClosedXML_Tests/Excel/CalcEngine/LookupTests.cs b/ClosedXML_Tests/Excel/CalcEngine/LookupTests.cs index a112f3d..a18e509 100644 --- a/ClosedXML_Tests/Excel/CalcEngine/LookupTests.cs +++ b/ClosedXML_Tests/Excel/CalcEngine/LookupTests.cs @@ -1,4 +1,5 @@ using ClosedXML.Excel; +using ClosedXML.Excel.CalcEngine.Exceptions; using NUnit.Framework; using System; @@ -106,6 +107,12 @@ value = workbook.Evaluate("=VLOOKUP(3,Data!$B$2:$I$71,8,TRUE)"); Assert.AreEqual(179.64, value); + value = workbook.Evaluate("=VLOOKUP(3,Data!$B$2:$I$71,8)"); + Assert.AreEqual(179.64, value); + + value = workbook.Evaluate("=VLOOKUP(3,Data!$B$2:$I$71,8,)"); + Assert.AreEqual(179.64, value); + value = workbook.Evaluate("=VLOOKUP(14.5,Data!$B$2:$I$71,8,TRUE)"); Assert.AreEqual(174.65, value); @@ -116,11 +123,11 @@ [Test] public void Vlookup_Exceptions() { - Assert.That(() => workbook.Evaluate(@"=VLOOKUP("""",Data!$B$2:$I$71,3,FALSE)"), Throws.Exception); - Assert.That(() => workbook.Evaluate(@"=VLOOKUP(50,Data!$B$2:$I$71,3,FALSE)"), Throws.Exception); - Assert.That(() => workbook.Evaluate(@"=VLOOKUP(20,Data!$B$2:$I$71,9,FALSE)"), Throws.Exception); + Assert.Throws(() => workbook.Evaluate(@"=VLOOKUP("""",Data!$B$2:$I$71,3,FALSE)")); + Assert.Throws(() => workbook.Evaluate(@"=VLOOKUP(50,Data!$B$2:$I$71,3,FALSE)")); + Assert.Throws(() => workbook.Evaluate(@"=VLOOKUP(-1,Data!$B$2:$I$71,2,TRUE)")); - Assert.That(() => workbook.Evaluate(@"=VLOOKUP(-1,Data!$B$2:$I$71,9,TRUE)"), Throws.Exception); + Assert.Throws(() => workbook.Evaluate(@"=VLOOKUP(20,Data!$B$2:$I$71,9,FALSE)")); } } } diff --git a/ClosedXML_Tests/Excel/CalcEngine/MathTrigTests.cs b/ClosedXML_Tests/Excel/CalcEngine/MathTrigTests.cs new file mode 100644 index 0000000..5012bb5 --- /dev/null +++ b/ClosedXML_Tests/Excel/CalcEngine/MathTrigTests.cs @@ -0,0 +1,123 @@ +using ClosedXML.Excel; +using NUnit.Framework; +using System; + +namespace ClosedXML_Tests.Excel.CalcEngine +{ + [TestFixture] + public class MathTrigTests + { + private readonly double tolerance = 1e-10; + + [Test] + public void Floor() + { + Object actual; + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(1.2)"); + Assert.AreEqual(1, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(1.7)"); + Assert.AreEqual(1, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(-1.7)"); + Assert.AreEqual(-2, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(1.2, 1)"); + Assert.AreEqual(1, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(1.7, 1)"); + Assert.AreEqual(1, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(-1.7, 1)"); + Assert.AreEqual(-2, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(0.4, 2)"); + Assert.AreEqual(0, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(2.7, 2)"); + Assert.AreEqual(2, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(7.8, 2)"); + Assert.AreEqual(6, actual); + + actual = XLWorkbook.EvaluateExpr(@"FLOOR(-5.5, -2)"); + Assert.AreEqual(-4, actual); + } + + [Test] + // Functions have to support a period first before we can implement this + public void FloorMath() + { + double actual; + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(24.3, 5)"); + Assert.AreEqual(20, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(6.7)"); + Assert.AreEqual(6, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(-8.1, 2)"); + Assert.AreEqual(-10, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(5.5, 2.1, 0)"); + Assert.AreEqual(4.2, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(5.5, -2.1, 0)"); + Assert.AreEqual(4.2, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(5.5, 2.1, -1)"); + Assert.AreEqual(4.2, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(5.5, -2.1, -1)"); + Assert.AreEqual(4.2, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(-5.5, 2.1, 0)"); + Assert.AreEqual(-6.3, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(-5.5, -2.1, 0)"); + Assert.AreEqual(-6.3, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(-5.5, 2.1, -1)"); + Assert.AreEqual(-4.2, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"FLOOR.MATH(-5.5, -2.1, -1)"); + Assert.AreEqual(-4.2, actual, tolerance); + } + + [Test] + public void Mod() + { + double actual; + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(1.5, 1)"); + Assert.AreEqual(0.5, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(3, 2)"); + Assert.AreEqual(1, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(-3, 2)"); + Assert.AreEqual(1, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(3, -2)"); + Assert.AreEqual(-1, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(-3, -2)"); + Assert.AreEqual(-1, actual, tolerance); + + ////// + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(-4.3, -0.5)"); + Assert.AreEqual(-0.3, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(6.9, -0.2)"); + Assert.AreEqual(-0.1, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(0.7, 0.6)"); + Assert.AreEqual(0.1, actual, tolerance); + + actual = (double)XLWorkbook.EvaluateExpr(@"MOD(6.2, 1.1)"); + Assert.AreEqual(0.7, actual, tolerance); + } + } +} diff --git a/ClosedXML_Tests/Excel/CalcEngine/StatisticalTests.cs b/ClosedXML_Tests/Excel/CalcEngine/StatisticalTests.cs index c59e34d..2550868 100644 --- a/ClosedXML_Tests/Excel/CalcEngine/StatisticalTests.cs +++ b/ClosedXML_Tests/Excel/CalcEngine/StatisticalTests.cs @@ -22,7 +22,7 @@ value = ws.Evaluate("AVERAGE(G3:G45)").CastTo(); Assert.AreEqual(49.3255814, value, tolerance); - Assert.That(() => ws.Evaluate("AVERAGE(D3:D45)"), Throws.Exception); + Assert.That(() => ws.Evaluate("AVERAGE(D3:D45)"), Throws.TypeOf()); } [Test] @@ -146,7 +146,7 @@ { var ws = workbook.Worksheets.First(); double value; - Assert.That(() => ws.Evaluate(@"=STDEV(D3:D45)"), Throws.Exception); + Assert.That(() => ws.Evaluate(@"=STDEV(D3:D45)"), Throws.TypeOf()); value = ws.Evaluate(@"=STDEV(H3:H45)").CastTo(); Assert.AreEqual(47.34511769, value, tolerance); @@ -163,7 +163,7 @@ { var ws = workbook.Worksheets.First(); double value; - Assert.That(() => ws.Evaluate(@"=STDEVP(D3:D45)"), Throws.Exception); + Assert.That(() => ws.Evaluate(@"=STDEVP(D3:D45)"), Throws.InvalidOperationException); value = ws.Evaluate(@"=STDEVP(H3:H45)").CastTo(); Assert.AreEqual(46.79135458, value, tolerance); @@ -180,7 +180,7 @@ { var ws = workbook.Worksheets.First(); double value; - Assert.That(() => ws.Evaluate(@"=VAR(D3:D45)"), Throws.Exception); + Assert.That(() => ws.Evaluate(@"=VAR(D3:D45)"), Throws.InvalidOperationException); value = ws.Evaluate(@"=VAR(H3:H45)").CastTo(); Assert.AreEqual(2241.560169, value, tolerance); @@ -197,7 +197,7 @@ { var ws = workbook.Worksheets.First(); double value; - Assert.That(() => ws.Evaluate(@"=VARP(D3:D45)"), Throws.Exception); + Assert.That(() => ws.Evaluate(@"=VARP(D3:D45)"), Throws.InvalidOperationException); value = ws.Evaluate(@"=VARP(H3:H45)").CastTo(); Assert.AreEqual(2189.430863, value, tolerance); diff --git a/ClosedXML_Tests/Excel/CalcEngine/TextTests.cs b/ClosedXML_Tests/Excel/CalcEngine/TextTests.cs index 3a89de2..59d0a59 100644 --- a/ClosedXML_Tests/Excel/CalcEngine/TextTests.cs +++ b/ClosedXML_Tests/Excel/CalcEngine/TextTests.cs @@ -1,4 +1,6 @@ using ClosedXML.Excel; +using ClosedXML.Excel.CalcEngine; +using ClosedXML.Excel.CalcEngine.Exceptions; using NUnit.Framework; using System; using System.Globalization; @@ -19,13 +21,13 @@ [Test] public void Char_Empty_Input_String() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Char("""")"), Throws.Exception); + Assert.Throws(() => XLWorkbook.EvaluateExpr(@"Char("""")")); } [Test] public void Char_Input_Too_Large() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Char(9797)"), Throws.Exception); + Assert.Throws< CellValueException>(() => XLWorkbook.EvaluateExpr(@"Char(9797)")); } [Test] @@ -56,7 +58,7 @@ public void Code_Empty_Input_String() { // Todo: more specific exception - ValueException? - Assert.That(() => XLWorkbook.EvaluateExpr(@"Code("""")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Code("""")"), Throws.TypeOf()); } [Test] @@ -82,7 +84,7 @@ [Test] public void Dollar_Empty_Input_String() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Dollar("", 3)"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Dollar("", 3)"), Throws.TypeOf()); } [Test] @@ -121,26 +123,26 @@ [Test] public void Find_Start_Position_Too_Large() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Find(""abc"", ""abcdef"", 10)"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Find(""abc"", ""abcdef"", 10)"), Throws.TypeOf()); } [Test] public void Find_String_In_Another_Empty_String() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Find(""abc"", """")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Find(""abc"", """")"), Throws.TypeOf()); } [Test] public void Find_String_Not_Found() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Find(""123"", ""asdf"")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Find(""123"", ""asdf"")"), Throws.TypeOf()); } [Test] public void Find_Case_Sensitive_String_Not_Found() { // Find is case-sensitive - Assert.That(() => XLWorkbook.EvaluateExpr(@"Find(""excel"", ""Microsoft Excel 2010"")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Find(""excel"", ""Microsoft Excel 2010"")"), Throws.TypeOf()); } [Test] @@ -159,7 +161,7 @@ [Test] public void Fixed_Input_Is_String() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Fixed(""asdf"")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Fixed(""asdf"")"), Throws.TypeOf()); } [Test] @@ -297,7 +299,7 @@ [Test] public void Rept_Start_Is_Negative() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Rept(""Francois"", -1)"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Rept(""Francois"", -1)"), Throws.TypeOf()); } [Test] @@ -344,7 +346,7 @@ [Test] public void Search_No_Parameters_With_Values() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Search("""", """")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Search("""", """")"), Throws.TypeOf()); } [Test] @@ -357,31 +359,31 @@ [Test] public void Search_Start_Position_Too_Large() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""abc"", ""abcdef"", 10)"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""abc"", ""abcdef"", 10)"), Throws.TypeOf()); } [Test] public void Search_Empty_Input_String() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""abc"", """")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""abc"", """")"), Throws.TypeOf()); } [Test] public void Search_String_Not_Found() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""123"", ""asdf"")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""123"", ""asdf"")"), Throws.TypeOf()); } [Test] public void Search_Wildcard_String_Not_Found() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""soft?2010"", ""Microsoft Excel 2010"")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""soft?2010"", ""Microsoft Excel 2010"")"), Throws.TypeOf()); } [Test] public void Search_Start_Position_Too_Large2() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""text"", ""This is some text"", 15)"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Search(""text"", ""This is some text"", 15)"), Throws.TypeOf()); } // http://www.excel-easy.com/examples/find-vs-search.html @@ -517,7 +519,7 @@ [Test] public void Value_Input_String_Is_Not_A_Number() { - Assert.That(() => XLWorkbook.EvaluateExpr(@"Value(""asdf"")"), Throws.Exception); + Assert.That(() => XLWorkbook.EvaluateExpr(@"Value(""asdf"")"), Throws.TypeOf()); } [Test] diff --git a/ClosedXML_Tests/Excel/Cells/XLCellTests.cs b/ClosedXML_Tests/Excel/Cells/XLCellTests.cs index 31e66f9..cfa1951 100644 --- a/ClosedXML_Tests/Excel/Cells/XLCellTests.cs +++ b/ClosedXML_Tests/Excel/Cells/XLCellTests.cs @@ -59,7 +59,7 @@ IXLCell cell = ws.Cell("A1"); var doubleList = new List { 1.0 / 0.0 }; - cell.Value = doubleList.AsEnumerable(); + cell.Value = doubleList; Assert.AreNotEqual(XLCellValues.Number, cell.DataType); } @@ -70,7 +70,7 @@ IXLCell cell = ws.Cell("A1"); var doubleList = new List { 0.0 / 0.0 }; - cell.Value = doubleList.AsEnumerable(); + cell.Value = doubleList; Assert.AreNotEqual(XLCellValues.Number, cell.DataType); } @@ -83,6 +83,22 @@ } [Test] + public void InsertData2() + { + IXLWorksheet ws = new XLWorkbook().Worksheets.Add("Sheet1"); + IXLRange range = ws.Cell(2, 2).InsertData(new[] { "a", "b", "c" }, false); + Assert.AreEqual("Sheet1!B2:B4", range.ToString()); + } + + [Test] + public void InsertData3() + { + IXLWorksheet ws = new XLWorkbook().Worksheets.Add("Sheet1"); + IXLRange range = ws.Cell(2, 2).InsertData(new[] { "a", "b", "c" }, true); + Assert.AreEqual("Sheet1!B2:D2", range.ToString()); + } + + [Test] public void IsEmpty1() { IXLWorksheet ws = new XLWorkbook().Worksheets.Add("Sheet1"); @@ -388,5 +404,27 @@ Assert.AreEqual("\u0018", wb.Worksheets.First().FirstCell().Value); } } + + [Test] + public void CanClearCellValueBySettingNullValue() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet1"); + var cell = ws.FirstCell(); + + cell.Value = "Test"; + Assert.AreEqual("Test", cell.Value); + Assert.AreEqual(XLCellValues.Text, cell.DataType); + + string s = null; + cell.SetValue(s); + Assert.AreEqual(string.Empty, cell.Value); + + cell.Value = "Test"; + cell.Value = null; + Assert.AreEqual(string.Empty, cell.Value); + } + } } } diff --git a/ClosedXML_Tests/Excel/Coordinates/XLAddressTests.cs b/ClosedXML_Tests/Excel/Coordinates/XLAddressTests.cs index 4d0329f..686c1f4 100644 --- a/ClosedXML_Tests/Excel/Coordinates/XLAddressTests.cs +++ b/ClosedXML_Tests/Excel/Coordinates/XLAddressTests.cs @@ -9,13 +9,14 @@ [Test] public void ToStringTest() { - IXLWorksheet ws = new XLWorkbook().Worksheets.Add("Sheet1"); + var ws = new XLWorkbook().Worksheets.Add("Sheet1"); IXLAddress address = ws.Cell(1, 1).Address; Assert.AreEqual("A1", address.ToString()); Assert.AreEqual("A1", address.ToString(XLReferenceStyle.A1)); Assert.AreEqual("R1C1", address.ToString(XLReferenceStyle.R1C1)); Assert.AreEqual("A1", address.ToString(XLReferenceStyle.Default)); + Assert.AreEqual("Sheet1!A1", address.ToString(XLReferenceStyle.Default, true)); Assert.AreEqual("A1", address.ToStringRelative()); Assert.AreEqual("Sheet1!A1", address.ToStringRelative(true)); @@ -39,6 +40,7 @@ Assert.AreEqual("A1", address.ToString(XLReferenceStyle.A1)); Assert.AreEqual("R1C1", address.ToString(XLReferenceStyle.R1C1)); Assert.AreEqual("A1", address.ToString(XLReferenceStyle.Default)); + Assert.AreEqual("'Sheet 1'!A1", address.ToString(XLReferenceStyle.Default, true)); Assert.AreEqual("A1", address.ToStringRelative()); Assert.AreEqual("'Sheet 1'!A1", address.ToStringRelative(true)); diff --git a/ClosedXML_Tests/Excel/ImageHandling/PictureTests.cs b/ClosedXML_Tests/Excel/ImageHandling/PictureTests.cs index b0ba0d6..a06902e 100644 --- a/ClosedXML_Tests/Excel/ImageHandling/PictureTests.cs +++ b/ClosedXML_Tests/Excel/ImageHandling/PictureTests.cs @@ -145,6 +145,35 @@ } [Test] + public void TestDefaultIds() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet1"); + + using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ClosedXML_Tests.Resource.Images.ImageHandling.png")) + { + ws.AddPicture(stream, XLPictureFormat.Png); + stream.Position = 0; + + ws.AddPicture(stream, XLPictureFormat.Png); + stream.Position = 0; + + ws.AddPicture(stream, XLPictureFormat.Png).Name = "Picture 4"; + stream.Position = 0; + + ws.AddPicture(stream, XLPictureFormat.Png); + stream.Position = 0; + } + + Assert.AreEqual(1, ws.Pictures.Skip(0).First().Id); + Assert.AreEqual(2, ws.Pictures.Skip(1).First().Id); + Assert.AreEqual(3, ws.Pictures.Skip(2).First().Id); + Assert.AreEqual(4, ws.Pictures.Skip(3).First().Id); + } + } + + [Test] public void XLMarkerTests() { IXLWorksheet ws = new XLWorkbook().Worksheets.Add("Sheet1"); diff --git a/ClosedXML_Tests/Excel/Loading/LoadingTests.cs b/ClosedXML_Tests/Excel/Loading/LoadingTests.cs index 218ae90..5b1fc42 100644 --- a/ClosedXML_Tests/Excel/Loading/LoadingTests.cs +++ b/ClosedXML_Tests/Excel/Loading/LoadingTests.cs @@ -1,6 +1,8 @@ using ClosedXML.Excel; using ClosedXML.Excel.Drawings; +using ClosedXML_Tests.Utils; using NUnit.Framework; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -27,7 +29,10 @@ @"Misc\InvalidPrintTitles.xlsx", @"Misc\ExcelProducedWorkbookWithImages.xlsx", @"Misc\EmptyCellValue.xlsx", - @"Misc\AllShapes.xlsx" + @"Misc\AllShapes.xlsx", + @"Misc\TableHeadersWithLineBreaks.xlsx", + @"Misc\TableWithNameNull.xlsx", + @"Misc\DuplicateImageNames.xlsx" }; foreach (var file in files) @@ -178,5 +183,26 @@ wb.SaveAs(ms, true); } } + + [Test] + public void CanLoadFromTemplate() + { + using (var tf1 = new TemporaryFile()) + using (var tf2 = new TemporaryFile()) + { + using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"Misc\AllShapes.xlsx"))) + using (var wb = new XLWorkbook(stream)) + { + // Save as temporary file + wb.SaveAs(tf1.Path); + } + + var workbook = XLWorkbook.OpenFromTemplate(tf1.Path); + Assert.True(workbook.Worksheets.Any()); + Assert.Throws(() => workbook.Save()); + + workbook.SaveAs(tf2.Path); + } + } } } diff --git a/ClosedXML_Tests/Excel/Misc/FormulaTests.cs b/ClosedXML_Tests/Excel/Misc/FormulaTests.cs index 8d2d470..3ff4c55 100644 --- a/ClosedXML_Tests/Excel/Misc/FormulaTests.cs +++ b/ClosedXML_Tests/Excel/Misc/FormulaTests.cs @@ -1,4 +1,5 @@ using ClosedXML.Excel; +using ClosedXML.Excel.CalcEngine.Exceptions; using NUnit.Framework; using System; using System.Linq; @@ -156,5 +157,34 @@ Assert.AreEqual(6, actual); } } + + [Test] + public void FormulaThatStartsWithEqualsAndPlus() + { + object actual; + actual = XLWorkbook.EvaluateExpr("=MID(\"This is a test\", 6, 2)"); + Assert.AreEqual("is", actual); + + actual = XLWorkbook.EvaluateExpr("=+MID(\"This is a test\", 6, 2)"); + Assert.AreEqual("is", actual); + + actual = XLWorkbook.EvaluateExpr("=+++++MID(\"This is a test\", 6, 2)"); + Assert.AreEqual("is", actual); + + actual = XLWorkbook.EvaluateExpr("+MID(\"This is a test\", 6, 2)"); + Assert.AreEqual("is", actual); + } + + [Test] + public void FormulasWithErrors() + { + Assert.Throws(() => XLWorkbook.EvaluateExpr("YEAR(#REF!)")); + Assert.Throws(() => XLWorkbook.EvaluateExpr("YEAR(#VALUE!)")); + Assert.Throws(() => XLWorkbook.EvaluateExpr("YEAR(#DIV/0!)")); + Assert.Throws(() => XLWorkbook.EvaluateExpr("YEAR(#NAME?)")); + Assert.Throws(() => XLWorkbook.EvaluateExpr("YEAR(#N/A)")); + Assert.Throws(() => XLWorkbook.EvaluateExpr("YEAR(#NULL!)")); + Assert.Throws(() => XLWorkbook.EvaluateExpr("YEAR(#NUM!)")); + } } } diff --git a/ClosedXML_Tests/Excel/Misc/HyperlinkTests.cs b/ClosedXML_Tests/Excel/Misc/HyperlinkTests.cs new file mode 100644 index 0000000..95b2431 --- /dev/null +++ b/ClosedXML_Tests/Excel/Misc/HyperlinkTests.cs @@ -0,0 +1,32 @@ +using ClosedXML.Excel; +using NUnit.Framework; + +namespace ClosedXML_Tests.Excel.Misc +{ + [TestFixture] + public class HyperlinkTests + { + [Test] + public void TestHyperlinks() + { + using (var wb = new XLWorkbook()) + { + var ws1 = wb.Worksheets.Add("Sheet1"); + var ws2 = wb.Worksheets.Add("Sheet2"); + + var targetCell = ws2.Cell("A1"); + var targetRange = ws2.Range("A1", "B1"); + + var linkCell1 = ws1.Cell("A1"); + linkCell1.Value = "Link to IXLCell"; + linkCell1.Hyperlink = new XLHyperlink(targetCell); + Assert.AreEqual("Sheet2!A1", linkCell1.Hyperlink.InternalAddress); + + var linkRange1 = ws1.Cell("A2"); + linkRange1.Value = "Link to IXLRangeBase"; + linkRange1.Hyperlink = new XLHyperlink(targetRange); + Assert.AreEqual("Sheet2!A1:B1", linkRange1.Hyperlink.InternalAddress); + } + } + } +} diff --git a/ClosedXML_Tests/Excel/Misc/SearchTests.cs b/ClosedXML_Tests/Excel/Misc/SearchTests.cs new file mode 100644 index 0000000..21e299d --- /dev/null +++ b/ClosedXML_Tests/Excel/Misc/SearchTests.cs @@ -0,0 +1,78 @@ +using ClosedXML.Excel; +using NUnit.Framework; +using System.Globalization; +using System.Linq; + +namespace ClosedXML_Tests.Excel.Misc +{ + [TestFixture] + public class SearchTests + { + [Test] + public void TestSearch() + { + using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"Examples\Misc\CellValues.xlsx"))) + using (var wb = new XLWorkbook(stream)) + { + var ws = wb.Worksheets.First(); + + IXLCells foundCells; + + foundCells = ws.Search("Initial Value"); + Assert.AreEqual(1, foundCells.Count()); + Assert.AreEqual("B2", foundCells.Single().Address.ToString()); + Assert.AreEqual("Initial Value", foundCells.Single().GetString()); + + foundCells = ws.Search("Using"); + Assert.AreEqual(2, foundCells.Count()); + Assert.AreEqual("D2", foundCells.First().Address.ToString()); + Assert.AreEqual("Using Get...()", foundCells.First().GetString()); + Assert.AreEqual(2, foundCells.Count()); + Assert.AreEqual("E2", foundCells.Last().Address.ToString()); + Assert.AreEqual("Using GetValue()", foundCells.Last().GetString()); + + foundCells = ws.Search("1234"); + Assert.AreEqual(4, foundCells.Count()); + Assert.AreEqual("C5,D5,E5,F5", string.Join(",", foundCells.Select(c => c.Address.ToString()).ToArray())); + + foundCells = ws.Search("Sep"); + Assert.AreEqual(2, foundCells.Count()); + Assert.AreEqual("B3,G3", string.Join(",", foundCells.Select(c => c.Address.ToString()).ToArray())); + + foundCells = ws.Search("1234", CompareOptions.Ordinal, true); + Assert.AreEqual(5, foundCells.Count()); + Assert.AreEqual("B5,C5,D5,E5,F5", string.Join(",", foundCells.Select(c => c.Address.ToString()).ToArray())); + + foundCells = ws.Search("test case", CompareOptions.Ordinal); + Assert.AreEqual(0, foundCells.Count()); + + foundCells = ws.Search("test case", CompareOptions.OrdinalIgnoreCase); + Assert.AreEqual(6, foundCells.Count()); + } + } + + [Test] + public void TestSearch2() + { + using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"Examples\Misc\Formulas.xlsx"))) + using (var wb = new XLWorkbook(stream)) + { + var ws = wb.Worksheets.First(); + + IXLCells foundCells; + + foundCells = ws.Search("3", CompareOptions.Ordinal); + Assert.AreEqual(10, foundCells.Count()); + Assert.AreEqual("C2", foundCells.First().Address.ToString()); + + foundCells = ws.Search("A2", CompareOptions.Ordinal, true); + Assert.AreEqual(6, foundCells.Count()); + Assert.AreEqual("C2,D2,B6,C6,D6,A11", string.Join(",", foundCells.Select(c => c.Address.ToString()).ToArray())); + + foundCells = ws.Search("RC", CompareOptions.Ordinal, true); + Assert.AreEqual(3, foundCells.Count()); + Assert.AreEqual("E2,E3,E4", string.Join(",", foundCells.Select(c => c.Address.ToString()).ToArray())); + } + } + } +} diff --git a/ClosedXML_Tests/Excel/NamedRanges/NamedRangesTests.cs b/ClosedXML_Tests/Excel/NamedRanges/NamedRangesTests.cs index 907bc96..afe5c7b 100644 --- a/ClosedXML_Tests/Excel/NamedRanges/NamedRangesTests.cs +++ b/ClosedXML_Tests/Excel/NamedRanges/NamedRangesTests.cs @@ -140,5 +140,38 @@ Assert.Throws(() => wb.NamedRanges.Add("MyRange", "A1:C1")); } } + + [Test] + public void NamedRangesWhenCopyingWorksheets() + { + using (var wb = new XLWorkbook()) + { + var ws1 = wb.AddWorksheet("Sheet1"); + ws1.FirstCell().Value = Enumerable.Range(1, 10); + wb.NamedRanges.Add("wbNamedRange", ws1.Range("A1:A10")); + ws1.NamedRanges.Add("wsNamedRange", ws1.Range("A3")); + + var ws2 = wb.AddWorksheet("Sheet2"); + ws2.FirstCell().Value = Enumerable.Range(101, 10); + ws1.NamedRanges.Add("wsNamedRangeAcrossSheets", ws2.Range("A4")); + + ws1.Cell("C1").FormulaA1 = "=wbNamedRange"; + ws1.Cell("C2").FormulaA1 = "=wsNamedRange"; + ws1.Cell("C3").FormulaA1 = "=wsNamedRangeAcrossSheets"; + + Assert.AreEqual(1, ws1.Cell("C1").Value); + Assert.AreEqual(3, ws1.Cell("C2").Value); + Assert.AreEqual(104, ws1.Cell("C3").Value); + + var wsCopy = ws1.CopyTo("Copy"); + Assert.AreEqual(1, wsCopy.Cell("C1").Value); + Assert.AreEqual(3, wsCopy.Cell("C2").Value); + Assert.AreEqual(104, wsCopy.Cell("C3").Value); + + Assert.AreEqual("Sheet1!A1:A10", wb.NamedRange("wbNamedRange").Ranges.First().RangeAddress.ToStringRelative(true)); + Assert.AreEqual("Copy!A3:A3", wsCopy.NamedRange("wsNamedRange").Ranges.First().RangeAddress.ToStringRelative(true)); + Assert.AreEqual("Sheet2!A4:A4", wsCopy.NamedRange("wsNamedRangeAcrossSheets").Ranges.First().RangeAddress.ToStringRelative(true)); + } + } } } diff --git a/ClosedXML_Tests/Excel/Ranges/XLRangeAddressTests.cs b/ClosedXML_Tests/Excel/Ranges/XLRangeAddressTests.cs index 669e811..c103c6e 100644 --- a/ClosedXML_Tests/Excel/Ranges/XLRangeAddressTests.cs +++ b/ClosedXML_Tests/Excel/Ranges/XLRangeAddressTests.cs @@ -13,6 +13,7 @@ IXLRangeAddress address = ws.Cell(1, 1).AsRange().RangeAddress; Assert.AreEqual("A1:A1", address.ToString()); + Assert.AreEqual("Sheet1!R1C1:R1C1", address.ToString(XLReferenceStyle.R1C1, true)); Assert.AreEqual("A1:A1", address.ToStringRelative()); Assert.AreEqual("Sheet1!A1:A1", address.ToStringRelative(true)); @@ -33,6 +34,7 @@ IXLRangeAddress address = ws.Cell(1, 1).AsRange().RangeAddress; Assert.AreEqual("A1:A1", address.ToString()); + Assert.AreEqual("'Sheet 1'!R1C1:R1C1", address.ToString(XLReferenceStyle.R1C1, true)); Assert.AreEqual("A1:A1", address.ToStringRelative()); Assert.AreEqual("'Sheet 1'!A1:A1", address.ToStringRelative(true)); diff --git a/ClosedXML_Tests/Excel/Rows/RowTests.cs b/ClosedXML_Tests/Excel/Rows/RowTests.cs index 17cddeb..bb74d4f 100644 --- a/ClosedXML_Tests/Excel/Rows/RowTests.cs +++ b/ClosedXML_Tests/Excel/Rows/RowTests.cs @@ -185,6 +185,36 @@ } [Test] + public void InsertingRowsAbove4() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Sheet1"); + + ws.Row(2).Height = 15; + ws.Row(3).Height = 20; + ws.Row(4).Height = 25; + ws.Row(5).Height = 35; + + ws.Row(2).FirstCell().SetValue("Row height: 15"); + ws.Row(3).FirstCell().SetValue("Row height: 20"); + ws.Row(4).FirstCell().SetValue("Row height: 25"); + ws.Row(5).FirstCell().SetValue("Row height: 35"); + + ws.Range("3:3").InsertRowsAbove(1); + + Assert.AreEqual(15, ws.Row(2).Height); + Assert.AreEqual(20, ws.Row(4).Height); + Assert.AreEqual(25, ws.Row(5).Height); + Assert.AreEqual(35, ws.Row(6).Height); + + Assert.AreEqual(20, ws.Row(3).Height); + ws.Row(3).ClearHeight(); + Assert.AreEqual(ws.RowHeight, ws.Row(3).Height); + } + } + + [Test] public void NoRowsUsed() { var wb = new XLWorkbook(); @@ -224,4 +254,4 @@ ws.Rows(1, 2).Ungroup(true); } } -} \ No newline at end of file +} diff --git a/ClosedXML_Tests/Excel/Saving/SavingTests.cs b/ClosedXML_Tests/Excel/Saving/SavingTests.cs index ca2fd98..60a1d7a 100644 --- a/ClosedXML_Tests/Excel/Saving/SavingTests.cs +++ b/ClosedXML_Tests/Excel/Saving/SavingTests.cs @@ -34,6 +34,35 @@ } [Test] + public void CanSaveFileMultipleTimesAfterDeletingWorksheet() + { + // https://github.com/ClosedXML/ClosedXML/issues/435 + + + using (var ms = new MemoryStream()) + { + using (XLWorkbook book1 = new XLWorkbook()) + { + book1.AddWorksheet("sheet1"); + book1.AddWorksheet("sheet2"); + + book1.SaveAs(ms); + } + ms.Position = 0; + + using (XLWorkbook book2 = new XLWorkbook(ms)) + { + var ws = book2.Worksheet(1); + Assert.AreEqual("sheet1", ws.Name); + ws.Delete(); + book2.Save(); + book2.Save(); + } + } + } + + + [Test] public void CanSaveAndValidateFileInAnotherCulture() { string[] cultures = new string[] { "it", "de-AT" }; diff --git a/ClosedXML_Tests/Excel/Styles/NumberFormatTests.cs b/ClosedXML_Tests/Excel/Styles/NumberFormatTests.cs index 3c94664..87ab21a 100644 --- a/ClosedXML_Tests/Excel/Styles/NumberFormatTests.cs +++ b/ClosedXML_Tests/Excel/Styles/NumberFormatTests.cs @@ -2,7 +2,6 @@ using NUnit.Framework; using System; using System.Data; -using System.Linq; namespace ClosedXML_Tests.Excel { @@ -14,7 +13,6 @@ using (var wb = new XLWorkbook()) { var ws = wb.AddWorksheet("Sheet1"); - ws.Column(1).Style.NumberFormat.Format = "yy-MM-dd"; var table = new DataTable(); table.Columns.Add("Date", typeof(DateTime)); @@ -24,9 +22,13 @@ table.Rows.Add(new DateTime(2017, 1, 1).AddMonths(i)); } - ws.Cell("A1").InsertData(table.AsEnumerable()); - + ws.Column(1).Style.NumberFormat.Format = "yy-MM-dd"; + ws.Cell("A1").InsertData(table); Assert.AreEqual("yy-MM-dd", ws.Cell("A5").Style.DateFormat.Format); + + ws.Row(1).Style.NumberFormat.Format = "yy-MM-dd"; + ws.Cell("A1").InsertData(table.AsEnumerable(), true); + Assert.AreEqual("yy-MM-dd", ws.Cell("E1").Style.DateFormat.Format); } } } diff --git a/ClosedXML_Tests/Excel/Tables/TablesTests.cs b/ClosedXML_Tests/Excel/Tables/TablesTests.cs index ddc7b84..adbe14b 100644 --- a/ClosedXML_Tests/Excel/Tables/TablesTests.cs +++ b/ClosedXML_Tests/Excel/Tables/TablesTests.cs @@ -1,4 +1,4 @@ -using ClosedXML.Attributes; +using ClosedXML.Attributes; using ClosedXML.Excel; using NUnit.Framework; using System; @@ -42,100 +42,112 @@ dt.Columns.Add("col1", typeof(string)); dt.Columns.Add("col2", typeof(double)); - var wb = new XLWorkbook(); - wb.AddWorksheet(dt); + using (var wb = new XLWorkbook()) + { + wb.AddWorksheet(dt); - using (var ms = new MemoryStream()) - wb.SaveAs(ms, true); + using (var ms = new MemoryStream()) + wb.SaveAs(ms, true); + } } [Test] public void CanSaveTableCreatedFromSingleRow() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().SetValue("Title"); - ws.Range("A1").CreateTable(); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().SetValue("Title"); + ws.Range("A1").CreateTable(); - using (var ms = new MemoryStream()) - wb.SaveAs(ms, true); + using (var ms = new MemoryStream()) + wb.SaveAs(ms, true); + } } [Test] public void CreatingATableFromHeadersPushCellsBelow() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().SetValue("Title") - .CellBelow().SetValue("X"); - ws.Range("A1").CreateTable(); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().SetValue("Title") + .CellBelow().SetValue("X"); + ws.Range("A1").CreateTable(); - Assert.AreEqual(ws.Cell("A2").GetString(), String.Empty); - Assert.AreEqual(ws.Cell("A3").GetString(), "X"); + Assert.AreEqual(ws.Cell("A2").GetString(), String.Empty); + Assert.AreEqual(ws.Cell("A3").GetString(), "X"); + } } [Test] public void Inserting_Column_Sets_Header() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().SetValue("Categories") - .CellBelow().SetValue("A") - .CellBelow().SetValue("B") - .CellBelow().SetValue("C"); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().SetValue("Categories") + .CellBelow().SetValue("A") + .CellBelow().SetValue("B") + .CellBelow().SetValue("C"); - IXLTable table = ws.RangeUsed().CreateTable(); - table.InsertColumnsAfter(1); - Assert.AreEqual("Column2", table.HeadersRow().LastCell().GetString()); + IXLTable table = ws.RangeUsed().CreateTable(); + table.InsertColumnsAfter(1); + Assert.AreEqual("Column2", table.HeadersRow().LastCell().GetString()); + } } [Test] public void SavingLoadingTableWithNewLineInHeader() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - string columnName = "Line1" + Environment.NewLine + "Line2"; - ws.FirstCell().SetValue(columnName) - .CellBelow().SetValue("A"); - ws.RangeUsed().CreateTable(); - using (var ms = new MemoryStream()) + using (var wb = new XLWorkbook()) { - wb.SaveAs(ms, true); - var wb2 = new XLWorkbook(ms); - IXLWorksheet ws2 = wb2.Worksheet(1); - IXLTable table2 = ws2.Table(0); - string fieldName = table2.Field(0).Name; - Assert.AreEqual("Line1\nLine2", fieldName); + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + string columnName = "Line1" + Environment.NewLine + "Line2"; + ws.FirstCell().SetValue(columnName) + .CellBelow().SetValue("A"); + ws.RangeUsed().CreateTable(); + using (var ms = new MemoryStream()) + { + wb.SaveAs(ms, true); + var wb2 = new XLWorkbook(ms); + IXLWorksheet ws2 = wb2.Worksheet(1); + IXLTable table2 = ws2.Table(0); + string fieldName = table2.Field(0).Name; + Assert.AreEqual("Line1\nLine2", fieldName); + } } } [Test] public void SavingLoadingTableWithNewLineInHeader2() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.Worksheets.Add("Test"); - - var dt = new DataTable(); - string columnName = "Line1" + Environment.NewLine + "Line2"; - dt.Columns.Add(columnName); - - DataRow dr = dt.NewRow(); - dr[columnName] = "some text"; - dt.Rows.Add(dr); - ws.Cell(1, 1).InsertTable(dt.AsEnumerable()); - - IXLTable table1 = ws.Table(0); - string fieldName1 = table1.Field(0).Name; - Assert.AreEqual(columnName, fieldName1); - - using (var ms = new MemoryStream()) + using (var wb = new XLWorkbook()) { - wb.SaveAs(ms, true); - var wb2 = new XLWorkbook(ms); - IXLWorksheet ws2 = wb2.Worksheet(1); - IXLTable table2 = ws2.Table(0); - string fieldName2 = table2.Field(0).Name; - Assert.AreEqual("Line1\nLine2", fieldName2); + IXLWorksheet ws = wb.Worksheets.Add("Test"); + + var dt = new DataTable(); + string columnName = "Line1" + Environment.NewLine + "Line2"; + dt.Columns.Add(columnName); + + DataRow dr = dt.NewRow(); + dr[columnName] = "some text"; + dt.Rows.Add(dr); + ws.Cell(1, 1).InsertTable(dt); + + IXLTable table1 = ws.Table(0); + string fieldName1 = table1.Field(0).Name; + Assert.AreEqual(columnName, fieldName1); + + using (var ms = new MemoryStream()) + { + wb.SaveAs(ms, true); + var wb2 = new XLWorkbook(ms); + IXLWorksheet ws2 = wb2.Worksheet(1); + IXLTable table2 = ws2.Table(0); + string fieldName2 = table2.Field(0).Name; + Assert.AreEqual("Line1\nLine2", fieldName2); + } } } @@ -146,10 +158,12 @@ dt.Columns.Add("col1", typeof(string)); dt.Columns.Add("col2", typeof(double)); - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().InsertTable(dt); - Assert.AreEqual(2, ws.Tables.First().ColumnCount()); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().InsertTable(dt); + Assert.AreEqual(2, ws.Tables.First().ColumnCount()); + } } [Test] @@ -157,10 +171,12 @@ { var l = new List(); - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().InsertTable(l); - Assert.AreEqual(1, ws.Tables.First().ColumnCount()); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().InsertTable(l); + Assert.AreEqual(1, ws.Tables.First().ColumnCount()); + } } [Test] @@ -168,10 +184,12 @@ { var l = new List(); - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().InsertTable(l); - Assert.AreEqual(2, ws.Tables.First().ColumnCount()); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().InsertTable(l); + Assert.AreEqual(2, ws.Tables.First().ColumnCount()); + } } [Test] @@ -183,184 +201,384 @@ new TestObjectWithAttributes() { Column1 = "c", Column2 = "d", MyField = 5, UnOrderedColumn = 777 } }; - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().InsertTable(l); - Assert.AreEqual(4, ws.Tables.First().ColumnCount()); - Assert.AreEqual("FirstColumn", ws.FirstCell().Value); - Assert.AreEqual("SecondColumn", ws.FirstCell().CellRight().Value); - Assert.AreEqual("SomeFieldNotProperty", ws.FirstCell().CellRight().CellRight().Value); - Assert.AreEqual("UnOrderedColumn", ws.FirstCell().CellRight().CellRight().CellRight().Value); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().InsertTable(l); + Assert.AreEqual(4, ws.Tables.First().ColumnCount()); + Assert.AreEqual("FirstColumn", ws.FirstCell().Value); + Assert.AreEqual("SecondColumn", ws.FirstCell().CellRight().Value); + Assert.AreEqual("SomeFieldNotProperty", ws.FirstCell().CellRight().CellRight().Value); + Assert.AreEqual("UnOrderedColumn", ws.FirstCell().CellRight().CellRight().CellRight().Value); + } } [Test] public void TableInsertAboveFromData() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().SetValue("Value"); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().SetValue("Value"); - IXLTable table = ws.Range("A1:A2").CreateTable(); - table.SetShowTotalsRow() - .Field(0).TotalsRowFunction = XLTotalsRowFunction.Sum; + IXLTable table = ws.Range("A1:A2").CreateTable(); + table.SetShowTotalsRow() + .Field(0).TotalsRowFunction = XLTotalsRowFunction.Sum; - IXLTableRow row = table.DataRange.FirstRow(); - row.Field("Value").Value = 3; - row = table.DataRange.InsertRowsAbove(1).First(); - row.Field("Value").Value = 2; - row = table.DataRange.InsertRowsAbove(1).First(); - row.Field("Value").Value = 1; + IXLTableRow row = table.DataRange.FirstRow(); + row.Field("Value").Value = 3; + row = table.DataRange.InsertRowsAbove(1).First(); + row.Field("Value").Value = 2; + row = table.DataRange.InsertRowsAbove(1).First(); + row.Field("Value").Value = 1; - Assert.AreEqual(1, ws.Cell(2, 1).GetDouble()); - Assert.AreEqual(2, ws.Cell(3, 1).GetDouble()); - Assert.AreEqual(3, ws.Cell(4, 1).GetDouble()); + Assert.AreEqual(1, ws.Cell(2, 1).GetDouble()); + Assert.AreEqual(2, ws.Cell(3, 1).GetDouble()); + Assert.AreEqual(3, ws.Cell(4, 1).GetDouble()); + } } [Test] public void TableInsertAboveFromRows() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().SetValue("Value"); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().SetValue("Value"); - IXLTable table = ws.Range("A1:A2").CreateTable(); - table.SetShowTotalsRow() - .Field(0).TotalsRowFunction = XLTotalsRowFunction.Sum; + IXLTable table = ws.Range("A1:A2").CreateTable(); + table.SetShowTotalsRow() + .Field(0).TotalsRowFunction = XLTotalsRowFunction.Sum; - IXLTableRow row = table.DataRange.FirstRow(); - row.Field("Value").Value = 3; - row = row.InsertRowsAbove(1).First(); - row.Field("Value").Value = 2; - row = row.InsertRowsAbove(1).First(); - row.Field("Value").Value = 1; + IXLTableRow row = table.DataRange.FirstRow(); + row.Field("Value").Value = 3; + row = row.InsertRowsAbove(1).First(); + row.Field("Value").Value = 2; + row = row.InsertRowsAbove(1).First(); + row.Field("Value").Value = 1; - Assert.AreEqual(1, ws.Cell(2, 1).GetDouble()); - Assert.AreEqual(2, ws.Cell(3, 1).GetDouble()); - Assert.AreEqual(3, ws.Cell(4, 1).GetDouble()); + Assert.AreEqual(1, ws.Cell(2, 1).GetDouble()); + Assert.AreEqual(2, ws.Cell(3, 1).GetDouble()); + Assert.AreEqual(3, ws.Cell(4, 1).GetDouble()); + } } [Test] public void TableInsertBelowFromData() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().SetValue("Value"); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().SetValue("Value"); - IXLTable table = ws.Range("A1:A2").CreateTable(); - table.SetShowTotalsRow() - .Field(0).TotalsRowFunction = XLTotalsRowFunction.Sum; + IXLTable table = ws.Range("A1:A2").CreateTable(); + table.SetShowTotalsRow() + .Field(0).TotalsRowFunction = XLTotalsRowFunction.Sum; - IXLTableRow row = table.DataRange.FirstRow(); - row.Field("Value").Value = 1; - row = table.DataRange.InsertRowsBelow(1).First(); - row.Field("Value").Value = 2; - row = table.DataRange.InsertRowsBelow(1).First(); - row.Field("Value").Value = 3; + IXLTableRow row = table.DataRange.FirstRow(); + row.Field("Value").Value = 1; + row = table.DataRange.InsertRowsBelow(1).First(); + row.Field("Value").Value = 2; + row = table.DataRange.InsertRowsBelow(1).First(); + row.Field("Value").Value = 3; - Assert.AreEqual(1, ws.Cell(2, 1).GetDouble()); - Assert.AreEqual(2, ws.Cell(3, 1).GetDouble()); - Assert.AreEqual(3, ws.Cell(4, 1).GetDouble()); + Assert.AreEqual(1, ws.Cell(2, 1).GetDouble()); + Assert.AreEqual(2, ws.Cell(3, 1).GetDouble()); + Assert.AreEqual(3, ws.Cell(4, 1).GetDouble()); + } } [Test] public void TableInsertBelowFromRows() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().SetValue("Value"); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().SetValue("Value"); - IXLTable table = ws.Range("A1:A2").CreateTable(); - table.SetShowTotalsRow() - .Field(0).TotalsRowFunction = XLTotalsRowFunction.Sum; + IXLTable table = ws.Range("A1:A2").CreateTable(); + table.SetShowTotalsRow() + .Field(0).TotalsRowFunction = XLTotalsRowFunction.Sum; - IXLTableRow row = table.DataRange.FirstRow(); - row.Field("Value").Value = 1; - row = row.InsertRowsBelow(1).First(); - row.Field("Value").Value = 2; - row = row.InsertRowsBelow(1).First(); - row.Field("Value").Value = 3; + IXLTableRow row = table.DataRange.FirstRow(); + row.Field("Value").Value = 1; + row = row.InsertRowsBelow(1).First(); + row.Field("Value").Value = 2; + row = row.InsertRowsBelow(1).First(); + row.Field("Value").Value = 3; - Assert.AreEqual(1, ws.Cell(2, 1).GetDouble()); - Assert.AreEqual(2, ws.Cell(3, 1).GetDouble()); - Assert.AreEqual(3, ws.Cell(4, 1).GetDouble()); + Assert.AreEqual(1, ws.Cell(2, 1).GetDouble()); + Assert.AreEqual(2, ws.Cell(3, 1).GetDouble()); + Assert.AreEqual(3, ws.Cell(4, 1).GetDouble()); + } } [Test] public void TableShowHeader() { - var wb = new XLWorkbook(); - IXLWorksheet ws = wb.AddWorksheet("Sheet1"); - ws.FirstCell().SetValue("Categories") - .CellBelow().SetValue("A") - .CellBelow().SetValue("B") - .CellBelow().SetValue("C"); + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().SetValue("Categories") + .CellBelow().SetValue("A") + .CellBelow().SetValue("B") + .CellBelow().SetValue("C"); - IXLTable table = ws.RangeUsed().CreateTable(); + IXLTable table = ws.RangeUsed().CreateTable(); - Assert.AreEqual("Categories", table.Fields.First().Name); + Assert.AreEqual("Categories", table.Fields.First().Name); - table.SetShowHeaderRow(false); + table.SetShowHeaderRow(false); - Assert.AreEqual("Categories", table.Fields.First().Name); + Assert.AreEqual("Categories", table.Fields.First().Name); - Assert.IsTrue(ws.Cell(1, 1).IsEmpty(true)); - Assert.AreEqual(null, table.HeadersRow()); - Assert.AreEqual("A", table.DataRange.FirstRow().Field("Categories").GetString()); - Assert.AreEqual("C", table.DataRange.LastRow().Field("Categories").GetString()); - Assert.AreEqual("A", table.DataRange.FirstCell().GetString()); - Assert.AreEqual("C", table.DataRange.LastCell().GetString()); + Assert.IsTrue(ws.Cell(1, 1).IsEmpty(true)); + Assert.AreEqual(null, table.HeadersRow()); + Assert.AreEqual("A", table.DataRange.FirstRow().Field("Categories").GetString()); + Assert.AreEqual("C", table.DataRange.LastRow().Field("Categories").GetString()); + Assert.AreEqual("A", table.DataRange.FirstCell().GetString()); + Assert.AreEqual("C", table.DataRange.LastCell().GetString()); - table.SetShowHeaderRow(); - IXLRangeRow headerRow = table.HeadersRow(); - Assert.AreNotEqual(null, headerRow); - Assert.AreEqual("Categories", headerRow.Cell(1).GetString()); + table.SetShowHeaderRow(); + IXLRangeRow headerRow = table.HeadersRow(); + Assert.AreNotEqual(null, headerRow); + Assert.AreEqual("Categories", headerRow.Cell(1).GetString()); - table.SetShowHeaderRow(false); + table.SetShowHeaderRow(false); - ws.FirstCell().SetValue("x"); + ws.FirstCell().SetValue("x"); - table.SetShowHeaderRow(); + table.SetShowHeaderRow(); - Assert.AreEqual("x", ws.FirstCell().GetString()); - Assert.AreEqual("Categories", ws.Cell("A2").GetString()); - Assert.AreNotEqual(null, headerRow); - Assert.AreEqual("A", table.DataRange.FirstRow().Field("Categories").GetString()); - Assert.AreEqual("C", table.DataRange.LastRow().Field("Categories").GetString()); - Assert.AreEqual("A", table.DataRange.FirstCell().GetString()); - Assert.AreEqual("C", table.DataRange.LastCell().GetString()); + Assert.AreEqual("x", ws.FirstCell().GetString()); + Assert.AreEqual("Categories", ws.Cell("A2").GetString()); + Assert.AreNotEqual(null, headerRow); + Assert.AreEqual("A", table.DataRange.FirstRow().Field("Categories").GetString()); + Assert.AreEqual("C", table.DataRange.LastRow().Field("Categories").GetString()); + Assert.AreEqual("A", table.DataRange.FirstCell().GetString()); + Assert.AreEqual("C", table.DataRange.LastCell().GetString()); + } } [Test] public void ChangeFieldName() { - XLWorkbook wb = new XLWorkbook(); + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet"); + ws.Cell("A1").SetValue("FName") + .CellBelow().SetValue("John"); - var ws = wb.AddWorksheet("Sheet"); - ws.Cell("A1").SetValue("FName") - .CellBelow().SetValue("John"); + ws.Cell("B1").SetValue("LName") + .CellBelow().SetValue("Doe"); - ws.Cell("B1").SetValue("LName") - .CellBelow().SetValue("Doe"); + var tbl = ws.RangeUsed().CreateTable(); + var nameBefore = tbl.Field(tbl.Fields.Last().Index).Name; + tbl.Field(tbl.Fields.Last().Index).Name = "LastName"; + var nameAfter = tbl.Field(tbl.Fields.Last().Index).Name; - var tbl = ws.RangeUsed().CreateTable(); - var nameBefore = tbl.Field(tbl.Fields.Last().Index).Name; - tbl.Field(tbl.Fields.Last().Index).Name = "LastName"; - var nameAfter = tbl.Field(tbl.Fields.Last().Index).Name; + var cellValue = ws.Cell("B1").GetString(); - var cellValue = ws.Cell("B1").GetString(); + Assert.AreEqual("LName", nameBefore); + Assert.AreEqual("LastName", nameAfter); + Assert.AreEqual("LastName", cellValue); - Assert.AreEqual("LName", nameBefore); - Assert.AreEqual("LastName", nameAfter); - Assert.AreEqual("LastName", cellValue); + tbl.ShowHeaderRow = false; + tbl.Field(tbl.Fields.Last().Index).Name = "LastNameChanged"; + nameAfter = tbl.Field(tbl.Fields.Last().Index).Name; + Assert.AreEqual("LastNameChanged", nameAfter); - tbl.ShowHeaderRow = false; - tbl.Field(tbl.Fields.Last().Index).Name = "LastNameChanged"; - nameAfter = tbl.Field(tbl.Fields.Last().Index).Name; - Assert.AreEqual("LastNameChanged", nameAfter); + tbl.SetShowHeaderRow(true); + nameAfter = tbl.Cell("B1").Value.ToString(); + Assert.AreEqual("LastNameChanged", nameAfter); - tbl.SetShowHeaderRow(true); - nameAfter = tbl.Cell("B1").Value.ToString(); - Assert.AreEqual("LastNameChanged", nameAfter); + var field = tbl.Field("LastNameChanged"); + Assert.AreEqual("LastNameChanged", field.Name); + + tbl.Cell(1, 1).Value = "FirstName"; + Assert.AreEqual("FirstName", tbl.Field(0).Name); + } } + + [Test] + public void CanDeleteTableColumn() + { + var l = new List() + { + new TestObjectWithAttributes() { Column1 = "a", Column2 = "b", MyField = 4, UnOrderedColumn = 999 }, + new TestObjectWithAttributes() { Column1 = "c", Column2 = "d", MyField = 5, UnOrderedColumn = 777 } + }; + + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet1"); + var table = ws.FirstCell().InsertTable(l); + + table.Column("C").Delete(); + + Assert.AreEqual(3, table.Fields.Count()); + + Assert.AreEqual("FirstColumn", table.Fields.First().Name); + Assert.AreEqual(0, table.Fields.First().Index); + + Assert.AreEqual("UnOrderedColumn", table.Fields.Last().Name); + Assert.AreEqual(2, table.Fields.Last().Index); + } + } + + [Test] + public void CanDeleteTableField() + { + var l = new List() + { + new TestObjectWithAttributes() { Column1 = "a", Column2 = "b", MyField = 4, UnOrderedColumn = 999 }, + new TestObjectWithAttributes() { Column1 = "c", Column2 = "d", MyField = 5, UnOrderedColumn = 777 } + }; + + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet1"); + var table = ws.FirstCell().InsertTable(l); + + table.Field("SomeFieldNotProperty").Delete(); + + Assert.AreEqual(3, table.Fields.Count()); + + Assert.AreEqual("FirstColumn", table.Fields.First().Name); + Assert.AreEqual(0, table.Fields.First().Index); + + Assert.AreEqual("UnOrderedColumn", table.Fields.Last().Name); + Assert.AreEqual(2, table.Fields.Last().Index); + } + } + + [Test] + public void OverlappingTablesThrowsException() + { + var dt = new DataTable("sheet1"); + dt.Columns.Add("col1", typeof(string)); + dt.Columns.Add("col2", typeof(double)); + + using (var wb = new XLWorkbook()) + { + IXLWorksheet ws = wb.AddWorksheet("Sheet1"); + ws.FirstCell().InsertTable(dt, true); + Assert.Throws(() => ws.FirstCell().CellRight().InsertTable(dt, true)); + } + } + + [Test] + public void OverwritingTableTotalsRow() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet1"); + + var data1 = Enumerable.Range(1, 10) + .Select(i => + new + { + Index = i, + Character = Convert.ToChar(64 + i), + String = new String('a', i) + }); + + var table = ws.FirstCell().InsertTable(data1, true) + .SetShowHeaderRow() + .SetShowTotalsRow(); + table.Fields.First().TotalsRowFunction = XLTotalsRowFunction.Sum; + + var data2 = Enumerable.Range(1, 20) + .Select(i => + new + { + Index = i, + Character = Convert.ToChar(64 + i), + String = new String('b', i), + Int = 64 + i + }); + + ws.FirstCell().CellBelow().InsertData(data2); + + table.Fields.ForEach(f => Assert.AreEqual(XLTotalsRowFunction.None, f.TotalsRowFunction)); + + Assert.AreEqual("11", table.Field(0).TotalsRowLabel); + Assert.AreEqual("K", table.Field(1).TotalsRowLabel); + Assert.AreEqual("bbbbbbbbbbb", table.Field(2).TotalsRowLabel); + } + } + + [Test] + public void CanResizeTable() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet1"); + + var data1 = Enumerable.Range(1, 10) + .Select(i => + new + { + Index = i, + Character = Convert.ToChar(64 + i), + String = new String('a', i) + }); + + var table = ws.FirstCell().InsertTable(data1, true) + .SetShowHeaderRow() + .SetShowTotalsRow(); + table.Fields.First().TotalsRowFunction = XLTotalsRowFunction.Sum; + + var data2 = Enumerable.Range(1, 10) + .Select(i => + new + { + Index = i, + Character = Convert.ToChar(64 + i), + String = new String('b', i), + Integer = 64 + i + }); + + ws.FirstCell().CellBelow().InsertData(data2); + table.Resize(table.FirstCell().Address, table.AsRange().LastCell().CellRight().Address); + + Assert.AreEqual(4, table.Fields.Count()); + + Assert.AreEqual("Column4", table.Field(3).Name); + + ws.Cell("D1").Value = "Integer"; + Assert.AreEqual("Integer", table.Field(3).Name); + } + } + + [Test] + public void TestTableCellTypes() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.AddWorksheet("Sheet1"); + + var data1 = Enumerable.Range(1, 10) + .Select(i => + new + { + Index = i, + Character = Convert.ToChar(64 + i), + String = new String('a', i) + }); + + var table = ws.FirstCell().InsertTable(data1, true) + .SetShowHeaderRow() + .SetShowTotalsRow(); + table.Fields.First().TotalsRowFunction = XLTotalsRowFunction.Sum; + + Assert.AreEqual(XLTableCellType.Header, table.HeadersRow().Cell(1).TableCellType()); + Assert.AreEqual(XLTableCellType.Data, table.HeadersRow().Cell(1).CellBelow().TableCellType()); + Assert.AreEqual(XLTableCellType.Total, table.TotalsRow().Cell(1).TableCellType()); + Assert.AreEqual(XLTableCellType.None, ws.Cell("Z100").TableCellType()); + } + } + + //TODO: Delete table (not underlying range) } } diff --git a/ClosedXML_Tests/Excel/Worksheets/XLWorksheetTests.cs b/ClosedXML_Tests/Excel/Worksheets/XLWorksheetTests.cs index f679187..76f612e 100644 --- a/ClosedXML_Tests/Excel/Worksheets/XLWorksheetTests.cs +++ b/ClosedXML_Tests/Excel/Worksheets/XLWorksheetTests.cs @@ -1,6 +1,7 @@ using ClosedXML.Excel; using NUnit.Framework; using System; +using System.IO; using System.Linq; namespace ClosedXML_Tests @@ -140,5 +141,57 @@ Assert.AreEqual(6, value); } } + + [Test] + public void CanRenameWorksheet() + { + using (var wb = new XLWorkbook()) + { + var ws1 = wb.AddWorksheet("Sheet1"); + var ws2 = wb.AddWorksheet("Sheet2"); + + ws1.Name = "New sheet name"; + Assert.AreEqual("New sheet name", ws1.Name); + + ws2.Name = "sheet2"; + Assert.AreEqual("sheet2", ws2.Name); + + Assert.Throws(() => ws1.Name = "SHEET2"); + } + } + + [Test] + public void HideWorksheet() + { + using (var ms = new MemoryStream()) + { + using (var wb = new XLWorkbook()) + { + wb.Worksheets.Add("VisibleSheet"); + wb.Worksheets.Add("HiddenSheet").Hide(); + wb.SaveAs(ms); + } + + // unhide the hidden sheet + using (var wb = new XLWorkbook(ms)) + { + Assert.AreEqual(XLWorksheetVisibility.Visible, wb.Worksheet("VisibleSheet").Visibility); + Assert.AreEqual(XLWorksheetVisibility.Hidden, wb.Worksheet("HiddenSheet").Visibility); + + var ws = wb.Worksheet("HiddenSheet"); + ws.Unhide().Name = "NoAlsoVisible"; + + Assert.AreEqual(XLWorksheetVisibility.Visible, ws.Visibility); + + wb.Save(); + } + + using (var wb = new XLWorkbook(ms)) + { + Assert.AreEqual(XLWorksheetVisibility.Visible, wb.Worksheet("VisibleSheet").Visibility); + Assert.AreEqual(XLWorksheetVisibility.Visible, wb.Worksheet("NoAlsoVisible").Visibility); + } + } + } } -} \ No newline at end of file +} diff --git a/ClosedXML_Tests/OleDb/OleDbTests.cs b/ClosedXML_Tests/OleDb/OleDbTests.cs index e502614..2aa897c 100644 --- a/ClosedXML_Tests/OleDb/OleDbTests.cs +++ b/ClosedXML_Tests/OleDb/OleDbTests.cs @@ -1,4 +1,5 @@ using ClosedXML.Excel; +using ClosedXML_Tests.Utils; using NUnit.Framework; using System; using System.Collections.Generic; @@ -16,7 +17,7 @@ [Test] public void TestOleDbValues() { - using (var tf = new TestFile(CreateTestFile())) + using (var tf = new TemporaryFile(CreateTestFile())) { Console.Write("Using temporary file\t{0}", tf.Path); var connectionString = string.Format(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties='Excel 12.0 Xml;HDR=YES;IMEX=1';", tf.Path); @@ -115,32 +116,5 @@ return path; } } - - internal class TestFile : IDisposable - { - internal TestFile(string path) - : this(path, false) - { } - - internal TestFile(string path, bool preserve) - { - this.Path = path; - this.Preserve = preserve; - } - - public string Path { get; private set; } - public bool Preserve { get; private set; } - - public void Dispose() - { - if (!Preserve) - File.Delete(Path); - } - - public override string ToString() - { - return this.Path; - } - } } } diff --git a/ClosedXML_Tests/Resource/Examples/Columns/DeletingColumns.xlsx b/ClosedXML_Tests/Resource/Examples/Columns/DeletingColumns.xlsx index bac6e30..e2ac29f 100644 --- a/ClosedXML_Tests/Resource/Examples/Columns/DeletingColumns.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Columns/DeletingColumns.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Comments/AddingComments.xlsx b/ClosedXML_Tests/Resource/Examples/Comments/AddingComments.xlsx index 3879998..5c9ae0a 100644 --- a/ClosedXML_Tests/Resource/Examples/Comments/AddingComments.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Comments/AddingComments.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsBlank.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsBlank.xlsx index 80be4ab..e57b38d 100644 --- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsBlank.xlsx +++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFIsBlank.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotBlank.xlsx b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotBlank.xlsx index 5f4f6ce..e233171 100644 --- a/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotBlank.xlsx +++ b/ClosedXML_Tests/Resource/Examples/ConditionalFormatting/CFNotBlank.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/ImageHandling/ImageAnchors.xlsx b/ClosedXML_Tests/Resource/Examples/ImageHandling/ImageAnchors.xlsx index 53aa802..d2a90ba 100644 --- a/ClosedXML_Tests/Resource/Examples/ImageHandling/ImageAnchors.xlsx +++ b/ClosedXML_Tests/Resource/Examples/ImageHandling/ImageAnchors.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/ImageHandling/ImageFormats.xlsx b/ClosedXML_Tests/Resource/Examples/ImageHandling/ImageFormats.xlsx index 237d910..3ec78e0 100644 --- a/ClosedXML_Tests/Resource/Examples/ImageHandling/ImageFormats.xlsx +++ b/ClosedXML_Tests/Resource/Examples/ImageHandling/ImageFormats.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/BlankCells.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/BlankCells.xlsx index 21ea101..3f4195a 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/BlankCells.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/BlankCells.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/CopyingWorksheets.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/CopyingWorksheets.xlsx index 6d14038..28c4b9b 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/CopyingWorksheets.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/CopyingWorksheets.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/DataTypes.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/DataTypes.xlsx index 87f74be..d7b9afd 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/DataTypes.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/DataTypes.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/DataTypesUnderDifferentCulture.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/DataTypesUnderDifferentCulture.xlsx index 5d0b538..f451273 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/DataTypesUnderDifferentCulture.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/DataTypesUnderDifferentCulture.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/Formulas.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/Formulas.xlsx index dea7693..c49d20a 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/Formulas.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/Formulas.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/FormulasWithEvaluation.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/FormulasWithEvaluation.xlsx index 7a93ddb..f1dde09 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/FormulasWithEvaluation.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/FormulasWithEvaluation.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/HideUnhide.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/HideUnhide.xlsx index a3c6ea5..1f99477 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/HideUnhide.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/HideUnhide.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/Hyperlinks.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/Hyperlinks.xlsx index 222ba7b..d724e36 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/Hyperlinks.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/Hyperlinks.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/InsertingData.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/InsertingData.xlsx index 9e9d20f..d9d1a57 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/InsertingData.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/InsertingData.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/InsertingTables.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/InsertingTables.xlsx deleted file mode 100644 index 212cbfe..0000000 --- a/ClosedXML_Tests/Resource/Examples/Misc/InsertingTables.xlsx +++ /dev/null Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/MergeMoves.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/MergeMoves.xlsx index 9839a6f..2cbaac9 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/MergeMoves.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/MergeMoves.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Misc/ShiftingFormulas.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/ShiftingFormulas.xlsx index 81bc2a4..a1aa8d0 100644 --- a/ClosedXML_Tests/Resource/Examples/Misc/ShiftingFormulas.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Misc/ShiftingFormulas.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/PivotTables/PivotTables.xlsx b/ClosedXML_Tests/Resource/Examples/PivotTables/PivotTables.xlsx index 6e3156d..369bb0c 100644 --- a/ClosedXML_Tests/Resource/Examples/PivotTables/PivotTables.xlsx +++ b/ClosedXML_Tests/Resource/Examples/PivotTables/PivotTables.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/ClearingRanges.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/ClearingRanges.xlsx index c1c9431..5e18500 100644 --- a/ClosedXML_Tests/Resource/Examples/Ranges/ClearingRanges.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Ranges/ClearingRanges.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/InsertingDeletingRows.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/InsertingDeletingRows.xlsx index 6cdd101..9588f7d 100644 --- a/ClosedXML_Tests/Resource/Examples/Ranges/InsertingDeletingRows.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Ranges/InsertingDeletingRows.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/NamedRanges.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/NamedRanges.xlsx index 8d4e6a2..2b450d6 100644 --- a/ClosedXML_Tests/Resource/Examples/Ranges/NamedRanges.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Ranges/NamedRanges.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/SelectingRanges.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/SelectingRanges.xlsx index a33e2a6..adecfee 100644 --- a/ClosedXML_Tests/Resource/Examples/Ranges/SelectingRanges.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Ranges/SelectingRanges.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/ShiftingRanges.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/ShiftingRanges.xlsx index 3f1bafd..453328f 100644 --- a/ClosedXML_Tests/Resource/Examples/Ranges/ShiftingRanges.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Ranges/ShiftingRanges.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/SortExample.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/SortExample.xlsx index 8b26d86..bd2c3d6 100644 --- a/ClosedXML_Tests/Resource/Examples/Ranges/SortExample.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Ranges/SortExample.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/TransposeRangesPlus.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/TransposeRangesPlus.xlsx index 44fbbbe..037fc86 100644 --- a/ClosedXML_Tests/Resource/Examples/Ranges/TransposeRangesPlus.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Ranges/TransposeRangesPlus.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/UsingTables.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/UsingTables.xlsx deleted file mode 100644 index 288d1fe..0000000 --- a/ClosedXML_Tests/Resource/Examples/Ranges/UsingTables.xlsx +++ /dev/null Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Ranges/WalkingRanges.xlsx b/ClosedXML_Tests/Resource/Examples/Ranges/WalkingRanges.xlsx index 4a705f7..e12dfc5 100644 --- a/ClosedXML_Tests/Resource/Examples/Ranges/WalkingRanges.xlsx +++ b/ClosedXML_Tests/Resource/Examples/Ranges/WalkingRanges.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Styles/UsingPhonetics.xlsx b/ClosedXML_Tests/Resource/Examples/Styles/UsingPhonetics.xlsx new file mode 100644 index 0000000..86fe3e6 --- /dev/null +++ b/ClosedXML_Tests/Resource/Examples/Styles/UsingPhonetics.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Tables/InsertingTables.xlsx b/ClosedXML_Tests/Resource/Examples/Tables/InsertingTables.xlsx new file mode 100644 index 0000000..89de679 --- /dev/null +++ b/ClosedXML_Tests/Resource/Examples/Tables/InsertingTables.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Tables/ResizingTables.xlsx b/ClosedXML_Tests/Resource/Examples/Tables/ResizingTables.xlsx new file mode 100644 index 0000000..dad3954 --- /dev/null +++ b/ClosedXML_Tests/Resource/Examples/Tables/ResizingTables.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Examples/Tables/UsingTables.xlsx b/ClosedXML_Tests/Resource/Examples/Tables/UsingTables.xlsx new file mode 100644 index 0000000..288d1fe --- /dev/null +++ b/ClosedXML_Tests/Resource/Examples/Tables/UsingTables.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Misc/DuplicateImageNames.xlsx b/ClosedXML_Tests/Resource/Misc/DuplicateImageNames.xlsx new file mode 100644 index 0000000..29df50b --- /dev/null +++ b/ClosedXML_Tests/Resource/Misc/DuplicateImageNames.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Misc/TableHeadersWithLineBreaks.xlsx b/ClosedXML_Tests/Resource/Misc/TableHeadersWithLineBreaks.xlsx new file mode 100644 index 0000000..8e7dbdc --- /dev/null +++ b/ClosedXML_Tests/Resource/Misc/TableHeadersWithLineBreaks.xlsx Binary files differ diff --git a/ClosedXML_Tests/Resource/Misc/TableWithNameNull.xlsx b/ClosedXML_Tests/Resource/Misc/TableWithNameNull.xlsx new file mode 100644 index 0000000..0a5a8de --- /dev/null +++ b/ClosedXML_Tests/Resource/Misc/TableWithNameNull.xlsx Binary files differ diff --git a/ClosedXML_Tests/Utils/TemporaryFile.cs b/ClosedXML_Tests/Utils/TemporaryFile.cs new file mode 100644 index 0000000..6d95b1f --- /dev/null +++ b/ClosedXML_Tests/Utils/TemporaryFile.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; + +namespace ClosedXML_Tests.Utils +{ + internal class TemporaryFile : IDisposable + { + internal TemporaryFile() + : this(System.IO.Path.ChangeExtension(System.IO.Path.GetTempFileName(), "xlsx")) + { } + + internal TemporaryFile(string path) + : this(path, false) + { } + + internal TemporaryFile(String path, bool preserve) + { + this.Path = path; + this.Preserve = preserve; + } + + + public string Path { get; private set; } + public bool Preserve { get; private set; } + + public void Dispose() + { + if (!Preserve) + File.Delete(Path); + } + + public override string ToString() + { + return this.Path; + } + } +} diff --git a/ClosedXML_Tests/packages.config b/ClosedXML_Tests/packages.config index c32aa84..5e0ab7c 100644 --- a/ClosedXML_Tests/packages.config +++ b/ClosedXML_Tests/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file