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,403 @@
|
|
1
|
+
module UniProp
|
2
|
+
class PropData
|
3
|
+
# メタデータを生成する
|
4
|
+
# @param [Pathname] output_path メタデータを生成するパス
|
5
|
+
# @param [Version] generated_version 作成するメタデータのバージョン
|
6
|
+
# @param [Version/EfficientVersion] using_version generated_versionのメタデータの生成に使用されるバージョン
|
7
|
+
# @raise [FileExistsError] pathに既にファイルが存在している場合に発生。生成されたメタデータを修正した後に再度メソッドを実行してしまい、メタデータが上書きされる事を防ぐための措置。
|
8
|
+
# @raise [MetaDataExistsError] generated_versionのメタデータが既に存在する場合に発生
|
9
|
+
def generate_metadata(output_path, using_version, generated_version)
|
10
|
+
if output_path.exist?
|
11
|
+
raise FileExistsError.new(output_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
generated_metadata = {}
|
15
|
+
generated_metadata["version_names"] = metadata.version_names
|
16
|
+
generated_metadata["version_metadatas"] = metadata.raw_version_metadatas
|
17
|
+
generated_metadata["version_metadatas"] << generate_version_metadata(using_version, generated_version)
|
18
|
+
|
19
|
+
generated_metadata["version_metadatas"].sort_by! { Version.name_to_weight(_1["version_name"]) }
|
20
|
+
|
21
|
+
output_path.write(JSON.pretty_generate(generated_metadata))
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Hash<String,Object>] version_metadataに相当するHash
|
25
|
+
def generate_version_metadata(using_version, generated_version)
|
26
|
+
if using_version.prop_data != generated_version.prop_data
|
27
|
+
raise PropDataDifferentError, "Unable to recreate metadata because the PropData objects of two versions passed when initialized are different."
|
28
|
+
end
|
29
|
+
|
30
|
+
prop_data = using_version.prop_data
|
31
|
+
|
32
|
+
if prop_data.metadata.has_raw_version_metadata?(generated_version.version_name)
|
33
|
+
raise MetaDataExistsError.new(generated_version.version_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
recreator = VersionMetaDataRecreator.new(using_version, generated_version)
|
37
|
+
|
38
|
+
version_metadata = {}
|
39
|
+
version_metadata["version_name"] = generated_version.version_name
|
40
|
+
version_metadata["file_formats"] = recreator.generate_file_formats
|
41
|
+
|
42
|
+
if generated_version.has_unihan?
|
43
|
+
version_metadata["unihan_files"] = generated_version.unihanprop.unihan_files.map { _1.basename_prefix }
|
44
|
+
version_metadata["unihan_properties"] = generated_version.unihanprop.unihan_properties.map { _1.longest_alias }
|
45
|
+
end
|
46
|
+
|
47
|
+
version_metadata
|
48
|
+
end
|
49
|
+
|
50
|
+
# PropDataオブジェクト作成時に渡したメタデータを使用し、プロパティ中心のメタデータを生成
|
51
|
+
# @param [EfficientVersion] version 生成するバージョン
|
52
|
+
# @param [Pathname] output_path メタデータを生成するパス
|
53
|
+
def generate_property_metadata(output_path, version)
|
54
|
+
if property_metadata.has_raw_version_metadata?(version.version_name)
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
md = property_metadata.raw_version_metadatas
|
59
|
+
|
60
|
+
if metadata.has_raw_version_metadata?(version.version_name)
|
61
|
+
puts "generating property metadata for #{version.version_name} ... "
|
62
|
+
md << generate_vsn_property_metadata(version)
|
63
|
+
else
|
64
|
+
raise MetaDataNotFoundError, "metadata for #{version.version_name} is not found."
|
65
|
+
end
|
66
|
+
|
67
|
+
output_path.write(JSON.pretty_generate(md))
|
68
|
+
end
|
69
|
+
|
70
|
+
# バージョン内のすべてのプロパティに対し、プロパティ中心のメタデータを生成
|
71
|
+
# @param [EfficientVersion] version
|
72
|
+
# @return [Hash<String,Object>] versionのプロパティ中心のメタデータ
|
73
|
+
def generate_vsn_property_metadata(version)
|
74
|
+
version_property_metadata = {}
|
75
|
+
version_property_metadata["version_name"] = version.version_name
|
76
|
+
version_property_metadata["properties"] = version.properties.map { generate_prop_property_metadata(version, _1) }
|
77
|
+
version_property_metadata
|
78
|
+
end
|
79
|
+
|
80
|
+
# プロパティに関するメタデータを生成
|
81
|
+
# @param [EfficientVersion] version
|
82
|
+
# @param [Property] property version内のプロパティ
|
83
|
+
# @return [Hash<String,Object>] プロパティに関するメタデータ
|
84
|
+
def generate_prop_property_metadata(version, property)
|
85
|
+
property_metadata = {}
|
86
|
+
property_metadata["property_name"] = property.longest_alias
|
87
|
+
|
88
|
+
property_metadata["positions"] = []
|
89
|
+
positions = property.actual_positions.to_a
|
90
|
+
|
91
|
+
positions.each do |position|
|
92
|
+
property_metadata["positions"] << {
|
93
|
+
"file_name" => position.propfile.basename_prefix,
|
94
|
+
"block" => position.block,
|
95
|
+
"range" => position.range.to_s,
|
96
|
+
"columns" => position.columns.size==1 ? position.columns[0] : position.columns
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
property_metadata["unihan"] = property.is_unihan_property?
|
101
|
+
property_metadata["type"] = property.property_value_type.to_s
|
102
|
+
property_metadata["derived"] = positions.any? { _1.propfile.is_derived? }
|
103
|
+
|
104
|
+
property_metadata
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class VersionMetaDataRecreator
|
109
|
+
attr_reader :using_version, :using_version_metadata, :generated_version
|
110
|
+
# using_versionのメタデータを使用してgenerated_versionのメタデータを作成するためのオブジェクトを生成
|
111
|
+
# @param [Version] using_version
|
112
|
+
# @param [Version/EfficientVersion] generated_version
|
113
|
+
def initialize(using_version, generated_version)
|
114
|
+
@using_version = using_version
|
115
|
+
@using_version_metadata = using_version.version_metadata
|
116
|
+
@generated_version = generated_version
|
117
|
+
end
|
118
|
+
|
119
|
+
# メタデータのfile_formats項を生成
|
120
|
+
# @return [Array<Hash<String,Object>>]
|
121
|
+
def generate_file_formats
|
122
|
+
return @file_formats if @file_formats
|
123
|
+
@file_formats = []
|
124
|
+
|
125
|
+
using_version.files.each_with_index do |file, i|
|
126
|
+
puts "recreating metadata for #{file.basename_prefix} (#{i+1}/#{using_version.files.size})"
|
127
|
+
|
128
|
+
next if !using_version_metadata.has_propfile_metadata?(file)
|
129
|
+
next if !generated_version.has_file?(file.basename_prefix)
|
130
|
+
|
131
|
+
@file_formats << { "file_name"=>file.basename_prefix, "blocks"=>generate_blocks(file) }
|
132
|
+
end
|
133
|
+
|
134
|
+
@file_formats
|
135
|
+
end
|
136
|
+
|
137
|
+
# メタデータのblocks項を生成
|
138
|
+
# @param [PropFile] using_file
|
139
|
+
def generate_blocks(using_file)
|
140
|
+
using_filename = using_file.basename_prefix
|
141
|
+
generated_file = generated_version.find_file(using_filename)
|
142
|
+
using_file_metadata = using_version_metadata.find_propfile_metadata(using_file)
|
143
|
+
|
144
|
+
block_generator = BlockGenerator.new(using_file, generated_file, using_file_metadata)
|
145
|
+
|
146
|
+
result_blocks = []
|
147
|
+
|
148
|
+
block_generator.generate_raw_blocks.each do |raw_block|
|
149
|
+
result_block = {}
|
150
|
+
result_block["content"] = raw_block.content
|
151
|
+
result_block["range"] = raw_block.range
|
152
|
+
result_blocks << result_block
|
153
|
+
end
|
154
|
+
|
155
|
+
result_blocks
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# BlockGenerator で使用するStruct
|
160
|
+
# @param [Symbol] type propertyのproperty_value_type
|
161
|
+
# @param [Property] property
|
162
|
+
Format = Struct.new(:type, :property)
|
163
|
+
|
164
|
+
# 既存のメタデータを使用し、メタデータ未知のPropFileに関するblocksを作成するクラス
|
165
|
+
class BlockGenerator
|
166
|
+
attr_reader :using_file, :generated_file, :using_file_metadata, :using_version_metadata, :generated_version
|
167
|
+
|
168
|
+
# @param [PropFile] using_file
|
169
|
+
# @param [PropFile] generated_file
|
170
|
+
# @param [PropFileMetaData] using_file_metadata using_fileのPropFileMetaData
|
171
|
+
def initialize(using_file, generated_file, using_file_metadata)
|
172
|
+
@using_file = using_file
|
173
|
+
@generated_file = generated_file
|
174
|
+
@generated_version = generated_file.version
|
175
|
+
@using_file_metadata = using_file_metadata
|
176
|
+
@using_version_metadata = using_file.version.version_metadata
|
177
|
+
end
|
178
|
+
|
179
|
+
# generated_fileのblocksに相当するArray<RawBlock>を作成
|
180
|
+
# @return [Array<RawBlock>]
|
181
|
+
def generate_raw_blocks
|
182
|
+
return @raw_blocks if @raw_blocks
|
183
|
+
@raw_blocks = []
|
184
|
+
|
185
|
+
using_file_metadata.blocks.size.times do |block_no|
|
186
|
+
range = matched_ranges[block_no]
|
187
|
+
|
188
|
+
if range
|
189
|
+
content = using_file_metadata.raw_blocks[block_no].content
|
190
|
+
@raw_blocks << RawBlock.new(content, range.to_s)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
@raw_blocks
|
195
|
+
end
|
196
|
+
|
197
|
+
# using_fileのblocksのフォーマットを取得
|
198
|
+
# @return [Array<Array<Object>>]
|
199
|
+
# @note 返り値のn番目の要素はblocks内のn番目のblockのフォーマットに対応
|
200
|
+
def block_format_types
|
201
|
+
@block_format_types ||= using_file_metadata.blocks.map { block_format_type(_1.content) }
|
202
|
+
end
|
203
|
+
|
204
|
+
# using_fileに、binary,enumerated,catalog以外の型のプロパティを含むブロックが2つ以上存在するか判定
|
205
|
+
# @return [Boolean] 存在する場合true
|
206
|
+
def has_multiple_free_value_block?
|
207
|
+
return @multiple_free_value_block_f if @multiple_free_value_block_f
|
208
|
+
|
209
|
+
free_value_exist_f = []
|
210
|
+
using_file_metadata.blocks.each do |block|
|
211
|
+
block_properties = block.content.reject { _1.class == Array }
|
212
|
+
.compact
|
213
|
+
|
214
|
+
free_value_exist_f << !block_properties.all? { _1.property_value_type==:binary ||
|
215
|
+
_1.property_value_type==:enumerated ||
|
216
|
+
_1.property_value_type==:catalog }
|
217
|
+
end
|
218
|
+
|
219
|
+
@multiple_free_value_block_f = free_value_exist_f.filter { _1 }.size > 1
|
220
|
+
@multiple_free_value_block_f
|
221
|
+
end
|
222
|
+
|
223
|
+
# blockの再生成に使用するフォーマットとして最も適切なものを取得
|
224
|
+
# @param [Array<Property?>/Array<Array<Property?>>]] Block.contentの値
|
225
|
+
# @return [Array<Object>]
|
226
|
+
def block_format_type(content)
|
227
|
+
content.map { column_format_type(_1) }
|
228
|
+
end
|
229
|
+
|
230
|
+
def column_format_type(column)
|
231
|
+
# columnがArrayの場合、その列には固有の表記法が使用されており、一様に判定を行う事はできないため、:text を返す
|
232
|
+
return Format.new(:text, column) if column.class == Array
|
233
|
+
|
234
|
+
# columnがnilの場合、nil (その列には判定を行わない)
|
235
|
+
return nil if !column
|
236
|
+
|
237
|
+
if has_multiple_free_value_block?
|
238
|
+
# 列挙型以外のプロパティを取るブロックが2つ以上存在する場合、
|
239
|
+
# コードポイントと値の対応を調べないと、同じプロパティに関する記述かの判定が不可能
|
240
|
+
return column
|
241
|
+
else
|
242
|
+
# 列挙型以外のプロパティを取るブロックが1つだけの場合、
|
243
|
+
# 記述されている値の型を調べるだけで、同じプロパティに関する記述かの判定が可能
|
244
|
+
type = (column.property_value_type==:miscellaneous) ? column.miscellaneous_format : column.property_value_type
|
245
|
+
|
246
|
+
# type==:uniqueの場合、判定時には:textと同様に扱いたいため、:textに変更
|
247
|
+
type = :text if type==:unique
|
248
|
+
|
249
|
+
return Format.new(type, column)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# generated_fileの各行が、何番目のブロックのblock_format_typeにマッチするか判定
|
254
|
+
# @return [Array<Integer?>] m行目がblocks内のn番目のblockのフォーマットに一致する場合、m番目の要素はn。どのブロックにもマッチしない場合、nil。
|
255
|
+
def lines_format
|
256
|
+
return @lines_format if @lines_format
|
257
|
+
@lines_format = []
|
258
|
+
|
259
|
+
generated_file.lines.size.times do |row|
|
260
|
+
# n行目がコメントの場合、line_formatによる判定は行わずnilを追加
|
261
|
+
if generated_file.comment?(row)
|
262
|
+
@lines_format << nil
|
263
|
+
next
|
264
|
+
end
|
265
|
+
@lines_format << line_format(row)
|
266
|
+
end
|
267
|
+
|
268
|
+
@lines_format
|
269
|
+
end
|
270
|
+
|
271
|
+
# @param [Integer] row
|
272
|
+
# @return [Array<Integer>] n番目のblockのフォーマットに一致する場合、n。どのブロックにもマッチしない場合、nil。
|
273
|
+
def line_format(row)
|
274
|
+
matched_blocks = []
|
275
|
+
using_file_metadata.blocks.size.times do |block_no|
|
276
|
+
codepoint_col_no = using_file_metadata.codepoint_column_nos[block_no]
|
277
|
+
|
278
|
+
matched_blocks << block_no if match_format?(row, codepoint_col_no, block_no)
|
279
|
+
end
|
280
|
+
|
281
|
+
if matched_blocks.size == 1
|
282
|
+
# line_formatの結果のサイズが1の場合
|
283
|
+
# row行目がマッチするブロックが一意に絞れているため、結果として使用
|
284
|
+
return matched_blocks[0]
|
285
|
+
else
|
286
|
+
# row行目がマッチするブロックが一意に絞れていない場合
|
287
|
+
# この場合、ある行がどのプロパティについて記述されているか、データファイルに記述されている場合が多い
|
288
|
+
# そのため、row行目の中に含まれるプロパティ名で判定
|
289
|
+
|
290
|
+
prop_including_blocks = []
|
291
|
+
using_file_metadata.blocks.size.times do |block_no|
|
292
|
+
using_file_metadata.blocks[block_no].content.compact.each do |prop|
|
293
|
+
next if prop.class == Array
|
294
|
+
prop_including_blocks << block_no if generated_file.has_property_alias?(row, prop)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
if prop_including_blocks.size == 1
|
299
|
+
return prop_including_blocks[0]
|
300
|
+
else
|
301
|
+
# プロパティ名を使用しても一意に絞れない場合、nil
|
302
|
+
return nil
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# row行目がformat_typeの形式に一致するか判定
|
308
|
+
# @param [Integer] row
|
309
|
+
# @param [Integer] codepoint_column_no
|
310
|
+
# @param [Integer] block_no
|
311
|
+
def match_format?(row, codepoint_column_no, block_no)
|
312
|
+
shaped_line = generated_file.shaped_lines[row]
|
313
|
+
format_type = block_format_types[block_no]
|
314
|
+
|
315
|
+
# row行目の中にcol_formatと異なるフォーマットの列が存在する場合、その時点でfalseを返す
|
316
|
+
format_type.each_with_index do |col_format, col_no|
|
317
|
+
next if !col_format # formatがnilの場合には判定を行わない
|
318
|
+
|
319
|
+
if col_format.class == Format
|
320
|
+
if [:enumerated, :catalog, :binary].include?(col_format.type) && generated_version.has_property?(col_format.property)
|
321
|
+
# 列挙型のプロパティ(enumerated, catalog, binary)の場合、 prop.version == generated_version でないと、propに新しいバージョンで追加された値が含まれず、プロパティの範囲の正確な推測が不可能
|
322
|
+
prop = generated_version.find_property(col_format.property)
|
323
|
+
else
|
324
|
+
prop = col_format.property
|
325
|
+
end
|
326
|
+
|
327
|
+
if generated_file.type_match?(row, col_no, col_format.type, prop)
|
328
|
+
next
|
329
|
+
else
|
330
|
+
return false
|
331
|
+
end
|
332
|
+
|
333
|
+
else
|
334
|
+
# col_format.class==Property の場合、型での判定はできない
|
335
|
+
# generated_fileのrow行目のcodepointがusing_fileで取っている値が、generated_fileと同じであるかを判定する
|
336
|
+
bvg = using_version_metadata.find_propfile_metadata(using_file).block_value_group(block_no)
|
337
|
+
shaped_line_dup = shaped_line.dup
|
338
|
+
codepoint = shaped_line_dup.delete_at(codepoint_column_no)
|
339
|
+
|
340
|
+
# codepointがnnnn..mmmm形式の場合、最初のnnnnのみを比較に使用
|
341
|
+
# nnnn..mmmmの範囲では同じ値を持つため、nnnnのみを使用すれば十分
|
342
|
+
codepoint = codepoint.split("..")[0]
|
343
|
+
generated_file_value = generated_file.value_at(row, col_no)
|
344
|
+
using_file_value = bvg.values_of(codepoint)
|
345
|
+
|
346
|
+
if using_file_value.class==String && using_file_value==generated_file_value ||
|
347
|
+
using_file_value.class==Array && using_file_value.include?(generated_file_value)
|
348
|
+
next
|
349
|
+
else
|
350
|
+
return false
|
351
|
+
end
|
352
|
+
|
353
|
+
end
|
354
|
+
end
|
355
|
+
true
|
356
|
+
end
|
357
|
+
|
358
|
+
# 各ブロックがgenerated_fileにおいてマッチした行数の範囲を取得
|
359
|
+
# @return [Array<Range<Integer>>]
|
360
|
+
# @note n番目の要素はn番目のブロックがマッチした範囲
|
361
|
+
def matched_ranges
|
362
|
+
return @matched_ranges if @matched_ranges
|
363
|
+
|
364
|
+
block_no_to_ranges = Hash.new { |hash,key| hash[key]=[] }
|
365
|
+
|
366
|
+
measured_block_no = nil # 範囲を計測中のblock_no
|
367
|
+
start_idx = nil # measured_block_noが最初に現れたidx
|
368
|
+
end_idx = nil # measured_block_noが最後に現れたidx
|
369
|
+
pre_idx = nil # 前回のblock_no(nil以外のInteger)が現れたidx
|
370
|
+
|
371
|
+
lines_format_dup = lines_format.dup
|
372
|
+
lines_format_dup << Float::NAN # 最後の要素まで処理を行うため、最後にNANを入れておく
|
373
|
+
|
374
|
+
lines_format_dup.each_with_index do |block_no, i|
|
375
|
+
next if !block_no # i行目がコメント行などの場合
|
376
|
+
|
377
|
+
if block_no != measured_block_no
|
378
|
+
# 前回までのmeasured_block_noの計測を終了
|
379
|
+
end_idx = pre_idx
|
380
|
+
if measured_block_no && start_idx && end_idx
|
381
|
+
block_no_to_ranges[measured_block_no] << Range.new(start_idx, end_idx)
|
382
|
+
end
|
383
|
+
|
384
|
+
# measured_block_noをblock_noに切り替え
|
385
|
+
measured_block_no = block_no
|
386
|
+
start_idx = i
|
387
|
+
end
|
388
|
+
|
389
|
+
pre_idx = i
|
390
|
+
end
|
391
|
+
|
392
|
+
# 同一ブロックが2つ以上に分かれた範囲に記述されていると判定されている場合、
|
393
|
+
# 最初の範囲だけをn番目のブロックの範囲として採用
|
394
|
+
# その場合、誤ったメタデータが生成されることになるが、検証メソッドにより検出され、ユーザに修正される
|
395
|
+
@matched_ranges = []
|
396
|
+
using_file_metadata.blocks.size.times do |block_no|
|
397
|
+
@matched_ranges[block_no] = block_no_to_ranges[block_no][0]
|
398
|
+
end
|
399
|
+
|
400
|
+
@matched_ranges
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|