Открыть значение 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
Есть ли у кого-нибудь идея получить правильное значение ячейки?
Ответы
Ответ 1
Все строки в листе 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;
}
}
}
}
Ответ 2
Амуррский ответ, кажется, идет девяносто процентов пути, но может потребоваться некоторый нюанс.
1) Функция GetSharedStringItemById возвращает объект SharedStringItem, а не строку, так что пример кода вызова не будет работать. Чтобы получить фактическое значение в виде строки, я считаю, что вам нужно задать свойство SharedStringItem InnerText следующим образом:
public static string GetSharedStringItemById(WorkbookPart workbookPart, int id)
{
return workbookPart.SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(id).InnerText;
}
2) Функция также (правильно) запрашивает int как часть своей подписи, но вызов кода кода предоставляет строку cell.CellValue.Text. Это тривиально, чтобы преобразовать строку в int, но это нужно сделать, поскольку написанный код может быть запутанным.
Ответ 3
Обнаружил этот очень полезный фрагмент совсем недавно, поэтому он не может содержать автора.
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;
}
Ответ 4
Другая опция: экспортируйте данные в таблицу html и используйте таблицы стилей, чтобы указать ячейки только для чтения. См. Эту страницу для получения дополнительной информации:
http://www.c-sharpcorner.com/UploadFile/kaushikborah28/79Nick08302007171404PM/79Nick.aspx
Ответ 5
Я нашел это сообщение о чтении целых данных 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));
к
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;
}
Я получил это исправление из этого ответа.