2016-03-19 3 views
4

задачC# ОТКРЫТЫЙ XML: пустые ячейки становятся пропущена при получении данных из EXCEL в DataTable

Импорт данных из excel в DataTable

Проблема

Клетка, что оленья кожа содержат каких-либо данных, пропущены, а самая следующая ячейка, имеющая данные в строке, используется как значение пустой колонии. Например

A1 пуст A2 имеет значение Tom то при импорте данных A1 получить значение A2 и A2 остается пустым

Чтобы сделать это очень ясно, я обеспечиваю некоторые снимки экрана ниже

Данные этого переводчика

enter image description here

Это DataTable после импорта данных из Excel enter image description here

Код

public class ImportExcelOpenXml 
{ 
    public static DataTable Fill_dataTable(string fileName) 
    { 
     DataTable dt = 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)) 
      { 
       dt.Columns.Add(GetCellValue(spreadSheetDocument, cell)); 
      } 

      foreach (Row row in rows) //this will also include your header row... 
      { 
       DataRow tempRow = dt.NewRow(); 

       for (int i = 0; i < row.Descendants<Cell>().Count(); i++) 
       { 
        tempRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i)); 
       } 

       dt.Rows.Add(tempRow); 
      } 

     } 

     dt.Rows.RemoveAt(0); //...so i'm taking it out here. 

     return dt; 
    } 


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

Мои мысли

I Тхи пк есть некоторая проблема с

public IEnumerable<T> Descendants<T>() where T : OpenXmlElement;

В случае, если я хочу, чтобы количество столбцов с помощью Descendants

IEnumerable<Row> rows = sheetData.Descendants<<Row>(); 
int colCnt = rows.ElementAt(0).Count(); 

ИЛИ

Если я получаю количество строк с помощью Descendants

IEnumerable<Row> rows = sheetData.Descendants<<Row>(); 
int rowCnt = rows.Count();` 

В обоих случаях es Descendants пропускает пустые ячейки

Есть ли альтернатива Descendants.

Ваши предложения будут высоко оценены

PS: Я тоже думал о получении значений ячеек с использованием имен столбцов, как A1, A2, но для того, чтобы сделать это, мне придется, чтобы получить точное количество столбцов и строки, которые невозможны с помощью функции Descendants.

+0

Пустые ячейки не имеют элемента 'Cell', поэтому вы не можете их найти. –

+0

@AlexanderDerck Тогда как решить эту проблему? –

+1

Использование библиотеки EPPlus было бы проще для этого (он использует открытый xml sdk), см. Пример [здесь] (http://stackoverflow.com/a/13396787/3410196) –

ответ

4
public void Read2007Xlsx() 
     { 
      try 
      { 
       DataTable dt = new DataTable(); 
       using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(@"D:\File.xlsx", 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)) 
        { 
         dt.Columns.Add(GetCellValue(spreadSheetDocument, cell)); 
        } 
        foreach (Row row in rows) //this will also include your header row... 
        { 
         DataRow tempRow = dt.NewRow(); 
         int columnIndex = 0; 
         foreach (Cell cell in row.Descendants<Cell>()) 
         { 
          // Gets the column index of the cell with data 
          int cellColumnIndex = (int)GetColumnIndexFromName(GetColumnName(cell.CellReference)); 
          cellColumnIndex--; //zero based index 
          if (columnIndex < cellColumnIndex) 
          { 
           do 
           { 
            tempRow[columnIndex] = ""; //Insert blank data here; 
            columnIndex++; 
           } 
           while (columnIndex < cellColumnIndex); 
          } 
          tempRow[columnIndex] = GetCellValue(spreadSheetDocument, cell); 

          columnIndex++; 
         } 
         dt.Rows.Add(tempRow); 
        } 
       } 
       dt.Rows.RemoveAt(0); //...so i'm taking it out here. 
      } 
      catch (Exception ex) 
      { 
      } 
     } 
     /// <summary> 
     /// Given a cell name, parses the specified cell to get the column name. 
     /// </summary> 
     /// <param name="cellReference">Address of the cell (ie. B2)</param> 
     /// <returns>Column Name (ie. B)</returns> 
     public static string GetColumnName(string cellReference) 
     { 
      // Create a regular expression to match the column name portion of the cell name. 
      Regex regex = new Regex("[A-Za-z]+"); 
      Match match = regex.Match(cellReference); 
      return match.Value; 
     } 
     /// <summary> 
     /// Given just the column name (no row index), it will return the zero based column index. 
     /// Note: This method will only handle columns with a length of up to two (ie. A to Z and AA to ZZ). 
     /// A length of three can be implemented when needed. 
     /// </summary> 
     /// <param name="columnName">Column Name (ie. A or AB)</param> 
     /// <returns>Zero based index if the conversion was successful; otherwise null</returns> 
     public static int? GetColumnIndexFromName(string columnName) 
     { 

      //return columnIndex; 
      string name = columnName; 
      int number = 0; 
      int pow = 1; 
      for (int i = name.Length - 1; i >= 0; i--) 
      { 
       number += (name[i] - 'A' + 1) * pow; 
       pow *= 26; 
      } 
      return number; 
     } 
     public static string GetCellValue(SpreadsheetDocument document, Cell cell) 
     { 
      SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart; 
      if (cell.CellValue ==null) 
      { 
      return ""; 
      } 
      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; 
      } 
     } 
0

Попробуйте этот код, я сделал небольшие изменения, и он работал на меня.

public static DataTable Fill_dataTable(string filePath) 
    { 
     DataTable dt = new DataTable(); 

     using (SpreadsheetDocument doc = SpreadsheetDocument.Open(filePath, false)) 
     { 
      Sheet sheet = doc.WorkbookPart.Workbook.Sheets.GetFirstChild<Sheet>(); 
      Worksheet worksheet = doc.WorkbookPart.GetPartById(sheet.Id.Value) as WorksheetPart.Worksheet; 
      IEnumerable<Row> rows = worksheet.GetFirstChild<SheetData>().Descendants<Row>(); 
      DataTable dt = new DataTable(); 
      List<string> columnRef = new List<string>(); 
      foreach (Row row in rows) 
      { 
       if (row.RowIndex != null) 
       { 
        if (row.RowIndex.Value == 1) 
        { 
         foreach (Cell cell in row.Descendants<Cell>()) 
         { 
          dt.Columns.Add(GetValue(doc, cell)); 
          columnRef.Add(cell.CellReference.ToString().Substring(0, cell.CellReference.ToString().Length - 1)); 
         } 
        } 
        else 
        { 
         dt.Rows.Add(); 
         int i = 0; 
         foreach (Cell cell in row.Descendants<Cell>()) 
         { 
          while (columnRef(i) + dt.Rows.Count + 1 != cell.CellReference) 
          { 
           dt.Rows(dt.Rows.Count - 1)(i) = ""; 
           i += 1; 
          } 

          dt.Rows(dt.Rows.Count - 1)(i) = GetValue(doc, cell); 
          i += 1; 
         } 
        } 
       } 
      } 
     } 

     return dt; 
    } 
0

Вы должны заменить ваше для кода контура, как показано ниже:

for (int i = 0; i < row.Descendants<Cell>().Count(); i++) 
{ 
     tempRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i)); 
} 

к

for (int i = 0; i < row.Descendants<Cell>().Count(); i++) 
{ 
    Cell cell = row.Descendants<Cell>().ElementAt(i); 
    int actualCellIndex = CellReferenceToIndex(cell); 
    tempRow[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; 
}