Class: Rack::Acceptable::LanguageTag

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/acceptable/language_tag.rb

Overview

inspired by the ‘langtag’ gem (author: Martin Dürst) rubyforge.org/projects/langtag/ www.langtag.net/

Constant Summary collapse

GRANDFATHERED_TAGS =
{
  'art-lojban'  => 'jbo' ,
  'cel-gaulish' => nil   ,
  'en-gb-oed'   => nil   ,
  'i-ami'       => 'ami' ,
  'i-bnn'       => 'bnn' ,
  'i-default'   => nil   ,
  'i-enochian'  => nil   ,
  'i-hak'       => 'hak' ,
  'i-klingon'   => 'tlh' ,
  'i-lux'       => 'lb'  ,
  'i-mingo'     => nil   ,
  'i-navajo'    => 'nv'  ,
  'i-pwn'       => 'pwn' ,
  'i-tao'       => 'tao' ,
  'i-tay'       => 'tay' ,
  'i-tsu'       => 'tsu' ,
  'no-bok'      => 'nb'  ,
  'no-nyn'      => 'nn'  ,
  'sgn-be-fr'   => 'sfb' ,
  'sgn-be-nl'   => 'vgt' ,
  'sgn-ch-de'   => 'sgg' ,
  'zh-guoyu'    => 'cmn' ,
  'zh-hakka'    => 'hak' ,
  'zh-min'      => nil   ,
  'zh-min-nan'  => 'nan' ,
  'zh-xiang'    => 'hsn' 
}.freeze
LANGTAG_COMPOSITION_REGEX =
/^#{language}#{script}#{region}(?=#{variants}#{extensions}#{privateuse}$)/o.freeze
LANGTAG_INFO_REGEX =
/^#{language}#{script}#{region}(#{variants})#{extensions}#{privateuse}$/o.freeze
PRIVATEUSE_REGEX =
/^x(?:-[a-z\d]{1,8})+$/i.freeze
PRIVATEUSE =
'x'.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*components) ⇒ LanguageTag

Returns a new instance of LanguageTag.



163
164
165
# File 'lib/rack/acceptable/language_tag.rb', line 163

def initialize(*components)
  @primary, @extlang, @script, @region, @variants, @extensions, @privateuse = *components
end

Instance Attribute Details

#extensionsObject

Returns the value of attribute extensions.



41
42
43
# File 'lib/rack/acceptable/language_tag.rb', line 41

def extensions
  @extensions
end

#extlangObject

Returns the value of attribute extlang.



41
42
43
# File 'lib/rack/acceptable/language_tag.rb', line 41

def extlang
  @extlang
end

#primaryObject

Returns the value of attribute primary.



41
42
43
# File 'lib/rack/acceptable/language_tag.rb', line 41

def primary
  @primary
end

#privateuseObject

Returns the value of attribute privateuse.



41
42
43
# File 'lib/rack/acceptable/language_tag.rb', line 41

def privateuse
  @privateuse
end

#regionObject

Returns the value of attribute region.



41
42
43
# File 'lib/rack/acceptable/language_tag.rb', line 41

def region
  @region
end

#scriptObject

Returns the value of attribute script.



41
42
43
# File 'lib/rack/acceptable/language_tag.rb', line 41

def script
  @script
end

#tagObject (readonly)

the most recent ‘build’ of tag



175
176
177
# File 'lib/rack/acceptable/language_tag.rb', line 175

def tag
  @tag
end

#variantsObject

Returns the value of attribute variants.



41
42
43
# File 'lib/rack/acceptable/language_tag.rb', line 41

def variants
  @variants
end

Class Method Details

.extract_language_info(langtag) ⇒ Object

Parameters

langtag<String>

The Language-Tag snippet.

Returns

Array or nil

It returns nil, when the Language-Tag passed:

  • does not conform the Language-Tag ABNF (malformed)

  • grandfathered

  • starts with ‘x’ singleton (‘privateuse’).

Otherwise you’ll get an Array with:

  • primary subtag (as String, downcased),

  • extlang (as String, downcased) or nil,

  • script (as String, capitalized) or nil,

  • region (as String, upcased) or nil

  • downcased variants (Array) or nil.

Notes

In most cases, it’s quite enough. Take a look, for example, at ‘35-character recomendation’.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/rack/acceptable/language_tag.rb', line 104

def extract_language_info(langtag)
  tag = langtag.downcase
  return nil if GRANDFATHERED_TAGS.key?(langtag)
  return nil unless LANGTAG_INFO_REGEX === tag

  primary     = $1
  extlang     = nil
  script      = $2
  region      = $3
  variants    = $4.split(Utils::HYPHEN_SPLITTER)[1..-1]

  primary, extlang = primary.split(Utils::HYPHEN_SPLITTER) if primary.include?(Const::HYPHEN)
  script.capitalize! if script
  region.upcase! if region

  [primary, extlang, script, region, variants]
end

.grandfathered?(tag) ⇒ Boolean

Checks if the String passed represents a ‘grandgathered’ Language-Tag. Works case-insensitively.

Returns:

  • (Boolean)


79
80
81
# File 'lib/rack/acceptable/language_tag.rb', line 79

def grandfathered?(tag)
  GRANDFATHERED_TAGS.key?(tag) || GRANDFATHERED_TAGS.key?(tag.downcase)
end

.parse(thing) ⇒ Object



122
123
124
125
126
# File 'lib/rack/acceptable/language_tag.rb', line 122

def parse(thing)
  return nil unless thing
  return thing if thing.kind_of?(self)
  self.new.recompose(thing)
end

.privateuse?(tag) ⇒ Boolean

Checks if the String passed could be treated as ‘privateuse’ Language-Tag. Works case-insensitively.

Returns:

  • (Boolean)


72
73
74
# File 'lib/rack/acceptable/language_tag.rb', line 72

def privateuse?(tag)
  PRIVATEUSE_REGEX === tag
end

Instance Method Details

#==(other) ⇒ Object



291
292
293
294
295
296
# File 'lib/rack/acceptable/language_tag.rb', line 291

def ==(other)
  return false unless other.kind_of?(self.class)
  compose
  other.compose
  @tag == other.tag || @tag.downcase == other.tag.downcase
end

#===(other) ⇒ Object



298
299
300
301
302
303
304
305
306
307
308
# File 'lib/rack/acceptable/language_tag.rb', line 298

def ===(other)
  if other.kind_of?(self.class)
    s = other.compose
  elsif other.respond_to?(:to_str)
    s = other.to_str
  else
    return false
  end
  compose
  @tag == s || @tag.downcase == s.downcase
end

#composeObject

Builds the String, which represents self. Does not perform validation or recomposition.



170
171
172
173
# File 'lib/rack/acceptable/language_tag.rb', line 170

def compose
  @tag = to_a.join(Const::HYPHEN)
  @tag
end

#has_singleton?(key) ⇒ Boolean Also known as: extension?

Checks if self has a singleton passed. Works case-insensitively.

Returns:

  • (Boolean)


148
149
150
151
# File 'lib/rack/acceptable/language_tag.rb', line 148

def has_singleton?(key)
  return false unless @extensions
  @extensions.key?(key) || @extensions.key?(key.downcase)
end

#has_variant?(variant) ⇒ Boolean

Checks if self has a variant passed. Works case-insensitively.

Notes

Destructively downcases current set of variants, if necessary. Just note, that variants are case-insensitive, and ‘convenient’ form of the Languge-Tag assumes they’re in ‘lowercase’ notation.

Returns:

  • (Boolean)


138
139
140
141
142
143
144
# File 'lib/rack/acceptable/language_tag.rb', line 138

def has_variant?(variant)
  return false unless @variants
  @variants.include?(variant) || begin
    @variants.map { |v| v.downcase! }
    @variants.include?(variant.downcase)
  end
end

#matched_by_basic_range?(range) ⇒ Boolean Also known as: has_prefix?

Checks if the basic Language-Range passed matches self.

Example

tag = LanguageTag.parse('de-Latn-DE')
tag.matched_by_basic_range?('de-Latn-DE') #=> true
tag.matched_by_basic_range?('de-Latn') #=> true
tag.matched_by_basic_range?('*') #=> true
tag.matched_by_basic_range?('de-La') #=> false
tag.matched_by_basic_range?('de-de') #=> false
tag.matched_by_basic_range?('malformedlangtag') #=> false

Returns:

  • (Boolean)


274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/rack/acceptable/language_tag.rb', line 274

def matched_by_basic_range?(range)
  if range.kind_of?(self.class)
    s = range.recompose.tag
  elsif range.respond_to?(:to_str)
    return true if range.to_str == Const::WILDCARD
    s = self.class.parse(range).tag
  else
    return false
  end
  recompose
  @tag == s || @tag.index(s + Const::HYPHEN) == 0
rescue
  false
end

#matched_by_extended_range?(range) ⇒ Boolean

Checks if the extended Language-Range passed matches self.

Returns:

  • (Boolean)


222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/rack/acceptable/language_tag.rb', line 222

def matched_by_extended_range?(range)
  recompose
  subtags = @composition.split(Const::HYPHEN)
  subranges = range.downcase.split(Const::HYPHEN)

  subrange = subranges.shift
  subtag = subtags.shift

  while subrange
    if subrange == Const::WILDCARD
      subrange = subranges.shift
    elsif subtag == nil
      return false
    elsif subtag == subrange
      subtag = subtags.shift
      subrange = subranges.shift
    elsif subtag.size == 1
      return false
    else
      subtag = subtags.shift
    end
  end
  true
rescue
  false
end

#nicecasedObject



177
178
179
180
# File 'lib/rack/acceptable/language_tag.rb', line 177

def nicecased
  recompose   # we could not conveniently format malformed or invalid tags
  @nicecased  #.dup #uuuuugh
end

#recompose(thing = nil) ⇒ Object

Parameters

thing<String, optional>

The Language-Tag snippet

Returns

self

Raises

ArgumentError

The Language-Tag composition:

  • does not conform the Language-Tag ABNF (malformed)

  • grandfathered

  • starts with ‘x’ singleton (‘privateuse’).

  • contains duplicate variants

  • contains duplicate singletons



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/rack/acceptable/language_tag.rb', line 341

def recompose(thing = nil)

  tag = nil
  compose

  if thing
    raise TypeError, "Can't convert #{thing.class} into String" unless thing.respond_to?(:to_str)
    return self if @tag == (tag = thing.to_str) || @tag.downcase == (tag = tag.downcase)
  else
    return self if @nicecased == (tag = @tag) || @composition == tag || @composition == (tag = tag.downcase)
  end

  if !GRANDFATHERED_TAGS.key?(tag) && LANGTAG_COMPOSITION_REGEX === tag

    @primary = $1
    @extlang    = nil
    @script     = $2
    @region     = $3
    components  = $'.split(Utils::HYPHEN_SPLITTER)
    components.shift

    @primary, @extlang = @primary.split(Utils::HYPHEN_SPLITTER) if @primary.include?(Const::HYPHEN)

    @script.capitalize! if @script
    @region.upcase! if @region

    @extensions = nil
    @variants   = nil
    singleton   = nil

    while c = components.shift
      if c.size == 1
        break if c == PRIVATEUSE
        @extensions ||= {}
        if @extensions.key?(c)
          raise ArgumentError, "Invalid langtag (repeated singleton: #{c.inspect}): #{thing.inspect}"
        end
        singleton = c
        @extensions[singleton = c] = []
      elsif singleton
        @extensions[singleton] << c
      else
        @variants ||= []
        if @variants.include?(c)
          raise ArgumentError, "Invalid langtag (repeated variant: #{c.inspect}): #{thing.inspect}"
        end
        @variants << c
      end
    end

    @privateuse   = components.empty? ? nil : components
    @nicecased    = compose
    @composition  = @tag.downcase

  else
    raise ArgumentError, "Malformed, grandfathered or 'privateuse' Language-Tag: #{thing.inspect}"
  end

  self
end

#singletonsObject

Builds an ordered list of singletons.



156
157
158
159
160
161
# File 'lib/rack/acceptable/language_tag.rb', line 156

def singletons
  return nil unless @extensions
  keys = @extensions.keys
  keys.sort!
  keys
end

#to_aObject



182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/rack/acceptable/language_tag.rb', line 182

def to_a
  ary = [@primary]
  ary << @extlang if @extlang
  ary << @script if @script
  ary << @region if @region
  ary.concat @variants if @variants
  singletons.each { |s| (ary << s).concat @extensions[s] } if @extensions
  (ary << PRIVATEUSE).concat @privateuse if @privateuse
  ary
rescue
  raise "LanguageTag has at least one malformed attribute: #{self.inspect}"
end

#valid?Boolean Also known as: langtag?

Validates self.

Notes

Validation is deferred by default, because the paranoid check & dup of everything is not a good way (in this case). So, you may create some tags, make them malformed/invalid, and still be able to compare and modify them. Only note, that things like ‘filtering’ and ‘lookup’ are not validation-free.

Returns:

  • (Boolean)


319
320
321
# File 'lib/rack/acceptable/language_tag.rb', line 319

def valid?
  !!recompose rescue false
end