ffi-icu 0.6.1 → 0.7.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 +4 -4
- data/README.md +46 -1
- data/lib/ffi-icu/break_iterator.rb +24 -5
- data/lib/ffi-icu/collation.rb +20 -15
- data/lib/ffi-icu/currency.rb +123 -0
- data/lib/ffi-icu/duration_formatting.rb +2 -2
- data/lib/ffi-icu/lib/util.rb +1 -1
- data/lib/ffi-icu/lib.rb +62 -9
- data/lib/ffi-icu/list_formatting.rb +52 -0
- data/lib/ffi-icu/locale.rb +1 -0
- data/lib/ffi-icu/message_formatting.rb +217 -0
- data/lib/ffi-icu/normalization.rb +5 -5
- data/lib/ffi-icu/normalizer.rb +11 -12
- data/lib/ffi-icu/number_formatting.rb +3 -3
- data/lib/ffi-icu/time_formatting.rb +12 -12
- data/lib/ffi-icu/transliteration.rb +14 -10
- data/lib/ffi-icu/uchar.rb +23 -0
- data/lib/ffi-icu/version.rb +1 -1
- data/lib/ffi-icu.rb +3 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ffb113b9b98bd46355e3d6750fc1026d7732d06a8575bf2022aa073e72c532e
|
|
4
|
+
data.tar.gz: 43b5a202de5a08790c2a257a8a05f9867df2a5b42624cab157dbf20eaa85eb37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 63fefe8619f8bfbad7ead9ac1ada9471074246b1a2d7c4af6cdb899250f464a07c8596bb55e836f44a128d1ce077b939404b2ad85c612298cc65a66bc1fe92f8
|
|
7
|
+
data.tar.gz: 06e98c3af40a060bbbd2a19a9e5b95fcea49d1d75316a1f2ea639d91d88721886fe079ad3e4952ac8750cdfbf62e5dc4550b0893c3583c1896764c46d1c3fd46
|
data/README.md
CHANGED
|
@@ -93,6 +93,28 @@ curf = ICU::NumberFormatting.create('en-US', :currency)
|
|
|
93
93
|
curf.format(1234.56, 'USD') #=> "$1,234.56"
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
## Message Formatting
|
|
97
|
+
|
|
98
|
+
Message formatting supports ICU MessageFormat patterns using numbered arguments:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
formatter = ICU::MessageFormatting.create(
|
|
102
|
+
'{0, plural, one {# item} other {# items}}',
|
|
103
|
+
locale: 'en-US'
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
formatter.format([2]) #=> "2 items"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Patterns can also be validated without formatting a message:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
ICU::MessageFormatting.validate('{0, select, male {He} female {She} other {They}}', locale: 'en-US')
|
|
113
|
+
#=> true
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The current API uses ICU's C MessageFormat API and therefore resolves numbered arguments only. Named arguments are not resolved by this API, and rich-text tags require a separate compatibility layer. Supported Ruby argument types are `String`, `Integer`, `Float`, and `BigDecimal`.
|
|
117
|
+
|
|
96
118
|
## Time Formatting/Parsing
|
|
97
119
|
|
|
98
120
|
Examples:
|
|
@@ -187,6 +209,26 @@ Example:
|
|
|
187
209
|
ICU::Transliteration.transliterate('Traditional-Simplified', '沈從文') # => "沈从文"
|
|
188
210
|
```
|
|
189
211
|
|
|
212
|
+
## List Formatting
|
|
213
|
+
|
|
214
|
+
Join items with locale-aware separators and conjunctions:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
ICU::ListFormatting.format(['Alice', 'Bob', 'Jane'], locale: 'en', style: :and)
|
|
218
|
+
# => "Alice, Bob, and Jane"
|
|
219
|
+
|
|
220
|
+
ICU::ListFormatting.format(['Alice', 'Bob', 'Jane'], locale: 'fr', style: :and)
|
|
221
|
+
# => "Alice, Bob et Jane"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Supported styles are:
|
|
225
|
+
|
|
226
|
+
- `:and`: the default
|
|
227
|
+
- `:or`
|
|
228
|
+
- `:unit`: ICU's units list type
|
|
229
|
+
|
|
230
|
+
Only ICU's wide width is exposed. Styles must be symbols, and items must be strings. The Ruby/FFI layer converts Ruby strings to ICU's UTF-16 `UChar` values before calling ICU. List formatting requires ICU 67 or newer.
|
|
231
|
+
|
|
190
232
|
## Locale
|
|
191
233
|
|
|
192
234
|
Examples:
|
|
@@ -197,7 +239,10 @@ locale.display_country('en-US') #=> "United States"
|
|
|
197
239
|
locale.display_language('es') #=> "inglés"
|
|
198
240
|
locale.display_name('es') #=> "inglés (Estados Unidos)"
|
|
199
241
|
locale.display_name_with_context('en-US', [:length_short]) #=> "English (US)"
|
|
200
|
-
locale.display_name_with_context('en-US', [:
|
|
242
|
+
locale.display_name_with_context('en-US', [:length_full]) #=> "English (United States)"
|
|
243
|
+
|
|
244
|
+
dari = ICU::Locale.new('fa-AF')
|
|
245
|
+
dari.display_name_with_context('en', [:dialect_names]) #=> "Dari"
|
|
201
246
|
```
|
|
202
247
|
|
|
203
248
|
[icu]: https://github.com/unicode-org/icu
|
|
@@ -20,16 +20,20 @@ module ICU
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def text=(str)
|
|
23
|
-
|
|
23
|
+
text_pointer = UCharPointer.from_utf8(str)
|
|
24
24
|
|
|
25
25
|
Lib.check_error do |err|
|
|
26
|
-
Lib.ubrk_setText(@iterator,
|
|
26
|
+
Lib.ubrk_setText(@iterator, text_pointer, text_pointer.length_in_uchars, err)
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
@text = str
|
|
30
|
+
@text_pointer = text_pointer
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
def each
|
|
31
34
|
return to_enum(:each) unless block_given?
|
|
32
35
|
|
|
36
|
+
# Positional methods expose ICU UTF-16 code-unit offsets.
|
|
33
37
|
int = first
|
|
34
38
|
|
|
35
39
|
while int != DONE
|
|
@@ -43,11 +47,13 @@ module ICU
|
|
|
43
47
|
def each_substring
|
|
44
48
|
return to_enum(:each_substring) unless block_given?
|
|
45
49
|
|
|
46
|
-
# each_char needed for 1.8, where String#[] works on bytes, not characters
|
|
47
50
|
chars = text.each_char.to_a
|
|
48
|
-
|
|
51
|
+
utf16_to_ruby = ruby_offsets_for(chars)
|
|
52
|
+
|
|
53
|
+
low = utf16_to_ruby[first]
|
|
49
54
|
|
|
50
55
|
while (high = self.next) != DONE
|
|
56
|
+
high = utf16_to_ruby[high]
|
|
51
57
|
yield(chars[low...high].join)
|
|
52
58
|
low = high
|
|
53
59
|
end
|
|
@@ -55,6 +61,19 @@ module ICU
|
|
|
55
61
|
self
|
|
56
62
|
end
|
|
57
63
|
|
|
64
|
+
def ruby_offsets_for(chars)
|
|
65
|
+
offsets = [0]
|
|
66
|
+
utf16_offset = 0
|
|
67
|
+
|
|
68
|
+
chars.each_with_index do |char, ruby_index|
|
|
69
|
+
utf16_offset += char.encode(Encoding::UTF_16LE).bytesize / UCharPointer::TYPE_SIZE
|
|
70
|
+
offsets[utf16_offset] = ruby_index + 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
offsets
|
|
74
|
+
end
|
|
75
|
+
private :ruby_offsets_for
|
|
76
|
+
|
|
58
77
|
def substrings
|
|
59
78
|
each_substring.to_a
|
|
60
79
|
end
|
|
@@ -64,7 +83,7 @@ module ICU
|
|
|
64
83
|
end
|
|
65
84
|
|
|
66
85
|
def previous
|
|
67
|
-
Lib.
|
|
86
|
+
Lib.ubrk_previous(@iterator)
|
|
68
87
|
end
|
|
69
88
|
|
|
70
89
|
def first
|
data/lib/ffi-icu/collation.rb
CHANGED
|
@@ -72,21 +72,15 @@ module ICU
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def compare(a, b)
|
|
75
|
-
Lib.ucol_strcoll(
|
|
76
|
-
@c,
|
|
77
|
-
UCharPointer.from_string(a), a.size,
|
|
78
|
-
UCharPointer.from_string(b), b.size
|
|
79
|
-
)
|
|
75
|
+
Lib.ucol_strcoll(@c, *collation_arguments(a, b))
|
|
80
76
|
end
|
|
81
77
|
|
|
82
78
|
def greater?(a, b)
|
|
83
|
-
Lib.ucol_greater(@c,
|
|
84
|
-
UCharPointer.from_string(b), b.size)
|
|
79
|
+
Lib.ucol_greater(@c, *collation_arguments(a, b))
|
|
85
80
|
end
|
|
86
81
|
|
|
87
82
|
def greater_or_equal?(a, b)
|
|
88
|
-
Lib.ucol_greaterOrEqual(@c,
|
|
89
|
-
UCharPointer.from_string(b), b.size)
|
|
83
|
+
Lib.ucol_greaterOrEqual(@c, *collation_arguments(a, b))
|
|
90
84
|
end
|
|
91
85
|
|
|
92
86
|
def equal?(*args)
|
|
@@ -96,8 +90,7 @@ module ICU
|
|
|
96
90
|
|
|
97
91
|
a, b = args
|
|
98
92
|
|
|
99
|
-
Lib.ucol_equal(@c,
|
|
100
|
-
UCharPointer.from_string(b), b.size)
|
|
93
|
+
Lib.ucol_equal(@c, *collation_arguments(a, b))
|
|
101
94
|
end
|
|
102
95
|
|
|
103
96
|
def collate(sortable)
|
|
@@ -110,15 +103,16 @@ module ICU
|
|
|
110
103
|
@rules ||= begin
|
|
111
104
|
length = FFI::MemoryPointer.new(:int)
|
|
112
105
|
ptr = Lib.ucol_getRules(@c, length)
|
|
113
|
-
ptr.read_array_of_uint16(length.read_int).
|
|
106
|
+
ptr.read_array_of_uint16(length.read_int).
|
|
107
|
+
pack('v*').force_encoding('UTF-16LE').encode('UTF-8')
|
|
114
108
|
end
|
|
115
109
|
end
|
|
116
110
|
|
|
117
111
|
def collation_key(string)
|
|
118
|
-
ptr = UCharPointer.
|
|
119
|
-
size = Lib.ucol_getSortKey(@c, ptr,
|
|
112
|
+
ptr = UCharPointer.from_utf8(string)
|
|
113
|
+
size = Lib.ucol_getSortKey(@c, ptr, ptr.length_in_uchars, nil, 0)
|
|
120
114
|
buffer = FFI::MemoryPointer.new(:char, size)
|
|
121
|
-
Lib.ucol_getSortKey(@c, ptr,
|
|
115
|
+
Lib.ucol_getSortKey(@c, ptr, ptr.length_in_uchars, buffer, size)
|
|
122
116
|
buffer.read_bytes(size - 1)
|
|
123
117
|
end
|
|
124
118
|
|
|
@@ -134,6 +128,17 @@ module ICU
|
|
|
134
128
|
end
|
|
135
129
|
end
|
|
136
130
|
|
|
131
|
+
def collation_arguments(left, right)
|
|
132
|
+
left_text = UCharPointer.from_utf8(left)
|
|
133
|
+
right_text = UCharPointer.from_utf8(right)
|
|
134
|
+
|
|
135
|
+
[
|
|
136
|
+
left_text, left_text.length_in_uchars,
|
|
137
|
+
right_text, right_text.length_in_uchars
|
|
138
|
+
]
|
|
139
|
+
end
|
|
140
|
+
private :collation_arguments
|
|
141
|
+
|
|
137
142
|
# create friendly named methods for setting attributes
|
|
138
143
|
ATTRIBUTES.each_key do |attribute|
|
|
139
144
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module ICU
|
|
2
|
+
class Currency
|
|
3
|
+
CURRENCY_TYPE_COMMON = 1
|
|
4
|
+
CURRENCY_TYPE_UNCOMMON = 2
|
|
5
|
+
CURRENCY_TYPE_DEPRECATED = 4
|
|
6
|
+
CURRENCY_TYPE_NON_DEPRECATED = 8
|
|
7
|
+
CURRENCY_TYPE_ALL = (2**31) - 1
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# Returns the default currency code for a locale, e.g. "USD" for "en_US"
|
|
11
|
+
def for_locale(locale)
|
|
12
|
+
ptr = UCharPointer.new(4)
|
|
13
|
+
length = Lib.check_error do |status|
|
|
14
|
+
Lib.ucurr_forLocale(locale.to_s, ptr, 4, status)
|
|
15
|
+
end
|
|
16
|
+
ptr.utf8_string(length)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns an array of ISO 4217 currency code strings.
|
|
20
|
+
# currency_type: :all (default), :common, :uncommon, :deprecated, :non_deprecated
|
|
21
|
+
def available(currency_type = :all)
|
|
22
|
+
flag = case currency_type
|
|
23
|
+
when :common then CURRENCY_TYPE_COMMON
|
|
24
|
+
when :uncommon then CURRENCY_TYPE_UNCOMMON
|
|
25
|
+
when :deprecated then CURRENCY_TYPE_DEPRECATED
|
|
26
|
+
when :non_deprecated then CURRENCY_TYPE_NON_DEPRECATED
|
|
27
|
+
else CURRENCY_TYPE_ALL
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
enum_ptr = Lib.check_error { |status| Lib.ucurr_openISOCurrencies(flag, status) }
|
|
31
|
+
begin
|
|
32
|
+
Lib.enum_ptr_to_array(enum_ptr)
|
|
33
|
+
ensure
|
|
34
|
+
Lib.uenum_close(enum_ptr)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Convenience: ICU::Currency.symbol('USD', 'en_US') => "$"
|
|
39
|
+
def symbol(code, locale)
|
|
40
|
+
new(code).symbol(locale)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Convenience: ICU::Currency.name('USD', 'en') => "US Dollar"
|
|
44
|
+
def name(code, locale)
|
|
45
|
+
new(code).name(locale)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
attr_reader :code
|
|
50
|
+
|
|
51
|
+
def initialize(code)
|
|
52
|
+
@code = code.to_s.upcase
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Currency symbol for the given locale, e.g. "$", "€"
|
|
56
|
+
def symbol(locale)
|
|
57
|
+
get_name(locale, :symbol_name)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Narrow (shortest unambiguous) symbol. Requires ICU >= 61.
|
|
61
|
+
def narrow_symbol(locale)
|
|
62
|
+
raise('narrow_symbol requires ICU >= 61') if Lib.version.to_a.first < 61
|
|
63
|
+
|
|
64
|
+
get_name(locale, :narrow_symbol_name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Long display name for the given locale, e.g. "US Dollar"
|
|
68
|
+
def name(locale)
|
|
69
|
+
get_name(locale, :long_name)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Plural form of the currency name.
|
|
73
|
+
# plural_count: "zero", "one", "two", "few", "many", "other"
|
|
74
|
+
def plural_name(locale, plural_count = 'other')
|
|
75
|
+
raise('plural_name requires ICU >= 4.2') unless Lib.respond_to?(:ucurr_getPluralName)
|
|
76
|
+
|
|
77
|
+
is_choice = FFI::MemoryPointer.new(:uint8)
|
|
78
|
+
len = FFI::MemoryPointer.new(:int32_t)
|
|
79
|
+
|
|
80
|
+
ptr = Lib.check_error do |status|
|
|
81
|
+
Lib.ucurr_getPluralName(currency_uchar, locale.to_s, is_choice, plural_count.to_s, len, status)
|
|
82
|
+
end
|
|
83
|
+
ptr.read_array_of_uint16(len.read_int32).pack('U*')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Number of decimal digits used when displaying this currency.
|
|
87
|
+
# usage: :standard (default) or :cash
|
|
88
|
+
def fraction_digits(usage = :standard)
|
|
89
|
+
if usage == :cash && Lib.respond_to?(:ucurr_getDefaultFractionDigitsForUsage)
|
|
90
|
+
Lib.check_error { |status| Lib.ucurr_getDefaultFractionDigitsForUsage(currency_uchar, :cash, status) }
|
|
91
|
+
else
|
|
92
|
+
Lib.check_error { |status| Lib.ucurr_getDefaultFractionDigits(currency_uchar, status) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ISO 4217 numeric code, e.g. 840 for USD. Requires ICU >= 49.
|
|
97
|
+
def numeric_code
|
|
98
|
+
raise('numeric_code requires ICU >= 49') unless Lib.respond_to?(:ucurr_getNumericCode)
|
|
99
|
+
|
|
100
|
+
Lib.ucurr_getNumericCode(currency_uchar)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def to_s
|
|
104
|
+
@code
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def currency_uchar
|
|
110
|
+
UCharPointer.from_utf8(@code, 4)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def get_name(locale, style)
|
|
114
|
+
is_choice = FFI::MemoryPointer.new(:uint8)
|
|
115
|
+
len = FFI::MemoryPointer.new(:int32_t)
|
|
116
|
+
|
|
117
|
+
ptr = Lib.check_error do |status|
|
|
118
|
+
Lib.ucurr_getName(currency_uchar, locale.to_s, style, is_choice, len, status)
|
|
119
|
+
end
|
|
120
|
+
ptr.read_array_of_uint16(len.read_int32).pack('U*')
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -264,7 +264,7 @@ module ICU
|
|
|
264
264
|
|
|
265
265
|
def number_formatter(skeleton)
|
|
266
266
|
@number_formatters[skeleton] ||= begin
|
|
267
|
-
skeleton_uchar = UCharPointer.
|
|
267
|
+
skeleton_uchar = UCharPointer.from_utf8(skeleton)
|
|
268
268
|
FFI::AutoPointer.new(
|
|
269
269
|
Lib.check_error do |error|
|
|
270
270
|
Lib.unumf_openForSkeletonAndLocale(skeleton_uchar, skeleton_uchar.length_in_uchars,
|
|
@@ -291,7 +291,7 @@ module ICU
|
|
|
291
291
|
end
|
|
292
292
|
|
|
293
293
|
def format_list(values)
|
|
294
|
-
value_uchars = values.map(&UCharPointer.method(:
|
|
294
|
+
value_uchars = values.map(&UCharPointer.method(:from_utf8))
|
|
295
295
|
value_uchars_array = FFI::MemoryPointer.new(:pointer, value_uchars.size)
|
|
296
296
|
value_uchars_array.put_array_of_pointer(0, value_uchars)
|
|
297
297
|
value_lengths_array = FFI::MemoryPointer.new(:int32_t, value_uchars.size)
|
data/lib/ffi-icu/lib/util.rb
CHANGED
data/lib/ffi-icu/lib.rb
CHANGED
|
@@ -19,18 +19,23 @@ module ICU
|
|
|
19
19
|
elsif FFI::Platform::IS_WINDOWS
|
|
20
20
|
ENV['PATH'].split(File::PATH_SEPARATOR)
|
|
21
21
|
else
|
|
22
|
+
multiarch_paths = Dir['/usr/lib/*-linux-gnu']
|
|
23
|
+
multiarch_paths.sort_by! do |path|
|
|
24
|
+
File.basename(path).start_with?("#{FFI::Platform::ARCH}-") ? 0 : 1
|
|
25
|
+
end
|
|
26
|
+
|
|
22
27
|
[
|
|
23
28
|
'/usr/local/{lib64,lib}',
|
|
24
29
|
'/opt/local/{lib64,lib}',
|
|
25
30
|
'/usr/{lib64,lib}'
|
|
26
|
-
] +
|
|
31
|
+
] + multiarch_paths # for Debian Multiarch http://wiki.debian.org/Multiarch
|
|
27
32
|
end
|
|
28
33
|
end
|
|
29
34
|
|
|
30
35
|
def self.find_lib(lib)
|
|
31
|
-
|
|
32
|
-
File.expand_path(File.join(path, lib))
|
|
33
|
-
end
|
|
36
|
+
search_paths.lazy.filter_map do |path|
|
|
37
|
+
Dir.glob(File.expand_path(File.join(path, lib))).first
|
|
38
|
+
end.first
|
|
34
39
|
end
|
|
35
40
|
|
|
36
41
|
def self.load_icu
|
|
@@ -120,6 +125,7 @@ module ICU
|
|
|
120
125
|
|
|
121
126
|
def self.check_error
|
|
122
127
|
ptr = FFI::MemoryPointer.new(:int)
|
|
128
|
+
ptr.write_int(0)
|
|
123
129
|
ret = yield(ptr)
|
|
124
130
|
error_code = ptr.read_int
|
|
125
131
|
|
|
@@ -203,6 +209,10 @@ module ICU
|
|
|
203
209
|
|
|
204
210
|
typedef VersionInfo, :version
|
|
205
211
|
|
|
212
|
+
attach_function :u_getVersion, "u_getVersion#{suffix}", [:version], :void
|
|
213
|
+
attach_function :u_versionToString, "u_versionToString#{suffix}", [:version, :pointer], :void
|
|
214
|
+
version ||= self.version.to_s
|
|
215
|
+
|
|
206
216
|
attach_function :u_errorName, "u_errorName#{suffix}", [:int], :string
|
|
207
217
|
attach_function :uenum_count, "uenum_count#{suffix}", [:pointer, :pointer], :int
|
|
208
218
|
attach_function :uenum_close, "uenum_close#{suffix}", [:pointer], :void
|
|
@@ -210,9 +220,6 @@ module ICU
|
|
|
210
220
|
attach_function :u_charsToUChars, "u_charsToUChars#{suffix}", [:string, :pointer, :int32_t], :void
|
|
211
221
|
attach_function :u_UCharsToChars, "u_UCharsToChars#{suffix}", [:pointer, :string, :int32_t], :void
|
|
212
222
|
|
|
213
|
-
attach_function :u_getVersion, "u_getVersion#{suffix}", [:version], :void
|
|
214
|
-
attach_function :u_versionToString, "u_versionToString#{suffix}", [:version, :pointer], :void
|
|
215
|
-
|
|
216
223
|
#
|
|
217
224
|
# Locale
|
|
218
225
|
#
|
|
@@ -345,16 +352,51 @@ module ICU
|
|
|
345
352
|
attach_function :ucol_getAttribute, "ucol_getAttribute#{suffix}", [:pointer, :int, :pointer], :int
|
|
346
353
|
attach_function :ucol_setAttribute, "ucol_setAttribute#{suffix}", [:pointer, :int, :int, :pointer], :void
|
|
347
354
|
|
|
355
|
+
# Currency
|
|
356
|
+
#
|
|
357
|
+
# https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/ucurr_8h.html
|
|
358
|
+
#
|
|
359
|
+
|
|
360
|
+
enum :ucurr_name_style, [
|
|
361
|
+
:symbol_name, 0, # "$"
|
|
362
|
+
:long_name, 1, # "US Dollar"
|
|
363
|
+
:narrow_symbol_name, 4, # shortest unambiguous symbol (ICU >= 61)
|
|
364
|
+
:formal_symbol_name, 5, # banking context (ICU >= 68)
|
|
365
|
+
:variant_symbol_name, 6 # regional variant (ICU >= 68)
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
enum :ucurrency_usage, [
|
|
369
|
+
:standard, 0, # UCURR_USAGE_STANDARD
|
|
370
|
+
:cash, 1 # UCURR_USAGE_CASH
|
|
371
|
+
]
|
|
372
|
+
|
|
373
|
+
attach_function :ucurr_getName, "ucurr_getName#{suffix}",
|
|
374
|
+
[:pointer, :string, :ucurr_name_style, :pointer, :pointer, :pointer], :pointer
|
|
375
|
+
attach_optional_function :ucurr_getPluralName, "ucurr_getPluralName#{suffix}",
|
|
376
|
+
[:pointer, :string, :pointer, :string, :pointer, :pointer], :pointer
|
|
377
|
+
attach_function :ucurr_forLocale, "ucurr_forLocale#{suffix}",
|
|
378
|
+
[:string, :pointer, :int32_t, :pointer], :int32_t
|
|
379
|
+
attach_function :ucurr_openISOCurrencies, "ucurr_openISOCurrencies#{suffix}",
|
|
380
|
+
[:uint32, :pointer], :pointer
|
|
381
|
+
attach_function :ucurr_getDefaultFractionDigits, "ucurr_getDefaultFractionDigits#{suffix}",
|
|
382
|
+
[:pointer, :pointer], :int32_t
|
|
383
|
+
attach_optional_function :ucurr_getDefaultFractionDigitsForUsage, "ucurr_getDefaultFractionDigitsForUsage#{suffix}",
|
|
384
|
+
[:pointer, :ucurrency_usage, :pointer], :int32_t
|
|
385
|
+
attach_optional_function :ucurr_getNumericCode, "ucurr_getNumericCode#{suffix}",
|
|
386
|
+
[:pointer], :int32_t
|
|
387
|
+
|
|
348
388
|
# Transliteration
|
|
349
389
|
#
|
|
350
390
|
# http://icu-project.org/apiref/icu4c/utrans_8h.html
|
|
351
391
|
#
|
|
352
392
|
|
|
353
393
|
class UParseError < FFI::Struct
|
|
394
|
+
U_PARSE_CONTEXT_LEN = 16
|
|
395
|
+
|
|
354
396
|
layout :line, :int32_t,
|
|
355
397
|
:offset, :int32_t,
|
|
356
|
-
:pre_context, :
|
|
357
|
-
:post_context, :
|
|
398
|
+
:pre_context, [:uint16, U_PARSE_CONTEXT_LEN],
|
|
399
|
+
:post_context, [:uint16, U_PARSE_CONTEXT_LEN]
|
|
358
400
|
|
|
359
401
|
def to_s
|
|
360
402
|
format('#<%<class>s:%<hash>x line: %<line>d offset: %<offset>d',
|
|
@@ -362,6 +404,17 @@ module ICU
|
|
|
362
404
|
end
|
|
363
405
|
end
|
|
364
406
|
|
|
407
|
+
# MessageFormat
|
|
408
|
+
#
|
|
409
|
+
# https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/umsg_8h.html
|
|
410
|
+
#
|
|
411
|
+
|
|
412
|
+
attach_function :umsg_open, "umsg_open#{suffix}",
|
|
413
|
+
[:pointer, :int32_t, :string, :pointer, :pointer], :pointer
|
|
414
|
+
attach_function :umsg_close, "umsg_close#{suffix}", [:pointer], :void
|
|
415
|
+
attach_function :umsg_format, "umsg_format#{suffix}",
|
|
416
|
+
[:pointer, :pointer, :int32_t, :pointer, :varargs], :int32_t
|
|
417
|
+
|
|
365
418
|
class UTransPosition < FFI::Struct
|
|
366
419
|
layout :context_start, :int32_t,
|
|
367
420
|
:context_limit, :int32_t,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ICU
|
|
4
|
+
module ListFormatting
|
|
5
|
+
VALID_STYLES = [:and, :or, :unit].freeze
|
|
6
|
+
|
|
7
|
+
STYLES_TO_TYPES = {
|
|
8
|
+
and: :and,
|
|
9
|
+
or: :or,
|
|
10
|
+
unit: :units
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def self.format(items, locale:, style: :and)
|
|
14
|
+
raise(ArgumentError, "Unknown style #{style}") unless VALID_STYLES.include?(style)
|
|
15
|
+
raise(ArgumentError, 'items must be strings') unless items.all?(String)
|
|
16
|
+
|
|
17
|
+
return '' if items.empty?
|
|
18
|
+
|
|
19
|
+
unless Lib.respond_to?(:ulistfmt_openForType) &&
|
|
20
|
+
Lib.respond_to?(:ulistfmt_close) &&
|
|
21
|
+
Lib.respond_to?(:ulistfmt_format)
|
|
22
|
+
raise('ICU::ListFormatting requires ICU >= 67')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
formatter = Lib.check_error do |error|
|
|
26
|
+
Lib.ulistfmt_openForType(locale, STYLES_TO_TYPES.fetch(style), :wide, error)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
value_uchars = items.map { |item| UCharPointer.from_utf8(item) }
|
|
31
|
+
value_uchars_array = FFI::MemoryPointer.new(:pointer, value_uchars.size)
|
|
32
|
+
value_uchars_array.put_array_of_pointer(0, value_uchars)
|
|
33
|
+
value_lengths_array = FFI::MemoryPointer.new(:int32_t, value_uchars.size)
|
|
34
|
+
value_lengths_array.put_array_of_int32(0, value_uchars.map(&:length_in_uchars))
|
|
35
|
+
|
|
36
|
+
Lib::Util.read_uchar_buffer(0) do |buffer, error|
|
|
37
|
+
Lib.ulistfmt_format(
|
|
38
|
+
formatter,
|
|
39
|
+
value_uchars_array,
|
|
40
|
+
value_lengths_array,
|
|
41
|
+
value_uchars.size,
|
|
42
|
+
buffer,
|
|
43
|
+
buffer.length_in_uchars,
|
|
44
|
+
error
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
ensure
|
|
48
|
+
Lib.ulistfmt_close(formatter)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/ffi-icu/locale.rb
CHANGED
|
@@ -45,6 +45,7 @@ module ICU
|
|
|
45
45
|
attr_reader :id
|
|
46
46
|
|
|
47
47
|
DISPLAY_CONTEXT = {
|
|
48
|
+
dialect_names: 1, # UDISPCTX_DIALECT_NAMES = (UDISPCTX_TYPE_DIALECT_HANDLING<<8) + 1
|
|
48
49
|
length_full: 512, # UDISPCTX_LENGTH_FULL = (UDISPCTX_TYPE_DISPLAY_LENGTH<<8) + 0
|
|
49
50
|
length_short: 513 # UDISPCTX_LENGTH_SHORT = (UDISPCTX_TYPE_DISPLAY_LENGTH<<8) + 1
|
|
50
51
|
}.freeze
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
|
|
5
|
+
module ICU
|
|
6
|
+
module MessageFormatting
|
|
7
|
+
class MessageFormatError < ICU::Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class InvalidPatternError < MessageFormatError
|
|
11
|
+
attr_reader :locale, :pattern, :parse_error
|
|
12
|
+
|
|
13
|
+
def initialize(message, locale:, pattern:, parse_error:)
|
|
14
|
+
super(message)
|
|
15
|
+
@locale = locale
|
|
16
|
+
@pattern = pattern
|
|
17
|
+
@parse_error = parse_error
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def offset
|
|
21
|
+
parse_error[:offset]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def line
|
|
25
|
+
parse_error[:line]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.create(pattern, locale:)
|
|
30
|
+
Formatter.new(pattern, locale:)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.format(pattern, locale:, arguments:)
|
|
34
|
+
create(pattern, locale:).format(arguments)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# rubocop:disable-next Naming/PredicateMethod
|
|
38
|
+
def self.validate(pattern, locale:)
|
|
39
|
+
create(pattern, locale:)
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.valid?(pattern, locale:)
|
|
44
|
+
validate(pattern, locale:)
|
|
45
|
+
rescue InvalidPatternError
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class Formatter
|
|
50
|
+
attr_reader :locale, :pattern
|
|
51
|
+
|
|
52
|
+
def initialize(pattern, locale:)
|
|
53
|
+
@pattern = String(pattern)
|
|
54
|
+
@locale = String(locale)
|
|
55
|
+
@pattern_pointer = UCharPointer.from_utf8(@pattern)
|
|
56
|
+
@formatter = open_formatter
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def format(arguments = [])
|
|
60
|
+
validate_argument_count!(arguments)
|
|
61
|
+
typed_arguments = typed_arguments_for(arguments)
|
|
62
|
+
validate_argument_types!(arguments)
|
|
63
|
+
|
|
64
|
+
result = Lib::Util.read_uchar_buffer_as_ptr(0) do |result, status|
|
|
65
|
+
Lib.umsg_format(
|
|
66
|
+
@formatter,
|
|
67
|
+
result,
|
|
68
|
+
result.length_in_uchars,
|
|
69
|
+
status,
|
|
70
|
+
*typed_arguments
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
result.utf8_string
|
|
74
|
+
rescue ICU::Error => e
|
|
75
|
+
raise MessageFormatError, "could not format message: #{e.message}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def open_formatter
|
|
81
|
+
parse_error = Lib::UParseError.new
|
|
82
|
+
status = FFI::MemoryPointer.new(:int32_t)
|
|
83
|
+
status.write_int32(0)
|
|
84
|
+
pointer = Lib.umsg_open(
|
|
85
|
+
@pattern_pointer,
|
|
86
|
+
@pattern_pointer.length_in_uchars,
|
|
87
|
+
@locale,
|
|
88
|
+
parse_error,
|
|
89
|
+
status
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
error_code = status.read_int32
|
|
93
|
+
return FFI::AutoPointer.new(pointer, Lib.method(:umsg_close)) unless error_code.positive?
|
|
94
|
+
|
|
95
|
+
raise InvalidPatternError.new(
|
|
96
|
+
"invalid message pattern: #{Lib.u_errorName(error_code)} " \
|
|
97
|
+
"at line #{parse_error[:line]}, offset #{parse_error[:offset]}",
|
|
98
|
+
locale: @locale,
|
|
99
|
+
pattern: @pattern,
|
|
100
|
+
parse_error: parse_error
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def typed_arguments_for(arguments)
|
|
105
|
+
unless arguments.is_a?(Array)
|
|
106
|
+
raise ArgumentError, 'MessageFormat arguments must be provided as an Array of numbered values'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
arguments.flat_map do |argument|
|
|
110
|
+
case argument
|
|
111
|
+
when String
|
|
112
|
+
[:pointer, null_terminated_uchar_pointer(argument)]
|
|
113
|
+
when Integer, Float, BigDecimal
|
|
114
|
+
[:double, argument.to_f]
|
|
115
|
+
else
|
|
116
|
+
raise ArgumentError, "unsupported MessageFormat argument type: #{argument.class}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def validate_argument_count!(arguments)
|
|
122
|
+
unless arguments.is_a?(Array)
|
|
123
|
+
raise ArgumentError, 'MessageFormat arguments must be provided as an Array of numbered values'
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
highest_argument = argument_types.keys.max
|
|
127
|
+
required_count = highest_argument ? highest_argument + 1 : 0
|
|
128
|
+
return if arguments.length >= required_count
|
|
129
|
+
|
|
130
|
+
raise ArgumentError, "MessageFormat pattern requires at least #{required_count} numbered arguments"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Maps each numbered argument in the pattern to its ICU type keyword
|
|
134
|
+
# (e.g. "plural", "select", "number") or nil for a simple argument.
|
|
135
|
+
def argument_types
|
|
136
|
+
types = {}
|
|
137
|
+
quoted = false
|
|
138
|
+
chars = @pattern.each_char.to_a
|
|
139
|
+
index = 0
|
|
140
|
+
|
|
141
|
+
while index < chars.length
|
|
142
|
+
if chars[index] == "'"
|
|
143
|
+
if chars[index + 1] == "'"
|
|
144
|
+
index += 2
|
|
145
|
+
next
|
|
146
|
+
elsif quoted
|
|
147
|
+
quoted = false
|
|
148
|
+
elsif ['{', '}', '#'].include?(chars[index + 1])
|
|
149
|
+
quoted = true
|
|
150
|
+
end
|
|
151
|
+
elsif chars[index] == '{' && !quoted
|
|
152
|
+
index += 1
|
|
153
|
+
index += 1 while index < chars.length && chars[index].match?(/\s/)
|
|
154
|
+
start = index
|
|
155
|
+
index += 1 while index < chars.length && chars[index].match?(/\d/)
|
|
156
|
+
next if index == start
|
|
157
|
+
|
|
158
|
+
arg_number = chars[start...index].join.to_i
|
|
159
|
+
index += 1 while index < chars.length && chars[index].match?(/\s/)
|
|
160
|
+
|
|
161
|
+
if chars[index] == '}'
|
|
162
|
+
types[arg_number] = nil unless types.key?(arg_number)
|
|
163
|
+
elsif chars[index] == ','
|
|
164
|
+
index += 1
|
|
165
|
+
index += 1 while index < chars.length && chars[index].match?(/\s/)
|
|
166
|
+
type_start = index
|
|
167
|
+
index += 1 while index < chars.length && chars[index].match?(/[A-Za-z]/)
|
|
168
|
+
type = chars[type_start...index].join.downcase
|
|
169
|
+
types[arg_number] = (type.empty? ? nil : type) unless types.key?(arg_number)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
index += 1
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
types
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def validate_argument_types!(arguments)
|
|
179
|
+
types = argument_types
|
|
180
|
+
arguments.each_with_index do |argument, index|
|
|
181
|
+
next unless types.key?(index)
|
|
182
|
+
|
|
183
|
+
case expected_kind_for(types[index])
|
|
184
|
+
when :string
|
|
185
|
+
unless argument.is_a?(String)
|
|
186
|
+
raise ArgumentError,
|
|
187
|
+
"MessageFormat argument #{index} expects a String, got #{argument.class}"
|
|
188
|
+
end
|
|
189
|
+
when :number
|
|
190
|
+
unless [Integer, Float, BigDecimal].any? { |klass| argument.is_a?(klass) }
|
|
191
|
+
raise ArgumentError,
|
|
192
|
+
"MessageFormat argument #{index} expects a numeric value, got #{argument.class}"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# ICU reads each argument slot from the vararg list according to the
|
|
199
|
+
# pattern: simple and select arguments read a UChar*, while number,
|
|
200
|
+
# date, time, choice, plural, and selectordinal read a double.
|
|
201
|
+
def expected_kind_for(type)
|
|
202
|
+
case type
|
|
203
|
+
when 'number', 'date', 'time', 'choice', 'plural', 'selectordinal' then :number
|
|
204
|
+
else :string
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def null_terminated_uchar_pointer(string)
|
|
209
|
+
pointer = UCharPointer.from_utf8(string)
|
|
210
|
+
terminated = UCharPointer.new(pointer.length_in_uchars + 1)
|
|
211
|
+
terminated.put_bytes(0, pointer.get_bytes(0, pointer.size))
|
|
212
|
+
terminated.put_uint16(pointer.size, 0)
|
|
213
|
+
terminated
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -3,28 +3,28 @@
|
|
|
3
3
|
module ICU
|
|
4
4
|
module Normalization
|
|
5
5
|
def self.normalize(input, mode = :default)
|
|
6
|
-
input_length = input.size
|
|
7
6
|
needed_length = out_length = options = 0
|
|
8
|
-
|
|
7
|
+
input_pointer = UCharPointer.from_utf8(input)
|
|
8
|
+
input_length = input_pointer.length_in_uchars
|
|
9
9
|
out_ptr = UCharPointer.new(out_length)
|
|
10
10
|
|
|
11
11
|
retried = false
|
|
12
12
|
|
|
13
13
|
begin
|
|
14
14
|
Lib.check_error do |error|
|
|
15
|
-
needed_length = Lib.unorm_normalize(
|
|
15
|
+
needed_length = Lib.unorm_normalize(input_pointer, input_length, mode, options, out_ptr, out_length, error)
|
|
16
16
|
end
|
|
17
17
|
rescue BufferOverflowError
|
|
18
18
|
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
19
19
|
|
|
20
|
-
out_ptr = out_ptr.resized_to(needed_length)
|
|
21
20
|
out_length = needed_length + 1
|
|
21
|
+
out_ptr = out_ptr.resized_to(out_length)
|
|
22
22
|
|
|
23
23
|
retried = true
|
|
24
24
|
retry
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
out_ptr.
|
|
27
|
+
out_ptr.utf8_string(needed_length)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
data/lib/ffi-icu/normalizer.rb
CHANGED
|
@@ -11,39 +11,38 @@ module ICU
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def normalize(input)
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
input_pointer = UCharPointer.from_utf8(input)
|
|
15
|
+
input_length = input_pointer.length_in_uchars
|
|
16
16
|
needed_length = capacity = 0
|
|
17
17
|
out_ptr = UCharPointer.new(needed_length)
|
|
18
18
|
|
|
19
19
|
retried = false
|
|
20
20
|
begin
|
|
21
21
|
Lib.check_error do |error|
|
|
22
|
-
needed_length = Lib.unorm2_normalize(@instance,
|
|
22
|
+
needed_length = Lib.unorm2_normalize(@instance, input_pointer, input_length, out_ptr, capacity, error)
|
|
23
23
|
end
|
|
24
24
|
rescue BufferOverflowError
|
|
25
25
|
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
26
26
|
|
|
27
|
-
capacity = needed_length
|
|
28
|
-
out_ptr = out_ptr.resized_to(
|
|
27
|
+
capacity = needed_length + 1
|
|
28
|
+
out_ptr = out_ptr.resized_to(capacity)
|
|
29
29
|
|
|
30
30
|
retried = true
|
|
31
31
|
retry
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
out_ptr.
|
|
34
|
+
out_ptr.utf8_string(needed_length)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
def normalized?(input)
|
|
38
|
+
input_pointer = UCharPointer.from_utf8(input)
|
|
39
|
+
input_length = input_pointer.length_in_uchars
|
|
40
40
|
|
|
41
41
|
Lib.check_error do |error|
|
|
42
|
-
Lib.unorm2_isNormalized(@instance,
|
|
42
|
+
Lib.unorm2_isNormalized(@instance, input_pointer, input_length, error)
|
|
43
43
|
end
|
|
44
|
-
|
|
45
|
-
result
|
|
46
44
|
end
|
|
45
|
+
alias normailzed? normalized?
|
|
47
46
|
|
|
48
47
|
def is_normalized?(input) # rubocop:disable Naming/PredicatePrefix
|
|
49
48
|
Warning.warn('is_normalized? is deprecated and will be removed after v0.7. Please use normalized? instead.')
|
|
@@ -97,7 +97,7 @@ module ICU
|
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
|
-
out_ptr.
|
|
100
|
+
out_ptr.utf8_string(needed_length)
|
|
101
101
|
rescue BufferOverflowError
|
|
102
102
|
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
103
103
|
|
|
@@ -136,10 +136,10 @@ module ICU
|
|
|
136
136
|
|
|
137
137
|
begin
|
|
138
138
|
Lib.check_error do |error|
|
|
139
|
-
needed_length = Lib.unum_format_currency(@f, number, UCharPointer.
|
|
139
|
+
needed_length = Lib.unum_format_currency(@f, number, UCharPointer.from_utf8(currency, 4), out_ptr,
|
|
140
140
|
needed_length, nil, error)
|
|
141
141
|
end
|
|
142
|
-
out_ptr.
|
|
142
|
+
out_ptr.utf8_string
|
|
143
143
|
rescue BufferOverflowError
|
|
144
144
|
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
145
145
|
|
|
@@ -91,8 +91,8 @@ module ICU
|
|
|
91
91
|
pattern_ptr = FFI::MemoryPointer.new(4)
|
|
92
92
|
|
|
93
93
|
if time_zone_str
|
|
94
|
-
time_zone = UCharPointer.
|
|
95
|
-
tz_len =
|
|
94
|
+
time_zone = UCharPointer.from_utf8(time_zone_str)
|
|
95
|
+
tz_len = time_zone.length_in_uchars
|
|
96
96
|
else
|
|
97
97
|
Lib.check_error do |error|
|
|
98
98
|
i_len = 150
|
|
@@ -142,8 +142,8 @@ module ICU
|
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
def parse(str)
|
|
145
|
-
str_u = UCharPointer.
|
|
146
|
-
str_l =
|
|
145
|
+
str_u = UCharPointer.from_utf8(str)
|
|
146
|
+
str_l = str_u.length_in_uchars
|
|
147
147
|
Lib.check_error do |error|
|
|
148
148
|
ret = Lib.udat_parse(@f, str_u, str_l, nil, error)
|
|
149
149
|
Time.at(ret / 1000.0)
|
|
@@ -170,7 +170,7 @@ module ICU
|
|
|
170
170
|
end
|
|
171
171
|
end
|
|
172
172
|
|
|
173
|
-
out_ptr.
|
|
173
|
+
out_ptr.utf8_string
|
|
174
174
|
rescue BufferOverflowError
|
|
175
175
|
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
176
176
|
|
|
@@ -209,7 +209,7 @@ module ICU
|
|
|
209
209
|
needed_length = Lib.udat_toPattern(@f, localized, out_ptr, needed_length, error)
|
|
210
210
|
end
|
|
211
211
|
|
|
212
|
-
out_ptr.
|
|
212
|
+
out_ptr.utf8_string
|
|
213
213
|
rescue BufferOverflowError
|
|
214
214
|
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
215
215
|
|
|
@@ -228,8 +228,8 @@ module ICU
|
|
|
228
228
|
end
|
|
229
229
|
|
|
230
230
|
def skeleton_format(skeleton_pattern_str, locale)
|
|
231
|
-
skeleton_pattern_ptr = UCharPointer.
|
|
232
|
-
skeleton_pattern_len =
|
|
231
|
+
skeleton_pattern_ptr = UCharPointer.from_utf8(skeleton_pattern_str)
|
|
232
|
+
skeleton_pattern_len = skeleton_pattern_ptr.length_in_uchars
|
|
233
233
|
|
|
234
234
|
needed_length = 0
|
|
235
235
|
pattern_ptr = UCharPointer.new(needed_length)
|
|
@@ -272,7 +272,7 @@ module ICU
|
|
|
272
272
|
return unless @hour_cycle
|
|
273
273
|
|
|
274
274
|
# Get the current pattern and convert to a skeleton
|
|
275
|
-
skeleton_str = pattern_to_skeleton_uchar(current_pattern_as_uchar).
|
|
275
|
+
skeleton_str = pattern_to_skeleton_uchar(current_pattern_as_uchar).utf8_string
|
|
276
276
|
|
|
277
277
|
# Manipulate the skeleton to make it work with the correct hour cycle.
|
|
278
278
|
skeleton_str.gsub!(/[hHkKjJ]/, HOUR_CYCLE_SYMS[@hour_cycle])
|
|
@@ -286,7 +286,7 @@ module ICU
|
|
|
286
286
|
end
|
|
287
287
|
|
|
288
288
|
# Convert the skeleton back to a pattern
|
|
289
|
-
new_pattern_str = skeleton_to_pattern_uchar(UCharPointer.
|
|
289
|
+
new_pattern_str = skeleton_to_pattern_uchar(UCharPointer.from_utf8(skeleton_str)).utf8_string
|
|
290
290
|
|
|
291
291
|
# We also need to manipulate the _pattern_, a little bit, because (according to Firefox source):
|
|
292
292
|
#
|
|
@@ -345,8 +345,8 @@ module ICU
|
|
|
345
345
|
end
|
|
346
346
|
|
|
347
347
|
def set_date_format_impl(localized, pattern_str)
|
|
348
|
-
pattern = UCharPointer.
|
|
349
|
-
pattern_len =
|
|
348
|
+
pattern = UCharPointer.from_utf8(pattern_str)
|
|
349
|
+
pattern_len = pattern.length_in_uchars
|
|
350
350
|
|
|
351
351
|
Lib.check_error do |_error|
|
|
352
352
|
Lib.udat_applyPattern(@f, localized, pattern, pattern_len)
|
|
@@ -26,15 +26,17 @@ module ICU
|
|
|
26
26
|
rules_length = 0
|
|
27
27
|
|
|
28
28
|
if rules
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
rules = UCharPointer.from_utf8(rules)
|
|
30
|
+
rules_length = rules.length_in_uchars
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
id = UCharPointer.from_utf8(id)
|
|
34
|
+
|
|
33
35
|
parse_error = Lib::UParseError.new
|
|
34
36
|
begin
|
|
35
37
|
Lib.check_error do |status|
|
|
36
|
-
ptr = Lib.utrans_openU(
|
|
37
|
-
|
|
38
|
+
ptr = Lib.utrans_openU(id, id.length_in_uchars, direction, rules, rules_length,
|
|
39
|
+
parse_error, status)
|
|
38
40
|
@tr = FFI::AutoPointer.new(ptr, Lib.method(:utrans_close))
|
|
39
41
|
end
|
|
40
42
|
rescue ICU::Error => e
|
|
@@ -45,9 +47,11 @@ module ICU
|
|
|
45
47
|
def transliterate(from)
|
|
46
48
|
# this is a bit unpleasant
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
input = UCharPointer.from_utf8(from)
|
|
51
|
+
input_length_in_uchars = input.length_in_uchars
|
|
52
|
+
capacity = input_length_in_uchars + 1
|
|
53
|
+
buf = UCharPointer.new(capacity)
|
|
54
|
+
buf.put_bytes(0, input.get_bytes(0, input.size))
|
|
51
55
|
limit = FFI::MemoryPointer.new(:int32)
|
|
52
56
|
text_length = FFI::MemoryPointer.new(:int32)
|
|
53
57
|
|
|
@@ -56,7 +60,7 @@ module ICU
|
|
|
56
60
|
begin
|
|
57
61
|
# resets to original size on retry
|
|
58
62
|
[limit, text_length].each do |ptr|
|
|
59
|
-
ptr.put_int32(0,
|
|
63
|
+
ptr.put_int32(0, input_length_in_uchars)
|
|
60
64
|
end
|
|
61
65
|
|
|
62
66
|
Lib.check_error do |error|
|
|
@@ -73,13 +77,13 @@ module ICU
|
|
|
73
77
|
# create a new buffer with more capacity instead of resizing,
|
|
74
78
|
# since the old buffer now has result data
|
|
75
79
|
buf.free
|
|
76
|
-
buf = UCharPointer.
|
|
80
|
+
buf = UCharPointer.from_utf8(from, capacity)
|
|
77
81
|
|
|
78
82
|
retried = true
|
|
79
83
|
retry
|
|
80
84
|
end
|
|
81
85
|
|
|
82
|
-
buf.
|
|
86
|
+
buf.utf8_string(text_length.get_int32(0))
|
|
83
87
|
end
|
|
84
88
|
end
|
|
85
89
|
end
|
data/lib/ffi-icu/uchar.rb
CHANGED
|
@@ -22,6 +22,22 @@ module ICU
|
|
|
22
22
|
ptr
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def self.from_utf8(str, capacity = nil)
|
|
26
|
+
chars = str.encode(Encoding::UTF_16LE).unpack('v*')
|
|
27
|
+
|
|
28
|
+
if capacity
|
|
29
|
+
raise(ArgumentError, "capacity is too small for string of #{chars.size} UChars") if capacity < chars.size
|
|
30
|
+
|
|
31
|
+
ptr = new(capacity)
|
|
32
|
+
else
|
|
33
|
+
ptr = new(chars.size)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
ptr.write_array_of_uint16(chars)
|
|
37
|
+
|
|
38
|
+
ptr
|
|
39
|
+
end
|
|
40
|
+
|
|
25
41
|
def initialize(size)
|
|
26
42
|
super(UCHAR_TYPE, size)
|
|
27
43
|
end
|
|
@@ -42,6 +58,13 @@ module ICU
|
|
|
42
58
|
wstring.pack('U*')
|
|
43
59
|
end
|
|
44
60
|
|
|
61
|
+
def utf8_string(length = nil)
|
|
62
|
+
length ||= size / TYPE_SIZE
|
|
63
|
+
|
|
64
|
+
wstring = read_array_of_uint16(length).pack('v*')
|
|
65
|
+
wstring.force_encoding(Encoding::UTF_16LE).encode(Encoding::UTF_8)
|
|
66
|
+
end
|
|
67
|
+
|
|
45
68
|
def length_in_uchars
|
|
46
69
|
size / type_size
|
|
47
70
|
end
|
data/lib/ffi-icu/version.rb
CHANGED
data/lib/ffi-icu.rb
CHANGED
|
@@ -25,11 +25,14 @@ require 'ffi-icu/lib/util'
|
|
|
25
25
|
require 'ffi-icu/uchar'
|
|
26
26
|
require 'ffi-icu/chardet'
|
|
27
27
|
require 'ffi-icu/collation'
|
|
28
|
+
require 'ffi-icu/currency'
|
|
28
29
|
require 'ffi-icu/locale'
|
|
29
30
|
require 'ffi-icu/transliteration'
|
|
30
31
|
require 'ffi-icu/normalization'
|
|
31
32
|
require 'ffi-icu/normalizer'
|
|
32
33
|
require 'ffi-icu/break_iterator'
|
|
33
34
|
require 'ffi-icu/number_formatting'
|
|
35
|
+
require 'ffi-icu/message_formatting'
|
|
34
36
|
require 'ffi-icu/time_formatting'
|
|
35
37
|
require 'ffi-icu/duration_formatting'
|
|
38
|
+
require 'ffi-icu/list_formatting'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ffi-icu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erick Guan
|
|
@@ -76,10 +76,13 @@ files:
|
|
|
76
76
|
- lib/ffi-icu/break_iterator.rb
|
|
77
77
|
- lib/ffi-icu/chardet.rb
|
|
78
78
|
- lib/ffi-icu/collation.rb
|
|
79
|
+
- lib/ffi-icu/currency.rb
|
|
79
80
|
- lib/ffi-icu/duration_formatting.rb
|
|
80
81
|
- lib/ffi-icu/lib.rb
|
|
81
82
|
- lib/ffi-icu/lib/util.rb
|
|
83
|
+
- lib/ffi-icu/list_formatting.rb
|
|
82
84
|
- lib/ffi-icu/locale.rb
|
|
85
|
+
- lib/ffi-icu/message_formatting.rb
|
|
83
86
|
- lib/ffi-icu/normalization.rb
|
|
84
87
|
- lib/ffi-icu/normalizer.rb
|
|
85
88
|
- lib/ffi-icu/number_formatting.rb
|