Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Créer un document Office 2007 en ActionScript

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par pinacolada (pinaColada), le 10 avril 2010

Le format de la version 2007 d'Office nommé OpenXML est un format libre, et il utilise des fichiers XML zippées. Grâce à un composant (libre lui aussi) et deux ou trois petites fonctions, vous pouvez donc accéder en lecture et en écriture au format .docx ou .xlsx. (Triturer les données obtenues sera évidemment plus difficile). Comment faire ? Voici le tutoriel en sept étapes au pas à pas, suivi du code complet, pour créer et sauvegarder sur votre ordinateur un fichier valide au format Word 2007, s'ouvrant parfaitement dans votre traitement de texte.

1/7 Creer un DocumentClass

Créer un document word avec FLASH, c'est possible, et même… assez simple !
Nous travaillons avec Flash Develop, en AS3 pur. Si ce n'est pas le cas, rassurez-vous : toutes les explications restent valables mais c'est une bonne occasion de tester cet éditeur de rêve…

Si vous n'utilisez pas Flash Develop, vous trouverez notamment des explications sur l'importation du .swc avec l'IDE de Flash dans cette discussion.
lilive - 13 mai 2010

Nous commençons avec cette structure toute simple :
Le dossier du programme se nomme Word2Flash. Il contient peu de choses : un dossier src avec, à la racine, un fichier AS3 nommé Word2Flash.as. Ce fichier est le fichier principal qui sera exécuté. Voici son squelette :

package src 
{
	import flash.display.Sprite;
 
	/**
	 * Écrire dans un fichier Word sous Flash
	 * @author pina34Colada@home
	 */
	public class Word2Flash extends Sprite
	{
		public function Word2Flash() 
		{
 
 
		}
	}
}

Il étend Sprite parce que c'est un documentClass. Cela signifie que c'est ce fichier qui sera compilé et lancé en tant que swf. Les autres fichiers dépendront de lui et seront appelés par lui. Il est inutile d'étendre MovieClip pour ce swf car nous n'aurons pas de frame et nous nous contentons d'importer et d'étendre la classe Sprite.

Créer ensuite dans le dossier src deux dossiers : un dossier docs et un dossiers libs. Nous obtenons donc ceci dans notre projet Word2Flash :

[src]
  |
  +---[docs]
  |
  +---[libs]
  |
  Word2Flash.as

On ne peut pas faire plus simple. La taille, la vitesse et la couleur de votre swf est à configurer à votre guise dans la partie Project/Properties du programme. Pensez tout de même à mettre le player sur Flash Player 10 !

2/7 - Télécharger et installer un composant

Télécharger et décompressez le composant qui permet de zipper ou de dézipper des fichiers l'adresse suivante :

http://nochump.com/blog/wp-content/uploads/2008/11/nochump-ziplib-105-dist.zip

Toute la magie de la lecture et de la compression d'un zip se trouve dedans et nous l'utiliserons (sans chercher à l'ouvrir). Ouvrez le fichier téléchargé (qui est lui-même un fichier zip), prenez dedans le fichier nommé “zip.swc” (taille : 11658 octets) et mettez-ce fichier dans le dossier [libs] du programme. Il n'y a pas d'autre fichier dans le zip, donc il serait difficile de se tromper !

Mais euh… comment on s'en sert ? Il reste une opération simple pour que ce composant soit accessible au code : il faut qu'il fasse partie des librairies de votre programme. FlashDevelop nous facilite les choses :

  1. Cliquez avec le bouton de droite sur le fichier zip.swc dans la fenêtre Project.
  2. Cochez “Add to library” (ajouter aux librairies) et voilà ! Votre composant fait partie de votre programme, comme si vous l'aviez codé vous même. Elle n'est pas belle, la vie ?

Vous vous demandez sans doute de quoi est capable ce composant : eh bien il donne à Flash une capacité intéressante : celle de lire et d'écrire des fichiers .zip conventionnels. Il est léger, simple à comprendre et à utiliser, totalement fonctionnel.
Il possède trois classes dont nous abuserons sans vergogne :

  1. La classe ZipFile : c'est un fichier zippé ouvert en lecture. On peut consulter le nombre de fichiers qu'il contient, le contenu de chaque fichier, grâce au tableau entries (un simple Array).
  2. La classe ZipEntry : c'est l'un des fichiers contenus dans le ZipFile. On peut en récupérer les données sous forme de byteArray. On peut en connaître la taille compressée ou non.
  3. La classe ZipOutput : c'est un fichier zippé ouvert en écriture : on peut y ajouter des données stockées dans des byteArrays, en prenant soin ensuite de fermer chaque entrée créée avec la méthode .closeEntry(), puis le ZipOutput lui-même avec la méthode .finish(). Ce fichier pourra être alors sauvegardé avec FileReference.save().

Les commandes sont simples, comme vous le constatez, n'ont qu'un ou aucun paramètre donc on les mémorise en trois minutes. Et pourtant, il fait tout ce dont nous avons besoin !

3/7 - Créer dans le package un fichier Word modèle

Maintenant, trichez ! Ouvrez un “document word” 2007 vierge et enregistrez-le dans le dossier [docs] du projet avec le nom de “modele.docx”. Je ne vais pas donner d'explication sur cette procédure, hein… Comme vous le constatez, on évitera l'accent pour rendre tout ceci compatible avec un serveur.

[src]
  |
  +---[docs]
  |      |
  |      - modele.docx 
  |
  +---[libs]
  |      |
  |      - zip.swc
  |
  - Word2Flash.as

Nous sommes d'accord sur cette structure ? Elle est déjà complète : passons aux données !

Le constructeur de la classe ZipFile n'a besoin que d'un paramètre : un flux de données. Ce type de données est géré en AS3 par le byteArray. Nous utiliserons plusieurs fois le byteArray dans ce tutoriel. Encore un de ces objets bien pratiques que Flash met à notre disposition. Il y a de nombreux moyens d'obtenir un byteArray :

  1. de le créer soi-même de toutes pièces.
  2. charger un fichier contenant ce flux de données, à l'aide de la classe FileReference. En effet, cette classe renvoie tout simplement un byteArray, donc ce qui nous intéresse, contenu dans le fichier choisi. On peut charger une image, un fichier xml, un texte ou un fichier .swf sous forme de byteArray avant de le triturer et de le sauvegarder. On peut donc maintenant tout faire avec Flash !

Prenons donc notre fichier Word créé mais vierge. Pourquoi passer par un fichier Word existant ? Parce qu'il contient déjà toute la structure d'un bon fichier au format docx, et donc il contient toutes les parties qui sont nécessaires à la création d'un autre fichier de même type. (Il en contient même un peu trop.) Nous le remplirons d'informations puis nous sauvegarderons le nouveau fichier, créant vraiment ainsi notre document word avec Flash. Nous verrons peut-être dans un autre tutoriel comment créer toute la structure d'un fichier Word, à la main, du début à la fin, sans modèle…

La propriété entries de l'objet ZipFile stocke tous les fichiers de l'archive zip comme des objets de type ZipEntry. Ceci signifie qu'un ZipFile est tout bêtement un dossier contenant des fichiers (zipEntry). Et un fichier au format .docx est exactement la même chose : il contient des dossiers et des fichiers, qui sont tous au format xml.

Enfin, récupérer les données contenues dans un zipEntry est possible grâce à la méthode getInput() qui renvoie les données (du texte ici, au format xml) inscrites dans ce fichier.

Ajoutons ces trois lignes d'import à notre document de classe Word2Flash, juste après l'import existant de flash.display.Sprite :

import nochump.util.zip.*
import flash.events.*;
import flash.net.*;

Puis, dans le corps de la fonction, entre accolades, les trois lignes suivantes :

	var chargeur:URLLoader = new URLLoader(new URLRequest("src/docs/modele.docx"));
	chargeur.dataFormat = URLLoaderDataFormat.BINARY;
	chargeur.addEventListener(Event.COMPLETE, surChargementModele);

Que venons nous de faire ? Nous chargeons notre modèle.docx, contenu dans notre dossier src/docs/. Il sera récupéré à la fin du chargement par l'événement COMPLETE (fichier chargé) dans une fonction nommée “surChargementModele”.

4/7 - Lire la partie document du modèle

Rechercher dans le contenu du zipFile le zipEntry qui nous intéresse, celui où se trouve le texte :

Créons vite cette fonction, que nous placerons après le constructeur de la classe :

public function surChargementModele(e:Event):void 
{
	var paquet:ZipFile = new ZipFile(e.target.data);
	var nbParties:int = paquet.size; 
	trace("Taille du fichier chargé :",URLLoader(e.target).bytesTotal," - Nombre de parties :", nbParties);
	for(var i:int = 0; i < nbParties; i++) {
		var entree:ZipEntry = paquet.entries[i];
		trace(i + 1, "-", entree.name, "- Taille : réelle =", entree.size, "compressée =", entree.compressedSize);  
	}
}

Voici ce que vous devriez obtenir en trace :

Taille du fichier chargé : 9828  - Nombre de parties : 11
1 - [Content_Types].xml - Taille : réelle = 1312 compressée = 358
2 - _rels/.rels - Taille : réelle = 590 compressée = 243
3 - word/_rels/document.xml.rels - Taille : réelle = 817 compressée = 250
4 - word/document.xml - Taille : réelle = 963 compressée = 430
5 - word/theme/theme1.xml - Taille : réelle = 6993 compressée = 1691
6 - word/settings.xml - Taille : réelle = 1538 compressée = 691
7 - word/fontTable.xml - Taille : réelle = 1031 compressée = 395
8 - word/webSettings.xml - Taille : réelle = 260 compressée = 187
9 - docProps/app.xml - Taille : réelle = 704 compressée = 363
10 - docProps/core.xml - Taille : réelle = 641 compressée = 332
11 - word/styles.xml - Taille : réelle = 14815 compressée = 1800

Eh oui : nous avons maintenant accès à tout le contenu du fichier .docx, qui est un simple fichier .zip contenant 11 fichiers xml. Un seul de ces fichiers nous intéresse pour l'instant. Devinez lequel ? Le fichier numéro 4 ! C'est celui dans lequel se trouve le texte affiché, et il se nomme “word/document.xml”. Ce nom indique qu'il y a dans l'archive un répertoire word contenant un document.xml.

Dans l'état actuel de notre programme, voici ce que nous avons :

package src 
{
	import flash.display.*;
	import nochump.util.zip.*
	import flash.events.*;
	import flash.net.*;
	/**
	 * Écrire dans un fichier Word sous Flash
	 * @author pina34Colada@home
	 */
	public class Word2Flash extends Sprite
	{
		public function Word2Flash()
		{
			var chargeur:URLLoader = new URLLoader(new URLRequest("src/docs/modele.docx"));
			chargeur.dataFormat = URLLoaderDataFormat.BINARY;
			chargeur.addEventListener(Event.COMPLETE, surChargementModele);
		}
		public function surChargementModele(e:Event):void 
		{
			var paquet:ZipFile = new ZipFile(e.target.data);
			var nbParties:int = paquet.size; 
			trace("Taille du fichier chargé :",URLLoader(e.target).bytesTotal," - Nombre de parties :", nbParties);
			for(var i:int = 0; i < nbParties; i++) {
				var entree:ZipEntry = paquet.entries[i];
				trace(i + 1, "-", entree.name, "- Taille : réelle =", entree.size, "compressée =", entree.compressedSize);  
			}
		}
	}
}

Quelles sont les possibilités offertes par notre sympathique composant ? Pouvons-nous tout d'abord lire ce fichier xml ? Voici ce que propose la documentation :

→ getInput (entry:ZipEntry):ByteArray Creates a byte array reading the given zip entry as uncompressed data.

Traduction : la méthode getInput crée un byteArray lisant l'entrée sous forme de données non compressées. C'est exactement ce que nous voulons faire. Nous devrons passer par trois étapes, qu'il faudra bientôt réduire en une petite fonction et qui nous donnera accès à n'importe quelle partie de l'archive par son nom. En voici le détail :

Première étape : accéder à la bonne entrée à l'aide de son nom.

	var doc:ZipEntry = paquet.getEntry("word/document.xml");

Seconde étape : lire les données de cette entrée, et les placer dans un byteArray à l'aide de cette fameuse méthode getInput.

	var donnees:ByteArray = paquet.getInput(doc);

Dernière étape : convertir ces données en données XML. Nous savons que ces données sont du texte, donc :

	var docXML:XML = new XML(donnees.readUTFBytes(donnees.length));

Nous utilisons là l'une des méthodes du byteArray qui permet de lire des données binaires en considérant que c'est du texte.

Ces trois étapes sont immédiatement transformées en une seule fonction, afin de simplifier l'analyse ultérieure du document complet. Une toute petite fonction, à laquelle nous passerons en paramètre le paquet complet et le nom de la partie, devrait pouvoir nous renvoyer le fichier xml désiré. Voici cette fonction :

public function partie(paquet:ZipFile, cheminPartie:String):XML 
{
	var entree:ZipEntry = paquet.getEntry(cheminPartie);
	if (entree == null) return null;
	var donnees:ByteArray = paquet.getInput(entree);
	return new XML(donnees.readUTFBytes(donnees.length));
}

Il s'agit très exactement des étapes précédentes, auxquelles nous ajoutons par précaution un test de présence… En effet, un document dans lequel manquerait une partie à laquelle nous pensions pouvoir accéder pourrait autrement la faire échouer. Testons immédiatement le résultat :

public function accesAuxDonnees(paquet:ZipFile):void 
{
	var content:XML = partie(paquet, "[Content_Types].xml");
	var rels:XML = partie(paquet, "_rels/.rels");
	var doc:XML = partie(paquet, "word/document.xml");
	trace("-------< Content >--------- \n",content.toXMLString());
	trace("-------< Relations >------- \n",rels.toXMLString())
	trace("-------< Document >-------- \n",doc.toXMLString());			
}

Pour lancer cette fonction, il faut placer l'ordre dans la fonction qui a accédé au fichier car son contenu n'est pas connu ailleurs dans le programme. La fonction ci-dessus appellera trois fois la fonction que nous avons nommée partie, et affichera pour les trois parties choisies le fichier xml qu'elles contiennent. Ajoutons en dernière ligne à la fonction surChargementModele l'appel suivant :

	accesAuxDonnees(paquet);

Elle nous donne comme renvoi pour partie(rels) le résultat suivant :

-------< Relations >-------
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>

Et pour partie(doc), ceci, que nous analyserons tout de suite :

-------< Document >--------
 <w:document xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006" 
	xmlns:o="urn:schemas-microsoft-com:office:office" 
	xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" 
	xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" 
	xmlns:v="urn:schemas-microsoft-com:vml" 
	xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" 
	xmlns:w10="urn:schemas-microsoft-com:office:word" 
	xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" 
	xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
  <w:body>
    <w:p w:rsidR="00B84305" w:rsidRDefault="00B84305"/>
    <w:sectPr w:rsidR="00B84305">
      <w:pgSz w:w="11906" w:h="16838"/>
      <w:pgMar w:top="1417" w:right="1417" w:bottom="1417" w:left="1417" w:header="708" w:footer="708" w:gutter="0"/>
      <w:cols w:space="708"/>
      <w:docGrid w:linePitch="360"/>
    </w:sectPr>
  </w:body>
</w:document>

Quel étrange objet ! Eh bien, non, il s'agit simplement de la page centrale d'un fichier Word ! Décomposons cet xml en parties, en le simplifiant pour la compréhension.

<document>
    <body>
        <p/>
        <sectPr>
            <pgSz 
                w="11906"
                h="16838"
            />
            <pgMar
                top="1417"
                right="1417"
                bottom="1417"
                left="1417" 
                header="708"
                footer="708" 
                gutter="0"
            />
            <cols
                space="708"
            />
            <docGrid 
                linePitch="360"
            />
	</sectPr>
    </body>
</document>

Rhaaaaaaaa, c'est bien mieux non ? Qu'ai-je donc fait là pour obtenir ce résultat ? J'ai supprimé les espaces de nom. C'est t'y quoi donc que ces espèces de trucs ? Parce que les fichiers xml n'ont pas de balises prédéfinies, il est très facile d'avoir les mêmes balises pour des fichiers XML différents en utilisation. Pour distinguer les noeuds et les attributs on pourrait avoir envie de dire :

    noeud "id" du fichier "parent" ajouté au noeud "id du fichier "enfant"

ou bien :

    attribut "nom" du noeud "auteur"
 +  attribut "nom" du noeud "livre" 
 +  attribut "nom" du fichier "librairie"

Pour faire de tels sauts d'un endroit à l'autre en étant sûr de ne pas se perdre en voyage, on utilise ces fameux espaces de noms : on place devant le noeud ou l'attribut un préfixe qui indique rapidement où il faudra chercher l'information désirée.
C'est comme si, pour chaque noeud ou pour chaque attribut, on ajoutait une adresse internet complète indiquant le sens réel de cet attribut. Cet espace de nom se présente, justement, sous la forme d'une adresse internet.
Pouvons-nous supprimer sans risque les espaces de nom dans notre résultat ? Normalement non, mais certains ne servent pas vraiment. Vous remarquerez que beaucoup d'espaces de noms sont déclarés dans le premier noeud du fichier doc mais qu'un seul d'entre eux est utilisé ensuite. On pourra sans difficulté supprimer ceux que nous n'utilisons pas de la partie que nous désirons produire.
Les espaces de nom sont longs à taper. Pour aller plus vite, on met dans les noeuds, devant les noms de noeuds ou d'attribut, une abréviation qui les remplace. Le petit w au début de chaque noeud est le rappel du w indiqué dans le premier noeud, et qui représente l'adresse entière de l'espace de nom :

    <document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">

Ce noeud utilise l'espace de nom “http://schemas.openxmlformats.org/wordprocessingml/2006/main” qui sera remplacé par “w”.

Les abréviations sont créées comme vous le voyez à l'aide d'un attribut spécial du langage XML, seul attribut que l'on peut mettre plusieurs fois dans un noeud : l'attribut xmlns, qui signifie donc xml namespace, et qui sera suivi par l'espace de nom entier, une adresse internet fictive en général, qui sert juste à spécifier de quoi il est exactement question. Inutile de tester ces adresses dans votre navigateur, vous n'irez nulle part !

Nous savons donc maintenant que <w:body> signifie : “dans l'espace de nom w nommé dans l'en-tête du document xml, ouvrons un noeud nommé body”.

Soudain, débarrassé de tous ces espaces sidérants, notre doc devient plus clair :

  • En fait on a comme noeud racine un noeud document, qui contient un body (un corps de document comme en html) composé d'un paragraphe <p/> vide. C'est vrai, notre document était vide.
  • Ensuite est indiqué dans ce corps le noeud <sectPr>, qui est un secteur, l'équivalent au choix d'un groupe de paragraphes, d'une page ou d'un groupe de pages.
  • Le noeud pgSz indique une largeur (w) de “11906” et une hauteur (h) de “16838”. Ce sont les dimensions de la page.
  • Le noeud suivant, pgMar indique le tailles des marges de la page : top (en haut) right (à droite) bottom (en bas) et left (à gauche).

Sont indiquées aussi la marge d'en-tête (header), la marge du pied de page (footer) et une gutter (bordure) de 0.
La section se termine par l'espacement des colonnes et la taille de la grille. Nous n'en aurons pas besoin.

Ce que nous voulons faire est simple : Créer une copie de ce document, dans laquelle nous mettrons notre texte. Et sauvegarder le résultat afin d'obtenir un fichier .docx.
Eh bien, créons avec notre composant un clone de ce fichier, dont nous modifierons la partie document.
C'est l'avant-dernière étape de notre tutoriel, la dernière consistant simplement à sauvegarder le résultat…

5/7 - Écrire une nouvelle version du document

Nous procéderons dans le sens inverse de la lecture :

  1. Créer un fichier document en xml
  2. Mettre ce fichier dans un byteArray
  3. Transmettre ce byteArray au composant capable de le zipper.

Première étape : Reprenons le fichier XML existant et simplifions-le. On peut écrire directement du xml, sans autre formatage, dans un document actionscript :

var espace:String="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
var w:Namespace = new Namespace("w", espace);
 
var modif:XML = <w:document xmlns:w={w.uri}>
  <w:body>
	<w:p><w:r><w:t>Bonjour, le monde !</w:t></w:r></w:p>
	<w:p><w:r><w:t>Ceci est le second paragraphe...</w:t></w:r></w:p>
	<w:p><w:r><w:t>Et ceci est la fin.</w:t></w:r></w:p>
  </w:body>
</w:document>

Ah oui, il est vraiment très simple, là !
J'ai supprimé tous les espaces de nom inutiles. Et je n'indique aucune marge, mon fichier Word s'ouvrira avec les marges par défaut.
Le fichier xml restant contient juste trois balises de paragraphes, placées dans le body, lui-même placé dans le document.
Le texte lui-même est dans une balise r (comme run) puis une balise t (comme text).
Un paragraphe dans Word (balise p) est… un paragraphe. Il contient des runs, c'est-à-dire des bouts de texte avec leur format collés les uns aux autres.
Chaque run, puis chaque text dans le run, peut avoir son propre format ou son propre style, et c'est là l'intérêt de ces subdivisions du paragraphe.
Si j'écris en html : <p>La lettre <b>A</b> est en gras</p> cela revient à créer dans Word trois balises t dans une balise r :

  1. Premier run : “La lettre ” : l'espace qui suit fait partie du texte
  2. Second run : “A”
  3. Dernier run ” est en gras” : l'espace du début fait partie du texte.

Et si j'écrivais dans une page html :

<p><font color="#FF0000">Le début est en rouge</font> et la suite <u>est</u> en noir...</p>

pour obtenir le même résultat dans Word, je créé deux runs dans un paragraphe. Et dans le second run, je mets trois textes… Voilà.

Seconde étape :

var donnees:ByteArray = new ByteArray;
donnees.writeMultiByte(modif,"UTF-8");

Dernière étape :

var nouveauZip:ZipOutput = new ZipOutput();
for (var i:int; i < paquet.size; i++) {
	var nomPartie:String = paquet.entries[i].name
	nouveauZip.putNextEntry(new ZipEntry(nomPartie));
	if (nomPartie != "word/document.xml") {
		nouveauZip.write(paquet.getInput(entree));
	} else {
		nouveauZip.write(donnees);
	}
	nouveauZip.closeEntry();
}
nouveau.finish();

Explications :

  1. Je crée un fichier zip nommé nouveauZip.
  2. Je cherche toutes les parties du modèle (paquet).
  3. Si le nom de la partie n'est pas celui de mon document, je copie l'ancienne partie.
  4. Dans le cas contraire, je mets la partie que je viens de créer à la place.
  5. Je ferme chaque entrée puis je ferme l'archive. C'est zippé !

6/7 - Sauvegarder l'archive créée

La fonction de sauvegarde avec FileReference, pour des raisons de sécurité, ne fonctionne que si l'utilisateur a cliqué quelque part.
Eh bien, nous créerons un clip qui servira de bouton. Quand l'utilisateur cliquera dessus, hop ! la boîte de dialogue s'ouvrira pour choisir l'emplacement du fichier docx.
L'intérêt du MovieClip c'est qu'il est dynamique : je pourrai lui ajouter n'importe quelle propriété, et ici, j'en profiterai pour lui ajouter notre tout nouveau zip avec comme nom de propriété document.
Créons la fonction de sauvegarde, dans un gestionnaire d'événement souris :

private	function enregistrer(e:MouseEvent):void 
{
	var nouveau:ZipOutput = MovieClip(e.target).document;
	var fr:FileReference = new FileReference
	fr.save(nouveau.byteArray);
}

7/7 - Créer un bouton pour la sauvegarde

Pour terminer et rendre la sauvegarde fonctionnelle, créons enfin un magnifique clip sur lequel l'utilisateur aura envie de cliquer…

	var btn:MovieClip = new MovieClip;
	btn.graphics.lineStyle(1,0xff0000); // bordure rouge
	btn.graphics.beginFill(0xccccff, 1);//sur fond bleu 
	btn.graphics.drawRoundRect(0, 0, 80, 30, 15, 15);
 
	btn.document = nouveau;				
	// passage de nouveau dans les propriétés du MovieClip
	btn.x = 10;
	btn.y = 10;			
	addChild(btn);
	btn.addEventListener(MouseEvent.CLICK, enregistrer);
	// surveillance du clic

Ce clip devra être créé dans la fonction qui a fabriqué le document, pour que sa propriété “document” puisse recevoir le nouveau fichier zip à sauvegarder (fichier qui est en réalité notre fameux fichier word);

CODE COMPLET DU TUTORIEL

Voici le code complet de ce tutoriel. Comme vous le constatez, il est très court. Il y a plus bas une version encore plus courte, sans les tests d'affichage.

package src 
{
	import flash.display.*;
	import flash.events.*;
	import flash.net.*;
	import flash.utils.ByteArray;
	import nochump.util.zip.*;
	/**
	 * Écrire dans un fichier Word sous Flash
	 */
	public class Word2Flash extends Sprite
	{ 
		public function Word2Flash()
		{
			var chargeur:URLLoader = new URLLoader(new URLRequest("src/docs/modele.docx"));
			chargeur.dataFormat = URLLoaderDataFormat.BINARY;
			chargeur.addEventListener(Event.COMPLETE, surChargementModele);
		}
		public function surChargementModele(e:Event):void
		{		
			var paquet:ZipFile = new ZipFile(e.target.data);
			var nbParties:int = paquet.size; 
			trace("Taille du fichier chargé :",URLLoader(e.target).bytesTotal," - Nombre de parties :", nbParties);
			for(var i:int = 0; i < nbParties; i++) {
				var entree:ZipEntry = paquet.entries[i];
				trace(i + 1, "-", entree.name, "- Taille : réelle =", entree.size, "compressée =", entree.compressedSize);  
			}			
			creerNouveauDoc(paquet);
		}
		public function creerNouveauDoc(paquet:ZipFile):void 
		{
			var espace:String="http://schemas.openxmlformats.org/wordprocessingml/2006/main";
			var w:Namespace = new Namespace("w", espace);
 
			var modif:XML = <w:document xmlns:w={w.uri}>
			  <w:body>
				<w:p><w:r><w:t>Bonjour, le monde !</w:t></w:r></w:p>
				<w:p><w:r><w:t>Ceci est le second paragraphe...</w:t></w:r></w:p>
				<w:p><w:r><w:t>Et ceci est la fin.</w:t></w:r></w:p>
			  </w:body>
			</w:document>
 
			var ba:ByteArray = new ByteArray;
			ba.writeMultiByte(modif, "UTF-8");
 
			var nouveau:ZipOutput = new ZipOutput();
			for (var i:int; i < paquet.size; i++) {
				var entree:ZipEntry = paquet.entries[i];
				nouveau.putNextEntry(new ZipEntry(entree.name));
				if (entree.name != "word/document.xml") {
					nouveau.write(paquet.getInput(entree));
				} else {
					nouveau.write(ba);
				}
				nouveau.closeEntry();
			}
			nouveau.finish();
 
			var btn:MovieClip = new MovieClip;
			btn.graphics.lineStyle(1,0xff0000);
			btn.graphics.beginFill(0xccccff, 1);
			btn.graphics.drawRoundRect(0, 0, 80, 30, 15, 15);
			btn.document = nouveau;
			btn.x = 10;
			btn.y = 10;			
			addChild(btn);
			btn.addEventListener(MouseEvent.CLICK, enregistrer);
		}
 
		private	function enregistrer(e:MouseEvent):void 
		{
			var nouveau:ZipOutput = MovieClip(e.target).document;
			var fr:FileReference = new FileReference
			fr.save(nouveau.byteArray);
		}	
	}
}

Code allégé

package src 
{
	import flash.display.*;
	import flash.events.*;
	import flash.net.*;
	import flash.utils.ByteArray;
	import nochump.util.zip.*;
	public class Word2Flash extends Sprite
	{ 
		public function Word2Flash()
		{
			var chargeur:URLLoader = new URLLoader(new URLRequest("src/docs/modele.docx"));
			chargeur.dataFormat = URLLoaderDataFormat.BINARY;
			chargeur.addEventListener(Event.COMPLETE, surChargementModele);
		}
		public function surChargementModele(e:Event):void
		{		
			var paquet:ZipFile = new ZipFile(e.target.data);		
			creerNouveauDoc(paquet);
		}
		public function creerNouveauDoc(paquet:ZipFile):void 
		{
			var espace:String="http://schemas.openxmlformats.org/wordprocessingml/2006/main";
			var w:Namespace = new Namespace("w", espace);
			var modif:XML = <w:document xmlns:w={w.uri}>
			  <w:body>
				<w:p><w:r><w:t>Bonjour, le monde !</w:t></w:r></w:p>
				<w:p><w:r><w:t>Ceci est le second paragraphe...</w:t></w:r></w:p>
				<w:p><w:r><w:t>Et ceci est la fin.</w:t></w:r></w:p>
			  </w:body>
			</w:document>
 
			var ba:ByteArray = new ByteArray;
			ba.writeMultiByte(modif, "UTF-8");
 
			var nouveau:ZipOutput = new ZipOutput();
			for (var i:int; i < paquet.size; i++) {
				var entree:ZipEntry = paquet.entries[i];
				nouveau.putNextEntry(new ZipEntry(entree.name));
				if (entree.name != "word/document.xml") {
					nouveau.write(paquet.getInput(entree));
				} else {
					nouveau.write(ba);
				}
				nouveau.closeEntry();
			}
			nouveau.finish();
 
			var btn:MovieClip = new MovieClip;
			btn.graphics.lineStyle(1,0xff0000);
			btn.graphics.beginFill(0xccccff, 1);
			btn.graphics.drawRoundRect(0, 0, 80, 30, 15, 15);
			btn.document = nouveau;
			btn.x = 10;
			btn.y = 10;			
			addChild(btn);
			btn.addEventListener(MouseEvent.CLICK, enregistrer);
		}
 
		private	function enregistrer(e:MouseEvent):void 
		{
			var nouveau:ZipOutput = MovieClip(e.target).document;
			var fr:FileReference = new FileReference
			fr.save(nouveau.byteArray);
		}	
	}
}

Et après ? Et Excel ? Et PowerPoint ?

J'entends déjà la (ou les) question suivante : Et si je ne veux pas créer un fichier mais ajouter un peu de texte à un fichier existant ? Eh bien, c'est possible, évidemment. Il faudra :

  1. charger le contenu du document et éventuellement l'afficher
  2. chercher le dernier paragraphe ou le paragraphe après lequel on veut écrire
  3. ajouter les nouveaux paragraphes ou en supprimer…
  4. écraser l'ancien fichier en enregistrant le nouveau avec le même nom et au même endroit.

Mais vous avez là tous les éléments pour y parvenir sans peine.

Pour Excel ou PowerPoint, les manipulations seront à peine plus complexes. La même technique peut bien entendu être utilisée.
Mais l'idéal est de créer une petite classe usine à fabriquer du document OpenXML, en y conservant tout ce qui est commun aux fichiers zippés, et d'ajouter quelques fonctions pour créer ou lire des documents spécifiques aux différents formats.
Avant cela, un tour sur les sites parlant d'OpenXML est indispensable. De nombreuses vidéos, des tutoriels (orientés VB.net, C# ou PHP, mais adaptables) montrent au pas à pas le fonctionnement de ce format.
Il y a quelques liens ci-dessous.

Liens sur le composant

Liens sur le format .docx et OpenXML

  1. Excellent, pédagogique, obligatoire, en anglais hélas : http://openxmldeveloper.org/articles/1970.aspx. Téléchargez sans hésiter ce pdf de 128 pages plein d'exemples que vous pourrez tester directement dans Flash, maintenant !
  2. Simple, pratique, en français : http://grandfather.developpez.com/articles/openxml/structure/. A la fin de ce tutoriel, un lien vers tout ce qu'il faut pour créer un framework de lecture de fichiers OpenXML en php. Avec les outils que nous venons de voir, ce framework peut aussi être évidemment créé en ActionScript…

Sommaire du composant utilisé (tiré de l'ASDOC, non traduit)

ZipEntry

Propriétés :

comment 	String		Gets the extra data. 
compressedSize  int		Gets the size of the compressed data.
crc 		uint		Gets the crc of the uncompressed data.
extra		ByteArray	Gets the extra data.
method 		int		Gets the compression method.
name 		String		[read-only] Returns the entry name.
size 		int		Gets the size of the uncompressed data. 
time 		Number		Gets the time of last modification of the entry. 
	

Méthodes :

ZipEntry	(name:String)	Constructor -	Creates a zip entry with the given name
isDirectory	():Boolean	Gets true, if the entry is a directory 
toString	():String	Gets the string representation of this ZipEntry. 

ZipFile

Propriétés :

entries		Array		[read-only] Returns an array of all Zip entries in this Zip file.
size		uint		[read-only] Returns the number of entries in this zip file. 

Méthodes :

ZipFile		(data:IDataInput) 	Constructor - Opens a Zip file reading the given data.
getEntry	(name:String):ZipEntry 	Searches for a zip entry in this archive with the given name.
getInput	(entry:ZipEntry):ByteArray	Creates a byte array reading the given zip entry as uncompressed data. 

ZipOutput

Propriétés :

byteArray	ByteArray	[read-only] Returns the byte array of the finished zip
size		uint		[read-only] Returns the number of entries in this zip file 

Méthodes :

ZipOutput	()		Constructor  
closeEntry	():void  
finish		():void
putNextEntry	(e:ZipEntry):void
write		(b:ByteArray):void