lang 0.1.0.pre

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.
@@ -0,0 +1,141 @@
1
+ module Lang #:nodoc:
2
+ module Tag
3
+
4
+ # Handles abstract compositions of subtags
5
+ # incl. basic and extended language-ranges.
6
+ #
7
+ # ==== Example
8
+ #
9
+ # class LanguageRange < Lang::Tag::Composition
10
+ #
11
+ # def initialize(thing)
12
+ # raise TypeError, "Can't convert #{thing.class} into String" unless thing.respond_to?(:to_str)
13
+ # sequence = thing.to_str
14
+ # unless /^(?:\*|[a-z]{1,8})(?:-[a-z\d]{1,8}|-\*)*$/i === sequence
15
+ # raise Error, "#{sequence.inspect} is not a language-range."
16
+ # end
17
+ # @sequence = sequence
18
+ # end
19
+ #
20
+ # def simplify! # to basic language-range
21
+ # /^\*-/ === @sequence ? @sequence = '*' : @sequence.gsub!('-*','')
22
+ # dirty
23
+ # end
24
+ #
25
+ # end
26
+ #
27
+ class Composition
28
+
29
+ def initialize(thing)
30
+ raise TypeError, "Can't convert #{thing.class} into String" unless thing.respond_to?(:to_str)
31
+ @sequence = thing.to_str
32
+ end
33
+
34
+ # Returns +true+ if compositions are equal.
35
+ # Allows comparison against +Strings+.
36
+ #
37
+ def ===(other)
38
+ return false unless other.respond_to?(:to_str)
39
+ s = other.to_str
40
+ composition == s || composition == s.downcase
41
+ end
42
+
43
+ # Returns +true+ if Compositions are equal.
44
+ #
45
+ def ==(other)
46
+ return false unless other.kind_of?(self.class)
47
+ self.composition == other.composition
48
+ end
49
+
50
+ def eql?(other)
51
+ return false unless other.kind_of?(self.class)
52
+ self.to_s == other.to_s
53
+ end
54
+
55
+ def hash
56
+ to_s.hash
57
+ end
58
+
59
+ def composition
60
+ @composition ||= to_s.downcase
61
+ end
62
+
63
+ def to_s
64
+ @sequence
65
+ end
66
+
67
+ alias :to_str :to_s
68
+
69
+ def to_a
70
+ to_s.split(HYPHEN_SPLITTER)
71
+ end
72
+
73
+ def decomposition
74
+ @decomposition ||= composition.split(HYPHEN_SPLITTER)
75
+ end
76
+
77
+ private :decomposition
78
+
79
+ def dirty
80
+ @composition = nil
81
+ @decomposition = nil
82
+ nil
83
+ end
84
+
85
+ private :dirty
86
+
87
+ # Duplicates self.
88
+ #
89
+ def dup
90
+ self.class.new(to_s.dup)
91
+ end
92
+
93
+ def length
94
+ to_s.length
95
+ end
96
+
97
+ # Returns the number of subtags in self.
98
+ #
99
+ def subtags_count
100
+ to_s.count(HYPHEN) + 1
101
+ end
102
+
103
+ #--
104
+ # RFC 5646, Section 2.1.1
105
+ # An implementation can reproduce this format without accessing the
106
+ # registry as follows. All subtags, including extension and private
107
+ # use subtags, use lowercase letters with two exceptions: two-letter
108
+ # and four-letter subtags that neither appear at the start of the tag
109
+ # nor occur after singletons. Such two-letter subtags are all
110
+ # uppercase (as in the tags "en-CA-x-ca" or "sgn-BE-FR") and four-
111
+ # letter subtags are titlecase (as in the tag "az-Latn-x-latn").
112
+ #++
113
+
114
+ def nicecase!
115
+ @sequence.downcase!
116
+ @sequence.gsub!(/-(?:([a-z\d]{4})|[a-z\d]{2}|[a-z\d]-.*)(?=-|$)/) do |sequence|
117
+ if $1
118
+ sequence = HYPHEN + $1.capitalize
119
+ elsif sequence.size == 3
120
+ sequence.upcase!
121
+ end
122
+ sequence
123
+ end
124
+ nil
125
+ end
126
+
127
+ def nicecase
128
+ duplicated = self.dup
129
+ duplicated.nicecase!
130
+ duplicated
131
+ end
132
+
133
+ def inspect
134
+ sprintf("#<%s:%#0x %s>", self.class.to_s, self.object_id, self.to_s)
135
+ end
136
+
137
+ end
138
+ end
139
+ end
140
+
141
+ # EOF
@@ -0,0 +1,143 @@
1
+ require 'lang/tag'
2
+
3
+ module Lang #:nodoc:
4
+ module Tag
5
+
6
+ # Basic and extended filtering.
7
+ # RFC 4647, Sections 3.3.1, 3.3.2.
8
+ #
9
+ module Filtering
10
+
11
+ WILDCARD = '*'.freeze
12
+
13
+ #--
14
+ # RFC 4647, Section 3.3.2 ('Extended Filtering')
15
+ #
16
+ # Much like basic filtering, extended filtering selects content with
17
+ # arbitrarily long tags that share the same initial subtags as the
18
+ # language range. In addition, extended filtering selects language
19
+ # tags that contain any intermediate subtags not specified in the
20
+ # language range. For example, the extended language range "de-*-DE"
21
+ # (or its synonym "de-DE") matches all of the following tags:
22
+ #
23
+ # de-DE (German, as used in Germany)
24
+ # de-de (German, as used in Germany)
25
+ # de-Latn-DE (Latin script)
26
+ # de-Latf-DE (Fraktur variant of Latin script)
27
+ # de-DE-x-goethe (private-use subtag)
28
+ # de-Latn-DE-1996 (orthography of 1996)
29
+ # de-Deva-DE (Devanagari script)
30
+ #
31
+ # The same range does not match any of the following tags for the
32
+ # reasons shown:
33
+ #
34
+ # de (missing 'DE')
35
+ # de-x-DE (singleton 'x' occurs before 'DE')
36
+ # de-Deva ('Deva' not equal to 'DE')
37
+ #++
38
+
39
+ # Checks if the *extended* language-range (in the shortest notation)
40
+ # passed matches self.
41
+ #
42
+ # ==== Example
43
+ # Lang::Tag('de-DE').matched_by_extended_range?('de-*-DE) #=> true
44
+ # Lang::Tag('de-DE-x-goethe').matched_by_extended_range?('de-*-DE) #=> true
45
+ # Lang::Tag('de-Latn-DE').matched_by_extended_range?('de-*-DE) #=> true
46
+ # Lang::Tag('de-Latf-DE').matched_by_extended_range?('de-*-DE) #=> true
47
+ # Lang::Tag('de-x-DE').matched_by_extended_range?('de-*-DE) #=> false
48
+ # Lang::Tag('de-Deva').matched_by_extended_range?('de-*-DE) #=> false
49
+ #
50
+ def matched_by_extended_range?(range)
51
+
52
+ subtags = decomposition.dup
53
+ subranges = range.to_str.downcase.split(HYPHEN_SPLITTER)
54
+
55
+ subrange = subranges.shift
56
+ subtag = subtags.shift
57
+
58
+ while subrange
59
+ if subrange == WILDCARD
60
+ subrange = subranges.shift
61
+ elsif subtag == nil
62
+ return false
63
+ elsif subtag == subrange
64
+ subtag = subtags.shift
65
+ subrange = subranges.shift
66
+ elsif subtag.size == 1
67
+ return false
68
+ else
69
+ subtag = subtags.shift
70
+ end
71
+ end
72
+ true
73
+ rescue
74
+ false
75
+ end
76
+
77
+ #--
78
+ # RFC 4647, Section 3.3.1 ('Basic Filtering')
79
+ #
80
+ # A language range matches a
81
+ # particular language tag if, in a case-insensitive comparison, it
82
+ # exactly equals the tag, or if it exactly equals a prefix of the tag
83
+ # such that the first character following the prefix is "-". For
84
+ # example, the language-range "de-de" (German as used in Germany)
85
+ # matches the language tag "de-DE-1996" (German as used in Germany,
86
+ # orthography of 1996), but not the language tags "de-Deva" (German as
87
+ # written in the Devanagari script) or "de-Latn-DE" (German, Latin
88
+ # script, as used in Germany).
89
+ #++
90
+
91
+ # Checks if the *basic* language-range passed matches self.
92
+ #
93
+ # ==== Example
94
+ # tag = Lang::Tag('de-Latn-DE')
95
+ # tag.matched_by_basic_range?('de-Latn-DE') #=> true
96
+ # tag.matched_by_basic_range?('de-Latn') #=> true
97
+ # tag.matched_by_basic_range?('*') #=> true
98
+ # tag.matched_by_basic_range?('de-La') #=> false
99
+ # tag.matched_by_basic_range?('de-de') #=> false
100
+ # tag.matched_by_basic_range?('malformedlangtag') #=> false
101
+ #
102
+ def matched_by_basic_range?(range)
103
+ if range.kind_of?(Composition)
104
+ s = range.composition
105
+ elsif range.respond_to?(:to_str)
106
+ s = range.to_str.downcase
107
+ return true if s == WILDCARD
108
+ else
109
+ return false
110
+ end
111
+
112
+ composition == s ||
113
+ composition.index(s + HYPHEN) == 0
114
+ end
115
+
116
+ alias :has_prefix? :matched_by_basic_range?
117
+
118
+ end
119
+
120
+ #--
121
+ # Filtering is defined for the language tags only.
122
+ #
123
+ # RFC 4647, Section 3.3
124
+ # Filtering is used to select the set of language tags
125
+ # that matches a given language priority list.
126
+ #++
127
+
128
+ class Langtag
129
+ include Filtering
130
+ end
131
+
132
+ class Grandfathered
133
+ include Filtering
134
+ end
135
+
136
+ class Privateuse
137
+ include Filtering
138
+ end
139
+
140
+ end
141
+ end
142
+
143
+ # EOF
@@ -0,0 +1,36 @@
1
+ require 'lang/tag'
2
+
3
+ module Lang #:nodoc:
4
+ module Tag
5
+
6
+ def self.Grandfathered(thing)
7
+ return thing if Grandfathered === thing
8
+ Grandfathered.new(thing)
9
+ end
10
+
11
+ # Handles grandfathered registrations.
12
+ #
13
+ class Grandfathered < Composition
14
+
15
+ def initialize(thing)
16
+ raise TypeError, "Can't convert #{thing.class} into String" unless thing.respond_to?(:to_str)
17
+ sequence = thing.to_str
18
+ unless Lang::Tag.grandfathered?(sequence)
19
+ raise ArgumentError, "#{sequence.inspect} is not a grandfathered language tag"
20
+ end
21
+ @sequence = sequence
22
+ end
23
+
24
+ def to_langtag
25
+ unless preferred_value = GRANDFATHERED[@sequence.downcase]
26
+ raise Error, "There is no preferred value for the grandfathered language tag #{@sequence.inspect}."
27
+ end
28
+ Tag::Langtag(preferred_value)
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ # EOF
@@ -0,0 +1,437 @@
1
+ require 'lang/tag'
2
+
3
+ module Lang
4
+ module Tag
5
+
6
+ def self.Langtag(thing = nil)
7
+ return thing if Langtag === thing
8
+ Langtag.new(thing)
9
+ end
10
+
11
+ # Handles the 'langtag' production
12
+ # i.e normal language tags.
13
+ #
14
+ class Langtag < Composition
15
+
16
+ attr_reader :language, :script, :region, :variants_sequence, :extensions_sequence, :privateuse_sequence
17
+
18
+ def initialize(thing = nil)
19
+ recompose(thing) if thing
20
+ end
21
+
22
+ #--
23
+ # RFC 5646, sec. 2.2.1:
24
+ # The primary language subtag is the first subtag in a language tag and
25
+ # cannot be omitted, with two exceptions:
26
+ #
27
+ # The single-character subtag 'x' as the primary subtag indicates
28
+ # that the language tag consists solely of subtags whose meaning is
29
+ # defined by private agreement. For example, in the tag "x-fr-CH",
30
+ # the subtags 'fr' and 'CH' do not represent the French language or
31
+ # the country of Switzerland (or any other value in the IANA
32
+ # registry) unless there is a private agreement in place to do so.
33
+ # See Section 4.6.
34
+ #
35
+ # The single-character subtag 'i' is used by some grandfathered tags
36
+ # (see Section 2.2.8) such as "i-klingon" and "i-bnn". (Other
37
+ # grandfathered tags have a primary language subtag in their first
38
+ # position.)
39
+ #++
40
+
41
+ #--
42
+ # RFC 5646, sec. 2.2.2:
43
+ # Extended language subtags are used to identify certain specially
44
+ # selected languages that, for various historical and compatibility
45
+ # reasons, are closely identified with or tagged using an existing
46
+ # primary language subtag. Extended language subtags are always used
47
+ # with their enclosing primary language subtag (indicated with a
48
+ # 'Prefix' field in the registry) when used to form the language tag.
49
+ #++
50
+
51
+ # Sets the language component for this langtag.
52
+ #
53
+ def language=(value)
54
+ raise InvalidComponentError, "Primary subtag cannot be omitted." unless value
55
+ sequence = value.to_str
56
+ if LANGUAGE_REGEX !~ sequence
57
+ raise InvalidComponentError,
58
+ "#{value.inspect} does not conform to the 'language' ABNF " \
59
+ "or to the associated rules."
60
+ end
61
+ @language = sequence
62
+ @primary = nil
63
+ @extlang = nil
64
+ dirty
65
+ validate
66
+ end
67
+
68
+ # Returns a primary language subtag.
69
+ #
70
+ def primary
71
+ return nil unless @language
72
+ decompose_language unless @primary
73
+ @primary
74
+ end
75
+
76
+ # Returns a second component of the extended language, if any.
77
+ #
78
+ def extlang
79
+ return nil unless @language
80
+ decompose_language unless @primary
81
+ @extlang
82
+ end
83
+
84
+ # Decomposes a language component.
85
+ #
86
+ def decompose_language
87
+ @primary, @extlang = @language.split(HYPHEN_SPLITTER, 2)
88
+ nil
89
+ end
90
+
91
+ protected :decompose_language
92
+
93
+ #--
94
+ # RFC 5646, sec. 2.2.3:
95
+ # Script subtags are used to indicate the script or writing system
96
+ # variations that distinguish the written forms of a language or its
97
+ # dialects.
98
+ #++
99
+
100
+ # Sets the script component for this langtag.
101
+ #
102
+ def script=(value)
103
+ subtag = value ? value.to_str : nil
104
+ if subtag && SCRIPT_REGEX !~ subtag
105
+ raise InvalidComponentError, "#{value.inspect} does not conform to the 'script' ABNF."
106
+ end
107
+ @script = subtag
108
+ dirty
109
+ validate
110
+ end
111
+
112
+ #--
113
+ # RFC 5646, sec. 2.2.4:
114
+ # Region subtags are used to indicate linguistic variations associated
115
+ # with or appropriate to a specific country, territory, or region.
116
+ # Typically, a region subtag is used to indicate variations such as
117
+ # regional dialects or usage, or region-specific spelling conventions.
118
+ # It can also be used to indicate that content is expressed in a way
119
+ # that is appropriate for use throughout a region, for instance,
120
+ # Spanish content tailored to be useful throughout Latin America.
121
+ #++
122
+
123
+ # Sets the region component for this langtag.
124
+ #
125
+ def region=(value)
126
+ subtag = value ? value.to_str : nil
127
+ if subtag && REGION_REGEX !~ subtag
128
+ raise InvalidComponentError, "#{value.inspect} does not conform to the 'region' ABNF."
129
+ end
130
+ @region = subtag
131
+ dirty
132
+ validate
133
+ end
134
+
135
+ #--
136
+ # RFC 5646, sec. 2.2.5:
137
+ # Variant subtags are used to indicate additional, well-recognized
138
+ # variations that define a language or its dialects that are not
139
+ # covered by other available subtags.
140
+ #++
141
+
142
+ # Sets the sequence of variants for this langtag.
143
+ #
144
+ # ==== Example
145
+ #
146
+ # tag = Lang::Tag('ja')
147
+ # tag.variants_sequence = 'hepburn-heploc'
148
+ # tag.variants #=> ['hepburn', 'heploc']
149
+ # tag.has_variant?('heploc') #=> true
150
+ # tag.has_variant?('nedis') #=> false
151
+ #
152
+ def variants_sequence=(value)
153
+ sequence = value ? value.to_str : nil
154
+ if sequence && VARIANTS_SEQUENCE_REGEX !~ "#{HYPHEN}#{sequence}"
155
+ raise InvalidComponentError, "#{value.inspect} does not conform to the 'variants' ABNF."
156
+ end
157
+ set_variants_sequence(sequence)
158
+ dirty
159
+ validate
160
+ end
161
+
162
+ # Friendly version of the #variants_sequence=.
163
+ # Sets the sequence of variants for this langtag.
164
+ #
165
+ # ==== Example
166
+ #
167
+ # tag = Lang::Tag('sl')
168
+ # tag.variants = ['rozaj', 'solba', '1994']
169
+ # tag.variants_sequence #=> 'rozaj-solba-1994'
170
+ # tag.variants #=> ['rozaj', 'solba', '1994']
171
+ #
172
+ def variants=(value)
173
+ subtags = Array(value).flatten
174
+ if subtags.empty?
175
+ self.variants_sequence = nil
176
+ else
177
+ self.variants_sequence = subtags.join(HYPHEN)
178
+ @variants = subtags
179
+ end
180
+ end
181
+
182
+ # Returns a list of variants of this lantag.
183
+ #
184
+ def variants
185
+ return nil unless @variants_sequence
186
+ @variants ||= @variants_sequence.split(HYPHEN_SPLITTER)
187
+ end
188
+
189
+ def set_variants_sequence(sequence)
190
+ if sequence && sequence.downcase.split(HYPHEN_SPLITTER).uniq!
191
+ raise InvalidComponentError, "#{sequence.inspect} sequence includes repeated variants."
192
+ end
193
+ @variants_sequence = sequence
194
+ @variants = nil
195
+ nil
196
+ end
197
+
198
+ protected :set_variants_sequence
199
+
200
+ # Checks if self has a variant or a sequence of
201
+ # variants passed. Works case-insensitively.
202
+ #
203
+ def has_variant?(sequence)
204
+ return false unless @variants_sequence
205
+ /(?:^|-)#{sequence}(?:-|$)/i === @variants_sequence
206
+ end
207
+
208
+ #--
209
+ # RFC 5646, sec. 2.2.6:
210
+ # Extensions provide a mechanism for extending language tags for use in
211
+ # various applications. They are intended to identify information that
212
+ # is commonly used in association with languages or language tags but
213
+ # that is not part of language identification.
214
+ #++
215
+
216
+ # Sets the sequence of extensions for this langtag.
217
+ #
218
+ def extensions_sequence=(value)
219
+ sequence = value ? value.to_str : nil
220
+ if sequence && EXTENSIONS_SEQUENCE_REGEX !~ "#{HYPHEN}#{sequence}"
221
+ raise InvalidComponentError, "#{value.inspect} does not conform to the 'extensions' ABNF."
222
+ end
223
+ set_extensions_sequence(sequence)
224
+ dirty
225
+ validate
226
+ end
227
+
228
+ # Friendly version of the #extensions_sequence=.
229
+ # Sets the sequence of extensions for this langtag.
230
+ #
231
+ def extensions=(value)
232
+ subtags = Array(value).flatten
233
+ self.extensions_sequence = subtags.empty? ? nil : subtags.join(HYPHEN)
234
+ end
235
+
236
+ def set_extensions_sequence(sequence)
237
+ if sequence
238
+ exthash = {}
239
+ sequence.split(EXTENSIONS_SEQUENCE_SPLITTER).each do |seq|
240
+ k,v = seq[0...1], seq[2..-1] # sequence.split(HYPHEN_SPLITTER,2)
241
+ k.downcase!
242
+ if exthash.key?(k)
243
+ raise InvalidComponentError, "#{sequence.inspect} sequence includes repeated singletons."
244
+ end
245
+ exthash[k] = v
246
+ end
247
+ @extensions_sequence = sequence
248
+ @extensions = exthash
249
+ else
250
+ @extensions_sequence = nil
251
+ @extensions = nil
252
+ end
253
+ nil
254
+ end
255
+
256
+ protected :set_extensions_sequence
257
+
258
+ # Builds an *ordered* list of *downcased* singletons.
259
+ #
260
+ def singletons
261
+ return nil unless @extensions
262
+ keys = @extensions.keys
263
+ keys.sort!
264
+ keys
265
+ end
266
+
267
+ # Returns a sequense of subtags for a singleton passed.
268
+ # Works case-insensitively.
269
+ #
270
+ def extension(key)
271
+ return nil unless @extensions
272
+ sequence = @extensions[key] || @extensions[key = key.downcase]
273
+ return sequence unless String === sequence
274
+ @extensions[key] = sequence.split(HYPHEN) #lazy
275
+ @extensions[key]
276
+ end
277
+
278
+ # Checks if self has a singleton passed.
279
+ # Works case-insensitively.
280
+ #
281
+ def has_singleton?(key)
282
+ return false unless @extensions
283
+ @extensions.key?(key) || @extensions.key?(key.downcase)
284
+ end
285
+
286
+ alias :has_extension? :has_singleton?
287
+
288
+ #--
289
+ # RFC 5646, sec. 2.2.7:
290
+ # Private use subtags are used to indicate distinctions in language
291
+ # that are important in a given context by private agreement.
292
+ #
293
+ # RFC 5646, sec. 2.2.7:
294
+ # For example, suppose a group of scholars is studying some texts in
295
+ # medieval Greek. They might agree to use some collection of private
296
+ # use subtags to identify different styles of writing in the texts.
297
+ # For example, they might use 'el-x-koine' for documents in the
298
+ # "common" style while using 'el-x-attic' for other documents that
299
+ # mimic the Attic style. These subtags would not be recognized by
300
+ # outside processes or systems, but might be useful in categorizing
301
+ # various texts for study by those in the group.
302
+ #++
303
+
304
+ def privateuse
305
+ return nil unless @privateuse_sequence
306
+ @privateuse ||= @privateuse_sequence.split(HYPHEN)[1..-1]
307
+ end
308
+
309
+ # Friendly version of the #privateuse_sequence=.
310
+ # Sets the 'privateuse' sequence for this langtag.
311
+ #
312
+ # ==== Example
313
+ #
314
+ # tag = Lang::Tag('de')
315
+ # tag.privateuse = ['private', 'use', 'sequence']
316
+ # tag.privateuse_sequence #=> 'x-private-use-sequence'
317
+ #
318
+ def privateuse=(value)
319
+ subtags = Array(value).flatten
320
+ if subtags.empty?
321
+ self.privateuse_sequence = nil
322
+ else
323
+ self.privateuse_sequence = subtags.unshift(PRIVATEUSE).join(HYPHEN)
324
+ @privateuse = subtags
325
+ end
326
+ end
327
+
328
+ # Sets the 'privateuse' sequence for this langtag.
329
+ #
330
+ def privateuse_sequence=(value)
331
+ sequence = value ? value.to_str : nil
332
+ if sequence && Tag::PRIVATEUSE_REGEX !~ sequence
333
+ raise InvalidComponentError, "#{value.inspect} does not conform to the 'privateuse' ABNF."
334
+ end
335
+ @privateuse_sequence = sequence
336
+ @privateuse = nil
337
+ dirty
338
+ validate
339
+ end
340
+
341
+ def dirty
342
+ @sequence = nil
343
+ super
344
+ end
345
+
346
+ private :dirty
347
+
348
+ def defer_validation(&block)
349
+ raise LocalJumpError, "No block given." unless block
350
+ @validation_deferred = true
351
+ yield
352
+ @validation_deferred = false
353
+ validate
354
+ nil
355
+ end
356
+
357
+ def validate
358
+ return if !!@validation_deferred
359
+ if @language.nil?
360
+ raise InvalidComponentError, "Primary subtag cannot be omitted."
361
+ end
362
+ nil
363
+ end
364
+
365
+ private :validate
366
+
367
+ def nicecase!
368
+
369
+ # ugly, but faster than recompose
370
+
371
+ if @language && @language.downcase!
372
+ @primary = nil
373
+ @extlang = nil
374
+ end
375
+
376
+ # [ISO639-1] recommends that language codes be written in lowercase ('mn' Mongolian).
377
+ # [ISO15924] recommends that script codes use lowercase with the initial letter capitalized ('Cyrl' Cyrillic).
378
+ # [ISO3166-1] recommends that country codes be capitalized ('MN' Mongolia).
379
+
380
+ @script.capitalize! if @script
381
+ @region.upcase! if @region
382
+
383
+ @variants = nil if @variants_sequence &&
384
+ @variants_sequence.downcase!
385
+
386
+ set_extensions_sequence(@extensions_sequence) if @extensions_sequence &&
387
+ @extensions_sequence.downcase!
388
+
389
+ @privateuse = nil if @privateuse_sequence &&
390
+ @privateuse_sequence.downcase!
391
+
392
+ @sequence = nil
393
+ end
394
+
395
+ def to_s
396
+ return @sequence if @sequence
397
+ @sequence = ""
398
+ @sequence << @language if @language
399
+ @sequence << HYPHEN << @script if @script
400
+ @sequence << HYPHEN << @region if @region
401
+ @sequence << HYPHEN << @variants_sequence if @variants_sequence
402
+ @sequence << HYPHEN << @extensions_sequence if @extensions_sequence
403
+ @sequence << HYPHEN << @privateuse_sequence if @privateuse_sequence
404
+ @sequence
405
+ end
406
+
407
+ def recompose(thing)
408
+
409
+ raise TypeError, "Can't convert #{thing.class} into String" unless thing.respond_to?(:to_str)
410
+ tag = thing.to_str
411
+
412
+ if LANGTAG_REGEX === tag
413
+
414
+ dirty
415
+
416
+ @sequence = tag
417
+ @primary = nil
418
+ @extlang = nil
419
+ @language = $1
420
+ @script = $2
421
+ @region = $3
422
+ set_variants_sequence $4[1..-1]
423
+ set_extensions_sequence $5[1..-1]
424
+ @privateuse_sequence = $'[1..-1]
425
+ @privateuse = nil
426
+
427
+ else
428
+ raise ArgumentError, "Ill-formed, grandfathered or 'privateuse' language tag: #{thing.inspect}."
429
+ end
430
+ self
431
+ end
432
+
433
+ end
434
+ end
435
+ end
436
+
437
+ # EOF