Générer des documents Open XML sur un serveur Linux en Java

Julien ChablePar Julien Chable, développeur/consultant chez Wygwam France sur les technologies Office, Open XML et SharePoint.

Blog de Julien.

La génération de documents Office a toujours été contraignante. Le plus souvent, la solution consistait à installer un Office sur le serveur, évidemment Windows,  et à utiliser l’automation ou autre technique pour pouvoir le manipuler (attention cette pratique n’est pas recommandée pour des raisons de robustesse et de montée en charge ! Référez vous à la KB 257757 de Microsoft. Aujourd’hui avec l’arrivée du standard Open XML, devenu norme ISO depuis peu, la génération de documents devient possible simplement en utilisant les technologies Zip et de XML sur n’importe quelle plateforme.

Ainsi il est devenu enfin possible de générer des documents Word, Excel et PowerPoint sans même avoir Office ou Windows d’installer sur une machine ! Plus généralement, n’importe quel système d’exploitation et technologie capable de manipuler le format compressé Zip et XML est capable de créer des documents Open XML.

Cet article va être l’occasion de vérifier cette théorie au travers d’une application de génération de document Open XML sur un serveur Linux et en Java s’il vous plait.

Introduction

L’idée autour de cet article est de générer un document à partir d’information saisie par un utilisateur puis de le renvoyer au client :

En environnement hétérogène :

Le format Open XML

Avant de commencer à parler de système d’exploitation et de plateforme de développement, nous allons nous arrêter quelques instants sur le format Open XML.

Open XML est un standard de l’ECMA depuis 2006 et une norme ISO depuis 2008. Vous pouvez trouver une introduction au format Open XML dans la partie de MSDN France réservée à cet effet : https://msdn2.microsoft.com/office/bb409621.

Un document Open XML est un fichier Zip compressé appelé le paquet dans le jargon Open XML. Dans celui-ci se trouve plusieurs fichiers XML ou binaire (des images, des sons, etc) appelés des parties. Chaque partie peut-être reliée à une autre, exemple une image au contenu du document, à l’aide d’un lien logique appelée une relation. Cet ensemble de notions regroupe ce que les spécifications appellent l’Open Packaging Convention ou OPC, qui est la partie commune aux formats spécifiant la structure interne d’un document Open XML.

Le schéma suivant montre la structure hiérarchique tel qu’elle se trouve dans un document Open XML de type Word (nouvelle extension .docx) :

Le schéma suivant représente la structure logique du même document, c'est-à-dire l’ensemble des relations entre les parties permettant à une application de reconstitué le document pour le présenter à l’utilisateur :

Cet article n’est pas orienté sur le format Open XML, néanmoins il nous manque encore un point crucial à introduire pour une bonne compréhension de l’application côté serveur que nous allons générer : le document minimal.

En effet pour pouvoir générer un document Open XML de type Word valide, nous devons créer au moins plusieurs parties dans le paquet :

·        Le fichier de type de contenu [Content_Types].xml

·        Le fichier de contenu principal (aka le contenu du document : les paragrapges, etc)

·        Le fichier de relations permettant de trouver le fichier de contenu

Comme nous le verrons dans la suite de cet article, la partie de type de contenu et celles de relations seront gérées par les librairies utilisées. Néanmoins le contenu de la partie principale contenant le contenu du document est à notre charge. L’objectif de cet article n’étant pas de créer des documents complexes, nous allons nous arrêter à la génération d’un document excessivement simple, puisque nous n’allons générer qu’un seul paragraphe !

Pour pouvoir générer un paragraphe, voici la structure XML nécessaire à la partie de contenu principale :

<document xmlns:w=...>
    <body>
        <p>
            <r>
                <t>Contenu du paragraphe</t>
            </r>
        </p>
    </body>
</document>

Maintenant que nous en savons suffisamment pour les besoins de cet article, nous allons nous intéresser à la génération du document sur le serveur en utilisant la librairie OpenXML4J.

OpenXML4J : une librairie Java pour générer de l’Open XML

Depuis la sortie de .NET 3, Microsoft fournit une API pour pouvoir manipuler la partie OPC des documents Open XML (paquet, parties et relations). Cette API contenue dans la DLL WindowsBase.dll fournit l’espace de nom System.IO.Packaging :

Du côté de Java, le projet open source (licence BSD/Apache v2) OpenXML4J offre une librairie similaire à celle de .NET pour manipuler la structure OPC d’un document Open XML.

Par exemple, le code suivant permet de lister toutes les parties d’un paquet en affichant l’URI et le type de contenu de chacune d’elles :

Package p = Package.open("Sample.docx", PackageAccess.READ);
for (PackagePart part : p.getParts())
   System.out.println(part.getUri() + " -> " + part.getContentType());

Nous utiliserons la librairie OpenXML4J dans l’application côté serveur afin de générer le document Open XML.

Créer le web service de génération de document Open XML côté serveur

Maintenant que les différentes notions indispensables au bon suivi de cet article ont été introduites, continuons avec un peu de code. Ce que nous allons créer dans cette partie est un web service Java s’exécutant dans un serveur d’application GlassFish v2.

Remarque :depuis la sortie de la version 6 de Java, vous pouvez héberger des Service Web directement du côté client sans utiliser de serveur d’application. Pour ce faire, utilisez la classe EndPoint : http://java.sun.com/javase/6/docs/api/javax/xml/ws/Endpoint.html.

La création d’un web service n’a rien en soi de bien compliqué puisqu’elle suit la logique de décoration avec des attributs relativement explicite comme le montre l’extrait de code suivant :

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService()
public class GenerateDocument {

@WebMethod()
public byte[] generateSimpleWordprocessingMLDocument(String content) throws Exception{
...
}
}

Le service web ainsi déclaré crée une méthode generateSimpleWordprocessingMLDocument que nous allons utiliser pour générer notre document OpenXML sur le serveur. Celle-ci comprend un paramètre de type chaine de caractères qui permettra au consommateur du service web de créer un document contenant un paragraphe avec le texte spécifié par ce paramètre. En retour cette méthode transmet le document généré sous forme d’un tableau d’octets.

Avant de vous présenter le code de génération de document Open XML, voici deux méthodes utilitaires importantes permettant de créer un nom de fichier temporaire et d’obtenir le contenu d’un fichier binaire sous formes de tableau d’octets :

private byte[] getBytesFromFile(File f) throws IOException{
FileInputStream fis = new FileInputStream(f);
byte[] retArr = new byte[(int)f.length()];
fis.read(retArr);
return retArr;
}
private File getTemporaryFile() throws IOException{
String filename = new Date().getTime() + ".tmp";
return new File(filename);
}

Comme nous l’avions vu en introduction, Open XML spécifie un document minimal qui représente le document minimaliste pouvant être conforme et affichable par une application. Nous allons recréer cette structure de toute pièce côté serveur. Une autre approche aurait pu consister à utiliser un document existant ‘modèle’, à l’ouvrir et à ne changer que ce qui devrait l’être. A des fins de démonstration nous avons retenu seulement la première solution.

Nous allons détailler le code suivant qui construit le document :

@WebMethod()
public byte[] generateSimpleWordprocessingMLDocument(String content) throws Exception{
File tmpFile = getTemporaryFile();
        
// Création du package
Package pkg = Package.create(tmpFile);
        
/* Création de la partie principale */
PackagePartName corePartName =
    PackagingURIHelper.createPartName("/word/document.xml");
        
// Création de la partie
PackagePart corePart = pkg.createPart(corePartName,
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml");
        
// Création de la relation principale
pkg.addRelationship(corePartName, TargetMode.INTERNAL,
        PackageRelationshipTypes.CORE_DOCUMENT, "rId1");
...
}

La première étape de ce code est assez simple puisqu’il crée un nouveau paquet dans un emplacement temporaire. Nous y ajoutons ensuite la partie principale du document nommé document.xml dans le répertoire word situé à la racine du paquet (d’où l’URI /word/document.xml).

Maintenant que nous avons créé la partie principale du document, celle qui contient le contenu du document, comment l’application va-t-elle être capable de retrouver celle-ci dans le paquet ? La localisation des parties dans le paquet est un rôle dédié aux relations. Nous allons par conséquent créer une relation entre le paquet et la partie principale. Une relation est typée afin que l’application consommatrice du document Open XML puisse identifier sans hésitation la nature de la partie ciblée par une relation. Par exemple, si une relation possède le type https://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument l’application saura qu’il s’agit de la partie principal du document, et par conséquent qu’elle devra la lire en priorité et qu’elle représente le contenu du document (et ce quelque soit le type de document : Word, Excel ou PowerPoint).

Notez que nous n’avons pas à nous souciez de la partie de type de contenu qui est gérée en interne par la librairie. Il en va de même pour la partie de relations pour laquelle le simple fait d’appeler les méthodes d’ajout ou de suppression de relations modifie le contenu de la partie de relations adéquate.

La structure du document est fin prête et il ne nous manque que la structure XML de la partie principale pour spécifier le contenu du document constitué d’un seul et unique paragraphe :

<document xmlns:w=...>
    <body>
        <p>
            <r>
                <t>Mon texte</t>
            </r>
        </p>
    </body>
</document>

Voici comment générer cette structure XML en Java avec la librairie DOM4J :

// Création du contenu principal du document
Document doc = DocumentHelper.createDocument();
Namespace nsWordprocessinML = new Namespace("w",
    "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
Element elDocument = doc.addElement(new QName("document",
    nsWordprocessinML));
Element elBody = elDocument.addElement(new QName("body",
    nsWordprocessinML));
Element elParagraph = elBody.addElement(new QName("p",
    nsWordprocessinML));
Element elRun = elParagraph
    .addElement(new QName("r", nsWordprocessinML));
Element elText = elRun.addElement(new QName("t", nsWordprocessinML));
    elText.setText(content);

Notez que l’on spécifie le contenu, spécifié dans les paramètres de la méthode, dans l’élément t situé dans un run (élément r) d’un paragraphe (élément p).

Une fois la structure XML réalisée, nous devons l’enregistrer dans la partie principale du document puis fermer le document avant de le retourner en fin de méthode :

// Sauvegarde le contenu XML du document dans sa partie
StreamHelper.saveXmlInStream(doc, corePart.getOutputStream());
pkg.close();
        
// Envoi du document encodé en binaire
byte[] retData = getBytesFromFile(tmpFile);
tmpFile.delete();
return retData;

Nous voici en fin de création du web service, nous allons devoir maintenant le déployer sur le serveur.

Remarque :les sources sont associées à l’article. Vous y trouverez un fichier Ant qui vous permettra de construire l’archive. Vous pouvez également utiliser NetBeans pour ouvrir ce projet.

Déployer le Service Web sur le serveur Glassfish

Pour déployer le Service Web sur le serveur, packagez votre projet sous la forme d’un fichier WAR (Web Archive) et copiez-le sur le serveur Glassfish v2 dans le répertoire autodeploy.

Si votre serveur est démarré (commande asadmin start domain <nom de votre domaine>) l’archive se déploiera automatiquement et vous pourrez constater l’apparition du Service Web dans la centrale d’administration du serveur JEE :

A partir d’ici, votre Service Web est correctement déployé sur le serveur et vous pouvez consulter le WSDL du service en effectuant la requête suivante : http://<adresse IP de votre serveur>:<port du serveur>/OpenXML4JWS/GenerateDocumentService?wsdl

Créer le client .NET pour consommer le web et récupérer le document Open XML

Nous allons consommer le Web Service créé précédemment à l’aide d’une application console classique. Pour cela nous allons utiliser la brique Windows Communication Foundation et la capacité de génération du proxy de Visual Studio 2005/2008.

Afin de créer les classes du proxy client qui consommera le Web Service, ajouter une Référence de service en spécifiant le WSDL du Service Web et un espace de nom OpenXML4JWSDemo. Visual Studio va créer pour vous toute la plomberie WCF nécessaire à l’appel du Web Service sans nécessité d’autres informations.

Commençons par utiliser les classes générées pour faire un appel à notre Service Web :

public static string GenerateSimpleDocument(string content)
{
OpenXML4JWSDemo.GenerateDocumentClient client = new SimpleDocBuilder.OpenXML4JWSDemo.GenerateDocumentClient();
Console.WriteLine("Début de génération du document sur [" + client.getEnvDetails() + "]");
byte[] wsData = client.generateSimpleWordprocessingMLDocument(content);
return GetFileFromBinary(wsData);
}

Vous remarquerez qu’il existe une méthode du client nommée getEnvDetails(). Nous avons rajouté cette méthode afin de pouvoir récupérer les informations de la plateforme d’exécution du Service Web (les sources intègrent cette méthode) et les afficher. Ceci afin de bien montrer que nous créons le document sur une plateforme autre que Windows.

Nous récupérons le document sous la forme d’un tableau d’octet qui sera ensuite reconverti en fichier sur l’ordinateur client avec la méthode suivante :

public static string GetFileFromBinary(byte[] data)
{
string fileName = @"C:\...\MyDoc.docx";
File.WriteAllBytes(fileName, data);
return fileName;
}

Remarque : pour cette démonstration l’emplacement du fichier est fixe, mais vous pouvez tout à fait le spécifier avec un paramètre supplémentaire dans la méthode.

Il ne reste plus qu’à utiliser ces quelques méthodes pour compléter notre application :

static void Main(string[] args)
{
Console.Write("Entrez le contenu du document : ");
string input = Console.ReadLine();

// Appel du Service Web de génération de document Open XML
string fichierGenere = GenerateSimpleDocument(input);
Console.WriteLine("Fichier '{0}' genere avec succes !", fichierGenere);
Console.ReadLine();
}

L’exécution de ce code affichera le résultat suivant dans la console :

Et vous générera le document suivant :

 

Conclusion

Cet article se veut volontairement démonstratif dans le sens où le document généré est simple et l’utilisation des plateformes rendues à leur plus simple expression. Néanmoins, la possibilité aujourd’hui de pouvoir générer des documents lisibles par Office (Office 2000/XP/2003 avec le Pack de compatibilité faisant partie des derniers Service Pack Office) depuis une infrastructure non Microsoft et en utilisant n’importe quelle plateforme de développement représente une réelle avancée dans l’interopérabilité des systèmes et des solutions d’entreprises. C’est aussi ici que la normalisation du format Open XML prend toute son importance et sa valeur.

Référence

 

Haut de pageHaut de page