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.
@@ -0,0 +1,277 @@
1
+ module UniProp
2
+ class UnicodeManager
3
+ attr_reader :prop_data
4
+
5
+ def initialize(prop_data)
6
+ @prop_data = prop_data
7
+ end
8
+
9
+ # @param [String] version バージョン名
10
+ # @return [VersionManager]
11
+ def version_manager(version)
12
+ prop_data.version_manager(version)
13
+ end
14
+
15
+ # version_nameのメタデータを検証
16
+ # @param [String] version_name
17
+ def validate_metadata(version_name)
18
+ prop_data.find_version(version_name).version_metadata.version_metadata_validator.run_all_validations
19
+ end
20
+
21
+ # initialize時に指定したメタデータを使用し、メタデータを作成
22
+ # @param [Pathname] file_path メタデータを生成するパス
23
+ # @param [String] using_version_name 生成に使用するバージョン名
24
+ # @param [String] generated_version_name 生成するバージョン名
25
+ def generate_metadata(file_path, using_version_name, generated_version_name)
26
+ prop_data.generate_metadata(
27
+ file_path,
28
+ prop_data.find_efficient_version(using_version_name),
29
+ prop_data.find_version(generated_version_name)
30
+ )
31
+ end
32
+
33
+ # メタデータに含まれる全バージョンの全プロパティ名を取得
34
+ # @return [Array<String>]
35
+ def properties
36
+ @properties ||= prop_data.version_managers
37
+ .map { _1.properties }
38
+ .reduce([], :|)
39
+ end
40
+
41
+ # codepointでpropertyのプロパティ値としてvalueが定義されているバージョン名をすべて取得
42
+ # @param [String] property プロパティ名
43
+ # @param [String] char 検索する1文字
44
+ # @param [String] value プロパティ値
45
+ def versions_of(property, char, value)
46
+ warn "Versions prior to 4.0-Update are not included in the results because the metadata does not exist."
47
+ prop_data.version_managers
48
+ .filter { _1.has_value?(property, char.ord, value)}
49
+ .map { _1.version.version_name }
50
+ end
51
+
52
+ # text_changed_codepointsの処理に加え、エイリアスの判定を実行し、値が変更されたコードポイントを取得
53
+ # @param [String] property
54
+ # @param [String] version1
55
+ # @param [String] version2
56
+ # @return [Array<Integer>]
57
+ def value_changed_codepoints(property, version1, version2)
58
+ pm1 = version_manager(version1).property_manager(property)
59
+ pm2 = version_manager(version2).property_manager(property)
60
+ vm2 = version_manager(version2)
61
+
62
+ # 文字列は変更されているが、別エイリアスに表記が変更されただけのコードポイントを判別
63
+ # 例: "Y"のエイリアスとして"Yes", "T", "True"、"N"のエイリアスとして"No", "F", "False"が存在する時、
64
+ # ["Y", "N"] -> ["False", "True"]が表記の変更だけで、値は変わっていないことを検出する手順
65
+ # ["Y", "N"]の各値のエイリアスを求め、["Yes", "T", "True", "N", "No", "F", "False"]の配列を生成
66
+ # ["False", "True"]から["Yes", "T", "True", "N", "No", "F", "False"]を引き、結果がemptyであれば値の変更は無いと判別
67
+ result = []
68
+ text_changed_codepoints(property, version1, version2).each do |cp|
69
+ values1 = pm1.values_of(cp)
70
+ values2 = pm2.values_of(cp)
71
+
72
+ values1 = values1.class==Array ? values1 : [values1]
73
+ values2 = values2.class==Array ? values2 : [values2]
74
+
75
+ values1_aliases = values1.map { vm2.value_aliases(property, _1) }
76
+ .flatten
77
+
78
+ result << cp if !(values2-values1_aliases).empty?
79
+ end
80
+
81
+ result
82
+ end
83
+
84
+ # version1で値が定義済みのコードポイントのうち、version2では他の値が定義されているものを取得
85
+ # version2の方が新しい場合、version1から変更された値のみが取得される。version1の方が新しい場合、version2で追加された値も取得される。
86
+ # @note 単にデータファイルの文字が異なるコードポイントを取得するだけで、エイリアスの判定は行わない(例: Ageプロパティのプロパティ値5.0はエイリアスとしてV5_0を持つが、5.0とV5_0は別の値とみなす)
87
+ # @param [String] property
88
+ # @param [String] version1
89
+ # @param [String] version2
90
+ # @return [Array<Integer>]
91
+ def text_changed_codepoints(property, version1, version2)
92
+ pvg1 = version_manager(version1).property_manager(property).property_value_group
93
+ pvg2 = version_manager(version2).property_manager(property).property_value_group
94
+
95
+ result = []
96
+ # Blockプロパティのように、多くのコードポイントに同じ値が定義されるプロパティが存在
97
+ # そのため、すべてのコードポイントの値を比較するのではなく、一度値ごとのコードポイントをまとめた方が効率的
98
+ pvg1.values_to_codepoints.each do |values, cps1|
99
+ cps2 = pvg2.values_to_codepoints[values].to_a
100
+ diff_cps = cps1 - cps2
101
+ result << diff_cps if !diff_cps.empty?
102
+ end
103
+
104
+ result.flatten
105
+ end
106
+ end
107
+
108
+ class VersionManager
109
+ attr_reader :version
110
+
111
+ # versionに含まれる情報を取得するためのオブジェクトを作成
112
+ # @param [EfficientVersion] version
113
+ def initialize(version)
114
+ @version = version
115
+ end
116
+
117
+ # PropertyManagerオブジェクトを作成
118
+ # @param [String] property_name
119
+ # @return [PropertyManager]
120
+ def property_manager(property_name)
121
+ @property_managers ||= []
122
+ pm = @property_managers.find { _1.property.has_alias?(property_name) }
123
+ return pm if pm
124
+
125
+ pm = PropertyManager.new(version.find_property(property_name))
126
+ @property_managers << pm
127
+ pm
128
+ end
129
+
130
+ # propertyプロパティのcodepointのプロパティ値を取得
131
+ # @param [String] property 検索するプロパティ名
132
+ # @param [String] 1文字の文字列
133
+ # @return [String/Array<String>] プロパティ値が1つの場合String、2つ以上の場合Array<String>
134
+ def values_of(property, char)
135
+ property_manager(property).values_of(char.ord)
136
+ end
137
+
138
+ # propertyプロパティが値としてvalueを持つコードポイントを取得
139
+ # @param [String] property プロパティ名
140
+ # @param [String] value プロパティ値
141
+ # @return [Array<Integer>]
142
+ def codepoints_of(property, value)
143
+ property_manager(property).property_value_group.value_including_codepoints(value)
144
+ end
145
+
146
+ # propertyがcodepointでプロパティ値としてvalueを持つか判定
147
+ # @param [String] property プロパティ名
148
+ # @param [Integer] codepoint
149
+ # @param [String] value プロパティ値
150
+ def has_value?(property, codepoint, value)
151
+ codepoints_of(property, value).include?(codepoint)
152
+ end
153
+
154
+ # codepointでvalueを取るプロパティを取得
155
+ # @param [String] char 1文字の文字列
156
+ # @param [String] value プロパティ値
157
+ # @return [Array<String>] プロパティ名
158
+ def properties_of(char, value)
159
+ properties.filter { has_value?(_1, char.ord, value) }
160
+ end
161
+
162
+ # バージョン内の全プロパティ名を取得
163
+ # @return [Array<String>]
164
+ def properties
165
+ # VersionMetaData#property_namesにはUniHanのプロパティが含まれないため、EffcientVersion#propertiesから取得
166
+ @properties ||= version.properties.map { _1.longest_alias }
167
+ end
168
+
169
+ # propertyをエイリアスに持つプロパティがバージョン内に存在するか確認
170
+ # @param [String] property
171
+ def has_property?(property)
172
+ version.has_property?(property)
173
+ end
174
+
175
+ # propertyにデフォルト値以外の値が割り当てられているコードポイントを取得
176
+ # @param [String] property
177
+ # @return [Array<Integer>]
178
+ def assigned_codepoints(property)
179
+ property_manager(property).property_value_group.codepoints
180
+ end
181
+
182
+ # propertyのプロパティ値の中のvalueのエイリアスを全て取得
183
+ # @note propertyが値としてvalueを持たない場合や、プロパティが列挙型でない場合、空の配列が帰る
184
+ # @param [String] property プロパティ名
185
+ # @param [String] value プロパティ値
186
+ # @return [Array<String>]
187
+ def value_aliases(property, value)
188
+ return [] if !version.has_property?(property)
189
+ prop = version.find_property(property)
190
+
191
+ if prop.has_property_value?(value)
192
+ prop.find_property_value(value).uncanonicaled_aliases
193
+ else
194
+ []
195
+ end
196
+ end
197
+ end
198
+
199
+ class PropertyManager
200
+ attr_reader :property, :version_metadata, :property_value_group, :version
201
+
202
+ # propertyに関する情報を取得するためのオブジェクトを作成
203
+ # @note VersionまたはEfficientVersionはpropertyのProperty#versionが使用されるため、Version/EfficientVersionの使用したい方でPropertyオブジェクトを生成しpropertyとして使用する
204
+ # @param [Property] property
205
+ def initialize(property)
206
+ @property = property
207
+ @version = @property.version
208
+ @version_metadata = @version.version_metadata
209
+ @property_value_group = @version.prop_data.property_metadata.find_version_property_metadata(@version).find_property_data(@property).property_value_group
210
+ end
211
+
212
+ # @return [Set<String>]
213
+ def values_of(codepoint)
214
+ value = property_value_group.values_of(codepoint)
215
+
216
+ if !value || value.empty?
217
+ # 値が存在しない(nil)場合や、値が記述されていない(空文字列)の場合、missingを使用
218
+ missing_value(codepoint)
219
+ else
220
+ value
221
+ end
222
+ end
223
+
224
+ # codepointのmissingを取得
225
+ # @param [Integer/String] codepoint
226
+ # @return [String]
227
+ def missing_value(codepoint)
228
+ if codepoint.class==String
229
+ codepoint = UniPropUtils::CodepointConverter.str_to_int(codepoint)
230
+ end
231
+
232
+ raw_missing_val = raw_missing_value(codepoint)
233
+
234
+ case raw_missing_val
235
+ when "<codepoint>" then codepoint.to_s(16).upcase
236
+ when "<script>" then
237
+ if @script || version.has_property?("Script")
238
+ @script ||= PropertyManager.new(version.find_property("Script"))
239
+ return @script.missing_value(codepoint)
240
+ else
241
+ return raw_missing_val
242
+ end
243
+ else raw_missing_val
244
+ end
245
+ end
246
+
247
+ # codepointのmissingを取得。<codepoint>などの特殊な値もそのまま返される
248
+ # @param [Integer/String] codepoint
249
+ # @return [String]
250
+ def raw_missing_value(codepoint)
251
+ if codepoint.class==String
252
+ codepoint = UniPropUtils::CodepointConverter.str_to_int(codepoint)
253
+ end
254
+
255
+ if version_metadata.property_missing_defs(property)
256
+ version_metadata.property_missing_defs(property).reverse_each do |missing_def|
257
+ if missing_def[:codepoint_range].include?(codepoint)
258
+ return missing_def[:missing_value]
259
+ end
260
+ end
261
+ end
262
+ nil
263
+ end
264
+
265
+ # value1とvalue2が同じプロパティ値を表すエイリアスであるかを判定
266
+ # @param [String] value1
267
+ # @param [String] value2
268
+ def same_value?(value1, value2)
269
+ # Propertyがproperty_valuesを1つ以上持つとき、そのプロパティの値はPropertyValueAliasesに記述されており、エイリアスが存在する可能性がある
270
+ if property.property_values.size >= 1 && property.has_property_value?(value1) && property.has_property_value?(value2)
271
+ property.find_property_value(value1) == property.find_property_value(value2)
272
+ else
273
+ value1 == value2
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,91 @@
1
+ module UniProp
2
+ class UnihanProp
3
+ attr_reader :unihan_files, :version
4
+
5
+ # @param [Set<PropFile>] unihan_files UnihanのPropFileオブジェクト
6
+ def initialize(unihan_files)
7
+ @unihan_files = unihan_files
8
+
9
+ versions = @unihan_files.map { _1.version }.uniq
10
+ if versions.size <= 1
11
+ @version = versions[0]
12
+ else
13
+ raise VersionDifferentError, "All versions of files in unihan_files should be the same."
14
+ end
15
+ end
16
+
17
+ # unihan_filesに含まれるすべてのPropFileのshaped_linesを連結したオブジェクトを取得
18
+ # @return [Array<Array<String>>]
19
+ def shaped_lines
20
+ @shaped_lines ||= unihan_files.map { _1.shaped_lines }
21
+ .reject { _1.empty? || !_1 }
22
+ .reduce([], :+)
23
+ end
24
+
25
+ # Unihanに含まれるプロパティ名を取得
26
+ # @note Unihanのファイルは必ず2列目にプロパティ名が記述されている
27
+ # @return [Set<String>]
28
+ def property_names
29
+ @property_names ||= shaped_lines.map { _1[1] }
30
+ .reject { !_1 }
31
+ .to_set
32
+ end
33
+
34
+ # Unihanに含まれるプロパティを取得
35
+ # @return [Set<Property>]
36
+ def unihan_properties
37
+ return @unihan_properties if @unihan_properties
38
+
39
+ @unihan_properties = Set.new
40
+ return if !version
41
+
42
+ property_names.each do |property_name|
43
+ if version.has_property?(property_name)
44
+ # Unihanのプロパティの一部はPropertyAliases.txtにも記述されている
45
+ # PropertyAliasesの方がプロパティのエイリアスなど、掲載されている情報が多いため、PropertyAliasesの記述を優先して使用
46
+ @unihan_properties << version.find_property(property_name)
47
+ else
48
+ @unihan_properties << UniProp::Property.new(version, property_name)
49
+ end
50
+ end
51
+
52
+ @unihan_properties
53
+ end
54
+
55
+ # @return [Hash<String,Array<String>>]
56
+ def property_name_to_shaped_lines
57
+ return @property_nameto_shaped_lines if @property_name_to_shaped_lines
58
+ @property_name_to_shaped_lines = shaped_lines.group_by { _1[1] }
59
+ @property_name_to_shaped_lines.delete(nil)
60
+ @property_name_to_shaped_lines
61
+ end
62
+
63
+ # @return [Hash<Property,Array<String>>]
64
+ def property_to_shaped_lines
65
+ return @property_to_shaped_lines if @property_to_shaped_lines
66
+
67
+ @property_to_shaped_lines = Hash.new
68
+
69
+ property_name_to_shaped_lines.each do |property_name, lines|
70
+ if version.has_unihan_property?(property_name)
71
+ prop = version.find_unihan_property(property_name)
72
+ @property_to_shaped_lines[prop] = lines
73
+ end
74
+ end
75
+
76
+ @property_to_shaped_lines
77
+ end
78
+
79
+ # @return [UnihanValueGroup]
80
+ def unihan_value_group(property)
81
+ return @property_to_unihan_value_group[property] if @property_to_unihan_value_group && @property_to_unihan_value_group[property]
82
+
83
+ @property_to_unihan_value_group ||= {}
84
+
85
+ unihan_value_group = UnihanValueGroup.new(property, property_to_shaped_lines[property])
86
+
87
+ @property_to_unihan_value_group[property] = unihan_value_group
88
+ @property_to_unihan_value_group[property]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,16 @@
1
+ module UniInteger
2
+ refine Integer do
3
+ # selfをU+xxxx形式の文字列に変換
4
+ def to_cp
5
+ "U+%04X" % self
6
+ end
7
+ end
8
+
9
+ refine Range do
10
+ def to_cp
11
+ if first.class==Integer && last.class==Integer
12
+ "#{"U+%04X" % first}..#{"%04X" % last}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ module UniString
2
+ class PropSearch
3
+ attr_reader :codepoint
4
+
5
+ # @param [Integer] codepoint
6
+ def initialize(codepoint)
7
+ @codepoint = codepoint
8
+ end
9
+
10
+ def method_missing(method, *args, &block)
11
+ property = method.to_s
12
+ version = args[0] || latest_version
13
+ UniProp::version(version).values_of(property, codepoint)
14
+ end
15
+ end
16
+
17
+ refine String do
18
+ # @param [String] version
19
+ # @param [String] property
20
+ def prop_value(version, property)
21
+ UniProp::version(version).values_of(property, ord)
22
+ end
23
+
24
+ def prop
25
+ if size == 1
26
+ @prop_search ||= PropSearch.new(ord)
27
+ else
28
+ raise(SizeError, "The size of String must be 1.")
29
+ end
30
+ end
31
+ end
32
+
33
+ class SizeError < StandardError; end
34
+ end