[英]Get the Column Index of a Cell in Excel using OpenXML C#
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.我有一张 excel 表,我正在使用 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).但是除了值之外,我还需要单元格的位置,格式为(rowindex,ColumnIndex)。 I've managed to get the rowIndex, but cant seem to figure out getting the column Index.我设法获得了rowIndex,但似乎无法弄清楚获得列索引。
I actually thought this was going to be easy but apparently it isnt.我实际上认为这很容易,但显然不是。
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.要获取索引,您可以使用具有CellReference
属性的Cell
对象,该属性以A1
、 B1
等格式提供引用。您可以使用该引用来提取列号。
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;您可能知道,在 Excel A = 1
、 B = 2
等直到Z = 26
时,单元格以A
为前缀以给出AA = 27
、 AB = 28
等。请注意,在AA
的情况下,第A
值为秒的 26 倍; ie it is "worth" 26 whilst the second A
is "worth" 1 giving a total of 27.即它“价值”26,而第二个A
“价值”1,总共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.然后将第二个字母的值乘以 26,将总数与第一个数字相加。 For the third you multiply it by 26 twice and add it, for the fourth multiply it by 26 3 times and so on.对于第三个,您将其乘以 26 两次并相加,对于第四个,将其乘以 26 3 次,依此类推。
So for column ABC
you would do:因此,对于ABC
列,您将执行以下操作:
C = 3
B = 2 * 26 = 52
A = 1 * 26 *26 = 676
3 + 52 + 676 = 731
In C# the following will work:在 C# 中,以下将起作用:
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).请注意, CellReference
也不能保证在 XML 中(尽管我从未见过它不在那里)。 In the case where the CellReference
is null the cell is placed in the leftmost available cell.在CellReference
为空的情况下,单元格被放置在最左边的可用单元格中。 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. RowIndex
在规范中也不是强制性的,因此它也可以省略,在这种情况下,单元格放置在可用的最高行中。 More information can be seen in this question .更多信息可以在这个问题中看到。 The answer from @BCdotWEB is correct approach in cases where the CellReference
is null
.在CellReference
为null
情况下, CellReference
的答案是正确的方法。
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;
}
[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 );
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.您得到的最接近的是提取单元格的CellReference
,该单元格的形式为A1
, B2
,实际上是COLUMN_ROW
格式。
What you can do is extract Row and Column from the CellReference
.您可以做的是从CellReference
提取 Row 和 Column 。 Yes this would need you to implement a method where you need to check char
by char
to verify for numbers and strings.是的,这需要你来实现,你需要检查的方法char
通过char
来验证数字和字符串。
Lets say you have A11
, then when you need to index column you need to extract A
which would give as column 1
.假设您有A11
,那么当您需要索引列时,您需要提取A
,它将作为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.再次看看这个问题的答案,它做同样的事情。
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这是为了找到存在/使用的列总数
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.在我的场景中,我只需要处理列名(没有单元格编号),并使用了 LINQ,认为值得放在这里以供参考。
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.要恢复原状,以及完整的代码和测试,请参阅此要点。
Slightly modified GetColumnIndex function in the @petelids answer.在@petelids 答案中稍微修改了GetColumnIndex函数。 Result will be zero-based index.结果将是从零开始的索引。 If need add 1 for a one-based Index.如果需要为基于 1 的索引添加 1。
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;
}
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);
}
}
}
private double CellReferenceToIndex(Cell cell)
{
// if Cell is ABC4 => position is
// = [Aindx * (26^2)] + [BIndx * (27^1)] + [CIndx * (27^0)]
// = [1 * (26^2)] + [2 * (27^1)] + [3 * (27^0)]
double index = 0;
char [] reference = cell.CellReference.ToString().ToUpper().Reverse().ToArray();
int letterPosition = 0;
foreach (char ch in reference)
{
if (char.IsLetter(ch))
{
int value = (ch - 'A') + 1; // so A is 1 not 0
index += value * Math.Pow(26, letterPosition++);
}
}
return index;
}
Just to add a new approach to this old question, I use this as a quick method to get the column index for a cell in a row (assuming you're looping through the Cells in a Row in SheetData, as the OP indicates they were).只是为了为这个老问题添加一种新方法,我使用它作为一种快速方法来获取一行中单元格的列索引(假设您正在循环通过 SheetData 中的一行中的单元格,因为 OP 表明它们是)。
You can use the ElementsBefore enumerable to count the Cells before the one you're currently looping on, and since that Count is one-based and Element IEnumerables are zero-based, using the Count will give you the column index of the Cell you're currently on (essentially, ElementsBefore + 1 = the current cell's column index).您可以使用 ElementsBefore 枚举来计算您当前循环的单元格之前的单元格,并且由于该 Count 是从一开始的,而 Element IEnumerables 是从零开始的,因此使用 Count 将为您提供单元格的列索引。当前重新打开(本质上,ElementsBefore + 1 = 当前单元格的列索引)。
So, something like this...所以,像这样的事情......
For Each r In sht.Elements(Of Row)
For Each c In sht.Elements(Of Row).ElementAt(r.RowIndex).Elements(Of Cell)
Dim iColumnIndex = c.ElementsBefore.Count
Next
Next
Some of the examples on this thread didn't work beyond Z.该线程上的一些示例在 Z 之后无法使用。
When validating, it's better to perform some Unit tests to confirm the column index is calculated correctly.验证时,最好执行一些单元测试以确认列索引计算正确。
Assuming the index count begins from 1, the following can be a useful reference...假设索引计数从 1 开始,以下可能是有用的参考...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.