O introducere informală în DOCX
Publicat: 2022-03-11Cu aproximativ un miliard de oameni care folosesc Microsoft Office, formatul DOCX este cel mai popular standard de facto pentru schimbul de fișiere de documente între birouri. Cel mai apropiat concurent al său - formatul ODT - este acceptat doar de Open/LibreOffice și de unele produse open source, ceea ce îl face departe de standard. Formatul PDF nu este un concurent, deoarece PDF-urile nu pot fi editate și nu conțin o structură completă a documentului, astfel încât pot lua doar modificări locale limitate, cum ar fi filigrane, semnături și altele asemenea. Acesta este motivul pentru care majoritatea documentelor de afaceri sunt create în format DOCX; nu există o alternativă bună pentru a-l înlocui.
În timp ce DOCX este un format complex, poate doriți să îl analizați manual pentru sarcini mai simple, cum ar fi indexarea, conversia în TXT și efectuarea altor modificări mici. Aș dori să vă ofer suficiente informații despre elementele interne ale DOCX, astfel încât să nu fie nevoie să faceți referire la specificațiile ECMA, un manual masiv de 5.000 de pagini.
Cel mai bun mod de a înțelege formatul este să creați un document simplu de un cuvânt cu MSWord și să observați cum editarea documentului modifică XML-ul de bază. Te vei confrunta cu unele cazuri în care DOCX nu se formatează corect în MS Word și nu știi de ce, sau vei întâlni cazuri în care nu este evident cum să generezi formatarea dorită. Vederea și înțelegerea exactă a ceea ce se întâmplă în XML vă va ajuta.
Am lucrat aproximativ un an la un editor DOCX colaborativ, CollabOffice, și vreau să împărtășesc o parte din aceste cunoștințe comunității dezvoltatorilor. În acest articol voi explica structura fișierului DOCX, rezumând informațiile care sunt împrăștiate pe internet. Acest articol este un intermediar între specificația uriașă și complexă ECMA și tutorialele simple pe internet disponibile în prezent. Puteți găsi fișierele care însoțesc acest articol în proiectul toptal-docx pe contul meu github.
Un fișier DOCX simplu
Un fișier DOCX este o arhivă ZIP de fișiere XML. Dacă creați un document Microsoft Word nou, gol, scrieți un singur cuvânt „Test” în interior și dezarhivați conținutul acestuia, veți vedea următoarea structură de fișiere:
Chiar dacă am creat un document simplu, procesul de salvare în Microsoft Word a generat teme implicite, proprietăți ale documentului, tabele cu fonturi și așa mai departe, în format XML.
Pentru a începe, să eliminăm lucrurile nefolosite și să ne concentrăm pe document.xml , care conține elementele de text principale. Când ștergeți un fișier, asigurați-vă că ați șters toate referințele la el din alte fișiere xml. Iată un exemplu de diferență de cod despre cum am șters dependențele de la app.xml și core.xml. Dacă aveți referințe nerezolvate/lipsă, MSWord va considera fișierul rupt.
Iată structura documentului DOCX simplificat și minimal (și aici este proiectul pe github):
Să-l defalcăm pe fișier de aici, de sus:
_rels/.rels
Aceasta definește referința care îi spune lui MS Word unde să caute conținutul documentului. În acest caz, se referă la 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
Acest fișier definește referințe la resurse, cum ar fi imagini, încorporate în conținutul documentului. Documentul nostru simplu nu are resurse încorporate, așa că eticheta de relație este goală:
<?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 conține informații despre tipurile de suporturi din document. Deoarece avem doar conținut text, este destul de simplu:
<?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
În cele din urmă, aici este principalul XML cu conținutul text al documentului. Am eliminat câteva dintre declarațiile de spațiu de nume pentru claritate, dar puteți găsi versiunea completă a fișierului în proiectul github. În acel fișier veți descoperi că unele dintre referințele la spațiul de nume din document sunt neutilizate, dar nu ar trebui să le ștergeți deoarece MS Word are nevoie de ele.
Iată exemplul nostru simplificat:
<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> Nodul principal <w:document> reprezintă documentul în sine, <w:body> conține paragrafe și imbricate în <w:body> sunt dimensiunile paginii definite de <w:sectPr> .
<w:rsidR> este un atribut pe care îl puteți ignora; este folosit de membrii MS Word.
Să aruncăm o privire la un document mai complex cu trei paragrafe. Am evidențiat XML-ul cu aceleași culori pe captura de ecran din Microsoft Word, astfel încât să puteți vedea corelația:
<w:pw:rsidR="0081206C" w:rsidRDefault="00E10CAE"> <w:r> <w:t xml:space="preserve">Acesta este primul paragraf exemplu al nostru. Este implicit aliniat la stânga, iar acum aș dori să vă prezint</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>unele îndrăznețe</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"> text</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">și, de asemenea, modificați</w:t> </w:r> <w:rw:rsidRPr="00E10CAE"> <w:rPr><w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> </w:rPr> <w:t>stilul fontului</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>la „Impact”.</w:t></w:r> </w:p> <w:pw:rsidR="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Acesta este un paragraf nou.</w:t> </w:r></w:p > <w:pw:rsidR="00E10CAE" w:rsidRPr="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>Acesta este încă un paragraf, puțin mai lung.</w:t> </w:r> </w:p>
Structura paragrafului
Un document simplu este format din paragrafe, un paragraf este format din execuții (o serie de text cu același font, culoare etc.), iar execuțiile constau din caractere (cum ar fi <w:t> ). Etichetele <w:t> pot avea mai multe caractere în interior și pot fi câteva în aceeași rulare.
Din nou, putem ignora <w:rsidR> .
Proprietățile textului
Proprietățile de bază ale textului sunt fontul, dimensiunea, culoarea, stilul și așa mai departe. Există aproximativ 40 de etichete care specifică aspectul textului. După cum puteți vedea în exemplul nostru cu trei paragrafe, fiecare rulare are propriile proprietăți în interiorul <w:rPr> , specificând <w:color> , <w:rFonts> și îndrăzneală <w:b> .
Un lucru important de remarcat este că proprietățile fac o distincție între cele două grupuri de caractere, script normal și complex (araba, de exemplu), și că proprietățile au o etichetă diferită în funcție de tipul de caracter pe care îl afectează.
Majoritatea etichetelor de proprietate de script obișnuite au o etichetă de script complex care se potrivește cu un „C” adăugat care specifică că proprietatea este pentru scripturi complexe. De exemplu: <w:i> (italic) devine <w:iCs> , iar eticheta aldine pentru scriptul normal, <w:b> , devine <w:bCs> pentru scriptul complex.
Stiluri
Există o întreagă bară de instrumente în Microsoft Word dedicată stilurilor: normal, fără spațiere, titlul 1, titlul 2, titlul și așa mai departe. Aceste stiluri sunt stocate în /word/styles.xml (notă: în primul pas din exemplul nostru simplu, am eliminat acest XML din DOCX. Faceți un nou DOCX pentru a vedea asta).
Odată ce ați definit textul ca stil, veți găsi referință la acest stil în interiorul etichetei de proprietăți de paragraf, <w:pPr> . Iată un exemplu în care mi-am definit textul cu stilul Titlul 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> și aici este stilul în sine de la 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> Căile xpath <w:style/w:rPr/w:b> specifică faptul că fontul este aldine, iar <w:style/w:rPr/w:color> indică culoarea fontului. <w:basedOn> instruiește MSWord să folosească stilul „Normal” pentru orice proprietăți lipsă.
Moștenirea proprietății
Proprietățile textului sunt moștenite. O rulare are propriile sale proprietăți ( w:p/w:r/w:rPr/* ), dar moștenește și proprietăți din paragraf ( w:r/w:pPr/* ), și ambele pot face referire la proprietăți de stil din /word/styles.xml .
<w:r> <w:rPr> <w:rStyle w:val="DefaultParagraphFont"/> <w:sz w:val="16"/> </w:rPr> <w:tab/> </w:r> Paragrafele și rulările încep cu proprietățile implicite: w:styles/w:docDefaults/w:rPrDefault/* și w:styles/w:docDefaults/w:pPrDefault/* . Pentru a obține rezultatul final al proprietăților unui personaj ar trebui:

- Utilizați proprietățile implicite de rulare/paragraf
- Adăugați proprietăți ale stilului de rulare/paragraf
- Adăugați proprietăți locale de execuție/paragraf
- Adăugați proprietățile execuției rezultate peste proprietățile paragrafului
Când spun „adăugați” B la A, mă refer la iterare prin toate proprietățile B și suprascrieți toate proprietățile lui A, lăsând toate proprietățile care nu se intersectează așa cum sunt.
Încă un loc unde pot fi localizate proprietățile implicite este în eticheta <w:style> cu w:type="paragraph" și w:default="1" . Rețineți că caracterele însele dintr-o rulare nu au niciodată un stil implicit, așa că <w:style w:type="character" w:default="1"> nu afectează de fapt niciun text.
1554402290400-dbb29eef3ba6035df7ad726dfc99b2af.png)
Comutați proprietăți
Unele dintre proprietăți sunt proprietăți „de comutare”, cum ar fi <w:b> (bold) sau <w:i> (italic); aceste atribute se comportă ca un operator XOR.
Aceasta înseamnă că, dacă stilul părinte este aldin și o rulare secundară este aldine, rezultatul va fi un text obișnuit, fără aldine.
Trebuie să faceți multe teste și inginerie inversă pentru a gestiona corect atributele de comutare. Aruncă o privire la paragraful 17.7.3 din specificația ECMA-376 Open XML pentru a obține regulile formale și detaliate pentru proprietățile de comutare/
Fonturi
Fonturile urmează aceleași reguli comune ca și alte atribute de text, dar valorile implicite ale proprietăților fontului sunt specificate într-un fișier temă separat, la care se face referire sub word/_rels/document.xml.rels astfel:
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/> Pe baza referinței de mai sus, numele fontului implicit va fi găsit în word/theme/themes1.xml , în interiorul unei etichete <a:theme> , a:themeElements/a:fontScheme/a:majorFont sau a:minorFont .
Dimensiunea implicită a fontului este 10, cu excepția cazului în care w:docDefaults/w:rPrDefault lipsește, atunci este dimensiunea 11.
Alinierea textului
Alinierea textului este specificată de o etichetă <w:jc> cu patru moduri w:val disponibile: "left" , "center" , "right" și "both" .
"left" este modul implicit; textul începe din stânga dreptunghiului paragrafului (de obicei, lățimea paginii). (Acest paragraf este aliniat la stânga, ceea ce este standard.)
Modul "center" , în mod previzibil, centrează toate caracterele în interiorul lățimii paginii. (Din nou, acest paragraf exemplifică alinierea centrată.)
În modul "right" , textul paragrafului este aliniat la marginea din dreapta. (Observați cum acest text este aliniat în partea dreaptă.)
Modul "both" pune spațiere suplimentară între cuvinte, astfel încât liniile să devină mai largi și să ocupe toată lățimea paragrafului, cu excepția ultimei linii care este aliniată la stânga. (Acest paragraf este o demonstrație în acest sens.)
Imagini
DOCX acceptă două tipuri de imagini: inline și flotante.
Imaginile în linie apar în interiorul unui paragraf împreună cu celelalte caractere, se folosește <w:drawing> în loc de <w:t> (text). Puteți găsi ID-ul imaginii cu următoarea sintaxă xpath:
w:drawing/wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip/@r:embed
ID-ul imaginii este folosit pentru a căuta numele fișierului în fișierul word/_rels/document.xml.rels și ar trebui să indice fișierul gif/jpeg din subfolderul word/media. (Consultați fișierul word/_rels/document.xml.rels al proiectului github, unde puteți vedea ID-ul imaginii.)
Imaginile plutitoare sunt plasate în raport cu paragrafele cu text care curge în jurul lor. (Iată al doilea document exemplu de proiect github cu o imagine plutitoare.)
Imaginile plutitoare folosesc <wp:anchor> în loc de <w:drawing> , așa că dacă ștergeți orice text din <w:p> , aveți grijă la ancore dacă nu doriți ca imaginile să fie eliminate.
Mese
Etichetele XML pentru tabele sunt similare cu marcarea tabelelor HTML – se potrivește cu <tr> etc.
<w:tbl> , tabelul în sine, are proprietăți de tabel <w:tblPr> , iar fiecare proprietate de coloană este prezentată de <w:gridCol> în interiorul <w:tblGrid> . Rândurile urmează unul câte unul ca etichete <w:tr> și fiecare rând ar trebui să aibă același număr de coloane așa cum este specificat în <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> Lățimea coloanelor din tabel poate fi specificată în eticheta <w:tblW> , dar dacă nu o definiți, MS Word va folosi algoritmii săi interni pentru a găsi lățimea optimă a coloanelor pentru cea mai mică dimensiune efectivă a tabelului.
Unități
Multe atribute XML din interiorul DOCX specifică dimensiuni sau distanțe. Deși sunt numere întregi în XML, toate au unități diferite, așa că este necesară o conversie. Subiectul este unul complicat, așa că aș recomanda acest articol de Lars Corneliussen despre unitățile din fișierele DOCX. Tabelul pe care îl prezintă este util, deși cu o mică greșeală de imprimare: inchi ar trebui să fie pt/72, nu pt*72.
Iată o foaie de cheat:
| CONVERSIUNI COMUNE DE UNITĂȚI DOCX XML | ||||||
| 20 de punct | Puncte dxa/20 | inci pct/72 | centimetri în*2,54 | Jumătate de dimensiune a fontului pct/144 | UEM în*914400 | |
| Exemplu | 11906 | 595,3 | 8,27... | 21.00086... | 4.135 | 7562088 |
| Etichete care folosesc acest lucru | pgSz/pgMar/w:spațiere | w:sz | wp:extent, a:ext | |||
Sfaturi pentru implementarea unui Layouter
Dacă doriți să convertiți un fișier DOCX (în PDF, de exemplu), să îl desenați pe pânză sau să numărați numărul de pagini, va trebui să implementați un layouter. Un layouter este un algoritm pentru calcularea pozițiilor caracterelor dintr-un fișier DOCX.
Aceasta este o sarcină complexă dacă aveți nevoie de redare cu fidelitate de 100%. Timpul necesar pentru implementarea unui layout bun este măsurat în ani-om, dar dacă aveți nevoie doar de unul simplu, limitat, se poate face relativ rapid.
Un layouter umple un dreptunghi părinte, care este de obicei un dreptunghi al paginii. Se adaugă cuvinte dintr-o cursă unul câte unul. Când linia curentă depășește, începe una nouă. Dacă paragraful este prea înalt pentru dreptunghiul părinte, este împachetat la pagina următoare.
Iată câteva lucruri importante de reținut dacă decideți să implementați un layouter:
- Aspectul ar trebui să aibă grijă de alinierea textului și de textul care plutește peste imagini
- Ar trebui să fie capabil să manipuleze obiecte imbricate, cum ar fi tabele imbricate
- Dacă doriți să oferiți suport complet pentru astfel de imagini, va trebui să implementați un layouter cu cel puțin două treceri, primul pas colectează pozițiile imaginilor plutitoare, iar al doilea umple spațiul gol cu caractere text.
- Fiți conștienți de indentări și spații. Fiecare paragraf are spațiere înainte și după, iar aceste numere sunt specificate de eticheta
w:spacing. Spațierea verticală este specificată de etichetelew:afterșiw:before. Rețineți că distanța dintre linii este specificată dew:line, dar aceasta nu este dimensiunea liniei așa cum s-ar putea aștepta. Pentru a obține dimensiunea liniei, luați înălțimea fontului curent, înmulțiți cuw:lineși împărțiți cu 12. - Fișierele DOCX nu conțin informații despre paginare. Nu veți găsi numărul de pagini din document decât dacă calculați de cât spațiu aveți nevoie pentru fiecare rând pentru a stabili numărul de pagini. Dacă trebuie să găsiți coordonatele exacte ale fiecărui caracter de pe pagină, asigurați-vă că țineți cont de toate spațiile, indentările și dimensiunile.
- Dacă implementați un layout DOCX cu funcții complete care gestionează tabelele, rețineți cazurile speciale când tabelele se întind pe mai multe pagini. O celulă care provoacă o depășire a paginii afectează și alte celule.
- Crearea unui algoritm optim pentru calcularea lățimii coloanelor unui tabel este o problemă de matematică dificilă, iar procesoarele de text și layouterii folosesc de obicei unele implementări suboptime. Propun utilizarea algoritmului din documentația tabelului HTML W3C ca primă aproximare. Nu am găsit o descriere a algoritmului folosit de MS Word, iar Microsoft a ajustat algoritmul de-a lungul timpului, astfel încât diferite versiuni de Word ar putea aranja tabelele ușor diferit.
Dacă ceva nu este clar: faceți o inginerie inversă a XML-ului!
Când nu este evident cum funcționează aceasta sau acea etichetă XML în MS Word, există două abordări principale pentru a o descoperi:
Creați conținutul dorit pas cu pas. Începeți cu un fișier docx simplu. Salvați fiecare pas în propriul fișier, ca în
1.docx,2.docx, de exemplu. Dezarhivați fiecare dintre ele și utilizați un instrument vizual de diferențiere pentru compararea folderelor pentru a vedea ce etichete apar după modificările dvs. (Pentru o opțiune comercială, încercați Araxis Merge, sau pentru o opțiune gratuită, WinMerge.)Dacă generați un fișier DOCX care nu-i place lui MS Word, lucrați înapoi. Simplificați-vă XML pas cu pas. La un moment dat veți afla care modificare a fost găsită incorectă în MS Word.
DOCX este destul de complex, nu-i așa?
Este complex, iar licența Microsoft interzice utilizarea MS Word pe partea de server pentru procesarea DOCX – acesta este destul de standard pentru produsele comerciale. Cu toate acestea, Microsoft a furnizat fișierul XSLT pentru a gestiona majoritatea etichetelor DOCX, dar nu vă va oferi o fidelitate de 100% sau chiar 99%. Procesele precum împachetarea textului peste imagini nu sunt acceptate, dar veți putea accepta majoritatea documentelor. (Dacă nu aveți nevoie de complexitate, luați în considerare utilizarea Markdown ca alternativă.)
Dacă aveți un buget suficient (nu există un motor de randare DOCX gratuit), poate doriți să utilizați produse comerciale precum Aspose sau docx4j. Cea mai populară soluție gratuită este LibreOffice pentru conversia între DOCX și alte formate, inclusiv PDF. Din păcate, LibreOffice conține multe erori mici în timpul conversiei și, deoarece este un produs C++ sofisticat, open-source, este lent și dificil de rezolvat problemele de fidelitate.
În mod alternativ, dacă găsești aspectul DOCX prea complicat pentru a fi implementat singur, îl poți converti și în HTML și poți folosi un browser pentru a-l reda. De asemenea, puteți lua în considerare unul dintre dezvoltatorii XML independenți de la Toptal.
Resurse DOCX pentru citiri suplimentare
- Specificația ECMA DOCX
- Bibliotecă OpenXML pentru manipularea DOCX din C#. Nu conține informații despre aspectul sau redarea codului, dar oferă o ierarhie de clasă care se potrivește cu fiecare nod XML posibil din DOCX.
- Puteți oricând să căutați sau să întrebați pe stackoverflow cu cuvinte cheie precum docx4j, OpenXML și docx; există oameni în comunitate care sunt informați.
