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