Une introduction informelle à DOCX

Publié: 2022-03-11

Avec environ un milliard de personnes utilisant Microsoft Office, le format DOCX est la norme de facto la plus populaire pour l'échange de fichiers de documents entre les bureaux. Son concurrent le plus proche - le format ODT - n'est supporté que par Open/LibreOffice et certains produits open source, ce qui le rend loin d'être standard. Le format PDF n'est pas un concurrent car les PDF ne peuvent pas être modifiés et ils ne contiennent pas une structure de document complète, de sorte qu'ils ne peuvent prendre que des modifications locales limitées comme les filigranes, les signatures, etc. C'est pourquoi la plupart des documents commerciaux sont créés au format DOCX ; il n'y a pas de bonne alternative pour le remplacer.

Bien que DOCX soit un format complexe, vous souhaiterez peut-être l'analyser manuellement pour des tâches plus simples telles que l'indexation, la conversion en TXT et d'autres petites modifications. J'aimerais vous donner suffisamment d'informations sur les composants internes de DOCX pour que vous n'ayez pas à vous référer aux spécifications ECMA, un énorme manuel de 5 000 pages.

La meilleure façon de comprendre le format est de créer un simple document d'un mot avec MSWord et d'observer comment l'édition du document modifie le XML sous-jacent. Vous serez confronté à certains cas où le DOCX ne se formate pas correctement dans MS Word et vous ne savez pas pourquoi, ou vous rencontrez des cas où il n'est pas évident de générer le formatage souhaité. Voir et comprendre exactement ce qui se passe dans le XML aidera cela.

J'ai travaillé pendant environ un an sur un éditeur DOCX collaboratif, CollabOffice, et je souhaite partager certaines de ces connaissances avec la communauté des développeurs. Dans cet article, je vais expliquer la structure du fichier DOCX, en résumant les informations dispersées sur Internet. Cet article est un intermédiaire entre l'énorme et complexe spécification ECMA et les simples tutoriels Internet actuellement disponibles. Vous pouvez trouver les fichiers qui accompagnent cet article dans le projet toptal-docx sur mon compte github.

Un fichier DOCX simple

Un fichier DOCX est une archive ZIP de fichiers XML. Si vous créez un nouveau document Microsoft Word vide, écrivez un seul mot "Test" à l'intérieur et décompressez son contenu, vous verrez la structure de fichier suivante :

Notre toute nouvelle structure de test DOCX.

Même si nous avons créé un document simple, le processus d'enregistrement dans Microsoft Word a généré des thèmes par défaut, des propriétés de document, des tables de polices, etc., au format XML.

Tous les fichiers à l'intérieur d'un DOCX sont des fichiers XML, même ceux avec l'extension ".rels".
Tweeter

Pour commencer, supprimons les éléments inutilisés et concentrons-nous sur document.xml , qui contient les principaux éléments de texte. Lorsque vous supprimez un fichier, assurez-vous d'avoir supprimé toutes les références de relation à celui-ci des autres fichiers xml. Voici un exemple de code-diff sur la façon dont j'ai effacé les dépendances à app.xml et core.xml. Si vous avez des références non résolues/manquantes, MSWord considérera le fichier comme cassé.

Voici la structure de notre document DOCX simplifié et minimal (et voici le projet sur github) :

Notre structure DOCX simplifiée.

Décomposons-le par fichier à partir d'ici, à partir du haut :

_rels/.rels

Cela définit la référence qui indique à MS Word où rechercher le contenu du document. Dans ce cas, il fait référence à word/document.xml :

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/> </Relationships>

_rels/document.xml.rels

Ce fichier définit les références aux ressources, telles que les images, incorporées dans le contenu du document. Notre document simple n'a pas de ressources intégrées, donc la balise de relation est vide :

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> </Relationships>

[Content_Types].xml

[Content_Types].xml contient des informations sur les types de médias à l'intérieur du document. Comme nous n'avons que du texte, c'est assez simple :

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> <Default Extension="xml" ContentType="application/xml"/> <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/> </Types>

document.xml

Enfin, voici le XML principal avec le contenu textuel du document. J'ai supprimé certaines déclarations d'espace de noms pour plus de clarté, mais vous pouvez trouver la version complète du fichier dans le projet github. Dans ce fichier, vous constaterez que certaines des références d'espace de noms dans le document sont inutilisées, mais vous ne devez pas les supprimer car MS Word en a besoin.

Voici notre exemple simplifié :

 <w:document> <w:body> <w:pw:rsidR="005F670F" w:rsidRDefault="005F79F5"> <w:r><w:t>Test</w:t></w:r> </w:p> <w:sectPr w:rsidR="005F670F"> <w:pgSz w:w="12240" w:h="15840"/> <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/> <w:cols w:space="720"/> <w:docGrid w:linePitch="360"/> </w:sectPr> </w:body> </w:document>

Le nœud principal <w:document> représente le document lui-même, <w:body> contient des paragraphes et imbriqués dans <w:body> se trouvent les dimensions de page définies par <w:sectPr> .

<w:rsidR> est un attribut que vous pouvez ignorer ; il est utilisé par les internes de MS Word.

Examinons un document plus complexe avec trois paragraphes. J'ai mis en surbrillance le XML avec les mêmes couleurs sur la capture d'écran de Microsoft Word, afin que vous puissiez voir la corrélation :

Exemple de paragraphe complexe avec style.

<w:pw:rsidR="0081206C" w:rsidRDefault="00E10CAE"> <w:r> <w:t xml:space="preserve">Voici notre premier paragraphe d'exemple. Sa valeur par défaut est alignée à gauche, et maintenant j'aimerais vous présenter</w:t> </w:r> <w:r> <w:rPr> <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:color w:val="000000"/> </w:rPr> <w:t>un peu de gras</w:t> </w:r> <w:r> <w:rPr> <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:b/> <w:color w:val="000000"/> </w:rPr> <w:t xml:space="preserve"> texte</w:t> </w:r> <w:r> <w:rPr> <w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/> <w:color w:val="000000"/> </w:rPr> <w:t xml:space="preserve">, </w:t> </w:r> <w:proofErr w:type="gramStart"/> <w:r> <w:t xml:space="preserve">et également modifier le</w:t> </w:r> <w:rw:rsidRPr="00E10CAE"> <w:rPr><w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> </w:rPr> <w:t>style de police</w:t> </w:r> <w:r> <w:rPr> <w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> </w:rPr> <w:t xml:space="preserve"> </w:t> </w:r> <w:r> <w:t>à 'Impact'.</w:t></w:r> </w:p> <w:pw:rsidR="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Ceci est un nouveau paragraphe.</w:t> </w:r></w:p > <w:pw:rsidR="00E10CAE" w:rsidRPr="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Ceci est un paragraphe de plus, un peu plus long.</w:t> </w:r> </w:p>

Structure des paragraphes

Un document simple se compose de paragraphes, un paragraphe se compose de séquences (une série de texte avec la même police, la même couleur, etc.) et les séquences sont constituées de caractères (tels que <w:t> ). Les balises <w:t> peuvent avoir plusieurs caractères à l'intérieur, et il peut y en avoir quelques-uns dans la même exécution.

Encore une fois, nous pouvons ignorer <w:rsidR> .

Propriétés du texte

Les propriétés de base du texte sont la police, la taille, la couleur, le style, etc. Il existe environ 40 balises qui spécifient l'apparence du texte. Comme vous pouvez le voir dans notre exemple de trois paragraphes, chaque exécution a ses propres propriétés à l'intérieur <w:rPr> , en spécifiant <w:color> , <w:rFonts> et boldness <w:b> .

Une chose importante à noter est que les propriétés font une distinction entre les deux groupes de caractères, script normal et complexe (arabe, par exemple), et que les propriétés ont une balise différente selon le type de caractère qu'elles affectent.

La plupart des balises de propriété de script normales ont une balise de script complexe correspondante avec un « C » ajouté spécifiant que la propriété est destinée aux scripts complexes. Par exemple : <w:i> (italique) devient <w:iCs> , et la balise en gras pour le script normal, <w:b> , devient <w:bCs> pour le script complexe.

modes

Il existe une barre d'outils complète dans Microsoft Word dédiée aux styles : normal, sans espacement, en-tête 1, en-tête 2, titre, etc. Ces styles sont stockés dans /word/styles.xml (remarque : dans la première étape de notre exemple simple, nous avons supprimé ce XML du DOCX. Créez un nouveau DOCX pour le voir).

Une fois que vous avez défini du texte en tant que style, vous trouverez une référence à ce style dans la balise de propriétés de paragraphe, <w:pPr> . Voici un exemple où j'ai défini mon texte avec le style Titre 1 :

 <w:p> <w:pPr> <w:pStyle w:val="Heading1"/> </w:pPr> <w:r> <w:t>My heading 1</w:t> </w:r> </w:p>

et voici le style lui-même de styles.xml :

 <w:style w:type="paragraph" w:style> <w:name w:val="heading 1"/> <w:basedOn w:val="Normal"/> <w:next w:val="Normal"/> <w:link w:val="Heading1Char"/> <w:uiPriority w:val="9"/> <w:qFormat/> <w:rsid w:val="002F7F18"/> <w:pPr> <w:keepNext/> <w:keepLines/> <w:spacing w:before="480" w:after="0"/> <w:outlineLvl w:val="0"/> </w:pPr> <w:rPr> <w:rFonts w:asciiTheme="majorHAnsi" w:eastAsiaTheme="majorEastAsia" w:hAnsiTheme="majorHAnsi" w:cstheme="majorBidi"/> <w:b/> <w:bCs/> <w:color w:val="365F91" w:themeColor="accent1" w:themeShade="BF"/> <w:sz w:val="28"/> <w:szCs w:val="28"/> </w:rPr> </w:style>

Le <w:style/w:rPr/w:b> spécifie que la police est en gras et <w:style/w:rPr/w:color> indique la couleur de la police. <w:basedOn> indique à MSWord d'utiliser le style "Normal" pour toutes les propriétés manquantes.

Héritage de propriété

Les propriétés de texte sont héritées. Une exécution a ses propres propriétés ( w:p/w:r/w:rPr/* ), mais elle hérite également des propriétés du paragraphe ( w:r/w:pPr/* ), et les deux peuvent référencer les propriétés de style du /word/styles.xml .

 <w:r> <w:rPr> <w:rStyle w:val="DefaultParagraphFont"/> <w:sz w:val="16"/> </w:rPr> <w:tab/> </w:r>

Les paragraphes et les séquences commencent par les propriétés par défaut : w:styles/w:docDefaults/w:rPrDefault/* et w:styles/w:docDefaults/w:pPrDefault/* . Pour obtenir le résultat final des propriétés d'un personnage, vous devez :

  1. Utiliser les propriétés de passage/paragraphe par défaut
  2. Ajouter les propriétés de style de passage/paragraphe
  3. Ajouter des propriétés de passage/paragraphe locales
  4. Ajouter les propriétés d'exécution des résultats sur les propriétés de paragraphe

Quand je dis "ajouter" B à A, je veux dire parcourir toutes les propriétés B et remplacer toutes les propriétés de A, en laissant toutes les propriétés non sécantes telles quelles.

Un autre endroit où les propriétés par défaut peuvent être situées est dans la <w:style> avec w:type="paragraph" et w:default="1" . Notez que les caractères eux-mêmes à l'intérieur d'une suite n'ont jamais de style par défaut, donc <w:style w:type="character" w:default="1"> n'affecte en fait aucun texte.

Tweeter

Les caractères d'une série peuvent hériter de son paragraphe et les deux peuvent hériter de styles.xml.

1554402290400-dbb29eef3ba6035df7ad726dfc99b2af.png)

Les caractères d'une série peuvent hériter de son paragraphe et les deux peuvent hériter de styles.xml.

Basculer les propriétés

Certaines des propriétés sont des propriétés « bascule », telles que <w:b> (gras) ou <w:i> (italique) ; ces attributs se comportent comme un opérateur XOR.

Cela signifie que si le style parent est en gras et qu'une séquence enfant est en gras, le résultat sera un texte normal et non gras.

Vous devez effectuer de nombreux tests et rétro-ingénierie pour gérer correctement les attributs de basculement. Consultez le paragraphe 17.7.3 de la spécification ECMA-376 Open XML pour obtenir les règles formelles et détaillées pour basculer les propriétés/

Les propriétés de basculement sont les plus complexes à gérer correctement pour un layouter.
Tweeter

Polices

Les polices suivent les mêmes règles communes que les autres attributs de texte, mais les valeurs par défaut des propriétés de police sont spécifiées dans un fichier de thème séparé, référencé sous word/_rels/document.xml.rels comme ceci :

 <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>

Sur la base de la référence ci-dessus, le nom de la police par défaut se trouvera dans word/theme/themes1.xml , à l'intérieur d'une <a:theme> , a:themeElements/a:fontScheme/a:majorFont ou a:minorFont balise.

La taille de police par défaut est 10 sauf si la w:docDefaults/w:rPrDefault est manquante, alors c'est la taille 11.

Alignement du texte

L'alignement du texte est spécifié par une <w:jc> avec quatre modes w:val disponibles : "left" , "center" , "right" et "both" .

"left" est le mode par défaut ; le texte commence à gauche du rectangle du paragraphe (généralement la largeur de la page). (Ce paragraphe est aligné à gauche, ce qui est standard.)

Le mode "center" , comme on pouvait s'y attendre, centre tous les caractères à l'intérieur de la largeur de la page. (Encore une fois, ce paragraphe illustre l'alignement centré.)

En mode "right" , le texte du paragraphe est aligné sur la marge de droite. (Remarquez comment ce texte est aligné sur le côté droit.)

Le mode "both" ajoute un espacement supplémentaire entre les mots afin que les lignes s'élargissent et occupent toute la largeur du paragraphe, à l'exception de la dernière ligne qui est alignée à gauche. (Ce paragraphe en est une démonstration.)

Images

DOCX prend en charge deux types d'images : en ligne et flottantes.

Les images en ligne apparaissent à l'intérieur d'un paragraphe avec les autres caractères, <w:drawing> est utilisé au lieu d'utiliser <w:t> (texte). Vous pouvez trouver l'ID d'image avec la syntaxe XPath suivante :

w:drawing/wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip/@r:embed

L'ID d'image est utilisé pour rechercher le nom de fichier dans le fichier word/_rels/document.xml.rels , et il doit pointer vers le fichier gif/jpeg dans le sous-dossier word/media. (Voir le fichier word/_rels/document.xml.rels du projet github, où vous pouvez voir l'ID de l'image.)

Les images flottantes sont placées par rapport aux paragraphes entourés de texte. (Voici l'exemple de document du projet github avec une image flottante.)

Les images flottantes utilisent <wp:anchor> au lieu de <w:drawing> , donc si vous supprimez du texte à l'intérieur de <w:p> , soyez prudent avec les ancres si vous ne voulez pas que les images soient supprimées.

Inline vs flottant.

Les options d'image de MS Word font référence à l'alignement d'image en tant que "mode d'habillage du texte".

les tables

Les balises XML pour les tableaux sont similaires au balisage de tableau HTML– est identique à <table>, correspond à <tr>, etc.

<w:tbl> , la table elle-même, a des propriétés de table <w:tblPr> , et chaque propriété de colonne est présentée par <w:gridCol> à l'intérieur de <w:tblGrid> . Les lignes suivent une par une en tant que balises <w:tr> et chaque ligne doit avoir le même nombre de colonnes que spécifié dans <w:tblGrid> :

 <w:tbl> <w:tblPr> <w:tblW w:w="5000" w:type="pct" /> </w:tblPr> <w:tblGrid><w:gridCol/><w:gridCol/></w:tblGrid> <w:tr> <w:tc><w:p><w:r><w:t>left</w:t></w:r></w:p></w:tc> <w:tc><w:p><w:r><w:t>right</w:t></w:r></w:p></w:tc> </w:tr> </w:tbl>

La largeur des colonnes de tableau peut être spécifiée dans la <w:tblW> , mais si vous ne la définissez pas, MS Word utilisera ses algorithmes internes pour trouver la largeur optimale des colonnes pour la plus petite taille de tableau effective.

Unités

De nombreux attributs XML dans DOCX spécifient des tailles ou des distances. Bien qu'il s'agisse d'entiers dans le XML, ils ont tous des unités différentes, une conversion est donc nécessaire. Le sujet est compliqué, je vous recommande donc cet article de Lars Corneliussen sur les unités dans les fichiers DOCX. Le tableau qu'il présente est utile, mais avec une petite faute d'impression : les pouces doivent être pt/72, et non pt*72.

Voici une feuille de triche :

CONVERSIONS COMMUNES D'UNITÉS DOCX XML
20e de point Points
dxa/20
Pouces
pt/72
Centimètres
en*2,54
Demi-taille de la police
pt/144
ÉMEU
dans*914400
Exemple 11906 595.3 8,27… 21.00086… 4 135 7562088
Balises utilisant ceci pgSz/pgMar/w : espacement w:sz wp:étendue, a:étendue

Conseils pour la mise en œuvre d'un layouter

Si vous souhaitez convertir un fichier DOCX (en PDF, par exemple), le dessiner sur un canevas ou compter le nombre de pages, vous devrez implémenter un layouter. Un layouter est un algorithme permettant de calculer la position des caractères à partir d'un fichier DOCX.

Il s'agit d'une tâche complexe si vous avez besoin d'un rendu fidèle à 100 %. Le temps nécessaire pour mettre en place un bon layouter se mesure en années-hommes, mais si vous n'en avez besoin que d'un simple et limité, cela peut se faire relativement rapidement.

Un layouter remplit un rectangle parent, qui est généralement un rectangle de la page. Il ajoute des mots d'une course un par un. Lorsque la ligne actuelle déborde, elle en commence une nouvelle. Si le paragraphe est trop haut pour le rectangle parent, il passe à la page suivante.

Voici quelques points importants à garder à l'esprit si vous décidez d'implémenter un layouter :

  • Le maquettiste doit prendre soin de l'alignement du texte et du texte flottant sur les images
  • Il doit être capable de gérer des objets imbriqués, tels que des tables imbriquées
  • Si vous souhaitez fournir une prise en charge complète de ces images, vous devrez implémenter un layouter avec au moins deux passes, la première étape collecte les positions des images flottantes et la seconde remplit l'espace vide avec des caractères de texte.
  • Faites attention aux indentations et aux espacements. Chaque paragraphe a un espacement avant et après, et ces nombres sont spécifiés par la balise w:spacing . L'espacement vertical est spécifié par les balises w:after et w:before . Notez que l'interligne est spécifié par w:line , mais ce n'est pas la taille de la ligne comme on pourrait s'y attendre. Pour obtenir la taille de la ligne, prenez la hauteur de police actuelle, multipliez par w:line et divisez par 12.
  • Les fichiers DOCX ne contiennent aucune information sur la pagination. Vous ne trouverez pas le nombre de pages dans le document à moins de calculer l'espace dont vous avez besoin pour chaque ligne pour déterminer le nombre de pages. Si vous avez besoin de trouver les coordonnées exactes de chaque caractère sur la page, assurez-vous de prendre en compte tous les espacements, indentations et tailles.
  • Si vous implémentez un modèle de mise en page DOCX complet qui gère les tableaux, notez les cas particuliers lorsque les tableaux s'étendent sur plusieurs pages. Une cellule qui provoque un débordement de page affecte également les autres cellules.
  • La création d'un algorithme optimal pour calculer la largeur des colonnes d'un tableau est un problème mathématique difficile et les traitements de texte et les mises en page utilisent généralement des implémentations sous-optimales. Je propose d'utiliser l'algorithme de la documentation des tables HTML du W3C comme première approximation. Je n'ai pas trouvé de description de l'algorithme utilisé par MS Word, et Microsoft a affiné l'algorithme au fil du temps, de sorte que différentes versions de Word peuvent disposer les tableaux de manière légèrement différente.

Si quelque chose n'est pas clair : procédez à la rétro-ingénierie du XML !

Lorsqu'il n'est pas évident de savoir comment telle ou telle balise XML fonctionne dans MS Word, il existe deux approches principales pour le comprendre :

  • Créez le contenu souhaité étape par étape. Commencez avec un simple fichier docx. Enregistrez chaque étape dans son propre fichier, comme dans 1.docx , 2.docx , par exemple. Décompressez chacun d'eux et utilisez un outil de comparaison visuel pour comparer les dossiers afin de voir quelles balises apparaissent après vos modifications. (Pour une option commerciale, essayez Araxis Merge, ou pour une option gratuite, WinMerge.)

  • Si vous générez un fichier DOCX que MS Word n'aime pas, revenez en arrière. Simplifiez votre XML étape par étape. À un moment donné, vous apprendrez quel changement MS Word a été trouvé incorrect.

DOCX est assez complexe, n'est-ce pas ?

C'est complexe et la licence de Microsoft interdit d'utiliser MS Word côté serveur pour le traitement de DOCX - c'est assez standard pour les produits commerciaux. Microsoft a cependant fourni le fichier XSLT pour gérer la plupart des balises DOCX, mais il ne vous donnera pas une fidélité de 100 % ou même de 99 %. Les processus tels que l'habillage du texte sur les images ne sont pas pris en charge, mais vous pourrez prendre en charge la majorité des documents. (Si vous n'avez pas besoin de complexité, envisagez d'utiliser Markdown comme alternative.)

Si vous disposez d'un budget suffisant (il n'y a pas de moteur de rendu DOCX gratuit), vous pouvez utiliser des produits commerciaux tels que Aspose ou docx4j. La solution gratuite la plus populaire est LibreOffice pour la conversion entre DOCX et d'autres formats, y compris PDF. Malheureusement, LibreOffice contient de nombreux petits bogues lors de la conversion, et comme il s'agit d'un produit C++ open-source sophistiqué, il est lent et difficile de résoudre les problèmes de fidélité.

Alternativement, si vous trouvez la mise en page DOCX trop compliquée à mettre en œuvre vous-même, vous pouvez également la convertir en HTML et utiliser un navigateur pour la rendre. Vous pouvez également envisager l'un des développeurs XML indépendants de Toptal.

Ressources DOCX pour aller plus loin

  • Spécification ECMA DOCX
  • Bibliothèque OpenXML pour la manipulation DOCX à partir de C#. Il ne contient pas d'informations sur la disposition ou le rendu du code, mais propose une hiérarchie de classes correspondant à chaque nœud XML possible dans DOCX.
  • Vous pouvez toujours rechercher ou demander sur stackoverflow avec des mots clés tels que docx4j, OpenXML et docx ; il y a des gens dans la communauté qui sont bien informés.