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 » :
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 :
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 :
public
enum
Sexe {
FEMALE
(
2
), //
MALE
(
1
);
private
final
int
code;
...
}
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 :
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 :
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 :
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 :
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.
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.001
d);
}
}
@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 » :
<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 :
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 » :
@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 » :
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 » :
@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 :
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 :
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 :
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.
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 :
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
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
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 :
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 :
@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 :
@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 :
// 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 : 6 commentaires
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▲
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.001
d);
}
}
@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
(
));
}
}
}
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;
}
}
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
(
);
}
}
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
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;
}
}
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;
}
}