Usando Excel OleDb para obtener nombres de hojas EN ORDEN DE HOJA

103

Estoy usando OleDb para leer un libro de Excel con muchas hojas.

Necesito leer los nombres de las hojas, pero los necesito en el orden en que están definidos en la hoja de cálculo; así que si tengo un archivo que se ve así;

|_____|_____|____|____|____|____|____|____|____|
|_____|_____|____|____|____|____|____|____|____|
|_____|_____|____|____|____|____|____|____|____|
\__GERMANY__/\__UK__/\__IRELAND__/

Entonces necesito conseguir el diccionario

1="GERMANY", 
2="UK", 
3="IRELAND"

He intentado usar OleDbConnection.GetOleDbSchemaTable(), y eso me da la lista de nombres, pero los ordena alfabéticamente. La clasificación alfa significa que no sé a qué número de hoja corresponde un nombre en particular. Así que consigo;

GERMANY, IRELAND, UK

que ha cambiado el orden de UKy IRELAND.

La razón por la que necesito que se ordene es que tengo que dejar que el usuario elija un rango de datos por nombre o índice; pueden solicitar "todos los datos de ALEMANIA a IRLANDA" o "datos de la hoja 1 a la hoja 3".

Cualquier idea será muy apreciada.

si pudiera usar las clases de interoperabilidad de la oficina, esto sería sencillo. Desafortunadamente, no puedo porque las clases de interoperabilidad no funcionan de manera confiable en entornos no interactivos como los servicios de Windows y los sitios ASP.NET, por lo que necesitaba usar OLEDB.

Steve Cooper
fuente
¿Qué versión del archivo de Excel estás leyendo?
yamen
30
wow, ¿cómo
dibujaste
4
@ АртёмЦарионов: son filas de barras verticales (|) y guiones bajos (_) para la tabla, y barras inclinadas hacia atrás y hacia adelante (\ /) para las pestañas. Cópielo en un editor de texto y verá.
Sid Holland

Respuestas:

17

No puedo encontrar esto en la documentación real de MSDN, pero un moderador en los foros dijo

Me temo que OLEDB no conserva el orden de las hojas como estaban en Excel

Nombres de hojas de Excel en orden de hojas

Parece que este sería un requisito lo suficientemente común como para que hubiera una solución decente.

Jeremy Breece
fuente
Sin embargo, esto respondió directamente, ahorra mucho tiempo en intentos innecesarios.
Shihe Zhang
75

¿No puede simplemente recorrer las hojas desde 0 hasta Número de nombres -1? de esa manera, debe colocarlos en el orden correcto.

Editar

A través de los comentarios, noté que hay muchas preocupaciones sobre el uso de las clases de interoperabilidad para recuperar los nombres de las hojas. Por lo tanto, aquí hay un ejemplo que usa OLEDB para recuperarlos:

/// <summary>
/// This method retrieves the excel sheet names from 
/// an excel workbook.
/// </summary>
/// <param name="excelFile">The excel file.</param>
/// <returns>String[]</returns>
private String[] GetExcelSheetNames(string excelFile)
{
    OleDbConnection objConn = null;
    System.Data.DataTable dt = null;

    try
    {
        // Connection String. Change the excel file to the file you
        // will search.
        String connString = "Provider=Microsoft.Jet.OLEDB.4.0;" + 
          "Data Source=" + excelFile + ";Extended Properties=Excel 8.0;";
        // Create connection object by using the preceding connection string.
        objConn = new OleDbConnection(connString);
        // Open connection with the database.
        objConn.Open();
        // Get the data table containg the schema guid.
        dt = objConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);

        if(dt == null)
        {
           return null;
        }

        String[] excelSheets = new String[dt.Rows.Count];
        int i = 0;

        // Add the sheet name to the string array.
        foreach(DataRow row in dt.Rows)
        {
           excelSheets[i] = row["TABLE_NAME"].ToString();
           i++;
        }

        // Loop through all of the sheets if you want too...
        for(int j=0; j < excelSheets.Length; j++)
        {
            // Query each excel sheet.
        }

        return excelSheets;
   }
   catch(Exception ex)
   {
       return null;
   }
   finally
   {
      // Clean up.
      if(objConn != null)
      {
          objConn.Close();
          objConn.Dispose();
      }
      if(dt != null)
      {
          dt.Dispose();
      }
   }
}

Extraído del artículo sobre CodeProject.

James
fuente
¡Ese es el código que me gustaría ver! ¿Cómo puede consultar "la hoja N" y el número de hojas?
Steve Cooper
13
Hola James. Este es prácticamente mi problema original: mientras que el método GetOleDbSchemaTable () obtiene los nombres, el número de fila no corresponde al número de hoja del libro de trabajo. Entonces, la Hoja 4 sería la fila 0, si fuera la primera en el alfabeto.
Steve Cooper
23
No responde la pregunta de los carteles (lo quiere en orden de aparición en Excel)
Andrew White
7
@Samuel No creo que haya resuelto el problema del OP directamente, sin embargo, ha parecido ayudar a muchos otros con un problema similar.
James
1
No resuelve la pregunta del OP, que es lo que vine a buscar. (Siempre publico el motivo de un voto negativo.)
Phil Nicholas
23

Dado que el código anterior no cubre los procedimientos para extraer la lista del nombre de la hoja para Excel 2007, el siguiente código será aplicable tanto para Excel (97-2003) como para Excel 2007 también:

public List<string> ListSheetInExcel(string filePath)
{
   OleDbConnectionStringBuilder sbConnection = new OleDbConnectionStringBuilder();
   String strExtendedProperties = String.Empty;
   sbConnection.DataSource = filePath;
   if (Path.GetExtension(filePath).Equals(".xls"))//for 97-03 Excel file
   {
      sbConnection.Provider = "Microsoft.Jet.OLEDB.4.0";
      strExtendedProperties = "Excel 8.0;HDR=Yes;IMEX=1";//HDR=ColumnHeader,IMEX=InterMixed
   }
   else if (Path.GetExtension(filePath).Equals(".xlsx"))  //for 2007 Excel file
   {
      sbConnection.Provider = "Microsoft.ACE.OLEDB.12.0";
      strExtendedProperties = "Excel 12.0;HDR=Yes;IMEX=1";
   }
   sbConnection.Add("Extended Properties",strExtendedProperties);
   List<string> listSheet = new List<string>();
   using (OleDbConnection conn = new OleDbConnection(sbConnection.ToString()))
   {
     conn.Open();
     DataTable dtSheet = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);         
     foreach (DataRow drSheet in dtSheet.Rows)
     {
        if (drSheet["TABLE_NAME"].ToString().Contains("$"))//checks whether row contains '_xlnm#_FilterDatabase' or sheet name(i.e. sheet name always ends with $ sign)
        {
             listSheet.Add(drSheet["TABLE_NAME"].ToString());
        } 
     }
  }
 return listSheet;
}

La función anterior devuelve la lista de hojas en particular el archivo de Excel para ambos tipos de Excel (97,2003,2007).

TruthOf42
fuente
11
Este código no devuelve las hojas en el orden en que aparecen en Excel
Andrew White
10

Esto es breve, rápido, seguro y utilizable ...

public static List<string> ToExcelsSheetList(string excelFilePath)
{
    List<string> sheets = new List<string>();
    using (OleDbConnection connection = 
            new OleDbConnection((excelFilePath.TrimEnd().ToLower().EndsWith("x")) 
            ? "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" + excelFilePath + "';" + "Extended Properties='Excel 12.0 Xml;HDR=YES;'"
            : "provider=Microsoft.Jet.OLEDB.4.0;Data Source='" + excelFilePath + "';Extended Properties=Excel 8.0;"))
    {
        connection.Open();
        DataTable dt = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
        foreach (DataRow drSheet in dt.Rows)
            if (drSheet["TABLE_NAME"].ToString().Contains("$"))
            {
                string s = drSheet["TABLE_NAME"].ToString();
                sheets.Add(s.StartsWith("'")?s.Substring(1, s.Length - 3): s.Substring(0, s.Length - 1));
            }
        connection.Close();
    }
    return sheets;
}
Mohammad Fathi MiMFa
fuente
No funciona "fuera de la caja". exceladdress- ¿Que es esto?
Michael Hutter
8

De otra manera:

un archivo xls (x) es solo una colección de archivos * .xml almacenados en un contenedor * .zip. descomprime el archivo "app.xml" en la carpeta docProps.

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
-<Properties xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes" xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
<TotalTime>0</TotalTime>
<Application>Microsoft Excel</Application>
<DocSecurity>0</DocSecurity>
<ScaleCrop>false</ScaleCrop>
-<HeadingPairs>
  -<vt:vector baseType="variant" size="2">
    -<vt:variant>
      <vt:lpstr>Arbeitsblätter</vt:lpstr>
    </vt:variant>
    -<vt:variant>
      <vt:i4>4</vt:i4>
    </vt:variant>
  </vt:vector>
</HeadingPairs>
-<TitlesOfParts>
  -<vt:vector baseType="lpstr" size="4">
    <vt:lpstr>Tabelle3</vt:lpstr>
    <vt:lpstr>Tabelle4</vt:lpstr>
    <vt:lpstr>Tabelle1</vt:lpstr>
    <vt:lpstr>Tabelle2</vt:lpstr>
  </vt:vector>
</TitlesOfParts>
<Company/>
<LinksUpToDate>false</LinksUpToDate>
<SharedDoc>false</SharedDoc>
<HyperlinksChanged>false</HyperlinksChanged>
<AppVersion>14.0300</AppVersion>
</Properties>

El archivo es un archivo alemán (Arbeitsblätter = hojas de trabajo). Los nombres de las tablas (Tabelle3, etc.) están en el orden correcto. Solo necesitas leer estas etiquetas;)

Saludos

Kraeppy
fuente
1
Esto funciona bien para archivos xlsx pero no para archivos xls. No tienen la misma estructura. ¿Sabes cómo se pueden extraer los mismos datos de un archivo xls?
rdans
6

Creé la función a continuación utilizando la información proporcionada en la respuesta de @kraeppy ( https://stackoverflow.com/a/19930386/2617732 ). Esto requiere el uso de .net framework v4.5 y requiere una referencia a System.IO.Compression. Esto solo funciona para archivos xlsx y no para archivos xls más antiguos.

    using System.IO.Compression;
    using System.Xml;
    using System.Xml.Linq;

    static IEnumerable<string> GetWorksheetNamesOrdered(string fileName)
    {
        //open the excel file
        using (FileStream data = new FileStream(fileName, FileMode.Open))
        {
            //unzip
            ZipArchive archive = new ZipArchive(data);

            //select the correct file from the archive
            ZipArchiveEntry appxmlFile = archive.Entries.SingleOrDefault(e => e.FullName == "docProps/app.xml");

            //read the xml
            XDocument xdoc = XDocument.Load(appxmlFile.Open());

            //find the titles element
            XElement titlesElement = xdoc.Descendants().Where(e => e.Name.LocalName == "TitlesOfParts").Single();

            //extract the worksheet names
            return titlesElement
                .Elements().Where(e => e.Name.LocalName == "vector").Single()
                .Elements().Where(e => e.Name.LocalName == "lpstr")
                .Select(e => e.Value);
        }
    }
rdans
fuente
2

Me gusta la idea de @deathApril para nombrar las hojas como 1_Germany, 2_UK, 3_IRELAND. También recibí tu problema para cambiar el nombre de cientos de hojas. Si no tiene problemas para cambiar el nombre de la hoja, puede usar esta macro para hacerlo por usted. Tomará menos de segundos cambiar el nombre de todos los nombres de las hojas. desafortunadamente ODBC, OLEDB devuelve el orden del nombre de la hoja por asc. No hay reemplazo para eso. Tiene que usar COM o cambiar el nombre de su nombre para que esté en el orden.

Sub Macro1()
'
' Macro1 Macro
'

'
Dim i As Integer
For i = 1 To Sheets.Count
 Dim prefix As String
 prefix = i
 If Len(prefix) < 4 Then
  prefix = "000"
 ElseIf Len(prefix) < 3 Then
  prefix = "00"
 ElseIf Len(prefix) < 2 Then
  prefix = "0"
 End If
 Dim sheetName As String
 sheetName = Sheets(i).Name
 Dim names
 names = Split(sheetName, "-")
 If (UBound(names) > 0) And IsNumeric(names(0)) Then
  'do nothing
 Else
  Sheets(i).Name = prefix & i & "-" & Sheets(i).Name
 End If
Next

End Sub

ACTUALIZACIÓN: Después de leer el comentario de @SidHoland sobre BIFF, surgió una idea. Los siguientes pasos se pueden realizar mediante código. No sé si realmente desea hacer eso para que los nombres de las hojas estén en el mismo orden. Avísame si necesitas ayuda para hacer esto a través del código.

1. Consider XLSX as a zip file. Rename *.xlsx into *.zip
2. Unzip
3. Go to unzipped folder root and open /docprops/app.xml
4. This xml contains the sheet name in the same order of what you see.
5. Parse the xml and get the sheet names

ACTUALIZACIÓN: Otra solución: NPOI podría ser útil aquí http://npoi.codeplex.com/

 FileStream file = new FileStream(@"yourexcelfilename", FileMode.Open, FileAccess.Read);

      HSSFWorkbook  hssfworkbook = new HSSFWorkbook(file);
        for (int i = 0; i < hssfworkbook.NumberOfSheets; i++)
        {
            Console.WriteLine(hssfworkbook.GetSheetName(i));
        }
        file.Close();

Esta solución funciona para xls. No probé xlsx.

Gracias,

Esen

Esen
fuente
1
No tiene que cambiar el nombre de las hojas o solo usar COM, ya que mi respuesta demuestra que puede usar DAO. Creo que también podría haber una forma de recuperarlos leyendo el BIFF , pero todavía estoy investigando eso.
Sid Holland
1
@SidHolland: DAO es un componente COM. El uso del componente COM en Server 2008 es un problema, por lo tanto, Steve eligió ADO.NET
Esen
Mi cerebro no pensó que DAO es un componente COM, a pesar de tener que agregarlo como referencia COM para usarlo. Gracias por la corrección. Su adición (cambiar el nombre a un archivo zip y leer el XML) es genial. No tenía idea de que funcionaría. Es, hasta ahora, el único método que mostrará las hojas en orden sin usar COM. +1!
Sid Holland
1

Esto funcionó para mí. Robado de aquí: ¿Cómo se obtiene el nombre de la primera página de un libro de Excel?

object opt = System.Reflection.Missing.Value;
Excel.Application app = new Microsoft.Office.Interop.Excel.Application();
Excel.Workbook workbook = app.Workbooks.Open(WorkBookToOpen,
                                         opt, opt, opt, opt, opt, opt, opt,
                                         opt, opt, opt, opt, opt, opt, opt);
Excel.Worksheet worksheet = workbook.Worksheets[1] as Microsoft.Office.Interop.Excel.Worksheet;
string firstSheetName = worksheet.Name;
eviljack
fuente
2
Hola. Me alegro de que tenga un código que funcione, pero eso usa las clases Interop y no funcionan de manera confiable en un servidor; no puede ejecutar este código en, digamos, Windows Server 2008. Por lo tanto, no puede usarlo en una aplicación web o en código del lado del servidor. Por eso optaba por Oledb, en lugar de Interop.
Steve Cooper
1

Prueba esto. Aquí está el código para ordenar los nombres de las hojas.

private Dictionary<int, string> GetExcelSheetNames(string fileName)
{
    Excel.Application _excel = null;
    Excel.Workbook _workBook = null;
    Dictionary<int, string> excelSheets = new Dictionary<int, string>();
    try
    {
        object missing = Type.Missing;
        object readOnly = true;
        Excel.XlFileFormat.xlWorkbookNormal
        _excel = new Excel.ApplicationClass();
        _excel.Visible = false;
        _workBook = _excel.Workbooks.Open(fileName, 0, readOnly, 5, missing,
            missing, true, Excel.XlPlatform.xlWindows, "\\t", false, false, 0, true, true, missing);
        if (_workBook != null)
        {
            int index = 0;
            foreach (Excel.Worksheet sheet in _workBook.Sheets)
            {
                // Can get sheet names in order they are in workbook
                excelSheets.Add(++index, sheet.Name);
            }
        }
    }
    catch (Exception e)
    {
        return null;
    }
    finally
    {
        if (_excel != null)
        {

            if (_workBook != null)
                _workBook.Close(false, Type.Missing, Type.Missing);
            _excel.Application.Quit();
        }
        _excel = null;
        _workBook = null;
    }
    return excelSheets;
}
Ravi Shankar
fuente
Ist nicht mal compilierfähig! (Zeile Excel.XlFileFormat.xlWorkbookNormal)
Michael Hutter
0

Según MSDN, en el caso de hojas de cálculo dentro de Excel, es posible que no funcione porque los archivos de Excel no son bases de datos reales. Por lo tanto, no podrá obtener el nombre de las hojas en el orden de visualización en el libro de trabajo.

Código para obtener el nombre de las hojas según su apariencia visual usando interoperabilidad:

Agregue una referencia a la biblioteca de objetos de Microsoft Excel 12.0.

El siguiente código dará el nombre de las hojas en el orden real almacenado en el libro de trabajo, no el nombre ordenado.

Código de muestra:

using Microsoft.Office.Interop.Excel;

string filename = "C:\\romil.xlsx";

object missing = System.Reflection.Missing.Value;

Microsoft.Office.Interop.Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();

Microsoft.Office.Interop.Excel.Workbook wb =excel.Workbooks.Open(filename,  missing,  missing,  missing,  missing,missing,  missing,  missing,  missing,  missing,  missing,  missing,  missing,  missing,  missing);

ArrayList sheetname = new ArrayList();

foreach (Microsoft.Office.Interop.Excel.Worksheet  sheet in wb.Sheets)
{
    sheetname.Add(sheet.Name);
}
Romil Kumar Jain
fuente
0

No veo ninguna documentación que diga que el orden en app.xml está garantizado para ser el orden de las hojas. PROBABLEMENTE lo es, pero no de acuerdo con la especificación OOXML.

El archivo workbook.xml, por otro lado, incluye el atributo sheetId, que determina la secuencia, desde 1 hasta el número de hojas. Esto está de acuerdo con la especificación OOXML. workbook.xml se describe como el lugar donde se guarda la secuencia de las hojas.

Entonces, leer workbook.xml después de extraerlo del XLSX sería mi recomendación. NO app.xml. En lugar de docProps / app.xml, use xl / workbook.xml y observe el elemento, como se muestra aquí:

'

<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="9303" /> 
  <workbookPr defaultThemeVersion="124226" /> 
- <bookViews>
  <workbookView xWindow="120" yWindow="135" windowWidth="19035" windowHeight="8445" /> 
  </bookViews>
- <sheets>
  <sheet name="By song" sheetId="1" r:id="rId1" /> 
  <sheet name="By actors" sheetId="2" r:id="rId2" /> 
  <sheet name="By pit" sheetId="3" r:id="rId3" /> 
  </sheets>
- <definedNames>
  <definedName name="_xlnm._FilterDatabase" localSheetId="0" hidden="1">'By song'!$A$1:$O$59</definedName> 
  </definedNames>
  <calcPr calcId="145621" /> 
  </workbook>

'

Vern Hamberg
fuente