blob: 2031fe33db1079b856493f1e8a95ccc3b574ff39 [file] [log] [blame]
/*******************************************************************************
* You may amend and distribute as you like, but don't remove this header!
*
* EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
* See http://www.codeplex.com/EPPlus for details.
*
* Copyright (C) 2011 Jan Källman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
* If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
*
* All code and executables are provided "as is" with no warranty either express or implied.
* The author accepts no liability for any damage or loss of business that this product may cause.
*
* Code change notes:
*
* Author Change Date
* ******************************************************************************
* Jan Källman Initial Release 2011-11-02
* Jan Källman Total rewrite 2010-03-01
* Jan Källman License changed GPL-->LGPL 2011-12-27
*******************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
namespace AppsheetEpplus;
/// <summary>
/// Worksheet hidden enumeration
/// </summary>
public enum eWorkSheetHidden {
/// <summary>
/// The worksheet is visible
/// </summary>
Visible,
/// <summary>
/// The worksheet is hidden but can be shown by the user via the user interface
/// </summary>
Hidden,
/// <summary>
/// The worksheet is hidden and cannot be shown by the user via the user interface
/// </summary>
VeryHidden,
}
[Flags]
internal enum CellFlags {
//Merged = 0x1,
RichText = 0x2,
SharedFormula = 0x4,
ArrayFormula = 0x8,
}
/// <summary>
/// Represents an Excel Chartsheet and provides access to its properties and methods
/// </summary>
public class ExcelChartsheet : ExcelWorksheet {
public ExcelChartsheet(
XmlNamespaceManager ns,
ExcelPackage pck,
ExcelWorkbook workbook,
Uri worksheetUri,
string name,
int sheetId,
int positionId,
eWorkSheetHidden hidden)
: base(ns, pck, workbook, worksheetUri, name, sheetId, positionId, hidden) {}
}
/// <summary>
/// Represents an Excel worksheet and provides access to its properties and methods
/// </summary>
public class ExcelWorksheet : XmlHelper, IEqualityComparer<ExcelWorksheet> {
internal class Formulas {
public Formulas(ISourceCodeTokenizer tokenizer) {
_tokenizer = tokenizer;
}
public static string RemoveDummyFunction(string formula) {
const string dummyFunctionConcatenate = "\"&\"";
const string dummyFunctionPrefix = "IFERROR(__xludf.DUMMYFUNCTION(\"";
const string dummyFunctionSuffix = "\"),";
if (string.IsNullOrEmpty(formula)) {
return formula;
}
// Look for Prefix
if (!formula.StartsWith(dummyFunctionPrefix)) {
return formula;
}
// Look for Suffix
int index = formula.LastIndexOf(dummyFunctionSuffix);
if (index < 0) {
return formula;
}
// Trim Suffix
formula = formula.Substring(0, index);
// Trim Prefix
formula = formula.Replace(dummyFunctionPrefix, "");
// Remove string concatentations from long formulas.
// Google break the quoted string into 254 character segments which are concatenated.
if (formula.Length >= 254) {
formula = formula.Replace(dummyFunctionConcatenate, "");
}
// Replace doubled quotes with single quote
formula = formula.Replace("\"\"", "\"");
// Return formula
return formula;
}
private readonly ISourceCodeTokenizer _tokenizer;
internal int Index { get; set; }
internal string Address { get; set; }
internal bool IsArray { get; set; }
public string Formula { get; set; }
public int StartRow { get; set; }
public int StartCol { get; set; }
private IEnumerable<Token> Tokens { get; set; }
internal string GetFormula(int row, int column, string worksheet) {
if ((StartRow == row && StartCol == column) || IsArray) {
return RemoveDummyFunction(Formula);
}
if (Tokens == null) {
Tokens = _tokenizer.Tokenize(RemoveDummyFunction(Formula), worksheet);
}
string f = "";
foreach (var token in Tokens) {
if (token.TokenType == TokenType.ExcelAddress) {
var a = new ExcelFormulaAddress(token.Value);
f += a.GetOffset(row - StartRow, column - StartCol);
} else {
f += token.Value;
}
}
return f;
}
}
/// <summary>
/// Collection containing merged cell addresses
/// </summary>
public class MergeCellsCollection : IEnumerable<string> {
internal MergeCellsCollection() {}
internal readonly CellStore<int> _cells = new();
private readonly List<string> _list = [];
internal List<string> List => _list;
public string this[int row, int column] {
get {
int ix = -1;
if (_cells.Exists(row, column, ref ix)
&& ix >= 0
&& ix
< List.Count) //Fixes issue 15075
{
return List[ix];
}
return null;
}
}
public string this[int index] => _list[index];
internal void Add(ExcelAddressBase address, bool doValidate) {
//Validate
if (doValidate && Validate(address) == false) {
throw (new ArgumentException("Can't merge and already merged range"));
}
var ix = _list.Count;
_list.Add(address.Address);
SetIndex(address, ix);
}
private bool Validate(ExcelAddressBase address) {
int ix = 0;
if (_cells.Exists(address._fromRow, address._fromCol, ref ix)) {
if (ix >= 0 && ix < _list.Count && _list[ix] != null && address.Address == _list[ix]) {
return true;
}
return false;
}
var cse = new CellsStoreEnumerator<int>(
_cells,
address._fromRow,
address._fromCol,
address._toRow,
address._toCol);
//cells
while (cse.Next()) {
return false;
}
//Entire column
cse = new(_cells, 0, address._fromCol, 0, address._toCol);
while (cse.Next()) {
return false;
}
//Entire row
cse = new(_cells, address._fromRow, 0, address._toRow, 0);
while (cse.Next()) {
return false;
}
return true;
}
internal void SetIndex(ExcelAddressBase address, int ix) {
if (address._fromRow == 1
&& address._toRow
== ExcelPackage.MaxRows) //Entire row
{
for (int col = address._fromCol; col <= address._toCol; col++) {
_cells.SetValue(0, col, ix);
}
} else if (address._fromCol == 1
&& address._toCol
== ExcelPackage.MaxColumns) //Entire row
{
for (int row = address._fromRow; row <= address._toRow; row++) {
_cells.SetValue(row, 0, ix);
}
} else {
for (int col = address._fromCol; col <= address._toCol; col++) {
for (int row = address._fromRow; row <= address._toRow; row++) {
_cells.SetValue(row, col, ix);
}
}
}
}
public int Count => _list.Count;
internal void Remove(string item) {
_list.Remove(item);
}
public IEnumerator<string> GetEnumerator() {
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return _list.GetEnumerator();
}
internal void Clear(ExcelAddressBase destination) {
var cse = new CellsStoreEnumerator<int>(
_cells,
destination._fromRow,
destination._fromCol,
destination._toRow,
destination._toCol);
var used = new HashSet<int>();
while (cse.Next()) {
var v = cse.Value;
if (!used.Contains(v) && _list[v] != null) {
var adr = new ExcelAddressBase(_list[v]);
if (!(destination.Collide(adr) == ExcelAddressBase.eAddressCollition.Inside
|| destination.Collide(adr) == ExcelAddressBase.eAddressCollition.Equal)) {
throw (new InvalidOperationException(
string.Format(
"Can't delete merged cells. A range is partly merged with the deleted range. {0}",
adr._address)));
}
used.Add(v);
}
}
_cells.Clear(
destination._fromRow,
destination._fromCol,
destination._toRow - destination._fromRow + 1,
destination._toCol - destination._fromCol + 1);
foreach (var i in used) {
_list[i] = null;
}
}
}
internal readonly CellStore<object> _values = new();
internal readonly CellStore<string> _types = new();
internal readonly CellStore<int> _styles = new();
internal readonly CellStore<object> _formulas = new();
internal readonly FlagCellStore _flags = new();
internal CellStore<List<Token>> _formulaTokens;
internal readonly CellStore<Uri> _hyperLinks = new();
internal readonly CellStore<ExcelComment> _commentsStore = new();
internal readonly Dictionary<int, Formulas> _sharedFormulas = new();
internal readonly ExcelPackage _package;
private readonly ExcelWorkbook _workbook;
private ExcelWorksheetView _sheetView;
internal static ImmutableArray<string> WorksheetSchemaNodeOrder = [
"sheetPr",
"tabColor",
"outlinePr",
"pageSetUpPr",
"dimension",
"sheetViews",
"sheetFormatPr",
"cols",
"sheetData",
"sheetProtection",
"protectedRanges",
"scenarios",
"autoFilter",
"sortState",
"dataConsolidate",
"customSheetViews",
"customSheetViews",
"mergeCells",
"phoneticPr",
"conditionalFormatting",
"dataValidations",
"hyperlinks",
"printOptions",
"pageMargins",
"pageSetup",
"headerFooter",
"linePrint",
"rowBreaks",
"colBreaks",
"customProperties",
"cellWatches",
"ignoredErrors",
"smartTags",
"drawing",
"legacyDrawing",
"legacyDrawingHF",
"picture",
"oleObjects",
"activeXControls",
"webPublishItems",
"tableParts",
"extLst",
];
protected override ImmutableArray<string> SchemaNodeOrder => WorksheetSchemaNodeOrder;
internal ExcelWorksheet(
XmlNamespaceManager ns,
ExcelPackage excelPackage,
ExcelWorkbook workbook,
Uri worksheetUri,
string name,
int sheetId,
int positionId,
eWorkSheetHidden hidden)
: base(ns, null) {
_workbook = workbook;
_package = excelPackage;
_names = new(Workbook, this);
Hidden = hidden;
Name = name;
PositionID = positionId;
SheetID = sheetId;
Part = _package.Package.GetPart(worksheetUri);
Part.SaveHandler = SaveHandler;
// First Columns, rows, cells, mergecells, hyperlinks and pagebreakes are loaded from an
// XmlTextReader to optimize speed.
using var stream = Part.GetStream();
var xr = new XmlTextReader(stream);
xr.ProhibitDtd = true;
xr.WhitespaceHandling = WhitespaceHandling.None;
LoadColumns(xr); //columnXml
long start = stream.Position;
LoadCells(xr);
var nextElementLength = GetAttributeLength(xr);
long end = stream.Position - nextElementLength;
LoadMergeCells(xr);
LoadHyperLinks(xr);
LoadRowPageBreakes(xr);
LoadColPageBreakes(xr);
// Then the rest of the XML is extracted and loaded into the WorksheetXml document.
stream.Seek(0, SeekOrigin.Begin);
var xml = GetWorkSheetXml(stream, start, end, out var encoding);
//first char is invalid sometimes??
if (xml[0] != '<') {
LoadXmlSafe(WorksheetXml, xml.Substring(1, xml.Length - 1), encoding);
} else {
LoadXmlSafe(WorksheetXml, xml, encoding);
}
ClearNodes();
TopNode = WorksheetXml.DocumentElement;
}
/// <summary>
/// The Zip.ZipPackagePart for the worksheet within the package
/// </summary>
internal ZipPackagePart Part { get; }
/// <summary>
/// The unique identifier for the worksheet.
/// </summary>
internal int SheetID { get; }
/// <summary>
/// The position of the worksheet.
/// </summary>
internal int PositionID { get; }
/// <summary>
/// Address for autofilter
/// <seealso cref="ExcelRangeBase.AutoFilter" />
/// </summary>
public ExcelAddressBase AutoFilterAddress {
get {
CheckSheetType();
string address = GetXmlNodeString("d:autoFilter/@ref");
if (address == "") {
return null;
}
return new(address);
}
internal set {
CheckSheetType();
SetXmlNodeString("d:autoFilter/@ref", value.Address);
}
}
internal void CheckSheetType() {
if (this is ExcelChartsheet) {
throw (new NotSupportedException(
"This property or method is not supported for a Chartsheet"));
}
}
/// <summary>
/// Returns a ExcelWorksheetView object that allows you to set the view state properties of the worksheet
/// </summary>
public ExcelWorksheetView View {
get {
if (_sheetView == null) {
XmlNode node = TopNode.SelectSingleNode("d:sheetViews/d:sheetView", NameSpaceManager);
if (node == null) {
CreateNode("d:sheetViews/d:sheetView"); //this one shouls always exist. but check anyway
node = TopNode.SelectSingleNode("d:sheetViews/d:sheetView", NameSpaceManager);
}
_sheetView = new(NameSpaceManager, node, this);
}
return (_sheetView);
}
}
/// <summary>
/// The worksheet's display name as it appears on the tab
/// </summary>
public string Name { get; }
private readonly ExcelNamedRangeCollection _names;
/// <summary>
/// Provides access to named ranges
/// </summary>
public ExcelNamedRangeCollection Names {
get {
CheckSheetType();
return _names;
}
}
/// <summary>
/// Indicates if the worksheet is hidden in the workbook
/// </summary>
public eWorkSheetHidden Hidden {
get {
string state = _workbook.GetXmlNodeString(
string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", SheetID));
if (state == "hidden") {
return eWorkSheetHidden.Hidden;
}
if (state == "veryHidden") {
return eWorkSheetHidden.VeryHidden;
}
return eWorkSheetHidden.Visible;
}
set {
if (value == eWorkSheetHidden.Visible) {
_workbook.DeleteNode(string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", SheetID));
} else {
string v;
v = value.ToString();
v = v.Substring(0, 1).ToLower(CultureInfo.InvariantCulture) + v.Substring(1);
_workbook.SetXmlNodeString(
string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", SheetID),
v);
}
}
}
private double _defaultRowHeight = double.NaN;
/// <summary>
/// Get/set the default height of all rows in the worksheet
/// </summary>
public double DefaultRowHeight {
get {
CheckSheetType();
if (double.IsNaN(_defaultRowHeight)) {
_defaultRowHeight = GetXmlNodeDouble("d:sheetFormatPr/@defaultRowHeight");
if (double.IsNaN(_defaultRowHeight)) {
_defaultRowHeight = 15; // Excel default height
}
}
return _defaultRowHeight;
}
set {
CheckSheetType();
_defaultRowHeight = value;
SetXmlNodeString(
"d:sheetFormatPr/@defaultRowHeight",
value.ToString(CultureInfo.InvariantCulture));
SetXmlNodeBool("d:sheetFormatPr/@customHeight", value != 15);
if (double.IsNaN(GetXmlNodeDouble("d:sheetFormatPr/@defaultColWidth"))) {
DefaultColWidth = 9.140625;
}
}
}
/// <summary>
/// Get/set the default width of all rows in the worksheet
/// </summary>
public double DefaultColWidth {
get {
CheckSheetType();
double ret = GetXmlNodeDouble("d:sheetFormatPr/@defaultColWidth");
if (double.IsNaN(ret)) {
ret = 9.140625; // Excel's default width
}
return ret;
}
set {
CheckSheetType();
SetXmlNodeString(
"d:sheetFormatPr/@defaultColWidth",
value.ToString(CultureInfo.InvariantCulture));
if (double.IsNaN(GetXmlNodeDouble("d:sheetFormatPr/@defaultRowHeight"))) {
DefaultRowHeight = 15;
}
}
}
/** <outlinePr applyStyles="1" summaryBelow="0" summaryRight="0" /> **/
private const string _outLineSummaryBelowPath = "d:sheetPr/d:outlinePr/@summaryBelow";
/// <summary>
/// Summary rows below details
/// </summary>
public bool OutLineSummaryBelow {
get {
CheckSheetType();
return GetXmlNodeBool(_outLineSummaryBelowPath);
}
set {
CheckSheetType();
SetXmlNodeString(_outLineSummaryBelowPath, value ? "1" : "0");
}
}
private const string _outLineSummaryRightPath = "d:sheetPr/d:outlinePr/@summaryRight";
/// <summary>
/// Summary rows to right of details
/// </summary>
public bool OutLineSummaryRight {
get {
CheckSheetType();
return GetXmlNodeBool(_outLineSummaryRightPath);
}
set {
CheckSheetType();
SetXmlNodeString(_outLineSummaryRightPath, value ? "1" : "0");
}
}
private const string _outLineApplyStylePath = "d:sheetPr/d:outlinePr/@applyStyles";
/// <summary>
/// Automatic styles
/// </summary>
public bool OutLineApplyStyle {
get {
CheckSheetType();
return GetXmlNodeBool(_outLineApplyStylePath);
}
set {
CheckSheetType();
SetXmlNodeString(_outLineApplyStylePath, value ? "1" : "0");
}
}
private const string _codeModuleNamePath = "d:sheetPr/@codeName";
internal string CodeModuleName {
get => GetXmlNodeString(_codeModuleNamePath);
set => SetXmlNodeString(_codeModuleNamePath, value);
}
/// <summary>
/// The XML document holding the worksheet data.
/// All column, row, cell, pagebreak, merged cell and hyperlink-data are loaded into memory and removed from the document when loading the document.
/// </summary>
internal XmlDocument WorksheetXml { get; } = new();
internal ExcelVmlDrawingCommentCollection _vmlDrawings;
/// <summary>
/// Vml drawings. underlaying object for comments
/// </summary>
internal ExcelVmlDrawingCommentCollection VmlDrawingsComments {
get {
if (_vmlDrawings == null) {
CreateVmlCollection();
}
return _vmlDrawings;
}
}
internal ExcelCommentCollection _comments;
/// <summary>
/// Collection of comments
/// </summary>
public ExcelCommentCollection Comments {
get {
CheckSheetType();
if (_comments == null) {
CreateVmlCollection();
_comments = new(_package, this, NameSpaceManager);
}
return _comments;
}
}
private void CreateVmlCollection() {
var vmlNode = WorksheetXml.DocumentElement.SelectSingleNode(
"d:legacyDrawing/@r:id",
NameSpaceManager);
if (vmlNode == null) {
_vmlDrawings = new(_package, this, null);
} else {
if (Part.RelationshipExists(vmlNode.Value)) {
var rel = Part.GetRelationship(vmlNode.Value);
var vmlUri = UriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri);
_vmlDrawings = new(_package, this, vmlUri);
_vmlDrawings.RelId = rel.Id;
}
}
}
/// <summary>
/// Get the lenth of the attributes
/// Conditional formatting attributes can be extremly long som get length of the attributes to finetune position.
/// </summary>
/// <param name="xr"></param>
/// <returns></returns>
private int GetAttributeLength(XmlTextReader xr) {
if (xr.NodeType != XmlNodeType.Element) {
return 0;
}
var length = 0;
for (int i = 0; i < xr.AttributeCount; i++) {
var a = xr.GetAttribute(i);
length += string.IsNullOrEmpty(a) ? 0 : a.Length;
}
return length;
}
private void LoadRowPageBreakes(XmlTextReader xr) {
if (!ReadUntil(xr, "rowBreaks", "colBreaks")) {
return;
}
while (xr.Read()) {
if (xr.LocalName == "brk") {
if (xr.NodeType == XmlNodeType.Element) {
if (int.TryParse(xr.GetAttribute("id"), out var id)) {
Row(id).PageBreak = true;
}
}
} else {
break;
}
}
}
private void LoadColPageBreakes(XmlTextReader xr) {
if (!ReadUntil(xr, "colBreaks")) {
return;
}
while (xr.Read()) {
if (xr.LocalName == "brk") {
if (xr.NodeType == XmlNodeType.Element) {
if (int.TryParse(xr.GetAttribute("id"), out var id)) {
Column(id).PageBreak = true;
}
}
} else {
break;
}
}
}
private void ClearNodes() {
if (WorksheetXml.SelectSingleNode("//d:cols", NameSpaceManager) != null) {
WorksheetXml.SelectSingleNode("//d:cols", NameSpaceManager).RemoveAll();
}
if (WorksheetXml.SelectSingleNode("//d:mergeCells", NameSpaceManager) != null) {
WorksheetXml.SelectSingleNode("//d:mergeCells", NameSpaceManager).RemoveAll();
}
if (WorksheetXml.SelectSingleNode("//d:hyperlinks", NameSpaceManager) != null) {
WorksheetXml.SelectSingleNode("//d:hyperlinks", NameSpaceManager).RemoveAll();
}
if (WorksheetXml.SelectSingleNode("//d:rowBreaks", NameSpaceManager) != null) {
WorksheetXml.SelectSingleNode("//d:rowBreaks", NameSpaceManager).RemoveAll();
}
if (WorksheetXml.SelectSingleNode("//d:colBreaks", NameSpaceManager) != null) {
WorksheetXml.SelectSingleNode("//d:colBreaks", NameSpaceManager).RemoveAll();
}
}
private const int _blocksize = 8192;
private string GetWorkSheetXml(Stream stream, long start, long end, out Encoding encoding) {
StreamReader sr = new StreamReader(stream);
int length = 0;
char[] block;
int pos;
StringBuilder sb = new StringBuilder();
Match startmMatch,
endMatch;
do {
int size = stream.Length < _blocksize ? (int)stream.Length : _blocksize;
block = new char[size];
pos = sr.ReadBlock(block, 0, size);
sb.Append(block, 0, pos);
length += size;
} while (length < start + 20 && length < end);
startmMatch = Regex.Match(sb.ToString(), string.Format("(<[^>]*{0}[^>]*>)", "sheetData"));
if (!startmMatch.Success) //Not found
{
encoding = sr.CurrentEncoding;
return sb.ToString();
}
string s = sb.ToString();
string xml = s.Substring(0, startmMatch.Index);
if (startmMatch.Value.EndsWith("/>")) {
xml += s.Substring(startmMatch.Index, s.Length - startmMatch.Index);
} else {
if (sr.Peek() != -1) {
/**** Fixes issue 14788. Fix by Philip Garrett ****/
long endSeekStart = end;
while (endSeekStart >= 0) {
endSeekStart = Math.Max(endSeekStart - _blocksize, 0);
int size = (int)(end - endSeekStart);
stream.Seek(endSeekStart, SeekOrigin.Begin);
block = new char[size];
sr = new(stream);
pos = sr.ReadBlock(block, 0, size);
sb = new();
sb.Append(block, 0, pos);
s = sb.ToString();
endMatch = Regex.Match(s, string.Format("(</[^>]*{0}[^>]*>)", "sheetData"));
if (endMatch.Success) {
break;
}
}
}
endMatch = Regex.Match(s, string.Format("(</[^>]*{0}[^>]*>)", "sheetData"));
xml +=
"<sheetData/>"
+ s.Substring(
endMatch.Index + endMatch.Length,
s.Length - (endMatch.Index + endMatch.Length));
}
if (sr.Peek() > -1) {
xml += sr.ReadToEnd();
}
encoding = sr.CurrentEncoding;
return xml;
}
private void GetBlockPos(string xml, string tag, ref int start, ref int end) {
Match startmMatch,
endMatch;
startmMatch = Regex.Match(xml.Substring(start), string.Format("(<[^>]*{0}[^>]*>)", tag)); //"<[a-zA-Z:]*" + tag + "[?]*>");
if (!startmMatch.Success) //Not found
{
start = -1;
end = -1;
return;
}
var startPos = startmMatch.Index + start;
if (startmMatch.Value.Substring(startmMatch.Value.Length - 2, 1) == "/") {
end = startPos + startmMatch.Length;
} else {
endMatch = Regex.Match(xml.Substring(start), string.Format("(</[^>]*{0}[^>]*>)", tag));
if (endMatch.Success) {
end = endMatch.Index + endMatch.Length + start;
}
}
start = startPos;
}
private bool ReadUntil(XmlTextReader xr, params string[] tagName) {
if (xr.EOF) {
return false;
}
while (!Array.Exists(tagName, tag => xr.LocalName.EndsWith(tag))) {
xr.Read();
if (xr.EOF) {
return false;
}
}
return (xr.LocalName.EndsWith(tagName[0]));
}
private void LoadColumns(
XmlTextReader xr) //(string xml)
{
var colList = new List<IRangeId>();
if (ReadUntil(xr, "cols", "sheetData")) {
//if (xml != "")
//{
//var xr=new XmlTextReader(new StringReader(xml));
while (xr.Read()) {
if (xr.NodeType == XmlNodeType.Whitespace) {
continue;
}
if (xr.LocalName != "col") {
break;
}
if (xr.NodeType == XmlNodeType.Element) {
int min = int.Parse(xr.GetAttribute("min"));
ExcelColumn col = new ExcelColumn(this, min);
col.ColumnMax = int.Parse(xr.GetAttribute("max"));
col.Width =
xr.GetAttribute("width") == null
? 0
: double.Parse(xr.GetAttribute("width"), CultureInfo.InvariantCulture);
col.BestFit =
xr.GetAttribute("bestFit") != null && xr.GetAttribute("bestFit") == "1"
? true
: false;
col.Collapsed =
xr.GetAttribute("collapsed") != null && xr.GetAttribute("collapsed") == "1"
? true
: false;
col.Phonetic =
xr.GetAttribute("phonetic") != null && xr.GetAttribute("phonetic") == "1"
? true
: false;
col.OutlineLevel = (short)(xr.GetAttribute("outlineLevel") == null
? 0
: int.Parse(xr.GetAttribute("outlineLevel"), CultureInfo.InvariantCulture));
col.Hidden =
xr.GetAttribute("hidden") != null && xr.GetAttribute("hidden") == "1" ? true : false;
_values.SetValue(0, min, col);
if (!(xr.GetAttribute("style") == null
|| !int.TryParse(xr.GetAttribute("style"), out var style))) {
_styles.SetValue(0, min, style);
}
}
}
}
}
/// <summary>
/// Load Hyperlinks
/// </summary>
/// <param name="xr">The reader</param>
private void LoadHyperLinks(XmlTextReader xr) {
if (!ReadUntil(xr, "hyperlinks", "rowBreaks", "colBreaks")) {
return;
}
while (xr.Read()) {
if (xr.LocalName == "hyperlink") {
ExcelCellBase.GetRowColFromAddress(
xr.GetAttribute("ref"),
out var fromRow,
out var fromCol,
out int toRow,
out var toCol);
ExcelHyperLink hl = null;
if (xr.GetAttribute("id", ExcelPackage._schemaRelationships) != null) {
var rId = xr.GetAttribute("id", ExcelPackage._schemaRelationships);
var uri = Part.GetRelationship(rId).TargetUri;
// Get Location, if any. EPPlus Bug 15517
var location = xr.GetAttribute("location");
location = (string.IsNullOrEmpty(location)) ? "" : "#" + location;
if (uri.IsAbsoluteUri) {
try {
hl = new(uri.AbsoluteUri + location);
} catch {
hl = new(uri.OriginalString + location, UriKind.Absolute);
}
} else {
hl = new(uri.OriginalString + location, UriKind.Relative);
}
hl.RId = rId;
Part.DeleteRelationship(rId); //Delete the relationship, it is recreated when we save the package.
} else if (xr.GetAttribute("location") != null) {
hl = new(xr.GetAttribute("location"), xr.GetAttribute("display"));
hl.RowSpann = toRow - fromRow;
hl.ColSpann = toCol - fromCol;
}
string tt = xr.GetAttribute("tooltip");
if (!string.IsNullOrEmpty(tt)) {
hl.ToolTip = tt;
}
_hyperLinks.SetValue(fromRow, fromCol, hl);
} else {
break;
}
}
}
/// <summary>
/// Load cells
/// </summary>
/// <param name="xr">The reader</param>
private void LoadCells(XmlTextReader xr) {
//var cellList=new List<IRangeID>();
//var rowList = new List<IRangeID>();
//var formulaList = new List<IRangeID>();
ReadUntil(xr, "sheetData", "mergeCells", "hyperlinks", "rowBreaks", "colBreaks");
ExcelAddressBase address = null;
string type = "";
int style = 0;
int row = 0;
int col = 0;
xr.Read();
while (!xr.EOF) {
while (xr.NodeType == XmlNodeType.EndElement) {
xr.Read();
}
if (xr.LocalName == "row") {
var r = xr.GetAttribute("r");
if (r == null) {
row++;
} else {
row = Convert.ToInt32(r);
}
if (DoAddRow(xr)) {
_values.SetValue(row, 0, AddRow(xr, row));
if (xr.GetAttribute("s") != null) {
_styles.SetValue(row, 0, int.Parse(xr.GetAttribute("s"), CultureInfo.InvariantCulture));
}
}
xr.Read();
} else if (xr.LocalName == "c") {
//if (cell != null) cellList.Add(cell);
//cell = new ExcelCell(this, xr.GetAttribute("r"));
var r = xr.GetAttribute("r");
if (r == null) {
//Handle cells with no reference
col++;
address = new(row, col, row, col);
} else {
address = new(r);
col = address._fromCol;
}
//Datetype
if (xr.GetAttribute("t") != null) {
type = xr.GetAttribute("t");
_types.SetValue(address._fromRow, address._fromCol, type);
} else {
type = "";
}
//Style
if (xr.GetAttribute("s") != null) {
style = int.Parse(xr.GetAttribute("s"));
_styles.SetValue(address._fromRow, address._fromCol, style);
_values.SetValue(address._fromRow, address._fromCol, null); //TODO:Better Performance ??
} else {
style = 0;
}
xr.Read();
} else if (xr.LocalName == "v") {
SetValueFromXml(xr, type, style, address._fromRow, address._fromCol);
xr.Read();
} else if (xr.LocalName == "f") {
string t = xr.GetAttribute("t");
if (t == null) {
_formulas.SetValue(address._fromRow, address._fromCol, xr.ReadElementContentAsString());
_values.SetValue(address._fromRow, address._fromCol, null);
//formulaList.Add(cell);
} else if (t == "shared") {
string si = xr.GetAttribute("si");
if (si != null) {
var sfIndex = int.Parse(si);
_formulas.SetValue(address._fromRow, address._fromCol, sfIndex);
_values.SetValue(address._fromRow, address._fromCol, null);
string fAddress = xr.GetAttribute("ref");
string formula = ConvertUtil.ExcelDecodeString(xr.ReadElementContentAsString());
if (formula != "") {
_sharedFormulas.Add(
sfIndex,
new(SourceCodeTokenizer.Default) {
Index = sfIndex,
Formula = formula,
Address = fAddress,
StartRow = address._fromRow,
StartCol = address._fromCol,
});
}
} else {
xr.Read(); //Something is wrong in the sheet, read next
}
} else if (t
== "array") //TODO: Array functions are not support yet. Read the formula for the start cell only.
{
string aAddress = xr.GetAttribute("ref");
ExcelRange addressRange = new ExcelRange(this, aAddress);
string formula = xr.ReadElementContentAsString();
bool isIndexMatchFormula =
Regex.IsMatch(formula, @"INDEX\(", RegexOptions.IgnoreCase)
&& Regex.IsMatch(formula, @"MATCH\(", RegexOptions.IgnoreCase)
&& !aAddress.Contains(":");
if (isIndexMatchFormula) {
addressRange.IsArrayFormula = false;
for (int colIndex = addressRange.Start.Column;
colIndex <= addressRange.End.Column;
colIndex++) {
for (int rowIndex = addressRange.Start.Row;
rowIndex <= addressRange.End.Row;
rowIndex++) {
var afIndex = GetMaxShareFunctionIndex(true);
_formulas.SetValue(rowIndex, colIndex, afIndex);
_values.SetValue(rowIndex, colIndex, null);
_sharedFormulas.Add(
afIndex,
new(SourceCodeTokenizer.Default) {
Index = afIndex,
Formula = formula,
Address = aAddress,
StartRow = address._fromRow,
StartCol = address._fromCol,
IsArray = false,
});
}
}
} else {
addressRange.IsArrayFormula = true;
var afIndex = GetMaxShareFunctionIndex(true);
for (int colIndex = addressRange.Start.Column;
colIndex <= addressRange.End.Column;
colIndex++) {
for (int rowIndex = addressRange.Start.Row;
rowIndex <= addressRange.End.Row;
rowIndex++) {
_formulas.SetValue(rowIndex, colIndex, afIndex);
_values.SetValue(rowIndex, colIndex, null);
}
}
_sharedFormulas.Add(
afIndex,
new(SourceCodeTokenizer.Default) {
Index = afIndex,
Formula = formula,
Address = aAddress,
StartRow = address._fromRow,
StartCol = address._fromCol,
IsArray = true,
});
}
} else // ??? some other type
{
xr.Read(); //Something is wrong in the sheet, read next
}
} else if (xr.LocalName
== "is") //Inline string
{
xr.Read();
if (xr.LocalName == "t") {
_values.SetValue(
address._fromRow,
address._fromCol,
ConvertUtil.ExcelDecodeString(xr.ReadElementContentAsString()));
//cell._value = xr.ReadInnerXml();
} else {
_values.SetValue(address._fromRow, address._fromCol, xr.ReadOuterXml());
_types.SetValue(address._fromRow, address._fromCol, "rt");
_flags.SetFlagValue(address._fromRow, address._fromCol, true, CellFlags.RichText);
//cell.IsRichText = true;
}
} else {
break;
}
}
}
private bool DoAddRow(XmlTextReader xr) {
var c = xr.GetAttribute("r") == null ? 0 : 1;
if (xr.GetAttribute("spans") != null) {
c++;
}
return xr.AttributeCount > c;
}
/// <summary>
/// Load merged cells
/// </summary>
/// <param name="xr"></param>
private void LoadMergeCells(XmlTextReader xr) {
if (ReadUntil(xr, "mergeCells", "hyperlinks", "rowBreaks", "colBreaks") && !xr.EOF) {
while (xr.Read()) {
if (xr.LocalName != "mergeCell") {
break;
}
if (xr.NodeType == XmlNodeType.Element) {
string address = xr.GetAttribute("ref");
_mergedCells.Add(new ExcelAddress(address), false);
}
}
}
}
/// <summary>
/// Update merged cells
/// </summary>
/// <param name="sw">The writer</param>
private void UpdateMergedCells(StreamWriter sw) {
sw.Write("<mergeCells>");
foreach (string address in _mergedCells) {
sw.Write("<mergeCell ref=\"{0}\" />", address);
}
sw.Write("</mergeCells>");
}
/// <summary>
/// Reads a row from the XML reader
/// </summary>
/// <param name="xr">The reader</param>
/// <param name="row">The row number</param>
/// <returns></returns>
private RowInternal AddRow(XmlTextReader xr, int row) {
return new() {
Collapsed =
(xr.GetAttribute("collapsed") != null && xr.GetAttribute("collapsed") == "1"
? true
: false),
OutlineLevel =
(xr.GetAttribute("outlineLevel") == null
? (short)0
: short.Parse(xr.GetAttribute("outlineLevel"), CultureInfo.InvariantCulture)),
Height =
(xr.GetAttribute("ht") == null
? -1
: double.Parse(xr.GetAttribute("ht"), CultureInfo.InvariantCulture)),
Hidden =
(xr.GetAttribute("hidden") != null && xr.GetAttribute("hidden") == "1" ? true : false),
Phonetic = xr.GetAttribute("ph") != null && xr.GetAttribute("ph") == "1" ? true : false,
CustomHeight =
xr.GetAttribute("customHeight") == null ? false : xr.GetAttribute("customHeight") == "1",
};
}
private static readonly DateTime _excelEpoch = new(1899, 12, 30);
public static DateTime IncorrectDurationFromOaDate(double value) {
// This behavior is wrong. Real OADate values have a discontinuity on 30 December 1899.
// For real OADate values, the negative sign applies only to the integer portion of
// the float, *not* to the decimal portion. For example, -0.5 and 0.5 both refer to the
// same date, and -1.5 is actually 1899-12-29 12:00 (1 day before 1899-12-30 00:00
// plus 0.5 days), *not* 1899-12-28 12:00 (1.5 days before 1899-12-30 00:00).
//
// Unfortunately, AppSheet's duration-handling code gets this very wrong, and treats the
// duration as the offset from 1899-12-30 00:00. This is correct for positive durations,
// but it's wrong for negative durations. This code tries to fix the bug that exists in
// AppSheet's duration-handling code here, and it succeeds in some cases and fails in
// others.
//
// This code also breaks date/time handling for dates before 1899-12-30 00:00 in some
// cases. Specifically, dates end up being offset by one day.
//
// Regardless, changing this behavior is risky, so this code simply replicates the
// existing behavior for
if (value >= 0) {
return DateTime.FromOADate(value);
}
// This looks like a very complicated way to call TimeSpan.FromDays(value), but
// TimeSpan.FromDays actually only guarantees millisecond precision, and critically
// rounding is different on .NET Core, resulting in values like (e.g.) 3:15:00 being
// incorrectly rounded.
var offset = DateTime.FromOADate(-value) - _excelEpoch;
return _excelEpoch - offset;
}
private void SetValueFromXml(XmlTextReader xr, string type, int styleId, int row, int col) {
//XmlNode vnode = colNode.SelectSingleNode("d:v", NameSpaceManager);
//if (vnode == null) return null;
if (type == "s") {
int ix = xr.ReadElementContentAsInt();
// Temporary debugging code to locate intermittent 'Index was out of range' exception.
if (ix < 0) {
throw new(
string.Format(
"ReadElementContentAsInt returned value '{0}' which is less than zero.",
ix));
}
if (ix >= _workbook._sharedStringsList.Count) {
throw new(
string.Format(
"ReadElementContentAsInt returned index value '{0}' which is greater than _sharedStringsList count of {1}.",
ix,
_workbook._sharedStringsList.Count));
}
_values.SetValue(row, col, _workbook._sharedStringsList[ix].Text);
if (_workbook._sharedStringsList[ix].isRichText) {
_flags.SetFlagValue(row, col, true, CellFlags.RichText);
}
} else if (type == "str") {
_values.SetValue(row, col, ConvertUtil.ExcelDecodeString(xr.ReadElementContentAsString()));
} else if (type == "b") {
_values.SetValue(row, col, (xr.ReadElementContentAsString() != "0"));
} else if (type == "e") {
_values.SetValue(row, col, GetErrorType(xr.ReadElementContentAsString()));
} else {
string v = xr.ReadElementContentAsString();
var nf = Workbook.Styles.CellXfs[styleId].NumberFormatId;
if ((nf >= 20 && nf <= 21)
|| (nf >= 45
&& nf
<= 47)) // Duration
{
if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out var res)) {
if (Workbook.Date1904) {
res += ExcelWorkbook._date1904Offset;
}
if (res >= -657435.0 && res < 2958465.9999999) {
// Get the Duration value expressed as a DateTime.
_values.SetValue(row, col, IncorrectDurationFromOaDate(res));
} else {
// Cope with Google Sheets export of cells having a formula.
// Rather than exporting the native value, they export the formatted value.
_values.SetValue(row, col, v);
}
} else {
// Cope with Google Sheets export of cells having a formula.
// Rather than exporting the native value, they export the formatted value.
_values.SetValue(row, col, v);
}
} else if ((nf >= 14 && nf <= 19)
|| (nf
== 22)) // DateTime
{
if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out var res)) {
if (Workbook.Date1904) {
res += ExcelWorkbook._date1904Offset;
}
if (res >= -657435.0 && res < 2958465.9999999) {
_values.SetValue(row, col, DateTime.FromOADate(res));
} else {
// Cope with Google Sheets export of cells having a formula.
// Rather than exporting the native value, they export the formatted value.
_values.SetValue(row, col, v);
}
} else {
// Cope with Google Sheets export of cells having a formula.
// Rather than exporting the native value, they export the formatted value.
_values.SetValue(row, col, v);
}
} else {
if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) {
_values.SetValue(row, col, d);
} else {
// Cope with Google Sheets export of cells having a formula.
// Rather than exporting the native value, they export the formatted value.
_values.SetValue(row, col, v);
//_values.SetValue(row, col, double.NaN);
}
}
}
}
private object GetErrorType(string v) {
return ExcelErrorValue.Parse(v.ToUpper(CultureInfo.InvariantCulture));
//switch(v.ToUpper())
//{
// case "#DIV/0!":
// return new ExcelErrorValue.cre(eErrorType.Div0);
// case "#REF!":
// return new ExcelErrorValue(eErrorType.Ref);
// case "#N/A":
// return new ExcelErrorValue(eErrorType.NA);
// case "#NAME?":
// return new ExcelErrorValue(eErrorType.Name);
// case "#NULL!":
// return new ExcelErrorValue(eErrorType.Null);
// case "#NUM!":
// return new ExcelErrorValue(eErrorType.Num);
// default:
// return new ExcelErrorValue(eErrorType.Value);
//}
}
///// <summary>
///// Provides access to an individual cell within the worksheet.
///// </summary>
///// <param name="row">The row number in the worksheet</param>
///// <param name="col">The column number in the worksheet</param>
///// <returns></returns>
//internal ExcelCell Cell(int row, int col)
//{
// return new ExcelCell(_values, row, col);
//}
/// <summary>
/// Provides access to a range of cells
/// </summary>
public ExcelRange Cells {
get {
CheckSheetType();
return new(this, 1, 1, ExcelPackage.MaxRows, ExcelPackage.MaxColumns);
}
}
/// <summary>
/// Provides access to the selected range of cells
/// </summary>
public ExcelRange SelectedRange {
get {
CheckSheetType();
return new(this, View.SelectedRange);
}
}
private readonly MergeCellsCollection _mergedCells = new();
/// <summary>
/// Addresses to merged ranges
/// </summary>
public MergeCellsCollection MergedCells {
get {
CheckSheetType();
return _mergedCells;
}
}
/// <summary>
/// Provides access to an individual row within the worksheet so you can set its properties.
/// </summary>
/// <param name="row">The row number in the worksheet</param>
/// <returns></returns>
public ExcelRow Row(int row) {
//ExcelRow r;
//ulong id = ExcelRow.GetRowID(_sheetID, row);
//TODO: Fixa.
//var v = _values.GetValue(row, 0);
//if (v!=null)
//{
// var ri=(RowInternal)v;
// r = new ExcelRow(this, row)
//}
//else
//{
//r = new ExcelRow(this, row);
//_values.SetValue(row, 0, r);
//_rows.Add(r);
//}
CheckSheetType();
if (row < 1 || row > ExcelPackage.MaxRows) {
throw (new ArgumentException("Row number out of bounds"));
}
return new(this, row);
//return r;
}
/// <summary>
/// Provides access to an individual column within the worksheet so you can set its properties.
/// </summary>
/// <param name="col">The column number in the worksheet</param>
/// <returns></returns>
public ExcelColumn Column(int col) {
CheckSheetType();
if (col < 1 || col > ExcelPackage.MaxColumns) {
throw (new ArgumentException("Column number out of bounds"));
}
var column = _values.GetValue(0, col) as ExcelColumn;
if (column != null) {
if (column.ColumnMin != column.ColumnMax) {
int maxCol = column.ColumnMax;
column.ColumnMax = col;
ExcelColumn copy = CopyColumn(column, col + 1, maxCol);
}
} else {
int r = 0,
c = col;
if (_values.PrevCell(ref r, ref c)) {
column = _values.GetValue(0, c) as ExcelColumn;
int maxCol = column.ColumnMax;
if (maxCol >= col) {
column.ColumnMax = col - 1;
if (maxCol > col) {
ExcelColumn newC = CopyColumn(column, col + 1, maxCol);
}
return CopyColumn(column, col, col);
}
}
//foreach (ExcelColumn checkColumn in _columns)
//{
// if (col > checkColumn.ColumnMin && col <= checkColumn.ColumnMax)
// {
// int maxCol = checkColumn.ColumnMax;
// checkColumn.ColumnMax = col - 1;
// if (maxCol > col)
// {
// ExcelColumn newC = CopyColumn(checkColumn, col + 1, maxCol);
// }
// return CopyColumn(checkColumn, col,col);
// }
//}
column = new(this, col);
_values.SetValue(0, col, column);
//_columns.Add(column);
}
return column;
}
/// <summary>
/// Returns the name of the worksheet
/// </summary>
/// <returns>The name of the worksheet</returns>
public override string ToString() {
return Name;
}
internal ExcelColumn CopyColumn(ExcelColumn c, int col, int maxCol) {
ExcelColumn newC = new ExcelColumn(this, col);
newC.ColumnMax = maxCol < ExcelPackage.MaxColumns ? maxCol : ExcelPackage.MaxColumns;
if (c.StyleName != "") {
newC.StyleName = c.StyleName;
} else {
newC.StyleID = c.StyleID;
}
newC.OutlineLevel = c.OutlineLevel;
newC.Phonetic = c.Phonetic;
newC.BestFit = c.BestFit;
//_columns.Add(newC);
_values.SetValue(0, col, newC);
newC._width = c._width;
newC._hidden = c._hidden;
return newC;
}
/// <summary>
/// Make the current worksheet active.
/// </summary>
public void Select() {
View.TabSelected = true;
//Select(Address, true);
}
/// <summary>
/// Selects a range in the worksheet. The active cell is the topmost cell.
/// Make the current worksheet active.
/// </summary>
/// <param name="address">An address range</param>
public void Select(string address) {
Select(address, true);
}
/// <summary>
/// Selects a range in the worksheet. The actice cell is the topmost cell.
/// </summary>
/// <param name="address">A range of cells</param>
/// <param name="selectSheet">Make the sheet active</param>
public void Select(string address, bool selectSheet) {
CheckSheetType();
int toCol,
toRow;
//Get rows and columns and validate as well
ExcelCellBase.GetRowColFromAddress(
address,
out var fromRow,
out var fromCol,
out toRow,
out toCol);
if (selectSheet) {
View.TabSelected = true;
}
View.SelectedRange = address;
View.ActiveCell = ExcelCellBase.GetAddress(fromRow, fromCol);
}
/// <summary>
/// Selects a range in the worksheet. The active cell is the topmost cell of the first address.
/// Make the current worksheet active.
/// </summary>
/// <param name="address">An address range</param>
public void Select(ExcelAddress address) {
CheckSheetType();
Select(address, true);
}
/// <summary>
/// Selects a range in the worksheet. The active cell is the topmost cell of the first address.
/// </summary>
/// <param name="address">A range of cells</param>
/// <param name="selectSheet">Make the sheet active</param>
public void Select(ExcelAddress address, bool selectSheet) {
CheckSheetType();
if (selectSheet) {
View.TabSelected = true;
}
string selAddress =
ExcelCellBase.GetAddress(address.Start.Row, address.Start.Column)
+ ":"
+ ExcelCellBase.GetAddress(address.End.Row, address.End.Column);
if (address.Addresses != null) {
foreach (var a in address.Addresses) {
selAddress +=
" "
+ ExcelCellBase.GetAddress(a.Start.Row, a.Start.Column)
+ ":"
+ ExcelCellBase.GetAddress(a.End.Row, a.End.Column);
}
}
View.SelectedRange = selAddress;
View.ActiveCell = ExcelCellBase.GetAddress(address.Start.Row, address.Start.Column);
}
/// <summary>
/// Inserts a new row into the spreadsheet. Existing rows below the position are
/// shifted down. All formula are updated to take account of the new row.
/// </summary>
/// <param name="rowFrom">The position of the new row</param>
/// <param name="rows">Number of rows to insert</param>
public void InsertRow(int rowFrom, int rows) {
InsertRow(rowFrom, rows, 0);
}
/// <summary>
/// Inserts a new row into the spreadsheet. Existing rows below the position are
/// shifted down. All formula are updated to take account of the new row.
/// </summary>
/// <param name="rowFrom">The position of the new row</param>
/// <param name="rows">Number of rows to insert.</param>
/// <param name="copyStylesFromRow">Copy Styles from this row. Applied to all inserted rows</param>
public void InsertRow(int rowFrom, int rows, int copyStylesFromRow) {
CheckSheetType();
var d = Dimension;
if (rowFrom < 1) {
throw (new ArgumentOutOfRangeException("rowFrom can't be lesser that 1"));
}
//Check that cells aren't shifted outside the boundries
if (d != null && d.End.Row > rowFrom && d.End.Row + rows > ExcelPackage.MaxRows) {
throw (new ArgumentOutOfRangeException(
"Can't insert. Rows will be shifted outside the boundries of the worksheet."));
}
_values.Insert(rowFrom, 0, rows, 0);
_formulas.Insert(rowFrom, 0, rows, 0);
_styles.Insert(rowFrom, 0, rows, 0);
_types.Insert(rowFrom, 0, rows, 0);
_commentsStore.Insert(rowFrom, 0, rows, 0);
_hyperLinks.Insert(rowFrom, 0, rows, 0);
_flags.Insert(rowFrom, 0, rows, 0);
foreach (var f in _sharedFormulas.Values) {
if (f.StartRow >= rowFrom) {
f.StartRow += rows;
}
var a = new ExcelAddressBase(f.Address);
if (a._fromRow >= rowFrom) {
a._fromRow += rows;
a._toRow += rows;
} else if (a._toRow >= rowFrom) {
a._toRow += rows;
}
f.Address = ExcelCellBase.GetAddress(a._fromRow, a._fromCol, a._toRow, a._toCol);
f.Formula = ExcelCellBase.UpdateFormulaReferences(f.Formula, rows, 0, rowFrom, 0);
}
var cse = new CellsStoreEnumerator<object>(_formulas);
while (cse.Next()) {
if (cse.Value is string) {
cse.Value = ExcelCellBase.UpdateFormulaReferences(
cse.Value.ToString(),
rows,
0,
rowFrom,
0);
}
}
FixMergedCellsRow(rowFrom, rows, false);
if (copyStylesFromRow > 0) {
var cseS = new CellsStoreEnumerator<int>(
_styles,
copyStylesFromRow,
0,
copyStylesFromRow,
ExcelPackage.MaxColumns); //Fixes issue 15068 , 15090
while (cseS.Next()) {
for (var r = 0; r < rows; r++) {
_styles.SetValue(rowFrom + r, cseS.Column, cseS.Value);
}
}
}
foreach (var tbl in Tables) {
tbl.Address = tbl.Address.AddRow(rowFrom, rows);
}
}
/// <summary>
/// Inserts a new column into the spreadsheet. Existing columns below the position are
/// shifted down. All formula are updated to take account of the new column.
/// </summary>
/// <param name="columnFrom">The position of the new column</param>
/// <param name="columns">Number of columns to insert</param>
public void InsertColumn(int columnFrom, int columns) {
InsertColumn(columnFrom, columns, 0);
}
///<summary>
/// Inserts a new column into the spreadsheet. Existing column to the left are
/// shifted. All formula are updated to take account of the new column.
/// </summary>
/// <param name="columnFrom">The position of the new column</param>
/// <param name="columns">Number of columns to insert.</param>
/// <param name="copyStylesFromColumn">Copy Styles from this column. Applied to all inserted columns</param>
public void InsertColumn(int columnFrom, int columns, int copyStylesFromColumn) {
CheckSheetType();
var d = Dimension;
if (columnFrom < 1) {
throw (new ArgumentOutOfRangeException("columnFrom can't be lesser that 1"));
}
//Check that cells aren't shifted outside the boundries
if (d != null
&& d.End.Column > columnFrom
&& d.End.Column + columns > ExcelPackage.MaxColumns) {
throw (new ArgumentOutOfRangeException(
"Can't insert. Columns will be shifted outside the boundries of the worksheet."));
}
_values.Insert(0, columnFrom, 0, columns);
_formulas.Insert(0, columnFrom, 0, columns);
_styles.Insert(0, columnFrom, 0, columns);
_types.Insert(0, columnFrom, 0, columns);
_commentsStore.Insert(0, columnFrom, 0, columns);
_hyperLinks.Insert(0, columnFrom, 0, columns);
_flags.Insert(0, columnFrom, 0, columns);
foreach (var f in _sharedFormulas.Values) {
if (f.StartCol >= columnFrom) {
f.StartCol += columns;
}
var a = new ExcelAddressBase(f.Address);
if (a._fromCol >= columnFrom) {
a._fromCol += columns;
a._toCol += columns;
} else if (a._toCol >= columnFrom) {
a._toCol += columns;
}
f.Address = ExcelCellBase.GetAddress(a._fromRow, a._fromCol, a._toRow, a._toCol);
f.Formula = ExcelCellBase.UpdateFormulaReferences(f.Formula, 0, columns, 0, columnFrom);
}
var cse = new CellsStoreEnumerator<object>(_formulas);
while (cse.Next()) {
if (cse.Value is string) {
cse.Value = ExcelCellBase.UpdateFormulaReferences(
cse.Value.ToString(),
0,
columns,
0,
columnFrom);
}
}
FixMergedCellsColumn(columnFrom, columns, false);
var csec = new CellsStoreEnumerator<object>(_values, 0, 1, 0, ExcelPackage.MaxColumns);
var lst = new List<ExcelColumn>();
foreach (var col in csec) {
if (col is ExcelColumn column) {
lst.Add(column);
}
}
for (int i = lst.Count - 1; i >= 0; i--) {
var c = lst[i];
if (c._columnMin >= columnFrom) {
if (c._columnMin + columns <= ExcelPackage.MaxColumns) {
c._columnMin += columns;
} else {
c._columnMin = ExcelPackage.MaxColumns;
}
if (c._columnMax + columns <= ExcelPackage.MaxColumns) {
c._columnMax += columns;
} else {
c._columnMax = ExcelPackage.MaxColumns;
}
} else if (c._columnMax >= columnFrom) {
var cc = c._columnMax - columnFrom;
c._columnMax = columnFrom - 1;
CopyColumn(c, columnFrom + columns, columnFrom + columns + cc);
}
}
if (copyStylesFromColumn > 0) {
for (var c = 0; c < columns; c++) {
var col = Column(columnFrom + c);
col.StyleID = Column(copyStylesFromColumn).StyleID;
}
}
//Adjust tables
foreach (var tbl in Tables) {
if (columnFrom > tbl.Address.Start.Column && columnFrom <= tbl.Address.End.Column) {
InsertTableColumns(columnFrom, columns, tbl);
}
tbl.Address = tbl.Address.AddColumn(columnFrom, columns);
}
}
private static void InsertTableColumns(int columnFrom, int columns, ExcelTable tbl) {
var node = tbl.Columns[0].TopNode.ParentNode;
var ix = columnFrom - tbl.Address.Start.Column - 1;
var insPos = node.ChildNodes[ix];
ix += 2;
for (int i = 0; i < columns; i++) {
var name = tbl.Columns.GetUniqueName(
string.Format("Column{0}", (ix++).ToString(CultureInfo.InvariantCulture)));
XmlElement tableColumn = (XmlElement)
tbl.TableXml.CreateNode(XmlNodeType.Element, "tableColumn", ExcelPackage._schemaMain);
tableColumn.SetAttribute(
"id",
(tbl.Columns.Count + i + 1).ToString(CultureInfo.InvariantCulture));
tableColumn.SetAttribute("name", name);
insPos = node.InsertAfter(tableColumn, insPos);
} //Create tbl Column
tbl._cols = new(tbl);
}
/// <summary>
/// Adds a value to the row of merged cells to fix for inserts or deletes
/// </summary>
/// <param name="row"></param>
/// <param name="rows"></param>
/// <param name="delete"></param>
private void FixMergedCellsRow(int row, int rows, bool delete) {
if (delete) {
_mergedCells._cells.Delete(row, 0, rows, 0);
} else {
_mergedCells._cells.Insert(row, 0, rows, 0);
}
List<int> removeIndex = [];
for (int i = 0; i < _mergedCells.Count; i++) {
if (!string.IsNullOrEmpty(_mergedCells[i])) {
ExcelAddressBase addr = new(_mergedCells[i]),
newAddr;
if (delete) {
newAddr = addr.DeleteRow(row, rows);
if (newAddr == null) {
removeIndex.Add(i);
continue;
}
} else {
newAddr = addr.AddRow(row, rows);
if (newAddr.Address != addr.Address) {
// _mergedCells._cells.Insert(row, 0, rows, 0);
_mergedCells.SetIndex(newAddr, i);
}
}
if (newAddr.Address != addr.Address) {
_mergedCells.List[i] = newAddr._address;
}
}
}
for (int i = removeIndex.Count - 1; i >= 0; i--) {
_mergedCells.List.RemoveAt(removeIndex[i]);
}
}
/// <summary>
/// Adds a value to the row of merged cells to fix for inserts or deletes
/// </summary>
/// <param name="column"></param>
/// <param name="columns"></param>
/// <param name="delete"></param>
private void FixMergedCellsColumn(int column, int columns, bool delete) {
if (delete) {
_mergedCells._cells.Delete(0, column, 0, columns);
} else {
_mergedCells._cells.Insert(0, column, 0, columns);
}
List<int> removeIndex = [];
for (int i = 0; i < _mergedCells.Count; i++) {
if (!string.IsNullOrEmpty(_mergedCells[i])) {
ExcelAddressBase addr = new(_mergedCells[i]),
newAddr;
if (delete) {
newAddr = addr.DeleteColumn(column, columns);
if (newAddr == null) {
removeIndex.Add(i);
continue;
}
} else {
newAddr = addr.AddColumn(column, columns);
if (newAddr.Address != addr.Address) {
_mergedCells.SetIndex(newAddr, i);
}
}
if (newAddr.Address != addr.Address) {
_mergedCells.List[i] = newAddr._address;
}
}
}
for (int i = removeIndex.Count - 1; i >= 0; i--) {
_mergedCells.List.RemoveAt(removeIndex[i]);
}
}
/// <summary>
/// Delete the specified row from the worksheet.
/// </summary>
/// <param name="row">A row to be deleted</param>
public void DeleteRow(int row) {
DeleteRow(row, 1);
}
/// <summary>
/// Delete the specified row from the worksheet.
/// </summary>
/// <param name="rowFrom">The start row</param>
/// <param name="rows">Number of rows to delete</param>
public void DeleteRow(int rowFrom, int rows) {
CheckSheetType();
if (rowFrom < 1 || rowFrom + rows > ExcelPackage.MaxRows) {
throw (new ArgumentException(
"Row out of range. Spans from 1 to "
+ ExcelPackage.MaxRows.ToString(CultureInfo.InvariantCulture)));
}
_values.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns);
_types.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns);
_formulas.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns);
_styles.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns);
_flags.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns);
_commentsStore.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns);
_hyperLinks.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns);
AdjustFormulasRow(rowFrom, rows);
FixMergedCellsRow(rowFrom, rows, true);
foreach (var tbl in Tables) {
tbl.Address = tbl.Address.DeleteRow(rowFrom, rows);
}
}
/// <summary>
/// Delete the specified column from the worksheet.
/// </summary>
/// <param name="column">The column to be deleted</param>
public void DeleteColumn(int column) {
DeleteColumn(column, 1);
}
/// <summary>
/// Delete the specified column from the worksheet.
/// </summary>
/// <param name="columnFrom">The start column</param>
/// <param name="columns">Number of columns to delete</param>
public void DeleteColumn(int columnFrom, int columns) {
if (columnFrom < 1 || columnFrom + columns > ExcelPackage.MaxColumns) {
throw (new ArgumentException(
"Column out of range. Spans from 1 to "
+ ExcelPackage.MaxColumns.ToString(CultureInfo.InvariantCulture)));
}
var col = _values.GetValue(0, columnFrom) as ExcelColumn;
if (col == null) {
var r = 0;
var c = columnFrom;
if (_values.PrevCell(ref r, ref c)) {
col = _values.GetValue(0, c) as ExcelColumn;
if (col._columnMax >= columnFrom) {
col.ColumnMax = columnFrom - 1;
}
}
}
_values.Delete(0, columnFrom, ExcelPackage.MaxRows, columns);
_types.Delete(0, columnFrom, ExcelPackage.MaxRows, columns);
_formulas.Delete(0, columnFrom, ExcelPackage.MaxRows, columns);
_styles.Delete(0, columnFrom, ExcelPackage.MaxRows, columns);
_flags.Delete(0, columnFrom, ExcelPackage.MaxRows, columns);
_commentsStore.Delete(0, columnFrom, ExcelPackage.MaxRows, columns);
_hyperLinks.Delete(0, columnFrom, ExcelPackage.MaxRows, columns);
AdjustFormulasColumn(columnFrom, columns);
FixMergedCellsColumn(columnFrom, columns, true);
var csec = new CellsStoreEnumerator<object>(_values, 0, columnFrom, 0, ExcelPackage.MaxColumns);
foreach (var column in csec) {
if (column is ExcelColumn excelColumn) {
if (excelColumn._columnMin >= columnFrom) {
excelColumn._columnMin -= columns;
excelColumn._columnMax -= columns;
}
}
}
foreach (var tbl in Tables) {
if (columnFrom >= tbl.Address.Start.Column && columnFrom <= tbl.Address.End.Column) {
var node = tbl.Columns[0].TopNode.ParentNode;
var ix = columnFrom - tbl.Address.Start.Column;
for (int i = 0; i < columns; i++) {
if (node.ChildNodes.Count > ix) {
node.RemoveChild(node.ChildNodes[ix]);
}
}
tbl._cols = new(tbl);
}
tbl.Address = tbl.Address.DeleteColumn(columnFrom, columns);
}
}
internal void AdjustFormulasRow(int rowFrom, int rows) {
var delSf = new List<int>();
foreach (var sf in _sharedFormulas.Values) {
var a = new ExcelAddress(sf.Address).DeleteRow(rowFrom, rows);
if (a == null) {
delSf.Add(sf.Index);
} else {
sf.Address = a.Address;
if (sf.StartRow > rowFrom) {
var r = Math.Min(sf.StartRow - rowFrom, rows);
sf.Formula = ExcelCellBase.UpdateFormulaReferences(sf.Formula, -r, 0, rowFrom, 0);
sf.StartRow -= r;
}
}
}
foreach (var ix in delSf) {
_sharedFormulas.Remove(ix);
}
var cse = new CellsStoreEnumerator<object>(
_formulas,
1,
1,
ExcelPackage.MaxRows,
ExcelPackage.MaxColumns);
while (cse.Next()) {
if (cse.Value is string) {
cse.Value = ExcelCellBase.UpdateFormulaReferences(
cse.Value.ToString(),
-rows,
0,
rowFrom,
0);
}
}
}
internal void AdjustFormulasColumn(int columnFrom, int columns) {
var delSf = new List<int>();
foreach (var sf in _sharedFormulas.Values) {
var a = new ExcelAddress(sf.Address).DeleteColumn(columnFrom, columns);
if (a == null) {
delSf.Add(sf.Index);
} else {
sf.Address = a.Address;
if (sf.StartCol > columnFrom) {
var c = Math.Min(sf.StartCol - columnFrom, columns);
sf.Formula = ExcelCellBase.UpdateFormulaReferences(sf.Formula, 0, -c, 0, 1);
sf.StartCol -= c;
}
}
}
foreach (var ix in delSf) {
_sharedFormulas.Remove(ix);
}
var cse = new CellsStoreEnumerator<object>(
_formulas,
1,
1,
ExcelPackage.MaxRows,
ExcelPackage.MaxColumns);
while (cse.Next()) {
if (cse.Value is string) {
cse.Value = ExcelCellBase.UpdateFormulaReferences(
cse.Value.ToString(),
0,
-columns,
0,
columnFrom);
}
}
}
/// <summary>
/// Deletes the specified row from the worksheet.
/// </summary>
/// <param name="rowFrom">The number of the start row to be deleted</param>
/// <param name="rows">Number of rows to delete</param>
/// <param name="shiftOtherRowsUp">Not used. Rows are always shifted</param>
public void DeleteRow(int rowFrom, int rows, bool shiftOtherRowsUp) {
DeleteRow(rowFrom, rows);
}
/// <summary>
/// Get the cell value from thw worksheet
/// </summary>
/// <param name="row">The row number</param>
/// <param name="column">The row number</param>
/// <returns>The value</returns>
public object GetValue(int row, int column) {
CheckSheetType();
var v = _values.GetValue(row, column);
if (v != null) {
if (_flags.GetFlagValue(row, column, CellFlags.RichText)) {
return Cells[row, column].RichText.Text;
}
return v;
}
return null;
}
/// <summary>
/// Get a strongly typed cell value from the worksheet
/// </summary>
/// <typeparam name="T">The type</typeparam>
/// <param name="row">The row number</param>
/// <param name="column">The row number</param>
/// <returns>The value. If the value can't be converted to the specified type, the default value will be returned</returns>
public T GetValue<T>(int row, int column) {
CheckSheetType();
//ulong cellID=ExcelCellBase.GetCellID(SheetID, Row, Column);
var v = _values.GetValue(row, column);
if (v == null) {
return default(T);
}
//var cell=((ExcelCell)_cells[cellID]);
if (_flags.GetFlagValue(row, column, CellFlags.RichText)) {
return (T)(object)Cells[row, column].RichText.Text;
}
return GetTypedValue<T>(v);
}
//Thanks to Michael Tran for parts of this method
internal T GetTypedValue<T>(object v) {
if (v == null) {
return default(T);
}
Type fromType = v.GetType();
Type toType = typeof(T);
if (fromType == toType) {
return (T)v;
}
var cnv = TypeDescriptor.GetConverter(fromType);
if (toType
== typeof(DateTime)) //Handle dates
{
if (fromType == typeof(TimeSpan)) {
return ((T)(object)(new DateTime(((TimeSpan)v).Ticks)));
}
if (fromType == typeof(string)) {
if (DateTime.TryParse(v.ToString(), out var dt)) {
return (T)(object)(dt);
}
return default(T);
}
if (cnv.CanConvertTo(typeof(double))) {
return (T)(object)(DateTime.FromOADate((double)cnv.ConvertTo(v, typeof(double))));
}
return default(T);
}
if (toType
== typeof(TimeSpan)) //Handle timespan
{
if (fromType == typeof(DateTime)) {
return ((T)(object)(new TimeSpan(((DateTime)v).Ticks)));
}
if (fromType == typeof(string)) {
if (TimeSpan.TryParse(v.ToString(), out var ts)) {
return (T)(object)(ts);
}
return default(T);
}
if (cnv.CanConvertTo(typeof(double))) {
return (T)
(object)(new TimeSpan(
DateTime.FromOADate((double)cnv.ConvertTo(v, typeof(double))).Ticks));
}
try {
// Issue 14682 -- "GetValue<decimal>() won't convert strings"
// As suggested, after all special cases, all .NET to do it's
// preferred conversion rather than simply returning the default
return (T)Convert.ChangeType(v, typeof(T));
} catch (Exception) {
// This was the previous behaviour -- no conversion is available.
return default(T);
}
}
if (cnv.CanConvertTo(toType)) {
return (T)cnv.ConvertTo(v, typeof(T));
}
if (toType.IsGenericType && toType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) {
toType = Nullable.GetUnderlyingType(toType);
if (cnv.CanConvertTo(toType)) {
return (T)cnv.ConvertTo(v, typeof(T));
}
}
if (fromType == typeof(double) && toType == typeof(decimal)) {
return (T)(object)Convert.ToDecimal(v);
}
if (fromType == typeof(decimal) && toType == typeof(double)) {
return (T)(object)Convert.ToDouble(v);
}
return default(T);
}
/// <summary>
/// Set the value of a cell
/// </summary>
/// <param name="row">The row number</param>
/// <param name="column">The column number</param>
/// <param name="value">The value</param>
public void SetValue(int row, int column, object value) {
CheckSheetType();
if (row < 1 || column < 1 || row > ExcelPackage.MaxRows && column > ExcelPackage.MaxColumns) {
throw new ArgumentOutOfRangeException("Row or Column out of range");
}
_values.SetValue(row, column, value);
}
/// <summary>
/// Set the value of a cell
/// </summary>
/// <param name="address">The Excel address</param>
/// <param name="value">The value</param>
public void SetValue(string address, object value) {
CheckSheetType();
ExcelCellBase.GetRowCol(address, out var row, out var col, true);
if (row < 1 || col < 1 || row > ExcelPackage.MaxRows && col > ExcelPackage.MaxColumns) {
throw new ArgumentOutOfRangeException("Address is invalid or out of range");
}
_values.SetValue(row, col, value);
}
/// <summary>
/// Get MergeCell Index No
/// </summary>
/// <param name="row"></param>
/// <param name="column"></param>
/// <returns></returns>
public int GetMergeCellId(int row, int column) {
for (int i = 0; i < _mergedCells.Count; i++) {
if (!string.IsNullOrEmpty(_mergedCells[i])) {
ExcelRange range = Cells[_mergedCells[i]];
if (range.Start.Row <= row && row <= range.End.Row) {
if (range.Start.Column <= column && column <= range.End.Column) {
return i + 1;
}
}
}
}
return 0;
}
internal void Save() {
DeletePrinterSettings();
if (!(this is ExcelChartsheet)) {
var d = Dimension;
if (d == null) {
DeleteAllNode("d:dimension/@ref");
} else {
SetXmlNodeString("d:dimension/@ref", d.Address);
}
SaveTables();
SavePivotTables();
}
}
internal void SaveHandler(StreamWriter streamWriter) {
//Create the nodes if they do not exist.
if (this is ExcelChartsheet) {
streamWriter.Write(WorksheetXml.OuterXml);
} else {
CreateNode("d:cols");
CreateNode("d:sheetData");
CreateNode("d:mergeCells");
CreateNode("d:hyperlinks");
CreateNode("d:rowBreaks");
CreateNode("d:colBreaks");
//StreamWriter sw=new StreamWriter(Part.GetStream(FileMode.Create, FileAccess.Write));
var xml = WorksheetXml.OuterXml;
int colStart = 0,
colEnd = 0;
GetBlockPos(xml, "cols", ref colStart, ref colEnd);
streamWriter.Write(xml.Substring(0, colStart));
UpdateColumnData(streamWriter);
int cellStart = colEnd,
cellEnd = colEnd;
GetBlockPos(xml, "sheetData", ref cellStart, ref cellEnd);
streamWriter.Write(xml.Substring(colEnd, cellStart - colEnd));
UpdateRowCellData(streamWriter);
int mergeStart = cellEnd,
mergeEnd = cellEnd;
GetBlockPos(xml, "mergeCells", ref mergeStart, ref mergeEnd);
streamWriter.Write(xml.Substring(cellEnd, mergeStart - cellEnd));
CleanupMergedCells(_mergedCells);
if (_mergedCells.Count > 0) {
UpdateMergedCells(streamWriter);
}
int hyperStart = mergeEnd,
hyperEnd = mergeEnd;
GetBlockPos(xml, "hyperlinks", ref hyperStart, ref hyperEnd);
streamWriter.Write(xml.Substring(mergeEnd, hyperStart - mergeEnd));
UpdateHyperLinks(streamWriter);
int rowBreakStart = hyperEnd,
rowBreakEnd = hyperEnd;
GetBlockPos(xml, "rowBreaks", ref rowBreakStart, ref rowBreakEnd);
streamWriter.Write(xml.Substring(hyperEnd, rowBreakStart - hyperEnd));
UpdateRowBreaks(streamWriter);
int colBreakStart = rowBreakEnd,
colBreakEnd = rowBreakEnd;
GetBlockPos(xml, "colBreaks", ref colBreakStart, ref colBreakEnd);
streamWriter.Write(xml.Substring(rowBreakEnd, colBreakStart - rowBreakEnd));
UpdateColBreaks(streamWriter);
streamWriter.Write(xml.Substring(colBreakEnd, xml.Length - colBreakEnd));
}
}
/// <summary>
/// Delete the printersettings relationship and part.
/// </summary>
private void DeletePrinterSettings() {
//Delete the relationship from the pageSetup tag
XmlAttribute attr = (XmlAttribute)
WorksheetXml.SelectSingleNode("//d:pageSetup/@r:id", NameSpaceManager);
if (attr != null) {
string relId = attr.Value;
//First delete the attribute from the XML
attr.OwnerElement.Attributes.Remove(attr);
if (Part.RelationshipExists(relId)) {
var rel = Part.GetRelationship(relId);
Uri printerSettingsUri = UriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri);
Part.DeleteRelationship(rel.Id);
//Delete the part from the package
if (_package.Package.PartExists(printerSettingsUri)) {
_package.Package.DeletePart(printerSettingsUri);
}
}
}
}
/// <summary>
/// Save all table data
/// </summary>
private void SaveTables() {
foreach (var tbl in Tables) {
if (tbl.ShowHeader || tbl.ShowTotal) {
int colNum = tbl.Address._fromCol;
var colVal = new HashSet<string>();
foreach (var col in tbl.Columns) {
string n = col.Name.ToLower(CultureInfo.InvariantCulture);
if (tbl.ShowHeader) {
n = tbl.WorkSheet.GetValue<string>(
tbl.Address._fromRow,
tbl.Address._fromCol + col.Position);
if (string.IsNullOrEmpty(n)) {
n = col.Name.ToLower(CultureInfo.InvariantCulture);
} else {
col.Name = n;
}
} else {
n = col.Name.ToLower(CultureInfo.InvariantCulture);
}
if (colVal.Contains(n)) {
throw (new InvalidDataException(
string.Format(
"Table {0} Column {1} does not have a unique name.",
tbl.Name,
col.Name)));
}
colVal.Add(n);
col.Name = ConvertUtil.ExcelEncodeString(col.Name);
if (tbl.ShowHeader) {
_values.SetValue(tbl.Address._fromRow, colNum, col.Name);
}
if (tbl.ShowTotal) {
SetTableTotalFunction(tbl, col, colNum);
}
if (!string.IsNullOrEmpty(col.CalculatedColumnFormula)) {
int fromRow = tbl.ShowHeader ? tbl.Address._fromRow + 1 : tbl.Address._fromRow;
int toRow = tbl.ShowTotal ? tbl.Address._toRow - 1 : tbl.Address._toRow;
for (int row = fromRow; row <= toRow; row++) {
//Cell(row, colNum).Formula = col.CalculatedColumnFormula;
SetFormula(row, colNum, col.CalculatedColumnFormula);
}
}
colNum++;
}
}
}
}
internal void SetTableTotalFunction(ExcelTable tbl, ExcelTableColumn col, int colNum = -1) {
if (tbl.ShowTotal == false) {
return;
}
if (colNum == -1) {
for (int i = 0; i < tbl.Columns.Count; i++) {
if (tbl.Columns[i].Name == col.Name) {
colNum = tbl.Address._fromCol + i;
}
}
}
if (col.TotalsRowFunction == RowFunctions.Custom) {
SetFormula(tbl.Address._toRow, colNum, col.TotalsRowFormula);
} else if (col.TotalsRowFunction != RowFunctions.None) {
switch (col.TotalsRowFunction) {
case RowFunctions.Average:
SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "101"));
break;
case RowFunctions.Count:
SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "102"));
break;
case RowFunctions.CountNums:
SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "103"));
break;
case RowFunctions.Max:
SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "104"));
break;
case RowFunctions.Min:
SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "105"));
break;
case RowFunctions.StdDev:
SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "107"));
break;
case RowFunctions.Var:
SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "110"));
break;
case RowFunctions.Sum:
SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "109"));
break;
default:
throw (new("Unknown RowFunction enum"));
}
} else {
_values.SetValue(tbl.Address._toRow, colNum, col.TotalsRowLabel);
}
}
internal void SetFormula(int row, int col, object value) {
_formulas.SetValue(row, col, value);
if (!_values.Exists(row, col)) {
_values.SetValue(row, col, null);
}
}
internal void SetStyle(int row, int col, int value) {
_styles.SetValue(row, col, value);
if (!_values.Exists(row, col)) {
_values.SetValue(row, col, null);
}
}
private void SavePivotTables() {
foreach (var pt in PivotTables) {
if (pt.DataFields.Count > 1) {
XmlElement parentNode;
if (pt.DataOnRows) {
parentNode =
pt.PivotTableXml.SelectSingleNode("//d:rowFields", pt.NameSpaceManager) as XmlElement;
if (parentNode == null) {
pt.CreateNode("d:rowFields");
parentNode =
pt.PivotTableXml.SelectSingleNode("//d:rowFields", pt.NameSpaceManager)
as XmlElement;
}
} else {
parentNode =
pt.PivotTableXml.SelectSingleNode("//d:colFields", pt.NameSpaceManager) as XmlElement;
if (parentNode == null) {
pt.CreateNode("d:colFields");
parentNode =
pt.PivotTableXml.SelectSingleNode("//d:colFields", pt.NameSpaceManager)
as XmlElement;
}
}
if (parentNode.SelectSingleNode("d:field[@ x= \"-2\"]", pt.NameSpaceManager) == null) {
XmlElement fieldNode = pt.PivotTableXml.CreateElement("field", ExcelPackage._schemaMain);
fieldNode.SetAttribute("x", "-2");
parentNode.AppendChild(fieldNode);
}
}
var ws = Workbook.Worksheets[pt.CacheDefinition.SourceRange.WorkSheet];
var t = ws.Tables.GetFromRange(pt.CacheDefinition.SourceRange);
var fields = pt.CacheDefinition.CacheDefinitionXml.SelectNodes(
"d:pivotCacheDefinition/d:cacheFields/d:cacheField",
NameSpaceManager);
int ix = 0;
if (fields != null) {
var flds = new HashSet<string>();
foreach (XmlElement node in fields) {
if (ix >= pt.CacheDefinition.SourceRange.Columns) {
break;
}
var fldName = node.GetAttribute("name"); //Fixes issue 15295 dup name error
if (string.IsNullOrEmpty(fldName)) {
fldName =
(t == null
? pt.CacheDefinition.SourceRange.Offset(0, ix++, 1, 1).Value.ToString()
: t.Columns[ix++].Name);
}
if (flds.Contains(fldName)) {
fldName = GetNewName(flds, fldName);
}
flds.Add(fldName);
node.SetAttribute("name", fldName);
}
foreach (var df in pt.DataFields) {
if (string.IsNullOrEmpty(df.Name)) {
string name;
if (df.Function == DataFieldFunctions.None) {
name = df.Field.Name; //Name must be set or Excel will crash on rename.
} else {
name = df.Function + " of " + df.Field.Name; //Name must be set or Excel will crash on rename.
}
//Make sure name is unique
var newName = name;
var i = 2;
while (pt.DataFields.ExistsDfName(newName, df)) {
newName = name + (i++).ToString(CultureInfo.InvariantCulture);
}
df.Name = newName;
}
}
}
}
}
private string GetNewName(HashSet<string> flds, string fldName) {
int ix = 2;
while (flds.Contains(fldName + ix.ToString(CultureInfo.InvariantCulture))) {
ix++;
}
return fldName + ix.ToString(CultureInfo.InvariantCulture);
}
private static string GetTotalFunction(ExcelTableColumn col, string functionNum) {
return string.Format("SUBTOTAL({0},{1}[{2}])", functionNum, col._tbl.Name, col.Name);
}
private void CleanupMergedCells(MergeCellsCollection mergedCells) {
int i = 0;
while (i < mergedCells.List.Count) {
if (mergedCells[i] == null) {
mergedCells.List.RemoveAt(i);
} else {
i++;
}
}
}
private void UpdateColBreaks(StreamWriter sw) {
StringBuilder breaks = new StringBuilder();
int count = 0;
var cse = new CellsStoreEnumerator<object>(_values, 0, 0, 0, ExcelPackage.MaxColumns);
while (cse.Next()) {
var col = cse.Value as ExcelColumn;
if (col != null && col.PageBreak) {
breaks.AppendFormat("<brk id=\"{0}\" max=\"16383\" man=\"1\" />", cse.Column);
count++;
}
}
if (count > 0) {
sw.Write("<colBreaks count=\"{0}\" manualBreakCount=\"{0}\">{1}</colBreaks>", count, breaks);
}
}
private void UpdateRowBreaks(StreamWriter sw) {
StringBuilder breaks = new StringBuilder();
int count = 0;
var cse = new CellsStoreEnumerator<object>(_values, 0, 0, ExcelPackage.MaxRows, 0);
//foreach(ExcelRow row in _rows)
while (cse.Next()) {
var row = cse.Value as RowInternal;
if (row != null && row.PageBreak) {
breaks.AppendFormat("<brk id=\"{0}\" max=\"1048575\" man=\"1\" />", cse.Row);
count++;
}
}
if (count > 0) {
sw.Write("<rowBreaks count=\"{0}\" manualBreakCount=\"{0}\">{1}</rowBreaks>", count, breaks);
}
}
/// <summary>
/// Inserts the cols collection into the XML document
/// </summary>
private void UpdateColumnData(StreamWriter sw) {
var cse = new CellsStoreEnumerator<object>(_values, 0, 1, 0, ExcelPackage.MaxColumns);
bool first = true;
while (cse.Next()) {
if (first) {
sw.Write("<cols>");
first = false;
}
var col = cse.Value as ExcelColumn;
ExcelStyleCollection<ExcelXfs> cellXfs = _workbook.Styles.CellXfs;
sw.Write("<col min=\"{0}\" max=\"{1}\"", col.ColumnMin, col.ColumnMax);
if (col.Hidden) {
//sbXml.Append(" width=\"0\" hidden=\"1\" customWidth=\"1\"");
sw.Write(" hidden=\"1\"");
} else if (col.BestFit) {
sw.Write(" bestFit=\"1\"");
}
sw.Write(
string.Format(
CultureInfo.InvariantCulture,
" width=\"{0}\" customWidth=\"1\"",
col.Width));
if (col.OutlineLevel > 0) {
sw.Write(" outlineLevel=\"{0}\" ", col.OutlineLevel);
if (col.Collapsed) {
if (col.Hidden) {
sw.Write(" collapsed=\"1\"");
} else {
sw.Write(" collapsed=\"1\" hidden=\"1\""); //Always hidden
}
}
}
if (col.Phonetic) {
sw.Write(" phonetic=\"1\"");
}
var styleId = col.StyleID >= 0 ? cellXfs[col.StyleID].newID : col.StyleID;
if (styleId > 0) {
sw.Write(" style=\"{0}\"", styleId);
}
sw.Write(" />");
}
if (!first) {
sw.Write("</cols>");
}
}
/// <summary>
/// Insert row and cells into the XML document
/// </summary>
private void UpdateRowCellData(StreamWriter sw) {
ExcelStyleCollection<ExcelXfs> cellXfs = _workbook.Styles.CellXfs;
int row = -1;
var ss = _workbook._sharedStrings;
var styles = _workbook.Styles;
var cache = new StringBuilder();
cache.Append("<sheetData>");
//Set a value for cells with style and no value set.
var cseStyle = new CellsStoreEnumerator<int>(
_styles,
0,
0,
ExcelPackage.MaxRows,
ExcelPackage.MaxColumns);
foreach (var s in cseStyle) {
if (!_values.Exists(cseStyle.Row, cseStyle.Column)) {
_values.SetValue(cseStyle.Row, cseStyle.Column, null);
}
}
var cse = new CellsStoreEnumerator<object>(
_values,
1,
0,
ExcelPackage.MaxRows,
ExcelPackage.MaxColumns);
//foreach (IRangeID r in _cells)
while (cse.Next()) {
if (cse.Column > 0) {
int styleId = cellXfs[styles.GetStyleId(this, cse.Row, cse.Column)].newID;
//Add the row element if it's a new row
if (cse.Row != row) {
WriteRow(cache, cellXfs, row, cse.Row);
row = cse.Row;
}
object v = cse.Value;
object formula = _formulas.GetValue(cse.Row, cse.Column);
if (formula is int sfId) {
var f = _sharedFormulas[sfId];
if (f.Address.IndexOf(':') > 0) {
if (f.StartCol == cse.Column && f.StartRow == cse.Row) {
if (f.IsArray) {
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\"{5}><f ref=\"{2}\" t=\"array\">{3}</f>{4}</c>",
cse.CellAddress,
styleId < 0 ? 0 : styleId,
f.Address,
SecurityElement.Escape(f.Formula),
GetFormulaValue(v),
GetCellType(v, true));
} else {
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\"{6}><f ref=\"{2}\" t=\"shared\" si=\"{3}\">{4}</f>{5}</c>",
cse.CellAddress,
styleId < 0 ? 0 : styleId,
f.Address,
sfId,
SecurityElement.Escape(f.Formula),
GetFormulaValue(v),
GetCellType(v, true));
}
} else if (f.IsArray) {
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\" />",
cse.CellAddress,
styleId < 0 ? 0 : styleId);
} else {
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\"{4}><f t=\"shared\" si=\"{2}\" />{3}</c>",
cse.CellAddress,
styleId < 0 ? 0 : styleId,
sfId,
GetFormulaValue(v),
GetCellType(v, true));
}
} else {
// We can also have a single cell array formula
if (f.IsArray) {
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\"{5}><f ref=\"{2}\" t=\"array\">{3}</f>{4}</c>",
cse.CellAddress,
styleId < 0 ? 0 : styleId,
string.Format("{0}:{1}", f.Address, f.Address),
SecurityElement.Escape(f.Formula),
GetFormulaValue(v),
GetCellType(v, true));
} else {
cache.AppendFormat("<c r=\"{0}\" s=\"{1}\">", f.Address, styleId < 0 ? 0 : styleId);
cache.AppendFormat(
"<f>{0}</f>{1}</c>",
SecurityElement.Escape(f.Formula),
GetFormulaValue(v));
}
}
} else if (formula != null && formula.ToString() != "") {
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\"{2}>",
cse.CellAddress,
styleId < 0 ? 0 : styleId,
GetCellType(v, true));
cache.AppendFormat(
"<f>{0}</f>{1}</c>",
SecurityElement.Escape(formula.ToString()),
GetFormulaValue(v));
} else {
if (v == null && styleId > 0) {
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\" />",
cse.CellAddress,
styleId < 0 ? 0 : styleId);
} else if (v != null) {
if ((v.GetType().IsPrimitive
|| v is double
|| v is decimal
|| v is DateTime
|| v is TimeSpan)) {
//string sv = GetValueForXml(v);
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\" {2}>",
cse.CellAddress,
styleId < 0 ? 0 : styleId,
GetCellType(v));
cache.AppendFormat("{0}</c>", GetFormulaValue(v));
} else {
int ix;
if (!ss.ContainsKey(v.ToString())) {
ix = ss.Count;
ss.Add(
v.ToString(),
new() {
isRichText = _flags.GetFlagValue(cse.Row, cse.Column, CellFlags.RichText),
pos = ix,
});
} else {
ix = ss[v.ToString()].pos;
}
cache.AppendFormat(
"<c r=\"{0}\" s=\"{1}\" t=\"s\">",
cse.CellAddress,
styleId < 0 ? 0 : styleId);
cache.AppendFormat("<v>{0}</v></c>", ix);
}
}
}
} else //ExcelRow
{
WriteRow(cache, cellXfs, row, cse.Row);
row = cse.Row;
}
if (cache.Length > 0x600000) {
sw.Write(cache.ToString());
cache = new();
}
}
if (row != -1) {
cache.Append("</row>");
}
cache.Append("</sheetData>");
sw.Write(cache.ToString());
sw.Flush();
}
private object GetFormulaValue(object v) {
if (v != null && v.ToString() != "") {
return "<v>" + SecurityElement.Escape(GetValueForXml(v)) + "</v>"; //Fixes issue 15071
}
return "";
}
private string GetCellType(object v, bool allowStr = false) {
if (v is bool) {
return " t=\"b\"";
}
if ((v is double d && double.IsInfinity(d)) || v is ExcelErrorValue) {
return " t=\"e\"";
}
if (allowStr
&& v != null
&& !(v.GetType().IsPrimitive
|| v is double
|| v is decimal
|| v is DateTime
|| v is TimeSpan)) {
return " t=\"str\"";
}
return "";
}
private string GetValueForXml(object v) {
string s;
try {
if (v is DateTime time) {
double sdv = time.ToOADate();
if (Workbook.Date1904) {
sdv -= ExcelWorkbook._date1904Offset;
}
s = sdv.ToString(CultureInfo.InvariantCulture);
} else if (v is TimeSpan span) {
s = new DateTime(span.Ticks).ToOADate().ToString(CultureInfo.InvariantCulture);
;
} else if (v.GetType().IsPrimitive || v is double || v is decimal) {
if (v is double d && double.IsNaN(d)) {
s = "";
} else if (v is double d1 && double.IsInfinity(d1)) {
s = "#NUM!";
} else {
s = Convert
.ToDouble(v, CultureInfo.InvariantCulture)
.ToString("R15", CultureInfo.InvariantCulture);
}
} else {
s = v.ToString();
}
} catch {
s = "0";
}
return s;
}
private void WriteRow(
StringBuilder cache,
ExcelStyleCollection<ExcelXfs> cellXfs,
int prevRow,
int row) {
if (prevRow != -1) {
cache.Append("</row>");
}
//ulong rowID = ExcelRow.GetRowID(SheetID, row);
cache.AppendFormat("<row r=\"{0}\" ", row);
RowInternal currRow = _values.GetValue(row, 0) as RowInternal;
if (currRow != null) {
if (currRow.Hidden) {
cache.Append("ht=\"0\" hidden=\"1\" ");
} else if (currRow.Height != DefaultRowHeight && currRow.Height >= 0) {
cache.AppendFormat(CultureInfo.InvariantCulture, "ht=\"{0}\" ", currRow.Height);
if (currRow.CustomHeight) {
cache.Append("customHeight=\"1\" ");
}
}
if (currRow.OutlineLevel > 0) {
cache.AppendFormat("outlineLevel =\"{0}\" ", currRow.OutlineLevel);
if (currRow.Collapsed) {
if (currRow.Hidden) {
cache.Append(" collapsed=\"1\" ");
} else {
cache.Append(" collapsed=\"1\" hidden=\"1\" "); //Always hidden
}
}
}
if (currRow.Phonetic) {
cache.Append("ph=\"1\" ");
}
}
var s = _styles.GetValue(row, 0);
if (s > 0) {
cache.AppendFormat("s=\"{0}\" customFormat=\"1\"", cellXfs[s].newID);
}
cache.Append(">");
}
/// <summary>
/// Update xml with hyperlinks
/// </summary>
/// <param name="sw">The stream</param>
private void UpdateHyperLinks(StreamWriter sw) {
Dictionary<string, string> hyps = new Dictionary<string, string>();
var cse = new CellsStoreEnumerator<Uri>(_hyperLinks);
bool first = true;
//foreach (ulong cell in _hyperLinks)
while (cse.Next()) {
if (first) {
sw.Write("<hyperlinks>");
first = false;
}
//int row, col;
var uri = _hyperLinks.GetValue(cse.Row, cse.Column);
//ExcelCell cell = _cells[cellId] as ExcelCell;
if (uri is ExcelHyperLink link && !string.IsNullOrEmpty(link.ReferenceAddress)) {
sw.Write(
"<hyperlink ref=\"{0}\" location=\"{1}\" {2}{3}/>",
Cells[cse.Row, cse.Column, cse.Row + link.RowSpann, cse.Column + link.ColSpann].Address,
ExcelCellBase.GetFullAddress(
SecurityElement.Escape(Name),
SecurityElement.Escape(link.ReferenceAddress)),
string.IsNullOrEmpty(link.Display)
? ""
: "display=\"" + SecurityElement.Escape(link.Display) + "\" ",
string.IsNullOrEmpty(link.ToolTip)
? ""
: "tooltip=\"" + SecurityElement.Escape(link.ToolTip) + "\" ");
} else if (uri != null) {
Uri hyp;
if (uri is ExcelHyperLink hyperLink) {
hyp = hyperLink.OriginalUri;
} else {
hyp = uri;
}
if (!hyps.ContainsKey(hyp.OriginalString)) {
var relationship = Part.CreateRelationship(
hyp,
TargetMode.External,
ExcelPackage._schemaHyperlink);
if (uri is ExcelHyperLink hl) {
sw.Write(
"<hyperlink ref=\"{0}\" {2}{3}r:id=\"{1}\" />",
ExcelCellBase.GetAddress(cse.Row, cse.Column),
relationship.Id,
string.IsNullOrEmpty(hl.Display)
? ""
: "display=\"" + SecurityElement.Escape(hl.Display) + "\" ",
string.IsNullOrEmpty(hl.ToolTip)
? ""
: "tooltip=\"" + SecurityElement.Escape(hl.ToolTip) + "\" ");
} else {
sw.Write(
"<hyperlink ref=\"{0}\" r:id=\"{1}\" />",
ExcelCellBase.GetAddress(cse.Row, cse.Column),
relationship.Id);
}
}
}
}
if (!first) {
sw.Write("</hyperlinks>");
}
}
/// <summary>
/// Dimension address for the worksheet.
/// Top left cell to Bottom right.
/// If the worksheet has no cells, null is returned
/// </summary>
public ExcelAddressBase Dimension {
get {
CheckSheetType();
if (_values.GetDimension(out var fromRow, out var fromCol, out var toRow, out var toCol)) {
var addr = new ExcelAddressBase(fromRow, fromCol, toRow, toCol);
addr._ws = Name;
return addr;
}
return null;
}
}
private ExcelSheetProtection _protection;
/// <summary>
/// Access to sheet protection properties
/// </summary>
public ExcelSheetProtection Protection {
get {
if (_protection == null) {
_protection = new(NameSpaceManager, TopNode);
}
return _protection;
}
}
private ExcelProtectedRangeCollection _protectedRanges;
public ExcelProtectedRangeCollection ProtectedRanges {
get {
if (_protectedRanges == null) {
_protectedRanges = new(NameSpaceManager, TopNode);
}
return _protectedRanges;
}
}
private ExcelTableCollection _tables;
/// <summary>
/// Tables defined in the worksheet.
/// </summary>
public ExcelTableCollection Tables {
get {
CheckSheetType();
if (Workbook._nextTableID == int.MinValue) {
Workbook.ReadAllTables();
}
if (_tables == null) {
_tables = new(this);
}
return _tables;
}
}
private ExcelPivotTableCollection _pivotTables;
/// <summary>
/// Pivottables defined in the worksheet.
/// </summary>
public ExcelPivotTableCollection PivotTables {
get {
CheckSheetType();
if (_pivotTables == null) {
if (Workbook._nextPivotTableID == int.MinValue) {
Workbook.ReadAllTables();
}
_pivotTables = new(this);
}
return _pivotTables;
}
}
private ExcelConditionalFormattingCollection _conditionalFormatting;
/// <summary>
/// ConditionalFormatting defined in the worksheet. Use the Add methods to create ConditionalFormatting and add them to the worksheet. Then
/// set the properties on the instance returned.
/// </summary>
/// <seealso cref="ExcelConditionalFormattingCollection"/>
public ExcelConditionalFormattingCollection ConditionalFormatting {
get {
CheckSheetType();
if (_conditionalFormatting == null) {
_conditionalFormatting = new(this);
}
return _conditionalFormatting;
}
}
private ExcelDataValidationCollection _dataValidation;
/// <summary>
/// DataValidation defined in the worksheet. Use the Add methods to create DataValidations and add them to the worksheet. Then
/// set the properties on the instance returned.
/// </summary>
/// <seealso cref="ExcelDataValidationCollection"/>
public ExcelDataValidationCollection DataValidations {
get {
CheckSheetType();
if (_dataValidation == null) {
_dataValidation = new(this);
}
return _dataValidation;
}
}
/// <summary>
/// Returns the style ID given a style name.
/// The style ID will be created if not found, but only if the style name exists!
/// </summary>
/// <param name="styleName"></param>
/// <returns></returns>
internal int GetStyleId(string styleName) {
ExcelNamedStyleXml namedStyle = null;
Workbook.Styles.NamedStyles.FindById(styleName, ref namedStyle);
if (namedStyle.XfId == int.MinValue) {
namedStyle.XfId = Workbook.Styles.CellXfs.FindIndexById(namedStyle.Style.Id);
}
return namedStyle.XfId;
}
/// <summary>
/// The workbook object
/// </summary>
public ExcelWorkbook Workbook => _workbook;
/// <summary>
/// Get the next ID from a shared formula or an Array formula
/// Sharedforumlas will have an id from 0-x. Array formula ids start from 0x4000001-.
/// </summary>
/// <param name="isArray">If the formula is an array formula</param>
/// <returns></returns>
internal int GetMaxShareFunctionIndex(bool isArray) {
int i = _sharedFormulas.Count + 1;
if (isArray) {
i |= 0x40000000;
}
while (_sharedFormulas.ContainsKey(i)) {
i++;
}
return i;
}
internal void UpdateCellsWithDate1904Setting() {
var cse = new CellsStoreEnumerator<object>(_values);
var offset = Workbook.Date1904 ? -ExcelWorkbook._date1904Offset : ExcelWorkbook._date1904Offset;
while (cse.MoveNext()) {
if (cse.Value is DateTime time) {
try {
double sdv = time.ToOADate();
sdv += offset;
cse.Value = DateTime.FromOADate(sdv);
} catch {}
}
}
}
public string GetFormula(int row, int col) {
var v = _formulas.GetValue(row, col);
if (v is int i) {
return _sharedFormulas[i].GetFormula(row, col, Name);
}
if (v != null) {
return v.ToString();
}
return "";
}
public string GetFormulaR1C1(int row, int col) {
var v = _formulas.GetValue(row, col);
if (v is int i) {
var sf = _sharedFormulas[i];
return ExcelCellBase.TranslateToR1C1(
Formulas.RemoveDummyFunction(sf.Formula),
sf.StartRow,
sf.StartCol);
}
if (v != null) {
return ExcelCellBase.TranslateToR1C1(Formulas.RemoveDummyFunction(v.ToString()), row, col);
}
return "";
}
public string GetFormulaR1C1_V1(int row, int col) {
var v = _formulas.GetValue(row, col);
if (v is int i) {
var sf = _sharedFormulas[i];
return ExcelCellBase.TranslateToR1C1_V1(
Formulas.RemoveDummyFunction(sf.Formula),
sf.StartRow,
sf.StartCol);
}
if (v != null) {
return ExcelCellBase.TranslateToR1C1_V1(Formulas.RemoveDummyFunction(v.ToString()), row, col);
}
return "";
}
public bool IsArrayFormula(int row, int col) =>
_flags.GetFlagValue(row, col, CellFlags.ArrayFormula);
public string GetArrayFormulaAddress(int row, int col) {
var v = _formulas.GetValue(row, col);
if ((v is int i) && (_sharedFormulas[i].IsArray)) {
return _sharedFormulas[i].Address;
}
return "";
}
public int GetStyleId(int row, int col) {
int styleId = 0;
if (!_styles.Exists(row, col, ref styleId) && !_styles.Exists(row, 0, ref styleId)) {
styleId = _styles.GetValue(0, col);
}
return styleId;
}
/// <summary>
/// Get the ExcelColumn for column (span ColumnMin and ColumnMax)
/// </summary>
/// <param name="column"></param>
/// <returns></returns>
internal ExcelColumn GetColumn(int column) {
var c = _values.GetValue(0, column) as ExcelColumn;
if (c == null) {
int row = 0,
col = column;
if (_values.PrevCell(ref row, ref col)) {
c = _values.GetValue(0, col) as ExcelColumn;
if (c != null && c.ColumnMax >= column) {
return c;
}
return null;
}
}
return c;
}
public bool Equals(ExcelWorksheet x, ExcelWorksheet y) {
return x.Name == y.Name
&& x.SheetID == y.SheetID
&& x.WorksheetXml.OuterXml == y.WorksheetXml.OuterXml;
}
public int GetHashCode(ExcelWorksheet obj) {
return obj.WorksheetXml.OuterXml.GetHashCode();
}
}