when_exe 0.4.4 → 0.4.5

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/lib/when_exe.rb +89 -3
  3. data/lib/when_exe/basictypes.rb +37 -7
  4. data/lib/when_exe/calendarnote.rb +18 -13
  5. data/lib/when_exe/calendartypes.rb +3 -9
  6. data/lib/when_exe/coordinates.rb +23 -373
  7. data/lib/when_exe/ephemeris.rb +7 -7
  8. data/lib/when_exe/ephemeris/notes.rb +0 -14
  9. data/lib/when_exe/events.rb +1851 -0
  10. data/lib/when_exe/icalendar.rb +121 -4
  11. data/lib/when_exe/linkeddata.rb +29 -18
  12. data/lib/when_exe/locales/akt.rb +63 -0
  13. data/lib/when_exe/locales/locale.rb +60 -11
  14. data/lib/when_exe/mini_application.rb +14 -8
  15. data/lib/when_exe/namespace.rb +42 -0
  16. data/lib/when_exe/parts/enumerator.rb +13 -2
  17. data/lib/when_exe/parts/geometric_complex.rb +51 -1
  18. data/lib/when_exe/parts/method_cash.rb +15 -10
  19. data/lib/when_exe/parts/resource.rb +47 -29
  20. data/lib/when_exe/region/chinese.rb +4 -3
  21. data/lib/when_exe/region/chinese/calendars.rb +4 -4
  22. data/lib/when_exe/region/chinese/epochs.rb +6 -6
  23. data/lib/when_exe/region/chinese/notes.rb +3 -3
  24. data/lib/when_exe/region/chinese/twins.rb +6 -6
  25. data/lib/when_exe/region/islamic.rb +1 -1
  26. data/lib/when_exe/region/japanese.rb +4 -4
  27. data/lib/when_exe/region/japanese/eclipses.rb +2 -2
  28. data/lib/when_exe/region/japanese/location.rb +93 -0
  29. data/lib/when_exe/region/japanese/notes.rb +29 -11
  30. data/lib/when_exe/region/japanese/residues.rb +1 -1
  31. data/lib/when_exe/region/japanese/twins.rb +18 -6
  32. data/lib/when_exe/region/location.rb +40 -0
  33. data/lib/when_exe/region/martian.rb +1 -1
  34. data/lib/when_exe/region/ryukyu.rb +1 -1
  35. data/lib/when_exe/spatial.rb +611 -0
  36. data/lib/when_exe/timestandard.rb +3 -3
  37. data/lib/when_exe/tmobjects.rb +32 -0
  38. data/lib/when_exe/tmposition.rb +211 -1318
  39. data/lib/when_exe/tmptypes.rb +1265 -0
  40. data/lib/when_exe/tmreference.rb +35 -0
  41. data/lib/when_exe/version.rb +3 -3
  42. data/test/events/example-datasets +7 -0
  43. data/test/events/history-dataset.csv +22 -0
  44. data/test/events/japanese-holiday-index.csv +28 -0
  45. data/test/events/japanese-holiday.csv +77 -0
  46. data/test/events/japanese-holiday.ttl +778 -0
  47. data/test/events/make_events_ttl.rb +18 -0
  48. data/test/events/mori_wikichoshi.csv +14 -0
  49. data/test/events/ndl_koyomi.csv +220 -0
  50. data/test/events/ndl_koyomi_index.csv +44 -0
  51. data/test/events/primeminister-dataset.csv +19 -0
  52. data/test/events/shogun-dataset.csv +22 -0
  53. data/test/events/test-history-dataset-edge-sparql.csv +26 -0
  54. data/test/events/test-history-dataset-edge.csv +27 -0
  55. data/test/events/test-history-dataset-sparql.csv +22 -0
  56. data/test/events/test-history-dataset.csv +23 -0
  57. data/test/events/test-history-events-edge.ttl +89 -0
  58. data/test/events/test-history-events.csv +6 -0
  59. data/test/examples/Terms.m17n +1 -1
  60. data/test/test.rb +6 -0
  61. data/test/test/coordinates.rb +2 -2
  62. data/test/test/events.rb +32 -0
  63. data/test/test/region/japanese.rb +20 -0
  64. data/test/test/region/m17n.rb +2 -2
  65. data/test/test/region/mayan.rb +6 -6
  66. data/test/test/tmposition.rb +63 -1
  67. metadata +26 -2
@@ -642,7 +642,7 @@ module When::Ephemeris
642
642
  # @return [When::Ephemeris::Coords]
643
643
  #
644
644
  def rh_to_h(t, loc)
645
- rotate_y(loc.lat / (360.0*When::Coordinates::Spatial::DEGREE) - 0.25)
645
+ rotate_y(loc.lat / (360.0*loc.degree) - 0.25)
646
646
  end
647
647
 
648
648
  # 赤道座標 -> 地平座標
@@ -655,7 +655,7 @@ module When::Ephemeris
655
655
  #
656
656
  def r_to_h(t, loc)
657
657
  rotate_z(-loc.local_sidereal_time(t) / 24).
658
- rotate_y(loc.lat / (360.0*When::Coordinates::Spatial::DEGREE) - 0.25)
658
+ rotate_y(loc.lat / (360.0*loc.degree) - 0.25)
659
659
  end
660
660
 
661
661
  # 球面の余弦 spherical law of cosines
@@ -1342,7 +1342,7 @@ module When::Ephemeris
1342
1342
  #
1343
1343
  def day_event(t, event, target=When.Resource('_ep:Sun'), height=nil)
1344
1344
  # 時刻の初期値
1345
- dl = @location.long / (360.0 * When::Coordinates::Spatial::DEGREE)
1345
+ dl = @location.long / (360.0 * @location.degree)
1346
1346
  t0 = (+t + dl).round - dl
1347
1347
  dt = _coords(t0, When::Coordinates::Spatial::EQUATORIAL, target).phi -
1348
1348
  @location.local_sidereal_time(t0) / 24.0 + 0.25 * (event||0)
@@ -1526,8 +1526,8 @@ module When::Ephemeris
1526
1526
  # difference of ecliptic longitude between zenith and target star
1527
1527
  # when the event happens
1528
1528
  pos = _coords(+t, When::Coordinates::Spatial::EQUATORIAL, target)
1529
- long = @location.long / When::Coordinates::Spatial::DEGREE
1530
- lat = @location.lat / When::Coordinates::Spatial::DEGREE
1529
+ long = @location.long / @location.degree
1530
+ lat = @location.lat / @location.degree
1531
1531
  dp = (sind(hs) - sind(lat) * sinc(pos.theta)) /
1532
1532
  (cosd(lat) * cosc(pos.theta))
1533
1533
 
@@ -1595,8 +1595,8 @@ module When::Ephemeris
1595
1595
  # @sun_1m = t = CYCLE_1M日 までの太陽年番号の比例定数
1596
1596
  def _normalize(args=[], options={})
1597
1597
  @location = When.Resource(@location, '_l:') if @location.kind_of?(String)
1598
- @long, @lat, @alt = [@location.long / When::Coordinates::Spatial::DEGREE,
1599
- @location.lat / When::Coordinates::Spatial::DEGREE,
1598
+ @long, @lat, @alt = [@location.long / @location.degree,
1599
+ @location.lat / @location.degree,
1600
1600
  @location.alt] if @location
1601
1601
  @formula ||= '1L'
1602
1602
  @time_standard ||= 'dynamical'
@@ -105,20 +105,6 @@ class When::CalendarNote
105
105
  [p1 % @den, p1 - p0]
106
106
  end
107
107
 
108
- #
109
- # イベントの標準的な間隔を返す
110
- #
111
- # @param [String] parameter 座標の分子と分母("#{ num }/#{ den }" の形式)
112
- #
113
- # @return [When::TM::IntervalLength]
114
- #
115
- # @private
116
- def event_delta(parameter=nil)
117
- return @delta unless parameter
118
- num, den = parameter.kind_of?(String) ? parameter.split(/\//, 2) : parameter
119
- When::TM::IntervalLength.new([(den || @den).to_f,1].max*0.9, 'day')
120
- end
121
-
122
108
  #
123
109
  # イベント日付(時刻付)
124
110
  #
@@ -0,0 +1,1851 @@
1
+ # -*- coding: utf-8 -*-
2
+ =begin
3
+ Copyright (C) 2015-2016 Takashi SUGA
4
+
5
+ You may use and/or modify this file according to the license
6
+ described in the LICENSE.txt file included in this archive.
7
+ =end
8
+
9
+ require 'when_exe/namespace'
10
+ require 'when_exe/tmduration'
11
+
12
+ module When
13
+
14
+ #
15
+ # 時間座標を持つイベント記録の管理
16
+ #
17
+ module Events
18
+
19
+ #
20
+ # イベント管理用範囲オブジェクト
21
+ #
22
+ class Range < ::Range
23
+
24
+ # 実数のための exclude_end? 判定用マージン
25
+ #
26
+ Delta = When::TM::Duration::SECOND / 4096.0 / When::TM::Duration::DAY
27
+
28
+ # 小さい方の境界
29
+ #
30
+ # @return [Object]
31
+ #
32
+ attr_reader :start
33
+
34
+ # 大きい方の境界
35
+ #
36
+ # @return [Object]
37
+ #
38
+ attr_reader :until
39
+
40
+ # オブジェクトを When::Events::Range 型に変換する
41
+ #
42
+ # @param [Object] source 変換元のオブジェクト
43
+ #
44
+ # @return [When::Events::Range] 変換結果
45
+ #
46
+ def self.convert_from(source)
47
+ case
48
+ when source.kind_of?(self) ; source
49
+ when source.respond_to?(:exclude_end?) ; new(source, source.last, source.exclude_end?)
50
+ when source.respond_to?(:succ) ; new(source, source.succ, true)
51
+ else raise ArgumentError, "Can't convert #{source} to #{self}"
52
+ end
53
+ end
54
+
55
+ # 範囲の重なりの判断が複雑になるか?
56
+ #
57
+ # @return [Boolean] true - 複雑, false - 単純
58
+ #
59
+ def is_complex?
60
+ case @start
61
+ when Array, Enumerator ; true
62
+ else ; false
63
+ end
64
+ end
65
+
66
+ # 指定オブジェクトが範囲内か?
67
+ #
68
+ # @param [Object] target 判定するオブジェクト
69
+ #
70
+ # @return [Boolean] true - 範囲内のオブジェクトあり, false - 範囲内のオブジェクトなし
71
+ #
72
+ def include?(target)
73
+ !include(target).empty?
74
+ end
75
+
76
+ # 含む対象の抽出
77
+ #
78
+ # @param [Object] target 判定するオブジェクト
79
+ #
80
+ # @return [Array<Object>] 実際に包含した範囲オブジェクトの Array
81
+ #
82
+ def include(target)
83
+ return [] if (exclude_end? && @until <= target) || (!exclude_end? && @until < target)
84
+
85
+ case @start
86
+ when ::Range
87
+ return @start.include?(target) ? [@start] : []
88
+
89
+ when Array
90
+ list = []
91
+ @start.each do |range|
92
+ break if _range_exceeded?(range, target)
93
+ list << range if _target_included?(range, target)
94
+ end
95
+ return list
96
+
97
+ when Enumerator
98
+ begin
99
+ list = []
100
+ while (range = @start.succ)
101
+ break if _range_exceeded?(range, target)
102
+ list << range if _target_included?(range, target)
103
+ end
104
+ return list
105
+ ensure
106
+ @start._rewind
107
+ end
108
+
109
+ else
110
+ return @start <= target ? [@start] : []
111
+ end
112
+ end
113
+
114
+ #
115
+ # 範囲の重なりの判断
116
+ #
117
+ # @param [Range] range 確認対象の単純範囲オブジェクト
118
+ #
119
+ # @return [Boolean] true - 重なる, false - 重ならない
120
+ #
121
+ def is_overlaped?(range)
122
+ !overlaped(range).empty?
123
+ end
124
+
125
+ #
126
+ # 範囲が重なる対象の抽出
127
+ #
128
+ # @param [Range] target 確認対象の単純範囲オブジェクト
129
+ #
130
+ # @return [Array<Object>] 実際に重なった範囲オブジェクトの Array
131
+ #
132
+ def overlaped(target)
133
+ first = target.first
134
+ last = target.last
135
+ last = last.respond_to?(:prev) ? last.prev : last - Delta if target.exclude_end?
136
+ return [] if (exclude_end? && @until <= first) || (!exclude_end? && @until < first)
137
+
138
+ case @start
139
+ when ::Range
140
+ return _target_exceeded?(@start, first) || _range_exceeded?(@start, last) ? [] : [@start]
141
+
142
+ when Array
143
+ list = []
144
+ @start.each do |range|
145
+ break if _range_exceeded?(range, last)
146
+ list << range unless _target_exceeded?(range, first)
147
+ end
148
+ return list
149
+
150
+ when Enumerator
151
+ begin
152
+ list = []
153
+ while (range = @start.succ)
154
+ break if _range_exceeded?(range, last)
155
+ list << range unless _target_exceeded?(range, first)
156
+ end
157
+ return list
158
+ ensure
159
+ @start._rewind
160
+ end
161
+
162
+ else
163
+ return @start <= last ? [@start] : []
164
+ end
165
+ end
166
+
167
+ #
168
+ # イベント管理用範囲オブジェクトの生成
169
+ #
170
+ # @param [Object] first 小さい方の境界
171
+ # @param [Object] last 大きい方の境界
172
+ # @param [Boolean] exclude_end 大きい方の境界を含まないか?
173
+ #
174
+ def initialize(first, last, exclude_end=false)
175
+ @start = first
176
+ @until = last
177
+ range = [first, last].map {|edge| edge.respond_to?(:first) ? edge.first : edge}
178
+ rase ArgumentError, "#{range.last} is less than #{range.first}" if range.last < range.first
179
+ super(range.first, range.last, exclude_end)
180
+ end
181
+
182
+ private
183
+
184
+ # target が小さすぎるか?
185
+ def _range_exceeded?(range, target)
186
+ focused = range.respond_to?(:first) ? range.first : range
187
+ return true if exclude_end? && @until <= focused
188
+ return true if !exclude_end? && @until < focused
189
+ target < focused
190
+ end
191
+
192
+ # target が大きすぎるか?
193
+ def _target_exceeded?(range, target)
194
+ return false if target == (range.respond_to?(:first) ? range.first : range)
195
+ return target >= range.succ unless range.respond_to?(:last)
196
+ return target >= range.last if range.exclude_end?
197
+ return target > range.last
198
+ end
199
+
200
+ # target が含まれるか?
201
+ def _target_included?(range, target)
202
+ range.respond_to?(:include?) ? range.include?(target) : (range == target)
203
+ end
204
+ end
205
+
206
+ if When.const_defined?(:Parts)
207
+
208
+ #
209
+ # 多言語対応データセット群
210
+ #
211
+ # 言語ごとに異なるデータセットを保持する
212
+ #
213
+ class DataSets
214
+
215
+ include When::Parts::Resource
216
+
217
+ # 名前
218
+ #
219
+ # @return [When::BasicTypes::M17n]
220
+ #
221
+ attr_reader :label
222
+
223
+ # 各言語用のデータセットの Hash
224
+ #
225
+ # @return [Hash<String=>When::Events::DataSet>]
226
+ #
227
+ attr_reader :datasets
228
+
229
+ # 単言語データセットオブジェクトの取得
230
+ #
231
+ # @param [String] language 言語コード
232
+ # @param [Numeric] limit オブジェクト生成待ち時間 / 秒
233
+ # @param [Boolean] limit false - 待たない, true - 無限に待つ
234
+ #
235
+ # @return [When::Events::DataSet]
236
+ #
237
+ def dataset(language='', limit=true)
238
+ if When.multi_thread
239
+ joined =
240
+ case limit
241
+ when Numeric ; @thread.join(limit)
242
+ when nil,false ; true
243
+ else ; @thread.join
244
+ end
245
+ return nil unless joined
246
+ end
247
+ return nil unless @datasets
248
+ When::Locale._hash_value(@datasets, language)
249
+ end
250
+
251
+ # 多言語対応 namespace の取得
252
+ #
253
+ # @param [String] prefix プレフィクス
254
+ # @param [String] language 言語コード
255
+ #
256
+ # @return [Array<String(namespace), String(説明)>]
257
+ #
258
+ def namespace(prefix, language)
259
+ When::Locale._hash_value(@prefixes[prefix], language)
260
+ end
261
+
262
+ #
263
+ # 指定の Event を主語とする Statement からなる RDF:Repository を生成する
264
+ #
265
+ # @param [Array<When::Events::Event>] events 登録する Event の Array
266
+ #
267
+ # @return [Hash<String(GraphURI)=>RDF:Repository>] 生成した Repository の Hash
268
+ #
269
+ def repository(events=nil)
270
+ repositories = {}
271
+ @datasets.each_pair do |language, dataset|
272
+ repositories[language] = dataset.repository(events)
273
+ end
274
+ _merge_each_graph(repositories)
275
+ end
276
+
277
+ #
278
+ # 指定の URI を主語とする Statement からなる RDF:Repository を生成する
279
+ #
280
+ # @param [String] uri 主語の URI
281
+ # @param [Integer] uri 主語のイベントの通し番号
282
+ # @param [String] graph 検索対象のグラフ(ダミー)
283
+ #
284
+ # @return [Hash<String(GraphURI)=>RDF:Repository>] 生成した Repository の Hash
285
+ #
286
+ def event(uri, graph=nil)
287
+ repositories = {}
288
+ @datasets.each_pair do |language, dataset|
289
+ repositories[language] = dataset.event(uri, graph)
290
+ end
291
+ _merge_each_graph(repositories)
292
+ end
293
+
294
+ #
295
+ # RDF::URI リソースで使用された prefix - namespace 対
296
+ #
297
+ # @return [Hash<String(prefix)=>String(namespace)>]
298
+ #
299
+ def used_ns
300
+ pair = {}
301
+ @datasets.each_value do |dataset|
302
+ pair.update(dataset.used_ns)
303
+ end
304
+ pair
305
+ end
306
+
307
+ #
308
+ # graph ごとに repository を merge する
309
+ #
310
+ def _merge_each_graph(repositories)
311
+ merged = {}
312
+ repositories.keys.inject([]) {|graphs,language| (graphs | repositories[language].keys) }.each do |graph|
313
+ repository_for_graph = ::RDF::Repository.new
314
+ repositories.each_value do |repository|
315
+ repository_for_graph << repository[graph] if repository.key?(graph)
316
+ end
317
+ merged[graph] = repository_for_graph
318
+ end
319
+ merged
320
+ end
321
+ private :_merge_each_graph
322
+
323
+ # 多言語対応データセットオブジェクトの生成
324
+ #
325
+ # @param [String] uri データセット定義の所在
326
+ # @param [Array<Array<String>>] rows 多言語対応データセット定義
327
+ # @param [Block] block キャッシュされたファイルパスの読み替え処理
328
+ #
329
+ def initialize(uri, rows, &block)
330
+
331
+ # 定義行を言語ごとに仕分けする
332
+ definitions = {}
333
+ @prefixes = {}
334
+ labels = nil
335
+ datasets = nil
336
+ (0...rows.size).to_a.reverse.each do |index|
337
+ items = rows[index].map {|item| item.strip}
338
+ prefix, namespace, word = items
339
+ if /\A(.+?):@\*\z/ =~ prefix
340
+ rows[index..index] = prefix_wildcard($1, namespace, word)
341
+ else
342
+ rows[index] = items
343
+ end
344
+ end
345
+ rows.each do |row|
346
+ next if row.first =~ /\A\s*#/
347
+ name, language= row.shift.split('@', 2)
348
+ language ||= ''
349
+ language.sub!('_', '-')
350
+ if definitions.key?(name)
351
+ if language == ''
352
+ definitions[name].values.each do |definition|
353
+ definition << row
354
+ end
355
+ else
356
+ definitions[name][language] << row
357
+ end
358
+ else
359
+ definitions[name] = Hash.new {|hash,key| hash[key]=[]}.merge({''=>[row]})
360
+ definitions[name][language] << row unless language == ''
361
+ end
362
+ case extract(name)
363
+ when LABEL ; labels = filter_for_dataset(definitions[name])
364
+ when REFERENCE ; datasets = filter_for_dataset(definitions[name])
365
+ when /\A(.+?):\z/; @prefixes[$1] = filter_for_dataset(definitions[name], true)
366
+ end
367
+ end
368
+
369
+ # 名前オブジェクトを生成する
370
+ @label = When::BasicTypes::M17n.new({
371
+ 'label'=>labels[''].first,
372
+ 'names'=>Hash[*(labels.keys.map {|label| [label, labels[label].first ]}).flatten],
373
+ 'link' =>Hash[*(datasets.keys.map {|dataset| [dataset, datasets[dataset].first]}).flatten]
374
+ })
375
+ @_pool = {'..'=>uri, '.'=>rows, @label.to_s=>@label}
376
+ @child = [@label]
377
+ @label._pool['..'] = self
378
+
379
+ # 各言語用のデータセットオブジェクトを生成する
380
+ if When.multi_thread
381
+ @thread = Thread.new do
382
+ begin
383
+ @datasets = create_datasets(datasets, uri, definitions, &block)
384
+ rescue => exception
385
+ puts exception
386
+ # puts exception.backtrace
387
+ raise exception
388
+ end
389
+ end
390
+ else
391
+ @datasets = create_datasets(datasets, uri, definitions, &block)
392
+ end
393
+ end
394
+
395
+ private
396
+
397
+ # 言語ごとのデータセットを登録する
398
+ def create_datasets(datasets, uri, definitions, &block)
399
+ Hash[*(datasets.keys.map {|language|
400
+ lines = []
401
+ definitions.each_pair do |name, definition|
402
+ lang = When::Locale._hash_key(definition, language)
403
+ definition[lang].each do |defs|
404
+ lines << [[name] + defs, lang]
405
+ end
406
+ end
407
+ [language, DataSet.new(lines, language, uri, self, &block)]
408
+ }).flatten]
409
+ end
410
+
411
+ # 名前空間のワイルドカードを展開する
412
+ def prefix_wildcard(prefix, namespace, word)
413
+ hash = When.Wikipedia(word).to_h
414
+ hash[:names].keys.map {|loc|
415
+ next nil unless /./ =~ loc
416
+ ["#{prefix}_#{loc}:@#{loc}", namespace.sub('*',loc), hash[:names][loc]]
417
+ }.compact
418
+ end
419
+
420
+ # データセット用の設定を選択する
421
+ def filter_for_dataset(target, filter=false)
422
+ hash = {}
423
+ target.each_pair do |language, definitions|
424
+ definitions.each do |definition|
425
+ if filter || DataSet.for_dataset?(definition.first)
426
+ hash[language] = definition
427
+ break
428
+ end
429
+ end
430
+ end
431
+ hash
432
+ end
433
+
434
+ # プレフィクスを名前空間に展開する
435
+ def extract(predicate)
436
+ return predicate unless /\A\{(.+?):(.+)\}\z/ =~ predicate && @prefixes.key?($1)
437
+ prefix, body = $~[1..2]
438
+ namespace = @prefixes[prefix].values.first.first
439
+ namespace = namespace.sub(/([a-z0-9])\z/i, '\1#')
440
+ namespace + body
441
+ end
442
+ end
443
+
444
+ # When.exe Time Schema
445
+ TS = When::Parts::Resource.base_uri.sub(/When\/$/, 'ts#')
446
+
447
+ else
448
+
449
+ # When.exe Time Schema - TS may not equal When::Resource.base_uri.sub(/When\/$/, 'ts#')
450
+ TS = 'http://hosi.org/ts#'
451
+
452
+ end
453
+
454
+ # Time Schema
455
+ TT = TS.sub(/#\z/,'/')
456
+ RANGE = TT + Range.class.to_s.gsub('::','/')
457
+ ID = TS + 'id'
458
+ IRI = TS + 'IRI'
459
+ REFERENCE = TS + 'reference'
460
+ GROUP = TS + 'group'
461
+ WHAT_DAY = TS + 'whatDay'
462
+ START = TS + 'start'
463
+ UNTIL = TS + 'until'
464
+ WEST = TS + 'west'
465
+ EAST = TS + 'east'
466
+ SOUTH = TS + 'south'
467
+ NORTH = TS + 'north'
468
+ BOTTOM = TS + 'bottom'
469
+ TOP = TS + 'top'
470
+
471
+ # XML Schema Definition
472
+ INTEGER = Namespace::XSD + '#integer'
473
+ FLOAT = Namespace::XSD + '#float'
474
+ DOUBLE = Namespace::XSD + '#double'
475
+ STRING = Namespace::XSD + '#string'
476
+ DATE = Namespace::XSD + '#date'
477
+ TIME = Namespace::XSD + '#time'
478
+ DATETIME = Namespace::XSD + '#dateTime'
479
+
480
+ # Resource Description Framework
481
+ TYPE = Namespace::RDF + 'type'
482
+ SUBJECT = Namespace::RDF + 'subject'
483
+ LABEL = Namespace::RDFS + 'label'
484
+ GRAPH = Namespace::RDFC + 'section-rdf-graph'
485
+
486
+ # Dublin Core
487
+ SOURCE = Namespace::DC + 'source'
488
+ CONTRIBUTOR = Namespace::DC + 'contributor'
489
+ LICENSE = Namespace::DCQ + 'license'
490
+ VALID = Namespace::DCQ + 'valid'
491
+ ABSTRACT = Namespace::DCQ + 'abstract'
492
+ HAS_PART = Namespace::DCQ + 'hasPart'
493
+ SPATIAL = Namespace::DCQ + 'spatial'
494
+ URI = Namespace::DCQ + 'URI'
495
+
496
+ # For Dataset
497
+ ForDataset = [LABEL, REFERENCE, CONTRIBUTOR, LICENSE]
498
+
499
+ # Index
500
+ EqualIndex = [SUBJECT, GRAPH, GROUP, CONTRIBUTOR, SPATIAL]
501
+ FirstEdge = {'valid'=>START, 'longitude'=>WEST, 'latitude'=>SOUTH, 'altitude'=>BOTTOM}
502
+ LastEdge = {'valid'=>UNTIL, 'longitude'=>EAST, 'latitude'=>NORTH, 'altitude'=>TOP }
503
+
504
+ # 同値インデクスの判断
505
+ #
506
+ # @param [String] predicate 述語
507
+ #
508
+ # @return [Symbol] :equal 作成する, nil 作成しない
509
+ #
510
+ def self.equal(predicate)
511
+ EqualIndex.include?(predicate) ?
512
+ :equal :
513
+ nil
514
+ end
515
+
516
+ # 境界インデクスの判断
517
+ #
518
+ # @param [String] predicate 述語
519
+ #
520
+ # @return [Symbol] :first 下限境界, :last 上限境界, nil どちらでもなし
521
+ #
522
+ def self.edge(predicate)
523
+ FirstEdge.values.include?(predicate) ?
524
+ :first :
525
+ LastEdge.values.include?(predicate) ?
526
+ :last :
527
+ nil
528
+ end
529
+
530
+ # 出現回数の限定
531
+ #
532
+ # @param [String] predicate 述語
533
+ #
534
+ # @return [Boolean] true 1回限定, false 限定なし
535
+ #
536
+ def self.cardinality1(predicate)
537
+ predicate != HAS_PART
538
+ end
539
+
540
+ #
541
+ # 一言語対応データセット
542
+ #
543
+ # 特定の言語用データセットを保持する
544
+ #
545
+ class DataSet
546
+
547
+ #
548
+ # オブジェクト操作定義
549
+ #
550
+
551
+ # 何もしない
552
+ nooperation = proc {|event, obj|
553
+ obj
554
+ }
555
+
556
+ # 名前空間の展開
557
+ to_uri = proc {|event, obj|
558
+ event.dataset.extract(obj)
559
+ }
560
+
561
+ # 整数化
562
+ to_i = proc {|event, obj|
563
+ obj.to_i
564
+ }
565
+
566
+ # 実数化
567
+ to_f = proc {|event, obj|
568
+ obj.to_f
569
+ }
570
+
571
+ # 下限整数化
572
+ first_to_i = proc {|event, obj|
573
+ obj.respond_to?(:first) ?
574
+ obj.first.to_i :
575
+ obj.to_i
576
+ }
577
+
578
+ # 下限実数化
579
+ first_to_f = proc {|event, obj|
580
+ obj.respond_to?(:first) ?
581
+ obj.first.to_f :
582
+ obj.to_f
583
+ }
584
+
585
+ # 上限整数化
586
+ last_to_i = proc {|event, obj|
587
+ !obj.respond_to?(:last) ?
588
+ obj.succ.to_i - 1 :
589
+ obj.exclude_end? ?
590
+ obj.last.to_i - 1 :
591
+ obj.last.succ.to_i - 1
592
+ }
593
+
594
+ # 上限実数化
595
+ last_to_f = proc {|event, obj|
596
+ !obj.respond_to?(:last) ?
597
+ obj.to_f :
598
+ obj.exclude_end? ?
599
+ obj.last.to_f - Range::Delta :
600
+ obj.last.respond_to?(:succ) ?
601
+ obj.last.succ.to_f - Range::Delta :
602
+ obj.last.to_f
603
+ }
604
+
605
+ # 日時範囲化
606
+ to_range = proc {|event, date|
607
+ target = When.when?(date, :parse=>When::Locale::EasternParser)
608
+ case target
609
+ when Array, Enumerator ; Range.convert_from(target)
610
+ else ; target
611
+ end
612
+ }
613
+
614
+ # 日付化
615
+ to_date = proc {|event, date|
616
+ Date.parse(date)
617
+ }
618
+
619
+ # 時刻化
620
+ to_time = proc {|event, time|
621
+ Time.parse(time)
622
+ }
623
+
624
+ # 日時化
625
+ to_date_time = proc {|event, date_time|
626
+ DateTime.parse(date_time)
627
+ }
628
+
629
+ # 経度
630
+ to_long = proc {|event, location|
631
+ location.kind_of?(Numeric) ? location :
632
+ When.where?(location).long
633
+ }
634
+
635
+ # 緯度
636
+ to_lat = proc {|event, location|
637
+ location.kind_of?(Numeric) ? location :
638
+ When.where?(location).lat
639
+ }
640
+
641
+ # 高度
642
+ to_alt = proc {|event, location|
643
+ location.kind_of?(Numeric) ? location :
644
+ When.where?(location).alt
645
+ }
646
+
647
+ #
648
+ # オブジェクト操作対応付け
649
+ #
650
+ Operations = Hash.new(nooperation).merge({
651
+ URI => to_uri,
652
+ IRI => to_uri,
653
+ INTEGER => to_i,
654
+ FLOAT => to_f,
655
+ DOUBLE => to_f,
656
+ [:first, INTEGER] => first_to_i,
657
+ [:first, FLOAT ] => first_to_f,
658
+ [:first, DOUBLE ] => first_to_f,
659
+ [:last, INTEGER] => last_to_i,
660
+ [:last, FLOAT ] => last_to_f,
661
+ [:last, DOUBLE ] => last_to_f,
662
+ RANGE => to_range,
663
+ DATE => to_date,
664
+ TIME => to_time,
665
+ DATETIME => to_date_time,
666
+
667
+ WEST => to_long,
668
+ EAST => to_long,
669
+ SOUTH => to_lat,
670
+ NORTH => to_lat,
671
+ BOTTOM => to_alt,
672
+ TOP => to_alt
673
+ })
674
+
675
+ #
676
+ # 自身の言語
677
+ #
678
+ # @return [String]
679
+ #
680
+ attr_reader :language
681
+
682
+ #
683
+ # 所属する多言語対応データセット
684
+ #
685
+ # @return [When::Events::DataSets]
686
+ #
687
+ attr_reader :parent
688
+
689
+ #
690
+ # 定義行の元情報
691
+ #
692
+ # @return [Array<String>]
693
+ #
694
+ attr_reader :definitions
695
+
696
+ #
697
+ # 時間座標を持つイベント
698
+ #
699
+ # @return [When::Events::Event]
700
+ #
701
+ attr_reader :events
702
+
703
+ #
704
+ # デフォルト Graph の URI
705
+ #
706
+ # @return [String(URI)]
707
+ #
708
+ attr_reader :default_graph
709
+
710
+ #
711
+ # 名前空間
712
+ #
713
+ # @return [Hash<String(prefix)=>String(namespace)>]
714
+ #
715
+ attr_reader :prefix
716
+
717
+ #
718
+ # 名前空間の説明
719
+ #
720
+ # @return [Array<Array<String(namespace), String(description)>>]
721
+ #
722
+ attr_reader :prefix_description
723
+
724
+ #
725
+ # ロール変数の定義
726
+ #
727
+ # @return [Hash<String(prefix:name)=>Hash>]
728
+ #
729
+ attr_reader :role
730
+
731
+ #
732
+ # RDF変数の定義
733
+ #
734
+ # @return [Hash<String(prefix:name)=>Hash>]
735
+ #
736
+ attr_reader :rdf
737
+
738
+ #
739
+ # CSV変数の定義
740
+ #
741
+ # @return [Hash<String(prefix:name)=>Hash>]
742
+ #
743
+ attr_reader :csv
744
+
745
+ #
746
+ # 一致キーのためのインデクス
747
+ #
748
+ # @return [Hash<String(prefix:name)=>Hash<String(key)=>Array>>]
749
+ #
750
+ attr_reader :index
751
+
752
+ #
753
+ # 順序キーのためのインデクス
754
+ #
755
+ # @return [Hash<String(prefix:name)=>Array>]
756
+ #
757
+ attr_reader :order
758
+
759
+ #
760
+ # データセットで使用しているRDF::URI リソース
761
+ #
762
+ # @return [Hash<String(iri)=>::RDF::URI>]
763
+ #
764
+ attr_reader :resource
765
+
766
+ #
767
+ # RDF::URI リソースで使用された prefix - namespace 対
768
+ #
769
+ # @return [Hash<String(prefix)=>String(namespace)>]
770
+ #
771
+ attr_reader :used_ns
772
+
773
+ #
774
+ # 指定の Event を主語とする Statement からなる RDF:Repository を生成する
775
+ #
776
+ # @param [Array<When::Events::Event>] events 登録する Event の Array
777
+ #
778
+ # @return [Hash<String(GraphURI)=>RDF:Repository>] 生成した Repository の Hash
779
+ #
780
+ def repository(events=nil)
781
+ if events
782
+ rep = Hash.new {|hash,key| hash[key] = ::RDF::Repository.new}
783
+ events.each do |event|
784
+ rep[''].insert(*event.statements)
785
+ rep[event.role[GRAPH]].insert(*event.statements) if @role.key?(GRAPH)
786
+ end
787
+ rep
788
+ else
789
+ @repository ||= repository(@events)
790
+ end
791
+ end
792
+
793
+ #
794
+ # 指定の URIまたはイベントの通し番号を主語とする Statement からなる RDF:Repository を生成する
795
+ #
796
+ # @param [String] uri 主語の URI
797
+ # @param [Integer] uri 主語のイベントの通し番号
798
+ # @param [String] graph 検索対象のグラフ(ダミー)
799
+ #
800
+ # @return [Hash<String(GraphURI)=>RDF:Repository>] 生成した Repository の Hash
801
+ #
802
+ def event(uri, graph=nil)
803
+ rep = Hash.new {|hash,key| hash[key] = ::RDF::Repository.new}
804
+ list = uri.kind_of?(String) ? @index[SUBJECT][uri] : [uri]
805
+ unless list.empty?
806
+ event = @events[list.first-1]
807
+ rep[''].insert(*event.statements)
808
+ rep[event.role[GRAPH]].insert(*event.statements) if @role.key?(GRAPH)
809
+ end
810
+ rep
811
+ end
812
+
813
+ #
814
+ # 外部 RDF を読み込んだ場合に動作を置き換える
815
+ #
816
+ module ExternalRepository
817
+ #
818
+ # 指定の Event を主語とする Statement からなる RDF:Repository を生成する
819
+ #
820
+ # @param [Array<When::Events::Event>] events 登録する Event の Array
821
+ #
822
+ # @return [Hash<String(GraphURI)=>RDF:Repository>] 生成した Repository の Hash
823
+ #
824
+ def repository(events=nil)
825
+ if events
826
+ rep = Hash.new {|hash,key| hash[key] = ::RDF::Repository.new}
827
+ events.each do |event|
828
+ @repository[''].query({:subject=>@resource[event.role[SUBJECT]]}) do |statement|
829
+ rep[''].insert(statement)
830
+ rep[event.role[GRAPH]].insert(statement) if @role.key?(GRAPH)
831
+ end
832
+ end
833
+ rep
834
+ else
835
+ @repository
836
+ end
837
+ end
838
+
839
+ #
840
+ # 指定の URI を主語とする Statement からなる RDF:Repository を生成する
841
+ #
842
+ # @param [String] uri 主語の URI
843
+ # @param [String] graph 検索対象のグラフ(ダミー)
844
+ #
845
+ # @return [Hash<String(GraphURI)=>RDF:Repository>] 生成した Repository の Hash
846
+ #
847
+ def event(uri, graph=nil)
848
+ rep = Hash.new {|hash,key| hash[key] = ::RDF::Repository.new}
849
+ @repository[''].query({:subject=>@resource[uri]}) do |statement|
850
+ rep[''].insert(statement)
851
+ rep[event.role[GRAPH]].insert(statement) if @role.key?(GRAPH)
852
+ end
853
+ rep
854
+ end
855
+
856
+ private
857
+
858
+ # リポジトリを準備する
859
+ def initialize_repository(source)
860
+ @used_predicate = {}
861
+ @repository = Hash.new {|hash,key| hash[key] = ::RDF::Repository.new}
862
+ @repository.update({''=>::RDF::Repository.load(source)})
863
+ valid = @resource[@role[VALID] && /<(.+?)>/ =~ @role[VALID][:target] ?
864
+ $1 : VALID]
865
+ @repository[''].query({:predicate=>valid}) do |statement|
866
+ yield(rdf_to_hash(statement.subject))
867
+ end
868
+ @events.each do |event|
869
+ statements = event.statements.reject {|statement|
870
+ predicate = statement.predicate.to_s
871
+ @used_predicate.key?(predicate) || @role[predicate][:copied]
872
+ }
873
+ @repository[''].insert(*statements)
874
+ @repository[event.role[GRAPH]].insert(*statements) if @role.key?(GRAPH)
875
+ end
876
+ end
877
+
878
+ # 主語 subject を指定してトリプルをハッシュ化する
879
+ def rdf_to_hash(subject)
880
+ hash = {SUBJECT=>subject.to_s}
881
+ @repository[''].query({:subject=>subject}) do |statement|
882
+ key = statement.predicate.to_s
883
+ value = statement.object
884
+ value = value.to_s unless value.kind_of?(Numeric)
885
+ case hash[key]
886
+ when Array ; hash[key] << value
887
+ when nil ; hash[key] = When::Events.cardinality1(key) ? value : [value]
888
+ else ; hash[key] = [hash[key], value]
889
+ end
890
+ @used_predicate[key] ||= statement.predicate
891
+ end
892
+ hash
893
+ end
894
+ end
895
+
896
+ #
897
+ # 外部 SPARQL サーバーを利用する場合に動作を置き換える
898
+ #
899
+ module SparqlRepository
900
+
901
+ #
902
+ # 指定の URI を主語とする Statement からなる RDF:Repository を生成する
903
+ #
904
+ # @param [String] uri 主語の URI
905
+ # @param [String] graph 検索対象のグラフ
906
+ #
907
+ # @return [Hash<String(GraphURI)=>RDF:Repository>] 生成した Repository の Hash
908
+ #
909
+ def event(uri, graph=nil)
910
+ rep = Hash.new {|hash,key| hash[key] = ::RDF::Repository.new}
911
+ subject = @resource[uri]
912
+
913
+ # 問い合わせ文字列を準備する
914
+ query_string = "SELECT DISTINCT ?predicate ?object \n"
915
+ query_string << "FROM <#{graph}> \n" if /./ =~ graph
916
+ query_string << "WHERE { \n <#{uri}> ?predicate ?object . \n} \n"
917
+ # return query_string
918
+
919
+ # 問い合わせを実行する
920
+ client = ::SPARQL::Client.new(@endpoint)
921
+ client.query(query_string).each do |solution|
922
+ statement = ::RDF::Statement.new(subject, solution[:predicate], solution[:object])
923
+ rep[''].insert(statement)
924
+ rep[graph].insert(statement) if graph
925
+ end
926
+ rep
927
+ end
928
+
929
+ #
930
+ # 指定の条件を満たす Event の Array を返す
931
+ #
932
+ # @param [Hash] options 以下の通り
933
+ # @option options [String] 'date' 日付範囲
934
+ # @option options [String] 'contributor' 情報提供者
935
+ # @option options [String] 'graph' グラフ
936
+ # @option options [String] 'group' グループ
937
+ # @option options [String] 'location' 空間位置
938
+ # @option options [String] 'keyword' キーワード
939
+ # @option options [String or Integer] 'limit' 最大取得数
940
+ # @option options [String or Integer] 'offset' 取得開始レコード
941
+ # @option options [Boolean] 'count' イベント数のみの確認か?
942
+ #
943
+ # @return [Array<When::Events::Event>] Event の Array
944
+ #
945
+ def intersection_events(options)
946
+
947
+ # 問い合わせ文字列を準備する
948
+ rdf_keys = Hash.new {|hash,key| hash[key]=[]}
949
+ role_keys = @role.keys - [LABEL, REFERENCE, WHAT_DAY]
950
+ role_keys.each do |role_key|
951
+ @role[role_key][:target].scan(/<(.+?)>/) {rdf_keys[$1] << role_key}
952
+ end
953
+ keywords = (/./ =~ options['keyword']) ? options['keyword'].split('*') : []
954
+ loc_edges = @order.key?(WEST) || @order.key?(SOUTH) || @order.key?(BOTTOM)
955
+ triples = []
956
+ predicates = {}
957
+ rdf_keys.each_pair do |rdf_key, role_key|
958
+ if role_key.include?(SUBJECT)
959
+ predicates['?s'] = rdf_key
960
+ elsif role_key.include?(HAS_PART)
961
+ keywords.each do |keyword|
962
+ triples << "?s <#{rdf_key}> \"#{keyword}\" . "
963
+ end
964
+ elsif role_key.include?(CONTRIBUTOR) && /\A[^!]/ =~ options['contributor']
965
+ triples << "?s <#{rdf_key}> \"#{options['contributor']}\" . "
966
+ elsif role_key.include?(GROUP) && /./ =~ options['group']
967
+ triples << "?s <#{rdf_key}> \"#{options['group']}\" . "
968
+ elsif role_key.include?(SPATIAL) && /./ =~ options['location'] && !loc_edges
969
+ triples << "?s <#{rdf_key}> \"#{options['location']}\" . "
970
+ else
971
+ object = "?o#{predicates.size}"
972
+ predicates[object] = rdf_key
973
+ triples << "?s <#{rdf_key}> #{object} . "
974
+ end
975
+ end
976
+ select = options['count'] ?
977
+ "SELECT DISTINCT (COUNT(*) AS ?count) " :
978
+ "SELECT DISTINCT #{predicates.keys.join(' ')} "
979
+ filters = []
980
+ filters.concat(range_filter(When.date_or_era(options['date']), predicates, rdf_keys, 'valid', options['date'])) if /./ =~ options['date']
981
+ filters.concat(regex_filter(options['contributor'], predicates, rdf_keys, 'contributor'))
982
+ if /./ =~ options['location'] && loc_edges
983
+ location = When.where?(options['location'])
984
+ filters.concat(range_filter(location.long, predicates, rdf_keys, 'longitude')) if @order.key?(WEST)
985
+ filters.concat(range_filter(location.lat, predicates, rdf_keys, 'latitude' )) if @order.key?(SOUTH)
986
+ filters.concat(range_filter(location.alt, predicates, rdf_keys, 'altitude' )) if @order.key?(BOTTOM)
987
+ end
988
+ query_string = "#{select}\n"
989
+ query_string << "FROM <#{options['graph']}> \n" if @role.key?(GRAPH) && /./ =~ options['graph']
990
+ query_string << "WHERE { \n #{triples.map {|t| t+"\n "}.join('')}"
991
+ query_string << " FILTER ( \n #{filters.map {|f| ' '+f}.join(" && \n ")}\n ) \n" unless filters.empty?
992
+ query_string << "} \n"
993
+ query_string << "ORDER BY #{attr2var(@order.key?(START) ? 'start' : 'valid', predicates, rdf_keys)} \n" unless options['count']
994
+ query_string << "LIMIT #{options['limit']} \n" if options['limit' ].to_i > 1
995
+ query_string << "OFFSET #{options['offset']} \n" if options['offset'].to_i > 1
996
+ return query_string if options['debug']
997
+
998
+ # 問い合わせを実行する
999
+ client = ::SPARQL::Client.new(@endpoint)
1000
+ if options['count']
1001
+ # イベント数のみ確認する
1002
+ client.query(query_string).each do |solution|
1003
+ return solution[:count].to_i
1004
+ end
1005
+ return 0
1006
+
1007
+ else
1008
+ # イベントの内容を取り出す
1009
+ client.query(query_string).map {|solution|
1010
+ event = Hash[*predicates.keys.map {|predicate|
1011
+ object = solution[predicate[1..-1].to_sym]
1012
+ [predicates[predicate],
1013
+ case object
1014
+ when ::RDF::Literal::Integer ; object.to_i
1015
+ when ::RDF::Literal::Numeric ; object.to_f
1016
+ else object.to_s
1017
+ end]
1018
+ }.flatten]
1019
+ rdf_keys.each_pair do |rdf_key, role_key|
1020
+ next if event.key?(rdf_key)
1021
+ if role_key.include?(HAS_PART)
1022
+ event[rdf_key] = keywords
1023
+ elsif role_key.include?(CONTRIBUTOR)
1024
+ event[rdf_key] = options['contributor']
1025
+ elsif role_key.include?(GRAPH)
1026
+ event[rdf_key] = (/./ =~ options['graph'] ? options['graph'] : @default_graph)
1027
+ elsif role_key.include?(SPATIAL)
1028
+ event[rdf_key] = options['location']
1029
+ end
1030
+ end
1031
+ uri = event[predicates['?s']]
1032
+ id = uri[@default_graph.length..-1] if @default_graph && uri.index(@default_graph)
1033
+ Event.new(self, id || uri, event)
1034
+ }
1035
+ end
1036
+ end
1037
+
1038
+ private
1039
+
1040
+ # リポジトリを準備する
1041
+ def initialize_repository(source)
1042
+ @endpoint = source
1043
+ end
1044
+
1045
+ def range_filter(target, predicates, rdf_keys, attr, altanative=nil)
1046
+ first, last, exclude =
1047
+ case @role[FirstEdge[attr]] && @role[FirstEdge[attr]][:type]
1048
+ when INTEGER
1049
+ range_to_args(target, :to_i)
1050
+ when DOUBLE, FLOAT
1051
+ range_to_args(target, :to_f)
1052
+ else
1053
+ case altanative
1054
+ when /\A(.+?)(\.{2,3})(.+?)(\^{1,2}.+)?\z/
1055
+ ["\"#{$1}#{$4}\"", "\"#{$3}#{$4}\"", $2=='...']
1056
+ else
1057
+ [altanative]
1058
+ end
1059
+ end
1060
+ lower = @order.key?(LastEdge[ attr]) ? LastEdge[ attr][/[a-z]+\z/i] :
1061
+ @order.key?(FirstEdge[attr]) ? FirstEdge[attr][/[a-z]+\z/i] : attr
1062
+ upper = @order.key?(FirstEdge[attr]) ? FirstEdge[attr][/[a-z]+\z/i] : attr
1063
+ last ||= first
1064
+ lower_var = attr2var(lower, predicates, rdf_keys)
1065
+ upper_var = attr2var(upper, predicates, rdf_keys)
1066
+ [lower,first] == [upper,last] ?
1067
+ ["#{lower_var} = #{first}"] : ["#{lower_var} >= #{first}", "#{upper_var} #{exclude ? '<' : '<='} #{last}"]
1068
+ end
1069
+
1070
+ def regex_filter(target, predicates, rdf_keys, attr='contributor')
1071
+ return [] unless target && /\A!(.+)\z/ =~ target.strip
1072
+ ["!regex(#{attr2var(attr, predicates, rdf_keys)}, \"^#{$1}$\") "]
1073
+ end
1074
+
1075
+ def range_to_args(target, method)
1076
+ if target.respond_to?(:first)
1077
+ return [target.first.send(method), target.last.succ.send(method), true] if !target.exclude_end? && target.last.respond_to?(:succ)
1078
+ return [target.first.send(method), target.last.send(method), target.exclude_end?]
1079
+ elsif target.respond_to?(:succ)
1080
+ return [target.send(method), target.succ.send(method), true]
1081
+ else
1082
+ return [target.send(method)]
1083
+ end
1084
+ end
1085
+
1086
+ def attr2var(attr, predicates, rdf_keys)
1087
+ predicates.each_pair do |var, rdf_key|
1088
+ rdf_keys[rdf_key].each do |role|
1089
+ return var if role.index(attr)
1090
+ end
1091
+ end
1092
+ nil
1093
+ end
1094
+ end
1095
+
1096
+ #
1097
+ # 指定の条件を満たす Event の Array を返す
1098
+ #
1099
+ # @param [Hash] options 以下の通り
1100
+ # @option options [String] 'date' 日付範囲
1101
+ # @option options [String] 'contributor' 情報提供者
1102
+ # @option options [String] 'graph' グラフ
1103
+ # @option options [String] 'group' グループ
1104
+ # @option options [String] 'location' 空間位置
1105
+ # @option options [String] 'day' 今日は何の日用の日付(共通)
1106
+ # @option options [String] 'lsday' 今日は何の日用の日付(エスニック)
1107
+ # @option options [String] 'keyword' キーワード
1108
+ #
1109
+ # @return [Array<When::Events::Event>] Event の Array
1110
+ #
1111
+ def intersection_events(options)
1112
+ list = []
1113
+
1114
+ # 日付範囲
1115
+ if @order.key?(START) && /./ =~ options['date']
1116
+ range = Range.convert_from(When.date_or_era(options['date']))
1117
+ list << (@order.key?(UNTIL) ?
1118
+ range_overlaped(START, UNTIL, range) :
1119
+ edge_included(START, range))
1120
+ end
1121
+
1122
+ # キーワード
1123
+ if @index.key?(HAS_PART) && /./ =~ options['keyword']
1124
+ options['keyword'].split('*').each {|key| list << @index[HAS_PART][key]}
1125
+ end
1126
+
1127
+ # 情報提供者
1128
+ if @index.key?(CONTRIBUTOR) && /\A(!)?(.+)/ =~ options['contributor']
1129
+ reverse, contributor = $1, $2
1130
+ sublist = @index[CONTRIBUTOR][contributor]
1131
+ if sublist
1132
+ sublist = (@order[START] || (1..@events.size).to_a) - sublist if reverse
1133
+ list << sublist
1134
+ end
1135
+ end
1136
+
1137
+ # グループ
1138
+ if @index.key?(GROUP) && /./ =~ options['group']
1139
+ list << @index[GROUP][options['group']]
1140
+ end
1141
+
1142
+ # 空間位置
1143
+ if /./ =~ options['location']
1144
+ if @order.key?(WEST) || @order.key?(SOUTH) || @order.key?(BOTTOM)
1145
+ range = When.where?(options['location'])
1146
+ range = When::Coordinates::Spatial::Range.new(range,range) unless range.kind_of?(When::Coordinates::Spatial::Range)
1147
+ [[WEST, EAST, :long],
1148
+ [SOUTH, NORTH, :lat ],
1149
+ [BOTTOM, TOP, :alt ]].each do |pattern|
1150
+ first, last, method = pattern
1151
+ if @order.key?(first)
1152
+ list << (@order.key?(last) ?
1153
+ range_overlaped(first, last, range.send(method), :to_f) :
1154
+ edge_included(first, range.send(method), :to_f))
1155
+ end
1156
+ end
1157
+ elsif @index.key?(SPATIAL)
1158
+ list << @index[SPATIAL][options['location']]
1159
+ end
1160
+ end
1161
+
1162
+ # グラフ
1163
+ if @index.key?(GRAPH) && /./ =~ options['graph']
1164
+ list << @index[GRAPH][options['graph']]
1165
+ end
1166
+
1167
+ # 今日は何の日
1168
+ if @index.key?(WHAT_DAY)
1169
+ list << @index[WHAT_DAY][[true, $1.to_i,$2.to_i]] if /\A(\d{1,2})[-\/]?(\d{1,2})\z/ =~ options['day']
1170
+ list << @index[WHAT_DAY][[false, $1.to_i,$2.to_i]] if /\A(\d{1,2})[-\/]?(\d{1,2})\z/ =~ options['lsday']
1171
+ end
1172
+
1173
+ # 共通集合
1174
+ list.compact!
1175
+ return list if list.empty?
1176
+ intersection = list.shift
1177
+ list.each {|s| intersection &= s}
1178
+ events = intersection.map {|id| @events[id-1]}
1179
+ return events unless range
1180
+
1181
+ # 個別の絞込み
1182
+ narrowed_events = []
1183
+ sort_required = false
1184
+ events.each do |event|
1185
+ event_range = event.role[VALID]
1186
+ if event_range.kind_of?(Range) && event_range.is_complex?
1187
+ event_range.overlaped(range).each do |focused_date|
1188
+ sort_required = true
1189
+ focused_event = event.deep_copy
1190
+ focused_event.role[VALID] = focused_date
1191
+ narrowed_events << focused_event
1192
+ end
1193
+ else
1194
+ narrowed_events << event
1195
+ end
1196
+ end
1197
+ narrowed_events.sort_by! {|event| event.role[VALID]} if sort_required
1198
+ narrowed_events
1199
+ end
1200
+
1201
+ #
1202
+ # 順序キーによる検索
1203
+ #
1204
+ # @param [String] edge 順序キー(ts:start, ts:until, ts:west, ts:east, ts:south, ts:north, ts:bottom, ts:top)
1205
+ # @param [Range] range 絞り込む範囲
1206
+ # @param [Symbol] method スカラー化メソッドの指定 :to_i - 整数変数, :to_f - 実数変数
1207
+ #
1208
+ # @return [Array<Integer>] イベントのIDの配列
1209
+ #
1210
+ def edge_included(edge, range, method=:to_i)
1211
+ _edge_included(edge, Range.new(*range_args(range, method)))
1212
+ end
1213
+
1214
+ #
1215
+ # 範囲の重なり
1216
+ #
1217
+ # @param [String] first_edge 順序キー(ts:start, ts:west, ts:south, ts:bottom)
1218
+ # @param [String] last_edge 順序キー(ts:until, ts:east, ts:north, ts:top)
1219
+ # @param [Range] range 絞り込む範囲
1220
+ # @param [Symbol] method スカラー化メソッドの指定 :to_i - 整数変数, :to_f - 実数変数
1221
+ #
1222
+ # @return [Array<Integer>] イベントのIDの配列
1223
+ #
1224
+ def range_overlaped(first_edge, last_edge, range, method=:to_i)
1225
+ args = range_args(range, method)
1226
+ upper = ::Range.new(args[0], +Float::MAX/4, false)
1227
+ lower = ::Range.new(-Float::MAX/4, args[1], args[2])
1228
+ ([first_edge, last_edge].map {|edge| [@order[edge].first, @order[edge].last]}.flatten.uniq.select {|id|
1229
+ lower.include?(@events[id-1].role[first_edge]) && upper.include?(@events[id-1].role[last_edge])
1230
+ } | (
1231
+ @order[last_edge][_start_edge(last_edge, upper, 0, @events.size-1)..-1] &
1232
+ @order[first_edge][0..._ended_edge(first_edge, lower, 0, @events.size-1)]
1233
+ )).sort_by {|id|
1234
+ @events[id-1].role[first_edge]
1235
+ }
1236
+ end
1237
+
1238
+ #
1239
+ # 範囲生成用引数の準備
1240
+ #
1241
+ def range_args(range, method)
1242
+ range.exclude_end? ?
1243
+ [range.first.send(method), range.last.send(method), true] :
1244
+ range.last.respond_to?(:succ) ?
1245
+ [range.first.send(method), range.last.succ.send(method), true] :
1246
+ [range.first.send(method), range.last.send(method), false]
1247
+ end
1248
+ private :range_args
1249
+
1250
+ # プレフィクスを名前空間に展開する
1251
+ #
1252
+ # @param [String] predicate プレフィクス付きの URI
1253
+ #
1254
+ # @return [String] プレフィクスを名前空間に展開した URI
1255
+ #
1256
+ def extract(predicate)
1257
+ return predicate unless /\A(.+?):(.+)\z/ =~ predicate && @prefix.key?($1)
1258
+ prefix, body = $~[1..2]
1259
+ namespace = @prefix[prefix].first
1260
+ namespace = namespace.sub(/([a-z0-9])\z/i, '\1#')
1261
+ namespace + body
1262
+ end
1263
+
1264
+ #
1265
+ # 一言語対応データセットを生成する
1266
+ #
1267
+ # @param [Array<Array<Array<String>, String>>] definitions 定義行の情報
1268
+ # @param [String] language 言語コード
1269
+ # @param [String] uri 定義の所在のルート(ts:referenceが相対位置の場合に使用)
1270
+ # @param [When::Events::DataSets] parent 所属する多言語対応データセット
1271
+ # @param [Block] block キャッシュされたファイルパスの読み替え処理
1272
+ #
1273
+ def initialize(definitions, language='', uri='', parent=nil, &block)
1274
+ @language = language
1275
+ @parent = parent
1276
+ @prefix = {}
1277
+ @role = {}
1278
+ @rdf = {}
1279
+ @csv = {}
1280
+ @l_to_i = {}
1281
+ @i_to_l = {}
1282
+ @role_for_dataset = {}
1283
+ @definitions = definitions
1284
+ @definitions.each do |definition, lang|
1285
+ parameters = definition[1..2].map {|item| item.strip}
1286
+ case definition.first
1287
+ when /\A(.+):\z/
1288
+ @prefix[$1] = parameters
1289
+ when /\A\[(.+)\]\z/
1290
+ key=extract($1)
1291
+ @csv[key ] = operation(key, parameters, lang)
1292
+ when /\A<(.+)>\z/
1293
+ key=extract($1)
1294
+ @rdf[key ] = operation(key, parameters, lang)
1295
+ when /\A\{(.+)\}\z/
1296
+ key=extract($1)
1297
+ (DataSet.for_dataset?(parameters.first, key) ? @role_for_dataset : @role)[key] = operation(key, parameters, lang)
1298
+ end
1299
+ end
1300
+ @prefix_description = @prefix.values.reject {|value| value.size < 2}.sort_by {|value| -value.first.length}
1301
+ @namespace_to_prefix = @prefix.invert.sort.reverse
1302
+ @used_ns = {}
1303
+ @resource = Hash.new {|hash,key|
1304
+ @namespace_to_prefix.each do |prefix|
1305
+ index = key.index(prefix.first.first)
1306
+ if index && index == 0
1307
+ @used_ns[prefix.last] = prefix.first.first
1308
+ break
1309
+ end
1310
+ end
1311
+ hash[key] = ::RDF::URI.new(key)
1312
+ }
1313
+
1314
+ @events = []
1315
+ @index = {}
1316
+ (@role.keys + @rdf.keys + @csv.keys).each {|item| @index[item] = Hash.new {|hash,key| hash[key]=[]}}
1317
+ target = @role_for_dataset[REFERENCE][:target]
1318
+ unless target =~ /:/ # Relative path
1319
+ path = uri.split('/')
1320
+ path[-1] = target
1321
+ target = path.join('/')
1322
+ end
1323
+ operation = @role_for_dataset[REFERENCE][:original]
1324
+ source = extract(target)
1325
+ source = yield(source) if block_given? && operation !~ /SPARQL|CalendarEra/i
1326
+ raise IOError, target + ': not ready' unless source
1327
+ for_each_record(source, operation) do |row|
1328
+ event = Event.new(self, @events.size+1, row)
1329
+ @events << event
1330
+
1331
+ @role.keys.each do |item|
1332
+ case item
1333
+ when LABEL, REFERENCE
1334
+ when HAS_PART
1335
+ if event.role[HAS_PART].kind_of?(Array)
1336
+ event.role[HAS_PART].each do |word|
1337
+ @index[HAS_PART][word] << @events.size
1338
+ end
1339
+ else
1340
+ event.each_word do |word|
1341
+ @index[HAS_PART][word] << @events.size
1342
+ end
1343
+ end
1344
+ when WHAT_DAY
1345
+ date = event.role[WHAT_DAY]
1346
+ key = [date.class.to_s !~ /\AWhen/ ||
1347
+ date.frame.kind_of?(When::CalendarTypes::Christian),
1348
+ date.month * 1, date.day]
1349
+ @index[WHAT_DAY][key] << @events.size
1350
+ else
1351
+ add_index(:role, item)
1352
+ end
1353
+ end
1354
+
1355
+ [:rdf, :csv].each do |method|
1356
+ send(method).keys.each do |item|
1357
+ add_index(method, item)
1358
+ end
1359
+ end
1360
+ end
1361
+
1362
+ @order = {}
1363
+ [:role, :rdf, :csv].each do |method|
1364
+ send(method).each_pair do |item, definition|
1365
+ next unless definition[:index] == :order
1366
+ @order[item] = (1..@events.size).to_a.sort_by {|id| @events[id-1].send(method)[item]}
1367
+ @order[item].each_with_index do |id, index|
1368
+ @events[id-1].order[item] = index + 1
1369
+ end
1370
+ end
1371
+ end
1372
+ @index.each_value do |hash|
1373
+ hash.each_value do |value|
1374
+ value.uniq!
1375
+ value.sort_by! {|id| @events[id-1].order[START]} if @order.key?(START)
1376
+ end
1377
+ end
1378
+
1379
+ @default_graph = $1 if @role.key?(SUBJECT) && /\A(.+)<(.+)>\z/ =~ extract(@role[SUBJECT][:target])
1380
+ @default_graph ||= parent.iri if parent
1381
+ end
1382
+
1383
+ private
1384
+
1385
+ # 定義行一行を解釈する
1386
+ def operation(key, definition, lang='')
1387
+ edge = When::Events.edge(key)
1388
+ if definition[1]
1389
+ type = nil
1390
+ definition[1].split(/\s+/).each do |predicate|
1391
+ if /\A(.+?):(.*)\z/ =~ predicate && @prefix.key?($1)
1392
+ body = $2
1393
+ namespace = @prefix[$1].first
1394
+ namespace = namespace.sub(/([a-z0-9])\z/i, '\1#')
1395
+ type = namespace + body
1396
+ end
1397
+ break
1398
+ end
1399
+ end
1400
+ type ||= (When.const_defined?(:Parts) ? RANGE : ::Date) if key == VALID
1401
+ operation = if /\A\s*strptime\((.+)\)\s*\z/ =~ definition[1]
1402
+ fmt = $1
1403
+ proc {|date| When.strptime(date, fmt)}
1404
+ else
1405
+ Operations[edge ? [edge, type] : type]
1406
+ end
1407
+ {:target => definition[0].gsub(/<(.+?)>|\{(.+?)\}/) {|match|
1408
+ match[1..-2] = extract($1 || $2)
1409
+ match
1410
+ },
1411
+ :original => definition[1],
1412
+ :index => When::Events.equal(key) || (edge ? :order : ''),
1413
+ :type => type,
1414
+ :operation => operation,
1415
+ :copied => /\A<[^>]+>\z/ =~ definition[0] && operation.equal?(Operations.default),
1416
+ :lang => lang
1417
+ }
1418
+ end
1419
+
1420
+ # データセット用の定義かイベント用の定義かの判断
1421
+ def self.for_dataset?(target, key=nil)
1422
+ return false if key && !ForDataset.include?(key)
1423
+ /\[.+?\]|<.+?>|\{.+?\}/ !~ target
1424
+ end
1425
+
1426
+ # イベントを生成・登録
1427
+ def for_each_record(source, operation='csv with header', &block)
1428
+ case operation
1429
+ when /(out|no)\s*header\s*(\((\d+)\))?/i
1430
+ @limit = $3.to_i if $3
1431
+ csv_row_vs_label([])
1432
+ open(source,'r') do |io|
1433
+ CSV.parse(io.read) do |row|
1434
+ yield(row)
1435
+ break if @limit && @events.size >= @limit
1436
+ end
1437
+ end
1438
+ when /header\s*(\((\d+)\))?/i
1439
+ @limit = $2.to_i if $2
1440
+ open(source,'r') do |io|
1441
+ CSV.parse(io.read) do |row|
1442
+ if @row_vs_label
1443
+ yield(row)
1444
+ else
1445
+ csv_row_vs_label(row)
1446
+ end
1447
+ break if @limit && @events.size >= @limit
1448
+ end
1449
+ end
1450
+ when /SPARQL/i
1451
+ extend SparqlRepository
1452
+ initialize_repository(source, &block)
1453
+ when /RDF/i
1454
+ extend ExternalRepository
1455
+ initialize_repository(source, &block)
1456
+ when /CalendarEra/i
1457
+ era = When.Resource(source)
1458
+ era = era.child.first unless era.child.empty?
1459
+ while (era)
1460
+ yield({VALID => When::Parts::GeometricComplex.new(era.first, era.last.indeterminated_position ?
1461
+ When.today+When::P6W : era.last),
1462
+ LABEL => era.label.translate(@language),
1463
+ REFERENCE => era.label.reference(@language)})
1464
+ era = era.succ
1465
+ end
1466
+ when /\A\/(.+)\/(\((\d+)\))?\z/
1467
+ @limit = $3.to_i if $3
1468
+ rexp = Regexp.compile($1)
1469
+ csv_row_vs_label([])
1470
+ open(source, 'r') do |file|
1471
+ file.read.gsub(/[\r\n]/,'').scan(rexp) do
1472
+ yield((1...$~.size).to_a.map {|i| $~[i]})
1473
+ end
1474
+ break if @limit && @events.size >= @limit
1475
+ end
1476
+ else
1477
+ raise ArgumentError, "Undefined operation: #{operation}"
1478
+ end
1479
+ end
1480
+
1481
+ # CSVのフィールド番号とフィールド名の対応を管理する
1482
+ def csv_row_vs_label(row)
1483
+ row.each_with_index do |label, index|
1484
+ @l_to_i[label] = index + 1
1485
+ @i_to_l[index + 1] = label
1486
+ end
1487
+
1488
+ [@rdf, @role, @csv].each do |element|
1489
+ element.each_value do |values|
1490
+ values[:target].scan(/\[(.+?)\]/) {verify_index_and_label($1)} if values[:target]
1491
+ end
1492
+ end
1493
+ @csv.keys.each do |key|
1494
+ verify_index_and_label(key)
1495
+ end
1496
+
1497
+ [@rdf, @role, @csv].each do |element|
1498
+ element.each_value do |values|
1499
+ values[:target].gsub!(/\[(.+?)\]/) {label_to_index($1)} if values[:target]
1500
+ end
1501
+ end
1502
+ csv_verified = {}
1503
+ @csv.each_pair do |item, value|
1504
+ next if value.empty?
1505
+ key = label_to_index(item)
1506
+ if @csv_varified.key?(key)
1507
+ raise ArgumentError, "Duplicated index and label: #{item}" unless value == csv_verified[key]
1508
+ else
1509
+ csv_verified[key] = value
1510
+ end
1511
+ end
1512
+ @csv = csv_verified
1513
+
1514
+ @i_to_l.each_pair do |index, label|
1515
+ next unless /:/ =~ label
1516
+ @rdf[label] ||= {:target => "[#{index}]",
1517
+ :original => '',
1518
+ :index => '',
1519
+ :operation => Operations.default}
1520
+ end
1521
+
1522
+ @row_vs_label = true
1523
+ end
1524
+
1525
+ # CSVのフィールド名→フィールド番号
1526
+ def label_to_index(item)
1527
+ case item
1528
+ when /\A\d+\z/
1529
+ "[#{item.to_i}]"
1530
+ when /\A[^:]+\z/, /\A[^\d:].*?:/
1531
+ raise ArgumentError, "Label undefined: #{item}" unless @l_to_i.key?(item)
1532
+ "[#{@l_to_i[item]}]"
1533
+ else
1534
+ index = verify_index_and_label(item)
1535
+ raise ArgumentError, "Irregal index format: #{item}" unless index
1536
+ "[#{index}]"
1537
+ end
1538
+ end
1539
+
1540
+ # CSVのフィールド番号とフィールド名を照合する
1541
+ def verify_index_and_label(item)
1542
+ return nil unless /\A(\d+?):(.+)\z/ =~ item
1543
+ index ,label = $~[1..2]
1544
+ index = index.to_i
1545
+ label = extact(label)
1546
+ if @i_to_l.key?(index) && @i_to_l[index] != label ||
1547
+ @l_to_i.key?(label) && @l_to_i[label] != index
1548
+ raise ArgumentError, "Duplicated index and label: #{item}"
1549
+ else
1550
+ @i_to_l[index] = label
1551
+ @l_to_i[label] = index
1552
+ end
1553
+ index
1554
+ end
1555
+
1556
+ # ロールを一致インデクス管理対象として登録する
1557
+ def add_index(method, item)
1558
+ return unless send(method)[item][:index] == :equal
1559
+ key = @events.last.send(method)[item]
1560
+ return unless key
1561
+ @index[item][key] << @events.size
1562
+ end
1563
+
1564
+ # 二分検索 - メイン
1565
+ def _edge_included(edge, range, from=0, to=@events.size-1)
1566
+ middle = (from + to) / 2
1567
+ if range.include?(@events[@order[edge][middle]-1].role[edge])
1568
+ if range.first <= @events[@order[edge].first-1].role[edge]
1569
+ start = 0
1570
+ else
1571
+ start = _start_edge(edge, range, 0, middle)
1572
+ end
1573
+ if range.exclude_end? && range.last >= @events[@order[edge].last-1].role[edge] ||
1574
+ !range.exclude_end? && range.last > @events[@order[edge].last-1].role[edge]
1575
+ ended = @events.size
1576
+ else
1577
+ ended = _ended_edge(edge, range, middle, @events.size-1)
1578
+ end
1579
+ return (start...ended).to_a.map {|i| @order[edge][i]}
1580
+ end
1581
+ return [] if from == to
1582
+ @events[@order[edge][middle]-1].role[edge] < range.first ?
1583
+ _edge_included(edge, range, middle+1, to) :
1584
+ _edge_included(edge, range, from, middle)
1585
+ end
1586
+
1587
+ # 二分検索 - 小さい方の境界
1588
+ def _start_edge(edge, range, from, to)
1589
+ return to if to - from <= 1
1590
+ middle = (from + to) / 2
1591
+ range.include?(@events[@order[edge][middle]-1].role[edge]) ?
1592
+ _start_edge(edge, range, from, middle) :
1593
+ _start_edge(edge, range, middle, to)
1594
+ end
1595
+
1596
+ # 二分検索 - 大きい方の境界
1597
+ def _ended_edge(edge, range, from, to)
1598
+ return to if to - from <= 1
1599
+ middle = (from + to) / 2
1600
+ range.include?(@events[@order[edge][middle]-1].role[edge]) ?
1601
+ _ended_edge(edge, range, middle, to) :
1602
+ _ended_edge(edge, range, from, middle)
1603
+ end
1604
+ end
1605
+
1606
+ #
1607
+ # 時間座標を持つイベント
1608
+ #
1609
+ class Event
1610
+
1611
+ #
1612
+ # 所属する一言語対応データセット
1613
+ #
1614
+ # @return [When::Events::DataSet]
1615
+ #
1616
+ attr_reader :dataset
1617
+
1618
+ #
1619
+ # 通し番号
1620
+ #
1621
+ # @return [Integer]
1622
+ #
1623
+ attr_reader :id
1624
+
1625
+ #
1626
+ # CSVの一行分
1627
+ #
1628
+ # @return [Array<String>]
1629
+ #
1630
+ attr_reader :row
1631
+
1632
+ #
1633
+ # ロール変数
1634
+ #
1635
+ # @return [Hash<String(prefix:name)=>Object>]
1636
+ #
1637
+ attr_accessor :role
1638
+ protected :role=
1639
+
1640
+ #
1641
+ # RDF変数
1642
+ #
1643
+ # @return [Hash<String(prefix:name)=>Object>]
1644
+ #
1645
+ attr_accessor :rdf
1646
+ protected :rdf=
1647
+
1648
+ #
1649
+ # CSV変数
1650
+ #
1651
+ # @return [Hash<String(番号)=>Object>]
1652
+ #
1653
+ attr_accessor :csv
1654
+ protected :csv=
1655
+
1656
+ #
1657
+ # 当該順序キーで何番目のイベントか
1658
+ #
1659
+ # @return [Hash<String(prefix:name)=>Array(Integer(id))>]
1660
+ #
1661
+ attr_reader :order
1662
+
1663
+ #
1664
+ # HAS_PART対象の文字列中の{}で囲まれた語に対して yield で指定された処理を行う
1665
+ #
1666
+ def each_word
1667
+ @role[HAS_PART].scan(/(\{+)(.*?)(\}+)/) do
1668
+ bra, word, cket = $~[1..3]
1669
+ next unless bra.length.odd? && cket.length.odd?
1670
+ yield(word)
1671
+ end
1672
+ end
1673
+
1674
+ #
1675
+ # 指定の情報を{}部分のマークアップ処理など行って整形して返す
1676
+ #
1677
+ # @param [String] item 返す情報の名称
1678
+ # @param [Symbol] method 返す情報が属するグループ(:role, :rdf, :csv)
1679
+ #
1680
+ # @return [String]
1681
+ #
1682
+ # @note ブロックを渡された場合、そのブロックに{}部分のマークアップを依頼する
1683
+ #
1684
+ def abstract(item=ABSTRACT, method=:role)
1685
+ send(method)[item].gsub(/(\{+)(.*?)(\}+)/) {
1686
+ bra, word, cket = $~[1..3]
1687
+ '{'*(bra.length/2) + (block_given? ? yield(word) : word) + '}'*(cket.length/2)
1688
+ }
1689
+ end
1690
+
1691
+ #
1692
+ # 指定の情報のIRIとその説明を返す
1693
+ #
1694
+ # @param [String] item 返す情報の名称
1695
+ # @param [Symbol] method 返す情報が属するグループ(:role, :rdf, :csv)
1696
+ #
1697
+ # @return [Array<String(IRI), String(説明)>]
1698
+ #
1699
+ def source(item=SOURCE, method=:role)
1700
+ iri = send(method)[item]
1701
+ return [nil, iri] unless /:\/\// =~ iri
1702
+ @dataset.prefix_description.each do |description|
1703
+ index = iri.index(description[0])
1704
+ return [iri, description[1]] if index && index == 0
1705
+ end
1706
+ [iri]
1707
+ end
1708
+
1709
+ #
1710
+ # イベントが属するグループ(@role[When::Events::GROUP])を返す
1711
+ #
1712
+ # @return [String]
1713
+ #
1714
+ def group
1715
+ @role[GROUP]
1716
+ end
1717
+
1718
+ #
1719
+ # 自身を主語とする RDF::Statement の Array を返す
1720
+ #
1721
+ # @return [Array<RDF::Statement>]
1722
+ #
1723
+ def statements
1724
+ unless @statements
1725
+ @statements = []
1726
+ raise ArgumentError, 'Role for rdf:subject not defined' unless @role.key?(SUBJECT)
1727
+ subject = @dataset.resource[@role[SUBJECT]]
1728
+ @role.each_pair do |predicate, object|
1729
+ case predicate
1730
+ when SUBJECT, ID, GRAPH, WHAT_DAY
1731
+ # Do nothing
1732
+ when HAS_PART
1733
+ if @role[HAS_PART].kind_of?(Array)
1734
+ words = @role[HAS_PART]
1735
+ else
1736
+ words = []
1737
+ each_word do |word|
1738
+ words << word
1739
+ end
1740
+ words.uniq!
1741
+ end
1742
+ words.each do |word|
1743
+ @statements << ::RDF::Statement(subject, @dataset.resource[predicate], word)
1744
+ end
1745
+ # when ABSTRACT
1746
+ # @statements << ::RDF::Statement(subject, @dataset.resource[predicate], abstract)
1747
+ else
1748
+ if [URI, IRI].include?(@dataset.role[predicate][:type])
1749
+ object = @dataset.resource[object]
1750
+ elsif object.respond_to?(:to_uri)
1751
+ object = object.to_uri
1752
+ elsif @dataset.role[predicate][:lang] != '' && object.kind_of?(String)
1753
+ object = ::RDF::Literal.new(object, {:language=>@dataset.role[predicate][:lang]})
1754
+ end
1755
+ @statements << ::RDF::Statement(subject, @dataset.resource[predicate], object)
1756
+ end
1757
+ end
1758
+ end
1759
+ @statements
1760
+ end
1761
+
1762
+ #
1763
+ # イベントの複製
1764
+ #
1765
+ # @return [When::Events::Event] コピー結果
1766
+ #
1767
+ def deep_copy
1768
+ result = self.dup
1769
+ result.csv = @csv.dup
1770
+ result.rdf = @rdf.dup
1771
+ result.role = @role.dup
1772
+ result
1773
+ end
1774
+
1775
+ #
1776
+ # イベントの生成
1777
+ #
1778
+ # @param [When::Events::DataSet] dataset 所属する一言語対応データセット
1779
+ # @param [Integer] id 通し番号
1780
+ # @param [Array<String>] row CSVの一行分
1781
+ # @param [Hash<String=>Object>] row RDFの1クエリ分
1782
+ #
1783
+ def initialize(dataset, id, row)
1784
+ @dataset = dataset
1785
+ @id = id
1786
+ @row = row
1787
+ @role = {}
1788
+ @rdf = {}
1789
+ @csv = {}
1790
+ @order = {}
1791
+
1792
+ case row
1793
+ when Hash
1794
+ @rdf = row
1795
+ when Array
1796
+ row.each_with_index do |item, index|
1797
+ @csv[(index+1).to_s] = item
1798
+ end
1799
+
1800
+ dataset.rdf.each_pair do |item, definition|
1801
+ fields = []
1802
+ format = definition[:target].gsub(/\[(\d+)\]/) {
1803
+ field = row[$1.to_i-1]
1804
+ field = escape(field, definition[:type])
1805
+ fields << field
1806
+ '%s'
1807
+ }
1808
+ @rdf[item] = definition[:operation].call(self, format == '%s' ? fields.first : format % fields)
1809
+ end
1810
+ end
1811
+
1812
+ @role[ID] = @rdf[ID] = id
1813
+ dataset.role.each_pair do |item, definition|
1814
+ fields = []
1815
+ format = definition[:target].gsub('%','%%').gsub(/\[(\d+)\]|<(.+?)>|\{(.+?)\}/) {|match|
1816
+ key = $1 || $2 || $3
1817
+ field = case match
1818
+ when /\A\[/ ; row[key.to_i-1]
1819
+ when /\A</ ; @rdf[key]
1820
+ when /\A\{/ ; @role[key]
1821
+ end
1822
+ field = escape(field, definition[:type])
1823
+ fields << field
1824
+ '%s'
1825
+ }
1826
+ pre_operation = DataSet::Operations[item]
1827
+ @role[item] =
1828
+ if pre_operation
1829
+ definition[:operation].call(self, pre_operation.call(self, format == '%s' ? fields.first : format % fields))
1830
+ else
1831
+ definition[:operation].call(self, format == '%s' ? fields.first : format % fields)
1832
+ end
1833
+ end
1834
+ end
1835
+
1836
+ private
1837
+
1838
+ # URI 仕様外の文字をエスケープする
1839
+ def escape(part, type)
1840
+ return part unless part.kind_of?(String)
1841
+ part = part.strip
1842
+ return part if /:\/\/|%/ =~ part
1843
+ case type
1844
+ when URI; CGI.escape(part.gsub(' ', '_')).gsub(/%2F/i,'/')
1845
+ when IRI; part.gsub(' ', '_')
1846
+ else part
1847
+ end
1848
+ end
1849
+ end
1850
+ end
1851
+ end