blob: a9fe81540026fe8ad7589fef2997699abb5a4a9e [file] [log] [blame]
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Globalization;
/// <summary>
/// A range address
/// </summary>
/// <remarks>Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5" </remarks>
internal static class ExcelAddressUtilities {
/// <summary>
/// Maximum number of columns in a worksheet (16384).
/// </summary>
private const int _maxColumns = 16384;
/// <summary>
/// Maximum number of rows in a worksheet (1048576).
/// </summary>
private const int _maxRows = 1048576;
private static readonly FrozenSet<char> _formulaChars = new HashSet<char> {
'(',
')',
'+',
'-',
'*',
'/',
'.',
'=',
'^',
'&',
'%',
'\"',
}.ToFrozenSet();
public enum AddressType {
Invalid,
InternalAddress,
ExternalAddress,
InternalName,
ExternalName,
Formula,
}
public 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 var intAddress)) {
if (intAddress.Contains(
'[')) //Table reference
{
return string.IsNullOrEmpty(wb) ? AddressType.InternalAddress : AddressType.ExternalAddress;
}
if (intAddress.Contains(',')) {
intAddress = intAddress[..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 GetRowCol(string address, out int row, out int col, bool throwException) {
var colPart = true;
var colStartIx = 0;
var colLength = 0;
col = 0;
row = 0;
if (address.EndsWith("#REF!")) {
row = 0;
col = 0;
return true;
}
var sheetMarkerIndex = address.IndexOf('!');
if (sheetMarkerIndex >= 0) {
colStartIx = sheetMarkerIndex + 1;
}
address = address.ToUpper(CultureInfo.InvariantCulture);
for (var i = colStartIx; i < address.Length; i++) {
var c = address[i];
if (colPart && c >= 'A' && c <= 'Z' && colLength <= 3) {
col *= 26;
col += c - 64;
colLength++;
} else if (c >= '0' && c <= '9') {
row *= 10;
row += c - 48;
colPart = false;
} else if (c == '$') {
if (i == colStartIx) {
colStartIx++;
} else {
colPart = false;
}
} else {
row = 0;
col = 0;
if (throwException) {
throw new($"Invalid Address format {address}");
}
return false;
}
}
return row != 0 || col != 0;
}
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;
}
return fromRow <= toRow
&& fromCol <= toCol
&& fromCol > -1
&& toCol <= _maxColumns
&& fromRow > -1
&& toRow <= _maxRows;
}
private static bool SplitAddress(string address, out string wb, out string intAddress) {
wb = "";
intAddress = "";
var text = "";
var isText = false;
var brackPos = -1;
for (var 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);
}
intAddress = address[(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;
foreach (var c in address) {
if (c == '\'') {
isText = !isText;
} else {
if (isText == false && _formulaChars.Contains(c)) {
return true;
}
}
}
return false;
}
}