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”並解壓縮它的內容,您將看到以下文件結構:

我們全新的測試 DOCX 結構。

儘管我們創建了一個簡單的文檔,但 Microsoft Word 中的保存過程已經生成了 XML 格式的默認主題、文檔屬性、字體表等等。

DOCX 中的所有文件都是 XML 文件,即使是帶有“.rels”擴展名的文件。
鳴叫

首先,讓我們刪除未使用的內容並關注包含主要文本元素的document.xml 。 刪除文件時,請確保已從其他 xml 文件中刪除了對該文件的所有關係引用。 這是一個關於我如何清除對 app.xml 和 core.xml 的依賴項的代碼差異示例。 如果您有任何未解決/缺失的引用,MSWord 將認為該文件已損壞。

這是我們簡化的最小 DOCX 文檔的結構(這裡是 github 上的項目):

我們簡化的 DOCX 結構。

讓我們從頂部按文件分解它:

_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/* 。 要獲得角色屬性的最終結果,您應該:

  1. 使用默認運行/段落屬性
  2. 附加運行/段落樣式屬性
  3. 附加本地運行/段落屬性
  4. 在段落屬性上附加結果運行屬性

當我說“附加”B 到 A 時,我的意思是遍歷所有 B 屬性並覆蓋所有 A 的屬性,使所有非相交屬性保持原樣。

另一個默認屬性可能位於的位置是<w:style>標記中w:type="paragraph"w:default="1" 。 請注意,運行中的字符本身永遠不會有默認樣式,因此<w:style w:type="character" w:default="1">實際上不會影響任何文本。

鳴叫

運行中的字符可以從其段落繼承,並且都可以從styles.xml 繼承。

1554402290400-dbb29eef3ba6035df7ad726dfc99b2af.png)

運行中的字符可以從其段落繼承,並且都可以從styles.xml 繼承。

切換屬性

一些屬性是“切換”屬性,例如<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:majorFonta: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>中的任何文本,如果您不想刪除圖像,請小心使用錨點。

內聯與浮動。

MS Word 的圖像選項將圖像對齊稱為“文本環繞模式”。

表格的 XML 標記類似於 HTML 表格標記—— 與 <table> 相同, 與 <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:afterw:before標籤指定。 請注意,行間距是由w:line指定的,但這不是人們可能期望的行的大小。 要獲得線條的大小,請取當前字體高度,乘以w:line並除以 12。
  • DOCX 文件不包含有關分頁的信息。 除非您計算每行需要多少空間來確定頁數,否則您不會找到文檔中的頁數。 如果您需要找到頁面上每個字符的準確坐標,請務必考慮所有間距、縮進和大小。
  • 如果您實現了一個處理表格的全功能 DOCX 佈局器,請注意表格跨多個頁面時的特殊情況。 導致頁面溢出的單元格也會影響其他單元格。
  • 創建用於計算表格列寬的最佳算法是一個具有挑戰性的數學問題,並且文字處理器和佈局器通常使用一些次優的實現。 我建議使用 W3C HTML 表格文檔中的算法作為第一近似值。 我還沒有找到對 MS Word 使用的算法的描述,並且微軟已經隨著時間的推移對算法進行了微調,因此不同版本的 Word 可能會稍微不同地佈置表格。

如果不清楚:對 XML 進行逆向工程!

當這個或那個 XML 標記在 MS Word 中的工作方式不明顯時,有兩種主要方法可以弄清楚:

  • 逐步創建所需的內容。 從一個簡單的 docx 文件開始。 將每個步驟保存到自己的文件中,例如1.docx2.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 上搜索或詢問; 社區中有一些知識淵博的人。