Использование Excel OleDb для получения имен листов в порядке листов

Я использую OleDb для чтения из книги excel со многими листами.

мне нужно прочитать имена листов, но они мне нужны в том порядке, в котором они определены в электронной таблице; поэтому, если у меня есть файл, который выглядит так;

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

тогда мне нужно получить словарь

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

Я пробовал использовать OleDbConnection.GetOleDbSchemaTable(), и это дает мне список имен, но он сортирует их по алфавиту. Альфа-сортировка означает, что я не знаю, какой номер листа определенное имя соответствовать. Так я получаю;

GERMANY, IRELAND, UK

который изменил порядок UK и IRELAND.

причина, по которой мне нужно его отсортировать, заключается в том, что я должен позволить пользователю выбрать диапазон данных по имени или индексу; они могут запросить "все данные из Германии в Ирландию" или "данные из листа 1 в лист 3".

любые идеи были бы весьма признательны.

если бы я мог использовать классы взаимодействия office, это было бы просто. К сожалению, я не могу поскольку классы взаимодействия не работают надежно в неинтерактивных средах, таких как службы windows и ASP.NET сайты, поэтому мне нужно было использовать OLEDB.

11 ответов


Не могу найти это в фактической документации MSDN, но модератор на форумах сказал

Я боюсь, что OLEDB не сохраняет порядок листов, как они были в Excel

имена листов Excel в порядке листов

кажется, что это было бы достаточно распространенным требованием, чтобы было приличное обходное решение.


можете ли вы не просто перебирать листы от 0 до количества имен -1? таким образом, вы должны получить их в правильном порядке.

редактировать

Я заметил через комментарии, что есть много проблем с использованием классов взаимодействия для извлечения имен листов. Поэтому вот пример использования OLEDB для их извлечения:

/// <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();
      }
   }
}

извлеченные из статьи на CodeProject.


поскольку приведенный выше код не охватывает процедуры извлечения списка имен листов для Excel 2007, следующий код будет применим как для Excel(97-2003), так и для 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;
}
Above возвращает список листов, в частности файл excel для обоих типов excel (97,2003,2007).

иначе:

файл xls (x) - это просто коллекция *.xml-файлы, хранящиеся в *.контейнер на молнии. распакуйте файл " app.xml " в папке 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>

файл является немецким файлом (Arbeitsblätter = worksheets). Имена таблиц (Tabelle3 и т. д.) находятся в правильном порядке. Вам просто нужно прочитать эти теги;)

в отношении


Я создал приведенную ниже функцию, используя информацию, предоставленную в ответе от @kraeppy (https://stackoverflow.com/a/19930386/2617732). Для этого требуется .net framework v4.5 для использования и требует Ссылки на систему.ИО.Сжатие. Это работает только для файлов xlsx, а не для старых файлов xls.

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

Это короткий, быстрый, безопасный и полезный...

public static List<string> ToExcelsSheetList(string exceladdress)
{
    List<string> sheets = new List<string>();
    using (OleDbConnection connection = new OleDbConnection((exceladdress.TrimEnd().ToLower().EndsWith("x")) ? "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" + exceladdress + "';" + "Extended Properties='Excel 12.0 Xml;HDR=YES;'"
        : "provider=Microsoft.Jet.OLEDB.4.0;Data Source='" + exceladdress + "';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;
}

мне нравится идея @deathApril назвать листы как 1_Germany, 2_UK, 3_IRELAND. Я также получил вашу проблему, чтобы сделать это переименование для сотен листов. Если у вас нет проблем с переименованием имени листа, вы можете использовать этот макрос для этого. Переименование всех имен листов займет меньше секунды. к сожалению, ODBC, OLEDB возвращает порядок имен листов asc. Этому нет замены. Вы должны использовать COM или переименовать свое имя, чтобы быть в порядок.

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

обновление: После прочтения комментария @SidHoland относительно Биффа мелькнула идея. Следующие шаги можно сделать с помощью кода. Не знаю, действительно ли вы хотите сделать это, чтобы получить имена листов в том же порядке. Дайте мне знать, если вам нужна помощь, чтобы сделать это через код.

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

обновление: Другое решение-NPOI может быть полезно здесь 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();

Это решение работает для xls. Я не пробовал файлы XLSX.

спасибо,

Эсен


это сработало для меня. Украдено отсюда: Как получить имя первой страницы книги 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;

согласно MSDN, в случае электронных таблиц внутри Excel это может не работать, потому что файлы Excel не являются реальными базами данных. Таким образом, вы не сможете получить имя листов в порядке их визуализации в книге.

код для получения имени листов в соответствии с их визуальным внешним видом с помощью interop:

добавить ссылку на библиотеку объектов Microsoft Excel 12.0.

следующий код даст имя листов в фактическом порядке, хранящемся в книге, а не в отсортированном имя.

Пример Кода:

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

Я не вижу никакой документации, в которой говорится о заказе в приложении.xml гарантированно будет порядком листов. Вероятно, это так, но не в соответствии со спецификацией OOXML.

книги.xml-файл, с другой стороны, включает атрибут sheetId, который определяет последовательность - от 1 до количества листов. Это согласно спецификации OOXML. рабочая тетрадь.xml описывается как место, где хранится последовательность листов.

Так что значение рабочая тетрадь.xml после его извлечения из формы XLSX будет моей рекомендацией. Не приложение.XML. Вместо docProps / app.xml, используйте xl / workbook.XML и посмотрите на элемент, как показано здесь -

`

<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:$O</definedName> 
  </definedNames>
  <calcPr calcId="145621" /> 
  </workbook>

`


попробуйте это. Вот код, чтобы получить имена листов в порядке.

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