Tìm hiểu cấu trúc XML của file docx và tùy biến lại gem docxtor
Bài đăng này đã không được cập nhật trong 3 năm
Tìm hiểu cấu trúc XML của file docx
Docx thực chất là một tài liệu Office Open XML được Microsoft phát triển và xuất hiện đầu tiên ở phiên bản Microsoft Office 2007.
Tài liệu này được lưu trữ đóng gói thành file nén ZIP chứa file XML và các file dữ liệu khác. Vì vậy để ví dụ chúng ta có thể tạo một file Docx bằng chương trình Office Word 2010 sau đó giải nén file bằng chương trình nén file thông thường ZIP hoặc Winzar. Sau khi giải nén ta được cấu trúc file như sau:
Cấu trúc cơ bản này gồm:
- [Content_Types].xml : file này cung cấp thông tin loại MIME được đóng gói trong Docx
- _rels : thư mục này lưu quan hệ của một relationship part với các thành phần khác
- file .rel : các file có định dạng .rel này lưu các relationship part. Các ứng dụng sẽ đọc ở file này đầu tiên.
- docProps/core.xml : file này lưu các thuộc tính chính của một số tài liệu Office Open XML
- word/_rels : thư mục này chứa các relationsship part của word. Ví dụ, mối quan hệ với file document.xml sẽ được lưu thành file document.xml.rel
- word/document.xml : đây là file chính chứa các thành phần cho tài liệu Word
Trong bài viết này, chúng ta sẽ chỉ tìm hiểu cấu trúc của 3 file [Content_Types].xml, word/document.xml và word/_rels/document.xml.rel
- [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"/>
<Override PartName="/word/styles.xml" ContentType=
"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
<Override PartName="/docProps/app.xml" ContentType=
"application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
<Override PartName="/word/settings.xml" ContentType=
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/>
<Override PartName="/word/theme/theme1.xml" ContentType=
"application/vnd.openxmlformats-officedocument.theme+xml"/>
<Override PartName="/word/fontTable.xml" ContentType=
"application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"/>
<Override PartName="/word/webSettings.xml" ContentType=
"application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"/>
<Override PartName="/docProps/core.xml" ContentType=
"application/vnd.openxmlformats-package.core-properties+xml"/>
</Types>
- word/_rels/document.xml.rels
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Relationships
xmlns="http://schemas.microsoft.com/package/2005/06/relationships">
<Relationship Id="rId1"
Type="http://schemas.microsoft.com/office/2006/relationships/image"
Target="http://en.wikipedia.org/images/wiki-en.png"
TargetMode="External" />
<Relationship Id="rId2"
Type="http://schemas.microsoft.com/office/2006/relationships/hyperlink"
Target="http://www.wikipedia.org"
TargetMode="External" />
</Relationships>
Xét cấu trúc trên, ta có thể hiểu, những ảnh được tham chiếu trong tài liệu có thể tìm thấy bằng cách tìm các thẻ Relationships có type là http://schemas.microsoft.com/office/2006/relationships/image
.
Từ file này, ứng dụng chỉ cần đọc ID của các thẻ Relationship để biết được URL. Ví dụ: để nhúng file ảnh vào tài liệu Word, ta chỉ cần sử dụng:
<pic:blipFill><a:blip r:embed="rId1"/></pic:blipFill>
hoặc
<v:imagedata w:rel="rId1" o:title="example" />
- word/document.xml
Cấu trúc XML của từng thành phần các bạn có thể tìm hiểu thêm tại đây
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:o12="http://schemas.microsoft.com/office/2004/7/core"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.microsoft.com/office/omml/2004/12/core"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/3/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/3/main">
<w:body>
<w:p>
<w:r w:rsidR="002847EC">
<w:t>Word 2007 rocks my world!</w:t>
</w:r>
</w:p>
</w:body>
</w:document>
Gem docxtor
Cách sử dụng các bạn có thể tham khảo thêm tại https://github.com/docxtor/docxtor.
Gem này về cơ bản khá dễ sử dụng và được hỗ trợ khá nhiều thành phần hơn so với các Gem Ruby khác. Nhưng nó không hỗ trợ đầy đủ các thành trong một file docx. Các thành phần được hỗ trợ:
- Header
- Main body: p, h1, table
- Style: chỉ hỗ trợ các style cơ bản bold, italic, underline, indent, line_break, font. Tức là các bạn không thể set color cho text được.
Ngoài ra, có một vấn đề khi sử dụng Gem này đó là các file docx được tạo ra khi đọc bở Offcie Word 2007 trở lên sẽ bị báo lỗi:
Vì vậy nếu bạn muốn sử dụng để tạo 1 file docx với các thuộc tính cơ bản, không quá phức tạp thì có thể lựa chọn sử dụng Gem này.
Nếu dự án của các bạn cần tạo file Docx phức tạp hơn, mình có một gợi ý là các bạn nên sử dụng Apache Poi
Còn nếu dự án của các bạn đã lựa chọn sử dụng Gem này từ đầu nhưng trong quá trình phát triển phát sinh thêm các yêu cầu đòi hỏi các thành phần khác như set color, insert image (giống dự án của mình, hic) thì các bạn có thể tham khảo cách mình tùy biến lại Gem này phía dưới.
Tùy biến Gem docxtor
Để tùy biến, các bạn hãy down code từ Githud về.
Thêm style set color cho text
Set color được set trong thành phần thẻ paragraph vì thế ta sẽ mở file này theo đường dẫn docxtor/lib/docxtor/document/paragraph.rb.
Ta để ý, các thuộc tính PROPERTIES được cài đặt thiếu các thành phần để set color.
Xét cấu trúc XML của phần này:
<w:color w:val="800000"/>
<w:sz w:val="28"/>
Ta có thể sửa lại Gem như sau:
PROPERTIES =
{
:p => {
:style => 'pStyle',
:align => 'jc',
:font_size_complex => 'szCs',
:spacing => {
:name => 'spacing',
:before => 'before',
:after => 'after'
},
:indent => {
:name => 'ind',
:start => 'start',
:end => 'end',
:hanging => 'hanging'
}
},
:r => {
:font_size => 'sz', #add sz
:font_color => 'color', #add color
:bg_color => 'shd',
:bold => 'b',
:italic => 'i',
:underline => 'u'
}
}
Thêm thành phần images
Gem docxtor chưa hỗ trợ thành phần image nên để chèn được image vào trong document.xml chúng ta cần tạo thêm file image.rb trong thư mục docxtor/lib/docxtor/document (tham khảo cấu trúc tương tự ở các thành phần khác)
# file lib / docxtor.rb
module Docxtor
....
module Document
....
autoload :Image, 'docxtor/document/image'
end
module Docxtor
module Document
class Image < Element
def after_initialize(link, *args)
# Your code goes here...
end
def render xml
# Your code goes here...
end
end
end
end
Trong file docx, một image được chèn vào có cấu trúc xml như sau (bạn có thể google search hoặc tạo 1 file docx và xem nội dung của document.xml):
<w:drawing>
<wp:inline distT="0" distB="0" distL="0" distR="0">
<wp:extent cx="1905000" cy="952500" />
<wp:effectExtent l="0" t="0" r="0" b="0" />
<wp:docPr id="rId2" name="Picture rId2" />
<wp:cNvGraphicFramePr>
<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1" />
</wp:cNvGraphicFramePr>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
<pic:nvPicPr>
<pic:cNvPr id="rId2" name="Picture rId2" />
<pic:cNvPicPr>
<a:picLocks noChangeAspect="1" noChangeArrowheads="1" />
</pic:cNvPicPr>
</pic:nvPicPr>
<pic:blipFill>
<a:blip r:embed="rId2" />
<a:srcRect />
<a:stretch>
<a:fillRect />
</a:stretch>
</pic:blipFill>
<pic:spPr bwMode="auto">
<a:xfrm>
<a:off x="0" y="0" />
<a:ext cx="1905000" cy="952500" />
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst />
</a:prstGeom>
<a:noFill />
<a:ln>
<a:noFill />
</a:ln>
</pic:spPr>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
<wp:extent cx="1905000" cy="952500" />
chính là width và height của imagimage;
<a:blip r:embed="rId2" />
chính là đoạn nhúng ảnh từ relationships document.xml.rel thông qua id rId2
Ta có thể đưa viết lại class Image như sau:
module Docxtor
module Document
class Image < Element
AUTO_WIDTH = 5
AUTO_HEIGHT = 5
AUTO_INDENT = 0
def after_initialize(link, *args)
@rId = 1 # gán tạm thời
@num = 1 # gán tạm thời
style = args.shift || {ời
@width = (style[:width] || AUTO_WIDTH) * 9144000 / 96
@height = (style[:height] || AUTO_HEIGHT) * 9144000 / 96
@indent = style[:indent] || AUTO_INDENT
end
def render xml
xml.w :p do
xml.w :pPr do
xml.w :ind, "w:left" => @indent
end
xml.w :r do
xml.w :rPr do
xml.w :noProof
end
drawing xml
end
end
end
def drawing xml
xml.w :drawing do
xml.wp :inline, "distT" => "0", "distB" => "0", "distL" => "0", "distR" => "0" do
xml.wp :extent, "cx" => @width, "cy" => @height
xml.wp :effectExtent, "l" => "0", "t" => "0", "r" => "0", "b" => "0"
xml.wp :docPr, "id" => @num, "name" => "Picture #{@num}"
xml.wp :cNvGraphicFramePr do
xml.a :graphicFrameLocks, "xmlns:a" => "http://schemas.openxmlformats.org/drawingml/2006/main", "noChangeAspect" => "1"
end
xml.a :graphic, "xmlns:a" => "http://schemas.openxmlformats.org/drawingml/2006/main" do
xml.a :graphicData, "uri" => "http://schemas.openxmlformats.org/drawingml/2006/picture" do
xml.pic :pic, "xmlns:pic" => "http://schemas.openxmlformats.org/drawingml/2006/picture" do
xml.pic :nvPicPr do
xml.pic :cNvPr, "id" => "0", "name" => "Picture #{@num}", "descr" => ""
xml.pic :cNvPicPr do
xml.a :picLocks, "noChangeAspect" => "1", "noChangeArrowheads" => "1"
end
end
xml.pic :blipFill do
xml.a :blip, "r:embed" => "#{@rId}" do
xml.a :extLst do
xml.a :ext, "uri" => "{28A0092B-C50C-407E-A947-70E740481C1C}" do
xml.a14 :useLocalDpi, "xmlns:a14" => "http://schemas.microsoft.com/office/drawing/2010/main", "val" => "0"
end
end
end
xml.a :srcRect
xml.a :stretch do
xml.a :fillRect
end
end
xml.pic :spPr, "bwMode" => "auto" do
xml.a :xfrm do
xml.a :off, "x" => "0", "y" => "0"
xml.a :ext, "cx" => @width, "cy" => @height
end
xml.a :prstGeom, "prst" => "rect" do
xml.a :avLst
end
xml.a :noFill
xml.a :ln do
xml.a :noFill
end
end
end
end
end
end
end
end
end
end
end
Tuy nhiên làm thế nào lấy được rid, hơn nữa, chúng ta vẫn chưa lưu được file image. Ta hãy quay lại xem cách lưu file của Gem xem sao.
package = Docxtor.generate do
table_of_contents "Contents"
h 1, "heading1"
p "content", :style => 'p2', :i => true, :align => 'center'
end
package.save('test.docx')
Hãy xem class Generator hoạt động thế nào:
module Docxtor
class Generator
class << self
def generate(template, &block)
template_parser = TemplateParser.new(template)
parts = template_parser.parts
# lấy tất cả các thành phần là header và footer lưu vào running_elements và gán vào parts
running_elements = RunningElementsBuilder.new(&block).elements
parts += rnning_elements
# build running_elements thành các relationship part vào file document.xml.rel và gán vào parts
parts < ReferenceBuilder.new(running_elements)
# build các thành phần khác vào file document.xml và gán vào parts
parts << Document::Builder.new(running_elements, &block)
# đóng gói các parts
Package::Builder.new(parts)
enarts
end
end
end
Như vậy, các relationship part được lưu ở đây chỉ là header và footer. Ta sẽ tạo thêm relationship part là image khi build Document::Builder.new(running_elements, &block)
Ta sửa như sau:
# file lib/docxtor/document/element.rb
module Docxtor
module Document
class Element
attr_accessor :elements, :xml, :reference
def self.map(mappings)
mappings.each do |name, klass|
define_method(name) do |*args, &block|
if name == :image
image_element = RunningElement.new(name, 1, *args)
@reference.add_element image_element
end
elements << klass.new(@reference, *args, &block)
end
end
end
......
end
end
end
# file lib/docxtor/document/image.rb
module Docxtor
module Document
class Image < Element
....
def after_initialize(link, *args)
@rId = @reference.last_element.reference_id
@num = @reference.last_element.num
....
end
end
end
end
# file lib/docxtor/reference_builder.rb
module Docxtor
class ReferenceBuilder
attr_accessor :elements
...
def add_element running_element
running_element.reference_id = "rId#{elements.length+1}"
running_element.num = elements.length+1
@elements << running_element
end
def last_element
@elements.last
end
...
end
end
# file lib/docxtor/running_element.rb
module Docxtor
class RunningElement
attr_accessor :pages, :type, :reference_id, :num
def initialize type, num, contents, options = {}
@type = type
@contents = contents
@align = options[:align]
@pages = options[:pages] || :default
@num = num
end
def reference_name
"#{type}Reference"
end
def name
if @type == :image
"media/#{@contents.split("/").last}"
else
"#{type}#{@num}.xml"
end
end
....
def content
if @type == :image
return File.read(@contents)
enend
....
end
end
end
Cuối cùng, ta sửa lại class Generate như sau:
module Docxtor
class Generator
class << self
def generate(template, &block)
template_parser = TemplateParser.new(template)
parts = template_parser.parts
running_elements = RunningElementsBuilder.new(&block).elements
reference = ReferenceBuilder.new(running_elements)
document = Document::Builder.new(reference, &block)
parts += document.reference.elements
parts << document.reference
parts << document
Package::Builder.new(parts)
end
end
end
end
Sửa lỗi với Office Word 2007
Lỗi này là do cấu trúc của header của các file XML không đúng với định dạng chuẩn của Office Open XML. Ta sẽ sửa lỗi này như sau:
Đầu tiên là sửa cấu trúc file header, file này được generate bởi class running_element, ta sửa như sau:
#file lib/docxtor/running_element.rb
module Docxtor
class RunningElement
def content
if @type == :image
return File.read(@contents)
end
xml = ::Builder::XmlMarkup.new
if @type == :header
xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8", :standalone => "yes"
xml.w :hdr,
"xmlns:wpc" => "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
"xmlns:mo" => "http://schemas.microsoft.com/office/mac/office/2008/main",
"xmlns:mc" => "http://schemas.openxmlformats.org/markup-compatibility/2006",
"xmlns:mv" => "urn:schemas-microsoft-com:mac:vml",
"xmlns:o" => "urn:schemas-microsoft-com:office:office",
"xmlns:r" => "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"xmlns:m" => "http://schemas.openxmlformats.org/officeDocument/2006/math",
"xmlns:v" => "urn:schemas-microsoft-com:vml",
"xmlns:wp14" => "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
"xmlns:wp" => "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"xmlns:w10" => "urn:schemas-microsoft-com:office:word",
"xmlns:w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xmlns:w14" => "http://schemas.microsoft.com/office/word/2010/wordml",
"xmlns:wpg" => "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
"xmlns:wpi" => "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
"xmlns:wne" => "http://schemas.microsoft.com/office/word/2006/wordml",
"xmlns:wps" => "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
"mc:Ignorable" => "w14 wp14" do |xml|
xml.w :p do |xml|
xml.w :pPr do |xml|
xml.w :jc, "w:val" => "#{@align}" if @align
xml.w :pStyle, "w:val" => "Header"
xml.w :proofErr, "w:type" => "spellStart"
xml.w :proofErr, "w:type" => "gramStart"
xml.w :r do |xml|
xml.w :t, @contents
end
xml.w :proofErr, "w:type" => "spellEnd"
xml.w :proofErr, "w:type" => "gramEnd"
end
end
end
else
xml.instruct! :xml, :version => "1.0", :encoding=>"UTF-8", :standalone => "yes"
xml.w :ftr, "xmlns:o" => "urn:schemas-microsoft-com:office:office",
"xmlns:r" => "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"xmlns:v" => "urn:schemas-microsoft-com:vml",
"xmlns:w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xmlns:w10" => "urn:schemas-microsoft-com:office:word",
"xmlns:wp" => "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" do |xml|
xml.w :p do |xml|
xml.w :pPr do |xml|
xml.w :jc, "w:val" => "#{@align}" if @align
end
if @contents == :pagenum
xml.w :r do |xml|
xml.w :fldChar, "w:fldCharType" => "begin"
end
xml.w :r do |xml|
xml.w :instrText, "PAGE"
end
xml.w :r do |xml|
xml.w :fldChar, "w:fldCharType" => "separate"
end
xml.w :r do |xml|
xml.w :t, "i"
end
xml.w :r do |xml|
xml.w :fldChar, "w:fldCharType" => "end"
end
else
xml.w :r do |xml|
xml.w :t, @contents
end
end
end
end
end
end
end
Tiếp theo ta sửa cấu trúc file document.xml, file này được generate bởi class Builder:
module Docxtor
module Document
class Builder
...
def render(&block)
xml = ::Builder::XmlMarkup.new
xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8", :standalone => "yes"
xml.w :document, "xmlns:wpc" => "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
"xmlns:mo" => "http://schemas.microsoft.com/office/mac/office/2008/main",
"xmlns:mc" => "http://schemas.openxmlformats.org/markup-compatibility/2006",
"xmlns:mv" => "urn:schemas-microsoft-com:mac:vml",
"xmlns:o" => "urn:schemas-microsoft-com:office:office",
"xmlns:r" => "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"xmlns:m" => "http://schemas.openxmlformats.org/officeDocument/2006/math",
"xmlns:v" => "urn:schemas-microsoft-com:vml",
"xmlns:wp14" => "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
"xmlns:wp" => "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"xmlns:w10" => "urn:schemas-microsoft-com:office:word",
"xmlns:w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xmlns:w14" => "http://schemas.microsoft.com/office/word/2010/wordml",
"xmlns:wpg" => "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
"xmlns:wpi" => "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
"xmlns:wne" => "http://schemas.microsoft.com/office/word/2006/wordml",
"xmlns:wps" => "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
"mc:Ignorable" => "w14 wp14" do
xml.w :body do
xml << Document::Root.new(reference, &block).render(::Builder::XmlMarkup.new)
render_running_elements xml
end
end
xml.target!
end
...
end
end
end
Ngoài ra, vì thêm thành phần image nên chúng ta cần khai báo thêm MIME là loại image trong [Content_Types].xml.
# file templates/default/[Content_Types].xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="xml" ContentType="application/xml"/>
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="jpg" ContentType="image/jpeg"/>
<Default Extension="jpeg" ContentType="image/jpeg"/>
<Default Extension="png" ContentType="image/png"/>
<Default Extension="gif" ContentType="image/gif"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
<Override PartName="/word/stylesWithEffects.xml" ContentType="application/vnd.ms-word.stylesWithEffects+xml"/>
<Override PartName="/word/settings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"/>
<Override PartName="/word/webSettings.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"/>
<Override PartName="/word/fontTable.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"/>
<Override PartName="/word/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>
<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
<Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/>
</Types>
Tham khảo:
http://en.wikipedia.org/wiki/Office_Open_XML_file_formats
https://msdn.microsoft.com/en-us/library/bb266220(v=office.12).aspx
Github: https://github.com/ducnhat1989/docxtor/compare/ba9091d...e53844d
All rights reserved