DOCX 的非正式介紹
已發表: 2022-03-11大約有 10 億人使用 Microsoft Office,DOCX 格式是在辦公室之間交換文檔文件的最流行的事實標準。 其最接近的競爭對手——ODT 格式——僅被 Open/LibreOffice 和一些開源產品支持,使其遠非標準。 PDF 格式不是競爭對手,因為 PDF 無法編輯且不包含完整的文檔結構,因此只能進行有限的本地更改,如水印、簽名等。 這就是為什麼大多數業務文檔都是以 DOCX 格式創建的; 沒有替代它的好方法。
雖然 DOCX 是一種複雜的格式,但您可能希望手動解析它以完成更簡單的任務,例如索引、轉換為 TXT 和進行其他小的修改。 我想為您提供有關 DOCX 內部結構的足夠信息,這樣您就不必參考 ECMA 規範,這是一本 5,000 頁的龐大手冊。
理解該格式的最佳方法是使用 MSWord 創建一個簡單的單字文檔,並觀察編輯文檔如何更改底層 XML。 您會遇到 DOCX 在 MS Word 中格式不正確且您不知道原因的情況,或者遇到不明顯如何生成所需格式的情況。 準確地查看和理解 XML 中正在發生的事情將對此有所幫助。
我在協作 DOCX 編輯器 CollabOffice 上工作了大約一年,我想與開發人員社區分享其中的一些知識。 在本文中,我將解釋 DOCX 文件結構,總結分散在 Internet 上的信息。 本文是龐大、複雜的 ECMA 規範和當前可用的簡單互聯網教程之間的中介。 您可以在我的 github 帳戶的toptal-docx項目中找到本文隨附的文件。
一個簡單的 DOCX 文件
DOCX 文件是 XML 文件的 ZIP 存檔。 如果您創建一個新的空 Microsoft Word 文檔,在裡面寫一個單詞“Test”並解壓縮它的內容,您將看到以下文件結構:
儘管我們創建了一個簡單的文檔,但 Microsoft Word 中的保存過程已經生成了 XML 格式的默認主題、文檔屬性、字體表等等。
首先,讓我們刪除未使用的內容並關注包含主要文本元素的document.xml 。 刪除文件時,請確保已從其他 xml 文件中刪除了對該文件的所有關係引用。 這是一個關於我如何清除對 app.xml 和 core.xml 的依賴項的代碼差異示例。 如果您有任何未解決/缺失的引用,MSWord 將認為該文件已損壞。
這是我們簡化的最小 DOCX 文檔的結構(這裡是 github 上的項目):
讓我們從頂部按文件分解它:
_rels/.rels
這定義了告訴 MS Word 在哪裡查找文檔內容的參考。 在這種情況下,它引用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
此文件定義對嵌入在文檔內容中的資源(例如圖像)的引用。 我們的簡單文檔沒有嵌入資源,因此關係標籤為空:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> </Relationships>[內容類型].xml
[Content_Types].xml包含有關文檔內媒體類型的信息。 因為我們只有文本內容,所以很簡單:
<?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>文檔.xml
最後,這裡是包含文檔文本內容的主要 XML。 為了清楚起見,我刪除了一些命名空間聲明,但您可以在 github 項目中找到該文件的完整版本。 在該文件中,您會發現文檔中的某些命名空間引用未使用,但您不應刪除它們,因為 MS Word 需要它們。
這是我們的簡化示例:
<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> 主節點<w:document>代表文檔本身, <w:body>包含段落,並且嵌套在<w:body>中的是由<w:sectPr>定義的頁面尺寸。
<w:rsidR>是一個可以忽略的屬性; 它被 MS Word 內部使用。
讓我們看一個包含三個段落的更複雜的文檔。 我在 Microsoft Word 的屏幕截圖中用相同顏色突出顯示了 XML,因此您可以看到相關性:
<w:pw:rsidR="0081206C" w:rsidRDefault="00E10CAE"> <w:r> <w:t xml:space="preserve">這是我們示例的第一段。 默認是左對齊,現在介紹一下</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>有些粗體</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"> 文本</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">也改成</w:t> </w:r> <w:rw:rsidRPr="00E10CAE"> <w:rPr><w:rFonts w:ascii="Impact" w:hAnsi="Impact"/> </w:rPr> <w:t>字體樣式</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>到“影響”。</w:t></w:r> </w:p> <w:pw:rsidR="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>這是新段落。</w:t> </w:r></w:p > <w:pw:rsidR="00E10CAE" w:rsidRPr="00E10CAE" w:rsidRDefault="00E10CAE"> <w:r> <w:t>這又是一段,有點長。</w:t> </w:r> </w:p>
段落結構
一個簡單的文檔由段落組成,一個段落由運行(一系列具有相同字體、顏色等的文本)組成,運行由字符組成(例如<w:t> )。 <w:t>標籤裡面可能有幾個字符,並且在同一次運行中可能有幾個字符。
同樣,我們可以忽略<w:rsidR> 。
文本屬性
基本文本屬性是字體、大小、顏色、樣式等。 大約有 40 個標籤用於指定文本外觀。 正如您在我們的三段示例中所見,每次運行在<w:rPr>內都有自己的屬性,指定<w:color> 、 <w:rFonts>和粗體<w:b> 。
需要注意的重要一點是,屬性區分了兩組字符,普通腳本和復雜腳本(例如阿拉伯語),並且屬性具有不同的標記,具體取決於它所影響的字符類型。
大多數普通腳本屬性標籤都有一個匹配的複雜腳本標籤,並添加了一個“C”,指定該屬性用於復雜腳本。 例如: <w:i> (斜體)變為<w:iCs> ,普通腳本的粗體標記<w:b>變為複雜腳本的<w:bCs> 。
風格
Microsoft Word 中有一個專門用於樣式的完整工具欄:正常、無間距、標題 1、標題 2、標題等。 這些樣式存儲在/word/styles.xml中(注意:在我們簡單示例的第一步中,我們從 DOCX 中刪除了這個 XML。創建一個新的 DOCX 來查看這個)。
將文本定義為樣式後,您將在段落屬性標籤<w:pPr>中找到對該樣式的引用。 這是一個示例,其中我使用樣式標題 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> 這是來自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> <w:style/w:rPr/w:b> xpath 指定字體為粗體, <w:style/w:rPr/w:color>表示字體顏色。 <w:basedOn>指示 MSWord 對任何缺失的屬性使用“普通”樣式。

財產繼承
文本屬性是繼承的。 運行有自己的屬性( w:p/w:r/w:rPr/* ),但它也繼承了段落的屬性( w:r/w:pPr/* ),並且兩者都可以從/word/styles.xml引用樣式屬性/word/styles.xml 。
<w:r> <w:rPr> <w:rStyle w:val="DefaultParagraphFont"/> <w:sz w:val="16"/> </w:rPr> <w:tab/> </w:r> 段落和運行以默認屬性開頭: w:styles/w:docDefaults/w:rPrDefault/*和w:styles/w:docDefaults/w:pPrDefault/* 。 要獲得角色屬性的最終結果,您應該:
- 使用默認運行/段落屬性
- 附加運行/段落樣式屬性
- 附加本地運行/段落屬性
- 在段落屬性上附加結果運行屬性
當我說“附加”B 到 A 時,我的意思是遍歷所有 B 屬性並覆蓋所有 A 的屬性,使所有非相交屬性保持原樣。
另一個默認屬性可能位於的位置是<w:style>標記中w:type="paragraph"和w:default="1" 。 請注意,運行中的字符本身永遠不會有默認樣式,因此<w:style w:type="character" w:default="1">實際上不會影響任何文本。
1554402290400-dbb29eef3ba6035df7ad726dfc99b2af.png)
切換屬性
一些屬性是“切換”屬性,例如<w:b> (粗體)或<w:i> (斜體); 這些屬性的行為類似於 XOR 運算符。
這意味著如果父樣式為粗體,而子樣式為粗體,則結果將是常規的非粗體文本。
您必須進行大量測試和逆向工程才能正確處理切換屬性。 查看 ECMA-376 Open XML 規範的第 17.7.3 段以獲取切換屬性的正式詳細規則/
字體
字體遵循與其他文本屬性相同的通用規則,但字體屬性默認值在單獨的主題文件中指定,在word/_rels/document.xml.rels下引用如下:
<Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/> 根據上述參考,默認字體名稱將在word/theme/themes1.xml中的<a:theme>標籤、 a:themeElements/a:fontScheme/a:majorFont或a:minorFont標籤內找到。
默認字體大小為 10,除非w:docDefaults/w:rPrDefault標記丟失,否則大小為 11。
文本對齊
文本對齊由<w:jc>標記指定,有四種可用的w:val模式: "left" 、 "center" 、 "right"和"both" 。
"left"是默認模式; 文本從段落矩形的左側開始(通常是頁面寬度)。 (本段左對齊,這是標準的。)
可以預見, "center"模式將所有字符居中在頁面寬度內。 (同樣,本段舉例說明了居中對齊。)
在"right"模式下,段落文本與右邊距對齊。 (請注意此文本如何與右側對齊。)
"both"模式在單詞之間放置額外的間距,以便行變得更寬並佔據整個段落寬度,但左對齊的最後一行除外。 (本段就是一個例子。)
圖片
DOCX 支持兩種圖像:內聯和浮動。
內聯圖像與其他字符一起出現在段落中,使用<w:drawing>而不是使用<w:t> (文本)。 您可以使用以下 xpath 語法找到圖像 ID:
w:drawing/wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip/@r:embed
圖像 ID 用於在word/_rels/document.xml.rels文件中查找文件名,它應該指向 word/media 子文件夾中的 gif/jpeg 文件。 (查看github項目的word/_rels/document.xml.rels文件,可以看到圖片ID。)
浮動圖像相對於段落放置,文本在它們周圍流動。 (這是帶有浮動圖像的 github 項目示例文檔。)
浮動圖像使用<wp:anchor>而不是<w:drawing> ,因此如果您刪除<w:p>中的任何文本,如果您不想刪除圖像,請小心使用錨點。
表
表格的 XML 標記類似於 HTML 表格標記—— 與 <tr> 等匹配。
<w:tbl> ,表本身,具有表屬性<w:tblPr> ,每個列屬性由<w:gridCol>內部<w:tblGrid> 。 行一個接一個地作為<w:tr>標記,並且每一行應該具有與<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> 表格列的寬度可以在<w:tblW>標記中指定,但如果您沒有定義它,MS Word 將使用其內部算法來找到最小有效表格大小的列的最佳寬度。
單位
DOCX 中的許多 XML 屬性指定大小或距離。 雖然它們是 XML 中的整數,但它們都有不同的單位,因此需要進行一些轉換。 這個主題很複雜,所以我推薦 Lars Corneliussen 的這篇關於 DOCX 文件中的單元的文章。 他展示的表格很有用,儘管有一點印刷錯誤:英寸應該是 pt/72,而不是 pt*72。
這是一個備忘單:
| 常見的 DOCX XML 單位轉換 | ||||||
| 20分 | 積分 DXA/20 | 英寸 點/72 | 厘米 在* 2,54 | 字體一半大小 pt/144 | 鴯 在* 914400 | |
| 例子 | 11906 | 595.3 | 8,27… | 21.00086… | 4,135 | 7562088 |
| 使用這個的標籤 | pgSz/pgMar/w:間距 | w:sz | wp:範圍,a:ext | |||
實現佈局器的提示
如果要轉換 DOCX 文件(例如轉換為 PDF)、在畫布上繪製或計算頁數,則必須實現佈局器。 佈局器是一種從 DOCX 文件中計算字符位置的算法。
如果您需要 100% 保真度渲染,這是一項複雜的任務。 實現一個好的佈局器所需的時間以人年為單位,但如果您只需要一個簡單、有限的佈局器,則可以相對快速地完成。
一個佈局器填充一個父矩形,它通常是頁面的一個矩形。 它一個一個地添加運行中的單詞。 噹噹前行溢出時,它會開始一個新的。 如果段落對於父矩形來說太高,則將其換行到下一頁。
如果您決定實施佈局器,請記住以下重要事項:
- 佈局器應注意文本對齊和浮動在圖像上的文本
- 它應該能夠處理嵌套對象,例如嵌套表
- 如果您想為此類圖像提供全面支持,您必須實現一個至少包含兩次傳遞的佈局器,第一步收集浮動圖像的位置,第二步用文本字符填充空白空間。
- 注意縮進和間距。 每個段落前後都有間距,這些數字由
w:spacing標籤指定。 垂直間距由w:after和w:before標籤指定。 請注意,行間距是由w:line指定的,但這不是人們可能期望的行的大小。 要獲得線條的大小,請取當前字體高度,乘以w:line並除以 12。 - DOCX 文件不包含有關分頁的信息。 除非您計算每行需要多少空間來確定頁數,否則您不會找到文檔中的頁數。 如果您需要找到頁面上每個字符的準確坐標,請務必考慮所有間距、縮進和大小。
- 如果您實現了一個處理表格的全功能 DOCX 佈局器,請注意表格跨多個頁面時的特殊情況。 導致頁面溢出的單元格也會影響其他單元格。
- 創建用於計算表格列寬的最佳算法是一個具有挑戰性的數學問題,並且文字處理器和佈局器通常使用一些次優的實現。 我建議使用 W3C HTML 表格文檔中的算法作為第一近似值。 我還沒有找到對 MS Word 使用的算法的描述,並且微軟已經隨著時間的推移對算法進行了微調,因此不同版本的 Word 可能會稍微不同地佈置表格。
如果不清楚:對 XML 進行逆向工程!
當這個或那個 XML 標記在 MS Word 中的工作方式不明顯時,有兩種主要方法可以弄清楚:
逐步創建所需的內容。 從一個簡單的 docx 文件開始。 將每個步驟保存到自己的文件中,例如
1.docx、2.docx。 解壓縮它們並使用可視差異工具進行文件夾比較,以查看更改後出現的標籤。 (對於商業選項,請嘗試 Araxis Merge,或者對於免費選項,WinMerge。)如果您生成 MS Word 不喜歡的 DOCX 文件,請向後工作。 逐步簡化您的 XML。 在某些時候,您將了解 MS Word 發現的哪些更改不正確。
DOCX 相當複雜,不是嗎?
它很複雜,而且微軟的許可證禁止在服務器端使用 MS Word 來處理 DOCX——這對於商業產品來說是相當標準的。 然而,Microsoft 提供了 XSLT 文件來處理大多數 DOCX 標籤,但它不會為您提供 100% 甚至 99% 的保真度。 不支持圖像上的文本環繞等過程,但您將能夠支持大多數文檔。 (如果您不需要復雜性,請考慮使用 Markdown 作為替代方案。)
如果您有足夠的預算(沒有免費的 DOCX 渲染引擎),您可能希望使用 Aspose 或 docx4j 等商業產品。 最受歡迎的免費解決方案是 LibreOffice,用於在 DOCX 和其他格式(包括 PDF)之間進行轉換。 不幸的是,LibreOffice 在轉換過程中包含許多小錯誤,而且由於它是一個複雜的開源 C++ 產品,因此修復保真度問題很慢而且很困難。
或者,如果您發現 DOCX 佈局過於復雜而無法自己實現,您也可以將其轉換為 HTML 並使用瀏覽器進行渲染。 您還可以考慮 Toptal 的一位自由 XML 開發人員。
DOCX 資源供進一步閱讀
- ECMA DOCX 規範
- 從 C# 進行 DOCX 操作的 OpenXML 庫。 它不包含有關佈局或呈現代碼的信息,但提供了與 DOCX 中每個可能的 XML 節點匹配的類層次結構。
- 您可以隨時使用 docx4j、OpenXML 和 docx 等關鍵字在 stackoverflow 上搜索或詢問; 社區中有一些知識淵博的人。
