when_exe 0.3.9 → 0.4.0

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.
@@ -112,16 +112,18 @@ module When::Coordinates
112
112
  return day if day.kind_of?(self)
113
113
 
114
114
  day ||= 0
115
- week = When.Resource('_co:Common::Week').child
115
+ week = When.Resource('_co:Common::Week')
116
+ dow = week.child
116
117
  case day
117
- when Numeric ; return week[day]
118
- when String ; day = When::EncodingConversion.to_internal_encoding(day)
119
- else ; return nil
118
+ when Numeric ; return dow[day]
119
+ when /\AWeek\z/ ; return week
120
+ when String ; day = When::EncodingConversion.to_internal_encoding(day)
121
+ else ; return nil
120
122
  end
121
123
 
122
124
  day, shift = day.split(':', 2)
123
125
  residue = day.split('&').inject(nil) {|res,d|
124
- r = _day_of_week(d.strip, week)
126
+ r = _day_of_week(d.strip, dow)
125
127
  return nil unless r
126
128
  res ? res & r : r
127
129
  }
@@ -132,11 +134,11 @@ module When::Coordinates
132
134
  end
133
135
  alias :to_residue :day_of_week
134
136
 
135
- def _day_of_week(day, week)
137
+ def _day_of_week(day, dow)
136
138
  match = day[/\A...|^.{1,2}\z/]
137
139
  if match
138
- week.size.times do |i|
139
- return week[i] if week[i].label.=~(/\A#{match}/i)
140
+ dow.size.times do |i|
141
+ return dow[i] if dow[i].label.=~(/\A#{match}/i)
140
142
  end
141
143
  end
142
144
 
@@ -1625,7 +1627,7 @@ module When::Coordinates
1625
1627
  HashProperty =
1626
1628
  [[:origin_of_MSC, 0], [:origin_of_LSC, 0], [:index_of_MSC, 0], [:epoch_in_CE, 0],
1627
1629
  :unit, :base, :pair, :note,
1628
- :location, :time_basis, :border, :formula]
1630
+ :location, :time_basis, :border, :formula, :domain]
1629
1631
 
1630
1632
  # 年/日の原点(origin of most significant coordinate)
1631
1633
  #
@@ -1940,6 +1940,11 @@ module When::Ephemeris
1940
1940
  @solar_degree = @year_length / 360
1941
1941
  extend SolarMethod
1942
1942
  end
1943
+
1944
+ # 通法
1945
+ @denominator = @year_length.denominator if @year_length.kind_of?(Rational)
1946
+ @denominator = [@denominator||0, @lunation_length.denominator].max if @lunation_length.kind_of?(Rational)
1947
+
1943
1948
  super
1944
1949
  end
1945
1950
  end
@@ -36,7 +36,7 @@ module When
36
36
  alias :_method_missing :method_missing
37
37
 
38
38
  #
39
- # �ϊ����\�b�h�̓o�^
39
+ # Registlation of conversion method
40
40
  #
41
41
  def method_missing(name, *args, &block)
42
42
  table_name = name.to_s.upcase
@@ -1,1101 +1,1103 @@
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
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
+ # 代表ラベルの設定
1021
+ unless @label
1022
+ raise ArgumentError, "label attribute not found: #{options['.']}" unless label_candidates
1023
+ @label = label_candidates
1024
+ end
1025
+
1026
+ # Array の処理(子オブジェクトの生成)
1027
+ leaves.each do |content|
1028
+ next unless content.kind_of?(Array)
1029
+ if content[0].kind_of?(Class)
1030
+ list = []
1031
+ content.each do |e|
1032
+ if e.kind_of?(Hash)
1033
+ list += e.keys.map {|key| Resource::ContentLine.new(key, e[key])}
1034
+ else
1035
+ list << e
1036
+ end
1037
+ end
1038
+ options['.'] = list
1039
+ @child << content[0].new(options.dup)
1040
+ else
1041
+ options.delete('.')
1042
+ @child << self.class.new(*(content + [options]))
1043
+ end
1044
+ end
1045
+
1046
+ # options の更新
1047
+ options.update(query)
1048
+ end
1049
+
1050
+ # ALTIDを持つ ContentLine の解析
1051
+ def _parse_altid(properties, content)
1052
+ key = content.predicate
1053
+ properties[key] ||= []
1054
+ if content.attribute['altid']
1055
+ found = false
1056
+ (0...properties[key].length).to_a.each do |i|
1057
+ prev = properties[key][i]
1058
+ if prev.attribute['altid'] && prev.attribute['altid'].object == content.attribute['altid'].object
1059
+ content.same_altid = prev
1060
+ properties[key][i] = content
1061
+ found = true
1062
+ break
1063
+ end
1064
+ end
1065
+ end
1066
+ properties[key] << content unless found
1067
+ end
1068
+
1069
+ # 配下のオブジェクトの前後関係の設定
1070
+ def _sequence
1071
+ return unless @child
1072
+ prev = @_pool['..'].child[-1] if @_pool['..'].respond_to?(:child)
1073
+ @child.each do |v|
1074
+ if prev
1075
+ v._pool['.<-'] = prev
1076
+ prev._pool['.->'] = v
1077
+ while (prev.child && prev.child[-1]) do
1078
+ prev = prev.child[-1]
1079
+ prev._pool['.->'] = v
1080
+ end
1081
+ end
1082
+ @_pool[v.label.to_s] = v
1083
+ prev = v
1084
+ end
1085
+ end
1086
+
1087
+ alias :__method_missing :method_missing
1088
+
1089
+ # その他のメソッド
1090
+ # When::Parts::Resource で定義されていないメソッドは
1091
+ # 処理を @child (type: Array) に委譲する
1092
+ #
1093
+ def method_missing(name, *args, &block)
1094
+ return __method_missing(name, *args, &block) if When::Parts::MethodCash::Escape.key?(name)
1095
+ self.class.module_eval %Q{
1096
+ def #{name}(*args, &block)
1097
+ @child.send("#{name}", *args, &block)
1098
+ end
1099
+ } unless When::Parts::MethodCash.escape(name)
1100
+ @child.send(name, *args, &block)
1101
+ end
1102
+ end
1103
+ end