В ходе работы над различными программными продуктами часто возникает необходимость импорта и экспорта данных из различных "закрытых" форматов файлов. Чаще всего эта необходимость возникает применительно к файлам в форматах офисных продуктов корпорации Microsoft, в частности Word (doc, docx) и Excel (xls, xlsx). В силу особенностей реализации этих форматов, реализация такой обработки "в лоб" целиком своими силами была бы весьма нетривиальной и достаточно трудоёмкой задачей. К счастью, основная часть работы уже сделана за нас - существуют открытые Java-библиотеки, позволяющие преобразовать эти файлы в объектную модель Java, после чего получение из них необходимой нам информации не составит особого труда. В этой заметке показан пример реализации такой выборки данных из документа .xls с помощью Apache POI (на примере версии 3.6) - популярной библиотеки от Apache Software Foundation.
Из чего состоит документ Excel
Для начала, определимся с терминологией. Документ Excel (workbook) состоит из одного или более листов (или вкладок, в оригинале - sheets). Каждый лист представляет из себя матрицу из m строк (rows) по n ячеек (cells) в каждой. Максимальные размерности m и n различаются для разных версий Excel. В API POI каждому из структурных элементов файла Excel соответствует свой базовый интерфейс и его реализации для документов .xls и .xls, а работа с файловой системой осуществляется посредством класса org.apache.poi.poifs.filesystem.POIFSFileSystem
.
Обратите внимание, что для страницы в документе Excel 2007 (.xlsx) существует сразу 3 реализации - помимо "общего" класса XSSFSheet добавлены XSSDialogSheet (страница-диалог) и XSSFChartSheet (страница, содержащая только график).
Алгоритм обработки
Таким образом, в общем случае, обработка документа Excel сводится к последовательному перебору ячеек и строк на каждой его странице. Значение каждой ячейки может быть преобразовано к нужному нам типу (строка, число, дата и т.д.) посредством методов, описанных в интерфейсе Cell и его реализациях.
Если тип содержимого ячейки не совпадает с тем типом данных, к которому мы пытаемся его преобразовать, будет сгенерировано исключение. Какое именно - зависит от конкретных типов данных между которыми мы пытаемся осуществить преобразование. К примеру, при попытке получения числового значения из строковой ячейки, будет сгенерирован java.lang.IllegalStateException
. Для того, чтобы избежать исключительной ситуации, можно заранее проверить, к какому типу относится содержимое конкретной ячейки - для этой цели существует метод getCellType(), объявленный всё в том же интерфейсе Cell.
Пример разбора файла
Теперь перейдём от теории к практике - рассмотрим на примере, как может выглядеть код простейшей программы на Java, получающей данные из документа xls посредством API Apache POI. Предположим, что мы хотим выбрать данные из некоего справочника контактной информации, который выглядит примерно так:

Таким образом, наш документ состоит из трёх столбцов - ФИО, Адрес и Телефон. Чтобы упростить задачу, создадим класс с теми же самыми полями, который будет хранить информацию о контактных лицах:
package com.tuneit.poi.sample;
/*
* Класс, предназначенный для хранения
* перснональной информации контактного лица
*/
public class ContactPerson {
private String name; //ФИО
private String address; //Адрес
private String phoneNumber; //Номер телефона
//Getters & setters
...
}
Это простейший POJO-класс, содержащий только поля (соответствующие столбцам нашего xls-документа) и get- и set-методы к ним (в листинге не приведены).
Теперь рассмотрим основной фрагмент кода - собственно, сам парсер содержимого документа:
public static final NAME_COLUMN_NUMBER = 0; //ФИО
public static final ADDRESS_COLUMN_NUMBER = 1; //Адрес
public static final PHONE_COLUMN_NUMBER = 2; //Телефон
...
public List<Person> getContacts(String path){
List<Person> contacts = new ArrayList<Person>(); //Создаём пустой список контактов
File addressDB = new File(path); //Переменная path содержит путь к документу в ФС
POIFSFileSystem fileSystem = new POIFSFileSystem(addressDB); //Открываем документ
HSSFWorkbook workBook = new HSSFWorkbook(fileSystem); // Получаем workbook
HSSFSheet sheet = workBook.getSheetAt(0); // Проверяем только первую страницу
Iterator<Row> rows = sheet.rowIterator(); // Перебираем все строки
// Пропускаем "шапку" таблицы
if (rows.hasNext()) {
rows.next();
}
// Перебираем все строки начиная со второй до тех пор, пока документ не закончится
while (rows.hasNext()) {
HSSFRow row = (HSSFRow) rows.next();
//Получаем ячейки из строки по номерам столбцов
HSSFCell nameCell = row.getCell(NAME_COLUMN_NUMBER); //ФИО
HSSFCell addressCell = row.getCell(ADDRESS_COLUMN_NUMBER); //Адрес
HSSFCell phoneCell = row.getCell(PHONE_COLUMN_NUMBER); //Номер телефона
// Если в первом столбце нет данных, то контакт не создаём
if (nameCell != null) {
Person person = new Person();
person.setName(nameCell.getStringCellValue()); //Получаем строковое значение из ячейки
person.setAddress(""); //Адрес может не быть задан
if (addressCell != null && !"".equals(addressCell.getStringCellValue())) {
person.setAddress(addressCell.getStringCellValue()); //Адрес - строка
}
person.setPhone(""); //Телефон тоже может не быть задан
if (phoneCell != null && !"".equals(phoneCell.getStringCellValue())) {
person.setPhoneNumber(phoneCell.getStringCellValue()); // Телефон - тоже строка
}
contacts.add(person); //Добавляем контакт в список
}
}
return contacts;
}
Таким образом, на выходе приведённого выше метода мы получим коллекцию объектов класса Person, сформированных из данных исходного xls-файла. Разумеется, в случае решения более общей задачи - например, написания универсального обработчика xls-документов, логика программы усложнится, но её основа, скорее всего, останется прежней - всё тот же последовательный перебор всех строк таблицы.