Hi, FWIW, i've written a much less featured xlsx reader/writer for a specific purpose in our code base, then i found your project and had a look at the code - after some fun writing different implementations we came across the concept of the bijective base-26 counting system. Perhaps this might be helpful to share the following code which i think is an elegant and possibly most optimal way from a performance point of view of doing the 1-based column number to column reference conversion -
public static class ExcelCellRefs
{
public const Int32 MaxRows = 1048576;
public const Int32 MaxCols = 16384;
/// <summary>
/// 1-based column number -> column reference e.g. 1 -> "A", 16384 => "XFD"
/// </summary>
public static Dictionary<String, Int32> ColRefToNum { get; private set; }
/// <summary>
/// Column reference -> 1-based column number e.g. "A" -> 1, "XFD" -> 16384
/// </summary>
public static Dictionary<Int32, String> ColNumToRef { get; private set; }
static ExcelCellRefs()
{
var colRefs = GenerateColRefs(MaxCols).ToArray();
var tuples = Enumerable.Range(1, MaxCols).
Select(c => new {Num = c, Ref = colRefs[c - 1]}).
ToArray();
ColRefToNum = tuples.ToDictionary(t => t.Ref, t => t.Num);
ColNumToRef = tuples.ToDictionary(t => t.Num, t => t.Ref);
}
/// <summary>
/// Generates Excel column reference names
/// e.g. the last column name for GenerateColRefs(16384) is "XFD"
/// </summary>
public static IEnumerable<String> GenerateColRefs(Int32 columnCount)
{
var names = new List<String>();
var i = 1;
while (i <= columnCount)
{
names.Add(ToBijectiveBase26(i));
i++;
}
return names;
}
/// <summary>
/// No concept of 0 in the bijective base-26 counting system,
/// it's a 26-adic counting system and not a pure base-26 numbering
/// (Thank you Internet for explaining that to me as i would have otherwise
/// had no idea...)
/// </summary>
/// <param name="n">The 1-based column number to convert</param>
/// <returns>The column name as used in Excel e.g. 16384 is "XFD"</returns>
public static String ToBijectiveBase26(Int32 n)
{
var chars = new List<char>();
while (n > 0)
{
--n;
chars.Insert(0, (char)('A' + n % 26));
n /= 26;
}
return String.Join(String.Empty, chars);
}
/// <summary>
/// No concept of 0 in the bijective base-26 counting system,
/// it's a 26-adic counting system and not a pure base-26 numbering
/// (Thank you Internet for explaining that to me as i would have otherwise
/// had no idea...)
/// </summary>
/// <param name="colRefName">The column name as used in Excel to convert</param>
/// <returns>The 1-based column number e.g. "XFD" will be 16384</returns>
public static Int32 FromBijectiveBase26(String colRefName)
{
var chars = colRefName.ToCharArray();
var result = 0;
var power = 0;
for (var i = chars.Length - 1; i >= 0; i--)
{
result += ((chars[i] - 'A') + 1) * (Int32)Math.Pow(26, power);
power++;
}
return result;
}
/// <summary>
/// Parses an Excel cell ref name into the 1-based row and column numbers
/// </summary>
/// <param name="cellRef">The cell ref name e.g. XFD123</param>
/// <param name="rowNumber">The 1-based row number e.g. XFD123 => 123</param>
/// <param name="columnNumber">The 1-based col number e.g. XFD123 => 16384</param>
/// <returns>true if the cell ref name is valid, false otherwise</returns>
public static Boolean ParseCellRef(String cellRef, out Int32 rowNumber, out Int32 columnNumber)
{
var cellRefLen = cellRef.Length;
if (cellRefLen < 2)
{
rowNumber = columnNumber = -1;
return false;
}
// using reg ex here proved to be too slow hence the hand-rolled parsing
var index = 0;
while (!(cellRef[index] >= 48 && cellRef[index] <= 57)) // fast !isdigit detection
index++;
var colRef = cellRef.Substring(0, index);
var rowNum = cellRef.Substring(index, cellRefLen - index);
columnNumber = ColRefToNum[colRef];
if (columnNumber > MaxCols)
{
rowNumber = columnNumber = -1;
return false;
}
var rowNumParsed = Int32.TryParse(rowNum, out rowNumber);
if (!(rowNumParsed && rowNumber <= MaxRows))
{
rowNumber = columnNumber = -1;
return false;
}
return true;
}
}