require 'pathname' require 'sablon/document_object_model/file_handler' module Sablon module DOM # Adds new relationships to the entry's corresponding relationships file class Relationships < FileHandler # # extends the Model class so it now has an "add_relationship" method def self.extend_model(model_klass) super do # # adds a relationship to the rels file for the current entry define_method(:add_relationship) do |rel_attr| # detemine name of rels file to augment rels_name = Relationships.rels_entry_name_for(@current_entry) # create the file if needed and update DOM create_entry_if_not_exist(rels_name, Relationships.file_template) @dom[rels_name].add_relationship(rel_attr) end # # adds file to the /word/media folder without overwriting an # existing file define_method(:add_media) do |name, data, rel_attr| rel_attr[:Target] = "media/#{name}" extension = name.match(/\.(\w+?)$/).to_a[1] type = rel_attr[:Type].match(%r{/(\w+?)$}).to_a[1] + "/#{extension}" # if @zip_contents["word/#{rel_attr[:Target]}"] names = @zip_contents.keys.map { |n| File.basename(n) } pattern = "^(\\d+)-#{name}" max_val = names.collect { |n| n.match(pattern).to_a[1].to_i }.max rel_attr[:Target] = "media/#{max_val + 1}-#{name}" end # # add the content to the zip and create the relationship @zip_contents["word/#{rel_attr[:Target]}"] = data add_content_type(extension, type) add_relationship(rel_attr) end # # locates an existing rId in the approprirate rels file define_method(:find_relationship_by) do |attribute, value, entry = nil| entry = @current_entry if entry.nil? # find the rels file and search it if it exists rels_name = Relationships.rels_entry_name_for(entry) return unless @dom[rels_name] # @dom[rels_name].find_relationship_by(attribute, value) end end end def self.file_template <<-XML.gsub(/^\s+|\n/, '') XML end def self.rels_entry_name_for(entry_name) par_dir = Pathname.new(File.dirname(entry_name)) par_dir.join('_rels', "#{File.basename(entry_name)}.rels").to_s end # Sets up the class instance to handle new relationships for a document. # I only care about tags that have an integer component def initialize(xml_node) super # @relationships = xml_node.root @max_rid = max_attribute_value('Relationship', 'Id') end # Finds the maximum value of an attribute by converting it to an # integer. Non numeric portions of values are ignored. def max_attribute_value(selector, attr_name) super(@relationships, selector, attr_name, query_method: :css) end # adds a new relationship and returns the corresponding rId for it def add_relationship(rel_attr) rel_attr['Id'] = "rId#{next_rid}" @relationships << relationship_tag(rel_attr) # rel_attr['Id'] end # Reurns an XML node based on the attribute value or nil if one does # not exist def find_relationship_by(attribute, value) @relationships.css(%(Relationship[#{attribute}="#{value}"])).first end private # increments the max rid and returns it def next_rid @max_rid += 1 end # Builds the relationship WordML tag and returns it def relationship_tag(rel_attr) attr_str = rel_attr.map { |k, v| %(#{k}="#{v}") }.join(' ') "" end end end end