রুবি ২.৪-এ শিপ করা নতুন ফিচারগুলোর মধ্যে উন্নত ইউনিকোড সাপোর্ট রয়েছে। নির্দিষ্টভাবে, upcase এবং downcase মেথডগুলো প্রত্যাশা অনুযায়ী কাজ করে, "ä"-কে "Ä"-তে এবং বিপরীতভাবে রূপান্তর করে। এটা আমাকে কৌতূহলী করে দিল: ২০১৩ সালের থেকেAndré Arko-এর ব্লগ পোস্ট "Strings in Ruby are UTF-8 now… right??" পড়ার পর অন্য কী ইউনিকোড উন্নতি হয়েছে?
আমি রুবির সকল স্ট্রিং মেথড টেস্ট করেছি, টেকনিক্যাল এরর খুঁজতে না, বরং "প্রিন্সিপল অফ লีസ്റ്റ সারপ্রাইজ"-এর লঙ্ঘন খুঁজতে। নির্দিষ্টভাবে, আমার অনুমান ছিল যে:
- ইউনিক ক্যারেক্টার ইউনিক: "e" এবং "ë" ভিন্ন, ঠিক যেমন "e" এবং "E" ভিন্ন।
- সিঙ্গল ক্যারেক্টার সিঙ্গল ক্যারেক্টার হিসেবেই গণ্য: কীভাবে সেগুলো ইউনিকোডে রিপ্রেজেন্ট করা হয় সেটা নজরদারি করুন। এর মানে "e" এবং "ë" প্রতিটি একক ক্যারেক্টার, যদিও দ্বিতীয়টি দুটি কোড পয়েন্ট দ্বারা রিপ্রেজেন্ট করা হয়।
- ক্যারেক্টার ইমিউটেবল। ক্যারেক্টারগুলির একটি স্ট্রিং রিভার্স করলে ভিন্ন ক্যারেক্টারগুলো পরিবর্তন করা উচিত নয়।
- হোয়াইটস্পেস হোয়াইটস্পেস হিসেবে চিন्हিত: ওই ঝামেলাদার ইউনিকোড হোয়াইটস্পেস ক্যারেক্টারও।
- ডিজিট ডিজিট হিসেবে চিন्हিত: সংখ্যা ২ সবসময় ২, কীভাবে লেখা হোক না কেন।
দুর্ভাগ্যবশত, রুবির অধিকাংশ স্ট্রিং ম্যানিপুলেশন মেথড এই টেস্টগুলোতে ফেইল করে। আপনি যদি ইউনিকোড স্ট্রিং নিয়ে কাজ করেন, তবে আপনাকে অত্যন্ত সতর্ক থাকতে হবে কোন মেথডগুলো ব্যবহার করছেন।
নোট: পাবলিকেশনের পর, কিছু পাঠক ইঙ্গিত দিয়েছিল যে আমি যেসব ফেইলার মেনশন করেছিলাম সেগুলো ঘটত না যদি আমি ইউনিকোড টেস্ট স্ট্রিংগুলো নর্মালাইজ করতাম। এটা সত্য। কিন্তু রুবি বা রেলস (যে সকল অ্যাপ আমি টেস্ট করেছিলাম) স্ট্রিংগুলো অটোমেটিক্যালি নর্মালাইজ করে না। এই টেস্টগুলো সবসময় ওয়ার্স্ট-কেস ইলাস্ট্রেট করার জন্য ছিল এবং আমি মনে করি সেগুলো এখনও সেই দৃষ্টিতে উপযোগী।
রুবি ২.৪.০-এ ইউনিকোড টেস্ট
| মেথড | টেস্ট | প্রত্যাশিত | ফলাফল | বিচার |
|---|---|---|---|---|
| #% | "%s" % "noël" |
"noël" |
"noël" |
OK |
| #* | "noël" * 2 |
"noëlnoël" |
"noëlnoël" |
OK |
| #<< | "noël" << "ë" |
"noëlë" |
"noëlë" |
OK |
| #<=> | "ä" <=> "z" |
-1 |
-1 |
OK |
| #== | "ä" == "ä" |
true |
true |
OK |
| #=~ | "ä" =~ /a./ |
nil |
0 |
Beware! |
| #[] | "ä"[0] |
"ä" |
"a" |
Beware! |
| #[]= | "ä"[0] = "u" |
"u" |
"u" |
OK |
| #b | "ä".b.encoding.to_s |
"ASCII-8BIT" |
"ASCII-8BIT" |
OK |
| #bytes | "ä".bytes |
[97, 204, 136] |
[97, 204, 136] |
OK |
| #bytesize | "ä".bytesize |
3 |
3 |
OK |
| #byteslice | "ä".byteslice(1) |
"\xCC" |
"\xCC" |
OK |
| #capitalize | "ä".capitalize |
"Ä" |
"Ä" |
OK |
| #casecmp | "äa".casecmp("äz") |
-1 |
-1 |
OK |
| #center | "ä".center(3) |
" ä " |
"ä " |
Beware! |
| #chars | "ä".chars |
["ä"] |
["a", "̈"] |
Beware! |
| #chomp | "ä ".chomp |
"ä" |
"ä" |
OK |
| #chop | "ä".chop |
"" |
"a" |
Beware! |
| #chr | "ä".chr |
"ä" |
"a" |
Beware! |
| #clear | "ä".clear |
"" |
"" |
OK |
| #codepoints | "ä".codepoints |
[97, 776] |
[97, 776] |
OK |
| #concat | "ä".concat("x") |
"äx" |
"äx" |
OK |
| #count | "ä".count("a") |
0 |
1 |
Beware! |
| #crypt | "123".crypt("ää") == "123".crypt("aa") |
false |
false |
OK |
| #delete | "ä".delete("a") |
"ä" |
"̈" |
Beware! |
| #downcase | "Ä".downcase |
"ä" |
"ä" |
OK |
| #dump | "ä".dump |
"\"a\\u0308\"" |
"\"a\\u0308\"" |
OK |
| #each_byte | "ä".each_byte.to_a |
[97, 204, 136] |
[97, 204, 136] |
OK |
| #each_char | "ä".each_char.to_a |
["ä"] |
["a", "̈"] |
Beware! |
| #each_codepoint | "ä".each_codepoint.to_a |
[97, 776] |
[97, 776] |
OK |
| #each_line | "ä".each_line.to_a |
["ä"] |
["ä"] |
OK |
| #empty? | "ä".empty? |
false |
false |
OK |
| #encode | "ä".encode("ASCII", undef: :replace) |
"a?" |
"a?" |
OK |
| #encoding | "ä".encoding.to_s |
"UTF-8" |
"UTF-8" |
OK |
| #end_with? | "ä".end_with?("ä") |
true |
true |
OK |
| #eql? | "ä".eql?("a") |
false |
false |
OK |
| #force_encoding | "ä".force_encoding("ASCII") |
"a\xCC\x88" |
"a\xCC\x88" |
OK |
| #getbyte | "ä".getbyte(2) |
136 |
136 |
OK |
| #gsub | "ä".gsub("a", "x") |
"ä" |
"ẍ" |
Beware! |
| #hash | "ä".hash == "a".hash |
false |
false |
OK |
| #include? | "ä".include?("a") |
false |
true |
Beware! |
| #index | "ä".index("a") |
nil |
0 |
Beware! |
| #replace | "ä".replace("u") |
"u" |
"u" |
OK |
| #insert | "ä".insert(1, "u") |
"äu" |
"aü" |
Beware! |
| #inspect | "ä".inspect |
"\"ä\"" |
"\"ä\"" |
OK |
| #intern | "ä".intern |
:ä |
:ä |
OK |
| #length | "ä".length |
1 |
2 |
Beware! |
| #ljust | "ä".ljust(3, "_") |
"ä__" |
"ä_" |
Beware! |
| #lstrip | " ä".lstrip |
"ä" |
"ä" |
OK |
| #match | "ä".match("a") |
nil |
# |
Beware! |
| #next | "ä".next |
"ä" |
"b̈" |
Beware! |
| #ord | "ä".ord |
97 |
97 |
OK |
| #partition | "händ".partition("a") |
["händ"] |
["h", "a", "̈nd"] |
Beware! |
| #prepend | "ä".prepend("ä") |
"ää" |
"ää" |
OK |
| #replace | "ä".replace("ẍ") |
"ẍ" |
"ẍ" |
OK |
| #reverse | "händ".reverse |
"dnäh" |
"dn̈ah" |
Beware! |
| #rpartition | "händ".rpartition("a") |
["händ"] |
["h", "a", "̈nd"] |
Beware! |
| #rstrip | "line ".rstrip |
"line" |
"line " |
Beware! |
| #scrub | "ä".scrub |
"ä" |
"ä" |
OK |
| #setbyte | s = "ä"; s.setbyte(0, "x".ord); s |
"ẍ" |
"ẍ" |
OK |
| #size | "ä".size |
1 |
2 |
Beware! |
| #slice | "ä".slice(0) |
"ä" |
"a" |
Beware! |
| #split | "ä".split("a") |
["ä"] |
["", "̈"] |
Beware! |
| #squeeze | "ää".squeeze("ä") |
"ä" |
"ää" |
Beware! |
| #start_with? | "ä".start_with?("a") |
false |
true |
Beware! |
| #strip | " line ".strip |
"line" |
" line " |
Beware! |
| #sub | "ä".sub("a", "x") |
"ä" |
"ẍ" |
Beware! |
| #succ | "ä".succ |
"b̈" |
"b̈" |
OK |
| #swapcase | "ä".swapcase |
"Ä" |
"Ä" |
OK |
| #to_c | "١".to_c |
(1+0i) |
(0+0i) |
Beware! |
| #to_f | "١".to_f |
1.0 |
0.0 |
Beware! |
| #to_i | "١".to_i |
1 |
0 |
Beware! |
| #to_r | "١".to_r |
(1/1) |
(0/1) |
Beware! |
| #to_sym | "ä".to_sym |
:ä |
:ä |
OK |
| #tr | "ä".tr("a", "b") |
"ä" |
"b̈" |
Beware! |
| #unpack | "ä".unpack("CCC") |
[97, 204, 136] |
[97, 204, 136] |
OK |
| #upto | "ä".upto("c̈").to_a |
["ä", "b̈", "c̈"] |
["ä", "b̈", "c̈"] |
OK |
| #valid_encoding? | "ä".valid_encoding? |
true |
true |
OK |