Использование Excel OleDb для получения имен листов В ЗАКАЗЕ ЛИСТА


103

Я использую OleDb для чтения из книги Excel с большим количеством листов.

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

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

Тогда мне нужен словарь

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

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

GERMANY, IRELAND, UK

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

Причина, по которой мне нужно его отсортировать, заключается в том, что я должен позволить пользователю выбирать диапазон данных по имени или индексу; они могут запросить «все данные из ГЕРМАНИИ в ИРЛАНДИЮ» или «данные с листа 1 на лист 3».

Любые идеи очень приветствуются.

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


Какую версию файла Excel вы читаете?
yamen

30
вау, как ты это нарисовал и как у тебя хватило терпения это нарисовать
l --'''''--------- '' '' '' '' '' ''

4
@ АртёмЦарионов - это ряды вертикальных полос (|) и подчеркиваний (_) для таблицы, а также обратной и прямой косой черты (\ /) для вкладок. Скопируйте его в текстовый редактор, и вы увидите.
Сид Холланд

Ответы:


17

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

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

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

Похоже, это было бы достаточно распространенным требованием, чтобы найти достойный обходной путь.


Однако это был прямой ответ, он сэкономил много времени на ненужных попытках.
Shihe Zhang

75

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

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

В комментариях я заметил, что есть много опасений по поводу использования классов Interop для получения имен листов. Поэтому вот пример использования 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.


Вот код, который я хотел бы увидеть! Как вы можете запросить «N-й лист» и количество листов?
Стив Купер,

13
Привет, Джеймс. Это в значительной степени моя первоначальная проблема - хотя метод GetOleDbSchemaTable () получает имена, номер строки не соответствует номеру листа книги. Таким образом, лист 4 был бы строкой 0, если бы он был первым в алфавите.
Стив Купер

23
Не отвечает на вопрос плаката (он хочет, чтобы это было в порядке появления в Excel)
Эндрю Уайт

7
@Samuel Я не думаю, что это решило проблему OP напрямую, однако, похоже, это помогло многим другим с аналогичной проблемой.
Джеймс

1
Не решает вопрос OP, который я искал. (Я всегда публикую причину отрицательного голоса.)
Фил Николас

23

Поскольку приведенный выше код не охватывает процедуры извлечения списка имен листов для 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;
}

Вышеупомянутая функция возвращает список листов в конкретном файле Excel для обоих типов Excel (97,2003,2007).


11
Этот код не возвращает листы в том порядке, в котором они отображаются в Excel
Эндрю Уайт,

11

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

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

Не работает "из коробки". exceladdress- что это?
Майкл Хаттер

8

По-другому:

Файл xls (x) - это просто набор файлов * .xml, хранящихся в контейнере * .zip. разархивируйте файл «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 = рабочие листы). Имена таблиц (Tabelle3 и т. Д.) Расположены в правильном порядке. Вам просто нужно прочитать эти теги;)

С уважением


1
Это хорошо работает для файлов xlsx, но не для файлов xls. У них разная структура. Вы знаете, как те же данные можно извлечь из файла xls?
rdans

6

Я создал приведенную ниже функцию, используя информацию, предоставленную в ответе от @kraeppy ( https://stackoverflow.com/a/19930386/2617732 ). Для этого требуется .net framework v4.5 и ссылка на System.IO.Compression. Это работает только для файлов 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);
        }
    }

2

Мне нравится идея @deathApril называть листы как 1_Germany, 2_UK, 3_IRELAND. У меня также возникла ваша проблема с переименованием сотен листов. Если у вас нет проблем с переименованием имени листа, вы можете использовать этот макрос, чтобы сделать это за вас. Переименование всех имен листов займет меньше секунды. к сожалению, ODBC, OLEDB возвращают порядок имен листов по возрастанию. Этому нет замены. Вы должны либо использовать 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 относительно BIFF возникла идея. Следующие шаги можно выполнить с помощью кода. Не знаю, действительно ли вы хотите сделать это, чтобы имена листов располагались в том же порядке. Дайте мне знать, если вам понадобится помощь в этом с помощью кода.

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 не пробовал.

Спасибо,

Esen


1
Вам не нужно переименовывать листы или использовать только COM, как показывает мой ответ, вы можете использовать DAO. Я думаю, что есть способ получить их, прочитав BIFF , но я все еще исследую этот вопрос.
Сид Холланд

1
@SidHolland: DAO - это компонент COM. Использование COM-компонента в Server 2008 является проблемой, поэтому Стив перешел на ADO.NET
Эсен

Мне не приходило в голову, что DAO - это компонент COM, несмотря на то, что для его использования пришлось добавить его как ссылку на COM. Спасибо за исправление. Ваше дополнение (переименование в zip и чтение XML) гениально. Я понятия не имел, что это сработает. Пока это единственный метод, который отображает листы по порядку без использования COM. +1!
Сид Холланд

1

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

2
Здравствуй. Рад, что у вас есть рабочий код, но в нем используются классы Interop, и они не работают надежно на сервере; вы не можете запустить этот код, скажем, в Windows Server 2008. Таким образом, вы не можете использовать его в веб-приложении или в коде на стороне сервера. Вот почему я выбрал oledb, а не Interop.
Стив Купер,

1

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

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

Ist nicht mal compilierfähig! (Zeile Excel.XlFileFormat.xlWorkbookNormal)
Майкл Хаттер

0

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

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

Добавить ссылку на библиотеку объектов 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);
}

0

Я не вижу никакой документации, в которой говорится, что порядок в app.xml гарантированно соответствует порядку листов. ВЕРОЯТНО, но не в соответствии со спецификацией OOXML.

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

Поэтому я рекомендую прочитать файл workbook.xml после его извлечения из XLSX. НЕ app.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$1:$O$59</definedName> 
  </definedNames>
  <calcPr calcId="145621" /> 
  </workbook>

`

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.