使用 OpenXML C# 获取 Excel 中单元格的列索引
Get the Column Index of a Cell in Excel using OpenXML C#
我已经四处寻找了一段时间,但似乎找不到如何做到这一点。
我有一个 excel sheet,我正在使用 OpenXML 阅读它。现在正常的做法是循环遍历行,然后循环遍历单元格以获取值,这很好。但是除了这些值,我还需要单元格的位置,其格式为 (rowindex, ColumnIndex)。我设法获得了行索引,但似乎无法弄清楚如何获得列索引。
我实际上认为这会很容易,但显然不是。
开始回答,我请你先看看this。
正如我所解释的,没有 提取行和列的简单方法。您得到的最接近的是提取 CellReference
的单元格,其形式为 A1
, B2
实际上是 COLUMN_ROW
格式。
您可以做的是从 CellReference
中提取行和列。是的,这需要您实现一种方法,您需要通过 char
检查 char
来验证数字和字符串。
假设您有 A11
,那么当您需要索引列时,您需要提取 A
,这将给出 column 1
。是的,这不是那么容易,但这是唯一的方法,除非您在 scan/iterate 通过单元格时简单地选择对列进行计数。
再看看 this 做同样事情的问题答案。
这比您想象的要复杂一些,因为架构允许省略空单元格。
要获取索引,您可以使用 Cell
object wihch has a CellReference
属性 以 A1
、B1
等格式提供参考。您可以使用该参考来提取列号。
您可能知道,在 Excel A = 1
、B = 2
等 Z = 26
中,单元格以 A
为前缀以提供AA = 27
、AB = 28
等。请注意,在 AA
的情况下,第一个 A
的值是第二个的 26 倍;即它是 "worth" 26 而第二个 A
是 "worth" 1 总共 27.
要计算列索引,您可以反转字母,然后取第一个字母的值并将其添加到 运行 总数中。然后将第二个字母的值乘以 26,将总数加到第一个数字上。对于第三次,您将其乘以 26 两次并相加,对于第四次,您将其乘以 26 三次,依此类推。
因此,对于第 ABC
列,您可以:
C = 3
B = 2 * 26 = 52
A = 1 * 26 *26 = 676
3 + 52 + 676 = 731
在 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;
}
请注意 CellReference
而不是 保证在 XML 中(尽管我从未见过它不存在)。在 CellReference
为 null 的情况下,单元格放置在最左边的可用单元格中。 RowIndex
在规范中也不是强制性的,因此它也可以省略,在这种情况下,单元格将放置在可用的最高行中。在 this question. The answer 中可以看到更多信息来自@BCdotWEB 在 CellReference
是 null
的情况下是正确的方法。
小即是美
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 );
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]);
}
}
这是求总列数present/used
在我的场景中,我只需要处理列名(没有单元格编号),并且使用了 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
要还原,以及完整的代码和测试,请参阅 this 要点。
稍微修改了@petelids 答案中的GetColumnIndex 函数。结果将是从零开始的索引。如果需要为基于 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;
}
只是为了给这个老问题添加一个新的方法,我用它作为一种快速方法来获取一行中单元格的列索引(假设您在 SheetData 中循环遍历一行中的单元格,如OP 表明他们是)。
您可以使用 ElementsBefore 枚举来计算您当前循环之前的单元格,并且由于该计数是 one-based 并且元素 IEnumerables 是 zero-based,使用计数将给出您当前所在单元格的列索引(本质上,ElementsBefore + 1 = 当前单元格的列索引)。
所以,像这样...
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
我已经四处寻找了一段时间,但似乎找不到如何做到这一点。 我有一个 excel sheet,我正在使用 OpenXML 阅读它。现在正常的做法是循环遍历行,然后循环遍历单元格以获取值,这很好。但是除了这些值,我还需要单元格的位置,其格式为 (rowindex, ColumnIndex)。我设法获得了行索引,但似乎无法弄清楚如何获得列索引。
我实际上认为这会很容易,但显然不是。
开始回答,我请你先看看this。
正如我所解释的,没有 提取行和列的简单方法。您得到的最接近的是提取 CellReference
的单元格,其形式为 A1
, B2
实际上是 COLUMN_ROW
格式。
您可以做的是从 CellReference
中提取行和列。是的,这需要您实现一种方法,您需要通过 char
检查 char
来验证数字和字符串。
假设您有 A11
,那么当您需要索引列时,您需要提取 A
,这将给出 column 1
。是的,这不是那么容易,但这是唯一的方法,除非您在 scan/iterate 通过单元格时简单地选择对列进行计数。
再看看 this 做同样事情的问题答案。
这比您想象的要复杂一些,因为架构允许省略空单元格。
要获取索引,您可以使用 Cell
object wihch has a CellReference
属性 以 A1
、B1
等格式提供参考。您可以使用该参考来提取列号。
您可能知道,在 Excel A = 1
、B = 2
等 Z = 26
中,单元格以 A
为前缀以提供AA = 27
、AB = 28
等。请注意,在 AA
的情况下,第一个 A
的值是第二个的 26 倍;即它是 "worth" 26 而第二个 A
是 "worth" 1 总共 27.
要计算列索引,您可以反转字母,然后取第一个字母的值并将其添加到 运行 总数中。然后将第二个字母的值乘以 26,将总数加到第一个数字上。对于第三次,您将其乘以 26 两次并相加,对于第四次,您将其乘以 26 三次,依此类推。
因此,对于第 ABC
列,您可以:
C = 3
B = 2 * 26 = 52
A = 1 * 26 *26 = 676
3 + 52 + 676 = 731
在 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;
}
请注意 CellReference
而不是 保证在 XML 中(尽管我从未见过它不存在)。在 CellReference
为 null 的情况下,单元格放置在最左边的可用单元格中。 RowIndex
在规范中也不是强制性的,因此它也可以省略,在这种情况下,单元格将放置在可用的最高行中。在 this question. The answer 中可以看到更多信息来自@BCdotWEB 在 CellReference
是 null
的情况下是正确的方法。
小即是美
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 );
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]);
}
}
这是求总列数present/used
在我的场景中,我只需要处理列名(没有单元格编号),并且使用了 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
要还原,以及完整的代码和测试,请参阅 this 要点。
稍微修改了@petelids 答案中的GetColumnIndex 函数。结果将是从零开始的索引。如果需要为基于 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;
}
只是为了给这个老问题添加一个新的方法,我用它作为一种快速方法来获取一行中单元格的列索引(假设您在 SheetData 中循环遍历一行中的单元格,如OP 表明他们是)。
您可以使用 ElementsBefore 枚举来计算您当前循环之前的单元格,并且由于该计数是 one-based 并且元素 IEnumerables 是 zero-based,使用计数将给出您当前所在单元格的列索引(本质上,ElementsBefore + 1 = 当前单元格的列索引)。
所以,像这样...
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