tml 5.6.5 → 5.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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