when_exe 0.3.7 → 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +210 -171
  3. data/bin/irb.rc +1 -0
  4. data/lib/when_exe.rb +78 -53
  5. data/lib/when_exe/basictypes.rb +27 -8
  6. data/lib/when_exe/calendarnote.rb +848 -805
  7. data/lib/when_exe/calendartypes.rb +110 -240
  8. data/lib/when_exe/coordinates.rb +2440 -2175
  9. data/lib/when_exe/core/compatibility.rb +1 -1
  10. data/lib/when_exe/core/duration.rb +13 -11
  11. data/lib/when_exe/core/extension.rb +38 -45
  12. data/lib/when_exe/ephemeris.rb +43 -14
  13. data/lib/when_exe/ephemeris/eclipse.rb +149 -0
  14. data/lib/when_exe/ephemeris/notes.rb +39 -7
  15. data/lib/when_exe/icalendar.rb +2 -6
  16. data/lib/when_exe/inspect.rb +1408 -1399
  17. data/lib/when_exe/parts/enumerator.rb +486 -477
  18. data/lib/when_exe/parts/resource.rb +1101 -1069
  19. data/lib/when_exe/parts/timezone.rb +6 -5
  20. data/lib/when_exe/region/babylonian.rb +405 -405
  21. data/lib/when_exe/region/bahai.rb +21 -61
  22. data/lib/when_exe/region/chinese/epochs.rb +81 -81
  23. data/lib/when_exe/region/chinese/twins.rb +51 -51
  24. data/lib/when_exe/region/christian.rb +7 -2
  25. data/lib/when_exe/region/coptic.rb +1 -1
  26. data/lib/when_exe/region/discordian.rb +9 -16
  27. data/lib/when_exe/region/french.rb +1 -1
  28. data/lib/when_exe/region/hanke_henry.rb +57 -0
  29. data/lib/when_exe/region/indian.rb +41 -73
  30. data/lib/when_exe/region/international_fixed.rb +97 -0
  31. data/lib/when_exe/region/iranian.rb +203 -203
  32. data/lib/when_exe/region/japanese.rb +13 -13
  33. data/lib/when_exe/region/japanese/eclipses.rb +1194 -0
  34. data/lib/when_exe/region/japanese/notes.rb +1482 -1383
  35. data/lib/when_exe/region/japanese/residues.rb +726 -721
  36. data/lib/when_exe/region/japanese/twins.rb +37 -37
  37. data/lib/when_exe/region/jewish.rb +2 -2
  38. data/lib/when_exe/region/pax.rb +60 -0
  39. data/lib/when_exe/region/positivist.rb +100 -0
  40. data/lib/when_exe/region/roman.rb +333 -334
  41. data/lib/when_exe/region/shire.rb +3 -20
  42. data/lib/when_exe/region/thai.rb +2 -2
  43. data/lib/when_exe/region/tibetan.rb +3 -3
  44. data/lib/when_exe/region/tranquility.rb +208 -0
  45. data/lib/when_exe/region/vanishing_leprechaun.rb +53 -0
  46. data/lib/when_exe/region/vietnamese.rb +4 -4
  47. data/lib/when_exe/region/world.rb +9 -13
  48. data/lib/when_exe/region/world_season.rb +89 -0
  49. data/lib/when_exe/region/zoroastrian.rb +4 -2
  50. data/lib/when_exe/tmobjects.rb +14 -4
  51. data/lib/when_exe/tmposition.rb +239 -81
  52. data/lib/when_exe/tmreference.rb +57 -28
  53. data/lib/when_exe/version.rb +1 -1
  54. data/link_to_online_documents +6 -3
  55. data/test/examples/today.rb +1 -1
  56. data/test/scripts.rb +23 -0
  57. data/test/scripts/2.ext.rb +169 -0
  58. data/test/scripts/2.rb +169 -0
  59. data/test/scripts/3.ext.rb +133 -0
  60. data/test/scripts/3.rb +134 -0
  61. data/test/scripts/4.ext.rb +112 -0
  62. data/test/scripts/4.min.rb +65 -0
  63. data/test/scripts/4.rb +136 -0
  64. data/test/scripts/5.ext.rb +78 -0
  65. data/test/scripts/5.rb +81 -0
  66. data/test/scripts/6.gcal.rb +131 -0
  67. data/test/scripts/6.rb +205 -0
  68. data/test/scripts/6.tz.rb +105 -0
  69. data/test/scripts/7.phase.rb +109 -0
  70. data/test/scripts/7.rb +95 -0
  71. data/test/scripts/7.term.rb +128 -0
  72. data/test/scripts/7.week.rb +84 -0
  73. data/test/scripts/8.ext.rb +61 -0
  74. data/test/scripts/8.rb +62 -0
  75. data/test/scripts/9.ext.rb +131 -0
  76. data/test/scripts/9.rb +130 -0
  77. data/test/scripts/chinese-luni-solar.rb +52 -0
  78. data/test/{examples → scripts}/geometric_complex.rb +41 -41
  79. data/test/scripts/geometric_complex.txt +18 -0
  80. data/test/scripts/korea.rb +59 -0
  81. data/test/scripts/thai-reviewed.txt +211 -0
  82. data/test/scripts/thai.rb +36 -0
  83. data/test/scripts/thai.txt +210 -0
  84. data/test/test.rb +7 -0
  85. data/test/test/basictypes.rb +22 -0
  86. data/test/test/coordinates.rb +2 -1
  87. data/test/test/ephemeris.rb +34 -2
  88. data/test/test/icalendar.rb +12 -0
  89. data/test/test/inspect.rb +37 -1
  90. data/test/test/parts.rb +4 -3
  91. data/test/test/region/armenian.rb +20 -0
  92. data/test/test/region/bahai.rb +58 -0
  93. data/test/test/region/chinese.rb +14 -3
  94. data/test/test/region/christian.rb +16 -35
  95. data/test/test/region/discordian.rb +20 -0
  96. data/test/test/region/indian.rb +30 -2
  97. data/test/test/region/japanese.rb +24 -0
  98. data/test/test/region/jewish.rb +2 -0
  99. data/test/test/region/m17n.rb +9 -0
  100. data/test/test/region/reforms.rb +121 -0
  101. data/test/test/region/residue.rb +17 -11
  102. data/test/test/region/shire.rb +58 -0
  103. data/test/test/region/swedish.rb +45 -0
  104. data/test/test/region/zoroastrian.rb +58 -0
  105. data/test/test/tmobjects.rb +74 -0
  106. data/test/test/tmposition.rb +468 -397
  107. data/when_exe.gemspec +2 -2
  108. metadata +49 -6
@@ -1,1069 +1,1101 @@
1
- # -*- coding: utf-8 -*-
2
- =begin
3
- Copyright (C) 2011-2014 Takashi SUGA
4
-
5
- You may use and/or modify this file according to the license described in the LICENSE.txt file included in this archive.
6
- =end
7
-
8
- #
9
- # 本ライブラリのための諸々の部品
10
- #
11
- module When::Parts
12
-
13
- #
14
- # Resource which has 'International Resource Identifier'
15
- #
16
- module Resource
17
-
18
- # @private
19
- LabelProperty = nil
20
-
21
- # @private
22
- ConstTypes = {
23
- 'SolarTerms' => ['CalendarNote', '%sSolarTerms', '_n:%sSolarTerms%s' ],
24
- 'LunarPhases' => ['LunarPhases', '%sLunarPhases', '_n:%sLunarPhases%s'],
25
- 'Terms' => ['BasicTypes::M17n', '%s', '_m:%s%s' ],
26
- 'Era' => ['TM::CalendarEra', '%s', '_e:%s%s' ],
27
- 'Residue' => ['Coordinates', '%s', '_co:%s%s' ],
28
- 'Week' => ['CalendarNote', '%sWeek', '_n:%sWeek%s' ],
29
- 'Note' => ['CalendarNote', '%s', '_n:%s%s' ],
30
- 'Notes' => ['CalendarNote', '%s', '_n:%s/Notes%s' ],
31
- nil => ['CalendarTypes', '%s', '_c:%s%s' ]
32
- }
33
-
34
- # @private
35
- ConstList = []
36
-
37
- # private
38
- IRIHeader = /\A[_a-z\d]+:[^:]/i
39
-
40
- # @private
41
- class ContentLine
42
-
43
- RFC6350 = {
44
- "\\\\" => "\\",
45
- "\\n" => "\n",
46
- "\\N" => "\n",
47
- "\\;" => ";",
48
- "\\:" => ":"
49
- }
50
-
51
- RFC6868 = {
52
- "^n" => "\n",
53
- "^^" => "^",
54
- "^'" => '"'
55
- }
56
-
57
- attr_reader :predicate
58
- attr_accessor :object
59
- attr_reader :attribute
60
- attr_accessor :namespace
61
- attr_accessor :same_altid
62
- attr_reader :marked
63
-
64
- def initialize(key, object=nil, marked=nil)
65
- key = key.downcase.gsub(/-/,'_') if (key==key.upcase)
66
- @predicate, @namespace = key.split(/:/).reverse
67
- object = object.gsub(/\^./) {|escape| RFC6868[escape] || escape} if object.instance_of?(String)
68
- @object = object
69
- @marked = marked
70
- @attribute = {}
71
- end
72
- end
73
-
74
- # @private
75
- module Synchronize
76
-
77
- # 排他実行
78
- #
79
- # 与えられたブロックを必要なら排他制御をして実行する
80
- #
81
- def synchronize
82
- if @_lock_
83
- @_lock_.synchronize do
84
- yield
85
- end
86
- else
87
- yield
88
- end
89
- end
90
- end
91
-
92
- #
93
- # Resource の has-a 親子関係を管理する
94
- #
95
- module Pool
96
-
97
- include Synchronize
98
-
99
- # 初期化
100
- # @return [void]
101
- #
102
- # @note
103
- # 本メソッドでマルチスレッド対応の管理変数の初期化を行っている。
104
- # このため、本メソッド自体はスレッドセーフでない。
105
- #
106
- def _setup_
107
- @_lock_ = Mutex.new if When.multi_thread
108
- @_pool = {}
109
- end
110
-
111
- # オブジェクト参照
112
- #
113
- # @param [String] label
114
- #
115
- # @return [When::Parts::Resource] 指定した label で登録した子 Resource を返す
116
- #
117
- def [](label)
118
-
119
- # nil label の場合
120
- return _pool[label] unless label
121
-
122
- # 階層がある場合
123
- terms = Resource._encode(label).split(/::/)
124
- terms.shift if terms[0] == ''
125
- return terms.inject(self) {|obj,term| obj = obj[Resource._decode(term)]} if terms.length >= 2
126
-
127
- # 階層がない場合
128
- _pool[Resource._decode(Resource._extract_prefix(terms[0]))]
129
- end
130
-
131
- # オブジェクト登録
132
- #
133
- # 指定した label で子 Resource を登録する
134
- #
135
- # @param [String] label
136
- # @param [When::Parts::Resource] obj
137
- #
138
- # @return [void]
139
- #
140
- def []=(label, obj)
141
- # raise NameError, "Name duplication" if (@_pool[label])
142
- _pool[label] = obj
143
- end
144
-
145
- # @private
146
- def pool_keys
147
- _pool.keys
148
- end
149
-
150
- # @private
151
- def _pool
152
- _setup_ unless @_pool
153
- @_pool
154
- end
155
- end
156
-
157
- class << self
158
-
159
- include Pool
160
-
161
- # Base URI for When_exe Resources
162
- #
163
- # @return [String]
164
- #
165
- def base_uri
166
- @base_uri ||= When::SourceURI
167
- end
168
-
169
- # Root Directory for When_exe Resources
170
- #
171
- # @return [String]
172
- #
173
- def root_dir
174
- @root_dir ||= When::RootDir
175
- end
176
-
177
- #
178
- # 略称を iri に変換する
179
- #
180
- # @param [String or Symbol] abbreviation 略称
181
- #
182
- # @return [String] iri or nil
183
- #
184
- # @private
185
- def _abbreviation_to_iri(abbreviation, abbreviation_types=ConstTypes)
186
- abbreviation_types[:pattern] ||= /\A(?=[A-Z])(.*?)(#{abbreviation_types.keys.compact.join('|')})?(\?.+|::.+)?\z/
187
- abbreviation.to_s =~ abbreviation_types[:pattern]
188
- return nil unless $1
189
- klass, name, iri = abbreviation_types[$2]
190
- key, name, iri = $2, name % $1, iri % [$1,$3]
191
- if klass.kind_of?(String)
192
- klass = klass.split('::').inject(When) {|k,n| k.const_get(n)}
193
- abbreviation_types[key][0] = klass
194
- end
195
- klass.const_defined?(name) ? iri : nil
196
- end
197
-
198
- # @private
199
- attr_reader :_prefix, :_prefix_values, :_prefix_index
200
- private :_prefix, :_prefix_values, :_prefix_index
201
-
202
- # 初期化
203
- #
204
- # @param [Hash] options 以下の通り
205
- # @option options [String] :base_uri Base URI for When_exe Resources (Default When::SourceURI)
206
- # @option options [Hash<String(namespace)=>String(URI)>] :additional_namespaces User defined namespaces (Default {})
207
- # @option options [String] :root_dir Root Directory for When_exe Resources Cash data (Default When::RootDir)
208
- # @option options [Boolean] :leave_const If true, leave Constants of When module defined
209
- #
210
- # @return [void]
211
- #
212
- # @note
213
- # 本メソッドでマルチスレッド対応の管理変数の初期化を行っている。
214
- # このため、本メソッド自体はスレッドセーフでない。
215
- #
216
- def _setup_(options={})
217
- super()
218
- @_prefix = {
219
- '_wp' => 'http://en.wikipedia.org/wiki/',
220
- '_w' => base_uri + '/',
221
- '_p' => base_uri + 'Parts/',
222
- '_b' => base_uri + 'BasicTypes/',
223
- '_m' => base_uri + 'BasicTypes/M17n/',
224
- '_co' => base_uri + 'Coordinates/',
225
- '_l' => base_uri + 'Coordinates/Spatial?',
226
- '_v' => base_uri + 'V/',
227
- '_rs' => base_uri + 'RS/',
228
- '_ex' => base_uri + 'EX/',
229
- '_tm' => base_uri + 'TM/',
230
- '_e' => base_uri + 'TM/CalendarEra/',
231
- '_t' => base_uri + 'TimeStandard/',
232
- '_ep' => base_uri + 'Ephemeris/',
233
- '_c' => base_uri + 'CalendarTypes/',
234
- '_n' => base_uri + 'CalendarNote/',
235
- '_sc' => base_uri + 'Ephemeris/V50/'
236
- }
237
- @base_uri = options[:base_uri] || When::SourceURI
238
- @root_dir = options[:root_dir] || When::RootDir
239
- @_prefix = options[:additional_namespaces].merge(@_prefix) if options[:additional_namespaces].kind_of?(Hash)
240
- @_prefix_values = @_prefix.values.sort.reverse
241
- @_prefix_index = @_prefix.invert
242
- unless options[:leave_const] || ConstList.empty?
243
- ConstList.delete_if do |constant|
244
- When.send(:remove_const, constant) if When.const_defined?(constant)
245
- true
246
- end
247
- When._define_common_calendar_types
248
- end
249
- end
250
-
251
- # 設定情報を取得する
252
- #
253
- # @return [Hash] 設定情報
254
- #
255
- def _setup_info
256
- {:base_uri => base_uri, :root_dir => root_dir}
257
- end
258
-
259
- # オブジェクト生成&参照
260
- #
261
- # 指定した iri の When::Parts::Resource オブジェクトを取得する。
262
- # 当該オブジェクトが未登録であれば生成する。
263
- #
264
- # @param [String] iri International Resource Identifier
265
- # @param [String] namespace (デフォルトの名前空間, 指定がないときは名前空間を省略しない)
266
- # @param [Block] block オブジェクトが見つからない場合の代替処理
267
- #
268
- # @return [When::Parts::Resource]
269
- #
270
- def _instance(iri, namespace=nil, &block)
271
- _setup_ unless @_pool
272
-
273
- case iri
274
- when Array ; return iri.map {|e| _instance(e, namespace)} # 配列は個別に処理
275
- when Resource ; return iri # 登録済みはそのまま
276
- when String ; # 解析処理へ
277
- else ; raise ArgumentError, "can't convert #{iri.class} to String" # 例外
278
- end
279
-
280
- # 内部文字列化
281
- iri = When::EncodingConversion.to_internal_encoding(iri)
282
-
283
- # 階層がある場合は、階層をたどる
284
- iri = Resource._decode(iri)
285
- iri = $1 while iri =~ /\A\((.*)\)\z/
286
- iri = namespace + iri if namespace && iri !~ IRIHeader
287
- root, *leaves= Resource._encode(iri).split(/::/)
288
- if leaves.size > 0
289
- return leaves.inject(_instance(Resource._decode(root))) {|obj,leaf| obj[Resource._decode(leaf)]}
290
- end
291
-
292
- # 登録ずみなら、参照
293
- iri = _extract_prefix(iri)
294
- path, query = iri.split(/\?/, 2)
295
- if When.multi_thread
296
- my_mutex = nil
297
- @_lock_.synchronize do
298
- @_pool ||= {}
299
- unless @_pool[iri]
300
- my_mutex = Mutex.new
301
- @_pool[iri] = my_mutex
302
- end
303
- end
304
- case @_pool[iri]
305
- when my_mutex; my_mutex.synchronize {@_pool[iri] = _create_object(iri, path, query, &block) }
306
- when Mutex ; @_pool[iri].synchronize {@_pool[iri]}
307
- else ; @_pool[iri]
308
- end
309
- else
310
- @_pool ||= {}
311
- @_pool[iri] ||= _create_object(iri, path, query, &block)
312
- end
313
- end
314
-
315
- # @private
316
- def _path_with_prefix(obj, simple=true)
317
- _setup_ unless @_pool
318
- path = obj.kind_of?(Class) ? obj.to_s.sub(/\AWhen::/, base_uri).gsub(/::/, '/') :
319
- obj.iri
320
- simple ? _simplify_path(path) : path
321
- end
322
-
323
- # @private
324
- def _simplify_path(path)
325
- _prefix_values.each do |value|
326
- index = path.index(value)
327
- return _prefix_index[value] + ':' + path[value.length..-1] if index
328
- end
329
- return path
330
- end
331
-
332
- # @private
333
- def _parse(line, type=nil)
334
- return line unless line.kind_of?(String)
335
- line.sub!(/\s#.*\z/, '')
336
- return When::Locale._split($1) if type && /\A#{type}:(.+)\z/i =~ line
337
- tokens = line.scan(/((?:[^\\:]|\\.)+)(?::(?!\z))?|:/).flatten
338
- return When::Locale._split(line) unless tokens.size > 1 && /\A(\*)?([A-Z][-A-Z_]{0,255})(?:;(.+))?\z/i =~ tokens[0]
339
- marked, key, property = $~[1..3]
340
- values = tokens[1..-1]
341
- value = values.join(':') unless values == [nil]
342
- content = ContentLine.new(key, value, marked)
343
- value ||= ''
344
- if property
345
- content.attribute['.'] = property + ':' + value
346
- property.scan(/((?:[^\\;]|\\.)+)(?:;(?!\z))?|;/).flatten.each do |pr|
347
- pr ||= ''
348
- pr.gsub!(/\\./) {|escape| ContentLine::RFC6350[escape] || escape}
349
- prop = ContentLine.new(*pr.split(/=/, 2))
350
- content.attribute[prop.predicate] = prop
351
- end
352
- else
353
- content.attribute['.'] = value
354
- end
355
- return content
356
- end
357
-
358
- # @private
359
- def _extract_prefix(path, capitalize=false)
360
- if (path =~ /\A(.+?):+(.+)\z/)
361
- prefix, klass = $~[1..2]
362
- if capitalize
363
- prefix = '_' + prefix.downcase
364
- klass = klass.capitalize if klass == klass.upcase
365
- end
366
- path = _prefix[prefix] + klass if _prefix[prefix]
367
- elsif capitalize && path =~ /\A(v[^\/]+|daylight$|standard$)/i
368
- klass = path.sub(/\Av/i, '').capitalize
369
- path = _prefix['_v'] + klass if When::V.const_defined?(klass) &&
370
- When::V.const_get(klass).kind_of?(Class)
371
- end
372
- return path
373
- end
374
-
375
- # @private
376
- def _replace_tags(source, tags)
377
- case source
378
- when When::BasicTypes::M17n
379
- source
380
- when String
381
- target = source.dup
382
- tags.each_pair do |key, value|
383
- target.gsub!(/#\{([?&][^=#}]+?=)?#{key}(:.*?)?\}/, '\1' + value) if value.kind_of?(String)
384
- end
385
- target.gsub(/#\{.+?(:(.*?))?\}/, '\2')
386
- when Array
387
- source.map {|target| _replace_tags(target, tags)}
388
- when Hash
389
- target = {}
390
- source.each_pair do |key, value|
391
- target[key] = _replace_tags(tags[key] || value, tags)
392
- end
393
- target
394
- else
395
- source
396
- end
397
- end
398
-
399
- # @private
400
- def _encode(iri)
401
- return iri unless iri =~ /\(/
402
-
403
- iri = iri.dup
404
- begin
405
- unless iri.gsub!(/\([^()]*\)/) {|token|
406
- token.gsub(/[():?&%]/) {|char|'%' + char.ord.to_s(16)}
407
- }
408
- raise ArgumentError, 'Brackets do not correspond: ' + iri
409
- end
410
- end while iri =~ /\(/
411
- iri
412
- end
413
-
414
- # @private
415
- def _decode(iri)
416
- return iri unless iri =~ /%28/
417
-
418
- iri = iri.dup
419
- begin
420
- unless iri.gsub!(/%28.*?%29/) {|token|
421
- token.gsub(/%([\dA-F]{2})/i) {$1.to_i(16).chr}
422
- }
423
- raise ArgumentError, 'Brackets do not correspond: ' + iri
424
- end
425
- end while iri =~ /%28/
426
- iri = $1 if iri =~ /\A\((.*)\)\z/
427
- iri
428
- end
429
-
430
- # @private
431
- def _instantiate(resource)
432
- return resource unless resource.kind_of?(Array)
433
- return resource[0].new(*resource[1..-1]) if resource[0].kind_of?(Class)
434
- return resource.map {|rsc| _instantiate(rsc)}
435
- end
436
-
437
- private
438
-
439
- # オブジェクト生成
440
- def _create_object(iri, path, query, &block)
441
- # query analyzation
442
- options = {}
443
- replace = {}
444
- if query
445
- options = Hash[*Resource._encode(query).split(/&/).map{|pair|
446
- key, value = pair.split(/=/, 2)
447
- [key, Resource._decode(value)]
448
- }.flatten]
449
- keys = options.keys
450
- keys.each do |key|
451
- replace[$1] = options.delete(key) if key =~ /\A([A-Z].*)/
452
- end
453
- end
454
- options['..'] = iri
455
-
456
- # internal Resource
457
- if path.index(Resource.base_uri) == 0
458
- list = _class(path)
459
- if list
460
- return _internal(list, replace, options)
461
- else
462
- object = yield(iri) if block_given?
463
- return object if object
464
- raise IOError, 'IRI not found - ' + path
465
- end
466
- end
467
-
468
- # external Resource
469
- begin
470
- object = When::Locale.send(:wikipedia_object, path, {:query=>query})
471
- return object if object
472
- OpenURI
473
- args = [path, "1".respond_to?(:force_encoding) ? 'r:utf-8' : 'r']
474
- args << {:ssl_verify_mode=>OpenSSL::SSL::VERIFY_NONE} if path =~ /\Ahttps:/
475
- open(*args) do |file|
476
- resource = file.read
477
- case resource[0..5].upcase
478
- when 'BEGIN:'
479
- options['.'] = _ics(_replace_tags(resource, replace).split(/[\n\r]+/))
480
- options['.'][0].new(options)
481
- when '<?XML '
482
- options['.'] = _xml(REXML::Document.new(_replace_tags(resource, replace)).root)
483
- options['.'][0].new(options)
484
- else
485
- raise NoMethodError, 'JSON not supported' unless Object.const_defined?(:JSON)
486
- _internal(_json([JSON.parse(resource)]), replace, options)
487
- end
488
- end
489
- rescue OpenURI::HTTPError => error
490
- message = error.message + " - #{path}"
491
- error = error.respond_to?(:uri) ?
492
- error.class.new(message, error.io, error.uri) :
493
- error.class.new(message, error.io)
494
- raise error
495
- end
496
- end
497
-
498
- # 指定のモジュール/クラスで直接定数が定義されているか
499
- if method(:const_defined?).arity == 1
500
- def _directry_defined?(klass, const)
501
- klass.const_defined?(const)
502
- end
503
- else
504
- def _directry_defined?(klass, const)
505
- klass.const_defined?(const, false)
506
- end
507
- end
508
-
509
- # 内部形式定義の取得
510
- def _class(path)
511
- list = [When]
512
- path[Resource.base_uri.length..-1].split(/\//).each do |mod|
513
- if list[0].kind_of?(Module) && _directry_defined?(list[0], mod)
514
- list.unshift(list[0].const_get(mod))
515
- else
516
- return nil unless list[0] == When::V
517
- list.unshift(When::V::Root)
518
- return list
519
- end
520
- end
521
- return list
522
- end
523
-
524
- # 内部形式定義のオブジェクト化
525
- def _internal(list, replace, options)
526
- case list[0]
527
- when Class
528
- list[0].new(options)
529
- when Array
530
- top = list[0][0]
531
- if top.kind_of?(Hash)
532
- top.each_pair do |key, value|
533
- replace.update(value[replace[key]]) if value.kind_of?(Hash) && value[replace[key]]
534
- end
535
- list[0] = list[0][1..-1]
536
- list[0] = _replace_tags(list[0], top.merge(replace))
537
- end
538
- if list[0][0].kind_of?(Class)
539
- # 配列の先頭がクラスである場合
540
- klass, *list = list[0]
541
- unless list[-1].kind_of?(Hash)
542
- if list.length == 1
543
- list[0] = {'.'=>Array(list[0])}
544
- else
545
- list << {}
546
- end
547
- end
548
- else
549
- # 配列の先頭がクラスではない場合
550
- klass, *list = [list[1], *list[0]]
551
- list << {} unless list[-1].kind_of?(Hash)
552
- end
553
- list[-1] = list[-1].merge(options)
554
- klass.new(*list)
555
- else
556
- list[0]
557
- end
558
- end
559
-
560
- # .xml フォーマットの読み込み
561
- def _xml(xml, namespace={})
562
- obj = [_class(_extract_prefix(xml.attributes['type'].to_s))[0]]
563
- xml.attributes.each_pair do |key,value|
564
- expanded_name = value.expanded_name
565
- next unless (expanded_name =~ /\Axmlns/)
566
- key = '' if expanded_name == 'xmlns'
567
- namespace[key] = value.to_s
568
- end
569
- obj << ContentLine.new('xmlns:namespace', namespace) if (namespace.size>0)
570
- xml.each do |e|
571
- next unless defined? e.name
572
- if (e.attributes['type'])
573
- obj << _xml(e, namespace)
574
- else
575
- content = ContentLine.new(e.expanded_name, e.attributes['ref']||e.text)
576
- e.attributes.each_pair do |key,value|
577
- attr = ContentLine.new(value.name, value)
578
- attr.namespace = value.prefix
579
- content.attribute[key] = attr
580
- end
581
- obj << content
582
- end
583
- end
584
- return obj
585
- end
586
-
587
- # .ics フォーマットの読み込み
588
- def _ics(ics, type=nil)
589
- obj = [type] if type
590
- indent = nil
591
- while (line = ics.shift) do
592
- line.chomp!
593
- case line
594
- when /\A\s*BEGIN:(.*)\z/
595
- if type
596
- subtype = $1
597
- obj[-1] = _parse(obj[-1], type) if obj.length > 1
598
- obj << _ics(ics, subtype)
599
- else
600
- type = $1
601
- obj = [type]
602
- end
603
- when /\A\s*END:(.*)\z/
604
- raise TypeError, "Irregal Type : #{$1}" unless type == $1
605
- obj[0] = _class(_extract_prefix(type, true))[0]
606
- obj[-1] = _parse(obj[-1], type)
607
- return obj
608
- when /\A\s*#/
609
- when /\A(\s*)(.*)\z/
610
- indent = $1 unless indent
611
- if indent.length < $1.length
612
- obj[-1] += line[(indent.length+1)..-1] # See RFC5545 3.1 Content Lines
613
- else
614
- obj << $2
615
- obj[-2] = _parse(obj[-2], type)
616
- end
617
- end
618
- end
619
- raise ArgumentError, "BEGIN-END mismatch"
620
- end
621
-
622
- # .json フォーマットの読み込み
623
- def _json(json)
624
- case json
625
- when Array
626
- json.map {|value| _json(value)}
627
- when Hash
628
- hash = {}
629
- json.each_pair {|key, value| hash[key] = _json(value)}
630
- hash
631
- when String
632
- return json unless json =~ /\AWhen::/
633
- begin
634
- return json.split('::').inject(Object) {|ns, sym| ns.const_get(sym)}
635
- rescue
636
- json
637
- end
638
- else
639
- json
640
- end
641
- end
642
- end
643
-
644
- include Synchronize
645
-
646
- # @private
647
- attr_reader :_pool
648
-
649
- # self が has-a 関係で包含するオブジェクト
650
- #
651
- # @return [Array<When::Parts::Resource>]
652
- #
653
- attr_accessor :child
654
- private :child=
655
-
656
- #
657
- # Resource包含階層で使用する namespace
658
- #
659
- # When::BasicTypes::M17n の生成に使用する namespace を定義する。
660
- # RFC 5545 に対する拡張である。
661
- # xml で記述する場合には、本ライブラリ以外でも namespace を定義している。
662
- #
663
- # @return [Hash] { prefix => prefix文字列 }
664
- #
665
- attr_reader :namespace
666
-
667
- #
668
- # Resource包含階層で使用する locale
669
- #
670
- # When::BasicTypes::M17n の生成に使用する locale を定義する。
671
- # RFC 5545 に対する拡張である。
672
- #
673
- # @return [Array<String>]
674
- #
675
- attr_reader :locale
676
-
677
- # strftime で有効な locale
678
- #
679
- # @return [Array<String>]
680
- #
681
- attr_reader :keys
682
-
683
- # オブジェクトの IRI
684
- #
685
- # @param [Boolean] prefix true ならIRI の先頭部分を簡約表現にする
686
- #
687
- # @return [Sring]
688
- #
689
- def iri(prefix=false)
690
- unless @iri
691
- root = @_pool['..']
692
- path = root.instance_of?(String) ? root : label.to_s
693
- if root.respond_to?(:iri)
694
- root_iri = root.iri
695
- path = root_iri + '::' + path if root_iri
696
- end
697
- @iri = path
698
- end
699
- prefix ? Resource._simplify_path(@iri) : @iri
700
- end
701
-
702
- # IRI または child の番号によるオブジェクト参照
703
- #
704
- # @param [String] iri オブジェクトの IRI
705
- # @param [Numeric] iri child の index
706
- #
707
- # @return [When::parts::Resource]
708
- #
709
- def [](iri)
710
- case iri
711
- when Numeric
712
- return child[iri * 1]
713
- when String
714
- obj = self
715
- Resource._encode(iri).split(/::/).each do |label|
716
- return obj.child if label == '*'
717
- if obj == Resource
718
- obj = Resource._instance(Resource._decode(label))
719
- else
720
- case label
721
- when '' ; obj = Resource
722
- when '.' # obj = obj
723
- else ; obj = obj._pool[Resource._decode(label)]
724
- end
725
- end
726
- raise ArgumentError, "IRI not found: #{iri}" unless obj
727
- end
728
- return obj
729
- else
730
- super(iri)
731
- #raise ArgumentError, "IRI not found: #{iri}"
732
- end
733
- end
734
-
735
- # self を直接に包含するオブジェクト
736
- #
737
- # @return [When::Parts::Resource]
738
- #
739
- def parent
740
- @_pool['..'].kind_of?(Resource) ? @_pool['..'] : nil
741
- end
742
-
743
- # self を包含するオブジェクト階層
744
- #
745
- # @param [Class] klass 階層を遡るクラス
746
- #
747
- # @return [When::Parts::Resource]
748
- #
749
- def hierarchy(klass=self.class)
750
- hierarchy = []
751
- parent = self
752
- while parent.kind_of?(klass)
753
- hierarchy << parent
754
- parent = parent.parent
755
- end
756
- hierarchy.reverse
757
- end
758
-
759
- # self が other を包含するか
760
- #
761
- # @return [Boolean]
762
- # [ true - 包含する ]
763
- # [ false - 包含しない ]
764
- #
765
- def include?(other)
766
- c = other
767
- while c.kind_of?(Resource)
768
- return true if c.equal?(self)
769
- c = c.parent
770
- end
771
- return false
772
- end
773
-
774
- # other が self を包含するか
775
- #
776
- # @return [Boolean]
777
- # [ true - 包含される ]
778
- # [ false - 包含されない ]
779
- #
780
- def included?(other)
781
- other.include?(self)
782
- end
783
-
784
- # 前のオブジェクト
785
- #
786
- # @return [When::Parts::Resource]
787
- #
788
- def prev
789
- c = self
790
- c = c.child[0] while c.child && c.child.size > 0
791
- c = c._pool['.<-']
792
- c = c.child[-1] while c && c.child && c.child.size > 0
793
- c
794
- end
795
-
796
- # 次のオブジェクト
797
- #
798
- # @return [When::Parts::Resource]
799
- #
800
- def next
801
- c = self
802
- c = c.child[0] while c.child && c.child.size > 0
803
- c._pool['.->']
804
- end
805
- alias :succ :next
806
-
807
- # オブジェクト包含階層の末端か?
808
- #
809
- # @return [Boolean]
810
- # [ true - IRIが付与された他のオブジェクトを包含していない ]
811
- # [ false - IRIが付与された他のオブジェクトを包含している ]
812
- #
813
- def leaf?
814
- !@child || (@child.length==0)
815
- end
816
-
817
- # IRIが付与されているか?
818
- #
819
- # @return [Boolean]
820
- # [ true - IRIが付与されている ]
821
- # [ false - IRIが付与されていない ]
822
- #
823
- def registered?
824
- leaf = self
825
- while leaf._pool['..'].respond_to?(:_pool)
826
- root = leaf._pool['..']
827
- return false unless leaf.equal?(root._pool[leaf.label])
828
- leaf = root
829
- end
830
- Resource._pool.value?(leaf)
831
- end
832
-
833
- # When::BasicTypes::M17n の生成/参照
834
- #
835
- # @param [When::BasicTypes::M17n] source 処理を行わず、そのままsourceを返す
836
- # @param [String] source locale と 文字列の対応
837
- # @param [Array] source 要素を個別に解釈して生成したオブジェクトのArrayを返す
838
- # @param [Hash] namespace prefix の指定
839
- # @param [Array] locale locale の定義順序の指定
840
- # @param [Hash] options (see {When::BasicTypes::M17n.new}[link:When/BasicTypes/M17n.html#method-c-new])
841
- #
842
- # @return [When::BasicTypes::M17n or Array<them>]
843
- #
844
- def m17n(source, namespace=nil, locale=nil, options={})
845
- case source
846
- when Array ; When::BasicTypes::M17n.new(source, namespace, locale, options)
847
- when When::BasicTypes::M17n ; source
848
- when String
849
- return self[$1] if source =~ /\A\s*\[((\.{1,2}|::)+[^\]]+)\]/
850
- When::BasicTypes::M17n.new(source, namespace, locale, options)
851
- else ; raise TypeError, "Invalid Type: #{source.class}"
852
- end
853
- end
854
-
855
- # 子オブジェクトを順に取り出す enumerator
856
- #
857
- def enum_for
858
- @child.enum_for
859
- end
860
- alias :to_enum :enum_for
861
-
862
- # Enumerator 生成のダミー
863
- #
864
- # @param [Object] other
865
- #
866
- # @return [Enumerator]
867
- #
868
- def ^(other)
869
- return nil
870
- end
871
-
872
- # 順次実行
873
- #
874
- # @param [Array] args 各サブクラスの enum_for にそのまま渡す
875
- # @param [Block] block 実行するブロック
876
- #
877
- # @return [Enumerator]
878
- #
879
- def each(*args, &block)
880
- enum = enum_for(*args)
881
- return enum unless block
882
- enum.each(&block)
883
- end
884
-
885
- # map, collect の再定義
886
- #
887
- # has-a 関係の子 Resource に対して map/collect を行う
888
- #
889
- # @param [Block] block 実行するブロック
890
- #
891
- # @return [Array]
892
- #
893
- def map(&block)
894
- @child.map(&block)
895
- end
896
- alias :collect :map
897
-
898
- protected
899
-
900
- # @private
901
- def pool_keys
902
- _pool.keys
903
- end
904
-
905
- private
906
-
907
- # 属性の設定
908
- def _attributes(args)
909
- options =_get_options(args)
910
- _set_variables(options)
911
- return args, options
912
- end
913
-
914
- # option の読み出し
915
- def _get_options(args)
916
- options = args[-1].kind_of?(Hash) ? args.pop : {}
917
- @_pool = {}
918
- @_pool['..'] = options['..'] if (options['..'])
919
-
920
- # 配下のオブジェクトの生成と関連付け
921
- if (options.key?('.'))
922
- _child(options)
923
- end
924
-
925
- options
926
- end
927
-
928
- # 変数の設定
929
- def _set_variables(options)
930
- @options = options[:options] || {} if options.key?(:options)
931
- options.each_pair do |key,value|
932
- unless (key =~ /\Aoptions$|^\.|^[A-Z]/)
933
- case "#{key}"
934
- when 'namespace' ; value = When::Locale._namespace(value)
935
- when 'locale' ; value = When::Locale._locale(value)
936
- end
937
- instance_variable_set("@#{key}", value)
938
- end
939
- end
940
- end
941
-
942
- # 配下のオブジェクトの生成
943
- def _child(options)
944
- @child = []
945
- query = options.dup
946
- options['..'] = self
947
- leaves = options.delete('.').map {|leaf| Resource._parse(leaf)}
948
- key_list = []
949
- properties = {}
950
- label_candidates = nil
951
-
952
- # ContentLine の処理(namespace, locale, altidの前処理)
953
- leaves.each do |content|
954
- next unless content.kind_of?(ContentLine)
955
- key = content.predicate
956
- value = content.object
957
- next options.delete(key) unless value
958
- case key
959
- when 'namespace'
960
- options[key] ||= {}
961
- if content.attribute['altid']
962
- options[key][content.attribute['prefix'].object] = content.object
963
- else
964
- options[key] = options[key].update(When::Locale._namespace(value))
965
- end
966
- when 'locale'
967
- options[key] = When::Locale._locale(value)
968
- else
969
- _parse_altid(properties, content)
970
- key_list << key unless key_list.include?(key)
971
- end
972
- end
973
-
974
- # ContentLine の処理(一般)
975
- key_list.each do |key|
976
- content = properties[key][0]
977
- value = content.same_altid ? When::BasicTypes::M17n.new(content, options['namespace'], []) : content.object
978
- value = When::BasicTypes::M17n.new(value, nil, nil, options) if value.instance_of?(String) && value =~ /\A\s*\[/
979
- @_pool[value.to_s] = value if value.kind_of?(When::BasicTypes::M17n)
980
- if content.marked || key == self.class::LabelProperty
981
- @label = value
982
- else
983
- options[key] = value
984
- label_candidates ||= value
985
- end
986
- end
987
-
988
- # Array の処理(子オブジェクトの生成)
989
- leaves.each do |content|
990
- next unless content.kind_of?(Array)
991
- if content[0].kind_of?(Class)
992
- list = []
993
- content.each do |e|
994
- if e.kind_of?(Hash)
995
- list += e.keys.map {|key| Resource::ContentLine.new(key, e[key])}
996
- else
997
- list << e
998
- end
999
- end
1000
- options['.'] = list
1001
- @child << content[0].new(options.dup)
1002
- else
1003
- options.delete('.')
1004
- @child << self.class.new(*(content + [options]))
1005
- end
1006
- end
1007
-
1008
- # 代表ラベルの設定
1009
- options.update(query)
1010
- unless @label
1011
- raise ArgumentError, "label attribute not found: #{options['.']}" unless label_candidates
1012
- @label = label_candidates
1013
- end
1014
- end
1015
-
1016
- # ALTIDを持つ ContentLine の解析
1017
- def _parse_altid(properties, content)
1018
- key = content.predicate
1019
- properties[key] ||= []
1020
- if content.attribute['altid']
1021
- found = false
1022
- (0...properties[key].length).to_a.each do |i|
1023
- prev = properties[key][i]
1024
- if prev.attribute['altid'] && prev.attribute['altid'].object == content.attribute['altid'].object
1025
- content.same_altid = prev
1026
- properties[key][i] = content
1027
- found = true
1028
- break
1029
- end
1030
- end
1031
- end
1032
- properties[key] << content unless found
1033
- end
1034
-
1035
- # 配下のオブジェクトの前後関係の設定
1036
- def _sequence
1037
- return unless @child
1038
- prev = @_pool['..'].child[-1] if @_pool['..'].respond_to?(:child)
1039
- @child.each do |v|
1040
- if prev
1041
- v._pool['.<-'] = prev
1042
- prev._pool['.->'] = v
1043
- while (prev.child && prev.child[-1]) do
1044
- prev = prev.child[-1]
1045
- prev._pool['.->'] = v
1046
- end
1047
- end
1048
- @_pool[v.label.to_s] = v
1049
- prev = v
1050
- end
1051
- end
1052
-
1053
- alias :__method_missing :method_missing
1054
-
1055
- # その他のメソッド
1056
- # When::Parts::Resource で定義されていないメソッドは
1057
- # 処理を @child (type: Array) に委譲する
1058
- #
1059
- def method_missing(name, *args, &block)
1060
- return __method_missing(name, *args, &block) if When::Parts::MethodCash::Escape.key?(name)
1061
- self.class.module_eval %Q{
1062
- def #{name}(*args, &block)
1063
- @child.send("#{name}", *args, &block)
1064
- end
1065
- } unless When::Parts::MethodCash.escape(name)
1066
- @child.send(name, *args, &block)
1067
- end
1068
- end
1069
- end
1
+ # -*- coding: utf-8 -*-
2
+ =begin
3
+ Copyright (C) 2011-2014 Takashi SUGA
4
+
5
+ You may use and/or modify this file according to the license described in the LICENSE.txt file included in this archive.
6
+ =end
7
+
8
+ #
9
+ # 本ライブラリのための諸々の部品
10
+ #
11
+ module When::Parts
12
+
13
+ #
14
+ # Resource which has 'International Resource Identifier'
15
+ #
16
+ module Resource
17
+
18
+ # @private
19
+ LabelProperty = nil
20
+
21
+ # @private
22
+ ConstTypes = {
23
+ 'SolarTerms' => ['CalendarNote', '%sSolarTerms', '_n:%sSolarTerms%s' ],
24
+ 'LunarPhases' => ['LunarPhases', '%sLunarPhases', '_n:%sLunarPhases%s'],
25
+ 'Terms' => ['BasicTypes::M17n', '%s', '_m:%s%s' ],
26
+ 'Era' => ['TM::CalendarEra', '%s', '_e:%s%s' ],
27
+ 'Residue' => ['Coordinates', '%s', '_co:%s%s' ],
28
+ 'Week' => ['CalendarNote', '%sWeek', '_n:%sWeek%s' ],
29
+ 'Note' => ['CalendarNote', '%s', '_n:%s%s' ],
30
+ 'Notes' => ['CalendarNote', '%s', '_n:%s/Notes%s' ],
31
+ nil => ['CalendarTypes', '%s', '_c:%s%s' ]
32
+ }
33
+
34
+ # @private
35
+ ConstList = []
36
+
37
+ # private
38
+ IRIHeader = /\A[_a-z\d]+:[^:]/i
39
+
40
+ # @private
41
+ class ContentLine
42
+
43
+ RFC6350 = {
44
+ "\\\\" => "\\",
45
+ "\\n" => "\n",
46
+ "\\N" => "\n",
47
+ "\\;" => ";",
48
+ "\\:" => ":"
49
+ }
50
+
51
+ RFC6868 = {
52
+ "^n" => "\n",
53
+ "^^" => "^",
54
+ "^'" => '"'
55
+ }
56
+
57
+ attr_reader :predicate
58
+ attr_accessor :object
59
+ attr_reader :attribute
60
+ attr_accessor :namespace
61
+ attr_accessor :same_altid
62
+ attr_reader :marked
63
+
64
+ def initialize(key, object=nil, marked=nil)
65
+ key = key.downcase.gsub(/-/,'_') if (key==key.upcase)
66
+ @predicate, @namespace = key.split(/:/).reverse
67
+ object = object.gsub(/\^./) {|escape| RFC6868[escape] || escape} if object.instance_of?(String)
68
+ @object = object
69
+ @marked = marked
70
+ @attribute = {}
71
+ end
72
+
73
+ #
74
+ # 行から RFC5545 の Property を取り出す
75
+ #
76
+ # @private
77
+ def self.extract_rfc5545_Property(specification, options)
78
+ return specification unless specification =~ /\A[-A-Z_]+=.+?:/i
79
+ caret = 0
80
+ specification.length.times do |i|
81
+ caret = specification[i..i] == '^' ? caret + 1 : 0
82
+ if caret[0] == 0 && specification[i..i] == ':'
83
+ specification[0...i].gsub(/\^./) {|escape| RFC6868[escape] || escape}.
84
+ scan(/((?:[^\\;]|\\.)+)(?:;(?!\z))?|;/).flatten.each do |pr|
85
+ pr ||= ''
86
+ pr.gsub!(/\\./) {|escape| RFC6350[escape] || escape}
87
+ key, value = pr.split(/=/, 2)
88
+ case key
89
+ when 'VALUE' ; options[:precision] = value
90
+ when 'TZID' ; options[:clock] =
91
+ case When::V::Timezone[value]
92
+ when Array ; When::V::Timezone[value][-1]
93
+ when nil ; When::Parts::Timezone.new(value)
94
+ else ; When::V::Timezone[value]
95
+ end
96
+ else ; options[key] = value
97
+ end
98
+ end
99
+ return specification[(i+1)..-1]
100
+ end
101
+ end
102
+ return specification
103
+ end
104
+ end
105
+
106
+ # @private
107
+ module Synchronize
108
+
109
+ # 排他実行
110
+ #
111
+ # 与えられたブロックを必要なら排他制御をして実行する
112
+ #
113
+ def synchronize
114
+ if @_lock_
115
+ @_lock_.synchronize do
116
+ yield
117
+ end
118
+ else
119
+ yield
120
+ end
121
+ end
122
+ end
123
+
124
+ #
125
+ # Resource has-a 親子関係を管理する
126
+ #
127
+ module Pool
128
+
129
+ include Synchronize
130
+
131
+ # 初期化
132
+ # @return [void]
133
+ #
134
+ # @note
135
+ # 本メソッドでマルチスレッド対応の管理変数の初期化を行っている。
136
+ # このため、本メソッド自体はスレッドセーフでない。
137
+ #
138
+ def _setup_
139
+ @_lock_ = Mutex.new if When.multi_thread
140
+ @_pool = {}
141
+ end
142
+
143
+ # オブジェクト参照
144
+ #
145
+ # @param [String] label
146
+ #
147
+ # @return [When::Parts::Resource] 指定した label で登録した子 Resource を返す
148
+ #
149
+ def [](label)
150
+
151
+ # nil label の場合
152
+ return _pool[label] unless label
153
+
154
+ # 階層がある場合
155
+ terms = Resource._encode(label).split(/::/)
156
+ terms.shift if terms[0] == ''
157
+ return terms.inject(self) {|obj,term| obj = obj[Resource._decode(term)]} if terms.length >= 2
158
+
159
+ # 階層がない場合
160
+ _pool[Resource._decode(Resource._extract_prefix(terms[0]))]
161
+ end
162
+
163
+ # オブジェクト登録
164
+ #
165
+ # 指定した label で子 Resource を登録する
166
+ #
167
+ # @param [String] label
168
+ # @param [When::Parts::Resource] obj
169
+ #
170
+ # @return [void]
171
+ #
172
+ def []=(label, obj)
173
+ # raise NameError, "Name duplication" if (@_pool[label])
174
+ _pool[label] = obj
175
+ end
176
+
177
+ # @private
178
+ def pool_keys
179
+ _pool.keys
180
+ end
181
+
182
+ # @private
183
+ def _pool
184
+ _setup_ unless @_pool
185
+ @_pool
186
+ end
187
+ end
188
+
189
+ class << self
190
+
191
+ include Pool
192
+
193
+ # Base URI for When_exe Resources
194
+ #
195
+ # @return [String]
196
+ #
197
+ def base_uri
198
+ @base_uri ||= When::SourceURI
199
+ end
200
+
201
+ # Root Directory for When_exe Resources
202
+ #
203
+ # @return [String]
204
+ #
205
+ def root_dir
206
+ @root_dir ||= When::RootDir
207
+ end
208
+
209
+ #
210
+ # 略称を iri に変換する
211
+ #
212
+ # @param [String or Symbol] abbreviation 略称
213
+ #
214
+ # @return [String] iri or nil
215
+ #
216
+ # @private
217
+ def _abbreviation_to_iri(abbreviation, abbreviation_types=ConstTypes)
218
+ abbreviation_types[:pattern] ||= /\A(?=[A-Z])(.*?)(#{abbreviation_types.keys.compact.join('|')})?(\?.+|::.+)?\z/
219
+ abbreviation.to_s =~ abbreviation_types[:pattern]
220
+ return nil unless $1
221
+ klass, name, iri = abbreviation_types[$2]
222
+ key, name, iri = $2, name % $1, iri % [$1,$3]
223
+ if klass.kind_of?(String)
224
+ klass = klass.split('::').inject(When) {|k,n| k.const_get(n)}
225
+ abbreviation_types[key][0] = klass
226
+ end
227
+ klass.const_defined?(name) ? iri : nil
228
+ end
229
+
230
+ # @private
231
+ attr_reader :_prefix, :_prefix_values, :_prefix_index
232
+ private :_prefix, :_prefix_values, :_prefix_index
233
+
234
+ # 初期化
235
+ #
236
+ # @param [Hash] options 以下の通り
237
+ # @option options [String] :base_uri Base URI for When_exe Resources (Default When::SourceURI)
238
+ # @option options [Hash<String(namespace)=>String(URI)>] :additional_namespaces User defined namespaces (Default {})
239
+ # @option options [String] :root_dir Root Directory for When_exe Resources Cash data (Default When::RootDir)
240
+ # @option options [Boolean] :leave_const If true, leave Constants of When module defined
241
+ #
242
+ # @return [void]
243
+ #
244
+ # @note
245
+ # 本メソッドでマルチスレッド対応の管理変数の初期化を行っている。
246
+ # このため、本メソッド自体はスレッドセーフでない。
247
+ #
248
+ def _setup_(options={})
249
+ super()
250
+ @_prefix = {
251
+ '_wp' => 'http://en.wikipedia.org/wiki/',
252
+ '_w' => base_uri + '/',
253
+ '_p' => base_uri + 'Parts/',
254
+ '_b' => base_uri + 'BasicTypes/',
255
+ '_m' => base_uri + 'BasicTypes/M17n/',
256
+ '_co' => base_uri + 'Coordinates/',
257
+ '_l' => base_uri + 'Coordinates/Spatial?',
258
+ '_v' => base_uri + 'V/',
259
+ '_rs' => base_uri + 'RS/',
260
+ '_ex' => base_uri + 'EX/',
261
+ '_tm' => base_uri + 'TM/',
262
+ '_e' => base_uri + 'TM/CalendarEra/',
263
+ '_t' => base_uri + 'TimeStandard/',
264
+ '_ep' => base_uri + 'Ephemeris/',
265
+ '_c' => base_uri + 'CalendarTypes/',
266
+ '_n' => base_uri + 'CalendarNote/',
267
+ '_sc' => base_uri + 'Ephemeris/V50/'
268
+ }
269
+ @base_uri = options[:base_uri] || When::SourceURI
270
+ @root_dir = options[:root_dir] || When::RootDir
271
+ @_prefix = options[:additional_namespaces].merge(@_prefix) if options[:additional_namespaces].kind_of?(Hash)
272
+ @_prefix_values = @_prefix.values.sort.reverse
273
+ @_prefix_index = @_prefix.invert
274
+ unless options[:leave_const] || ConstList.empty?
275
+ ConstList.delete_if do |constant|
276
+ When.send(:remove_const, constant) if When.const_defined?(constant)
277
+ true
278
+ end
279
+ When._define_common_calendar_types
280
+ end
281
+ end
282
+
283
+ # 設定情報を取得する
284
+ #
285
+ # @return [Hash] 設定情報
286
+ #
287
+ def _setup_info
288
+ {:base_uri => base_uri, :root_dir => root_dir}
289
+ end
290
+
291
+ # オブジェクト生成&参照
292
+ #
293
+ # 指定した iri When::Parts::Resource オブジェクトを取得する。
294
+ # 当該オブジェクトが未登録であれば生成する。
295
+ #
296
+ # @param [String] iri International Resource Identifier
297
+ # @param [String] namespace (デフォルトの名前空間, 指定がないときは名前空間を省略しない)
298
+ # @param [Block] block オブジェクトが見つからない場合の代替処理
299
+ #
300
+ # @return [When::Parts::Resource]
301
+ #
302
+ def _instance(iri, namespace=nil, &block)
303
+ _setup_ unless @_pool
304
+
305
+ case iri
306
+ when Array ; return iri.map {|e| _instance(e, namespace)} # 配列は個別に処理
307
+ when Resource ; return iri # 登録済みはそのまま
308
+ when String ; # 解析処理へ
309
+ else ; raise ArgumentError, "can't convert #{iri.class} to String" # 例外
310
+ end
311
+
312
+ # 内部文字列化
313
+ iri = When::EncodingConversion.to_internal_encoding(iri)
314
+
315
+ # 階層がある場合は、階層をたどる
316
+ iri = Resource._decode(iri)
317
+ iri = $1 while iri =~ /\A\((.*)\)\z/
318
+ iri = namespace + iri if namespace && iri !~ IRIHeader
319
+ root, *leaves= Resource._encode(iri).split(/::/)
320
+ if leaves.size > 0
321
+ return leaves.inject(_instance(Resource._decode(root))) {|obj,leaf| obj[Resource._decode(leaf)]}
322
+ end
323
+
324
+ # 登録ずみなら、参照
325
+ iri = _extract_prefix(iri)
326
+ path, query = iri.split(/\?/, 2)
327
+ if When.multi_thread
328
+ my_mutex = nil
329
+ @_lock_.synchronize do
330
+ @_pool ||= {}
331
+ unless @_pool[iri]
332
+ my_mutex = Mutex.new
333
+ @_pool[iri] = my_mutex
334
+ end
335
+ end
336
+ case @_pool[iri]
337
+ when my_mutex; my_mutex.synchronize {@_pool[iri] = _create_object(iri, path, query, &block) }
338
+ when Mutex ; @_pool[iri].synchronize {@_pool[iri]}
339
+ else ; @_pool[iri]
340
+ end
341
+ else
342
+ @_pool ||= {}
343
+ @_pool[iri] ||= _create_object(iri, path, query, &block)
344
+ end
345
+ end
346
+
347
+ # @private
348
+ def _path_with_prefix(obj, simple=true)
349
+ _setup_ unless @_pool
350
+ path = obj.kind_of?(Class) ? obj.to_s.sub(/\AWhen::/, base_uri).gsub(/::/, '/') :
351
+ obj.iri
352
+ simple ? _simplify_path(path) : path
353
+ end
354
+
355
+ # @private
356
+ def _simplify_path(path)
357
+ _prefix_values.each do |value|
358
+ index = path.index(value)
359
+ return _prefix_index[value] + ':' + path[value.length..-1] if index
360
+ end
361
+ return path
362
+ end
363
+
364
+ # @private
365
+ def _parse(line, type=nil)
366
+ return line unless line.kind_of?(String)
367
+ line.sub!(/\s#.*\z/, '')
368
+ return When::Locale._split($1) if type && /\A#{type}:(.+)\z/i =~ line
369
+ tokens = line.scan(/((?:[^\\:]|\\.)+)(?::(?!\z))?|:/).flatten
370
+ return When::Locale._split(line) unless tokens.size > 1 && /\A(\*)?([A-Z][-A-Z_]{0,255})(?:;(.+))?\z/i =~ tokens[0]
371
+ marked, key, property = $~[1..3]
372
+ values = tokens[1..-1]
373
+ value = values.join(':') unless values == [nil]
374
+ content = ContentLine.new(key, value, marked)
375
+ value ||= ''
376
+ if property
377
+ content.attribute['.'] = property + ':' + value
378
+ property.scan(/((?:[^\\;]|\\.)+)(?:;(?!\z))?|;/).flatten.each do |pr|
379
+ pr ||= ''
380
+ pr.gsub!(/\\./) {|escape| ContentLine::RFC6350[escape] || escape}
381
+ prop = ContentLine.new(*pr.split(/=/, 2))
382
+ content.attribute[prop.predicate] = prop
383
+ end
384
+ else
385
+ content.attribute['.'] = value
386
+ end
387
+ return content
388
+ end
389
+
390
+ # @private
391
+ def _extract_prefix(path, capitalize=false)
392
+ if (path =~ /\A(.+?):+(.+)\z/)
393
+ prefix, klass = $~[1..2]
394
+ if capitalize
395
+ prefix = '_' + prefix.downcase
396
+ klass = klass.capitalize if klass == klass.upcase
397
+ end
398
+ path = _prefix[prefix] + klass if _prefix[prefix]
399
+ elsif capitalize && path =~ /\A(v[^\/]+|daylight$|standard$)/i
400
+ klass = path.sub(/\Av/i, '').capitalize
401
+ path = _prefix['_v'] + klass if When::V.const_defined?(klass) &&
402
+ When::V.const_get(klass).kind_of?(Class)
403
+ end
404
+ return path
405
+ end
406
+
407
+ # @private
408
+ def _replace_tags(source, tags)
409
+ case source
410
+ when When::BasicTypes::M17n
411
+ source
412
+ when String
413
+ target = source.dup
414
+ tags.each_pair do |key, value|
415
+ target.gsub!(/#\{([?&][^=#}]+?=)?#{key}(:.*?)?\}/, '\1' + value) if value.kind_of?(String)
416
+ end
417
+ target.gsub(/#\{.+?(:(.*?))?\}/, '\2')
418
+ when Array
419
+ source.map {|target| _replace_tags(target, tags)}
420
+ when Hash
421
+ target = {}
422
+ source.each_pair do |key, value|
423
+ target[key] = _replace_tags(tags[key] || value, tags)
424
+ end
425
+ target
426
+ else
427
+ source
428
+ end
429
+ end
430
+
431
+ # @private
432
+ def _encode(iri)
433
+ return iri unless iri =~ /\(/
434
+
435
+ iri = iri.dup
436
+ begin
437
+ unless iri.gsub!(/\([^()]*\)/) {|token|
438
+ token.gsub(/[():?&%]/) {|char|'%' + char.ord.to_s(16)}
439
+ }
440
+ raise ArgumentError, 'Brackets do not correspond: ' + iri
441
+ end
442
+ end while iri =~ /\(/
443
+ iri
444
+ end
445
+
446
+ # @private
447
+ def _decode(iri)
448
+ return iri unless iri =~ /%28/
449
+
450
+ iri = iri.dup
451
+ begin
452
+ unless iri.gsub!(/%28.*?%29/) {|token|
453
+ token.gsub(/%([\dA-F]{2})/i) {$1.to_i(16).chr}
454
+ }
455
+ raise ArgumentError, 'Brackets do not correspond: ' + iri
456
+ end
457
+ end while iri =~ /%28/
458
+ iri = $1 if iri =~ /\A\((.*)\)\z/
459
+ iri
460
+ end
461
+
462
+ # @private
463
+ def _instantiate(resource)
464
+ return resource unless resource.kind_of?(Array)
465
+ return resource[0].new(*resource[1..-1]) if resource[0].kind_of?(Class)
466
+ return resource.map {|rsc| _instantiate(rsc)}
467
+ end
468
+
469
+ private
470
+
471
+ # オブジェクト生成
472
+ def _create_object(iri, path, query, &block)
473
+ # query analyzation
474
+ options = {}
475
+ replace = {}
476
+ if query
477
+ options = Hash[*Resource._encode(query).split(/&/).map{|pair|
478
+ key, value = pair.split(/=/, 2)
479
+ [key, Resource._decode(value)]
480
+ }.flatten]
481
+ keys = options.keys
482
+ keys.each do |key|
483
+ replace[$1] = options.delete(key) if key =~ /\A([A-Z].*)/
484
+ end
485
+ end
486
+ options['..'] = iri
487
+
488
+ # internal Resource
489
+ if path.index(Resource.base_uri) == 0
490
+ list = _class(path)
491
+ if list
492
+ return _internal(list, replace, options)
493
+ else
494
+ object = yield(iri) if block_given?
495
+ return object if object
496
+ raise IOError, 'IRI not found - ' + path
497
+ end
498
+ end
499
+
500
+ # external Resource
501
+ begin
502
+ object = When::Locale.send(:wikipedia_object, path, {:query=>query})
503
+ return object if object
504
+ OpenURI
505
+ args = [path, "1".respond_to?(:force_encoding) ? 'r:utf-8' : 'r']
506
+ args << {:ssl_verify_mode=>OpenSSL::SSL::VERIFY_NONE} if path =~ /\Ahttps:/
507
+ open(*args) do |file|
508
+ resource = file.read
509
+ case resource[0..5].upcase
510
+ when 'BEGIN:'
511
+ options['.'] = _ics(_replace_tags(resource, replace).split(/[\n\r]+/))
512
+ options['.'][0].new(options)
513
+ when '<?XML '
514
+ options['.'] = _xml(REXML::Document.new(_replace_tags(resource, replace)).root)
515
+ options['.'][0].new(options)
516
+ else
517
+ raise NoMethodError, 'JSON not supported' unless Object.const_defined?(:JSON)
518
+ _internal(_json([JSON.parse(resource)]), replace, options)
519
+ end
520
+ end
521
+ rescue OpenURI::HTTPError => error
522
+ message = error.message + " - #{path}"
523
+ error = error.respond_to?(:uri) ?
524
+ error.class.new(message, error.io, error.uri) :
525
+ error.class.new(message, error.io)
526
+ raise error
527
+ end
528
+ end
529
+
530
+ # 指定のモジュール/クラスで直接定数が定義されているか
531
+ if method(:const_defined?).arity == 1
532
+ def _directry_defined?(klass, const)
533
+ klass.const_defined?(const)
534
+ end
535
+ else
536
+ def _directry_defined?(klass, const)
537
+ klass.const_defined?(const, false)
538
+ end
539
+ end
540
+
541
+ # 内部形式定義の取得
542
+ def _class(path)
543
+ list = [When]
544
+ path[Resource.base_uri.length..-1].split(/\//).each do |mod|
545
+ if list[0].kind_of?(Module) && _directry_defined?(list[0], mod)
546
+ list.unshift(list[0].const_get(mod))
547
+ else
548
+ return nil unless list[0] == When::V
549
+ list.unshift(When::V::Root)
550
+ return list
551
+ end
552
+ end
553
+ return list
554
+ end
555
+
556
+ # 内部形式定義のオブジェクト化
557
+ def _internal(list, replace, options)
558
+ case list[0]
559
+ when Class
560
+ list[0].new(options)
561
+ when Array
562
+ top = list[0][0]
563
+ if top.kind_of?(Hash)
564
+ top.each_pair do |key, value|
565
+ replace.update(value[replace[key]]) if value.kind_of?(Hash) && value[replace[key]]
566
+ end
567
+ list[0] = list[0][1..-1]
568
+ list[0] = _replace_tags(list[0], top.merge(replace))
569
+ end
570
+ if list[0][0].kind_of?(Class)
571
+ # 配列の先頭がクラスである場合
572
+ klass, *list = list[0]
573
+ unless list[-1].kind_of?(Hash)
574
+ if list.length == 1
575
+ list[0] = {'.'=>Array(list[0])}
576
+ else
577
+ list << {}
578
+ end
579
+ end
580
+ else
581
+ # 配列の先頭がクラスではない場合
582
+ klass, *list = [list[1], *list[0]]
583
+ list << {} unless list[-1].kind_of?(Hash)
584
+ end
585
+ list[-1] = list[-1].merge(options)
586
+ klass.new(*list)
587
+ else
588
+ list[0]
589
+ end
590
+ end
591
+
592
+ # .xml フォーマットの読み込み
593
+ def _xml(xml, namespace={})
594
+ obj = [_class(_extract_prefix(xml.attributes['type'].to_s))[0]]
595
+ xml.attributes.each_pair do |key,value|
596
+ expanded_name = value.expanded_name
597
+ next unless (expanded_name =~ /\Axmlns/)
598
+ key = '' if expanded_name == 'xmlns'
599
+ namespace[key] = value.to_s
600
+ end
601
+ obj << ContentLine.new('xmlns:namespace', namespace) if (namespace.size>0)
602
+ xml.each do |e|
603
+ next unless defined? e.name
604
+ if (e.attributes['type'])
605
+ obj << _xml(e, namespace)
606
+ else
607
+ content = ContentLine.new(e.expanded_name, e.attributes['ref']||e.text)
608
+ e.attributes.each_pair do |key,value|
609
+ attr = ContentLine.new(value.name, value)
610
+ attr.namespace = value.prefix
611
+ content.attribute[key] = attr
612
+ end
613
+ obj << content
614
+ end
615
+ end
616
+ return obj
617
+ end
618
+
619
+ # .ics フォーマットの読み込み
620
+ def _ics(ics, type=nil)
621
+ obj = [type] if type
622
+ indent = nil
623
+ while (line = ics.shift) do
624
+ line.chomp!
625
+ case line
626
+ when /\A\s*BEGIN:(.*)\z/
627
+ if type
628
+ subtype = $1
629
+ obj[-1] = _parse(obj[-1], type) if obj.length > 1
630
+ obj << _ics(ics, subtype)
631
+ else
632
+ type = $1
633
+ obj = [type]
634
+ end
635
+ when /\A\s*END:(.*)\z/
636
+ raise TypeError, "Irregal Type : #{$1}" unless type == $1
637
+ obj[0] = _class(_extract_prefix(type, true))[0]
638
+ obj[-1] = _parse(obj[-1], type)
639
+ return obj
640
+ when /\A\s*#/
641
+ when /\A(\s*)(.*)\z/
642
+ indent = $1 unless indent
643
+ if indent.length < $1.length
644
+ obj[-1] += line[(indent.length+1)..-1] # See RFC5545 3.1 Content Lines
645
+ else
646
+ obj << $2
647
+ obj[-2] = _parse(obj[-2], type)
648
+ end
649
+ end
650
+ end
651
+ raise ArgumentError, "BEGIN-END mismatch"
652
+ end
653
+
654
+ # .json フォーマットの読み込み
655
+ def _json(json)
656
+ case json
657
+ when Array
658
+ json.map {|value| _json(value)}
659
+ when Hash
660
+ hash = {}
661
+ json.each_pair {|key, value| hash[key] = _json(value)}
662
+ hash
663
+ when String
664
+ return json unless json =~ /\AWhen::/
665
+ begin
666
+ return json.split('::').inject(Object) {|ns, sym| ns.const_get(sym)}
667
+ rescue
668
+ json
669
+ end
670
+ else
671
+ json
672
+ end
673
+ end
674
+ end
675
+
676
+ include Synchronize
677
+
678
+ # @private
679
+ attr_reader :_pool
680
+
681
+ # self が has-a 関係で包含するオブジェクト
682
+ #
683
+ # @return [Array<When::Parts::Resource>]
684
+ #
685
+ attr_accessor :child
686
+ private :child=
687
+
688
+ #
689
+ # Resource包含階層で使用する namespace
690
+ #
691
+ # When::BasicTypes::M17n の生成に使用する namespace を定義する。
692
+ # RFC 5545 に対する拡張である。
693
+ # xml で記述する場合には、本ライブラリ以外でも namespace を定義している。
694
+ #
695
+ # @return [Hash] { prefix => prefix文字列 }
696
+ #
697
+ attr_reader :namespace
698
+
699
+ #
700
+ # Resource包含階層で使用する locale
701
+ #
702
+ # When::BasicTypes::M17n の生成に使用する locale を定義する。
703
+ # RFC 5545 に対する拡張である。
704
+ #
705
+ # @return [Array<String>]
706
+ #
707
+ attr_reader :locale
708
+
709
+ # strftime で有効な locale
710
+ #
711
+ # @return [Array<String>]
712
+ #
713
+ attr_reader :keys
714
+
715
+ # オブジェクトの IRI
716
+ #
717
+ # @param [Boolean] prefix true ならIRI の先頭部分を簡約表現にする
718
+ #
719
+ # @return [Sring]
720
+ #
721
+ def iri(prefix=false)
722
+ unless @iri
723
+ root = @_pool['..']
724
+ path = root.instance_of?(String) ? root : label.to_s
725
+ if root.respond_to?(:iri)
726
+ root_iri = root.iri
727
+ path = root_iri + '::' + path if root_iri
728
+ end
729
+ @iri = path
730
+ end
731
+ prefix ? Resource._simplify_path(@iri) : @iri
732
+ end
733
+
734
+ # IRI または child の番号によるオブジェクト参照
735
+ #
736
+ # @param [String] iri オブジェクトの IRI
737
+ # @param [Numeric] iri child の index
738
+ #
739
+ # @return [When::parts::Resource]
740
+ #
741
+ def [](iri)
742
+ case iri
743
+ when Numeric
744
+ return child[iri * 1]
745
+ when String
746
+ obj = self
747
+ Resource._encode(iri).split(/::/).each do |label|
748
+ return obj.child if label == '*'
749
+ if obj == Resource
750
+ obj = Resource._instance(Resource._decode(label))
751
+ else
752
+ case label
753
+ when '' ; obj = Resource
754
+ when '.' # obj = obj
755
+ else ; obj = obj._pool[Resource._decode(label)]
756
+ end
757
+ end
758
+ raise ArgumentError, "IRI not found: #{iri}" unless obj
759
+ end
760
+ return obj
761
+ else
762
+ super(iri)
763
+ #raise ArgumentError, "IRI not found: #{iri}"
764
+ end
765
+ end
766
+
767
+ # self を直接に包含するオブジェクト
768
+ #
769
+ # @return [When::Parts::Resource]
770
+ #
771
+ def parent
772
+ @_pool['..'].kind_of?(Resource) ? @_pool['..'] : nil
773
+ end
774
+
775
+ # self を包含するオブジェクト階層
776
+ #
777
+ # @param [Class] klass 階層を遡るクラス
778
+ #
779
+ # @return [When::Parts::Resource]
780
+ #
781
+ def hierarchy(klass=self.class)
782
+ hierarchy = []
783
+ parent = self
784
+ while parent.kind_of?(klass)
785
+ hierarchy << parent
786
+ parent = parent.parent
787
+ end
788
+ hierarchy.reverse
789
+ end
790
+
791
+ # self が other を包含するか
792
+ #
793
+ # @return [Boolean]
794
+ # [ true - 包含する ]
795
+ # [ false - 包含しない ]
796
+ #
797
+ def include?(other)
798
+ c = other
799
+ while c.kind_of?(Resource)
800
+ return true if c.equal?(self)
801
+ c = c.parent
802
+ end
803
+ return false
804
+ end
805
+
806
+ # other が self を包含するか
807
+ #
808
+ # @return [Boolean]
809
+ # [ true - 包含される ]
810
+ # [ false - 包含されない ]
811
+ #
812
+ def included?(other)
813
+ other.include?(self)
814
+ end
815
+
816
+ # 前のオブジェクト
817
+ #
818
+ # @return [When::Parts::Resource]
819
+ #
820
+ def prev
821
+ c = self
822
+ c = c.child[0] while c.child && c.child.size > 0
823
+ c = c._pool['.<-']
824
+ c = c.child[-1] while c && c.child && c.child.size > 0
825
+ c
826
+ end
827
+
828
+ # 次のオブジェクト
829
+ #
830
+ # @return [When::Parts::Resource]
831
+ #
832
+ def next
833
+ c = self
834
+ c = c.child[0] while c.child && c.child.size > 0
835
+ c._pool['.->']
836
+ end
837
+ alias :succ :next
838
+
839
+ # オブジェクト包含階層の末端か?
840
+ #
841
+ # @return [Boolean]
842
+ # [ true - IRIが付与された他のオブジェクトを包含していない ]
843
+ # [ false - IRIが付与された他のオブジェクトを包含している ]
844
+ #
845
+ def leaf?
846
+ !@child || (@child.length==0)
847
+ end
848
+
849
+ # IRIが付与されているか?
850
+ #
851
+ # @return [Boolean]
852
+ # [ true - IRIが付与されている ]
853
+ # [ false - IRIが付与されていない ]
854
+ #
855
+ def registered?
856
+ leaf = self
857
+ while leaf._pool['..'].respond_to?(:_pool)
858
+ root = leaf._pool['..']
859
+ return false unless leaf.equal?(root._pool[leaf.label])
860
+ leaf = root
861
+ end
862
+ Resource._pool.value?(leaf)
863
+ end
864
+
865
+ # When::BasicTypes::M17n の生成/参照
866
+ #
867
+ # @param [When::BasicTypes::M17n] source 処理を行わず、そのままsourceを返す
868
+ # @param [String] source locale と 文字列の対応
869
+ # @param [Array] source 要素を個別に解釈して生成したオブジェクトのArrayを返す
870
+ # @param [Hash] namespace prefix の指定
871
+ # @param [Array] locale locale の定義順序の指定
872
+ # @param [Hash] options (see {When::BasicTypes::M17n.new}[link:When/BasicTypes/M17n.html#method-c-new])
873
+ #
874
+ # @return [When::BasicTypes::M17n or Array<them>]
875
+ #
876
+ def m17n(source, namespace=nil, locale=nil, options={})
877
+ case source
878
+ when Array ; When::BasicTypes::M17n.new(source, namespace, locale, options)
879
+ when When::BasicTypes::M17n ; source
880
+ when String
881
+ return self[$1] if source =~ /\A\s*\[((\.{1,2}|::)+[^\]]+)\]/
882
+ When::BasicTypes::M17n.new(source, namespace, locale, options)
883
+ else ; raise TypeError, "Invalid Type: #{source.class}"
884
+ end
885
+ end
886
+
887
+ # 子オブジェクトを順に取り出す enumerator
888
+ #
889
+ def enum_for
890
+ @child.enum_for
891
+ end
892
+ alias :to_enum :enum_for
893
+
894
+ # Enumerator 生成のダミー
895
+ #
896
+ # @param [Object] other
897
+ #
898
+ # @return [Enumerator]
899
+ #
900
+ def ^(other)
901
+ return nil
902
+ end
903
+
904
+ # 順次実行
905
+ #
906
+ # @param [Array] args 各サブクラスの enum_for にそのまま渡す
907
+ # @param [Block] block 実行するブロック
908
+ #
909
+ # @return [Enumerator]
910
+ #
911
+ def each(*args, &block)
912
+ enum = enum_for(*args)
913
+ return enum unless block
914
+ enum.each(&block)
915
+ end
916
+
917
+ # map, collect の再定義
918
+ #
919
+ # has-a 関係の子 Resource に対して map/collect を行う
920
+ #
921
+ # @param [Block] block 実行するブロック
922
+ #
923
+ # @return [Array]
924
+ #
925
+ def map(&block)
926
+ @child.map(&block)
927
+ end
928
+ alias :collect :map
929
+
930
+ protected
931
+
932
+ # @private
933
+ def pool_keys
934
+ _pool.keys
935
+ end
936
+
937
+ private
938
+
939
+ # 属性の設定
940
+ def _attributes(args)
941
+ options =_get_options(args)
942
+ _set_variables(options)
943
+ return args, options
944
+ end
945
+
946
+ # option の読み出し
947
+ def _get_options(args)
948
+ options = args[-1].kind_of?(Hash) ? args.pop : {}
949
+ @_pool = {}
950
+ @_pool['..'] = options['..'] if (options['..'])
951
+
952
+ # 配下のオブジェクトの生成と関連付け
953
+ if (options.key?('.'))
954
+ _child(options)
955
+ end
956
+
957
+ options
958
+ end
959
+
960
+ # 変数の設定
961
+ def _set_variables(options)
962
+ @options = options[:options] || {} if options.key?(:options)
963
+ options.each_pair do |key,value|
964
+ unless (key =~ /\Aoptions$|^\.|^[A-Z]/)
965
+ case "#{key}"
966
+ when 'namespace' ; value = When::Locale._namespace(value)
967
+ when 'locale' ; value = When::Locale._locale(value)
968
+ end
969
+ instance_variable_set("@#{key}", value)
970
+ end
971
+ end
972
+ end
973
+
974
+ # 配下のオブジェクトの生成
975
+ def _child(options)
976
+ @child = []
977
+ query = options.dup
978
+ options['..'] = self
979
+ leaves = options.delete('.').map {|leaf| Resource._parse(leaf)}
980
+ key_list = []
981
+ properties = {}
982
+ label_candidates = nil
983
+
984
+ # ContentLine の処理(namespace, locale, altidの前処理)
985
+ leaves.each do |content|
986
+ next unless content.kind_of?(ContentLine)
987
+ key = content.predicate
988
+ value = content.object
989
+ next options.delete(key) unless value
990
+ case key
991
+ when 'namespace'
992
+ options[key] ||= {}
993
+ if content.attribute['altid']
994
+ options[key][content.attribute['prefix'].object] = content.object
995
+ else
996
+ options[key] = options[key].update(When::Locale._namespace(value))
997
+ end
998
+ when 'locale'
999
+ options[key] = When::Locale._locale(value)
1000
+ else
1001
+ _parse_altid(properties, content)
1002
+ key_list << key unless key_list.include?(key)
1003
+ end
1004
+ end
1005
+
1006
+ # ContentLine の処理(一般)
1007
+ key_list.each do |key|
1008
+ content = properties[key][0]
1009
+ value = content.same_altid ? When::BasicTypes::M17n.new(content, options['namespace'], []) : content.object
1010
+ value = When::BasicTypes::M17n.new(value, nil, nil, options) if value.instance_of?(String) && value =~ /\A\s*\[/
1011
+ @_pool[value.to_s] = value if value.kind_of?(When::BasicTypes::M17n)
1012
+ if content.marked || key == self.class::LabelProperty
1013
+ @label = value
1014
+ else
1015
+ options[key] = value
1016
+ label_candidates ||= value
1017
+ end
1018
+ end
1019
+
1020
+ # Array の処理(子オブジェクトの生成)
1021
+ leaves.each do |content|
1022
+ next unless content.kind_of?(Array)
1023
+ if content[0].kind_of?(Class)
1024
+ list = []
1025
+ content.each do |e|
1026
+ if e.kind_of?(Hash)
1027
+ list += e.keys.map {|key| Resource::ContentLine.new(key, e[key])}
1028
+ else
1029
+ list << e
1030
+ end
1031
+ end
1032
+ options['.'] = list
1033
+ @child << content[0].new(options.dup)
1034
+ else
1035
+ options.delete('.')
1036
+ @child << self.class.new(*(content + [options]))
1037
+ end
1038
+ end
1039
+
1040
+ # 代表ラベルの設定
1041
+ options.update(query)
1042
+ unless @label
1043
+ raise ArgumentError, "label attribute not found: #{options['.']}" unless label_candidates
1044
+ @label = label_candidates
1045
+ end
1046
+ end
1047
+
1048
+ # ALTIDを持つ ContentLine の解析
1049
+ def _parse_altid(properties, content)
1050
+ key = content.predicate
1051
+ properties[key] ||= []
1052
+ if content.attribute['altid']
1053
+ found = false
1054
+ (0...properties[key].length).to_a.each do |i|
1055
+ prev = properties[key][i]
1056
+ if prev.attribute['altid'] && prev.attribute['altid'].object == content.attribute['altid'].object
1057
+ content.same_altid = prev
1058
+ properties[key][i] = content
1059
+ found = true
1060
+ break
1061
+ end
1062
+ end
1063
+ end
1064
+ properties[key] << content unless found
1065
+ end
1066
+
1067
+ # 配下のオブジェクトの前後関係の設定
1068
+ def _sequence
1069
+ return unless @child
1070
+ prev = @_pool['..'].child[-1] if @_pool['..'].respond_to?(:child)
1071
+ @child.each do |v|
1072
+ if prev
1073
+ v._pool['.<-'] = prev
1074
+ prev._pool['.->'] = v
1075
+ while (prev.child && prev.child[-1]) do
1076
+ prev = prev.child[-1]
1077
+ prev._pool['.->'] = v
1078
+ end
1079
+ end
1080
+ @_pool[v.label.to_s] = v
1081
+ prev = v
1082
+ end
1083
+ end
1084
+
1085
+ alias :__method_missing :method_missing
1086
+
1087
+ # その他のメソッド
1088
+ # When::Parts::Resource で定義されていないメソッドは
1089
+ # 処理を @child (type: Array) に委譲する
1090
+ #
1091
+ def method_missing(name, *args, &block)
1092
+ return __method_missing(name, *args, &block) if When::Parts::MethodCash::Escape.key?(name)
1093
+ self.class.module_eval %Q{
1094
+ def #{name}(*args, &block)
1095
+ @child.send("#{name}", *args, &block)
1096
+ end
1097
+ } unless When::Parts::MethodCash.escape(name)
1098
+ @child.send(name, *args, &block)
1099
+ end
1100
+ end
1101
+ end