【问题标题】:Get the Column Index of a Cell in Excel using OpenXML C#使用 OpenXML C# 在 Excel 中获取单元格的列索引
【发布时间】:2015-03-05 10:38:43
【问题描述】:

我已经四处寻找了一段时间,似乎无法找到如何做到这一点。 我有一个 Excel 表,我正在使用 OpenXML 阅读它。现在正常的事情是遍历行,然后遍历单元格以获取值,这很好。但除了值之外,我还需要单元格的位置,格式为(rowindex,ColumnIndex)。我设法获得了 rowIndex,但似乎无法弄清楚获得列索引。

我实际上认为这会很容易,但显然事实并非如此。

【问题讨论】:

标签: c# excel openxml


【解决方案1】:

这比您想象的要复杂一些,因为架构允许省略空单元格。

要获取索引,您可以使用具有CellReference 属性的Cell 对象以A1B1 等格式提供引用。您可以使用该引用来提取列号。

您可能知道,在 Excel 中 A = 1B = 2 等直至 Z = 26 时,单元格都以 A 为前缀以给出 AA = 27AB = 28 等。请注意,在这种情况下AA 第一个 A 的值是第二个的 26 倍;即它的“价值”为 26,而第二个 A 的“价值”为 1,总共为 27。

要计算列索引,您可以反转字母,然后获取第一个字母的值并将其添加到运行总计中。然后取第二个字母的值乘以 26,将总数加到第一个数字上。第三次将其乘以 26 两次并相加,第四次将其乘以 26 3 次,依此类推。

所以对于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 为空的情况下,单元格被放置在最左边的可用单元格中。 RowIndex 在规范中也不是强制性的,因此它也可以省略,在这种情况下,单元格被放置在可用的最高行中。更多信息请见this question。在CellReferencenull 的情况下,来自@BCdotWEB 的answer 是正确的方法。

【讨论】:

  • 可以从 columnNumber = 0 开始,返回时不要加一。
  • 第二个字母中的 Z 会发生什么?不会和第三个A一样吗?所以Z = 26*26A = 1*26*26。我在问,因为我正在尝试从单元格索引到单元格名称的相反操作。
  • 第二个字母中的Z 是“价值” 26。这将添加到第一个字母的值的 26 倍。例如。 ZZ 将是 (26 * 26) + 26 = 702。第三列中的A 是“值得”1。这将添加到第二个字母值的 26 倍和第一个字母值的 26 * 26 倍。例如 AAA 将是 (26 * 26 * 1) + (26 * 1) + 1 = 703
  • @bostero2 顺便说一句,如果还不清楚,请创建一个新问题并在此处联系我,我会为您写一个关于如何执行相反操作的答案。
  • @petelids 是的,发表评论后想通了。谢谢!
【解决方案2】:

小而美

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;
}

【讨论】:

  • 愿意解释一下 64 位吗?
  • @Yaron: 64 是 ('A' - 1)
【解决方案3】:
    [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 );

【讨论】:

    【解决方案4】:

    要开始回答,我请你先看看this

    正如我所解释的,有 NO 简单的方法来提取行和列。您得到的最接近的是提取单元格的CellReference,其形式为A1B2,实际上是COLUMN_ROW 格式。

    您可以做的是从CellReference 中提取行和列。是的,这需要您实现一种方法,您需要通过char 检查char 以验证数字和字符串。

    假设您有 A11 ,那么当您需要索引列时,您需要提取 A ,这将作为 column 1。是的,这并不容易,但这是唯一的方法,除非您在扫描/遍历单元格时简单地选择对列进行计数。

    再看this问题答案,它做同样的事情。

    【讨论】:

    • 嗯..好的,但我确实需要索引列。所以我现在想的是找到一种方法来替换单元格引用中的字母以获取列索引。跨度>
    • 正如答案中所解释的那样,没有简单的方法 :) 就像我在链接问题中指出的那样简单计算 :)
    • @QV1 你得到问题的答案了吗?或者你需要更多解释:)
    • 我明白了,谢谢,实际上,因为我只需要索引,为单元循环创建一个计数器就可以了。一旦我完成测试,我将很快发布我的代码。感谢您的帮助!
    • @QV1 很好,如果您认为答案有助于/解决问题,请不要忘记投票或将问题标记为正确 :)
    【解决方案5】:
        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]);
                            }
            }
    

    这是查找当前/使用的列总数

    【讨论】:

      【解决方案6】:
          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);
                  }
              }
          }
      

      【讨论】:

      • 为一个已有 5 年历史的问题提供一个答案,该问题有一些其他答案(有很多赞成票)似乎并不是特别有用。
      • 也许不是,但我更喜欢这个答案。更容易阅读,更紧凑。
      【解决方案7】:

      在我的场景中,我只需要处理列名(没有单元格编号),并使用 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 gist。

      【讨论】:

        【解决方案8】:

        在@petelids 答案中略微修改了 GetColumnIndex 函数。结果将是从零开始的索引。如果需要为从一开始的索引添加 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;
        }
        

        【讨论】:

          【解决方案9】:
          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;
              }
          

          【讨论】:

          • 对@Subodh Wasankar 进行修改,这将为索引> 26 的Cols 正确获取索引
          【解决方案10】:

          只是为了给这个老问题添加一种新方法,我用它作为一种快速方法来获取一行中单元格的列索引(假设您正在循环通过 SheetData 中的一行中的单元格,作为 OP表示他们是)。

          您可以使用 ElementsBefore 枚举来计算当前循环的单元格之前的单元格,并且由于该 Count 是从 1 开始的并且 Element IEnumerables 是从零开始的,因此使用 Count 将为您提供列索引您当前所在的单元格(本质上,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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-10-11
            • 1970-01-01
            • 1970-01-01
            • 2017-05-24
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多