Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to create mail merge fields inside Caracal documents #120

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions lib/caracal/core/models/mail_merge_model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
require 'caracal/core/models/base_model'


module Caracal
module Core
module Models

# This class encapsulates the logic needed to store and manipulate
# text data.
#
class MailMergeModel < BaseModel

#--------------------------------------------------
# Configuration
#--------------------------------------------------

# accessors
attr_reader :mail_merge_content
attr_reader :mail_merge_style
attr_reader :mail_merge_font
attr_reader :mail_merge_color
attr_reader :mail_merge_size
attr_reader :mail_merge_bold
attr_reader :mail_merge_italic
attr_reader :mail_merge_underline
attr_reader :mail_merge_bgcolor
attr_reader :mail_merge_highlight_color
attr_reader :mail_merge_vertical_align



#--------------------------------------------------
# Public Methods
#--------------------------------------------------

#========== GETTERS ===============================

# .run_attributes
def run_attributes
{
style: mail_merge_style,
font: mail_merge_font,
color: mail_merge_color,
size: mail_merge_size,
bold: mail_merge_bold,
italic: mail_merge_italic,
underline: mail_merge_underline,
bgcolor: mail_merge_bgcolor,
highlight_color: mail_merge_highlight_color,
vertical_align: mail_merge_vertical_align,
no_proof: true
}
end


#========== SETTERS ===============================

# booleans
[:bold, :italic, :underline].each do |m|
define_method "#{ m }" do |value|
instance_variable_set("@mail_merge_#{ m }", !!value)
end
end

# integers
[:size].each do |m|
define_method "#{ m }" do |value|
instance_variable_set("@mail_merge_#{ m }", value.to_i)
end
end

# strings
[:bgcolor, :color, :content, :font, :highlight_color, :style].each do |m|
define_method "#{ m }" do |value|
instance_variable_set("@mail_merge_#{ m }", value.to_s)
end
end

# symbols
[:vertical_align].each do |m|
define_method "#{ m }" do |value|
instance_variable_set("@mail_merge_#{ m }", value.to_s.to_sym)
end
end


#========== VALIDATION ============================

def valid?
a = [:content]
a.map { |m| send("mail_merge_#{ m }") }.compact.size == a.size
end


#--------------------------------------------------
# Private Methods
#--------------------------------------------------
private

def option_keys
[:content, :style, :font, :color, :size, :bold, :italic, :underline, :bgcolor, :highlight_color, :vertical_align]
end

def method_missing(method, *args, &block)
# I'm on the fence with respect to this implementation. We're ignoring
# :method_missing errors to allow syntax flexibility for paragraph-type
# models. The issue is the syntax format of those models--the way we pass
# the content value as a special argument--coupled with the model's
# ability to accept nested instructions.
#
# By ignoring method missing errors here, we can pass the entire paragraph
# block in the initial, built-in call to :text.
end

end

end
end
end
28 changes: 27 additions & 1 deletion lib/caracal/core/models/paragraph_model.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# encoding: UTF-8

require 'caracal/core/models/base_model'
require 'caracal/core/models/bookmark_model'
require 'caracal/core/models/link_model'
require 'caracal/core/models/text_model'
require 'caracal/core/models/mail_merge_model'
require 'caracal/errors'


Expand Down Expand Up @@ -31,7 +34,17 @@ class ParagraphModel < BaseModel
# initialization
def initialize(options={}, &block)
content = options.delete(:content) { "" }
text content, options.dup, &block

# Determine if this is actually a mail merge field
if content.start_with?('«') && content.end_with?('»')
fields = content.scan(/«(.*)»/)
if fields.count > 0
mail_merge fields.last.first, options.dup, &block
end
else
text content, options.dup, &block
end

super options, &block
end

Expand Down Expand Up @@ -162,6 +175,19 @@ def text(*args, &block)
model
end

# .mail_merge
def mail_merge(*args, &block)
options = Caracal::Utilities.extract_options!(args)
options.merge!({ content: args.first }) if args.first

model = Caracal::Core::Models::MailMergeModel.new(options, &block)
if model.valid?
runs << model
else
raise Caracal::Errors::InvalidModelError, ':text method must receive a string for the display text.'
end
model
end

#========== VALIDATION ============================

Expand Down
12 changes: 12 additions & 0 deletions lib/caracal/renderers/document_renderer.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# encoding: UTF-8

require 'nokogiri'

require 'caracal/renderers/xml_renderer'
Expand Down Expand Up @@ -75,6 +77,7 @@ def render_run_attributes(xml, model, paragraph_level=false)
else
xml['w'].rPr do
unless attrs.empty?
xml['w'].noProof if attrs[:no_proof]
xml['w'].rStyle( { 'w:val' => attrs[:style] }) unless attrs[:style].nil?
xml['w'].color( { 'w:val' => attrs[:color] }) unless attrs[:color].nil?
xml['w'].sz( { 'w:val' => attrs[:size] }) unless attrs[:size].nil?
Expand Down Expand Up @@ -303,6 +306,15 @@ def render_text(xml, model)
end
end

def render_mailmerge(xml, model)
xml['w'].fldSimple({ 'w:instr' => " MERGEFIELD #{model.mail_merge_content} \\* MERGEFORMAT "}) do
xml['w'].r do
render_run_attributes(xml, model, false)
xml['w'].t({ 'xml:space' => 'preserve' }, "«#{model.mail_merge_content}»")
end
end
end

def render_table(xml, model)
borders = %w(top left bottom right horizontal vertical).select do |m|
model.send("table_border_#{ m }_size") > 0
Expand Down
154 changes: 154 additions & 0 deletions spec/lib/caracal/core/models/mail_merge_model.rb.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
require 'spec_helper'

describe Caracal::Core::Models::MailMergeModel do
subject do
described_class.new do
content '=test'
font 'Courier New'
color '666666'
size 20
bold false
italic false
underline true
bgcolor 'cccccc'
vertical_align :subscript
highlight_color 'yellow'
end
end

#-------------------------------------------------------------
# Configuration
#-------------------------------------------------------------

describe 'configuration tests' do

# accessors
describe 'accessors' do
it { expect(subject.mail_merge_content).to eq '=test' }
it { expect(subject.mail_merge_font).to eq 'Courier New' }
it { expect(subject.mail_merge_color).to eq '666666' }
it { expect(subject.mail_merge_size).to eq 20 }
it { expect(subject.mail_merge_bold).to eq false }
it { expect(subject.mail_merge_italic).to eq false }
it { expect(subject.mail_merge_underline).to eq true }
it { expect(subject.mail_merge_bgcolor).to eq 'cccccc' }
it { expect(subject.mail_merge_highlight_color).to eq 'yellow' }
it { expect(subject.mail_merge_vertical_align).to eq :subscript }
end

end


#-------------------------------------------------------------
# Public Methods
#-------------------------------------------------------------

describe 'public method tests' do

#=============== GETTERS ==========================

# .run_attributes
describe '.run_attributes' do
let(:expected) { { style: nil, font: 'Courier New', color: '666666', size: 20, bold: false, italic: false, underline: true, bgcolor: 'cccccc', highlight_color: 'yellow', vertical_align: :subscript, no_proof: true } }

it { expect(subject.run_attributes).to eq expected }
end


#=============== SETTERS ==========================

# booleans
describe '.bold' do
before { subject.bold(true) }

it { expect(subject.mail_merge_bold).to eq true }
end
describe '.italic' do
before { subject.italic(true) }

it { expect(subject.mail_merge_italic).to eq true }
end
describe '.underline' do
before { subject.underline(true) }

it { expect(subject.mail_merge_underline).to eq true }
end

# integers
describe '.size' do
before { subject.size(24) }

it { expect(subject.mail_merge_size).to eq 24 }
end

# strings
describe '.bgcolor' do
before { subject.bgcolor('dddddd') }

it { expect(subject.mail_merge_bgcolor).to eq 'dddddd' }
end
describe '.color' do
before { subject.color('999999') }

it { expect(subject.mail_merge_color).to eq '999999' }
end
describe '.content' do
before { subject.content('Something Else') }

it { expect(subject.mail_merge_content).to eq 'Something Else' }
end
describe '.font' do
before { subject.font('Palantino') }

it { expect(subject.mail_merge_font).to eq 'Palantino' }
end
describe '.hightlight_color' do
before { subject.highlight_color('green') }

it { expect(subject.mail_merge_highlight_color).to eq 'green' }
end

#symbols
describe '.vertical_align' do
before { subject.vertical_align(:superscript) }

it { expect(subject.mail_merge_vertical_align).to eq :superscript }
end

#=============== VALIDATION ===========================

describe '.valid?' do
describe 'when required attributes provided' do
it { expect(subject.valid?).to eq true }
end
[:content].each do |prop|
describe "when #{ prop } nil" do
before do
allow(subject).to receive("mail_merge_#{ prop }").and_return(nil)
end

it { expect(subject.valid?).to eq false }
end
end
end

end


#-------------------------------------------------------------
# Private Methods
#-------------------------------------------------------------

describe 'private method tests' do

# .option_keys
describe '.option_keys' do
let(:actual) { subject.send(:option_keys).sort }
let(:expected) { [:bgcolor, :bold, :color, :content, :font, :highlight_color, :italic, :size, :style, :underline, :vertical_align].sort }

it { expect(actual).to eq expected }
end

end

end
9 changes: 9 additions & 0 deletions spec/lib/caracal/core/models/paragraph_model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@
it { expect(subject.runs.size).to eq length + 1 }
end

# .mail_merge
describe '.mail_merge' do
let!(:length) { subject.runs.length }

before { subject.mail_merge '=test' }

it { expect(subject.runs.size).to eq length + 1 }
end

# .bookmark
describe '.bookmark_start' do
let!(:length) { subject.runs.length }
Expand Down