Tutoriel pour manipuler un fichier Excel 2010, avec Apache POI, en 5 minutes

Thierry-Leriche-Dessirier

Dans ce rapide tutoriel, nous allons voir comment utiliser la bibliothèque Apache POI pour lire, manipuler et écrire des fichiers Excel 2010 depuis un programme Java. Commentez ♪

- Titre : Tutoriel pour manipuler un fichier Excel 2010, avec Apache POI, en 5 minutes

- Auteur : Thierry-Leriche-Dessirier

- Parution : 28 août 2014

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 Thierry-Leriche-Dessirier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Avant de commencer

- I-B. Mise à jour

- II. Découverte du projet d'exemple

- II-A. Télécharger, installer et importer le projet d'exemple

- II-B. Les données à lire

- II-C. Les objets du modèle

- III. Action

- III-A. Écriture des tests

- III-B. Ajout d'Apache POI

- III-C. Lecture du fichier Excel 2010

- III-D. Modification et écriture d'un fichier

- III-E. Un peu de style

- IV. Conclusion

- V. Remerciements

- VI. Annexes

- VI-A. Liens

- VI-B. Liens personnels

- VI-C. Codes sources complets

I - Introduction

Suite à la lecture des tutoriels intitulés « Les fichiers CSV avec Java » et « Charger des données depuis un fichier CSV simple en 5 minutes », qui décrivent différentes façons de lire des fichiers au format CSV, vous avez été nombreux à me demander comment effectuer la même chose sur des fichiers Excel. Étant donnée la structure tabulaire des fichiers CSV et d'Excel, au moins dans sa représentation, il est en effet légitime de se poser la question. Ce document va donc tenter d'y répondre.

Pour cela, nous allons utiliser la bibliothèque Apache POI qui va faire l'essentiel du travail, notamment en décryptant le format Excel.

I-A - Avant de commencer

Pour écrire ce tutoriel, j'ai utilisé les éléments suivants :

- Java JDK 1.7.0_51 ;

- Eclipse Luna 4.4 ;

- Maven 3.0.5 ;

- JUnit 4.11 ;

- Log4j 1.2.17 ;

- POI 3.10.

I-B - Mise à jour

28/08/2014 : création du document

25/09/2014 : ajout d'un paragraphe sur l'évaluation des formules

II - Découverte du projet d'exemple

II-A - Télécharger, installer et importer le projet d'exemple

Durée estimée : 1 minute.

Pour commencer, je vous propose de télécharger le fichier Zip « article-poi-5-min-01.zip », contenant un projet Java-Maven d'exemple.

Compilez le projet d'exemple et importez-le dans Eclipse (comme expliqué dans le tutoriel « Importer un projet Maven dans Eclipse en 5 minutes »).

Pour suivre ce tutoriel, vous pouvez vous contenter de lire les codes proposés ci-dessous (codes complets en annexe).

II-B - Les données à lire

Ce tutoriel montre comment lire le fichier Excel 2010, nommé « chien-01.xlsx », que vous trouverez dans le dossier « src/test/resources ». Il contient une liste de chiens avec quelques attributs comme leurs noms, tailles, races, etc.

II-C - Les objets du modèle

Durée estimée : 30 secondes.

Comme la plupart de mes articles d'exemple parlent de chien, j'ai repris les objets du domaine que j'utilise habituellement. L'objet central est sans surprise l'interface « Chien » :

Chien.java

public interface Chien extends Serializable {

String getNom();

String getNomComplet();

Sexe getSexe();

RaceDeChien getRace();

List<String> getCouleurs();

Double getPoids();

}

En complément, je vous propose une implémentation « SimpleChien » qui n'a rien de particulier :

SimpleChien.java

public class SimpleChien implements Chien {

private String nom;
private String nomComplet;
private Sexe sexe;
private RaceDeChien race;
private List<String> couleurs;
private Double poids;

...

Les codes sources complets sont proposés en annexe.

Quant aux enum « Sexe » et « RaceDeChien », elles ne devraient pas nécessiter trop de questions :

Sexe.java

public enum Sexe {

FEMALE(2), //
MALE(1);

private final int code;

...
}

RaceDeChien.java

public enum RaceDeChien {
BASSET_ALPES("Basset des Alpes", "basset_alp"), //
BERGER_ALLEMAND("Berger allemand", "berger_all"), //
CANICHE("Caniche", "caniche"), //
HARRIER("Harrier", "harrier"), //
GOLDEN("Golden retriever", "golden_ret"), //
ROTTWEILER("Rottweiler", "rottweiler"), //
WESTIE("Westie", "westie");

private final String label;
private final String code;

...
}

Nous allons lire des données sur des chiens. Cela va se faire dans une classe de type DAO dont l'interface « ChienDao » (simplement reprise d'un autre tutoriel) définit le contrat :

ChienDao.java

public interface ChienDao {

List<Chien> findAllChiens();

}

Dans ce projet d'exemple, je ne vous pollue pas avec les différentes implémentations (cf. mes autres articles) pour lire les données depuis un fichier CSV, une base de données, un web service, etc. Je vous propose simplement l'implémentation « PoiChienDao » qui est vide pour l'instant :

PoiChienDao.java

public class PoiChienDao implements ChienDao {

@Override
public List<Chien> findAllChiens() {
throw new UnsupportedOperationException("not yet");
}

}

Avec tout cela, on a de quoi avancer tranquillement.

III - Action

III-A - Écriture des tests

Durée estimée : 1 minute.

Avant d'aller plus loin, nous allons écrire des tests unitaires. En gros nous allons faire du « TDD » (Test Driven Development) ou plus précisément du « 3T »(Tests en Trois Temps). Ceci sera fait dans la classe de test « PoiChienDaoTest », dont une version vide est déjà présente dans le projet d'exemple. Dans cet article, on ne va pas non plus chercher à être absolument exhaustif.

L'interface ChienDao ne fait que renvoyer la liste des chiens. On va donc se limiter à ça :

PoiChienDaoTest.java

public class PoiChienDaoTest {

private static final String fileName = "src/test/resources/chien-01.xlsx";

private ChienDao dao;

@Before
public void doBefore() {
dao = new PoiChienDao(fileName);
}

@Test
public void testFindAllchien() {

// Arrange
final int expectedSize = 5;

// Act
List<Chien> chiens = dao.findAllChiens();

// Assert
Assert.assertNotNull(chiens);
Assert.assertEquals(expectedSize, chiens.size());

}

...

Notez que j'ai ajouté un constructeur au DAO pour que ça compile :

PoiChienDao.java

public class PoiChienDao implements ChienDao {

private String fileName;

public PoiChienDao(final String fileName) {
super();
this.fileName = fileName;
}

...

On peut aussi tester les noms, les tailles, les races, etc.

PoiChienDaoTest.java

public class PoiChienDaoTest {

...

@Test
public void testOrdreDesChiens() {
log.debug("testFindAllchien");

// Arrange
final String[] expectedNoms = { "Milou", "Pluto", "Lassie", "Volt", "Medor" };

// Act
final List<Chien> chiens = dao.findAllChiens();

// Assert
for (int i = 0; i < expectedNoms.length; i++) {
Assert.assertEquals(expectedNoms[i], chiens.get(i).getNom());
}
}

@Test
public void testTaillesDesChiens() {
log.debug("testFindAllchien");

// Arrange
final double[] expectedPoids = { 12.5, 24, 32.3, 14, 32 };

// Act
final List<Chien> chiens = dao.findAllChiens();

// Assert
for (int i = 0; i < expectedPoids.length; i++) {
Assert.assertEquals(expectedPoids[i], chiens.get(i).getPoids().doubleValue(), 0.001d);
}
}

@Test
public void testRacesDesChiens() {
log.debug("testFindAllchien");

// Arrange
final RaceDeChien[] expectedRaces = { CANICHE, GOLDEN, BERGER_ALLEMAND, CANICHE, ROTTWEILER };

// Act
final List<Chien> chiens = dao.findAllChiens();

// Assert
for (int i = 0; i < expectedRaces.length; i++) {
Assert.assertEquals(expectedRaces[i], chiens.get(i).getRace());
}
}

Quand on lance les tests, on constate qu'ils sont tous rouges, ce qui est justement ce qu'on voulait. On peut maintenant passer à la suite.

III-B - Ajout d'Apache POI

Durée estimée : 30 secondes.

Pour profiter de la bibliothèque « Apache POI », vous devez ajouter des dépendances dans le fichier « pom.xml » :

pom.xml

<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.10-FINAL</version>
</dependency>

<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.10-FINAL</version>
</dependency>

Relancez ensuite une installation Maven, en sautant les tests :

Maven

mvn clean install eclipse:eclipse -Dmaven.test.skip

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Chiens 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
...
Downloading: http://.../public/org/apache/poi/poi-ooxml-schemas/3.10-FINAL/poi-ooxml-schemas-3.10-FINAL.jar
Downloaded: http://.../public/org/apache/poi/poi-ooxml-schemas/3.10-FINAL/poi-ooxml-schemas-3.10-FINAL.jar (4831 KB at 1223.0 KB/sec)
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.273s
[INFO] Finished at: Mon Aug 04 20:27:59 GMT+01:00 2014
[INFO] Final Memory: 12M/157M
[INFO] ------------------------------------------------------------------------

N'oubliez pas de faire un « refresh » (touche F5) dans Eclipse. Vous devriez constater que la bibliothèque « poi-3.10-Final.jar » est apparue dans le dossier « referenced Librairies ».

III-C - Lecture du fichier Excel 2010

Durée estimée : 3 minutes.

Pour commencer, il faut ouvrir le fichier. Pour la version 2010 d'Excel, cela se fait à l'aide de l'utilitaire « WorkbookFactory » :

PoiChienDao.java

@Override
public List<Chien> findAllChiens() {

final File file = new File(fileName);

try {
final Workbook workbook = WorkbookFactory.create(file);

} catch (InvalidFormatException | IOException e) {
e.printStackTrace();
}

...
}

Un fichier Excel est constitué de plusieurs feuilles qu'on peut sélectionner soit par leur nom, soit par leur position. Dans le fichier d'exemple, la feuille qui nous intéresse se nomme « Feuil1 » :

PoiChienDao.java

final Workbook workbook = WorkbookFactory.create(file);
final Sheet sheet = workbook.getSheet("Feuil1");

Personnellement, je préfère utiliser le nom, plutôt que la position. En effet, on n'est pas à l'abri qu'un autre utilisateur ait changé l'ordre des feuilles.

Pour la suite, il suffit de lire les lignes une par une. Les lignes commencent à l'index « 0 », mais, puisqu'on va sauter les titres, on va ici commencer à l'index « 1 » :

PoiChienDao.java

@Override
public List<Chien> findAllChiens() {

final File file = new File(fileName);

final List<Chien> chiens = new ArrayList<Chien>();

try {
final Workbook workbook = WorkbookFactory.create(file);
final Sheet sheet = workbook.getSheet("Feuil1");

int index = 1;
Row row = sheet.getRow(index++);

while (row != null) {

final Chien chien = rowToChien(row);
chiens.add(chien);

row = sheet.getRow(index++);
}

} catch (InvalidFormatException | IOException e) {
e.printStackTrace();
}

return chiens;
}

private static Chien rowToChien(final Row row) {
...
}

À ce stade, on peut déjà relancer les tests, ne serait-ce que pour vérifier qu'on trouve le bon nombre de chiens.

Il ne reste plus qu'à écrire la méthode qui prend une ligne pour la convertir en chien. La bibliothèque permet de lire directement le bon type dans les cellules :

PoiChienDao.java

private static Chien rowToChien(final Row row) {
final SimpleChien chien = new SimpleChien();

final String nom = row.getCell(0).getStringCellValue();
chien.setNom(nom);

final String nomComplet = row.getCell(1).getStringCellValue();
chien.setNomComplet(nomComplet);

Pour le sexe, qui est représenté par la valeur « 1 » ou « 2 », il faut utiliser la méthode « getNumericCellValue » en faisant attention au fait qu'elle renvoie un double qu'il faudra donc convertir :

PoiChienDao.java

private static Chien rowToChien(final Row row) {
final SimpleChien chien = new SimpleChien();

final String nom = row.getCell(0).getStringCellValue();
chien.setNom(nom);

final String nomComplet = row.getCell(1).getStringCellValue();
chien.setNomComplet(nomComplet);

final int codeSexe = (int) row.getCell(2).getNumericCellValue();
final Sexe sexe = Sexe.valueOfByCode(codeSexe);
chien.setSexe(sexe);

final String codeRace = row.getCell(3).getStringCellValue();
final RaceDeChien race = RaceDeChien.valueOfByCode(codeRace);
chien.setRace(race);

final String couleurs = row.getCell(4).getStringCellValue();
chien.setCouleurs(stringToList(couleurs));

final double poids = row.getCell(5).getNumericCellValue();
chien.setPoids(poids);

return chien;
}

Quand on relance les tests, tout est vert. On est donc légitimement en droit de penser qu'on a fini…

Le code du projet à cette étape est disponible dans le fichier « article-poi-5-min-02.zip ».

À ce stade, il est intéressant d'insister sur le fait que POI sait déterminer le type de donnée d'une cellule :

Type de donnée

Row row = sheet.getRow(index);
final Cell cell = row.getCell(i);
final int cellType = cell.getCellType();

switch (cellType) {

case Cell.CELL_TYPE_BLANK:
...
break;

case Cell.CELL_TYPE_ERROR:
final byte error = cell.getErrorCellValue();
...
break;

case Cell.CELL_TYPE_STRING:
final String stringValue = cell.getStringCellValue();
...
break;

case Cell.CELL_TYPE_NUMERIC:
final double numericValue = cell.getNumericCellValue();
...
break;

case Cell.CELL_TYPE_BOOLEAN:
final boolean booleanValue = cell.getBooleanCellValue();
...
break;

case Cell.CELL_TYPE_FORMULA:
final String formula = cell.getCellFormula();
...
break;
}

Vous remarquez qu'on peut lire une cellule de type formule. La valeur lue sera la formule et non le résultat de la formule. Si c'est le résultat qui nous intéresse, il faut évaluer la formule comme suit.

Evaluation de la formule

FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
...

CellValue cellValue = evaluator.evaluate(cell);

int age = (int) cellValue.getNumberValue();

III-D - Modification et écriture d'un fichier

Durée estimée : 2 minutes.

Maintenant qu'on sait lire un fichier, on a envie d'aller plus loin. Dans la suite, je vais partir du fichier qu'on vient de lire, je vais ajouter une colonne intitulée « Prix » et j'enregistrerai le fichier sous un autre nom.

Reprenons la lecture pour commencer. On peut la résumer au code suivant :

Test ajout du prix

private static final String fileName1 = "src/test/resources/chien-01.xlsx";

@Test
public void testAjoutPrix() throws Exception {

final File file1 = new File(fileName1);

// Lecture fichier original
final Workbook workbook = WorkbookFactory.create(file1);
final Sheet sheet = workbook.getSheet("Feuil1");

final Row titreRow = sheet.getRow(0);

int index = 1;
Row row = sheet.getRow(index++);

while (row != null) {
...

row = sheet.getRow(index++);
}
}

Dans un premier temps, je vais ajouter une colonne (juste une cellule en fait) avec le titre « Prix » :

Test ajout du prix

@Test
public void testAjoutPrix() throws Exception {
...

// Ajout d'une cellule
final Row titreRow = sheet.getRow(0);
final Cell prixTitreCell = titreRow.createCell(6);
prixTitreCell.setCellValue("Prix");

Je veux maintenant remplir les valeurs de cette colonne pour chaque ligne. Dans une animalerie classique, le prix d'un chien dépend généralement de sa race. On pourra donc écrire un code ressemblant au suivant :

Test ajout du prix

@Test
public void testAjoutPrix() throws Exception {
...

// Modifications
int index = 1;
Row row = sheet.getRow(index++);

while (row != null) {
final String codeRace = row.getCell(3).getStringCellValue();
final RaceDeChien race = RaceDeChien.valueOfByCode(codeRace);

double prix = 0;
switch (race) {
case CANICHE:
prix = 99.00;
break;

case BASSET_ALPES:
case HARRIER:
prix = 150.00;
break;

case GOLDEN:
prix = 899.99;
break;

default:
prix = 450.00;
break;
}
final Cell prixCell = row.createCell(6);
prixCell.setCellValue(prix);

row = sheet.getRow(index++);
}

Oui, c'est normal que les caniches soient moins chers que les autres, car ils sont plus petits ;-)

Il ne reste plus qu'à enregistrer tout ça :

Test ajout du prix

private static final String fileName1 = "src/test/resources/chien-01.xlsx";
private static final String fileName2 = "src/test/resources/chien-02.xlsx";

@Test
public void testAjoutPrix() throws Exception {
...

// Ecriture dans un autre fichier
final File file2 = new File(fileName2);

final FileOutputStream fos = new FileOutputStream(file2);

workbook.write(fos);
fos.close();
}

III-E - Un peu de style

Durée estimée : 2 minutes.

Pour le moment, le fichier n'est pas très joli. On va donc lui appliquer un peu de style. On commence avec la cellule du titre « Prix » à laquelle on va appliquer le même style que pour les autres titres :

Style du titre

@Test
public void testAjoutPrix() throws Exception {
...

// Ajout d'une cellule
final Row titreRow = sheet.getRow(0);
final Cell prixTitreCell = titreRow.createCell(6);
prixTitreCell.setCellValue("Prix");

final Cell nomCell = titreRow.getCell(0);
final CellStyle nomCellStyle = nomCell.getCellStyle();
prixTitreCell.setCellStyle(nomCellStyle);

Il n'y a pas beaucoup de chiens dans l'exemple. S'il y en avait plus, il serait difficile de bien distinguer les lignes. On va donc en mettre une sur deux en couleur :

Une ligne sur deux en couleur

@Test
public void testAjoutPrix() throws Exception {
...

// Style
final CellStyle pairStyle = workbook.createCellStyle();
pairStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
pairStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);

...

while (row != null) {
...

// Coloriage d'une ligne sur deux
if (index % 2 == 0) {
for (int i = 0; i < 7; i++) {
row.getCell(i).setCellStyle(pairStyle);
}
}
}

Ici, je n'ai colorié que les cellules qui m'intéressaient. On peut aussi colorier la ligne entière, mais je trouve ça moins joli…

J'utilise la méthode « setFillForegroundColor() » à ne pas confondre avec « setFillBackgroundColor() ». La Doc de POI indique qu'on doit définir le « foreground » avant le « background ».

Le code du projet à cette étape est disponible dans le fichier « article-poi-5-min-03.zip ».

Avant de conclure, je voudrais attirer votre attention sur le fait que POI gère les cellules vides, ou inexistantes, d'une façon déconcertante. Lorsqu'on ouvre une feuille de calcul dans Excel, on a l'impression qu'il y a des cellules à l'infini horizontalement comme verticalement. Mais ce n'est pas comme ça que POI fonctionne. Si on essaie d'atteindre une cellule vide, pour la colorier par exemple, ça ne marchera pas. Pour contrer ce problème, il faut passer un second paramètre au getter pour indiquer le comportement à adopter en cas d'une cellule vide ou inexistante :

Cellule vide

// row.getCell(i).setCellStyle(pairStyle);

row.getCell(i, Row.CREATE_NULL_AS_BLANK).setCellStyle(pairStyle);

IV - Conclusion

Allez, on a déjà bien dépassé le contrat des « 5 minutes ». On va s'arrêter là car c'est largement suffisant pour comprendre comment fonctionne la bibliothèque Apache POI. Vous avez pu constater que c'est relativement simple.

Bien entendu, les fonctionnalités que nous avons découvertes sont relativement simples et POI sait faire bien plus que cela. Nous avons néanmoins vu celles qui me semblent être les plus importantes et que vous utiliserez à coup sûr dans vos projets. Vous avez toutes les cartes en main.

Voici tout de même quelques pistes si vous décidez d'approfondir la découverte d'Apache POI. La bibliothèque permet de :

- manipuler les formules, la mise en page, mises en forme conditionnelles, etc. ;

- utiliser des API à la SAX/StAX (eventmodel / streaming) ;

- travailler avec l'ancien format Excel (.xls) ;

- avoir une API générique qui abstrait en partie le format utilisé (.xls ou .xlsx) ;

- manipuler d'autres types de documents Office (Word, PowerPoint, Publisher, Outlook, Visio).

En outre, si vous prévoyez de créer de nombreux fichiers Excel, par exemple pour générer des factures, des bons de commande ou encore des documents administratifs, je vous conseille d'utiliser des modèles (templates), c'est-à-dire des documents Excel qui contiennent déjà toute la mise en forme, les titres, les couleurs, les formules, etc.

Vos retours nous aident à améliorer nos publications. N'hésitez donc pas à commenter cet article sur le forum : Commentez

V - Remerciements

D'abord j'adresse mes remerciements à l'équipe POI, chez Apache, pour avoir développé une bibliothèque aussi utile et pour la maintenir. Je n'oublie pas tous les contributeurs qui participent notamment sur le forum.

Plus spécifiquement en ce qui concerne cet article, je tiens à remercier l'équipe de Developpez.com et plus particulièrement à Séb Piller, tchize_, OButterlin, Logan, Alain Bernard, Mickael Baron et Cédric Duprez.

VI - Annexes

VI-A - Liens

Apache POI : http://poi.apache.org

VI-B - Liens personnels

Retrouvez ma page et mes autres articles sur Developpez.com à l'adresse https://thierry-leriche-dessirier.developpez.com/#page_articles

Suivez-moi sur Twitter : @thierryleriche (https://twitter.com/thierryleriche)

Et sur mon site ICAUDA : http://www.icauda.com

VI-C - Codes sources complets

PoiChienDaoTest.java

package com.icauda.article.poi.chien.dao;

import static com.icauda.article.poi.chien.domain.RaceDeChien.BERGER_ALLEMAND;
import static com.icauda.article.poi.chien.domain.RaceDeChien.CANICHE;
import static com.icauda.article.poi.chien.domain.RaceDeChien.GOLDEN;
import static com.icauda.article.poi.chien.domain.RaceDeChien.ROTTWEILER;

import java.util.List;

import org.apache.log4j.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.icauda.article.poi.chien.domain.Chien;
import com.icauda.article.poi.chien.domain.RaceDeChien;

public class PoiChienDaoTest {

private static final Logger log = Logger.getLogger(PoiChienDaoTest.class);

private static final String fileName = "src/test/resources/chien-01.xlsx";

private ChienDao dao;

@Before
public void doBefore() {
dao = new PoiChienDao(fileName);
}

@Test
public void testFindAllchien() {

log.debug("testFindAllchien");

// Arrange
final int expectedSize = 5;

// Act
final List<Chien> chiens = dao.findAllChiens();

// Assert
Assert.assertNotNull(chiens);
Assert.assertEquals(expectedSize, chiens.size());

}

@Test
public void testOrdreDesChiens() {
log.debug("testFindAllchien");

// Arrange
final String[] expectedNoms = { "Milou", "Pluto", "Lassie", "Volt", "Medor" };

// Act
final List<Chien> chiens = dao.findAllChiens();

// Assert
for (int i = 0; i < expectedNoms.length; i++) {
Assert.assertEquals(expectedNoms[i], chiens.get(i).getNom());
}
}

@Test
public void testTaillesDesChiens() {
log.debug("testFindAllchien");

// Arrange
final double[] expectedPoids = { 12.5, 24, 32.3, 14, 32 };

// Act
final List<Chien> chiens = dao.findAllChiens();

// Assert
for (int i = 0; i < expectedPoids.length; i++) {
Assert.assertEquals(expectedPoids[i], chiens.get(i).getPoids().doubleValue(), 0.001d);
}
}

@Test
public void testRacesDesChiens() {
log.debug("testFindAllchien");

// Arrange
final RaceDeChien[] expectedRaces = { CANICHE, GOLDEN, BERGER_ALLEMAND, CANICHE, ROTTWEILER };

// Act
final List<Chien> chiens = dao.findAllChiens();

// Assert
for (int i = 0; i < expectedRaces.length; i++) {
Assert.assertEquals(expectedRaces[i], chiens.get(i).getRace());
}
}

}

PoiChienDao.java

package com.icauda.article.poi.chien.dao;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

import com.icauda.article.poi.chien.domain.Chien;
import com.icauda.article.poi.chien.domain.RaceDeChien;
import com.icauda.article.poi.chien.domain.Sexe;
import com.icauda.article.poi.chien.domain.SimpleChien;

public class PoiChienDao implements ChienDao {

private static final Logger log = Logger.getLogger(PoiChienDao.class);

private String fileName;

public PoiChienDao(final String fileName) {
super();
this.fileName = fileName;
}

@Override
public List<Chien> findAllChiens() {

final File file = new File(fileName);

final List<Chien> chiens = new ArrayList<Chien>();

try {
final Workbook workbook = WorkbookFactory.create(file);
final Sheet sheet = workbook.getSheet("Feuil1");

int index = 1;
Row row = sheet.getRow(index++);

while (row != null) {

final Chien chien = rowToChien(row);
chiens.add(chien);

row = sheet.getRow(index++);
}

} catch (InvalidFormatException | IOException e) {
log.error(e.getMessage(), e);
// e.printStackTrace();
}

return chiens;
}

private static Chien rowToChien(final Row row) {
final SimpleChien chien = new SimpleChien();

final String nom = row.getCell(0).getStringCellValue();
chien.setNom(nom);

final String nomComplet = row.getCell(1).getStringCellValue();
chien.setNomComplet(nomComplet);

final int codeSexe = (int) row.getCell(2).getNumericCellValue();
final Sexe sexe = Sexe.valueOfByCode(codeSexe);
chien.setSexe(sexe);

final String codeRace = row.getCell(3).getStringCellValue();
final RaceDeChien race = RaceDeChien.valueOfByCode(codeRace);
chien.setRace(race);

final String couleurs = row.getCell(4).getStringCellValue();
chien.setCouleurs(stringToList(couleurs));

final double poids = row.getCell(5).getNumericCellValue();
chien.setPoids(poids);

return chien;
}

private static List<String> stringToList(final String s) {
final List<String> couleurs = new ArrayList<String>();
// TODO ... Utilise le Splitter de Guava...
return couleurs;
}

}

PoiChienDao2Test.java

package com.icauda.article.poi.chien.dao;

import java.io.File;
import java.io.FileOutputStream;

import org.apache.log4j.Logger;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.junit.Before;
import org.junit.Test;

import com.icauda.article.poi.chien.domain.RaceDeChien;

public class PoiChienDao2Test {

private static final Logger log = Logger.getLogger(PoiChienDaoTest.class);

private static final String fileName1 = "src/test/resources/chien-01.xlsx";
private static final String fileName2 = "src/test/resources/chien-02.xlsx";

@Before
public void doBefore() {
}

@Test
public void testAjoutPrix() throws Exception {

log.debug("testAjoutPrix");

final File file1 = new File(fileName1);

// Lecture fichier original
final Workbook workbook = WorkbookFactory.create(file1);
final Sheet sheet = workbook.getSheet("Feuil1");

// Ajout d'une cellule
final Row titreRow = sheet.getRow(0);
final Cell prixTitreCell = titreRow.createCell(6);
prixTitreCell.setCellValue("Prix");

final Cell nomCell = titreRow.getCell(0);
final CellStyle nomCellStyle = nomCell.getCellStyle();
prixTitreCell.setCellStyle(nomCellStyle);

// Style
final CellStyle pairStyle = workbook.createCellStyle();
pairStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
pairStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);

// Modifications
int index = 1;
Row row = sheet.getRow(index++);

while (row != null) {
final String codeRace = row.getCell(3).getStringCellValue();
final RaceDeChien race = RaceDeChien.valueOfByCode(codeRace);

double prix = 0;
switch (race) {
case CANICHE:
prix = 99.00;
break;

case BASSET_ALPES:
case HARRIER:
prix = 150.00;
break;

case GOLDEN:
prix = 899.99;
break;

default:
prix = 450.00;
break;
}
final Cell prixCell = row.createCell(6);
prixCell.setCellValue(prix);

// Coloriage d'une ligne sur deux
if (index % 2 == 0) {
for (int i = 0; i < 7; i++) {
row.getCell(i).setCellStyle(pairStyle);
}
}

row = sheet.getRow(index++);
}

// Écriture dans un autre fichier
final File file2 = new File(fileName2);

// Comme je lance le test plusieurs fois, je pars d'une copie blanche a chaque fois.
if (file2.exists()) {
file2.delete();
}

final FileOutputStream fos = new FileOutputStream(file2);

workbook.write(fos);
fos.close();
}

}

SimpleChien.java

public class SimpleChien implements Chien {

private String nom;
private String nomComplet;
private Sexe sexe;
private RaceDeChien race;
private List<String> couleurs;
private Double poids;

public SimpleChien() {
// rien...
}

public SimpleChien(final String nom) {
this.nom = nom;
}

public SimpleChien(final String nom, final RaceDeChien race, final Sexe sexe) {
this(nom);
this.race = race;
this.sexe = sexe;
}

@Override
public String toString() {
return "SimpleChien [nom=" + nom + "]";
}

// Getters et setters

Sexe.java

public enum Sexe {

FEMALE(2), //
MALE(1);

private final int code;

Sexe(final int code) {
this.code = code;
}

public static Sexe valueOfByCode(final int code) {
switch (code) {
case 2:
return FEMALE;
case 1:
return MALE;
default:
throw new IllegalArgumentException("Le sexe demande n'existe pas.");
}
}

public boolean isMale() {
return this == MALE;
}

public int getCode() {
return code;
}
}

RaceDeChien.java

public enum RaceDeChien {
BASSET_ALPES("Basset des Alpes", "basset_alp"), //
BERGER_ALLEMAND("Berger allemand", "berger_all"), //
CANICHE("Caniche", "caniche"), //
HARRIER("Harrier", "harrier"), //
GOLDEN("Golden retriever", "golden_ret"), //
ROTTWEILER("Rottweiler", "rottweiler"), //
WESTIE("Westie", "westie");

private final String label;
private final String code;

RaceDeChien(final String label, final String code) {
this.label = label;
this.code = code;
}

public static RaceDeChien valueOfByCode(final String code) {

if (code == null || code.isEmpty()) {
throw new IllegalArgumentException("Le code ne peut pas etre vide.");
}

for (RaceDeChien race : values()) {
if (race.code.equalsIgnoreCase(code)) {
return race;
}
}

throw new IllegalArgumentException("La race de chien demandee n'existe pas.");
}

public String getLabel() {
return label;
}

public String getCode() {
return code;
}
}