Class: Sheng::MergeField
- Inherits:
-
Object
show all
- Defined in:
- lib/sheng/merge_field.rb
Defined Under Namespace
Classes: NotAMergeFieldError
Constant Summary
collapse
- MATH_TOKENS =
%w[+ - / * ( )]
- REGEXES =
{
instruction_text: /^\s*MERGEFIELD(.*)\\\* MERGEFORMAT\s*$/,
key_string: /^(?<prefix>start:|end:|if:|end_if:|unless:|end_unless:)?\s*(?<key>[^\|]+)\s*\|?(?<filters>.*)?/
}
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
Constructor Details
#initialize(element) ⇒ MergeField
Returns a new instance of MergeField.
23
24
25
26
27
28
|
# File 'lib/sheng/merge_field.rb', line 23
def initialize(element)
@element = element
@xml_document = element.document
@instruction_text = Sheng::Support.(element)
@errors = []
end
|
Instance Attribute Details
#element ⇒ Object
Returns the value of attribute element.
21
22
23
|
# File 'lib/sheng/merge_field.rb', line 21
def element
@element
end
|
#errors ⇒ Object
Returns the value of attribute errors.
21
22
23
|
# File 'lib/sheng/merge_field.rb', line 21
def errors
@errors
end
|
#xml_document ⇒ Object
Returns the value of attribute xml_document.
21
22
23
|
# File 'lib/sheng/merge_field.rb', line 21
def xml_document
@xml_document
end
|
Class Method Details
.from_element(element) ⇒ Object
14
15
16
17
18
|
# File 'lib/sheng/merge_field.rb', line 14
def from_element(element)
new(element)
rescue NotAMergeFieldError => e
nil
end
|
Instance Method Details
#==(other) ⇒ Object
30
31
32
|
# File 'lib/sheng/merge_field.rb', line 30
def ==(other)
other.is_a?(self.class) && other.element == element
end
|
#add_previous_sibling(fragment_to_add) ⇒ Object
148
149
150
151
152
153
154
155
156
|
# File 'lib/sheng/merge_field.rb', line 148
def add_previous_sibling(fragment_to_add)
if inline?
[xml].flatten.first.add_previous_sibling(fragment_to_add)
elsif is_table_row_marker?
containing_element.ancestors[1].add_previous_sibling(fragment_to_add)
else
containing_element.add_previous_sibling(fragment_to_add)
end
end
|
#block_prefix ⇒ Object
86
87
88
89
90
91
|
# File 'lib/sheng/merge_field.rb', line 86
def block_prefix
@potential_prefix ||= begin
potential_prefix = raw_key.match(REGEXES[:key_string])[:prefix]
potential_prefix && potential_prefix.gsub(/\:$/, '')
end
end
|
#block_type ⇒ Object
77
78
79
80
81
82
83
84
|
# File 'lib/sheng/merge_field.rb', line 77
def block_type
return nil unless block_prefix
if ["start", "end"].include?(block_prefix)
Sequence
else
ConditionalBlock
end
end
|
#comma_series_conjunction ⇒ Object
109
110
111
112
113
114
115
|
# File 'lib/sheng/merge_field.rb', line 109
def comma_series_conjunction
if filters.detect { |f| f =~ /^series_with_commas\((.*)\)$/ }
$1
else
"and"
end
end
|
#containing_element ⇒ Object
131
132
133
134
|
# File 'lib/sheng/merge_field.rb', line 131
def containing_element
parents_until_container = new_style? ? 2 : 1
element.ancestors[parents_until_container - 1]
end
|
#filter_value(value) ⇒ Object
252
253
254
255
256
257
|
# File 'lib/sheng/merge_field.rb', line 252
def filter_value(value)
filters.inject(value) { |val, filter_string|
filterer = Filters.filter_for(filter_string)
filterer.filter(val)
}
end
|
#filters ⇒ Object
42
43
44
45
|
# File 'lib/sheng/merge_field.rb', line 42
def filters
match = raw_key.match(REGEXES[:key_string])
match[:filters].split("|").map(&:strip)
end
|
#get_value(data_set) ⇒ Object
227
228
229
230
231
232
233
234
235
236
237
238
239
|
# File 'lib/sheng/merge_field.rb', line 227
def get_value(data_set)
interpolated_string = key_parts.map { |token|
if Support.is_numeric?(token) || MATH_TOKENS.include?(token)
token
else
data_set.fetch(token)
end
}.join(" ")
return interpolated_string unless key_has_math?
Dentaku::Calculator.new.evaluate!(interpolated_string.gsub(",", ""))
end
|
#in_table_row? ⇒ Boolean
140
141
142
|
# File 'lib/sheng/merge_field.rb', line 140
def in_table_row?
containing_element.ancestors[1] && containing_element.ancestors[1].name == "tr"
end
|
#inline? ⇒ Boolean
144
145
146
|
# File 'lib/sheng/merge_field.rb', line 144
def inline?
containing_element.children.text != xml.text
end
|
#interpolate(data_set) ⇒ Object
241
242
243
244
245
246
247
248
249
250
|
# File 'lib/sheng/merge_field.rb', line 241
def interpolate(data_set)
value = get_value(data_set)
replace_mergefield(filter_value(value))
rescue DataSet::KeyNotFound, Dentaku::UnboundVariableError, Filters::UnsupportedFilterError => e
@errors << e
nil
end
|
#is_end? ⇒ Boolean
117
118
119
|
# File 'lib/sheng/merge_field.rb', line 117
def is_end?
block_prefix && block_prefix.match(/^end/)
end
|
#is_start? ⇒ Boolean
93
94
95
|
# File 'lib/sheng/merge_field.rb', line 93
def is_start?
block_prefix && !block_prefix.match(/^end/)
end
|
#is_table_row_marker? ⇒ Boolean
136
137
138
|
# File 'lib/sheng/merge_field.rb', line 136
def is_table_row_marker?
in_table_row? && (is_start? || is_end?)
end
|
#iteration_variable ⇒ Object
97
98
99
100
101
102
103
|
# File 'lib/sheng/merge_field.rb', line 97
def iteration_variable
if filters.detect { |f| f =~ /^as\((.*)\)$/ }
$1.to_sym
else
:item
end
end
|
#key ⇒ Object
38
39
40
|
# File 'lib/sheng/merge_field.rb', line 38
def key
raw_key.match(REGEXES[:key_string])[:key].strip
end
|
#key_has_math? ⇒ Boolean
223
224
225
|
# File 'lib/sheng/merge_field.rb', line 223
def key_has_math?
!(MATH_TOKENS & key_parts).empty?
end
|
#key_parts ⇒ Object
195
196
197
198
199
200
201
202
203
204
|
# File 'lib/sheng/merge_field.rb', line 195
def key_parts
@key_parts ||= key.gsub(",", "").
gsub(".", "_DOTSEPARATOR_").
split(/\b|\s/).
map(&:strip).
reject(&:empty?).
map { |token|
token.gsub("_DOTSEPARATOR_", ".")
}
end
|
#new_style? ⇒ Boolean
34
35
36
|
# File 'lib/sheng/merge_field.rb', line 34
def new_style?
element.name == 'fldChar'
end
|
#next_element ⇒ Object
158
159
160
161
162
163
164
165
166
|
# File 'lib/sheng/merge_field.rb', line 158
def next_element
if inline?
[xml].flatten.last.next_element
elsif is_table_row_marker?
containing_element.ancestors[1].next_element
else
containing_element.next_element
end
end
|
#raw_key ⇒ Object
47
48
49
|
# File 'lib/sheng/merge_field.rb', line 47
def raw_key
@raw_key ||= @instruction_text.gsub(REGEXES[:instruction_text], '\1').strip
end
|
#remove ⇒ Object
121
122
123
124
125
126
127
128
129
|
# File 'lib/sheng/merge_field.rb', line 121
def remove
if inline?
xml.remove
elsif is_table_row_marker?
containing_element.ancestors[1].remove
else
containing_element.remove
end
end
|
#replace_mergefield(value) ⇒ Object
181
182
183
184
185
186
187
188
189
190
191
192
193
|
# File 'lib/sheng/merge_field.rb', line 181
def replace_mergefield(value)
value_as_string = if value.is_a?(BigDecimal)
value.to_s("F")
else
value.to_s
end
new_run = Sheng::Support.new_text_run(
value_as_string, xml_document: xml_document, style_run: styling_run
)
xml.before(new_run)
xml.remove
end
|
#required_hash(placeholder: nil) ⇒ Object
212
213
214
215
216
217
218
219
220
221
|
# File 'lib/sheng/merge_field.rb', line 212
def required_hash(placeholder: nil)
required_variables.inject({}) { |assembled, variable|
parts = variable.split(/\./)
last_key = parts.pop
hash = parts.reverse.inject(last_key => placeholder) do |memo, key|
memo = { key => memo }; memo
end
Sheng::Support.merge_required_hashes(assembled, hash)
}
end
|
#required_variables ⇒ Object
206
207
208
209
210
|
# File 'lib/sheng/merge_field.rb', line 206
def required_variables
key_parts.reject { |token|
Support.is_numeric?(token) || MATH_TOKENS.include?(token)
}
end
|
#series_with_commas? ⇒ Boolean
105
106
107
|
# File 'lib/sheng/merge_field.rb', line 105
def series_with_commas?
filters.detect { |f| f =~ /^series_with_commas/ }
end
|
#start_key ⇒ Object
67
68
69
70
71
72
73
74
75
|
# File 'lib/sheng/merge_field.rb', line 67
def start_key
if is_start?
"#{block_prefix}:#{key}"
elsif block_prefix == "end"
"start:#{key}"
else
"#{block_prefix.gsub(/^end_/, '')}:#{key}"
end
end
|
#styling_paragraph ⇒ Object
51
52
53
54
|
# File 'lib/sheng/merge_field.rb', line 51
def styling_paragraph
return nil if inline?
containing_element.at_xpath(".//w:pPr")
end
|
#styling_run ⇒ Object
56
57
58
59
60
61
62
63
64
65
|
# File 'lib/sheng/merge_field.rb', line 56
def styling_run
if new_style?
separator_field = element.ancestors[1].at_xpath(".//w:fldChar[contains(@w:fldCharType, 'separate')]")
if separator_field
separator_field.parent.next_element.at_xpath(".//w:rPr")
end
else
element.at_xpath(".//w:rPr")
end
end
|
#xml ⇒ Object
168
169
170
171
172
173
174
175
176
177
178
179
|
# File 'lib/sheng/merge_field.rb', line 168
def xml
return element unless new_style?
nodeset = Nokogiri::XML::NodeSet.new(xml_document)
current_node = element.parent
nodeset << current_node
loop do
current_node = current_node.next_element
nodeset << current_node
break if current_node.at_xpath("./w:fldChar[contains(@w:fldCharType, 'end')]")
end
nodeset
end
|