when_exe 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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