uniprop 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +31 -0
- data/README.md +29 -0
- data/Rakefile +12 -0
- data/lib/resources/metadata.json +19899 -0
- data/lib/resources/settings.rb +120 -0
- data/lib/uniprop/consts.rb +31 -0
- data/lib/uniprop/downloader.rb +262 -0
- data/lib/uniprop/dsl.rb +53 -0
- data/lib/uniprop/efficient_elements.rb +40 -0
- data/lib/uniprop/errors.rb +31 -0
- data/lib/uniprop/inspects.rb +122 -0
- data/lib/uniprop/metadata_generator.rb +403 -0
- data/lib/uniprop/metadata_processor.rb +673 -0
- data/lib/uniprop/metadata_validator.rb +282 -0
- data/lib/uniprop/propdata.rb +293 -0
- data/lib/uniprop/unicode_elements.rb +998 -0
- data/lib/uniprop/unicode_manager.rb +277 -0
- data/lib/uniprop/unihanprop.rb +91 -0
- data/lib/uniprop/uniinteger.rb +16 -0
- data/lib/uniprop/unistring.rb +34 -0
- data/lib/uniprop/utils.rb +542 -0
- data/lib/uniprop/value_group.rb +276 -0
- data/lib/uniprop/version.rb +5 -0
- data/lib/uniprop.rb +29 -0
- data/sig/uniprop.rbs +4 -0
- data/uniprop.gemspec +39 -0
- metadata +75 -0
@@ -0,0 +1,998 @@
|
|
1
|
+
module UniProp
|
2
|
+
module Alias
|
3
|
+
attr_reader :longest_alias
|
4
|
+
|
5
|
+
# 文字列を正規化
|
6
|
+
# @param [String] str 正規化前の文字列
|
7
|
+
# @return [String] strを正規化したもの
|
8
|
+
def self.canonical(str)
|
9
|
+
str.gsub(/[-_ ]/, '').downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
# @note aliasはインスタンス化時にも追加可能だが、add_aliasを使用する事でも追加可能
|
13
|
+
# @param [*String] new_aliases 追加するalias(個数任意)
|
14
|
+
def initialize(*new_aliases)
|
15
|
+
new_aliases.each { add_alias _1 }
|
16
|
+
end
|
17
|
+
|
18
|
+
# aliasを追加
|
19
|
+
# @param [String] new_alias 追加するalias
|
20
|
+
# @note aliasはcanonicalを使用して正規化されて追加される
|
21
|
+
def add_alias(new_alias)
|
22
|
+
if new_alias.class == String
|
23
|
+
if new_alias.size > @longest_alias.to_s.size
|
24
|
+
@longest_alias = new_alias
|
25
|
+
end
|
26
|
+
aliases << Alias.canonical(new_alias)
|
27
|
+
uncanonicaled_aliases << new_alias
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# :nocov:
|
32
|
+
# @return [Array<String>] 追加済みの正規化済みのalias
|
33
|
+
def aliases
|
34
|
+
@aliases ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Array<String>] 追加済みのalias
|
38
|
+
def uncanonicaled_aliases
|
39
|
+
@uncanonicaled_aliases ||= []
|
40
|
+
end
|
41
|
+
# :nocov:
|
42
|
+
|
43
|
+
def has_alias?(alias_str)
|
44
|
+
return aliases.include?(Alias.canonical(alias_str))
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Boolean] self.aliasesとother.aliasesが完全に同じ場合にtrue
|
48
|
+
def ==(other)
|
49
|
+
aliases==other.aliases
|
50
|
+
end
|
51
|
+
|
52
|
+
# @private
|
53
|
+
def eql?(other); self==other end
|
54
|
+
|
55
|
+
# @private
|
56
|
+
def hash
|
57
|
+
aliases.sort.join.hash
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Property
|
62
|
+
attr_reader :property_value_type, :version
|
63
|
+
|
64
|
+
include Alias
|
65
|
+
|
66
|
+
def initialize(version, *new_aliases)
|
67
|
+
@version = version
|
68
|
+
super(*new_aliases)
|
69
|
+
end
|
70
|
+
|
71
|
+
def property_value_type=(type)
|
72
|
+
type = type.downcase
|
73
|
+
if (
|
74
|
+
type == "catalog" ||
|
75
|
+
type == "enumerated" ||
|
76
|
+
type == "binary" ||
|
77
|
+
type == "string" ||
|
78
|
+
type == "numeric" ||
|
79
|
+
type == "miscellaneous"
|
80
|
+
)
|
81
|
+
@property_value_type = type.to_sym
|
82
|
+
else
|
83
|
+
raise PropertyValueTypeNotExistsError.new(type)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# settings.rbのmiscellaneous_formats内のformat_typeを小文字のシンボルで取得。記述されていない場合はnil
|
88
|
+
# @return [Symbol?]
|
89
|
+
def miscellaneous_format
|
90
|
+
version.property_to_miscellaneous_formats.dig(self, :format_type)&.downcase&.to_sym
|
91
|
+
end
|
92
|
+
|
93
|
+
# settings.rbのmiscellaneous_formats内のunique_thresholdを取得。記述されていない場合はnil
|
94
|
+
# @return [Integer?/Float?]
|
95
|
+
def unique_threshold
|
96
|
+
version.property_to_miscellaneous_formats.dig(self, :unique_threshold)
|
97
|
+
end
|
98
|
+
|
99
|
+
def ==(other)
|
100
|
+
# プロパティのエイリアスはバージョン更新時に増えることがあるため、versionの異なるPropertyを比較する場合には、プロパティが増えていてもtrueを返す
|
101
|
+
if version > other.version
|
102
|
+
return (other.aliases-aliases).empty?
|
103
|
+
elsif version < other.version
|
104
|
+
return (aliases-other.aliases).empty?
|
105
|
+
else
|
106
|
+
aliases==other.aliases
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [Array<PropertyValue>]
|
111
|
+
def property_values
|
112
|
+
version.property_to_property_values[self]
|
113
|
+
end
|
114
|
+
|
115
|
+
# :nocov:
|
116
|
+
# property_value_aliasをエイリアスに持つPropertyValueをproperty_valuesに持つか判定
|
117
|
+
# @param [String] property_value_alias
|
118
|
+
def has_property_value?(property_value_alias)
|
119
|
+
!!find_property_value(property_value_alias)
|
120
|
+
rescue
|
121
|
+
false
|
122
|
+
end
|
123
|
+
# :nocov:
|
124
|
+
|
125
|
+
# property_value_aliasをエイリアスに持つPropertyValueをproperty_valuesの中から取得
|
126
|
+
# @param [String] property_value_alias
|
127
|
+
# @return [PropertyValue]
|
128
|
+
# @raise [PropertyValueNotFoundError] 該当するPropertyValueが存在しない場合に発生
|
129
|
+
def find_property_value(property_value_alias)
|
130
|
+
pv = property_values.find { _1.has_alias?(property_value_alias)}
|
131
|
+
|
132
|
+
if pv
|
133
|
+
return pv
|
134
|
+
else
|
135
|
+
raise(PropertyValueNotFoundError, "#{longest_alias} doesn't have #{property_value_alias} as value.")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# :nocov:
|
140
|
+
# versionに含まれるPropFileの中の、このプロパティが含まれる場所を取得
|
141
|
+
# @return [Array<Position>]
|
142
|
+
def actual_positions
|
143
|
+
@actual_positions ||= version.version_metadata.property_to_actual_positions[self]
|
144
|
+
end
|
145
|
+
# :nocov:
|
146
|
+
|
147
|
+
# propfile中でプロパティが含まれる列を取得
|
148
|
+
# @note プロパティがpropfileに含まれない場合、空の配列を返す
|
149
|
+
# @param [PropFile] propfile
|
150
|
+
# @return [Array<Integer>]
|
151
|
+
def actual_columns(propfile)
|
152
|
+
columns = []
|
153
|
+
actual_positions.each { columns<<_1.column if _1.propfile==propfile }
|
154
|
+
columns
|
155
|
+
end
|
156
|
+
|
157
|
+
# Unihanのプロパティか判定
|
158
|
+
def is_unihan_property?
|
159
|
+
version.unihan_properties.include?(self)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class PropertyValue
|
164
|
+
include Alias
|
165
|
+
|
166
|
+
attr_accessor :property
|
167
|
+
def initialize(property, *new_aliases)
|
168
|
+
@property = property
|
169
|
+
new_aliases.each { add_alias _1 }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class PropFile
|
174
|
+
attr_accessor :version, :strip_regexp, :split_regexp, :basename_prefix
|
175
|
+
|
176
|
+
# @param [Pathname/String] path キャッシュのPathnameまたはbasename_prefixに相当するString
|
177
|
+
# @note fileをPathnameで指定する場合、絶対パスでの指定が必要
|
178
|
+
# @param [Regexp] strip_regexp 対応するファイル内で使われる空白文字の正規表現
|
179
|
+
# @param [Regexp] split_regexp 対応するファイル内で使われる区切り文字の正規表現
|
180
|
+
def initialize(path, version, strip_regexp: /\s+/, split_regexp: /;/)
|
181
|
+
if path.class==Pathname
|
182
|
+
@cache_path = path
|
183
|
+
@basename_prefix = UniPropUtils::FileManager.prefix(@cache_path)
|
184
|
+
else
|
185
|
+
@basename_prefix = path
|
186
|
+
end
|
187
|
+
|
188
|
+
# # strip_regexp, split_regexpが引数で指定されていない場合、settings.rbの記述を使用
|
189
|
+
# file_format = version.prop_data.find_settings_value(version.prop_data.unihan_files_information, "file_format", version.version_name)
|
190
|
+
|
191
|
+
# strip_regexp ||= file_format[:strip]
|
192
|
+
# split_regexp ||= file_format[:split]
|
193
|
+
|
194
|
+
@version = version
|
195
|
+
@strip_regexp = strip_regexp
|
196
|
+
@split_regexp = split_regexp
|
197
|
+
end
|
198
|
+
|
199
|
+
# @return [Pathname]
|
200
|
+
# @raise [FileNotFoundError] キャッシュが存在せず、ダウンロードにも失敗した場合に発生
|
201
|
+
def cache_path
|
202
|
+
return @cache_path if @cache_path
|
203
|
+
|
204
|
+
# キャッシュの中にbasename_prefixと同名のファイルがある場合、それを使用
|
205
|
+
if version.has_cache_file?(basename_prefix)
|
206
|
+
@cache_path = version.find_cache_file_path(basename_prefix)
|
207
|
+
return @cache_path
|
208
|
+
end
|
209
|
+
|
210
|
+
# キャッシュが保存されていない場合、ダウンロードを試みる
|
211
|
+
download_myself
|
212
|
+
if version.has_cache_file?(basename_prefix)
|
213
|
+
@cache_path = version.find_cache_file_path(basename_prefix)
|
214
|
+
return @cache_path
|
215
|
+
else
|
216
|
+
raise FileNotFoundError, "#{basename_prefix} does not exist in cache and download failed."
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# :nocov:
|
221
|
+
# versionの、basename_prefixに該当するファイル名のファイルをUnicode.orgからダウンロード
|
222
|
+
def download_myself
|
223
|
+
version.download_file(basename_prefix)
|
224
|
+
end
|
225
|
+
# :nocov:
|
226
|
+
|
227
|
+
def is_meta_file?() false end
|
228
|
+
|
229
|
+
def is_unihan_file?() false end
|
230
|
+
|
231
|
+
# ファイルコンテンツを改行で区切った配列を取得
|
232
|
+
# @return [Array]
|
233
|
+
def lines() @lines ||= cache_path.readlines.map(&:chomp) end
|
234
|
+
|
235
|
+
# ファイルコンテンツからコメントを削除したものを改行で区切った配列を取得
|
236
|
+
# @return [Array]
|
237
|
+
# @note コメントのみからなる行は空文字列に変換されるだけであり、要素は削除されない (行数がインデックスと対応)
|
238
|
+
def lines_without_comment
|
239
|
+
@lines_without_comment ||= lines.map { |l| l.gsub(/#.*/,'') }
|
240
|
+
end
|
241
|
+
|
242
|
+
# lines_without_commentのうち、空文字列となった要素を削除した配列を取得
|
243
|
+
# @return [Array]
|
244
|
+
def netto_lines
|
245
|
+
@netto_lines ||= lines_without_comment.reject { |l| l.match(/^\s*$/) }
|
246
|
+
end
|
247
|
+
|
248
|
+
# @param [PropFile/Pathname] other
|
249
|
+
def ==(other)
|
250
|
+
if other.class == self.class
|
251
|
+
# cache_pathで判定したほうが簡潔に書けるが、キャッシュにファイルが存在しない場合にも判定を行うため、このような実装にしてある
|
252
|
+
return version==other.version && basename_prefix==other.basename_prefix
|
253
|
+
elsif other.class == Pathname
|
254
|
+
return @cache_path==other
|
255
|
+
else
|
256
|
+
return false
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# lines_without_commentをstrip_regexpとsplit_regexpで処理した配列を取得
|
261
|
+
# @return [Array]
|
262
|
+
# @note strip_regexp==/\s+/ の場合であっても、各列の最初と最後の空白しか除去されない。「0000; 1111 2222; 3333;」の、1111と2222の間の空白は除去されない。
|
263
|
+
# def values
|
264
|
+
def shaped_lines
|
265
|
+
return @shaped_lines if @shaped_lines
|
266
|
+
|
267
|
+
@shaped_lines = []
|
268
|
+
|
269
|
+
# String#splitはlimit==0(デフォルト)の場合、配列末尾の空文字列が削除される
|
270
|
+
# それを防ぐため、limit==-1としてある。これはlimit<0にする事が目的であり、-1という値に意味は無い
|
271
|
+
if strip_regexp == /\s+/
|
272
|
+
lines_without_comment.each do |line|
|
273
|
+
@shaped_lines << line.split(sep=split_regexp, limit=-1)
|
274
|
+
.map { _1.gsub(/^\s+/, '') }
|
275
|
+
.map { _1.gsub(/\s+$/, '') }
|
276
|
+
end
|
277
|
+
else
|
278
|
+
lines_without_comment.each do |line|
|
279
|
+
@shaped_lines << line.split(sep=split_regexp, limit=-1)
|
280
|
+
.map { _1.gsub(strip_regexp, '') }
|
281
|
+
end
|
282
|
+
end
|
283
|
+
@shaped_lines
|
284
|
+
end
|
285
|
+
|
286
|
+
# @return [Array] valuesから空の配列を削除したArray
|
287
|
+
def netto_shaped_lines
|
288
|
+
@netto_shaped_lines ||= shaped_lines.reject { _1.empty? }
|
289
|
+
end
|
290
|
+
|
291
|
+
# 各列に含まれる値(codepointを含む)の配列の配列を取得。
|
292
|
+
# @note 返り値の配列のインデックスnは、n列目(最初の列を0列目とする)に含まれるすべての値を含む配列。
|
293
|
+
# @return [Array<Set<String>>]
|
294
|
+
def contents
|
295
|
+
return @contents if @contents
|
296
|
+
|
297
|
+
@contents = []
|
298
|
+
|
299
|
+
shaped_lines.each do |shaped_line|
|
300
|
+
shaped_line.each_with_index do |col_value, i|
|
301
|
+
@contents[i] ||= Set.new
|
302
|
+
@contents[i] << Alias.canonical(col_value)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# valuesでは区切り文字で区切られたそれぞれの部分を列とみなす。(1行に区切り文字がn個あれば、n+1列あるとみなされる)
|
307
|
+
# しかし実際には、最後の区切り文字の右側にコメントしか記述されない事が多いので、その場合は最終列を削除。
|
308
|
+
# contentsでは、最後の列に実際に値が無い場合(空orコメントのみの場合)には列とみなさない。
|
309
|
+
if @contents[-1].empty? || @contents[-1] == Set.new([""])
|
310
|
+
@contents = @contents[...-1]
|
311
|
+
end
|
312
|
+
|
313
|
+
@contents
|
314
|
+
end
|
315
|
+
|
316
|
+
# ファイル内に含まれるすべての値(codepointを含む)を取得。
|
317
|
+
# @return [Set<String>]
|
318
|
+
def values
|
319
|
+
@values ||= contents.reduce(Set.new, :merge)
|
320
|
+
end
|
321
|
+
|
322
|
+
# :nocov:
|
323
|
+
# 引数の列がユニーク列(行数に対し、記述されている値の割合が閾値以上の列。Nameプロパティなど、それぞれのcodepointが異なる値を取る傾向にあるプロパティが該当)であるかを判定
|
324
|
+
# @param [Integer] column
|
325
|
+
# @param [Float] unique_threshold
|
326
|
+
# @return [Boolean] ユニーク列であればtrue
|
327
|
+
def unique_column?(column, unique_threshold)
|
328
|
+
(contents[column].size.to_f / netto_lines.size) > unique_threshold
|
329
|
+
end
|
330
|
+
# :nocov:
|
331
|
+
|
332
|
+
# 引数の行・列の値を取得
|
333
|
+
# @return [String]
|
334
|
+
def value_at(row, column)
|
335
|
+
shaped_lines.dig(row, column)
|
336
|
+
end
|
337
|
+
|
338
|
+
# rowの中の1つ以上の列に、propのエイリアスが含まれるかを判定
|
339
|
+
# @param [Integer] row 検索する行の番号
|
340
|
+
# @param [Property] prop
|
341
|
+
def has_property_alias?(row, prop)
|
342
|
+
!!shaped_lines[row]&.any? { prop.has_alias?(_1) }
|
343
|
+
end
|
344
|
+
|
345
|
+
# propのエイリアスが含まれる行の範囲を取得
|
346
|
+
# @param [Property] prop
|
347
|
+
# @return [Array<Range>]
|
348
|
+
def property_alias_including_ranges(prop)
|
349
|
+
property_alias_including_rows = []
|
350
|
+
|
351
|
+
lines.size.times do |row|
|
352
|
+
property_alias_including_rows << row if has_property_alias?(row, prop)
|
353
|
+
end
|
354
|
+
|
355
|
+
UniPropUtils::RangeProcessor.array_to_ranges(property_alias_including_rows)
|
356
|
+
end
|
357
|
+
|
358
|
+
# rowがコメントのみから成る行かを判定
|
359
|
+
# @note 空行もコメント行とみなす
|
360
|
+
# @param [Integer] row
|
361
|
+
def comment?(row)
|
362
|
+
if 0 <= row && row <= lines.size-1
|
363
|
+
lines_without_comment[row].match?(/^\s*$/)
|
364
|
+
else
|
365
|
+
false
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# コメントのみから成る行の範囲を取得
|
370
|
+
# @return [Array<Range>]
|
371
|
+
def comment_ranges
|
372
|
+
return @comment_ranges if @comment_ranges
|
373
|
+
|
374
|
+
comment_rows = []
|
375
|
+
lines.size.times do |row|
|
376
|
+
comment_rows << row if comment?(row)
|
377
|
+
end
|
378
|
+
@comment_ranges = UniPropUtils::RangeProcessor.array_to_ranges(comment_rows)
|
379
|
+
|
380
|
+
@comment_ranges
|
381
|
+
end
|
382
|
+
|
383
|
+
# missing valueについて記述された行のみを取得
|
384
|
+
# @return [Array<String>]
|
385
|
+
def missing_value_lines
|
386
|
+
@missing_value_lines ||= lines.filter { _1.match?(/@missing/) }
|
387
|
+
end
|
388
|
+
|
389
|
+
# 空行・コメントのみ以外の行の範囲を取得
|
390
|
+
# @return [Array<Range>]
|
391
|
+
def information_containing_ranges
|
392
|
+
UniPropUtils::RangeProcessor.sub(Range.new(0, lines.size-1), comment_ranges)
|
393
|
+
end
|
394
|
+
|
395
|
+
# row, columnの値がprop.property_value_typeの型にマッチする値かを判定
|
396
|
+
# @param [Property] prop
|
397
|
+
# @param [Integer] row
|
398
|
+
# @param [Integer] column
|
399
|
+
# @return [Boolean]
|
400
|
+
# @note Miscellaneousプロパティの判定方法はsettings.rbで指定可能
|
401
|
+
def property_value_type_match?(row, column, prop)
|
402
|
+
if prop.property_value_type == :miscellaneous
|
403
|
+
return type_match?(row, column, prop.miscellaneous_format, prop)
|
404
|
+
else
|
405
|
+
return type_match?(row, column, prop.property_value_type, prop)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# row, columnの値がtypeの型にマッチする値かを判定
|
410
|
+
# @param [Integer] row
|
411
|
+
# @param [Integer] column
|
412
|
+
# @param [Symbol] type
|
413
|
+
# @param [Property] prop
|
414
|
+
# @return [Boolean]
|
415
|
+
def type_match?(row, column, type, prop)
|
416
|
+
# データファイルには、一番右の;の右側に情報が記述されるフォーマットと、コメントのみが記述されるフォーマットが存在
|
417
|
+
# row行目だけを取り出し、一番右側の列が値を持たない(value_atがnil)場合、たまたまrow行目に値が記述されていない(値が空文字列)だけか、ファイル全体として一番右の列に値が記述されていないのか、判定不可能
|
418
|
+
# そのため、ここでは値が存在しない列に対しては、空文字列を値として持つと仮定して判定を行う
|
419
|
+
value = value_at(row, column) || ""
|
420
|
+
|
421
|
+
case type
|
422
|
+
when :catalog, :enumerated
|
423
|
+
return UniPropUtils::TypeJudgementer.validate_enumerative(value, prop)
|
424
|
+
when :binary
|
425
|
+
return UniPropUtils::TypeJudgementer.validate_binary(value, prop)
|
426
|
+
when :string
|
427
|
+
return UniPropUtils::TypeJudgementer.validate_string(value)
|
428
|
+
when :numeric
|
429
|
+
return UniPropUtils::TypeJudgementer.validate_numeric(value)
|
430
|
+
when :jamo_short_name
|
431
|
+
# Jamo_Short_Nameは、プロパティ値のエイリアス1つか、空文字列(missing)を値として取る(15.0.0でコードポイント110Bの値が空文字列として明示的に記述されている)
|
432
|
+
return prop.property_values.any? { _1.has_alias?(value) } || value.empty?
|
433
|
+
when :script_extensions
|
434
|
+
# Script_Extensionsは1つ以上のScriptプロパティのプロパティ値を取る。2つ以上取る場合、ファイル内では半角スペース区切りで記述される。
|
435
|
+
return value.split.all? { version.find_property("Script").has_property_value?(_1) }
|
436
|
+
when :text
|
437
|
+
# 任意の文字列(空文字列も含む)である事を表すtextでは常にtrueを返す
|
438
|
+
return true
|
439
|
+
else
|
440
|
+
return false
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# column列目の中で、propが取りうる値の範囲を取得
|
445
|
+
# @param [Integer] column
|
446
|
+
# @param [Property] prop
|
447
|
+
# @return [Array<Range>]
|
448
|
+
def property_value_type_match_ranges(column, prop)
|
449
|
+
# miscellanesou_format==unqueの場合、ファイル内の全範囲をreturn
|
450
|
+
if prop.property_value_type==:miscellaneous && prop.miscellaneous_format==:unique
|
451
|
+
if unique_column?(column, prop.unique_threshold)
|
452
|
+
return information_containing_ranges
|
453
|
+
else
|
454
|
+
return []
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
# それ以外の場合、property_value_type_match? がtrueとなる行の範囲をreturn
|
459
|
+
property_value_including_rows = []
|
460
|
+
|
461
|
+
lines.size.times do |row|
|
462
|
+
if property_value_type_match?(row, column, prop)
|
463
|
+
property_value_including_rows << row
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
UniPropUtils::RangeProcessor.array_to_ranges(property_value_including_rows)
|
468
|
+
end
|
469
|
+
|
470
|
+
# row行目の列数を取得
|
471
|
+
# @param [Integer] row
|
472
|
+
# @return [Integer]
|
473
|
+
def column_size(row)
|
474
|
+
shaped_line = shaped_lines[row].to_a
|
475
|
+
shaped_line[-1]&.empty? ? shaped_line.size-1 : shaped_line.size
|
476
|
+
end
|
477
|
+
|
478
|
+
# rangeで指定された行の範囲内のうち、最大の列数を取得
|
479
|
+
# @param [Range<Integer>]
|
480
|
+
# @return [Integer]
|
481
|
+
def max_column_size(range)
|
482
|
+
range.map{column_size(_1)}.max
|
483
|
+
end
|
484
|
+
|
485
|
+
# property_value_type_match_rangesの最小値を下限、最大値を上限とする範囲の中で、空行、コメント行、column列目の値がpropの行のいずれかに該当する範囲を取得
|
486
|
+
# @return [Array<Range<Integer>>]
|
487
|
+
def verbose_property_value_type_match_ranges(column, prop)
|
488
|
+
prop_ranges = property_value_type_match_ranges(column, prop)
|
489
|
+
|
490
|
+
if prop_ranges.empty?
|
491
|
+
return prop_ranges
|
492
|
+
else
|
493
|
+
prop_begin_col = UniPropUtils::RangeProcessor.min(prop_ranges)
|
494
|
+
prop_end_col = UniPropUtils::RangeProcessor.max(prop_ranges)
|
495
|
+
|
496
|
+
return UniPropUtils::RangeProcessor.sum_up(
|
497
|
+
comment_ranges.map { UniPropUtils::RangeProcessor.cut_external(_1, prop_begin_col, prop_end_col) }.compact + prop_ranges
|
498
|
+
)
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
# 修正済みメタデータを参照し、ファイル内に含まれるプロパティを取得
|
503
|
+
# @return [Set<Property>]
|
504
|
+
def actual_properties
|
505
|
+
@actual_properties ||= version.version_metadata.propfile_to_actual_properties[self]
|
506
|
+
end
|
507
|
+
|
508
|
+
# @return [Array<Array<String>>]
|
509
|
+
def shaped_missing_value_lines
|
510
|
+
@shaped_missing_value_lines ||= missing_value_lines.map {
|
511
|
+
_1.gsub(/\s/, '')
|
512
|
+
.split(/;/)
|
513
|
+
}
|
514
|
+
end
|
515
|
+
|
516
|
+
# @return [PropFileValueGroup]
|
517
|
+
def propfile_value_group
|
518
|
+
@propfile_value_group ||= PropFileValueGroup.new(self)
|
519
|
+
end
|
520
|
+
|
521
|
+
def is_derived?
|
522
|
+
basename_prefix.start_with?(/Derived/)
|
523
|
+
end
|
524
|
+
|
525
|
+
class PropertyAliases < self
|
526
|
+
def is_meta_file?() true; end
|
527
|
+
|
528
|
+
# PropertyAliasesを解析し、タイプとプロパティの関係を取得
|
529
|
+
# @return [Hash<String, Set<Array<String>>>]
|
530
|
+
def property_value_type_to_shaped_lines
|
531
|
+
return @property_type_to_shaped_lines if @property_type_to_shaped_lines
|
532
|
+
|
533
|
+
@property_type_to_shaped_lines = Hash.new { |hash, key| hash[key]=Set.new }
|
534
|
+
mps = UniPropUtils::FileRegexp.matched_positions(cache_path.read, /#\s*={10,}\n#\s(.+)\sProperties\n#\s*={10,}/)
|
535
|
+
|
536
|
+
mps.size.times do |i|
|
537
|
+
mp = mps[i]
|
538
|
+
next_mp = mps[i+1]
|
539
|
+
|
540
|
+
begin_i = mp[:end_col] + 1
|
541
|
+
end_i = next_mp ? next_mp[:begin_col] : lines.size
|
542
|
+
|
543
|
+
property_type = mp[:match_data][1]
|
544
|
+
|
545
|
+
(begin_i...end_i).each { @property_type_to_shaped_lines[property_type] << shaped_lines[_1] if !shaped_lines[_1].empty?}
|
546
|
+
end
|
547
|
+
|
548
|
+
@property_type_to_shaped_lines
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
class PropertyValueAliases < self
|
553
|
+
def is_meta_file?() true; end
|
554
|
+
|
555
|
+
# このPropertyValueAliasesに含まれる、プロパティ値のエイリアスの一覧を取得。
|
556
|
+
# @return [Set]
|
557
|
+
def property_value_aliases
|
558
|
+
return @property_value_aliases if @property_value_aliases
|
559
|
+
|
560
|
+
@property_value_aliases = Set.new
|
561
|
+
|
562
|
+
contents[1..].each { @property_value_aliases.merge(_1) }
|
563
|
+
|
564
|
+
@property_value_aliases
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
class UnihanFile < self
|
569
|
+
def initialize(cache_path, version, strip_regexp: nil, split_regexp: nil)
|
570
|
+
if !strip_regexp || !split_regexp
|
571
|
+
# strip_regexp, split_regexpが引数で指定されていない場合、settings.rbの記述を使用
|
572
|
+
file_format = version.prop_data.settings.unihan_file_format(version.version_name)
|
573
|
+
|
574
|
+
strip_regexp ||= file_format[:strip]
|
575
|
+
split_regexp ||= file_format[:split]
|
576
|
+
end
|
577
|
+
super(cache_path, version, strip_regexp: strip_regexp, split_regexp: split_regexp)
|
578
|
+
end
|
579
|
+
|
580
|
+
# Unihanの場合はファイル名とパスが一致せず、Unihan.zipに記述されているため、Unihan.zipをダウンロード・展開
|
581
|
+
def download_myself
|
582
|
+
UniPropUtils::DownloaderWrapper.download_unihan(version.version_name, version.cache_path.parent)
|
583
|
+
UniPropUtils::FileManager.recursive_unzip(version.file_cache_paths)
|
584
|
+
end
|
585
|
+
|
586
|
+
def is_unihan_file?() true end
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
class Version
|
591
|
+
include Comparable
|
592
|
+
attr_accessor :directory, :cache_path
|
593
|
+
attr_reader :major, :minor, :tiny, :prop_data, :version_name, :unicode_beta, :excluded_extensions, :excluded_directories, :excluded_files, :included_files, :property_aliases_file_name, :property_value_aliases_file_name
|
594
|
+
|
595
|
+
def initialize(prop_data, version_name)
|
596
|
+
@prop_data = prop_data
|
597
|
+
@version_name = version_name
|
598
|
+
@major, @minor, @tiny = self.class.parse(@version_name).values
|
599
|
+
@cache_path = @prop_data.cache_path + Pathname.new(@version_name)
|
600
|
+
@unicode_beta = @prop_data.settings.unicode_beta(@version_name)
|
601
|
+
@excluded_extensions = @prop_data.settings.excluded_extensions(@version_name).map { _1.downcase }
|
602
|
+
@excluded_directories = @prop_data.settings.excluded_directories(@version_name).map { _1.downcase }
|
603
|
+
@excluded_files = @prop_data.settings.excluded_files(@version_name).map { _1.downcase }
|
604
|
+
@included_files = @prop_data.settings.included_files(@version_name).map { _1.downcase }
|
605
|
+
@property_aliases_file_name = "propertyaliases"
|
606
|
+
@property_value_aliases_file_name = "propertyvaluealiases"
|
607
|
+
end
|
608
|
+
|
609
|
+
# バージョン名を対応するx.y.z形式に変換する
|
610
|
+
# @param [String] version_name
|
611
|
+
# @return [Hash<Symbol,Integer>] Symbolはmajor,minor,tiny
|
612
|
+
def self.parse(version_name)
|
613
|
+
case version_name
|
614
|
+
when /^(\d+)\.(\d+)\.(\d+)$/, /^(\d+)\.(\d+)-Update(\d+)$/
|
615
|
+
return {major: $1.to_i, minor: $2.to_i, tiny: $3.to_i}
|
616
|
+
when /^(\d+)\.(\d+)-Update$/
|
617
|
+
return {major: $1.to_i, minor: $2.to_i, tiny: 0}
|
618
|
+
else
|
619
|
+
raise ParseError
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
# バージョン名からweightを算出
|
624
|
+
# @param [String] version_name
|
625
|
+
# @return [Integer]
|
626
|
+
def self.name_to_weight(version_name)
|
627
|
+
parsed_version_name = parse(version_name)
|
628
|
+
parsed_version_name[:major]*10000 + parsed_version_name[:minor]*100 + parsed_version_name[:tiny]
|
629
|
+
end
|
630
|
+
|
631
|
+
# Versionに含まれるファイルのうち、settings.rbの記述に一致するファイルを全件unicode.orgからダウンロード
|
632
|
+
def download_version_files(since: true)
|
633
|
+
UniPropUtils::DownloaderWrapper.download_version(version_name, cache_path.parent, excluded_extensions, excluded_directories, excluded_files, included_files, unicode_beta: unicode_beta, since: since)
|
634
|
+
end
|
635
|
+
|
636
|
+
# ファイル名を指定してversionのファイルをダウンロード
|
637
|
+
# @param [String] file_name Unicodeファイルのbasename_prefixに一致するファイル名
|
638
|
+
def download_file(file_name, since: true)
|
639
|
+
if UniPropUtils::FileManager.unihan_file?(file_name)
|
640
|
+
UniPropUtils::DownloaderWrapper.download_unihan(version_name, cache_path.parent, unicode_beta: unicode_beta, since: since)
|
641
|
+
UniPropUtils::FileManager.recursive_unzip(file_cache_paths)
|
642
|
+
else
|
643
|
+
UniPropUtils::DownloaderWrapper.unicode_basename_download(file_name, version_name, cache_path.parent, unicode_beta: unicode_beta, since: since)
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
# Versionに含まれるPropFile一覧を取得する
|
648
|
+
# @param [Boolean] reconfirm unicode.orgからファイルをダウンロードする処理は最初の1回のみ行われ、2回目以降はローカルキャッシュを参照するが、reconfirm==trueの場合、ローカルキャッシュの参照は行わず、再度unicode.orgからファイルをダウンロードする。
|
649
|
+
# @return [Set<PropFile>]
|
650
|
+
def files(reconfirm: false, since: true, reload: false)
|
651
|
+
if @files && !reconfirm && !reload
|
652
|
+
return @files
|
653
|
+
end
|
654
|
+
|
655
|
+
if !cache_path.exist? || reconfirm
|
656
|
+
download_version_files(since: since)
|
657
|
+
end
|
658
|
+
|
659
|
+
@files = cache_files(since: since, reload: reload)
|
660
|
+
@files
|
661
|
+
end
|
662
|
+
|
663
|
+
# キャッシュに保存されているファイルを取得
|
664
|
+
# @note キャッシュに該当するディレクトリが存在しない場合、空のSetが返る
|
665
|
+
# @param [Boolean] reconfirm unicode.orgにアクセスし、キャッシュのファイルを全て最新バージョンに更新する
|
666
|
+
# @param [Boolean] reload trueの場合、メモ化した値を使用せず、キャッシュを再読み込みする
|
667
|
+
# @return [Set<PropFile>]
|
668
|
+
def cache_files(reconfirm: false, since: true, reload: false)
|
669
|
+
return @cache_files if @cache_files && !reconfirm && !reload
|
670
|
+
|
671
|
+
# キャッシュを最新バージョンに更新
|
672
|
+
if reconfirm
|
673
|
+
cache_files(since: since).each { download_file(_1.basename_prefix, since: since)}
|
674
|
+
end
|
675
|
+
|
676
|
+
# Unihan.zipを展開
|
677
|
+
UniPropUtils::FileManager.recursive_unzip(file_cache_paths)
|
678
|
+
|
679
|
+
@cache_files = Set.new
|
680
|
+
|
681
|
+
file_cache_paths.each do |path|
|
682
|
+
# 4.1.0ではUnihan.zipの中のUnihan.txtと、そうでないUnihan.txtが存在し、ファイルの内容は同一
|
683
|
+
# そのような場合に対処するため、basename_prefixが同一のPropFileオブジェクトが既に作成されている場合、オブジェクトの作成を行わない
|
684
|
+
next if @cache_files.any? { _1.basename_prefix==UniPropUtils::FileManager.prefix(path) }
|
685
|
+
|
686
|
+
propfile = create_propfile(path)
|
687
|
+
@cache_files << propfile if propfile
|
688
|
+
end
|
689
|
+
|
690
|
+
@cache_files
|
691
|
+
end
|
692
|
+
|
693
|
+
# cache_pathに保存されているファイルのうち、settings.rbで使用する事にされているファイルのパスを取得
|
694
|
+
# @note プログラム実行中にキャッシュの内容は変更されるため、メモ化は行わず、都度探索を行う
|
695
|
+
# @return [Array<Pathname>]
|
696
|
+
def file_cache_paths
|
697
|
+
UniPropUtils::FileManager.filter_file(cache_path.glob('**/*'), excluded_extensions, excluded_directories, excluded_files, included_files)
|
698
|
+
end
|
699
|
+
|
700
|
+
# settings.rbの内容を考慮しながらPathnameオブジェクトからPropFileオブジェクトを作成
|
701
|
+
# @param [Pathname] file_path
|
702
|
+
# @return [PropFile]
|
703
|
+
def create_propfile(path)
|
704
|
+
return if UniPropUtils::FileManager.ext_no_dot(path).downcase != "txt"
|
705
|
+
|
706
|
+
# pathがPropertyAliases.txt, PropertyValueAliases.txtの場合、それらのクラスのインスタンスをreturn
|
707
|
+
if UniPropUtils::FileManager.prefix(path).downcase == property_aliases_file_name
|
708
|
+
return property_aliases_file
|
709
|
+
elsif UniPropUtils::FileManager.prefix(path).downcase == property_value_aliases_file_name
|
710
|
+
return property_value_aliases_file
|
711
|
+
|
712
|
+
# UnihanのファイルにはUnihanFileのインスタンスをreturn
|
713
|
+
elsif UniPropUtils::FileManager.unihan_file?(path, unihan_file_names)
|
714
|
+
return PropFile::UnihanFile.new(path, self)
|
715
|
+
else
|
716
|
+
return PropFile.new(path, self)
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
# file_nameに対応するファイルのキャッシュのパスを取得
|
721
|
+
# @param [String] file_name Unicodeファイルのprefixに一致するファイル名
|
722
|
+
# @return [Pathname] file_nameに対応するキャッシュのローカルのパス
|
723
|
+
# @raise [FileNotFoundError] ファイルがキャッシュに存在しない場合発生
|
724
|
+
def find_cache_file_path(file_name)
|
725
|
+
path = file_cache_paths.find { Alias.canonical(UniPropUtils::FileManager.prefix(_1)) == Alias.canonical(file_name) }
|
726
|
+
|
727
|
+
if path
|
728
|
+
return path
|
729
|
+
else
|
730
|
+
raise(FileNotFoundError, "#{file_name} has not yet been downloaded.")
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
# キャッシュにfile_nameが表すファイルが存在するかを判定
|
735
|
+
def has_cache_file?(file_name)
|
736
|
+
return !!find_cache_file_path(file_name)
|
737
|
+
rescue
|
738
|
+
false
|
739
|
+
end
|
740
|
+
|
741
|
+
# ファイル名/ファイルパスを指定してバージョン内のPropFileオブジェクトを取得
|
742
|
+
# @param [String/Pathname] propfile
|
743
|
+
# @param [Boolean] confirm trueの場合、ファイルが存在しない際にUnicode.orgからのダウンロードを試みる
|
744
|
+
# @raise [FileNotFoundError] ファイルが存在しない場合に発生
|
745
|
+
# @return [PropFile]
|
746
|
+
def find_file(propfile, confirm: true)
|
747
|
+
if propfile.class==String
|
748
|
+
file = files.find { |f| Alias.canonical(f.basename_prefix) == Alias.canonical(UniPropUtils::FileManager.prefix(propfile)) }
|
749
|
+
elsif propfile.class==Pathname
|
750
|
+
file = files.find { |f| f==propfile }
|
751
|
+
end
|
752
|
+
|
753
|
+
if file
|
754
|
+
return file
|
755
|
+
else
|
756
|
+
if confirm==true
|
757
|
+
if propfile.class==Pathname
|
758
|
+
propfile = propfile.basename
|
759
|
+
end
|
760
|
+
download_file(UniPropUtils::FileManager.prefix(propfile))
|
761
|
+
# ダウンロード後、キャッシュを再読み込みして再度検索を行う
|
762
|
+
files(reload: true)
|
763
|
+
return find_file(propfile, confirm: false)
|
764
|
+
end
|
765
|
+
|
766
|
+
raise(FileNotFoundError, "#{propfile} is not found.")
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
770
|
+
# @param [String/Pathname] propfile
|
771
|
+
def has_file?(propfile)
|
772
|
+
!!find_file(propfile)
|
773
|
+
rescue
|
774
|
+
false
|
775
|
+
end
|
776
|
+
|
777
|
+
# ProeprtyAliasesに該当するPropFileを取得
|
778
|
+
# @return [PropertyAliases]
|
779
|
+
def property_aliases_file
|
780
|
+
return @property_aliases_file if @property_aliases_file
|
781
|
+
|
782
|
+
if !has_cache_file?(property_aliases_file_name)
|
783
|
+
download_file(property_aliases_file_name)
|
784
|
+
end
|
785
|
+
|
786
|
+
property_aliases_file_path = find_cache_file_path(property_aliases_file_name)
|
787
|
+
|
788
|
+
if property_aliases_file_path
|
789
|
+
@property_aliases_file = PropFile::PropertyAliases.new(property_aliases_file_path, self)
|
790
|
+
end
|
791
|
+
|
792
|
+
@property_aliases_file
|
793
|
+
end
|
794
|
+
|
795
|
+
# ProeprtyValueAliasesに該当するPropFileを取得
|
796
|
+
# @return [PropertyValueAliases]
|
797
|
+
def property_value_aliases_file
|
798
|
+
return @property_value_aliases_file if @property_value_aliases_file
|
799
|
+
|
800
|
+
if !has_cache_file?(property_value_aliases_file_name)
|
801
|
+
download_file(property_value_aliases_file_name)
|
802
|
+
end
|
803
|
+
|
804
|
+
property_value_aliases_file_path = find_cache_file_path(property_value_aliases_file_name)
|
805
|
+
|
806
|
+
if property_value_aliases_file_path
|
807
|
+
@property_value_aliases_file = PropFile::PropertyAliases.new(property_value_aliases_file_path, self)
|
808
|
+
end
|
809
|
+
|
810
|
+
@property_value_aliases_file
|
811
|
+
end
|
812
|
+
|
813
|
+
# PropertyAliasesに記述されているProperty一覧を取得
|
814
|
+
# @note exclude_unihan==falseの場合であっても、PropertyAliasesに記述されていないUnihanのプロパティは取得されない
|
815
|
+
# @param [Boolean] exclude_unihan trueの場合、Unihanのプロパティを除外
|
816
|
+
# @return [Set<Property>]
|
817
|
+
def properties(exclude_unihan: false)
|
818
|
+
if !@properties
|
819
|
+
@properties = Set.new
|
820
|
+
|
821
|
+
# PropertyAliasesをもとに、全プロパティのPropertyオブジェクトを作成
|
822
|
+
property_aliases_file.property_value_type_to_shaped_lines.each do |property_value_type, shaped_lines|
|
823
|
+
shaped_lines.each do |shaped_line|
|
824
|
+
new_prop = Property.new(self, *shaped_line)
|
825
|
+
new_prop.property_value_type = property_value_type.downcase
|
826
|
+
@properties << new_prop
|
827
|
+
end
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
if exclude_unihan
|
832
|
+
return @properties - unihan_properties
|
833
|
+
else
|
834
|
+
return @properties + unihan_properties
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
def has_unihan?
|
839
|
+
unihan_files.size!=0
|
840
|
+
end
|
841
|
+
|
842
|
+
# @return [UnihanProp]
|
843
|
+
def unihanprop
|
844
|
+
@unihanprop ||= UnihanProp.new(unihan_files)
|
845
|
+
end
|
846
|
+
|
847
|
+
# return [Array<Property>]
|
848
|
+
def unihan_properties
|
849
|
+
unihanprop.unihan_properties
|
850
|
+
end
|
851
|
+
|
852
|
+
# @return [Hash<Property, Array<PropertyValue>>]
|
853
|
+
def property_to_property_values
|
854
|
+
return @property_to_property_values if @property_to_property_values
|
855
|
+
|
856
|
+
@property_to_property_values = Hash.new { |hash, key| hash[key]=[] }
|
857
|
+
|
858
|
+
property_value_aliases_file.netto_shaped_lines.each do |shaped_line|
|
859
|
+
prop = find_property(shaped_line[0])
|
860
|
+
@property_to_property_values[prop] << PropertyValue.new(prop, *shaped_line[1..])
|
861
|
+
end
|
862
|
+
|
863
|
+
@property_to_property_values
|
864
|
+
end
|
865
|
+
|
866
|
+
# Version内に存在する、property_nameをaliasとして持つPropertyオブジェクトを取得
|
867
|
+
# @param [String/Property] property
|
868
|
+
# @return [Property]
|
869
|
+
# @raise [PropertyNotFoundError] プロパティが存在しない場合に発生
|
870
|
+
def find_property(property)
|
871
|
+
if property.class==String
|
872
|
+
prop = properties.find { _1.has_alias?(property) }
|
873
|
+
elsif property.class==Property
|
874
|
+
# エイリアス名が長いほど、正しい答えを得られる可能性が高い
|
875
|
+
# エイリアス名が短いほど、複数のプロパティが同じエイリアス名を持っている可能性が高い
|
876
|
+
property.aliases.sort_by{ _1.size }.reverse_each do |prop_alias|
|
877
|
+
return find_property(prop_alias) if has_property?(prop_alias)
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
if prop
|
882
|
+
return prop
|
883
|
+
else
|
884
|
+
raise PropertyNotFoundError.new(property)
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
# @param [Property/String] prop
|
889
|
+
def has_property?(prop)
|
890
|
+
!!find_property(prop)
|
891
|
+
rescue
|
892
|
+
false
|
893
|
+
end
|
894
|
+
|
895
|
+
# @param [String] property_name
|
896
|
+
# @param [String/Property] property
|
897
|
+
# @return [Property]
|
898
|
+
# @raise [PropertyNotFoundError] プロパティが存在しない場合に発生
|
899
|
+
def find_unihan_property(property)
|
900
|
+
if property.class==String
|
901
|
+
prop = unihan_properties.find { _1.has_alias?(property) }
|
902
|
+
elsif property.class==Property
|
903
|
+
# エイリアス名が長いほど、正しい答えを得られる可能性が高い
|
904
|
+
# エイリアス名が短いほど、複数のプロパティが同じエイリアス名を持っている可能性が高い
|
905
|
+
property.aliases.sort_by{ _1.size }.reverse_each do |prop_alias|
|
906
|
+
return find_unihan_property(prop_alias) if has_unihan_property?(prop_alias)
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
if prop
|
911
|
+
return prop
|
912
|
+
else
|
913
|
+
raise PropertyNotFoundError.new(property)
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
917
|
+
# @param [String/Property] property
|
918
|
+
def has_unihan_property?(property)
|
919
|
+
!!find_unihan_property(property)
|
920
|
+
rescue
|
921
|
+
false
|
922
|
+
end
|
923
|
+
|
924
|
+
# contentを対応するPropertyオブジェクトに変換して返す。対応するPropertyオブジェクトが無い場合にはnilを返す。contentがArrayの場合、再帰的に変換を実行。
|
925
|
+
# @param [String/Array<String>] content
|
926
|
+
# @return [Property?/Array<Property?>]
|
927
|
+
def convert_property(content)
|
928
|
+
if content.class==Array
|
929
|
+
return content.map { convert_property(_1) }
|
930
|
+
else
|
931
|
+
return find_property(content) rescue nil
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
# @return [Array<Property>] Versionに含まれるUnihanファイル
|
936
|
+
def unihan_files
|
937
|
+
@unihan_files ||= files.filter { _1.is_unihan_file? }
|
938
|
+
end
|
939
|
+
|
940
|
+
# @return [Array<String>?] settings.rbに記述されているUnihanファイル名
|
941
|
+
def unihan_file_names
|
942
|
+
return @unihan_file_names if @unihan_file_names
|
943
|
+
|
944
|
+
# キャッシュにUnihanファイルが無い場合、ダウンロードを試みる
|
945
|
+
if file_cache_paths.all? { !UniPropUtils::FileManager.unihan_file?(_1) }
|
946
|
+
begin
|
947
|
+
UniPropUtils::DownloaderWrapper.download_unihan(version_name, cache_path.parent)
|
948
|
+
UniPropUtils::FileManager.recursive_unzip(file_cache_paths)
|
949
|
+
rescue FileNotFoundError
|
950
|
+
# Unicode.orgの対象バージョンにもUnihan.zip, Unihan.txtが存在しない場合(FileNotFoundError)は処理を継続
|
951
|
+
# downloader.rbに関する例外などはrescueしない
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
@unihan_file_names = Set.new
|
956
|
+
file_cache_paths.each { @unihan_file_names << UniPropUtils::FileManager.prefix(_1) if UniPropUtils::FileManager.unihan_file?(_1) }
|
957
|
+
@unihan_file_names = @unihan_file_names.to_a
|
958
|
+
@unihan_file_names
|
959
|
+
end
|
960
|
+
|
961
|
+
# @return [VersionMetadata]
|
962
|
+
# @raise [MetadataNotFoundError] Versionに対応するメタデータが存在しない場合に発生
|
963
|
+
def version_metadata
|
964
|
+
@version_metadata ||= VersionMetaData.new(self, prop_data.metadata.find_raw_version_metadata(version_name))
|
965
|
+
end
|
966
|
+
|
967
|
+
def has_version_metadata?
|
968
|
+
!!version_metadata
|
969
|
+
rescue
|
970
|
+
false
|
971
|
+
end
|
972
|
+
|
973
|
+
# settings.rbのPROPERTIES_INFORMATIONのmiscellaneous_formatsを、Propertyオブジェクトをキーとして整理する
|
974
|
+
# @note settings.rbに定義が無いプロパティをキーに指定すると、空のハッシュを返す
|
975
|
+
# @return [Hash<Property,Hash<Symbol,String>>]
|
976
|
+
def property_to_miscellaneous_formats
|
977
|
+
return @property_to_miscellaneous_formats if @property_to_miscellaneous_formats
|
978
|
+
@property_to_miscellaneous_formats = Hash.new { |hash,key| hash[key]={} }
|
979
|
+
|
980
|
+
properties.each do |prop|
|
981
|
+
prop.uncanonicaled_aliases.each do |als|
|
982
|
+
fmt = prop_data.settings.miscellaneous_format(version_name, als)
|
983
|
+
|
984
|
+
if fmt
|
985
|
+
@property_to_miscellaneous_formats[prop] = fmt
|
986
|
+
break
|
987
|
+
end
|
988
|
+
end
|
989
|
+
end
|
990
|
+
|
991
|
+
@property_to_miscellaneous_formats
|
992
|
+
end
|
993
|
+
|
994
|
+
def <=>(other) weight <=> other.weight end
|
995
|
+
|
996
|
+
def weight() major*10000 + minor*100 + tiny end
|
997
|
+
end
|
998
|
+
end
|