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 &nbsp; 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