when_exe 0.4.0 → 0.4.1

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