Export Adobe FDF And XFDF From ActiveRecord
Join the DZone community and get the full member experience.
Join For FreeI needed to export XFDF from an application. This code is kind of untested, but is based on the solution I came up with. I would appreciate responses/modifications. It's very straightforward mixin stuff, for the most part.
# (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:
# :filename - The filename of the associated PDF document. REQUIRED!
# :indentation - How much to indent the resulting XFDF (XML)
# :include - Which associated models to include in the generated XFDF.
# :exclude_attributes - Which attributes of the current model should not be exported. By default all non-internal attributes are exported (i.e. everything but _id fields).
# :include_attributes - 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).
# :attributes - 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:
# :filename - The filename of the associated PDF document. REQUIRED!
# :include - Which associated models to include in the generated FDF.
# :exclude_attributes - Which attributes of the current model should not be exported. By default all non-internal attributes are exported (i.e. everything but _id fields).
# :include_attributes - 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).
# :attributes - 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 << "<>"
else
data << "<>"
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
Opinions expressed by DZone contributors are their own.
Comments