OpenXML et Java - partie 2 : Les documents Open XML
Paru le 13 septembre 2006
Par Julien Chable
Sur cette page
Les documents
Les propriétés du document
Changer les propriétés du document
Changer les propriétés du document
Changement des propriétés
L’image d’aperçu
Création basique d’un document Word
Transformer un document en HTML
Conclusion
Téléchargements :le code source associé
Après avoir présenté la structure d’un document Open XML dans la première partie de cet article, nous allons maintenant aborder la manipulation de documents, notamment ceux de type Word. Le code source associé à cet article propose un framework rudimentaire, mais plus évolué que celui de la première partie, sur lequel nous nous appuierons dans les exemples.
Prérequis pour réaliser et exécuter les exemples de cet article : avoir installé le JDK 5 de Java SE et l’IDE Eclipse 3.1 au minimum, avoir des connaissances en Java.
Les documents
Chaque type de document (Word, Excel et PowerPoint) contient une partie principale qui représente le contenu du document. Selon le type de document, le nom de ce fichier, ainsi que le type de contenu MIME et l’URI varient, néanmoins le type de la relation de cette partie est toujours le même : http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument. Il convient donc de toujours rechercher le contenu principal d’un document Open XML à l’aide du type de relation.
L’exemple suivant vous permet de récupérer la partie du contenu principal de votre document, cela quel que soit le type :
public final static String NS_CORE_DOCUMENT =
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
...
final String APP_ROOT = System.getProperty("user.dir") + File.separator;
ZipFile zipFile = null;
try {
zipFile = new ZipFile(APP_ROOT + "sample.docx");
} catch (IOException e) {
e.printStackTrace();
}
Package p = Package.open(zipFile, PackageAccess.Read);
// Obtention de la relation de la partie du contenu du document
PackageRelationship coreDocRelationship =
p.getRelationshipsByType(PackageRelationshipConstants. NS_CORE_DOCUMENT).getRelationship(0);
// Obtention de la partie du contenu du document à partir de cette
// relation
PackagePart coreDocument = p.getPart(coreDocRelationship);
System.out.println(coreDocument.getUri() + " -> "
+ coreDocument.getContentType());
L’affichage de la sortie du listing 1 pour plusieurs types de document :
- Word :
word/document.xml -> application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml - Excel :
xl/workbook.xml -> application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml - PowerPoint :
ppt/presentation.xml -> application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml
Pour résumer, voici les extensions et l’URI de la partie principale du contenu de différents types de documents :
- WordProcessingML (.docx) : word/document.xml
- SpreadsheetML (.xlsx) : xl/workbook.xml
- PresentationML (.pptx) : ppt/presentation.xml
Haut de page
Les propriétés du document
Les documents Open XML peuvent faire référence à une partie contenant les propriétés – appelées les métadonnées - du document : titre, sujet, nom de l’auteur, date de création, date de dernière modification, langue, … Un ensemble d’informations très utiles aussi bien pour les utilisateurs que pour les développeurs.
.jpg)
Pour obtenir la partie contenant les propriétés du document, seules quelques lignes de code suffisent :
public final static String NS_CORE_PROPERTIES =
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
...
Package p = Package.open(zipFile, PackageAccess.Read);
// Obtention de la relation de la partie des propriétés du document
PackageRelationship corePropertiesRelationship =
p.getRelationshipsByType(PackageRelationshipConstants.NS_CORE_PROPERTIES).getRelationship(0);
// Obtention de la partie à partir de la relation
PackagePart coreDocument = p.getPart(corePropertiesRelationship);
System.out.println(coreDocument.getUri() + " -> "
+ coreDocument.getContentType());
La console affiche :
docProps/core.xml -> application/vnd.openxmlformats-package.core-properties+xml
Voici un extrait du fichier de propriétés du document sample.docx contenu avec les sources de l’article :
.gif)
Propritétés
L’utilisation de la classe OpenXMLDocument rend l’accès aux propriétés relativement simple :
...
OpenXMLDocument docx = new OpenXMLDocument(Package.open(zipFile,
PackageAccess.Read));
System.out.println(docx.getCoreProperties().getCreator());
System.out.println(docx.getCoreProperties().getTitle());
System.out.println(docx.getCoreProperties().getSubject());
La sortie:
Julien CHABLE
Lorem Ipsum
Sample document
Haut de page
Changer les propriétés du document
Changer les propriétés d’un document est aussi simple que de les extraire, voici quelques lignes de code vous permettant de le faire :
// Le fichier de destination
File destFile = new File(APP_ROOT + "sample_out.docx");
// Ouverture du document
Package pack = Package.open(zipFile, PackageAccess.ReadWrite);
OpenXMLDocument docx = new OpenXMLDocument(pack);
CoreProperties coreProps = docx.getCoreProperties();
coreProps.setCreator("MSDN powa !");
coreProps.setDescription("Une nouvelle description");
coreProps.setTitle("SampleListing4");
// Sauvegarde du document
docx.save(destFile);
Pour s’assurer de ce changement, vous pouvez utiliser le code du listing 3 permettant d’afficher les propriétés, ou alors consulter directement le fichier core.xml après avoir décompressé le document. Ci-dessous un aperçu du fichier modifié core.xml :
Haut de page
Changement des propriétés
Les propriétés étendues
Un document Open XML peut aussi posséder des informations supplémentaires, en plus de celles déjà contenues dans les propriétés du document. Par exemple, il peut être intéressant de connaître le nom et la version de l’application qui a généré le fichier, ou tout simplement, les statistiques du document : nombre de mots ou de paragraphes contenus dans le document, nombre de caractères, ...
Voici l’extrait d’un fichier de propriétés étendues :
.gif)
Propriétés étendues
Le framework rudimentaire associé à cet article n’offre ni classe ni méthode d’aucune sorte pour accéder à ces informations. Par conséquent, nous allons nous retrousser les manches et utiliser directement l’API DOM pour extraire les informations à partir de la partie des propriétés étendues :
...
// Ouverture du package
Package p = Package.open(..., PackageAccess.Read);
// Obtention de la relation de la partie à partir de son type
PackageRelationship extendedPropertiesRelationship =
p.getRelationshipsByType(PackageRelationshipConstants.NS_EXTENDED_PROPERTIES).getRelationship(0);
// Obtention de la partie à partir de cette relation
PackagePart extPropsPart = p.getPart(extendedPropertiesRelationship);
System.out.println(extPropsPart.getUri() + " -> "
+ extPropsPart.getContentType());//
Extraction du contenu
try {
InputStream inStream = extPropsPart.getInputStream();
// Création du parser DOM
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setIgnoringElementContentWhitespace(true);
DocumentBuilder documentBuilder;
documentBuilder = documentBuilderFactory.newDocumentBuilder();
// On parse le document XML en arbre DOM
Document extPropsDoc = documentBuilder.parse(inStream);
// Extraction du nom et de la version de l'application qui a généré
// ce fichier Open XML
System.out.println("Document généré avec "
+ extPropsDoc.getElementsByTagName("Application").item(0)
.getTextContent()
+ " vers. "
+ extPropsDoc.getElementsByTagName("AppVersion").item(0)
.getTextContent());
// Extraction des statistiques du document
System.out.println("Le document contient "+ extPropsDoc.getElementsByTagName("Words").item(0)
.getTextContent() + " mots composés de "+ extPropsDoc.getElementsByTagName("Characters").item(0)
.getTextContent() + " caractères répartis sur " + extPropsDoc.getElementsByTagName("Lines").item(0)
.getTextContent() + " lignes");
inStream.close();
} catch (Exception ioe) {
System.err
.println("Echec de l'extraction des propriétés étendues du document ! :(");
}
L’exécution de ce code produit l’affichage suivant :
docProps/app.xml -> application/vnd.openxmlformats-officedocument.extended-properties+xml
Document généré avec Microsoft Office Word vers. 12.0000
Le document contient 230 mots composés de 1476 caractères répartis sur 36 lignes
Haut de page
L’image d’aperçu
Certains documents Open XML, c’est le cas des documents PowerPoint 2007, possèdent une image d’aperçu du document. Cette partie possède un type de relation spéciale : http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail.
Le code suivant exploite deux méthodes – getThumbnails() et extractParts() - de la classe OpenXMLDocument pour extraire les fichiers d’aperçu du document et les placer dans un répertoire nommé ‘export’ :
final String APP_ROOT = System.getProperty("user.dir") + File.separator;
ZipFile zipFile = null;
// Le fichier source
try {
zipFile = new ZipFile(APP_ROOT + "sample.pptx");
} catch (IOException e) {
...
}
// Le répertoire de destination
File destFile = new File(APP_ROOT + "export");
// Ouverture du package
OpenXMLDocument docx = OpenXMLDocument.open(zipFile, PackageAccess.Read);
// Extraction des aperçus
docx.extractParts(docx.getThumbnails(), destFile);
Il serait dommage de ne pas voir ce qu’il se passe en coulisses, voici donc le détail de ces deux méthodes.
La première extrait toutes les parties dont la relation est de type aperçu :
public final static String NS_THUMBNAIL_PART
= "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail";
...
/**
* Obtenir toutes les parties étant des images d'aperçus du document.
*/
public ArrayList<PackagePart>
getThumbnails() {
return container.getPartByRelationshipType(
PackageRelationshipConstants.NS_THUMBNAIL_PART);
}
La seconde est une méthode qui extrait le contenu des parties spécifiées vers le dossier de destination passé en paramètre :
/**
* Extrait le contenu des parties spécifiées dans le répertoire cible.
*
* @param parts
* Les parties à extraire.
* @param destFolder
* Le répertoire de destination.
*/
public void extractParts(ArrayList<PackagePart>
parts, File destFolder) {
for (PackagePart part : parts) {
String filename = PackageURIHelper.getFilename(part.getUri());
try {
InputStream ins = part.getInputStream();
FileOutputStream fw = new FileOutputStream(destFolder
.getAbsolutePath()
+ File.separator + filename);
byte[] buff = new byte[512];
while (ins.available() > 0) {
ins.read(buff);
fw.write(buff);
}
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Le résultat de l’exécution :
.gif) | .gif) |
| La présentation dans PowerPoint 20007 | L’image dans l’explorateur Windows |
Haut de page
Création basique d’un document Word
Pour simplifier cet exemple, nous allons créer un document à partir d’un autre, complètement vierge, dont nous modifierons le contenu ; cette manipulation est plus simple à comprendre et à réaliser dans le cadre de cet article, qu’une création ‘from scracth’. Pour ajouter des paragraphes dans le document, les classes ParagraphBuilder, Paragraph et Run du framework associé à cet article vont nous être particulièrement utiles.
La classe ParagraphBuilder sert à créer des paragraphes, cela facilite la création de paragraphes ayant toujours les mêmes propriétés. Le code suivant met en place un ParagraphBuilder capable de créer des paragraphes dont le contenu est centré horizontalement sur la page :
// Création du 'fabricant' de paragraphes
ParagraphBuilder paraBuilder = new ParagraphBuilder();
paraBuilder.setAlignment(ParagraphAlignment.CENTER);
Une fois le ParagraphBuilder paramétré, vous pouvez créer un nouveau paragraphe prêt à l’emploi en utilisant la méthode newParagraph() :
// On va construire le premier paragraphe
Paragraph par1 = paraBuilder.newParagraph();
Un paragraphe contient plusieurs mots, cependant chaque mot ne possède pas forcément les mêmes propriétés de formatage. Par exemple, un mot ou une suite de mots peuvent être soulignés, alors que les suivants, eux, peuvent être en italique ou en gras. Chaque groupe de mots possédant des caractéristiques de formatage similaires est regroupé dans ce que l’on appelle un Run. Un paragraphe est donc un ensemble de Run qui mis bout à bout forme le texte du paragraphe.
L’exemple suivant montre la création d’un paragraphe dont le contenu est ‘Hello Office OpenXML 2007’ :
...
Paragraph par1 = paraBuilder.newParagraph();
// On ajoute des Run en modifiant leur format
Run r1 = new Run("Hello");
r1.setBold(true); // Mise en gras
Run r2 = new Run(" Office");
r2.setItalic(true); // Mise en italique
Run r3 = new Run(" OpenXML");
r3.setUnderline(UnderlineStyle.SINGLE); // Soulignement simple
Run r4 = new Run(" 2007");
r4.setVerticalAlignement(VerticalAlignment.SUPERSCRIPT); // Mise en exposant
// On ajoute les Run au paragraphe
par1.addRun(r1);
par1.addRun(r2);
par1.addRun(r3);
par1.addRun(r4);
// On ajoute le paragraphe au contenu du document.
docx.appendParagraph(par1);
Le listing 8 reprend l’ensemble des listings 7-x afin de créer un document Word contenant deux paragraphes :
...
Package pack = Package.open(zipFile, PackageAccess.ReadWrite);
WordDocument docx = new WordDocument(pack);
// Création du 'fabricant' de paragraphes
ParagraphBuilder paraBuilder = new ParagraphBuilder();
paraBuilder.setAlignment(ParagraphAlignment.CENTER);
// On va construire le premier paragraphe
Paragraph par1 = paraBuilder.newParagraph();
// On ajoute des Run en modifiant leur format
Run r1 = new Run("Hello");
r1.setBold(true);
Run r2 = new Run(" Office");
r2.setItalic(true);
Run r3 = new Run(" OpenXML");
r3.setUnderline(UnderlineStyle.SINGLE);
Run r4 = new Run(" 2007");
r4.setVerticalAlignement(VerticalAlignment.SUPERSCRIPT);
// On ajoute les Run au paragraphe
par1.addRun(r1);
par1.addRun(r2);
par1.addRun(r3);
par1.addRun(r4);
// On ajoute le paragraphe 1 au contenu du document.
docx.appendParagraph(par1);
// Construction d'un deuxième paragraphe
paraBuilder.setBold(true);
Paragraph par2 = paraBuilder.newParagraph();
Run r21 = new Run("Your potential. Our passion.");
r21.setFontSize(55);
par2.addRun(r21);
// On ajoute le deuxième paragraphe
docx.appendParagraph(par2);
// Sauvegarde du document
docx.save(destFile);
Le document Word résultant de l’exécution de cet exemple :
.gif)
Remarque : les classes du framework de cet article ne gèrent que les propriétés basiques d’un paragraphe : alignement vertical et horizontal, l’italique, le gras, le soulignement, l’exposant et l’indice.
Haut de page
Transformer un document en HTML
Le format Open XML se reposant sur XML, la transformation de son contenu en HTML est un exercice des plus simples. L’utilisation de la technologie de transformation XSLT (eXtensible Stylesheet Language Transformation) reste la solution privilégiée, et c’est celle dont nous userons.
Pour notre exemple, nous utiliserons le document généré par l’exemple précédent, ce fichier étant relativement simple. Voici le XML du contenu de ce document :
.gif)
Taille lisible pour l’utilisateur
Et voici la feuille de style de transformation que nous allons utiliser pour transformer cette structure XML en HTML :
<?xml version="1.0" encoding="utf-8"?>
<!-- Feuille de transformation basique du contenu d'un document Word en HTML. -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/3/main">
<xsl:output method="html"/>
<xsl:template match="/w:document">
<xsl:apply-templates select="w:body"/>
</xsl:template>
<xsl:template match="w:body">
<html>
<body>
<xsl:for-each select="w:p">
<p>
<xsl:apply-templates select="w:pPr"/>
<xsl:apply-templates select="w:r"/>
</p>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template match="w:pPr">
<xsl:attribute name="style">
<xsl:apply-templates/>
</xsl:attribute>
</xsl:template>
<xsl:template match="w:jc">
text-align:<xsl:value-of select="@w:val"/>
</xsl:template>
<xsl:template match="w:r">
<span>
<xsl:apply-templates select="w:rPr"/>
<xsl:value-of select="w:t"/>
</span>
</xsl:template>
<xsl:template match="w:rPr">
<xsl:attribute name="style">
<xsl:apply-templates/>
</xsl:attribute>
</xsl:template>
<!-- Propriétés de format -->
<xsl:template match="w:sz">
font-size:<xsl:value-of select="@w:val"/>px;
</xsl:template>
<xsl:template match="w:vertAlign">
<xsl:variable name="jcVal" select="@w:val"/>
<xsl:if test="$jcVal = 'superscript'">
font-size:33%;position:relative;bottom:0.5em;
</xsl:if>
<xsl:if test="$jcVal = 'subscript'">
font-size:33%;position:relative;bottom:-0.5em;
</xsl:if>
</xsl:template>
<xsl:template match="w:b">font-weight:bold;</xsl:template>
<xsl:template match="w:i">font-style:italic;</xsl:template>
<xsl:template match="w:u">text-decoration:underline;</xsl:template>
</xsl:stylesheet>
Au niveau du code vous n’avez pas à vous soucier de ces détails, puisque la classe de transformation WordToHTMLTransformer possède une méthode très simple d’utilisation. Celle-ci prend en paramètre le document Word que nous voulons convertir, et renvoie en sortie un flux de lecture contenant le résultat de la transformation :
WordDocument docx = new WordDocument(...);
...
WordToHTMLTransformer wt = new WordToHTMLTransformer();
InputStream transformStream = wt.transform(docx);
Voici l’exemple complet transformant le document Open XML Word en HTML :
final String APP_ROOT = System.getProperty("user.dir") + File.separator;
ZipFile zipFile = null; // Le fichier source
try {
zipFile = new ZipFile(APP_ROOT + "sample_out.docx");
} catch (IOException e) {
e.printStackTrace();
}
// La destination du fichier de sortie
File destFile = new File(APP_ROOT + "output.html");
Package pack = Package.open(zipFile, PackageAccess.ReadWrite);
WordDocument docx = new WordDocument(pack);
WordToHTMLTransformer wt = new WordToHTMLTransformer();
try {
InputStream transformStream = wt.transform(docx);
BufferedWriter outStream = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(destFile)));
BufferedReader br = new BufferedReader(new InputStreamReader(
transformStream));
String buff;
while ((buff = br.readLine()) != null)
outStream.write(buff);
outStream.close();
br.close();
} catch (Exception e) {
e.printStackTrace();
}
Le résultat de la transformation XSLT dans Internet Explorer 7 :
.gif)
Haut de page
Conclusion
Dans cette dernière partie de notre article Open XML et Java, nous avons parcouru quelques unes des possibilités de manipulation d’un document Open XML à l’aide d’un framework Java rudimentaire. Comme vous pouvez le constater, ce nouveau format de document étend considérablement les possibilités offertes aux développeurs.
L’ouverture des spécifications et les technologies utilisées par Open XML permettent enfin de profiter d’un des meilleurs formats de fichier bureautique existants à ce jour. Un format ‘next generation’ qui rompt de manière décisive avec les formats précédents pour la plus grande joie des développeurs et des utilisateurs.
Références :
Auteur :
Julien Chable, étudiant à l’EFREI et Microsoft Student Partner est à l’origine de plusieurs articles sur Java et .NET dans des magazines orientés vers le développement. Il est également administrateur du réseau communautaire CodeS SourceS : www.codes-sources.com et peut être contacté via son site http://julien.chable.net ou son blog http://blogs.developpeur.org/neodante/
Haut de page