Использование OpenXmlReader
Я ненавижу прибегать к StackOverflow для чего-то настолько (казалось бы) базового, но я боролся с Microsoft в течение последних нескольких часов и, кажется, попал в тупик. Я пытаюсь читать (большие) таблицы Excel 2007+, и Google любезно сообщил мне, что использование OpenXML SDK является довольно популярным выбором. Поэтому я попробовал эту штуку, прочитал несколько учебников, проверил страницы собственной библиотеки Microsoft и получил очень мало из них.
Я использую небольшую тестовую таблицу с только один столбец чисел и одна строка-крупномасштабное тестирование придет позже. Я попробовал несколько реализаций, подобных той, которую я собираюсь опубликовать, и ни одна из них не читает данные. Приведенный ниже код был в основном взят из другого потока StackOverflow, где он, казалось, работал - не так для меня. Я решил, что вы, ребята, будете проверять / отлаживать / помогать с этой версией, потому что она, вероятно, будет менее сломана, чем все, что я написал сегодня.
static void ReadExcelFileSAX(string fileName)
{
using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(fileName, true))
{
WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
OpenXmlPartReader reader = new OpenXmlPartReader(worksheetPart);
string text;
string rowNum;
while (reader.Read())
{
if (reader.ElementType == typeof(Row))
{
do
{
if (reader.HasAttributes)
{
rowNum = reader.Attributes.First(a => a.LocalName == "r").Value;
Console.Write("rowNum: " + rowNum); //we never even get here, I tested it with a breakpoint
}
} while (reader.ReadNextSibling()); // Skip to the next row
Console.ReadKey();
break; // We just looped through all the rows so no need to continue reading the worksheet
}
if (reader.ElementType == typeof(Cell))
{
}
if (reader.ElementType != typeof(Worksheet)) // Dont' want to skip the contents of the worksheet
reader.Skip(); // Skip contents of any node before finding the first row.
}
reader.Close();
Console.WriteLine();
Console.ReadKey();
}
}
И, на заметку сбоку, есть ли какие-нибудь хорошие альтернативы использованию OpenXML SDK я как-то пропустил?
2 ответа:
Я думаю, что вы взяли неправильный
WorksheetPart
для чтения строк.Линия
workbookPart.WorksheetParts.First();
Получает первый
WorksheetPart
из коллекции, который не должен обязательно должен быть первый лист, как вы видите его в Microsoft Excel.Итак, повторите все
WorksheetParts
, и вы увидите некоторые выходные данные на вашем окно консоли.static void ReadExcelFileSAX(string fileName) { using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(fileName, true)) { WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart; // Iterate through all WorksheetParts foreach (WorksheetPart worksheetPart in workbookPart.WorksheetParts) { OpenXmlPartReader reader = new OpenXmlPartReader(worksheetPart); string text; string rowNum; while (reader.Read()) { if (reader.ElementType == typeof(Row)) { do { if (reader.HasAttributes) { rowNum = reader.Attributes.First(a => a.LocalName == "r").Value; Console.Write("rowNum: " + rowNum); } } while (reader.ReadNextSibling()); // Skip to the next row break; // We just looped through all the rows so no // need to continue reading the worksheet } if (reader.ElementType != typeof(Worksheet)) reader.Skip(); } reader.Close(); } } }
Для чтения всех значений ячеек используйте следующую функцию (все детали обработки ошибок опущены):
static void ReadAllCellValues(string fileName) { using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(fileName, false)) { WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart; foreach(WorksheetPart worksheetPart in workbookPart.WorksheetParts) { OpenXmlReader reader = OpenXmlReader.Create(worksheetPart); while (reader.Read()) { if (reader.ElementType == typeof(Row)) { reader.ReadFirstChild(); do { if (reader.ElementType == typeof(Cell)) { Cell c = (Cell)reader.LoadCurrentElement(); string cellValue; if (c.DataType != null && c.DataType == CellValues.SharedString) { SharedStringItem ssi = workbookPart.SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(int.Parse(c.CellValue.InnerText)); cellValue = ssi.Text.Text; } else { cellValue = c.CellValue.InnerText; } Console.Out.Write("{0}: {1} ", c.CellReference, cellValue); } } while (reader.ReadNextSibling()); Console.Out.WriteLine(); } } } } }
В приведенном выше коде вы видите, что ячейки с типом данных
SharedString
должно обрабатываться с помощью theSharedStringTablePart
.
Для чтения пустых ячеек я использую переменную, назначенную вне считывателя строк и в цикле while, я проверяю, является ли индекс столбца больше или нет из моей переменной, поскольку он увеличивается после каждого чтения ячейки. если это не соответствует,я заполняю свою колонку значением, которое я хочу. Это трюк, который я использовал, чтобы поймать пустые ячейки в мое значение столбца уважения. Вот код:
public static DataTable ReadIntoDatatableFromExcel(string newFilePath) { /*Creating a table with 20 columns*/ var dt = CreateProviderRvenueSharingTable(); try { /*using stream so that if excel file is in another process then it can read without error*/ using (Stream stream = new FileStream(newFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(stream, false)) { var workbookPart = spreadsheetDocument.WorkbookPart; var workbook = workbookPart.Workbook; /*get only unhide tabs*/ var sheets = workbook.Descendants<Sheet>().Where(e => e.State == null); foreach (var sheet in sheets) { var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id); /*Remove empty sheets*/ List<Row> rows = worksheetPart.Worksheet.Elements<SheetData>().First().Elements<Row>() .Where(r => r.InnerText != string.Empty).ToList(); if (rows.Count > 1) { OpenXmlReader reader = OpenXmlReader.Create(worksheetPart); int i = 0; int BTR = 0;/*Break the reader while empty rows are found*/ while (reader.Read()) { if (reader.ElementType == typeof(Row)) { /*ignoring first row with headers and check if data is there after header*/ if (i < 2) { i++; continue; } reader.ReadFirstChild(); DataRow row = dt.NewRow(); int CN = 0; if (reader.ElementType == typeof(Cell)) { do { Cell c = (Cell)reader.LoadCurrentElement(); /*reader skipping blank cells so data is getting worng in datatable's rows according to header*/ if (CN != 0) { int cellColumnIndex = ExcelHelper.GetColumnIndexFromName( ExcelHelper.GetColumnName(c.CellReference)); if (cellColumnIndex < 20 && CN < cellColumnIndex - 1) { do { row[CN] = string.Empty; CN++; } while (CN < cellColumnIndex - 1); } } /*stopping execution if first cell does not have any value which means empty row*/ if (CN == 0 && c.DataType == null && c.CellValue == null) { BTR++; break; } string cellValue = GetCellValue(c, workbookPart); row[CN] = cellValue; CN++; /*if any text exists after T column (index 20) then skip the reader*/ if (CN == 20) { break; } } while (reader.ReadNextSibling()); } /*reader skipping blank cells so fill the array upto 19 index*/ while (CN != 0 && CN < 20) { row[CN] = string.Empty; CN++; } if (CN == 20) { dt.Rows.Add(row); } } /*escaping empty rows below data filled rows after checking 5 times */ if (BTR > 5) break; } reader.Close(); } } } } } catch (Exception ex) { throw ex; } return dt; } private static string GetCellValue(Cell c, WorkbookPart workbookPart) { string cellValue = string.Empty; if (c.DataType != null && c.DataType == CellValues.SharedString) { SharedStringItem ssi = workbookPart.SharedStringTablePart.SharedStringTable .Elements<SharedStringItem>() .ElementAt(int.Parse(c.CellValue.InnerText)); if (ssi.Text != null) { cellValue = ssi.Text.Text; } } else { if (c.CellValue != null) { cellValue = c.CellValue.InnerText; } } return cellValue; } public static int GetColumnIndexFromName(string columnNameOrCellReference) { int columnIndex = 0; int factor = 1; for (int pos = columnNameOrCellReference.Length - 1; pos >= 0; pos--) // R to L { if (Char.IsLetter(columnNameOrCellReference[pos])) // for letters (columnName) { columnIndex += factor * ((columnNameOrCellReference[pos] - 'A') + 1); factor *= 26; } } return columnIndex; } public static string GetColumnName(string cellReference) { /* Advance from L to R until a number, then return 0 through previous position*/ for (int lastCharPos = 0; lastCharPos <= 3; lastCharPos++) if (Char.IsNumber(cellReference[lastCharPos])) return cellReference.Substring(0, lastCharPos); throw new ArgumentOutOfRangeException("cellReference"); }
Код работает для: 1. Этот код считывает пустые ячейки 2. пропустить пустые строки после чтение завершено. 3. прочитайте лист от первого в порядке возрастания 4. если файл excel используется другим процессом, OpenXML все равно считывает его.