class Avv2word::Document
Public Class Methods
content_types_xml_file()
click to toggle source
# File lib/avv2word/document.rb, line 96 def content_types_xml_file '[Content_Types].xml' end
create(content, template_name = nil, extras = false)
click to toggle source
# File lib/avv2word/document.rb, line 49 def create(content, template_name = nil, extras = false) content = extract_images_html(content) content = escape_footnotes(content) template_name += extension if template_name && !template_name.end_with?(extension) document = new(template_file(template_name)) document.replace_files(content, extras) document.generate end
create_and_save(content, file_path, template_name = nil, extras = false)
click to toggle source
# File lib/avv2word/document.rb, line 58 def create_and_save(content, file_path, template_name = nil, extras = false) File.open(file_path, 'wb') do |out| out << create(content, template_name, extras) end end
create_with_content(template, content, extras = false)
click to toggle source
# File lib/avv2word/document.rb, line 64 def create_with_content(template, content, extras = false) content = extract_images_html(content) template += extension unless template.end_with?(extension) document = new(template_file(template)) document.replace_files(content, extras) document.generate end
doc_xml_file()
click to toggle source
# File lib/avv2word/document.rb, line 76 def doc_xml_file 'word/document.xml' end
escape_footnotes(html)
click to toggle source
# File lib/avv2word/document.rb, line 38 def escape_footnotes(html) doc = Nokogiri::HTML.fragment(html) nodes_with_footnotes = doc.css("footnote") nodes_with_footnotes.each do |node| node.name = "footnote" node.content = node.attributes["data-value"] node.attributes.map{|k,v| node.attributes[k].remove } end doc.to_html end
extension()
click to toggle source
# File lib/avv2word/document.rb, line 72 def extension '.docx' end
extract_images_html(html)
click to toggle source
# File lib/avv2word/document.rb, line 8 def extract_images_html(html) require "base64" require "fileutils" # create working dir FileUtils.mkdir_p("tmp_imgs") resize = 1 # future feature; half size: 0.5 doc = Nokogiri::HTML.fragment(html) doc.css("img").each_with_index do |img_elm,i| mime_type, img_data = img_elm.attributes["src"].value.split(",") # ^--data:image/jpeg;base64 ext = mime_type.match(/image\/(\w+?);base64/)[1] img_path = "tmp_imgs/image#{i+1}.#{ext}" File.open(img_path,"wb"){|f| f.write Base64.decode64(img_data)} img_elm.attributes["src"].value = img_path match_dimensions = %x( identify #{img_path} ).match(/(?<x>\d+)x(?<y>\d+)/) if match_dimensions img_elm["style"] = "width:#{match_dimensions[:x].to_i*resize}px;height:#{match_dimensions[:y].to_i*resize}px" else img_elm["style"] = "width:100px;height:100px" end # img_elm["style"] = "width:100.5px;height:100.5px" # alt: style="width:350px;height:150px" # alt: img_elm["data-width"] = "236px" end doc.to_html end
footnotes_xml_file()
click to toggle source
# File lib/avv2word/document.rb, line 100 def footnotes_xml_file 'word/footnotes.xml' end
header_xml_file()
click to toggle source
# File lib/avv2word/document.rb, line 92 def header_xml_file 'word/header.xml' end
new(template_path)
click to toggle source
# File lib/avv2word/document.rb, line 109 def initialize(template_path) @replaceable_files = {} @template_path = template_path @image_files = [] end
numbering_xml_file()
click to toggle source
# File lib/avv2word/document.rb, line 80 def numbering_xml_file 'word/numbering.xml' end
relations_xml_file()
click to toggle source
# File lib/avv2word/document.rb, line 84 def relations_xml_file 'word/_rels/document.xml.rels' end
styles_xml_file()
click to toggle source
# File lib/avv2word/document.rb, line 104 def styles_xml_file 'word/styles.xml' end
Public Instance Methods
generate()
click to toggle source
Generate a string representing the contents of a docx file.
# File lib/avv2word/document.rb, line 118 def generate Zip::File.open(@template_path) do |template_zip| buffer = Zip::OutputStream.write_buffer do |out| template_zip.each do |entry| next if entry.name =~ /\/$/ out.put_next_entry entry.name if @replaceable_files[entry.name] && entry.name == Document.doc_xml_file source = entry.get_input_stream.read # Change only the body of document. TODO: Improve this... source = source.sub(/(<w:body>)((.|\n)*?)(<w:sectPr)/, "\\1#{@replaceable_files[entry.name]}\\4") # add header and footer only if they really exist if entry.name == 'word/document.xml' source.sub!('<!--<w:headerReference w:type="default" r:id="rId8"/>-->','<w:headerReference w:type="default" r:id="rId8"/>') if @header source.sub!('<!--<w:footerReference w:type="default" r:id="rId9"/>-->','<w:footerReference w:type="default" r:id="rId9"/>') if @footer end out.write(source) elsif @replaceable_files[entry.name] out.write(@replaceable_files[entry.name]) elsif entry.name == Document.content_types_xml_file raw_file = entry.get_input_stream.read content_types = @image_files.empty? ? raw_file : inject_image_content_types(raw_file) out.write(content_types) else out.write(template_zip.read(entry.name)) end end unless @image_files.empty? #stream the image files into the media folder using open-uri @image_files.each do |hash| out.put_next_entry("word/media/#{hash[:filename]}") open(hash[:url], 'rb') do |f| out.write(f.read) end end end %w( word/_rels/header.xml.rels word/_rels/footer.xml.rels ).each do |f| if @replaceable_files[f] out.put_next_entry f out.write(@replaceable_files[f]) end end end buffer.string end end
replace_files(html, extras = false)
click to toggle source
# File lib/avv2word/document.rb, line 168 def replace_files(html, extras = false) html = '<body></body>' if html.nil? || html.empty? header_html = (html =~ /(<header>.*?<\/header>)/m ? $1 : '') footer_html = (html =~ /(<footer>.*?<\/footer>)/m ? $1 : '') original_source = Nokogiri::HTML(html.gsub(/>[\t\n\r\f]+</, '><')) # whitespace characters without space; nokogiri changes to \u00a0 header = Nokogiri::HTML(header_html.gsub(/>\s+</, '><')) footer = Nokogiri::HTML(footer_html.gsub(/>\s+</, '><')) @header = (header_html.empty? ? false : true) @footer = (footer_html.empty? ? false : true) transform_and_replace(header, xslt_path('header'), Document.header_xml_file) transform_and_replace(footer, xslt_path('footer'), Document.footer_xml_file) transform_and_replace(original_source, xslt_path('relations'), Document.relations_xml_file) source = xslt(stylesheet_name: 'cleanup').transform(original_source) transform_and_replace(source, xslt_path('numbering'), Document.numbering_xml_file) transform_doc_xml(source, extras) local_images(source) local_images(footer, :footer) if @footer local_images(header, :header) if @header add_footnotes(source.css("footnote").map{ |footnote| footnote.text }) add_comments(source.css("comment")) unless source.css("comment").empty? end
transform_doc_xml(source, extras = false)
click to toggle source
# File lib/avv2word/document.rb, line 194 def transform_doc_xml(source, extras = false) transformed_source = xslt(stylesheet_name: 'cleanup').transform(source) transformed_source = xslt(stylesheet_name: 'inline_elements').transform(transformed_source) transform_and_replace(transformed_source, document_xslt(extras), Document.doc_xml_file, extras) end
Private Instance Methods
add_comments(comments)
click to toggle source
# File lib/avv2word/document.rb, line 202 def add_comments(comments) @replaceable_files["word/comments.xml"] = %(<?xml version="1.0" encoding="UTF-8"?><w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" 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:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" 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 w15 w16se w16cid wp14">) comments.each_with_index do |comment,id| time = comment.attributes["time"].value # 2020-04-29T11:49:00Z author = comment.attributes["author"].value message = comment.attributes["message"].value initials = comment.attributes["initials"].value comment_generator = %Q(<w:comment w:id="#{id}" w:author="#{author}" w:date="#{time}" w:initials="#{initials}"><w:p w14:paraId="2F08968D" w14:textId="06CE6739" w:rsidR="009A6661" w:rsidRDefault="009A6661"><w:pPr><w:pStyle w:val="CommentText"/></w:pPr><w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:annotationRef/></w:r><w:r><w:t>#{message}</w:t></w:r></w:p></w:comment>) @replaceable_files["word/comments.xml"] += comment_generator end @replaceable_files["word/comments.xml"] += "</w:comments>" rels = Nokogiri::XML(@replaceable_files["word/_rels/document.xml.rels"]) new_rel = %(<Relationship Id="rIdXX" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="comments.xml"/>) new_rel.sub!(/XX/, (rels.xpath("//xmlns:Relationship").size+1).to_s ) rels.children.first << Nokogiri::XML.parse(new_rel).child @replaceable_files["word/_rels/document.xml.rels"] = rels.to_xml insert_nodes_to_xml(Document.content_types_xml_file, ['<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>']) end
add_footnotes(footnotes)
click to toggle source
# File lib/avv2word/document.rb, line 223 def add_footnotes(footnotes) if footnotes.empty? @replaceable_files["word/footnotes.xml"] = "" return end #text = Zip::File.open("templates/default.docx"){|zipfile| zipfile.read("word/footnotes.xml")} text = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><w:footnotes xmlns:wpc=\"http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas\" xmlns:cx=\"http://schemas.microsoft.com/office/drawing/2014/chartex\" xmlns:cx1=\"http://schemas.microsoft.com/office/drawing/2015/9/8/chartex\" xmlns:cx2=\"http://schemas.microsoft.com/office/drawing/2015/10/21/chartex\" xmlns:cx3=\"http://schemas.microsoft.com/office/drawing/2016/5/9/chartex\" xmlns:cx4=\"http://schemas.microsoft.com/office/drawing/2016/5/10/chartex\" xmlns:cx5=\"http://schemas.microsoft.com/office/drawing/2016/5/11/chartex\" xmlns:cx6=\"http://schemas.microsoft.com/office/drawing/2016/5/12/chartex\" xmlns:cx7=\"http://schemas.microsoft.com/office/drawing/2016/5/13/chartex\" xmlns:cx8=\"http://schemas.microsoft.com/office/drawing/2016/5/14/chartex\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:aink=\"http://schemas.microsoft.com/office/drawing/2016/ink\" xmlns:am3d=\"http://schemas.microsoft.com/office/drawing/2017/model3d\" 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:w15=\"http://schemas.microsoft.com/office/word/2012/wordml\" xmlns:w16cid=\"http://schemas.microsoft.com/office/word/2016/wordml/cid\" xmlns:w16se=\"http://schemas.microsoft.com/office/word/2015/wordml/symex\" 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 w15 w16se w16cid wp14\"><w:footnote w:type=\"separator\" w:id=\"-1\"><w:p w:rsidR=\"00C3170C\" w:rsidRDefault=\"00C3170C\" w:rsidP=\"001755D5\"><w:r><w:separator/></w:r></w:p></w:footnote><w:footnote w:type=\"continuationSeparator\" w:id=\"0\"><w:p w:rsidR=\"00C3170C\" w:rsidRDefault=\"00C3170C\" w:rsidP=\"001755D5\"><w:r><w:continuationSeparator/></w:r></w:p></w:footnote><w:footnote w:id=\"1\"><w:p w:rsidR=\"001755D5\" w:rsidRDefault=\"001755D5\"><w:pPr><w:pStyle w:val=\"FootnoteText\"/></w:pPr><w:r><w:rPr><w:rStyle w:val=\"FootnoteReference\"/></w:rPr><w:footnoteRef/></w:r><w:r><w:t xml:space=\"preserve\"> How are you?</w:t></w:r></w:p></w:footnote></w:footnotes>" doc = Nokogiri::XML.parse( text ) fn_node = doc.xpath("//w:footnote").last footnotes.each_with_index do |footnote,i| fn_node2 = fn_node.clone fn_node2.parent = fn_node.parent fn_node2["w:id"] = (fn_node["w:id"].to_i + i).to_s fn_node2.xpath("w:p/w:r/w:t").first.content = " " + footnote end fn_node.remove @replaceable_files["word/footnotes.xml"] = doc.to_xml # add relation rels = Nokogiri::XML(@replaceable_files["word/_rels/document.xml.rels"]) [ #'<Relationship Id="rIdXX" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes" Target="endnotes.xml"/>', '<Relationship Id="rIdXX" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml"/>' ].each do |new_rel| new_rel.sub!(/XX/, (rels.xpath("//xmlns:Relationship").size+1).to_s ) rels.children.first << Nokogiri::XML.parse(new_rel).child end @replaceable_files["word/_rels/document.xml.rels"] = rels.to_xml # settings if @replaceable_files["word/settings.xml"] sets = Nokogiri::XML(@replaceable_files["word/settings.xml"]) else sets = nil Zip::File.open(@template_path) do |zipfile| sets = Nokogiri::XML.parse(zipfile.read("word/settings.xml")) end end ['<w:footnotePr><w:footnote w:id="-1"/><w:footnote w:id="0"/></w:footnotePr>', #'<w:endnotePr><w:endnote w:id="-1"/><w:endnote w:id="0"/></w:endnotePr>' ].each do |new_type| sets.children.first << Nokogiri::XML.parse(new_type).child end @replaceable_files["word/settings.xml"] = sets.to_xml # styles if @replaceable_files["word/styles.xml"] styles = Nokogiri::XML(@replaceable_files["word/styles.xml"]) else styles = nil Zip::File.open(@template_path) do |zipfile| styles = Nokogiri::XML.parse(zipfile.read("word/styles.xml")) end end [ '<w:style w:type="paragraph" w:styleId="FootnoteText"><w:name w:val="footnote text"/><w:basedOn w:val="Normal"/><w:link w:val="FootnoteTextChar"/><w:uiPriority w:val="99"/><w:semiHidden/><w:unhideWhenUsed/><w:rsid w:val="008D12FB"/><w:rsid w:val="008D12FB"/><w:rPr><w:sz w:val="16"/></w:rPr></w:style>', '<w:style w:type="character" w:customStyle="1" w:styleId="FootnoteTextChar"><w:name w:val="Footnote Text Char"/><w:basedOn w:val="DefaultParagraphFont"/><w:link w:val="FootnoteText"/><w:uiPriority w:val="99"/><w:semiHidden/></w:style>', '<w:style w:type="character" w:styleId="FootnoteReference"><w:name w:val="footnote reference"/><w:basedOn w:val="DefaultParagraphFont"/><w:uiPriority w:val="99"/><w:semiHidden/><w:unhideWhenUsed/><w:rsid w:val="008D12FB"/><w:rPr><w:vertAlign w:val="superscript"/></w:rPr></w:style>' ].each do |new_style| styles.children.first << Nokogiri::XML.parse(new_style).child end [ '<w:lsdException w:name="footnote text" w:semiHidden="1" w:unhideWhenUsed="1"/>', '<w:lsdException w:name="footnote reference" w:semiHidden="1" w:unhideWhenUsed="1"/>' ].each do |new_latent_style| styles.xpath("/w:styles/w:latentStyles").first << Nokogiri::XML.parse(new_latent_style).child end @replaceable_files["word/styles.xml"] = styles.to_xml # content type insert_nodes_to_xml(Document.content_types_xml_file, [ # '<Override PartName="/word/endnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"/>', '<Override PartName="/word/footnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"/>' ]) end
content_type_from_extension(ext)
click to toggle source
get extension from filename and clean to match content_types
# File lib/avv2word/document.rb, line 362 def content_type_from_extension(ext) ext == "jpg" ? "jpeg" : ext end
inject_image_content_types(source)
click to toggle source
inject the required content_types into the [content_types].xml fileā¦
# File lib/avv2word/document.rb, line 367 def inject_image_content_types(source) doc = Nokogiri::XML(source) #get a list of all extensions currently in content_types file existing_exts = doc.css("Default").map { |node| node.attribute("Extension").value }.compact #get a list of extensions we need for our images required_exts = @image_files.map{ |i| i[:ext] } #workout which required extensions are missing from the content_types file missing_exts = (required_exts - existing_exts).uniq #inject missing extensions into document missing_exts.each do |ext| doc.at_css("Types").add_child( "<Default Extension='#{ext}' ContentType='image/#{content_type_from_extension(ext)}'/>") end #return the amended source to be saved into the zip doc.to_s end
inject_relations(relations, noko_html_source)
click to toggle source
# File lib/avv2word/document.rb, line 296 def inject_relations(relations, noko_html_source) rel_doc = Nokogiri::XML(relations) relations_root = rel_doc.css("Relationships").first relations_size = relations_root.css("Relationship").size img_counter = 0 html = noko_html_source.to_xml html.scan(/<img.*?>|<a.*?\/a>/).each_with_index do |link,i| new_relation = relations_root.css("Relationship").last.clone case link when /^<a/ a_href = link.match(/href="(.*?)"/)[1] new_relation["Id"] = "rId#{i + 1 + relations_size}" new_relation["Type"] = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" new_relation["TargetMode"] = "External" new_relation["Target"] = a_href when /^<img/ img_counter += 1 img_src = link.match(/src="(.*?)"/)[1] new_relation["Id"] = "rId#{i + 1 + relations_size}" new_relation["Type"] = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" new_relation["Target"] = "media/image#{img_counter}#{File.extname(img_src)}" new_relation.remove_attribute("TargetMode") if new_relation["TargetMode"] end relations_root.add_child new_relation end rel_doc.to_xml end
insert_nodes_to_xml(target_xml_file, inputs)
click to toggle source
# File lib/avv2word/document.rb, line 388 def insert_nodes_to_xml(target_xml_file, inputs) xml_content = if @replaceable_files[target_xml_file] Nokogiri::XML(@replaceable_files[target_xml_file]) else Zip::File.open(@template_path) do |zipfile| Nokogiri::XML.parse(zipfile.read(target_xml_file)) end end inputs.each do |new_node| xml_content.children.first << Nokogiri::XML.parse(new_node).child end @replaceable_files[target_xml_file] = xml_content.to_xml end
local_images(source, type = nil)
click to toggle source
generates an array of hashes with filename and full url for all images to be embeded in the word document
# File lib/avv2word/document.rb, line 353 def local_images(source, type = nil) source.css('img').sort_by{|e| e.attributes['src'].value}.each_with_index do |image,i| filename = image['data-filename'] ? image['data-filename'] : image['src'].split("/").last ext = File.extname(filename).delete(".").downcase @image_files << { filename: "image#{@image_files.size+1}.#{ext}", url: image['src'], ext: ext } end end
transform_and_replace(source, stylesheet_path, file, remove_ns = false)
click to toggle source
# File lib/avv2word/document.rb, line 327 def transform_and_replace(source, stylesheet_path, file, remove_ns = false) stylesheet = xslt(stylesheet_path: stylesheet_path) content = stylesheet.apply_to(source) content.gsub!(/\s*xmlns:(\w+)="(.*?)\s*"/, '') if remove_ns if ( file =~ /document\.xml\.rels/ ) && (( source.css("a").size > 0 ) || ( source.css("img").size > 0 )) content = inject_relations(content, source) elsif file =~ /header/i @replaceable_files["word/_rels/header.xml.rels"] = %(<?xml version="1.0" encoding="UTF-8"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\n</Relationships>) source.css("img").each_with_index do |noko_img,i| # <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image3.png"/> new_head_rel = %Q(<Relationship Id="rId#{i+10}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/#{File.basename(noko_img["src"])}"/>) @replaceable_files["word/_rels/header.xml.rels"].sub!(/<\/Relationships>/, new_head_rel + "</Relationships>") end elsif file =~ /footer/i @replaceable_files["word/_rels/footer.xml.rels"] = %(<?xml version="1.0" encoding="UTF-8"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\n</Relationships>) source.css("img").each_with_index do |noko_img,i| new_foot_rel = %Q(<Relationship Id="rId#{i+10}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/#{File.basename(noko_img["src"])}"/>) @replaceable_files["word/_rels/footer.xml.rels"].sub!(/<\/Relationships>/, new_foot_rel + "</Relationships>") end end @replaceable_files[file] = content end