tml 5.6.5 → 5.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c34d0e6209b1b34c4143f0b16ecbc73aec7a5324
4
- data.tar.gz: 20f9c409c786a9a67cb76e74eb3cdbe285f9eb3a
3
+ metadata.gz: a26bbf3c59deca21956a94bc33d811377797034e
4
+ data.tar.gz: fdd19956dcf43dc02228c8c43ea8687a8eb47d34
5
5
  SHA512:
6
- metadata.gz: 8ee0f8b41b9d66d8d1f05172df2318eade8f05c408f299137fb45e791b3cd03b0ac20da55e8c28cda300faa08c736c8f0d9dfdec49fbae47dfc4094cef5bf2dc
7
- data.tar.gz: 690f0c2581fcdc822a8e63c45265fdcce2eb2a4b9298a700dd9845b9429ef1911395acf1995946bbcd9aa7d91764e638277db9dc404d438421c16c9c3334e2af
6
+ metadata.gz: f5277433d7a88c672d46f9c214add49e06bb52752fd1e9f23b06f8c1fad3f0db9c40723c3d159078644b59ecfd8f6d372c7b8665f8e6781404788cf19b7ffd7c
7
+ data.tar.gz: 3e31327a1b93feffc6f86e1d974c0ba64e0a90aac225a154d18eb8c7de67316f1bf10438db03a88e40fe47c4a38fca0bbf6779e4ede7a5c77f8c70855e4755b0
data/lib/tml.rb CHANGED
@@ -32,7 +32,9 @@
32
32
 
33
33
  module Tml
34
34
  module Api end
35
- module Tokens end
35
+ module Tokens
36
+ module XMessage end
37
+ end
36
38
  module Tokenizers end
37
39
  module Rules end
38
40
  module Decorators end
@@ -64,7 +66,7 @@ module Tml
64
66
  end
65
67
  end
66
68
 
67
- %w(tml/base.rb tml tml/api tml/rules_engine tml/tokens tml/tokenizers tml/decorators tml/cache_adapters tml/cache tml/ext).each do |f|
69
+ %w(tml/base.rb tml tml/api tml/rules_engine tml/tokens tml/tokens/x_message tml/tokenizers tml/decorators tml/cache_adapters tml/cache tml/ext).each do |f|
68
70
  if f.index('.rb')
69
71
  require(File.expand_path(File.join(File.dirname(__FILE__), f)))
70
72
  next
@@ -237,7 +237,7 @@ class Tml::Application < Tml::Base
237
237
  source = Tml::Source.new(:source => source_key, :application => self)
238
238
  params << {
239
239
  :source => source_key,
240
- :keys => keys.values.collect{|tkey| tkey.to_hash(:label, :description, :locale, :level)}
240
+ :keys => keys.values.collect{|tkey| tkey.to_hash(:label, :description, :locale, :level, :syntax)}
241
241
  }
242
242
  source.reset_cache
243
243
  end
@@ -307,6 +307,8 @@ class Tml::Application < Tml::Base
307
307
 
308
308
  # Cache translations within application object
309
309
  def cache_translations(locale, key, new_translations)
310
+ return if new_translations.nil?
311
+
310
312
  self.translations ||= {}
311
313
  self.translations[locale] ||= {}
312
314
  self.translations[locale][key] = new_translations.collect do |t|
data/lib/tml/config.rb CHANGED
@@ -242,6 +242,7 @@ module Tml
242
242
  :trade => '&trade;', # TM
243
243
  },
244
244
  :decoration => {
245
+ :anchor => "<a href='{$href}'>{$0}</a>",
245
246
  :strong => '<strong>{$0}</strong>',
246
247
  :bold => '<strong>{$0}</strong>',
247
248
  :b => '<strong>{$0}</strong>',
@@ -280,6 +281,7 @@ module Tml
280
281
  :trade => '™',
281
282
  },
282
283
  :decoration => {
284
+ :anchor => '{$0}',
283
285
  :strong => '{$0}',
284
286
  :bold => '{$0}',
285
287
  :b => '{$0}',
@@ -409,6 +411,32 @@ module Tml
409
411
  # @default_level
410
412
  #end
411
413
 
414
+ def xmessage_rule_key_mapping
415
+ @rule_key_mapping ||= {
416
+ number: {
417
+ one: 'singular',
418
+ few: 'few',
419
+ many: 'many',
420
+ other: 'plural'
421
+ },
422
+ gender: {
423
+ male: 'male',
424
+ female: 'female',
425
+ neutral: 'neutral',
426
+ other: 'other',
427
+ },
428
+ date: {
429
+ future: 'future',
430
+ present: 'present',
431
+ past: 'past'
432
+ }
433
+ }
434
+ end
435
+
436
+ def xmessage_decoration_tokens
437
+ @xmessage_tokens ||= @default_tokens[:html][:decoration].keys
438
+ end
439
+
412
440
  def default_locale
413
441
  @locale[:default] || 'en'
414
442
  end
data/lib/tml/language.rb CHANGED
@@ -149,14 +149,17 @@ class Tml::Language < Tml::Base
149
149
  :description => params[:description],
150
150
  :locale => hash_value(params[:options], :locale) || Tml.session.block_option(:locale) || Tml.config.default_locale,
151
151
  :level => hash_value(params[:options], :level) || Tml.session.block_option(:level) || Tml.config.default_level,
152
+ :syntax => hash_value(params[:options], :syntax),
152
153
  :translations => []
153
154
  })
154
155
 
155
- # pp "Translating #{params[:label]} from: #{translation_key.locale.inspect} to #{locale.inspect}"
156
+ # pp "Translating #{params[:label]} from: #{translation_key.locale.inspect} to #{locale.inspect} with syntax #{translation_key.syntax}"
156
157
  # Tml.logger.info("Translating #{params[:label]} from: #{translation_key.locale.inspect} to #{locale.inspect}")
157
158
 
158
159
  params[:tokens] ||= {}
159
- params[:tokens][:viewing_user] ||= Tml.session.current_user
160
+ if params[:tokens].is_a?(Hash)
161
+ params[:tokens][:viewing_user] ||= Tml.session.current_user
162
+ end
160
163
 
161
164
  if Tml.config.disabled? or application.nil?
162
165
  return translation_key.substitute_tokens(params[:label], params[:tokens], self, params[:options]).tml_translated
@@ -212,6 +215,9 @@ class Tml::Language < Tml::Base
212
215
  end
213
216
 
214
217
  translation_key.translate(self, params[:tokens], params[:options]).tml_translated
218
+ rescue Exception => ex
219
+ pp ex, ex.backtrace
220
+ return params[:label]
215
221
  end
216
222
  alias :tr :translate
217
223
 
@@ -54,11 +54,11 @@ module Tml
54
54
  attr_accessor :text, :context, :tokens, :opts
55
55
 
56
56
  def self.supported_tokens
57
- [Tml::Tokens::Data, Tml::Tokens::Method, Tml::Tokens::Transform]
57
+ [Tml::Tokens::Data, Tml::Tokens::Method, Tml::Tokens::Transform, Tml::Tokens::Map]
58
58
  end
59
59
 
60
60
  def self.required?(label)
61
- label.index("{")
61
+ label.index('{')
62
62
  end
63
63
 
64
64
  def initialize(text, context={}, opts={})
@@ -84,10 +84,12 @@ module Tml
84
84
 
85
85
  def substitute(language, options = {})
86
86
  label = self.text
87
+
87
88
  tokens.each do |token|
88
89
  next unless token_allowed?(token)
89
90
  label = token.substitute(label, context, language, options)
90
91
  end
92
+
91
93
  label
92
94
  end
93
95
 
@@ -53,7 +53,7 @@ module Tml
53
53
  module Tokenizers
54
54
  class Decoration
55
55
 
56
- attr_reader :tokens, :fragments, :context, :text, :opts
56
+ attr_reader :tokens, :fragments, :context, :label, :opts
57
57
 
58
58
  RESERVED_TOKEN = 'tml'
59
59
 
@@ -63,14 +63,14 @@ module Tml
63
63
  RE_LONG_TOKEN_END = '\[\/[\w]*\]' # [/link]
64
64
  RE_HTML_TOKEN_START = '<[^\>]*>' # <link>
65
65
  RE_HTML_TOKEN_END = '<\/[^\>]*>' # </link>
66
- RE_TEXT = '[^\[\]<>]+' # '[\w\s!.:{}\(\)\|,?]*'
66
+ RE_TEXT = '[^\[\]<>]+' # '[\w\s!.:{}\(\)\|,?]*'
67
67
 
68
68
  def self.required?(label)
69
69
  label.index('[') or label.index('<')
70
70
  end
71
71
 
72
- def initialize(text, context = {}, opts = {})
73
- @text = "[#{RESERVED_TOKEN}]#{text}[/#{RESERVED_TOKEN}]"
72
+ def initialize(label, context = {}, opts = {})
73
+ @label = label
74
74
  @context = context
75
75
  @opts = opts
76
76
  tokenize
@@ -84,12 +84,12 @@ module Tml
84
84
  RE_HTML_TOKEN_START,
85
85
  RE_HTML_TOKEN_END,
86
86
  RE_TEXT].join('|')
87
- @fragments = text.scan(/#{re}/)
87
+ @fragments = "[#{RESERVED_TOKEN}]#{@label}[/#{RESERVED_TOKEN}]".scan(/#{re}/)
88
88
  @tokens = []
89
89
  end
90
90
 
91
91
  def parse
92
- return @text unless fragments
92
+ return @label unless fragments
93
93
  token = fragments.shift
94
94
 
95
95
  if token.match(/#{RE_SHORT_TOKEN_START}/)
@@ -136,66 +136,9 @@ module Tml
136
136
  tree
137
137
  end
138
138
 
139
- def default_decoration(token_name, token_value)
140
- default_decoration = Tml.config.default_token_value(normalize_token(token_name), :decoration)
141
-
142
- unless default_decoration
143
- Tml.logger.error("Invalid decoration token value for #{token_name} in #{text}")
144
- return token_value
145
- end
146
-
147
- default_decoration = default_decoration.clone
148
- decoration_token_values = context[token_name.to_sym] || context[token_name.to_s]
149
-
150
- default_decoration.gsub!('{$0}', token_value.to_s)
151
-
152
- if decoration_token_values.is_a?(Hash)
153
- decoration_token_values.keys.each do |key|
154
- default_decoration.gsub!("{$#{key}}", decoration_token_values[key].to_s)
155
- end
156
- end
157
-
158
- # remove unused attributes
159
- default_decoration = default_decoration.gsub(/\{\$[^}]*\}/, '')
160
- default_decoration
161
- end
162
-
163
- def allowed_token?(token)
164
- return true if opts[:allowed_tokens].nil?
165
- opts[:allowed_tokens].include?(token)
166
- end
167
-
168
- def apply(token, value)
169
- return value if token == RESERVED_TOKEN
170
- return value unless allowed_token?(token)
171
-
172
- method = context[token.to_sym] || context[token.to_s]
173
-
174
- if method
175
- if method.is_a?(Proc)
176
- return method.call(value)
177
- end
178
-
179
- if method.is_a?(Array) or method.is_a?(Hash)
180
- return default_decoration(token, value)
181
- end
182
-
183
- if method.is_a?(String)
184
- return method.to_s.gsub('{$0}', value)
185
- end
186
-
187
- return value
188
- end
189
-
190
- if Tml.config.default_token_value(normalize_token(token), :decoration)
191
- return default_decoration(token, value)
192
- end
193
-
194
- value
195
- end
196
-
197
- def normalize_token(name)
198
- name.to_s.gsub(/(\d)*$/, '')
139
+ def apply(token_name, value)
140
+ token = ::Tml::Tokens::Decoration.new(label, token_name)
141
+ token.apply(context, value, opts[:allowed_tokens])
199
142
  end
200
143
 
201
144
  def evaluate(expr)
@@ -211,7 +154,7 @@ module Tml
211
154
  end
212
155
 
213
156
  def substitute
214
- evaluate(parse).gsub('[/tml]', '')
157
+ evaluate(parse).gsub(/[<\[]\/tml[>\]]/, '')
215
158
  end
216
159
 
217
160
  end
@@ -103,7 +103,7 @@ module Tml
103
103
 
104
104
  def translate_tml(tml)
105
105
  return tml if empty_string?(tml)
106
- pp tml
106
+ # pp tml
107
107
 
108
108
  tml = generate_data_tokens(tml)
109
109
 
@@ -0,0 +1,490 @@
1
+ # encoding: UTF-8
2
+ #--
3
+ # Copyright (c) 2016 Translation Exchange, Inc
4
+ #
5
+ # _______ _ _ _ ______ _
6
+ # |__ __| | | | | (_) | ____| | |
7
+ # | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
8
+ # | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
9
+ # | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
10
+ # |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
11
+ # __/ |
12
+ # |___/
13
+ # Permission is hereby granted, free of charge, to any person obtaining
14
+ # a copy of this software and associated documentation files (the
15
+ # "Software"), to deal in the Software without restriction, including
16
+ # without limitation the rights to use, copy, modify, merge, publish,
17
+ # distribute, sublicense, and/or sell copies of the Software, and to
18
+ # permit persons to whom the Software is furnished to do so, subject to
19
+ # the following conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be
22
+ # included in all copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ #++
32
+
33
+ #######################################################################
34
+ #
35
+ # Decoration Token Forms:
36
+ #
37
+ # [link: click here]
38
+ # or
39
+ # [link] click here [/link]
40
+ #
41
+ # Decoration Tokens Allow Nesting:
42
+ #
43
+ # [link: {count} {_messages}]
44
+ # [link: {count||message}]
45
+ # [link: {count||person, people}]
46
+ # [link: {user.name}]
47
+ #
48
+ #######################################################################
49
+
50
+ module Tml
51
+ module Tokenizers
52
+ class XMessage
53
+
54
+ attr_accessor :label, :pos, :len, :last, :options, :tree
55
+
56
+ def optional_style_format_types
57
+ @optional_style_format_types ||= {
58
+ 'text' => true,
59
+ 'date' => true,
60
+ 'time' => true,
61
+ 'number' => true,
62
+ 'name' => true,
63
+ 'list' => true,
64
+ 'possessive' => true,
65
+ 'salutation' => true
66
+ }
67
+ end
68
+
69
+ def initialize(text, opts = {})
70
+ @label = text
71
+ @pos = 0
72
+ @len = @label ? @label.length : 0
73
+ @last = nil
74
+ @options = opts || {}
75
+ @tree = nil
76
+ tokenize
77
+ end
78
+
79
+ def update_last
80
+ @last = @pos > 0 ? @label[@pos - 1] : nil
81
+ end
82
+
83
+ def next_char
84
+ return if @len == 0 || @pos >= @len
85
+ update_last
86
+ @pos += 1
87
+ @label[@pos - 1]
88
+ end
89
+
90
+ def peek_char
91
+ return if @len == 0
92
+ @label[@pos]
93
+ end
94
+
95
+ def revert
96
+ if (@pos > 0)
97
+ @pos -= 1
98
+ update_last
99
+ end
100
+ end
101
+
102
+ def escaped?
103
+ @last && @last == '\\'
104
+ end
105
+
106
+ def no_format_style(result, c, argument_index, format_type)
107
+ raise "no format style allowed for format type '" + format_type + "'";
108
+ end
109
+
110
+ def collection_format_style(result, c, argument_index, format_type)
111
+ # register the format element
112
+ styles = []
113
+ subtype = 'text'; # default
114
+
115
+ if c == ','
116
+ # we have a sub-type
117
+ subtype = ''
118
+ c = next_char
119
+ while c && !',}'.index(c)
120
+ subtype += c
121
+ c = next_char
122
+ unless c
123
+ raise "expected ',' or '}', but found end of string"
124
+ end
125
+ end
126
+ end
127
+
128
+ result << {index: argument_index, type: format_type, subtype: subtype, styles: styles}
129
+
130
+ if c == '}'
131
+ return
132
+ end
133
+
134
+ # parse format style
135
+ while c
136
+ c = next_char
137
+ unless c
138
+ raise "expected '}', '|' or format style value, but found end of string"
139
+ end
140
+
141
+ if c == '}' && !escaped?
142
+ return
143
+ elsif c == '|'
144
+ next
145
+ end
146
+
147
+ style_key = ''
148
+ while c && !'#<|}'.index(c)
149
+ style_key += c
150
+ c = next_char
151
+ unless c
152
+ raise "expected '#', '<' or '|', but found end of string"
153
+ end
154
+ end
155
+
156
+ if c == '<'
157
+ style_key += c
158
+ end
159
+
160
+ items = []
161
+ styles << {key: style_key, items: items}
162
+
163
+ if '#<'.index(c)
164
+ traverse_text(items)
165
+ elsif '|}'.index(c)
166
+ # we found a key without value e.g. {0,param,possessive} and {0,param,prefix#.|possessive}
167
+ revert
168
+ end
169
+ end
170
+ end
171
+
172
+ def text_format_style(result, c, argument_index, format_type)
173
+ # parse format style
174
+ buffer = ''
175
+ c = next_char
176
+ unless c
177
+ raise "expected format style or '}', but found end of string"
178
+ end
179
+
180
+ while c
181
+ if c == '}'
182
+ result << {index: argument_index, type: format_type, value: buffer}
183
+ return
184
+ end
185
+
186
+ # keep adding to buffer
187
+ buffer += c
188
+ c = next_char
189
+ unless c
190
+ raise "expected '}', but found end of string"
191
+ end
192
+ end
193
+ end
194
+
195
+ def default_format_style(result, c, argument_index, format_type)
196
+ # register the format element
197
+ styles = []
198
+ result << {index: argument_index, type: format_type, styles: styles}
199
+
200
+ # parse format style
201
+ while c
202
+ c = next_char
203
+ unless c
204
+ raise "expected '}', '|' or format style value, but found end of string"
205
+ end
206
+
207
+ if c == '}' && !escaped?
208
+ return
209
+ elsif c == '|'
210
+ next
211
+ end
212
+
213
+ style_key = ''
214
+ while c && !'#<+|}'.index(c)
215
+ style_key += c
216
+ c = next_char
217
+ unless c
218
+ raise "expected '#', '<', '+' or '|', but found end of string"
219
+ end
220
+ end
221
+
222
+ if c == '<' || c == '+'
223
+ style_key += c
224
+ end
225
+
226
+ items = []
227
+ styles << {key: style_key, items: items}
228
+
229
+ if '#<+'.index(c)
230
+ traverse_text(items)
231
+ elsif '|}'.index(c)
232
+ # we found a key without value e.g. {0,param,possessive} and {0,param,prefix#.|possessive}
233
+ revert
234
+ end
235
+ end
236
+ end
237
+
238
+ def traverse_format_element(result)
239
+ argument_index = -1
240
+ format_type = nil
241
+ c = next_char
242
+
243
+ unless c
244
+ raise 'expected place holder index, but found end of string'
245
+ end
246
+
247
+ if c.match(/[\d:]/)
248
+ # process argument index
249
+ is_keyword = c == ':'
250
+ index = ''
251
+ while c && !',}'.index(c)
252
+ index += c
253
+ c = next_char
254
+ unless c
255
+ raise "expected ',' or '}', but found end of string";
256
+ end
257
+ end
258
+
259
+ if !is_keyword && !index.match(/\d+/)
260
+ throw "argument index must be numeric: #{index}"
261
+ end
262
+
263
+ argument_index = is_keyword ? index : index * 1
264
+ end
265
+
266
+ if c != '}'
267
+ # process format type
268
+ format_type = ''
269
+ c = next_char
270
+ unless c
271
+ raise 'expected format type, but found end of string'
272
+ end
273
+
274
+ while c && !',}'.index(c) && !escaped?
275
+ format_type += c
276
+ c = next_char
277
+ unless c
278
+ raise "expected ',' or '}', but found end of string"
279
+ end
280
+ end
281
+ end
282
+
283
+ if c == '}' && !escaped?
284
+ if format_type && optional_style_format_types[format_type]
285
+ # we found {0,number} or {0,possessive} or {0,salutation}, which are valid expressions
286
+ result << {type: format_type, index: argument_index}
287
+ else
288
+ if format_type
289
+ # we found something like {0,<type>}, which is invalid.
290
+ raise "expected format style for format type '#{format_type}'"
291
+ end
292
+
293
+ # push param format element
294
+ result << {type: 'param', index: argument_index}
295
+ end
296
+ elsif c == ','
297
+ processors = {
298
+ list: 'collection_format_style',
299
+ date: 'text_format_style',
300
+ time: 'text_format_style',
301
+ number: 'text_format_style',
302
+ suffix: 'text_format_style',
303
+ possessive: 'no_format_style',
304
+ salutation: 'no_format_style',
305
+ default: 'default_format_style'
306
+ }
307
+ processor = (processors[format_type.to_sym] || processors[:default])
308
+ self.send(processor, result, c, argument_index, format_type)
309
+ else
310
+ raise "expected ',' or '}', but found '#{c}' at position #{@pos}"
311
+ end
312
+ end
313
+
314
+ def traverse_text(result)
315
+ in_quoted_string = false
316
+ buffer = ''
317
+ c = next_char
318
+
319
+ while c do
320
+ if c == "'"
321
+ in_quoted_string = !in_quoted_string
322
+ end
323
+
324
+ if !in_quoted_string && c == '{' && !escaped?
325
+ unless buffer.empty?
326
+ result << {type: 'trans', value: buffer}
327
+ buffer = ''
328
+ end
329
+ traverse_format_element(result)
330
+ elsif !in_quoted_string && (c == '|' || c == '}') && !escaped?
331
+ revert
332
+ break
333
+ else
334
+ buffer += c
335
+ end
336
+ c = next_char
337
+ end
338
+
339
+ unless buffer.empty?
340
+ result << {type: 'trans', value: buffer}
341
+ buffer = ''
342
+ end
343
+
344
+ result
345
+ end
346
+
347
+ def tokenize
348
+ result = []
349
+ traverse_text(result)
350
+ @tree = result
351
+ rescue Exception => ex
352
+ pp ex
353
+ pp "Failed to parse the expression: " + @label
354
+ @tree = nil
355
+ end
356
+
357
+ def rule_key(context_key, rule_key)
358
+ return rule_key unless Tml.config.xmessage_rule_key_mapping[context_key.to_sym]
359
+ Tml.config.xmessage_rule_key_mapping[context_key.to_sym][rule_key.to_sym] || rule_key
360
+ end
361
+
362
+ def choice(language, token, token_object)
363
+ return unless token
364
+
365
+ context_key = token.context_keys.first
366
+ return unless context_key
367
+
368
+ ctx = language.context_by_keyword(context_key)
369
+ return unless ctx
370
+
371
+ # pp context_key, token_object
372
+
373
+ rule = ctx.find_matching_rule(token_object)
374
+ if rule
375
+ # pp context_key, rule.keyword
376
+ return rule_key(context_key, rule.keyword)
377
+ end
378
+
379
+ nil
380
+ end
381
+
382
+ def data?(type)
383
+ return false unless type
384
+ %w(param number).include?(type)
385
+ end
386
+
387
+ def decoration?(type)
388
+ return false unless type
389
+ Tml.config.xmessage_decoration_tokens.include?(type.to_sym)
390
+ end
391
+
392
+ def choice?(type)
393
+ type == 'choice'
394
+ end
395
+
396
+ def map?(type)
397
+ type == 'map'
398
+ end
399
+
400
+ def compile(language, exp, buffer, params)
401
+ style = nil
402
+
403
+ exp.each do |el|
404
+ token = token_by_type(el[:type], el)
405
+ token_object = get_token_object(params, token)
406
+ token_value = get_token_value(token_object, token, language)
407
+
408
+ if el[:styles]
409
+ if choice?(el[:type])
410
+ key = choice(language, token, token_object)
411
+ style = el[:styles].find{ |style|
412
+ style[:key] == key
413
+ }
414
+ if style
415
+ compile(language, style[:items], buffer, params)
416
+ end
417
+ elsif map?(el[:type])
418
+ style = el[:styles].find{ |style|
419
+ style[:key] == token_value
420
+ }
421
+ compile(language, style[:items], buffer, params)
422
+ elsif decoration?(el[:type])
423
+ buffer << token.open_tag(token_object)
424
+ compile(language, el[:styles][0][:items], buffer, params)
425
+ buffer << token.close_tag
426
+ else
427
+ compile(language, el[:styles][0][:items], buffer, params)
428
+ end
429
+ elsif data?(el[:type])
430
+ buffer << token_value
431
+ else
432
+ buffer << el[:value]
433
+ end
434
+ end
435
+
436
+ buffer
437
+ end
438
+
439
+ def tokens
440
+ @tokens ||= begin
441
+ tokens = []
442
+ extract_tokens(tree, tokens)
443
+ tokens.uniq{ |t| [t.class.name, t.full_name] }
444
+ end
445
+ end
446
+
447
+ def token_by_type(type, data)
448
+ if decoration?(type)
449
+ Tml::Tokens::XMessage::Decoration.new(label, data)
450
+ elsif data?(type)
451
+ Tml::Tokens::XMessage::Data.new(label, data)
452
+ elsif choice?(type)
453
+ Tml::Tokens::XMessage::Choice.new(label, data)
454
+ elsif map?(type)
455
+ return Tml::Tokens::XMessage::Map.new(label, data)
456
+ else
457
+ nil
458
+ end
459
+ end
460
+
461
+ def extract_tokens(tree, tokens)
462
+ tree.each do |fragment|
463
+ token = token_by_type(fragment[:type], fragment)
464
+ tokens << token if token
465
+ if fragment[:items]
466
+ extract_tokens(fragment[:items], tokens)
467
+ elsif fragment[:styles]
468
+ extract_tokens(fragment[:styles], tokens)
469
+ end
470
+ end
471
+ end
472
+
473
+ def get_token_object(token_values, token)
474
+ return nil unless token
475
+ token.token_object(token_values)
476
+ end
477
+
478
+ def get_token_value(token_object, token, language)
479
+ return nil unless token_object && token
480
+ token.token_value(token_object, language)
481
+ end
482
+
483
+ def substitute(language, tokens = {}, options = {})
484
+ return @label unless tree
485
+ compile(language, tree, [], tokens).join('')
486
+ end
487
+
488
+ end
489
+ end
490
+ end