diff --git a/lib/caracal/core/models/mail_merge_model.rb b/lib/caracal/core/models/mail_merge_model.rb new file mode 100644 index 00000000..31594b05 --- /dev/null +++ b/lib/caracal/core/models/mail_merge_model.rb @@ -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 diff --git a/lib/caracal/core/models/paragraph_model.rb b/lib/caracal/core/models/paragraph_model.rb index 5eef1254..13abccfe 100644 --- a/lib/caracal/core/models/paragraph_model.rb +++ b/lib/caracal/core/models/paragraph_model.rb @@ -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' @@ -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 @@ -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 ============================ diff --git a/lib/caracal/renderers/document_renderer.rb b/lib/caracal/renderers/document_renderer.rb index b571f40b..a13f1a44 100644 --- a/lib/caracal/renderers/document_renderer.rb +++ b/lib/caracal/renderers/document_renderer.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + require 'nokogiri' require 'caracal/renderers/xml_renderer' @@ -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? @@ -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 diff --git a/spec/lib/caracal/core/models/mail_merge_model.rb.spec b/spec/lib/caracal/core/models/mail_merge_model.rb.spec new file mode 100644 index 00000000..1fae5eb2 --- /dev/null +++ b/spec/lib/caracal/core/models/mail_merge_model.rb.spec @@ -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 diff --git a/spec/lib/caracal/core/models/paragraph_model_spec.rb b/spec/lib/caracal/core/models/paragraph_model_spec.rb index c70f803e..da5f1ec9 100644 --- a/spec/lib/caracal/core/models/paragraph_model_spec.rb +++ b/spec/lib/caracal/core/models/paragraph_model_spec.rb @@ -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 }