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