when_exe 0.4.2 → 0.5.0

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