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 上搜索或詢問; 社區中有一些知識淵博的人。