when_exe 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
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