/*******************************************************************************
* 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.Text;
using System.Text.RegularExpressions;
namespace OfficeOpenXml
{
///
/// A range address
///
/// Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5"
public class ExcelAddressBase : ExcelCellBase
{
internal protected int _fromRow=-1, _toRow, _fromCol, _toCol;
internal protected string _wb;
internal protected string _ws;
internal protected string _address;
internal protected event EventHandler AddressChange;
internal enum eAddressCollition
{
No,
Partly,
Inside,
Equal
}
#region "Constructors"
internal ExcelAddressBase()
{
}
///
/// Creates an Address object
///
/// start row
/// start column
/// End row
/// End column
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);
}
///
/// Creates an Address object
///
/// Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5"
/// The Excel Address
public ExcelAddressBase(string address)
{
SetAddress(address);
}
///
/// Address is an defined name
///
/// the name
/// Should always be true
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)
{
if(address.StartsWith("'"))
{
int pos = address.IndexOf("'", 1);
SetWbWs(address.Substring(1,pos-1).Replace("''","'"));
_address = address.Substring(pos + 2);
}
else if (address.StartsWith("[")) //Remove any external reference
{
SetWbWs(address);
}
else
{
_address = address;
}
if(_address.IndexOfAny(new char[] {',','!'}) > -1)
{
//Advanced address. Including Sheet or multi
ExtractAddress(_address);
}
else
{
//Simple address
GetRowColFromAddress(_address, out _fromRow, out _fromCol, out _toRow, out _toCol);
_addresses = null;
_start = null;
_end = null;
_firstAddress = _address;
}
_address = address;
Validate();
}
internal void ChangeAddress()
{
if (AddressChange != null)
{
AddressChange(this, new EventArgs());
}
}
private void SetWbWs(string address)
{
int pos;
if (address[0] == '[')
{
pos = address.LastIndexOf("]");
_wb = address.Substring(1, pos - 1);
_ws = address.Substring(pos + 1);
}
else
{
_wb = "";
_ws = address;
}
pos = _ws.IndexOf("!");
if (pos > -1)
{
_address = _ws.Substring(pos + 1);
_ws = _ws.Substring(0, pos);
}
}
ExcelCellAddress _start = null;
#endregion
///
/// Gets the row and column of the top left cell.
///
/// The start row column.
public ExcelCellAddress Start
{
get
{
if (_start == null)
{
_start = new ExcelCellAddress(_fromRow, _fromCol);
}
return _start;
}
}
ExcelCellAddress _end = null;
///
/// Gets the row and column of the bottom right cell.
///
/// The end row column.
public ExcelCellAddress End
{
get
{
if (_end == null)
{
_end = new ExcelCellAddress(_toRow, _toCol);
}
return _end;
}
}
///
/// The address for the range
///
public virtual string Address
{
get
{
return _address;
}
}
///
/// If the address is a defined name
///
public bool IsName
{
get
{
return _fromRow < 0;
}
}
public override string ToString()
{
return base.ToString();
}
string _firstAddress;
///
/// returns the first address if the address is a multi address.
/// A1:A2,B1:B2 returns A1:A2
///
internal string FirstAddress
{
get
{
if (string.IsNullOrEmpty(_firstAddress))
{
return _address;
}
else
{
return _firstAddress;
}
}
}
internal string AddressSpaceSeparated
{
get
{
return _address.Replace(',', ' '); //Conditional formatting and a few other places use space as separator for mulit addresses.
}
}
///
/// Validate the address
///
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
{
get
{
return _ws;
}
}
List _addresses = null;
internal List Addresses
{
get
{
return _addresses;
}
}
private void ExtractAddress(string fullAddress)
{
string first="", second="";
bool isText=false, hasSheet=false;
if (fullAddress == "#REF!")
{
SetAddress(ref fullAddress, ref second, ref hasSheet );
return;
}
foreach (char c in fullAddress)
{
if(c=='\'')
{
isText=!isText;
}
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;
}
}
}
}
SetAddress(ref first, ref second, ref hasSheet);
}
#region Address manipulation methods
internal eAddressCollition Collide(ExcelAddressBase address)
{
if (address.WorkSheet != WorkSheet)
{
return eAddressCollition.No;
}
if (address._fromRow > _toRow || address._fromCol > _toCol
||
_fromRow > address._toRow || _fromCol > address._toCol)
{
return eAddressCollition.No;
}
else if (address._fromRow == _fromRow && address._fromCol == _fromCol &&
address._toRow == _toRow && address._toCol == _toCol)
{
return eAddressCollition.Equal;
}
else if (address._fromRow >= _fromRow && address._toRow <= _toRow &&
address._fromCol >= _fromCol && address._toCol <= _toCol)
{
return eAddressCollition.Inside;
}
else
return eAddressCollition.Partly;
}
internal ExcelAddressBase AddRow(int row, int rows)
{
if (row > _toRow)
{
return this;
}
else if (row <= _fromRow)
{
return new ExcelAddressBase(_fromRow + rows, _fromCol, _toRow + rows, _toCol);
}
else
{
return new ExcelAddressBase(_fromRow, _fromCol, _toRow + rows, _toCol);
}
}
internal ExcelAddressBase DeleteRow(int row, int rows)
{
if (row > _toRow) //After
{
return this;
}
else if (row+rows <= _fromRow) //Before
{
return new ExcelAddressBase(_fromRow - rows, _fromCol, _toRow - rows, _toCol);
}
else if (row <= _fromRow && row + rows > _toRow) //Inside
{
return null;
}
else //Partly
{
if (row <= _fromRow)
{
return new ExcelAddressBase(row, _fromCol, _toRow - rows, _toCol);
}
else
{
return new ExcelAddressBase(_fromRow, _fromCol, _toRow - rows < row ? row - 1 : _toRow - rows, _toCol);
}
}
}
internal ExcelAddressBase AddColumn(int col, int cols)
{
if (col > _toCol)
{
return this;
}
else if (col <= _fromCol)
{
return new ExcelAddressBase(_fromRow, _fromCol + cols, _toRow, _toCol + cols);
}
else
{
return new ExcelAddressBase(_fromRow, _fromCol, _toRow, _toCol + cols);
}
}
internal ExcelAddressBase DeleteColumn(int col, int cols)
{
if (col > _toCol) //After
{
return this;
}
else if (col + cols <= _fromRow) //Before
{
return new ExcelAddressBase(_fromRow, _fromCol - cols, _toRow, _toCol - cols);
}
else if (col <= _fromCol && col + cols > _toCol) //Inside
{
return null;
}
else //Partly
{
if (col <= _fromCol)
{
return new ExcelAddressBase(_fromRow, col, _toRow, _toCol - cols);
}
else
{
return new ExcelAddressBase(_fromRow, _fromCol, _toRow, _toCol - cols < col ? col - 1 : _toCol - cols);
}
}
}
#endregion
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);
}
else
{
if (_addresses == null) _addresses = new List();
_addresses.Add(new ExcelAddress(_ws, address));
}
}
internal enum AddressType
{
Invalid,
InternalAddress,
ExternalAddress,
InternalName,
ExternalName
}
internal static AddressType IsValid(string Address)
{
string ws="";
if (Address.StartsWith("'"))
{
int ix = Address.IndexOf('\'', 1);
if (ix > -1)
{
ws = Address.Substring(1, ix-1);
Address = Address.Substring(ix + 2);
}
}
if (Address.IndexOfAny(new char[] { '(', ')', '+', '-', '*', '/', '.', '=','^','&','%','\"' })>-1)
{
return AddressType.Invalid;
}
if (Address.IndexOf('!') > 0)
{
string[] split = Address.Split('!');
if (split.Length == 2)
{
ws = split[0];
Address = split[1];
}
else if (split.Length == 3 && split[1] == "#REF" && split[2] == "")
{
ws = split[0];
Address = "#REF!";
if (ws.StartsWith("[") && ws.IndexOf("]") > 1)
{
return AddressType.ExternalAddress;
}
else
{
return AddressType.InternalAddress;
}
}
else
{
return AddressType.Invalid;
}
}
int _fromRow, _fromCol, _toRow, _toCol;
if (ExcelAddressBase.GetRowColFromAddress(Address, out _fromRow, out _fromCol, out _toRow, out _toCol))
{
if (_fromRow > 0 && _fromCol > 0 && _toRow <= ExcelPackage.MaxRows && _toCol <= ExcelPackage.MaxColumns)
{
if (ws.StartsWith("[") && ws.IndexOf("]") > 1)
{
return AddressType.ExternalAddress;
}
else
{
return AddressType.InternalAddress;
}
}
else
{
return AddressType.Invalid;
}
}
else
{
if(IsValidName(Address))
{
if (ws.StartsWith("[") && ws.IndexOf("]") > 1)
{
return AddressType.ExternalName;
}
else
{
return AddressType.InternalName;
}
}
else
{
return AddressType.Invalid;
}
}
}
private static bool IsValidName(string address)
{
if (Regex.IsMatch(address, "[^0-9./*-+,½!\"@#£%&/{}()\\[\\]=?`^~':;<>|][^/*-+,½!\"@#£%&/{}()\\[\\]=?`^~':;<>|]*"))
{
return true;
}
else
{
return false;
}
}
}
///
/// Range address with the address property readonly
///
public class ExcelAddress : ExcelAddressBase
{
internal ExcelAddress()
: base()
{
}
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;
}
///
/// The address for the range
///
/// Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5"
public new string Address
{
get
{
if (string.IsNullOrEmpty(_address) && _fromRow>0)
{
_address = GetAddress(_fromRow, _fromCol, _toRow, _toCol);
}
return _address;
}
set
{
SetAddress(value);
base.ChangeAddress();
}
}
}
}