Un'introduzione informale a DOCX
Pubblicato: 2022-03-11Con circa un miliardo di persone che utilizzano Microsoft Office, il formato DOCX è lo standard de facto più diffuso per lo scambio di file di documenti tra uffici. Il suo concorrente più vicino, il formato ODT, è supportato solo da Open/LibreOffice e da alcuni prodotti open source, il che lo rende tutt'altro che standard. Il formato PDF non è un concorrente perché i PDF non possono essere modificati e non contengono una struttura completa del documento, quindi possono accettare solo modifiche locali limitate come filigrane, firme e simili. Questo è il motivo per cui la maggior parte dei documenti aziendali viene creata nel formato DOCX; non c'è una buona alternativa per sostituirlo.
Sebbene DOCX sia un formato complesso, potresti volerlo analizzare manualmente per attività più semplici come l'indicizzazione, la conversione in TXT e apportare altre piccole modifiche. Vorrei darvi informazioni sufficienti sugli interni DOCX in modo da non dover fare riferimento alle specifiche ECMA, un enorme manuale di 5.000 pagine.
Il modo migliore per comprendere il formato è creare un semplice documento di una parola con MSWord e osservare come la modifica del documento cambia l'XML sottostante. Affronterai alcuni casi in cui il DOCX non si formatta correttamente in MS Word e non sai perché, o ti imbatterai in casi in cui non è evidente come generare la formattazione desiderata. Vedere e capire esattamente cosa sta succedendo nell'XML aiuterà questo.
Ho lavorato per circa un anno su un editor DOCX collaborativo, CollabOffice, e voglio condividere alcune di queste conoscenze con la comunità degli sviluppatori. In questo articolo spiegherò la struttura del file DOCX, riassumendo le informazioni sparse su Internet. Questo articolo è un intermediario tra l'enorme e complessa specifica ECMA e i semplici tutorial su Internet attualmente disponibili. Puoi trovare i file che accompagnano questo articolo nel progetto toptal-docx sul mio account github.
Un semplice file DOCX
Un file DOCX è un archivio ZIP di file XML. Se crei un nuovo documento di Microsoft Word vuoto, scrivi una singola parola "Test" all'interno e decomprimi il contenuto, vedrai la seguente struttura di file:
Anche se abbiamo creato un documento semplice, il processo di salvataggio in Microsoft Word ha generato temi predefiniti, proprietà del documento, tabelle dei caratteri e così via, in formato XML.
Per iniziare, rimuoviamo le cose inutilizzate e concentriamoci su document.xml , che contiene i principali elementi di testo. Quando elimini un file, assicurati di aver eliminato tutti i riferimenti di relazione ad esso da altri file xml. Ecco un esempio di code-diff su come ho cancellato le dipendenze da app.xml e core.xml. Se hai riferimenti irrisolti/mancanti, MSWord considererà il file danneggiato.
Ecco la struttura del nostro documento DOCX semplificato e minimale (ed ecco il progetto su github):
Analizziamolo per file da qui, dall'alto:
_rels/.rels
Questo definisce il riferimento che dice a MS Word dove cercare il contenuto del documento. In questo caso, fa riferimento a 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
Questo file definisce i riferimenti alle risorse, come le immagini, incorporate nel contenuto del documento. Il nostro semplice documento non ha risorse incorporate, quindi il tag di relazione è vuoto:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> </Relationships>[Tipi_di_contenuto].xml
[Content_Types].xml contiene informazioni sui tipi di media all'interno del documento. Dal momento che abbiamo solo contenuto di testo, è piuttosto semplice:
<?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>documento.xml
Infine, ecco l'XML principale con il contenuto testuale del documento. Ho rimosso alcune delle dichiarazioni dello spazio dei nomi per chiarezza, ma puoi trovare la versione completa del file nel progetto github. In quel file scoprirai che alcuni dei riferimenti allo spazio dei nomi nel documento non sono utilizzati, ma non dovresti eliminarli perché MS Word ne ha bisogno.
Ecco il nostro esempio semplificato:
<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> Il nodo principale <w:document> rappresenta il documento stesso, <w:body> contiene paragrafi e all'interno di <w:body> ci sono le dimensioni della pagina definite da <w:sectPr> .
<w:rsidR> è un attributo che puoi ignorare; è usato dagli interni di MS Word.
Diamo un'occhiata a un documento più complesso con tre paragrafi. Ho evidenziato l'XML con gli stessi colori sullo screenshot di Microsoft Word, quindi puoi vedere la correlazione:
<w:pw:rsidR="0081206C" w:rsidRDefault="00E10CAE"> <w:r> <w:t xml:space="preserve">Questo è il nostro primo paragrafo di esempio. L'impostazione predefinita è allineata a sinistra e ora vorrei presentarvi</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>qualche grassetto</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"> testo</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">e cambia anche il</w:t> </w:r> <w:rw:rsidRPr="00E10CAE"> <w:rPr><w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> </w:rPr> <w:t>stile carattere</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>su 'Impatto'.</w:t></w:r> </w:p> <w:pw:rsidR="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Questo è un nuovo paragrafo.</w:t> </w:r></w:p > <w:pw:rsidR="00E10CAE" w:rsidRPr="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Questo è un paragrafo in più, un po' più lungo.</w:t> </w:r> </w:p>
Struttura del paragrafo
Un documento semplice è costituito da paragrafi, un paragrafo è costituito da sequenze (una serie di testo con lo stesso carattere, colore, ecc.) e le sequenze sono costituite da caratteri (come <w:t> ). I <w:t> possono contenere più caratteri e potrebbero essercene alcuni nella stessa sequenza.
Ancora una volta, possiamo ignorare <w:rsidR> .
Proprietà del testo
Le proprietà di base del testo sono font, dimensione, colore, stile e così via. Ci sono circa 40 tag che specificano l'aspetto del testo. Come puoi vedere nel nostro esempio di tre paragrafi, ogni corsa ha le sue proprietà all'interno di <w:rPr> , specificando <w:color> , <w:rFonts> e boldness <w:b> .
Una cosa importante da notare è che le proprietà fanno una distinzione tra i due gruppi di caratteri, scrittura normale e complessa (l'arabo, per esempio), e che le proprietà hanno un tag diverso a seconda del tipo di carattere che sta interessando.
La maggior parte dei normali tag di proprietà di script ha un tag di script complesso corrispondente con una "C" aggiunta che specifica che la proprietà è per script complessi. Ad esempio: <w:i> (corsivo) diventa <w:iCs> e il tag in grassetto per lo script normale, <w:b> , diventa <w:bCs> per lo script complesso.
Stili
C'è un'intera barra degli strumenti in Microsoft Word dedicata agli stili: normale, senza spaziatura, intestazione 1, intestazione 2, titolo e così via. Questi stili sono archiviati in /word/styles.xml (nota: nel primo passaggio del nostro semplice esempio, abbiamo rimosso questo XML da DOCX. Crea un nuovo DOCX per vederlo).
Dopo aver definito il testo come stile, troverai il riferimento a questo stile all'interno del tag delle proprietà del paragrafo, <w:pPr> . Ecco un esempio in cui ho definito il mio testo con lo stile Titolo 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> ed ecco lo stile stesso da 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> Il <w:style/w:rPr/w:b> specifica che il carattere è in grassetto e <w:style/w:rPr/w:color> indica il colore del carattere. <w:basedOn> indica a MSWord di utilizzare lo stile "Normale" per tutte le proprietà mancanti.
Eredità di proprietà
Le proprietà del testo vengono ereditate. Una corsa ha le proprie proprietà ( w:p/w:r/w:rPr/* ), ma eredita anche le proprietà del paragrafo ( w:r/w:pPr/* ), ed entrambi possono fare riferimento alle proprietà di stile dal file /word/styles.xml .
<w:r> <w:rPr> <w:rStyle w:val="DefaultParagraphFont"/> <w:sz w:val="16"/> </w:rPr> <w:tab/> </w:r> I paragrafi e le esecuzioni iniziano con le proprietà predefinite: w:styles/w:docDefaults/w:rPrDefault/* e w:styles/w:docDefaults/w:pPrDefault/* . Per ottenere il risultato finale delle proprietà di un personaggio dovresti:

- Usa le proprietà di esecuzione/paragrafo predefinite
- Aggiungi le proprietà dello stile di esecuzione/paragrafo
- Aggiungi le proprietà di esecuzione/paragrafo locali
- Aggiungi le proprietà della corsa dei risultati alle proprietà del paragrafo
Quando dico "aggiungi" B ad A, intendo scorrere tutte le proprietà di B e sovrascrivere tutte le proprietà di A, lasciando tutte le proprietà non intersecanti così come sono.
Un altro punto in cui possono trovarsi le proprietà predefinite è nel <w:style> con w:type="paragraph" e w:default="1" . Nota, che i caratteri stessi all'interno di una sequenza non hanno mai uno stile predefinito, quindi <w:style w:type="character" w:default="1"> in realtà non influisce sul testo.
1554402290400-dbb29eef3ba6035df7ad726dfc99b2af.png)
Attiva/disattiva proprietà
Alcune delle proprietà sono proprietà "toggle", come <w:b> (grassetto) o <w:i> (corsivo); questi attributi si comportano come un operatore XOR.
Ciò significa che se lo stile padre è in grassetto e una sequenza figlio è in grassetto, il risultato sarà un testo normale, non in grassetto.
Devi eseguire molti test e reverse engineering per gestire correttamente gli attributi di attivazione/disattivazione. Dai un'occhiata al paragrafo 17.7.3 della specifica Open XML ECMA-376 per ottenere le regole formali e dettagliate per le proprietà di commutazione/
Caratteri
I caratteri seguono le stesse regole comuni degli altri attributi di testo, ma i valori predefiniti delle proprietà dei caratteri sono specificati in un file del tema separato, referenziato in word/_rels/document.xml.rels in questo modo:
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/> Sulla base del riferimento sopra, il nome del carattere predefinito si troverà in word/theme/themes1.xml , all'interno di un <a:theme> , a:themeElements/a:fontScheme/a:majorFont o a:minorFont .
La dimensione del carattere predefinita è 10 a meno che non manchi il tag w:docDefaults/w:rPrDefault , quindi è la dimensione 11.
Allineamento del testo
L'allineamento del testo è specificato da un <w:jc> con quattro modalità w:val disponibili: "left" , "center" , "right" e "both" .
"left" è la modalità predefinita; il testo inizia a sinistra del rettangolo del paragrafo (solitamente la larghezza della pagina). (Questo paragrafo è allineato a sinistra, che è standard.)
La modalità "center" , prevedibilmente, centra tutti i caratteri all'interno della larghezza della pagina. (Ancora una volta, questo paragrafo esemplifica l'allineamento centrato.)
In modalità "right" , il testo del paragrafo è allineato al margine destro. (Nota come questo testo è allineato al lato destro.)
La modalità "both" inserisce una spaziatura extra tra le parole in modo che le righe si allarghino e occupino l'intera larghezza del paragrafo, ad eccezione dell'ultima riga che è allineata a sinistra. (Questo paragrafo ne è una dimostrazione.)
immagini
DOCX supporta due tipi di immagini: inline e floating.
Le immagini in linea vengono visualizzate all'interno di un paragrafo insieme agli altri caratteri, viene utilizzato <w:drawing> invece di utilizzare <w:t> (testo). Puoi trovare l'ID immagine con la seguente sintassi xpath:
w:drawing/wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip/@r:embed
L'ID immagine viene utilizzato per cercare il nome del file nel file word/_rels/document.xml.rels e dovrebbe puntare al file gif/jpeg all'interno della sottocartella word/media. (Vedi il file word/_rels/document.xml.rels del progetto github, dove puoi vedere l'ID immagine.)
Le immagini mobili vengono posizionate rispetto ai paragrafi con il testo che scorre attorno ad essi. (Ecco il documento di esempio del progetto github con un'immagine mobile.)
Le immagini mobili usano <wp:anchor> invece di <w:drawing> , quindi se elimini del testo all'interno di <w:p> , fai attenzione con gli ancoraggi se non vuoi che le immagini vengano rimosse.
Tabelle
I tag XML per le tabelle sono simili al markup delle tabelle HTML– corrisponde a <tr>, ecc.
<w:tbl> , la tabella stessa, ha le proprietà della tabella <w:tblPr> e ogni proprietà della colonna è presentata da <w:gridCol> all'interno di <w:tblGrid> . Le righe seguono una ad una come <w:tr> e ogni riga dovrebbe avere lo stesso numero di colonne specificato in <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 larghezza per le colonne della tabella può essere specificata nel <w:tblW> , ma se non lo si definisce MS Word utilizzerà i suoi algoritmi interni per trovare la larghezza ottimale delle colonne per la dimensione minima effettiva della tabella.
Unità
Molti attributi XML all'interno di DOCX specificano dimensioni o distanze. Sebbene siano numeri interi all'interno dell'XML, hanno tutti unità diverse, quindi è necessaria una conversione. L'argomento è complicato, quindi consiglierei questo articolo di Lars Corneliussen sulle unità nei file DOCX. La tabella che presenta è utile, anche se con un piccolo errore di stampa: i pollici dovrebbero essere pt/72, non pt*72.
Ecco un cheat sheet:
| CONVERSIONI DI UNITÀ XML DOCX COMUNI | ||||||
| 20 di punto | Punti dx/20 | Pollici pt/72 | Centimetri in*2,54 | Dimensione del carattere dimezzata punto/144 | EMU in*914400 | |
| Esempio | 11906 | 595.3 | 8,27… | 21.00086… | 4.135 | 7562088 |
| Tag che usano questo | pgSz/pgMar/w:spaziatura | w:tg | wp:estensione, a:est | |||
Suggerimenti per l'implementazione di un layouter
Se vuoi convertire un file DOCX (ad esempio in PDF), disegnarlo su tela o contare il numero di pagine, dovrai implementare un layouter. Un layouter è un algoritmo per calcolare le posizioni dei caratteri da un file DOCX.
Questo è un compito complesso se hai bisogno di un rendering fedele al 100%. La quantità di tempo necessaria per implementare un buon layouter è misurata in anni uomo, ma se ne serve solo uno semplice e limitato, può essere fatto in tempi relativamente brevi.
Un layouter riempie un rettangolo principale, che di solito è un rettangolo della pagina. Aggiunge parole da una corsa una per una. Quando la linea corrente va in overflow, ne inizia una nuova. Se il paragrafo è troppo alto per il rettangolo principale, viene spostato alla pagina successiva.
Ecco alcune cose importanti da tenere a mente se si decide di implementare un layouter:
- Il layouter dovrebbe occuparsi dell'allineamento del testo e del testo fluttuante sulle immagini
- Dovrebbe essere in grado di gestire oggetti nidificati, come tabelle nidificate
- Se vuoi fornire un supporto completo per tali immagini, dovrai implementare un layouter con almeno due passaggi, il primo passaggio raccoglie le posizioni delle immagini fluttuanti e il secondo riempie lo spazio vuoto con caratteri di testo.
- Fai attenzione ai rientri e alle spaziature. Ogni paragrafo ha spazi prima e dopo, e questi numeri sono specificati dal tag
w:spacing. La spaziatura verticale è specificata dai tagw:afterew:before. Si noti che l'interlinea è specificata daw:line, ma questa non è la dimensione della riga come ci si potrebbe aspettare. Per ottenere la dimensione della linea, prendi l'altezza del carattere corrente, moltiplica perw:linee dividi per 12. - I file DOCX non contengono informazioni sull'impaginazione. Non troverai il numero di pagine nel documento a meno che non calcoli lo spazio necessario per ogni riga per accertare il numero di pagine. Se hai bisogno di trovare le coordinate esatte di ogni carattere sulla pagina, assicurati di prendere in considerazione tutte le spaziature, le rientranze e le dimensioni.
- Se si implementa un layouter DOCX completo che gestisce le tabelle, notare i casi speciali in cui le tabelle si estendono su più pagine. Una cella che causa un overflow della pagina ha effetto anche su altre celle.
- La creazione di un algoritmo ottimale per calcolare la larghezza delle colonne di una tabella è un problema matematico impegnativo e gli elaboratori di testi e i layouter di solito utilizzano alcune implementazioni non ottimali. Propongo di utilizzare l'algoritmo della documentazione della tabella HTML del W3C come prima approssimazione. Non ho trovato una descrizione dell'algoritmo utilizzato da MS Word e Microsoft ha perfezionato l'algoritmo nel tempo in modo che versioni diverse di Word possano disporre le tabelle in modo leggermente diverso.
Se qualcosa non è chiaro: decodifica l'XML!
Quando non è ovvio come funziona questo o quel tag XML all'interno di MS Word, ci sono due approcci principali per capirlo:
Crea il contenuto desiderato passo dopo passo. Inizia con un semplice file docx. Salva ogni passaggio nel proprio file, come in
1.docx,2.docx, per esempio. Decomprimi ciascuno di essi e utilizza uno strumento di differenza visiva per il confronto delle cartelle per vedere quali tag vengono visualizzati dopo le modifiche. (Per un'opzione commerciale, prova Araxis Merge, o per un'opzione gratuita, WinMerge.)Se generi un file DOCX che non piace a MS Word, lavora al contrario. Semplifica il tuo XML passo dopo passo. Ad un certo punto imparerai quale modifica MS Word è stata trovata errata.
DOCX è piuttosto complesso, vero?
È complesso e la licenza di Microsoft vieta l'uso di MS Word sul lato server per l'elaborazione di DOCX: questo è piuttosto standard per i prodotti commerciali. Microsoft ha, tuttavia, fornito il file XSLT per gestire la maggior parte dei tag DOCX, ma non ti darà il 100 percento o addirittura il 99 percento di fedeltà. Processi come il ritorno a capo del testo sulle immagini non sono supportati, ma sarai in grado di supportare la maggior parte dei documenti. (Se non hai bisogno di complessità, considera l'utilizzo di Markdown come alternativa.)
Se hai un budget sufficiente (non esiste un motore di rendering DOCX gratuito), potresti voler utilizzare prodotti commerciali come Aspose o docx4j. La soluzione gratuita più popolare è LibreOffice per la conversione tra DOCX e altri formati, incluso PDF. Sfortunatamente, LibreOffice contiene molti piccoli bug durante la conversione e, poiché è un prodotto C++ sofisticato e open source, è lento e difficile risolvere i problemi di fedeltà.
In alternativa, se ritieni che il layout DOCX sia troppo complicato per essere implementato da solo, puoi anche convertirlo in HTML e utilizzare un browser per il rendering. Puoi anche considerare uno degli sviluppatori XML freelance di Toptal.
Risorse DOCX per ulteriori letture
- Specifica ECMA DOCX
- Libreria OpenXML per la manipolazione DOCX da C#. Non contiene informazioni sul layout o sul rendering del codice, ma offre una gerarchia di classi corrispondente a ogni possibile nodo XML in DOCX.
- Puoi sempre cercare o chiedere su StackOverflow con parole chiave come docx4j, OpenXML e docx; ci sono persone nella comunità che sono informate.
