| /******************************************************************************* | |
| * 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 Added 18-MAR-2010 | |
| * Jan Källman License changed GPL-->LGPL 2011-12-16 | |
| *******************************************************************************/ | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Globalization; | |
| namespace AppsheetEpplus; | |
| public class ExcelTableAddress { | |
| public string Name { get; set; } | |
| public string ColumnSpan { get; set; } | |
| public bool IsAll { get; set; } | |
| public bool IsHeader { get; set; } | |
| public bool IsData { get; set; } | |
| public bool IsTotals { get; set; } | |
| public bool IsThisRow { get; set; } | |
| } | |
| /// <summary> | |
| /// A range address | |
| /// </summary> | |
| /// <remarks>Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5" </remarks> | |
| public class ExcelAddressBase : ExcelCellBase { | |
| protected internal int _fromRow = -1, | |
| _toRow, | |
| _fromCol, | |
| _toCol; | |
| protected internal bool _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed; | |
| protected internal string _wb; | |
| protected internal string _ws; | |
| protected internal string _address; | |
| protected internal event EventHandler AddressChange; | |
| internal enum eAddressCollition { | |
| No, | |
| Partly, | |
| Inside, | |
| Equal, | |
| } | |
| internal enum eShiftType { | |
| Right, | |
| Down, | |
| EntireRow, | |
| EntireColumn, | |
| } | |
| internal ExcelAddressBase() {} | |
| /// <summary> | |
| /// Creates an Address object | |
| /// </summary> | |
| /// <param name="fromRow">start row</param> | |
| /// <param name="fromCol">start column</param> | |
| /// <param name="toRow">End row</param> | |
| /// <param name="toColumn">End column</param> | |
| public ExcelAddressBase(int fromRow, int fromCol, int toRow, int toColumn) { | |
| _fromRow = fromRow; | |
| _toRow = toRow; | |
| _fromCol = fromCol; | |
| _toCol = toColumn; | |
| Validate(); | |
| _address = GetAddress(_fromRow, _fromCol, _toRow, _toCol); | |
| } | |
| /// <summary> | |
| /// Creates an Address object | |
| /// </summary> | |
| /// <param name="fromRow">start row</param> | |
| /// <param name="fromCol">start column</param> | |
| /// <param name="toRow">End row</param> | |
| /// <param name="toColumn">End column</param> | |
| /// <param name="fromRowFixed">start row fixed</param> | |
| /// <param name="fromColFixed">start column fixed</param> | |
| /// <param name="toRowFixed">End row fixed</param> | |
| /// <param name="toColFixed">End column fixed</param> | |
| public ExcelAddressBase( | |
| int fromRow, | |
| int fromCol, | |
| int toRow, | |
| int toColumn, | |
| bool fromRowFixed, | |
| bool fromColFixed, | |
| bool toRowFixed, | |
| bool toColFixed) { | |
| _fromRow = fromRow; | |
| _toRow = toRow; | |
| _fromCol = fromCol; | |
| _toCol = toColumn; | |
| _fromRowFixed = fromRowFixed; | |
| _fromColFixed = fromColFixed; | |
| _toRowFixed = toRowFixed; | |
| _toColFixed = toColFixed; | |
| Validate(); | |
| _address = GetAddress( | |
| _fromRow, | |
| _fromCol, | |
| _toRow, | |
| _toCol, | |
| _fromRowFixed, | |
| fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| /// <summary> | |
| /// Creates an Address object | |
| /// </summary> | |
| /// <remarks>Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5" </remarks> | |
| /// <param name="address">The Excel Address</param> | |
| public ExcelAddressBase(string address) { | |
| SetAddress(address); | |
| } | |
| /// <summary> | |
| /// Creates an Address object | |
| /// </summary> | |
| /// <remarks>Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5" </remarks> | |
| /// <param name="address">The Excel Address</param> | |
| /// <param name="pck">Reference to the package to find information about tables and names</param> | |
| /// <param name="referenceAddress">The address</param> | |
| protected ExcelAddressBase( | |
| string address, | |
| ExcelWorkbook workbook, | |
| ExcelAddressBase referenceAddress) { | |
| SetAddress(address); | |
| SetRcFromTable(workbook, referenceAddress); | |
| } | |
| internal void SetRcFromTable(ExcelWorkbook workbook, ExcelAddressBase referenceAddress) { | |
| if (string.IsNullOrEmpty(_wb) && Table != null) { | |
| foreach (var ws in workbook.Worksheets) { | |
| foreach (var t in ws.Tables) { | |
| if (t.Name.Equals(Table.Name, StringComparison.InvariantCultureIgnoreCase)) { | |
| _ws = ws.Name; | |
| if (Table.IsAll) { | |
| _fromRow = t.Address._fromRow; | |
| _toRow = t.Address._toRow; | |
| } else { | |
| if (Table.IsThisRow) { | |
| if (referenceAddress == null) { | |
| _fromRow = -1; | |
| _toRow = -1; | |
| } else { | |
| _fromRow = referenceAddress._fromRow; | |
| _toRow = _fromRow; | |
| } | |
| } else if (Table.IsHeader && Table.IsData) { | |
| _fromRow = t.Address._fromRow; | |
| _toRow = t.ShowTotal ? t.Address._toRow - 1 : t.Address._toRow; | |
| } else if (Table.IsData && Table.IsTotals) { | |
| _fromRow = t.ShowHeader ? t.Address._fromRow + 1 : t.Address._fromRow; | |
| _toRow = t.Address._toRow; | |
| } else if (Table.IsHeader) { | |
| _fromRow = t.ShowHeader ? t.Address._fromRow : -1; | |
| _toRow = t.ShowHeader ? t.Address._fromRow : -1; | |
| } else if (Table.IsTotals) { | |
| _fromRow = t.ShowTotal ? t.Address._toRow : -1; | |
| _toRow = t.ShowTotal ? t.Address._toRow : -1; | |
| } else { | |
| _fromRow = t.ShowHeader ? t.Address._fromRow + 1 : t.Address._fromRow; | |
| _toRow = t.ShowTotal ? t.Address._toRow - 1 : t.Address._toRow; | |
| } | |
| } | |
| if (string.IsNullOrEmpty(Table.ColumnSpan)) { | |
| _fromCol = t.Address._fromCol; | |
| _toCol = t.Address._toCol; | |
| return; | |
| } | |
| var col = t.Address._fromCol; | |
| var cols = Table.ColumnSpan.Split(':'); | |
| foreach (var c in t.Columns) { | |
| if (_fromCol <= 0 | |
| && cols[0] | |
| .Equals( | |
| c.Name, | |
| StringComparison.InvariantCultureIgnoreCase)) //Issue15063 Add invariant igore case | |
| { | |
| _fromCol = col; | |
| if (cols.Length == 1) { | |
| _toCol = _fromCol; | |
| return; | |
| } | |
| } else if (cols.Length > 1 | |
| && _fromCol > 0 | |
| && cols[1] | |
| .Equals( | |
| c.Name, | |
| StringComparison.InvariantCultureIgnoreCase)) //Issue15063 Add invariant igore case | |
| { | |
| _toCol = col; | |
| return; | |
| } | |
| col++; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Address is an defined name | |
| /// </summary> | |
| /// <param name="address">the name</param> | |
| /// <param name="isName">Should always be true</param> | |
| internal ExcelAddressBase(string address, bool isName) { | |
| if (isName) { | |
| _address = address; | |
| _fromRow = -1; | |
| _fromCol = -1; | |
| _toRow = -1; | |
| _toCol = -1; | |
| _start = null; | |
| _end = null; | |
| } else { | |
| SetAddress(address); | |
| } | |
| } | |
| protected internal void SetAddress(string address) { | |
| address = address.Trim(); | |
| if (address.StartsWith("'")) { | |
| SetWbWs(address); | |
| } else if (address.StartsWith( | |
| "[")) //Remove any external reference | |
| { | |
| SetWbWs(address); | |
| } else { | |
| _address = address; | |
| } | |
| if (_address.IndexOfAny([',', '!', '[']) > -1) { | |
| //Advanced address. Including Sheet or multi or table. | |
| ExtractAddress(_address); | |
| } else { | |
| //Simple address | |
| GetRowColFromAddress( | |
| _address, | |
| out _fromRow, | |
| out _fromCol, | |
| out _toRow, | |
| out _toCol, | |
| out _fromRowFixed, | |
| out _fromColFixed, | |
| out _toRowFixed, | |
| out _toColFixed); | |
| _addresses = null; | |
| _start = null; | |
| _end = null; | |
| } | |
| _address = address; | |
| Validate(); | |
| } | |
| internal void ChangeAddress() { | |
| if (AddressChange != null) { | |
| AddressChange(this, new()); | |
| } | |
| } | |
| private void SetWbWs(string address) { | |
| int pos = 0; | |
| // Get Workbook, if any | |
| if (address[pos] == '[') { | |
| pos = address.IndexOf("]"); | |
| _wb = address.Substring(1, pos - 1); | |
| pos++; | |
| } else { | |
| _wb = ""; | |
| } | |
| // Get Worksheet | |
| if (address[pos] == '\'') { | |
| int startPos = pos; | |
| pos = address.IndexOf("'", pos + 1); | |
| while (pos < address.Length && address[pos + 1] == '\'') { | |
| pos = address.IndexOf("'", pos + 2); | |
| } | |
| _ws = address.Substring(startPos + 1, pos - startPos - 1).Replace("''", "'"); | |
| pos++; | |
| } else { | |
| int startPos = pos; | |
| pos = address.IndexOf("!", pos); | |
| if (pos > -1) { | |
| _ws = address.Substring(startPos, pos - startPos); | |
| } | |
| } | |
| // Get Address | |
| pos = address.IndexOf("!", pos); | |
| if (pos > -1) { | |
| _address = address.Substring(pos + 1); | |
| } else { | |
| _address = ""; | |
| } | |
| } | |
| internal void ChangeWorksheet(string wsName, string newWs) { | |
| if (_ws == wsName) { | |
| _ws = newWs; | |
| } | |
| var fullAddress = GetAddress(); | |
| if (Addresses != null) { | |
| foreach (var a in Addresses) { | |
| if (a._ws == wsName) { | |
| a._ws = newWs; | |
| fullAddress += "," + a.GetAddress(); | |
| } else { | |
| fullAddress += "," + a._address; | |
| } | |
| } | |
| } | |
| _address = fullAddress; | |
| } | |
| private string GetAddress() { | |
| var adr = ""; | |
| if (string.IsNullOrEmpty(_wb)) { | |
| adr = "[" + _wb + "]"; | |
| } | |
| if (string.IsNullOrEmpty(_ws)) { | |
| adr += string.Format("'{0}'!", _ws); | |
| } | |
| adr += GetAddress(_fromRow, _fromCol, _toRow, _toCol); | |
| return adr; | |
| } | |
| private ExcelCellAddress _start; | |
| /// <summary> | |
| /// Gets the row and column of the top left cell. | |
| /// </summary> | |
| /// <value>The start row column.</value> | |
| public ExcelCellAddress Start { | |
| get { | |
| if (_start == null) { | |
| _start = new(_fromRow, _fromCol); | |
| } | |
| return _start; | |
| } | |
| } | |
| private ExcelCellAddress _end; | |
| /// <summary> | |
| /// Gets the row and column of the bottom right cell. | |
| /// </summary> | |
| /// <value>The end row column.</value> | |
| public ExcelCellAddress End { | |
| get { | |
| if (_end == null) { | |
| _end = new(_toRow, _toCol); | |
| } | |
| return _end; | |
| } | |
| } | |
| private ExcelTableAddress _table; | |
| public ExcelTableAddress Table => _table; | |
| /// <summary> | |
| /// The address for the range | |
| /// </summary> | |
| public virtual string Address => _address; | |
| /// <summary> | |
| /// If the address is a defined name | |
| /// </summary> | |
| public bool IsName => _fromRow < 0; | |
| /// <summary> | |
| /// Returns the address text | |
| /// </summary> | |
| /// <returns></returns> | |
| public override string ToString() { | |
| return _address; | |
| } | |
| private string _firstAddress; | |
| /// <summary> | |
| /// returns the first address if the address is a multi address. | |
| /// A1:A2,B1:B2 returns A1:A2 | |
| /// </summary> | |
| internal string FirstAddress { | |
| get { | |
| if (string.IsNullOrEmpty(_firstAddress)) { | |
| return _address; | |
| } | |
| return _firstAddress; | |
| } | |
| } | |
| internal string AddressSpaceSeparated => _address.Replace(',', ' '); //Conditional formatting and a few other places use space as separator for mulit addresses. | |
| /// <summary> | |
| /// Validate the address | |
| /// </summary> | |
| protected void Validate() { | |
| if (_fromRow > _toRow || _fromCol > _toCol) { | |
| throw new ArgumentOutOfRangeException( | |
| "Start cell Address must be less or equal to End cell address"); | |
| } | |
| } | |
| internal string WorkSheet => _ws; | |
| protected internal List<ExcelAddress> _addresses; | |
| internal virtual List<ExcelAddress> Addresses => _addresses; | |
| private bool ExtractAddress(string fullAddress) { | |
| var brackPos = new Stack<int>(); | |
| var bracketParts = new List<string>(); | |
| string first = "", | |
| second = ""; | |
| bool isText = false, | |
| hasSheet = false; | |
| try { | |
| if (fullAddress == "#REF!") { | |
| SetAddress(ref fullAddress, ref second, ref hasSheet); | |
| return true; | |
| } | |
| if (fullAddress.StartsWith("!")) { | |
| // invalid address! | |
| return false; | |
| } | |
| for (int i = 0; i < fullAddress.Length; i++) { | |
| var c = fullAddress[i]; | |
| if (c == '\'') { | |
| if (isText && i + 1 < fullAddress.Length && fullAddress[i] == '\'') { | |
| if (hasSheet) { | |
| second += c; | |
| } else { | |
| first += c; | |
| } | |
| } | |
| isText = !isText; | |
| } else { | |
| if (brackPos.Count > 0) { | |
| if (c == '[' && !isText) { | |
| brackPos.Push(i); | |
| } else if (c == ']' && !isText) { | |
| if (brackPos.Count > 0) { | |
| var from = brackPos.Pop(); | |
| bracketParts.Add(fullAddress.Substring(from + 1, i - from - 1)); | |
| if (brackPos.Count == 0) { | |
| HandleBrackets(first, second, bracketParts); | |
| } | |
| } else { | |
| //Invalid address! | |
| return false; | |
| } | |
| } | |
| } else if (c == '[' && !isText) { | |
| brackPos.Push(i); | |
| } else if (c == '!' && !isText && !first.EndsWith("#REF") && !second.EndsWith("#REF")) { | |
| hasSheet = true; | |
| } else if (c == ',' && !isText) { | |
| SetAddress(ref first, ref second, ref hasSheet); | |
| } else { | |
| if (hasSheet) { | |
| second += c; | |
| } else { | |
| first += c; | |
| } | |
| } | |
| } | |
| } | |
| if (Table == null) { | |
| SetAddress(ref first, ref second, ref hasSheet); | |
| } | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| private void HandleBrackets(string first, string second, List<string> bracketParts) { | |
| if (!string.IsNullOrEmpty(first)) { | |
| _table = new(); | |
| Table.Name = first; | |
| foreach (var s in bracketParts) { | |
| if (s.IndexOf("[") < 0) { | |
| switch (s.ToLower(CultureInfo.InvariantCulture)) { | |
| case "#all": | |
| _table.IsAll = true; | |
| break; | |
| case "#headers": | |
| _table.IsHeader = true; | |
| break; | |
| case "#data": | |
| _table.IsData = true; | |
| break; | |
| case "#totals": | |
| _table.IsTotals = true; | |
| break; | |
| case "#this row": | |
| _table.IsThisRow = true; | |
| break; | |
| default: | |
| if (string.IsNullOrEmpty(_table.ColumnSpan)) { | |
| _table.ColumnSpan = s; | |
| } else { | |
| _table.ColumnSpan += ":" + s; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| internal eAddressCollition Collide(ExcelAddressBase address) { | |
| if (address.WorkSheet != WorkSheet && address.WorkSheet != null) { | |
| return eAddressCollition.No; | |
| } | |
| if (address._fromRow > _toRow | |
| || address._fromCol > _toCol | |
| || _fromRow > address._toRow | |
| || _fromCol > address._toCol) { | |
| return eAddressCollition.No; | |
| } | |
| if (address._fromRow == _fromRow | |
| && address._fromCol == _fromCol | |
| && address._toRow == _toRow | |
| && address._toCol == _toCol) { | |
| return eAddressCollition.Equal; | |
| } | |
| if (address._fromRow >= _fromRow | |
| && address._toRow <= _toRow | |
| && address._fromCol >= _fromCol | |
| && address._toCol <= _toCol) { | |
| return eAddressCollition.Inside; | |
| } | |
| return eAddressCollition.Partly; | |
| } | |
| internal ExcelAddressBase AddRow(int row, int rows, bool setFixed = false) { | |
| if (row > _toRow) { | |
| return this; | |
| } | |
| if (row <= _fromRow) { | |
| return new( | |
| (setFixed && _fromRowFixed ? _fromRow : _fromRow + rows), | |
| _fromCol, | |
| (setFixed && _toRowFixed ? _toRow : _toRow + rows), | |
| _toCol, | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| return new( | |
| _fromRow, | |
| _fromCol, | |
| (setFixed && _toRowFixed ? _toRow : _toRow + rows), | |
| _toCol, | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| internal ExcelAddressBase DeleteRow(int row, int rows, bool setFixed = false) { | |
| if (row | |
| > _toRow) //After | |
| { | |
| return this; | |
| } | |
| if (row + rows | |
| <= _fromRow) //Before | |
| { | |
| return new( | |
| (setFixed && _fromRowFixed ? _fromRow : _fromRow - rows), | |
| _fromCol, | |
| (setFixed && _toRowFixed ? _toRow : _toRow - rows), | |
| _toCol, | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| if (row <= _fromRow | |
| && row + rows | |
| > _toRow) //Inside | |
| { | |
| return null; | |
| } //Partly | |
| if (row <= _fromRow) { | |
| return new( | |
| row, | |
| _fromCol, | |
| (setFixed && _toRowFixed ? _toRow : _toRow - rows), | |
| _toCol, | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| return new( | |
| _fromRow, | |
| _fromCol, | |
| (setFixed && _toRowFixed | |
| ? _toRow | |
| : _toRow - rows < row | |
| ? row - 1 | |
| : _toRow - rows), | |
| _toCol, | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| internal ExcelAddressBase AddColumn(int col, int cols, bool setFixed = false) { | |
| if (col > _toCol) { | |
| return this; | |
| } | |
| if (col <= _fromCol) { | |
| return new( | |
| _fromRow, | |
| (setFixed && _fromColFixed ? _fromCol : _fromCol + cols), | |
| _toRow, | |
| (setFixed && _toColFixed ? _toCol : _toCol + cols), | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| return new( | |
| _fromRow, | |
| _fromCol, | |
| _toRow, | |
| (setFixed && _toColFixed ? _toCol : _toCol + cols), | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| internal ExcelAddressBase DeleteColumn(int col, int cols, bool setFixed = false) { | |
| if (col | |
| > _toCol) //After | |
| { | |
| return this; | |
| } | |
| if (col + cols | |
| <= _fromCol) //Before | |
| { | |
| return new( | |
| _fromRow, | |
| (setFixed && _fromColFixed ? _fromCol : _fromCol - cols), | |
| _toRow, | |
| (setFixed && _toColFixed ? _toCol : _toCol - cols), | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| if (col <= _fromCol | |
| && col + cols | |
| > _toCol) //Inside | |
| { | |
| return null; | |
| } //Partly | |
| if (col <= _fromCol) { | |
| return new( | |
| _fromRow, | |
| col, | |
| _toRow, | |
| (setFixed && _toColFixed ? _toCol : _toCol - cols), | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| return new( | |
| _fromRow, | |
| _fromCol, | |
| _toRow, | |
| (setFixed && _toColFixed | |
| ? _toCol | |
| : _toCol - cols < col | |
| ? col - 1 | |
| : _toCol - cols), | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| internal ExcelAddressBase Insert( | |
| ExcelAddressBase address, | |
| eShiftType shift /*, out ExcelAddressBase topAddress, out ExcelAddressBase leftAddress, out ExcelAddressBase rightAddress, out ExcelAddressBase bottomAddress*/) { | |
| //Before or after, no change | |
| //if ((_toRow > address._fromRow && _toCol > address.column) || | |
| // (_fromRow > address._toRow && column > address._toCol)) | |
| if (_toRow < address._fromRow | |
| || _toCol < address._fromCol | |
| || (_fromRow > address._toRow && _fromCol > address._toCol)) { | |
| //topAddress = null; | |
| //leftAddress = null; | |
| //rightAddress = null; | |
| //bottomAddress = null; | |
| return this; | |
| } | |
| int rows = address.Rows; | |
| int cols = address.Columns; | |
| string retAddress = ""; | |
| if (shift == eShiftType.Right) { | |
| if (address._fromRow > _fromRow) { | |
| retAddress = GetAddress( | |
| _fromRow, | |
| _fromCol, | |
| address._fromRow, | |
| _toCol, | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| if (address._fromCol > _fromCol) { | |
| retAddress = GetAddress( | |
| _fromRow < address._fromRow ? _fromRow : address._fromRow, | |
| _fromCol, | |
| address._fromRow, | |
| _toCol, | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| } | |
| } | |
| if (_toRow < address._fromRow) { | |
| if (_fromRow < address._fromRow) {} | |
| } | |
| return null; | |
| } | |
| private void SetAddress(ref string first, ref string second, ref bool hasSheet) { | |
| string ws, | |
| address; | |
| if (hasSheet) { | |
| ws = first; | |
| address = second; | |
| first = ""; | |
| second = ""; | |
| } else { | |
| address = first; | |
| ws = ""; | |
| first = ""; | |
| } | |
| hasSheet = false; | |
| if (string.IsNullOrEmpty(_firstAddress)) { | |
| if (string.IsNullOrEmpty(_ws) || !string.IsNullOrEmpty(ws)) { | |
| _ws = ws; | |
| } | |
| _firstAddress = address; | |
| GetRowColFromAddress( | |
| address, | |
| out _fromRow, | |
| out _fromCol, | |
| out _toRow, | |
| out _toCol, | |
| out _fromRowFixed, | |
| out _fromColFixed, | |
| out _toRowFixed, | |
| out _toColFixed); | |
| } else { | |
| if (_addresses == null) { | |
| _addresses = []; | |
| } | |
| _addresses.Add(new(_ws, address)); | |
| } | |
| } | |
| internal enum AddressType { | |
| Invalid, | |
| InternalAddress, | |
| ExternalAddress, | |
| InternalName, | |
| ExternalName, | |
| Formula, | |
| } | |
| internal static AddressType IsValid(string address) { | |
| if (address == "#REF!") { | |
| return AddressType.Invalid; | |
| } | |
| if (double.TryParse( | |
| address, | |
| NumberStyles.Any, | |
| CultureInfo.InvariantCulture, | |
| out _)) //A double, no valid address | |
| { | |
| return AddressType.Invalid; | |
| } | |
| if (IsFormula(address)) { | |
| return AddressType.Formula; | |
| } | |
| if (SplitAddress(address, out var wb, out _, out var intAddress)) { | |
| if (intAddress.Contains( | |
| "[")) //Table reference | |
| { | |
| return string.IsNullOrEmpty(wb) ? AddressType.InternalAddress : AddressType.ExternalAddress; | |
| } | |
| if (intAddress.Contains(",")) { | |
| intAddress = intAddress.Substring(0, intAddress.IndexOf(',')); | |
| } | |
| if (IsAddress(intAddress)) { | |
| return string.IsNullOrEmpty(wb) ? AddressType.InternalAddress : AddressType.ExternalAddress; | |
| } | |
| return string.IsNullOrEmpty(wb) ? AddressType.InternalName : AddressType.ExternalName; | |
| } | |
| return AddressType.Invalid; | |
| } | |
| private static bool IsAddress(string intAddress) { | |
| if (string.IsNullOrEmpty(intAddress)) { | |
| return false; | |
| } | |
| var cells = intAddress.Split(':'); | |
| int toRow, | |
| toCol; | |
| if (!GetRowCol(cells[0], out var fromRow, out var fromCol, false)) { | |
| return false; | |
| } | |
| if (cells.Length > 1) { | |
| if (!GetRowCol(cells[1], out toRow, out toCol, false)) { | |
| return false; | |
| } | |
| } else { | |
| toRow = fromRow; | |
| toCol = fromCol; | |
| } | |
| if (fromRow <= toRow | |
| && fromCol <= toCol | |
| && fromCol > -1 | |
| && toCol <= ExcelPackage.MaxColumns | |
| && fromRow > -1 | |
| && toRow <= ExcelPackage.MaxRows) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| private static bool SplitAddress( | |
| string address, | |
| out string wb, | |
| out string ws, | |
| out string intAddress) { | |
| wb = ""; | |
| ws = ""; | |
| intAddress = ""; | |
| var text = ""; | |
| bool isText = false; | |
| var brackPos = -1; | |
| for (int i = 0; i < address.Length; i++) { | |
| if (address[i] == '\'') { | |
| isText = !isText; | |
| if (i > 0 && address[i - 1] == '\'') { | |
| text += "'"; | |
| } | |
| } else { | |
| if (address[i] == '!' && !isText) { | |
| if (text.Length > 0 && text[0] == '[') { | |
| wb = text.Substring(1, text.IndexOf("]") - 1); | |
| ws = text.Substring(text.IndexOf("]") + 1); | |
| } else { | |
| ws = text; | |
| } | |
| intAddress = address.Substring(i + 1); | |
| return true; | |
| } | |
| if (address[i] == '[' && !isText) { | |
| if (i | |
| > 0) //Table reference return full address; | |
| { | |
| intAddress = address; | |
| return true; | |
| } | |
| brackPos = i; | |
| } else if (address[i] == ']' && !isText) { | |
| if (brackPos > -1) { | |
| wb = text; | |
| text = ""; | |
| } else { | |
| return false; | |
| } | |
| } else { | |
| text += address[i]; | |
| } | |
| } | |
| } | |
| intAddress = text; | |
| return true; | |
| } | |
| private static bool IsFormula(string address) { | |
| var isText = false; | |
| for (int i = 0; i < address.Length; i++) { | |
| if (address[i] == '\'') { | |
| isText = !isText; | |
| } else { | |
| if (isText == false | |
| && address | |
| .Substring(i, 1) | |
| .IndexOfAny(['(', ')', '+', '-', '*', '/', '.', '=', '^', '&', '%', '\"']) > -1) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| public int Rows => _toRow - _fromRow + 1; | |
| public int Columns => _toCol - _fromCol + 1; | |
| internal static String GetWorkbookPart(string address) { | |
| int ix; | |
| if (address[0] == '[') { | |
| ix = address.IndexOf(']') + 1; | |
| if (ix > 0) { | |
| return address.Substring(1, ix - 2); | |
| } | |
| } | |
| return ""; | |
| } | |
| internal static string GetWorksheetPart(string address, string defaultWorkSheet, ref int endIx) { | |
| if (address == "") { | |
| return defaultWorkSheet; | |
| } | |
| var ix = 0; | |
| if (address[0] == '[') { | |
| ix = address.IndexOf(']') + 1; | |
| } | |
| if (ix > 0 && ix < address.Length) { | |
| if (address[ix] == '\'') { | |
| return GetString(address, ix, out endIx); | |
| } | |
| var ixEnd = address.IndexOf('!', ix); | |
| if (ixEnd > ix) { | |
| return address.Substring(ix, ixEnd - ix); | |
| } | |
| return defaultWorkSheet; | |
| } | |
| return defaultWorkSheet; | |
| } | |
| internal static void SplitAddress( | |
| string fullAddress, | |
| out string wb, | |
| out string ws, | |
| out string address, | |
| string defaultWorksheet = "") { | |
| wb = GetWorkbookPart(fullAddress); | |
| int ix = 0; | |
| ws = GetWorksheetPart(fullAddress, defaultWorksheet, ref ix); | |
| if (ix < fullAddress.Length) { | |
| if (fullAddress[ix] == '!') { | |
| address = fullAddress.Substring(ix + 1); | |
| } else { | |
| address = fullAddress.Substring(ix); | |
| } | |
| } else { | |
| address = ""; | |
| } | |
| } | |
| private static string GetString(string address, int ix, out int endIx) { | |
| var strIx = address.IndexOf("''"); | |
| while (strIx > -1) { | |
| strIx = address.IndexOf("''"); | |
| } | |
| endIx = address.IndexOf("'"); | |
| return address.Substring(ix, endIx - ix).Replace("''", "'"); | |
| } | |
| internal bool IsValidRowCol() { | |
| return !(_fromRow > _toRow | |
| || _fromCol > _toCol | |
| || _fromRow < 1 | |
| || _fromCol < 1 | |
| || _toRow > ExcelPackage.MaxRows | |
| || _toCol > ExcelPackage.MaxColumns); | |
| } | |
| } | |
| /// <summary> | |
| /// Range address with the address property readonly | |
| /// </summary> | |
| public class ExcelAddress : ExcelAddressBase { | |
| internal ExcelAddress() {} | |
| public ExcelAddress(int fromRow, int fromCol, int toRow, int toColumn) | |
| : base(fromRow, fromCol, toRow, toColumn) { | |
| _ws = ""; | |
| } | |
| public ExcelAddress(string address) | |
| : base(address) {} | |
| internal ExcelAddress(string ws, string address) | |
| : base(address) { | |
| if (string.IsNullOrEmpty(_ws)) { | |
| _ws = ws; | |
| } | |
| } | |
| internal ExcelAddress(string ws, string address, bool isName) | |
| : base(address, isName) { | |
| if (string.IsNullOrEmpty(_ws)) { | |
| _ws = ws; | |
| } | |
| } | |
| public ExcelAddress(string address, ExcelWorkbook workbook, ExcelAddressBase referenceAddress) | |
| : base(address, workbook, referenceAddress) {} | |
| /// <summary> | |
| /// The address for the range | |
| /// </summary> | |
| /// <remarks>Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5" </remarks> | |
| public new string Address { | |
| get { | |
| if (string.IsNullOrEmpty(_address) && _fromRow > 0) { | |
| _address = GetAddress(_fromRow, _fromCol, _toRow, _toCol); | |
| } | |
| return _address; | |
| } | |
| set { | |
| SetAddress(value); | |
| ChangeAddress(); | |
| } | |
| } | |
| } | |
| public class ExcelFormulaAddress : ExcelAddressBase { | |
| private bool _fromRowFixed, | |
| _toRowFixed, | |
| _fromColFixed, | |
| _toColFixed; | |
| public ExcelFormulaAddress(int fromRow, int fromCol, int toRow, int toColumn) | |
| : base(fromRow, fromCol, toRow, toColumn) { | |
| _ws = ""; | |
| } | |
| public ExcelFormulaAddress(string address) | |
| : base(address) { | |
| SetFixed(); | |
| } | |
| private void SetFixed() { | |
| if (Address.IndexOf("[") >= 0) { | |
| return; | |
| } | |
| var address = FirstAddress; | |
| if (_fromRow == _toRow && _fromCol == _toCol) { | |
| GetFixed(address, out _fromRowFixed, out _fromColFixed); | |
| } else { | |
| var cells = address.Split(':'); | |
| GetFixed(cells[0], out _fromRowFixed, out _fromColFixed); | |
| GetFixed(cells[1], out _toRowFixed, out _toColFixed); | |
| } | |
| } | |
| private void GetFixed(string address, out bool rowFixed, out bool colFixed) { | |
| rowFixed = colFixed = false; | |
| var ix = address.IndexOf('$'); | |
| while (ix > -1) { | |
| ix++; | |
| if (ix < address.Length) { | |
| if (address[ix] >= '0' && address[ix] <= '9') { | |
| rowFixed = true; | |
| break; | |
| } | |
| colFixed = true; | |
| } | |
| ix = address.IndexOf('$', ix); | |
| } | |
| } | |
| /// <summary> | |
| /// The address for the range | |
| /// </summary> | |
| /// <remarks>Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5" </remarks> | |
| public new string Address { | |
| get { | |
| if (string.IsNullOrEmpty(_address) && _fromRow > 0) { | |
| _address = GetAddress( | |
| _fromRow, | |
| _fromCol, | |
| _toRow, | |
| _toCol, | |
| _fromRowFixed, | |
| _toRowFixed, | |
| _fromColFixed, | |
| _toColFixed); | |
| } | |
| return _address; | |
| } | |
| set { | |
| SetAddress(value); | |
| ChangeAddress(); | |
| SetFixed(); | |
| } | |
| } | |
| internal new List<ExcelFormulaAddress> _addresses; | |
| public new List<ExcelFormulaAddress> Addresses { | |
| get { | |
| if (_addresses == null) { | |
| _addresses = []; | |
| } | |
| return _addresses; | |
| } | |
| } | |
| internal string GetOffset(int row, int column) { | |
| int fromRow = _fromRow, | |
| fromCol = _fromCol, | |
| toRow = _toRow, | |
| tocol = _toCol; | |
| var isMulti = (fromRow != toRow || fromCol != tocol); | |
| if (!_fromRowFixed) { | |
| fromRow += row; | |
| } | |
| if (!_fromColFixed) { | |
| fromCol += column; | |
| } | |
| if (isMulti) { | |
| if (!_toRowFixed) { | |
| toRow += row; | |
| } | |
| if (!_toColFixed) { | |
| tocol += column; | |
| } | |
| } else { | |
| toRow = fromRow; | |
| tocol = fromCol; | |
| } | |
| string a = GetAddress( | |
| fromRow, | |
| fromCol, | |
| toRow, | |
| tocol, | |
| _fromRowFixed, | |
| _fromColFixed, | |
| _toRowFixed, | |
| _toColFixed); | |
| if (Addresses != null) { | |
| foreach (var sa in Addresses) { | |
| a += "," + sa.GetOffset(row, column); | |
| } | |
| } | |
| return a; | |
| } | |
| } |