# (X)FDF Export for ActiveRecord # Based on Justin Koivisto's FDF library for PHP # Author: Sean Cribbs, seancribbs_AT_gmail_DOT_com, http://seancribbs.com module FDF def self.included(base) base.extend ClassMethods end module ClassMethods # Options: # <tt>:filename</tt> - The filename of the associated PDF document. REQUIRED! # <tt>:indentation</tt> - How much to indent the resulting XFDF (XML) # <tt>:include</tt> - Which associated models to include in the generated XFDF. # <tt>:exclude_attributes</tt> - Which attributes of the current model should not be exported. By default all non-internal attributes are exported (i.e. everything but _id fields). # <tt>:include_attributes</tt> - Which attributes of the current model should be exported in addition to the default. By default all non-internal attributes are exported (i.e. everything but _id fields). # <tt>:attributes</tt> - Override which attributes to export. def exports_xfdf(options = {}) raise ArgumentError, "A :filename option must be specified." unless options[:filename] options[:indentation] ||= 2 options[:include] = options[:include].is_a?(Array) ? options[:include] : [options[:include]].compact unless included_modules.include? XFDFMethods class_inheritable_accessor :xfdf_options extend ClassMethods include XFDFMethods end self.xfdf_options = options end # Options: # <tt>:filename</tt> - The filename of the associated PDF document. REQUIRED! # <tt>:include</tt> - Which associated models to include in the generated FDF. # <tt>:exclude_attributes</tt> - Which attributes of the current model should not be exported. By default all non-internal attributes are exported (i.e. everything but _id fields). # <tt>:include_attributes</tt> - Which attributes of the current model should be exported in addition to the default. By default all non-internal attributes are exported (i.e. everything but _id fields). # <tt>:attributes</tt> - Override which attributes to export. def exports_fdf(options = {}) raise ArgumentError, "A :filename option must be specified." unless options[:filename] options[:include] = options[:include].is_a?(Array) ? options[:include] : [options[:include]].compact unless included_modules.include? FDFMethods class_inheritable_accessor :fdf_options extend ClassMethods include FDFMethods end self.fdf_options = options end end module XFDFMethods def to_xfdf(options = {}) options.reverse_merge! self.class.xfdf_options fields = Util.collect_values(self, self.class.content_columns.map(&:name), options) filename = options[:filename] xml = Builder::XmlMarkup.new :indentation => options[:indentation] xml.instruct! xml.xfdf("xmlns" => "http://ns.adobe.com/xfdf/", "xml:space" => "preserve") { xml.f :href => filename xml.fields { fields.each do |field, value| xml.field(:name => field) { if value.is_a? Array value.each {|item| xml.value(item.to_s) } else xml.value(value.to_s) end } end } } xml.target! end end module FDFMethods def to_fdf(options={}) options.reverse_merge! self.class.fdf_options fields = Util.collect_values(self, self.class.content_columns.map(&:name), options) filename = options[:filename] data = "%FDF-1.2\n%âã�?Ó\n1 0 obj\n<< \n/FDF << /Fields [ " fields.each do |field, value| if value.is_a? Array data << "<</T(#{field})/V[" value.each {|v| data << "(#{v.strip})"} data << "]>>" else data << "<</T(#{field})/V(#{value.strip})>>" end end end data << "] \n/F (#{filename}) /ID [ <#{MD5.md5(Time.now).to_s}>\n ] >>" << " \n>> \nendobj\ntrailer\n" << "<<\n/Root 1 0 R \n\n>>\n%%EOF\n" end module Util def self.collect_values(object, defaults, options = {}) attrs = [] if options[:attributes] attrs = stringify_all(options[:attributes]) rescue [] else [:include_attributes, :exclude_attributes].each do |opt| options[opt] = stringify_all(options[opt]) rescue [] end attrs = stringify_all(defaults) + options[:include_attributes] - options[:exclude_attributes] end fields = attrs.inject({}) do |hash, key| value = object.send(key) rescue nil hash.merge key => value end fields.merge collect_association_values(object, options) end def self.collect_association_values(object, options = {}) return {} if options[:include].blank? values = {} options[:include].each do |association| unless object.send(association).blank? models = object.send(association) unless models.is_a? Array columns = models.class.content_columns.map(&:name) values.merge! association_dump(association, models, columns) else models.each_with_index do |model, index| columns = model.class.content_columns.map(&:name) values.merge! association_dump("#{association.singularize}_#{index+1}", model, columns) end end end end values end def self.association_dump(prefix, object, attributes) attributes.inject({}) do |hash, attr| value = object.send(attr) rescue nil hash.merge "#{prefix}_#{attr}" => value end end def self.stringify_all(ary) ary.compact.map(&:to_s).uniq end end end ActiveRecord::Base.send :include, FDF
Also available from: http://pastie.caboo.se/38835