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 +4 -4
- data/lib/tml.rb +4 -2
- data/lib/tml/application.rb +3 -1
- data/lib/tml/config.rb +28 -0
- data/lib/tml/language.rb +8 -2
- data/lib/tml/tokenizers/data.rb +4 -2
- data/lib/tml/tokenizers/decoration.rb +10 -67
- data/lib/tml/tokenizers/dom.rb +1 -1
- data/lib/tml/tokenizers/x_message.rb +490 -0
- data/lib/tml/tokens/data.rb +14 -1
- data/lib/tml/tokens/decoration.rb +112 -0
- data/lib/tml/tokens/map.rb +83 -0
- data/lib/tml/tokens/transform.rb +0 -1
- data/lib/tml/tokens/x_message/choice.rb +61 -0
- data/lib/tml/tokens/x_message/data.rb +54 -0
- data/lib/tml/tokens/x_message/decoration.rb +101 -0
- data/lib/tml/tokens/x_message/map.rb +54 -0
- data/lib/tml/translation_key.rb +8 -1
- data/lib/tml/utils.rb +1 -1
- data/lib/tml/version.rb +1 -1
- metadata +9 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: a26bbf3c59deca21956a94bc33d811377797034e
         | 
| 4 | 
            +
              data.tar.gz: fdd19956dcf43dc02228c8c43ea8687a8eb47d34
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 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 | 
| 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
         | 
    
        data/lib/tml/application.rb
    CHANGED
    
    | @@ -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  =>  '™',       # 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] | 
| 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 |  | 
    
        data/lib/tml/tokenizers/data.rb
    CHANGED
    
    | @@ -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, : | 
| 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              = '[^\[\]<>]+' | 
| 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( | 
| 73 | 
            -
                    @ | 
| 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 =  | 
| 87 | 
            +
                    @fragments = "[#{RESERVED_TOKEN}]#{@label}[/#{RESERVED_TOKEN}]".scan(/#{re}/)
         | 
| 88 88 | 
             
                    @tokens = []
         | 
| 89 89 | 
             
                  end
         | 
| 90 90 |  | 
| 91 91 | 
             
                  def parse
         | 
| 92 | 
            -
                    return @ | 
| 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  | 
| 140 | 
            -
                     | 
| 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( | 
| 157 | 
            +
                    evaluate(parse).gsub(/[<\[]\/tml[>\]]/, '')
         | 
| 215 158 | 
             
                  end
         | 
| 216 159 |  | 
| 217 160 | 
             
                end
         | 
    
        data/lib/tml/tokenizers/dom.rb
    CHANGED
    
    
| @@ -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
         |