Usando Excel OleDb para obter nomes de planilhas EM ORDEM DE FOLHA

103

Estou usando o OleDb para ler uma pasta de trabalho do Excel com muitas planilhas.

Preciso ler os nomes das planilhas, mas preciso deles na ordem em que estão definidos na planilha; então, se eu tiver um arquivo parecido com este;

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

Então eu preciso pegar o dicionário

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

Eu tentei usar OleDbConnection.GetOleDbSchemaTable()e isso me dá uma lista de nomes, mas os classifica em ordem alfabética. A classificação alfa significa que não sei a qual número de folha corresponde um determinado nome. Então eu consigo;

GERMANY, IRELAND, UK

que mudou a ordem de UKe IRELAND.

A razão pela qual preciso que seja classificado é que devo deixar o usuário escolher um intervalo de dados por nome ou índice; eles podem pedir 'todos os dados da ALEMANHA para a IRLANDA' ou 'dados da folha 1 à folha 3'.

Quaisquer ideias seriam muito apreciadas.

se eu pudesse usar as aulas de interoperabilidade de escritório, isso seria simples. Infelizmente, não posso porque as classes de interoperabilidade não funcionam de forma confiável em ambientes não interativos, como serviços do Windows e sites ASP.NET, então precisei usar OLEDB.

Steve Cooper
fonte
Qual versão do arquivo Excel você está lendo?
yamen
30
Nossa, como você desenhou isso e como você teve paciência para desenhar
l --''''''--------- '' '' '' '' '' ''
4
@ АртёмЦарионов - são linhas de barras verticais (|) e sublinhados (_) para a tabela, e barras invertidas e anteriores (\ /) para as guias. Copie para um editor de texto e você verá.
Sid Holland

Respostas:

17

Não é possível encontrar isso na documentação real do MSDN, mas um moderador nos fóruns disse

Receio que o OLEDB não preserve a ordem da planilha como acontecia no Excel

Nomes de planilhas do Excel na ordem das planilhas

Parece que esse seria um requisito comum o suficiente para que houvesse uma solução alternativa decente.

Jeremy Breece
fonte
No entanto, a resposta foi direta, mas economizou muito tempo em tentativas desnecessárias.
Shihe Zhang
75

Você não pode simplesmente percorrer as folhas de 0 a Contagem de nomes -1? dessa forma, você deve colocá-los na ordem correta.

Editar

Percebi através dos comentários que há muitas preocupações sobre o uso das classes Interop para recuperar os nomes das planilhas. Portanto, aqui está um exemplo usando OLEDB para recuperá-los:

/// <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 do artigo no CodeProject.

James
fonte
Esse é o código que eu gostaria de ver! Como você pode consultar 'a enésima folha' e o número de folhas?
Steve Cooper
13
Olá James. Este é basicamente meu problema original - enquanto o método GetOleDbSchemaTable () obtém os nomes, o número da linha não corresponde ao número da planilha da pasta de trabalho. Portanto, a Folha 4 seria a linha 0, se fosse a primeira no alfabeto.
Steve Cooper
23
Não responde à pergunta dos pôsteres (ele quer que apareça no Excel)
Andrew White,
7
@Samuel Não acho que tenha resolvido o problema do OP diretamente, no entanto, pareceu ajudar muitas outras pessoas com um problema semelhante.
James
1
Não resolve a dúvida do OP, que é o que vim procurar. (Eu sempre posto o motivo de um voto negativo.)
Phil Nicholas
23

Uma vez que o código acima não cobre os procedimentos para extrair a lista do nome da planilha para o Excel 2007, o código a seguir também será aplicável para o Excel (97-2003) e o Excel 2007:

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

A função acima retorna uma lista de planilhas em um arquivo excel específico para ambos os tipos de excel (97,2003,2007).

TruthOf42
fonte
11
Este código não retorna as planilhas na ordem em que aparecem no Excel
Andrew White
10

Isso é curto, rápido, seguro e utilizável ...

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
fonte
Não funciona "fora da caixa". exceladdress- o que é isso?
Michael Hutter
8

Outra maneira:

um arquivo xls (x) é apenas uma coleção de arquivos * .xml armazenados em um contêiner * .zip. descompacte o arquivo "app.xml" na pasta 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>

O arquivo é um arquivo alemão (Arbeitsblätter = planilhas). Os nomes das tabelas (Tabelle3 etc) estão na ordem correta. Você só precisa ler essas tags;)

Saudações

kraeppy
fonte
1
Isso funciona bem para arquivos xlsx, mas não para arquivos xls. Eles não têm a mesma estrutura. Você sabe como os mesmos dados podem ser extraídos de um arquivo xls?
rdans
6

Criei a função abaixo usando as informações fornecidas na resposta de @kraeppy ( https://stackoverflow.com/a/19930386/2617732 ). Isso requer que o .net framework v4.5 seja usado e requer uma referência a System.IO.Compression. Isso só funciona para arquivos xlsx e não para os arquivos xls mais antigos.

    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
fonte
2

Gosto da ideia de @deathApril para nomear as planilhas como 1_Germany, 2_UK, 3_IRELAND. Também recebi o seu problema para renomear centenas de planilhas. Se você não tiver problemas para renomear o nome da planilha, poderá usar esta macro para fazer isso por você. Levará menos de segundos para renomear todos os nomes de planilhas. infelizmente ODBC, OLEDB retornar a ordem do nome da folha por asc. Não há substituto para isso. Você deve usar COM ou renomear seu nome para que esteja no pedido.

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

ATUALIZAÇÃO: Depois de ler o comentário de @SidHoland sobre o BIFF, uma ideia surgiu. As etapas a seguir podem ser executadas por meio de código. Não sei se você realmente deseja fazer isso para colocar os nomes das planilhas na mesma ordem. Avise-me se precisar de ajuda para fazer isso por meio de 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

ATUALIZAÇÃO: Outra solução - NPOI pode ser útil aqui 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 solução funciona para xls. Eu não tentei xlsx.

Obrigado,

Esen

Esen
fonte
1
Você não precisa renomear as planilhas ou apenas usar COM, como minha resposta demonstra que você pode usar DAO. Acho que também pode haver uma maneira de recuperá-los lendo o BIFF , mas ainda estou investigando isso.
Sid Holland
1
@SidHolland: DAO é um componente COM. Usar o componente COM no Server 2008 é um problema, portanto Steve foi com ADO.NET
Esen
Meu cérebro não percebeu que o DAO é um componente COM, apesar de ter que adicioná-lo como uma referência COM para usá-lo. Obrigado pela correção. Sua adição (renomear para um zip e ler o XML) é genial. Eu não tinha ideia de que isso funcionaria. É, até o momento, o único método que irá mostrar as planilhas em ordem sem o uso do COM. +1!
Sid Holland
1

Isso funcionou para mim. Roubado daqui: como você consegue o nome da primeira página de uma pasta de trabalho do 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;
malvado
fonte
2
Oi. Que bom que você tem um código funcionando, mas que usa as classes Interop, e elas não funcionam de maneira confiável em um servidor; você não pode executar esse código, digamos, no Windows Server 2008. Portanto, você não pode usá-lo em um aplicativo da web ou em código do lado do servidor. É por isso que eu estava optando pelo Oledb, em vez do Interop.
Steve Cooper,
1

Experimente isso. Aqui está o código para colocar os nomes das folhas em ordem.

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
fonte
Ist nicht mal compilierfähig! (Zeile Excel.XlFileFormat.xlWorkbookNormal)
Michael Hutter
0

De acordo com o MSDN, em um caso de planilhas dentro do Excel, pode não funcionar porque os arquivos do Excel não são bancos de dados reais. Assim, você não conseguirá obter o nome das planilhas na ordem de sua visualização na pasta de trabalho.

Código para obter o nome das folhas de acordo com sua aparência visual usando interoperabilidade:

Adicione referência à Biblioteca de objetos do Microsoft Excel 12.0.

O código a seguir fornecerá o nome das planilhas na ordem real armazenada na pasta de trabalho, não o nome classificado.

Código de amostra:

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
fonte
0

Não vejo nenhuma documentação que diga que a ordem em app.xml é garantida como a ordem das planilhas. PROVAVELMENTE é, mas não de acordo com a especificação OOXML.

O arquivo workbook.xml, por outro lado, inclui o atributo sheetId, que determina a sequência - de 1 ao número de folhas. Isso está de acordo com a especificação OOXML. workbook.xml é descrito como o local onde a sequência das planilhas é mantida.

Portanto, a leitura do workbook.xml depois de extraído do XLSX seria minha recomendação. NÃO app.xml. Em vez de docProps / app.xml, use xl / workbook.xml e observe o elemento, conforme mostrado aqui -

`

<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
fonte