Tìm hiểu cấu trúc XML của file docx và tùy biến lại gem docxtor
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 ...
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à awidth 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 @awidth = (style[:awidth] || 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" => @awidth, "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" => @awidth, "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 <