16
votes

I've been looking around for a while now and cannot seem to find out how to do this. I've got an excel sheet, which I'm reading using OpenXML. Now the normal thing would be to loop through the rows and then loop through the cells to get the values, which is fine. But along with the values I need the location of the cell, which would be in the format (rowindex, ColumnIndex). I've managed to get the rowIndex, but cant seem to figure out getting the column Index.

I actually thought this was going to be easy but apparently it isnt.

8
For future readers, I recommend seeing this answer.Jesse Good

8 Answers

26
votes

This is slightly trickier than you might imagine because the schema allows for empty cells to be omitted.

To get the index you can use the Cell object wihch has a CellReference property that gives the reference in the format A1, B1 etc. You can use that reference to extract the column number.

As you probably know, in Excel A = 1, B = 2 etc up to Z = 26 at which point the cells are prefixed with A to give AA = 27, AB = 28 etc. Note that in the case of AA the first A has a value of 26 times the second; i.e. it is "worth" 26 whilst the second A is "worth" 1 giving a total of 27.

To work out the column index you can reverse the letters then take the value of the first letter and add it to a running total. Then take the value of the second letter and multiply it by 26, adding the total to the first number. For the third you multiply it by 26 twice and add it, for the fourth multiply it by 26 3 times and so on.

So for column ABC you would do:

C = 3
B = 2 * 26 = 52
A = 1 * 26 *26 = 676
3 + 52 + 676 = 731

In C# the following will work:

private static int? GetColumnIndex(string cellReference)
{
    if (string.IsNullOrEmpty(cellReference))
    {
        return null;
    }

    //remove digits
    string columnReference = Regex.Replace(cellReference.ToUpper(), @"[\d]", string.Empty);

    int columnNumber = -1;
    int mulitplier = 1;

    //working from the end of the letters take the ASCII code less 64 (so A = 1, B =2...etc)
    //then multiply that number by our multiplier (which starts at 1)
    //multiply our multiplier by 26 as there are 26 letters
    foreach (char c in columnReference.ToCharArray().Reverse())
    {
        columnNumber += mulitplier * ((int)c - 64);

        mulitplier = mulitplier * 26;
    }

    //the result is zero based so return columnnumber + 1 for a 1 based answer
    //this will match Excel's COLUMN function
    return columnNumber + 1;
}

Note that the CellReference is not guaranteed to be in the XML either (although I've never seen it not there). In the case where the CellReference is null the cell is placed in the leftmost available cell. The RowIndex is also not mandatory in the spec so it too can be omitted in which case the cell is placed in the highest row available. More information can be seen in this question. The answer from @BCdotWEB is correct approach in cases where the CellReference is null.

9
votes

Small is beautifull

int ColumnIndex(string reference)
{
  int ci=0;
  reference=reference.ToUpper();
  for (int ix = 0; ix < reference.Length && reference[ix] >= 'A';ix++ ) 
       ci = (ci * 26) + ((int)reference[ix] - 64);
  return ci;
}
5
votes
    [TestCase( 1, 0, "A1" )]
    [TestCase( 2, 25, "Z2" )]
    [TestCase( 2, 38, "AM2" )]
    [TestCase( 2, (26 * 4) + 1, "DB2" )]
    [TestCase( 2, (26 * 26 * 26 * 18) + (26 * 26 * 1) + (26 * 26 * 1) + ( 26 * 1 ) + 2, "RBAC2" )]
    public void CanGetCorrectCellReference( int row, int column, string expected )
        => GetCellReference( (uint)row, (uint)column ).Value.ShouldEqual( expected );

    public static StringValue GetCellReference( uint row, uint column ) =>
        new StringValue($"{GetColumnName("",column)}{row}");

    static string GetColumnName( string prefix, uint column ) => 
        column < 26 ? $"{prefix}{(char)( 65 + column)}" : 
        GetColumnName( GetColumnName( prefix, ( column - column % 26 ) / 26 - 1 ), column % 26 );
3
votes

To start answer , I invite you to look at this first.

As I have explained there is NO easy way to extract Row and Column. The closest you get is the extraction of CellReference of a cell which would have the form of A1 , B2 which is actualy COLUMN_ROW format.

What you can do is extract Row and Column from the CellReference. Yes this would need you to implement a method where you need to check char by charto verify for numbers and strings.

Lets say you have A11 , then when you need to index column you need to extract A which would give as column 1. Yes it's not that easy, but it's the only way unless you simply chose to count the columns when you scan/iterate through cells.

Again look at this questions answer which does the same thing.

1
votes
    Row row = worksheetPart.Worksheet.GetFirstChild<SheetData>().Elements<Row>().FirstOrDefault();
   var totalnumberOfColumns = 0;
    if (row != null)
        {
            var spans = row.Spans != null ? row.Spans.InnerText : "";
                if (spans != String.Empty)
                        {
                            //spans.Split(':')[1];
                            string[] columns = spans.Split(':');
                            startcolumnInuse = int.Parse(columns[0]);
                            endColumnInUse = int.Parse(columns[1]);
                            totalnumberOfColumns = int.Parse(columns[1]);
                        }
        }

this is to find the total number of columns present/used enter image description here

0
votes

In my scenario I only needed to deal with column names (no cell numbers), and used LINQ, thought it's worth putting here for the reference.

const int AsciiTrim = 'A' - 1; //64
const int LastChar = 'Z' - AsciiTrim; //26

var colIndex = columnName
    .Reverse()
    .Select(ch => ch - AsciiTrim)
    .Select((ch, i) => ch * Math.Pow(LastChar, i))
    .Sum()
    - 1; //make zero-index based

To revert back, and for the full code and test, see this gist.

0
votes

Slightly modified GetColumnIndex function in the @petelids answer. Result will be zero-based index. If need add 1 for a one-based Index.

private static int CellReferenceToIndex(string reference)
{
    foreach (char ch in reference)
    {
        if (Char.IsLetter(ch))
        {
            int value = (int)ch - (int)'A';
            index = (index == 0) ? value : ((index + 1) * 26) + value;
        }
        else
            return index;
    }
    return index;
}
0
votes
    public static void CellReferenceToIndex(string reference, out int row_index, out int col_index)
    {
        row_index = 0;
        col_index = 0;

        foreach(char c in reference)
        {
            if (c >= '0' && c <= '9')
            {
                row_index = row_index * 10 + (c - '0');
            }
            if (c >= 'A' && c <= 'Z')
            {
                col_index = col_index * ('Z' - 'A' + 1) + (c - 'A' + 1);
            }
        }
    }