открыть xml excel значение ячейки чтения

Я использую Open XML SDK для открытия файла Excel xlsx и пытаюсь прочитать значение ячейки в позиции A1 на каждом листе. Я использую следующий код:

using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(openFileDialog1.FileName, false))
{
    var sheets = spreadsheetDocument.WorkbookPart.Workbook.Descendants<Sheet>();

    foreach (Sheet sheet in sheets)
    {
        WorksheetPart worksheetPart = (WorksheetPart)spreadsheetDocument.WorkbookPart.GetPartById(sheet.Id);
        Worksheet worksheet = worksheetPart.Worksheet;

        Cell cell = GetCell(worksheet, "A", 1);

        Console.Writeline(cell.CellValue.Text);
     }
}

private static Cell GetCell(Worksheet worksheet, string columnName, uint rowIndex)
{
     Row row = GetRow(worksheet, rowIndex);

     if (row == null)
         return null;

     return row.Elements<Cell>().Where(c => string.Compare
               (c.CellReference.Value, columnName +
               rowIndex, true) == 0).First();
}

// Given a worksheet and a row index, return the row.
private static Row GetRow(Worksheet worksheet, uint rowIndex)
{
    return worksheet.GetFirstChild<SheetData>().
          Elements<Row>().Where(r => r.RowIndex == rowIndex).First();
} 

Текст на первом листе в позиции A1 является просто «тестовым», однако в моей консоли я вижу значение «0» как cell.CellValue.Text

Есть ли у кого-нибудь идея получить правильное значение ячейки?


person jwdehaan    schedule 25.02.2011    source источник


Ответы (5)


Все строки на листе Excel хранятся в массиве, подобном структуре, называемой SharedStringTable. Цель этой таблицы - централизовать все строки в массиве на основе индекса, а затем, если эта строка используется несколько раз в документе, просто ссылаться на индекс в этом массиве. При этом 0, который вы получили, когда получили текстовое значение ячейки A1, является индексом в SharedStringTable. Чтобы получить реальное значение, вы можете использовать эту вспомогательную функцию:

public static SharedStringItem GetSharedStringItemById(WorkbookPart workbookPart, int id)
{
    return workbookPart.SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(id);
}

Затем в своем коде назовите это так, чтобы получить реальное значение:

Cell cell = GetCell(worksheet, "A", 1);

string cellValue = string.Empty;

if (cell.DataType != null)
{
    if (cell.DataType == CellValues.SharedString)
    {
       int id = -1;

       if (Int32.TryParse(cell.InnerText, out id))
       {
           SharedStringItem item = GetSharedStringItemById(workbookPart, id);

           if (item.Text != null)
           {
               cellValue = item.Text.Text;
           }
           else if (item.InnerText != null)
           {
               cellValue = item.InnerText;
           }
           else if (item.InnerXml != null)
           {
               cellValue = item.InnerXml;
           }
       }
    }
}
person amurra    schedule 26.02.2011
comment
Это правильно, но не решает всех необходимых проблем. Прежде чем искать значение ячейки в SST, вам нужно фактически определить, представляет ли значение ячейки индекс SST или действительно является значением. - person Samuel Neff; 26.02.2011
comment
@Samuel Neff - по умолчанию Excel помещает все основные строки в SST, и в этом вопросе его интересует только получение этого базового строкового значения. Нет необходимости усложнять базовый сценарий. Если он имеет дело с формулами или другими частями данных, очевидно, что приведенный выше код необходимо будет изменить, чтобы включить ваш комментарий. - person amurra; 26.02.2011
comment
склонны соглашаться с amurra в этом вопросе - OP просто запрашивает базовое значение. тот факт, что благодаря этим комментариям он теперь знает, что ему, возможно, придется учесть другие вещи, делает ответ достаточным для заданного вопроса. другие вещи, например формулы, можно задать в другом вопросе. - person Todd Main; 28.02.2011
comment
Я добавляю этот комментарий, потому что фактическое решение для определения того, представляет ли значение ячейки индекс SST, по какой-то причине никогда не публиковалось (очень раздражает): if (cell.DataType! = Null && cell.DataType == CellValues.SharedString) - person genki; 22.09.2011
comment
Самуэль и генки правы. Если есть дата или другое значение, которое не является общей строкой, этот код будет подавлять попытки получить SI из id, где id уже является значением, а не фактической идентификацией si - person donkz; 02.04.2015
comment
Код был обновлен для проверки наличия общего строкового типа данных перед доступом к SharedStringTable, поэтому он больше не должен подавляться. - person amurra; 02.04.2015
comment
Отличный пример / решение @amurra, но весь Open XML SDK заставляет вас не беспокоиться. Я предполагаю, что часть «не SharedString» - это просто вернуть текст или внутренний текст? Я не получаю бит item.Text.Text. - person B H; 04.04.2016
comment
Я согласен с вами в том, что вам нужно многое сделать для работы с Open XML SDK. Без SDK вам пришлось бы напрямую манипулировать XML, чтобы, по крайней мере, вы могли работать со строго типизированными объектами. Я думаю, что двойной Text.Text связан с наличием элемента Text, который затем имеет свойство Text, поэтому вам нужно использовать Text.Text, чтобы получить к нему доступ. Прошло некоторое время с тех пор, как я использовал этот код, так что это может немного отличаться. - person amurra; 04.04.2016

Ответ Амурры, кажется, соответствует девяноста процентам пути, но может потребоваться некоторый нюанс.

1) Функция GetSharedStringItemById возвращает SharedStringItem, а не строку, так что пример вызывающего кода не будет работать. Чтобы получить фактическое значение в виде строки, я считаю, что вам нужно запросить свойство InnerText SharedStringItem следующим образом:

public static string GetSharedStringItemById(WorkbookPart workbookPart, int id)
{
    return workbookPart.SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(id).InnerText;
}

2) Функция также (правильно) запрашивает int как часть своей подписи, но вызов кода примера предоставляет строку cell.CellValue.Text. Преобразовать строку в int тривиально, но это необходимо сделать, так как написанный код может сбивать с толку.

person Brent    schedule 17.11.2011

Нашел этот очень полезный фрагмент довольно давно, так что не могу иметь в виду автора.

private static string GetCellValue(string fileName, string sheetName, string addressName)
    {
        string value = null;

        using(SpreadsheetDocument document =  SpreadsheetDocument.Open(fileName, false))
        {
            WorkbookPart wbPart = document.WorkbookPart;

            // Find the sheet with the supplied name, and then use that Sheet
            // object to retrieve a reference to the appropriate worksheet.
            Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().
              Where(s => s.Name == sheetName).FirstOrDefault();

            if(theSheet == null)
            {
                throw new ArgumentException("sheetName");
            }

            // Retrieve a reference to the worksheet part, and then use its 
            // Worksheet property to get a reference to the cell whose 
            // address matches the address you supplied:
            WorksheetPart wsPart = (WorksheetPart)(wbPart.GetPartById(theSheet.Id));
            Cell theCell = wsPart.Worksheet.Descendants<Cell>().
              Where(c => c.CellReference == addressName).FirstOrDefault();

            // If the cell does not exist, return an empty string:
            if(theCell != null)
            {
                value = theCell.InnerText;

                // If the cell represents a numeric value, you are done. 
                // For dates, this code returns the serialized value that 
                // represents the date. The code handles strings and Booleans
                // individually. For shared strings, the code looks up the 
                // corresponding value in the shared string table. For Booleans, 
                // the code converts the value into the words TRUE or FALSE.
                if(theCell.DataType != null)
                {
                    switch(theCell.DataType.Value)
                    {
                        case CellValues.SharedString:
                            // For shared strings, look up the value in the shared 
                            // strings table.
                            var stringTable = wbPart.
                              GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
                            // If the shared string table is missing, something is 
                            // wrong. Return the index that you found in the cell.
                            // Otherwise, look up the correct text in the table.
                            if(stringTable != null)
                            {
                                value = stringTable.SharedStringTable.
                                  ElementAt(int.Parse(value)).InnerText;
                            }
                            break;

                        case CellValues.Boolean:
                            switch(value)
                            {
                                case "0":
                                    value = "FALSE";
                                    break;
                                default:
                                    value = "TRUE";
                                    break;
                            }
                            break;
                    }
                }
            }
        }
        return value;
    }
person Florian    schedule 24.08.2012
comment
Это из msdn: msdn.microsoft.com / ru-ru / library / office / - person nickvane; 13.12.2012
comment
Этот код настолько медленный, что нецелесообразно загружать что-либо, кроме таблицы 5x5. Чтобы добавить одну строку, требуется примерно 200 мсек! - person donkz; 02.04.2015
comment
Это вполне может быть связано с тем, что он каждый раз открывает файл. :) - person Chris Rae; 08.03.2017

Я нашел это сообщение о чтении всех данных Excel как таблица данных очень полезна. Он также использует open-xml sdk.

using System;
using System.Data;
using System.Linq;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;

public static DataTable ReadAsDataTable(string fileName)
{
    DataTable dataTable = new DataTable();
    using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(fileName, false))
    {
        WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;
        IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
        string relationshipId = sheets.First().Id.Value;
        WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument.WorkbookPart.GetPartById(relationshipId);
        Worksheet workSheet = worksheetPart.Worksheet;
        SheetData sheetData = workSheet.GetFirstChild<SheetData>();
        IEnumerable<Row> rows = sheetData.Descendants<Row>();

        foreach (Cell cell in rows.ElementAt(0))
        {
            dataTable.Columns.Add(GetCellValue(spreadSheetDocument, cell));
        }

        foreach (Row row in rows)
        {
            DataRow dataRow = dataTable.NewRow();
            for (int i = 0; i < row.Descendants<Cell>().Count(); i++)
            {
                dataRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i));
            }

            dataTable.Rows.Add(dataRow);
        }

    }
    dataTable.Rows.RemoveAt(0);

    return dataTable;
}

private static string GetCellValue(SpreadsheetDocument document, Cell cell)
{
    SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart;
    string value = cell.CellValue.InnerXml;

    if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
    {
        return stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;
    }
    else
    {
        return value;
    }
}

Примечание. Проблема заключается в том, что при чтении Excel игнорируются пустые ячейки в каждой строке. Таким образом, этот код лучше всего подходит, когда вы уверены, что каждая ячейка в каждой строке будет содержать некоторые данные. Если вам нужна соответствующая обработка того же самого, вы можете сделать следующее:

Измените код цикла for:

dataRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i));

to

Cell cell = row.Descendants<Cell>().ElementAt(i);
int actualCellIndex = CellReferenceToIndex(cell);
dataRow[actualCellIndex] = GetCellValue(spreadSheetDocument, cell);

и добавьте ниже метод, который используется в приведенном выше модифицированном фрагменте кода:

private static int CellReferenceToIndex(Cell cell)
{
    int index = 0;
    string reference = cell.CellReference.ToString().ToUpper();
    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;
}

Я получил это исправление из этого ответа.

person RBT    schedule 17.01.2018

Другой вариант: экспортируйте данные в таблицу html и используйте таблицы стилей, чтобы указать ячейки, доступные только для чтения. Дополнительную информацию см. На этой странице: http://www.c-sharpcorner.com/UploadFile/kaushikborah28/79Nick08302007171404PM/79Nick.aspx

person BA TabNabber    schedule 12.05.2011