Ruby 2.4 के साथ शिप की गई नई विशेषताओं में बेहतर यूनिकोड सपोर्ट है। विशेष रूप से, upcase और downcase जैसे मेथड्स अपेक्षा के अनुरूप काम करते हैं, "ä" को "Ä" और वापस में बदलते हैं। इसने मुझे उत्सुक बना दिया: 2013 के बाद से क्या अन्य यूनिकोड सुधार किए गए हैं जब मैंने André Arko का ब्लॉग पोस्ट "Strings in Ruby are UTF-8 now… right??" पढ़ा था?
मैंने Ruby के सभी स्ट्रिंग मेथड्स को टेस्ट किया, तकनीकी त्रुटियों की तलाश नहीं, बल्कि "लीस्ट सरप्राइज के सिद्धांत" के उल्लंघन की। विशेष रूप से, मेरी धारणाएं थीं कि:
- अद्वितीय वर्ण अद्वितीय हैं: "e" और "ë" अलग हैं, जैसे "e" और "E" हैं।
- सिंगल वर्ण सिंगल वर्ण के रूप में गिने जाते हैं, चाहे वे यूनिकोड में कैसे भी दर्शाए गए हों। इसका मतलब है कि "e" और "ë" प्रत्येक एक सिंगल वर्ण हैं, भले ही बाद वाले को दो कोड पॉइंट्स द्वारा दर्शाया गया हो।
- वर्ण अपरिवर्तनीय हैं। वर्णों की एक स्ट्रिंग को उलटने से व्यक्तिगत वर्णों को नहीं बदलना चाहिए।
- व्हाइटस्पेस को व्हाइटस्पेस के रूप में माना जाता है। यहां तक कि वे ट्रिकी यूनिकोड व्हाइटस्पेस वर्ण भी।
- अंकों को अंकों के रूप में माना जाता है। संख्या 2 हमेशा संख्या 2 है चाहे इसे कैसे भी लिखा जाए।
दुर्भाग्य से, Ruby के अधिकांश स्ट्रिंग मैनिपुलेशन मेथड्स इन टेस्ट्स में विफल होते हैं। यदि आप यूनिकोड स्ट्रिंग्स के साथ काम कर रहे हैं, तो आपको बहुत सावधान रहना होगा कि आप किनका उपयोग करते हैं।
नोट: प्रकाशन के बाद, कुछ पाठकों ने बताया कि मैंने जिन विफलताओं का उल्लेख किया है, वे नहीं हुई होतीं यदि मैंने यूनिकोड टेस्ट स्ट्रिंग्स को नॉर्मलाइज़ किया होता। यह सच है। हालांकि, Ruby या Rails द्वारा किसी भी ऐप में स्ट्रिंग्स स्वचालित रूप से नॉर्मलाइज़ नहीं होतीं (जिन्हें मैंने टेस्ट किया)। ये टेस्ट हमेशा सबसे खराब स्थिति को स्पष्ट करने के लिए थे और मुझे लगता है कि वे उस संबंध में अभी भी उपयोगी हैं।
Ruby 2.4.0 के साथ यूनिकोड टेस्ट्स
| मेथड | टेस्ट | अपेक्षित | परिणाम | निर्णय |
|---|---|---|---|---|
| #% | "%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 | सावधान! |
| #[] | "ä"[0] | "ä" | "a" | सावधान! |
| #[]= | "ä"[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) | " ä " | "ä " | सावधान! |
| #chars | "ä".chars | ["ä"] | ["a", "̈"] | सावधान! |
| #chomp | "ä ".chomp | "ä" | "ä" | OK |
| #chop | "ä".chop | "" | "a" | सावधान! |
| #chr | "ä".chr | "ä" | "a" | सावधान! |
| #clear | "ä".clear | "" | "" | OK |
| #codepoints | "ä".codepoints | [97, 776] | [97, 776] | OK |
| #concat | "ä".concat("x") | "äx" | "äx" | OK |
| #count | "ä".count("a") | 0 | 1 | सावधान! |
| #crypt | "123".crypt("ää") == "123".crypt("aa") | false | false | OK |
| #delete | "ä".delete("a") | "ä" | "̈" | सावधान! |
| #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", "̈"] | सावधान! |
| #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") | "ä" | "ẍ" | सावधान! |
| #hash | "ä".hash == "a".hash | false | false | OK |
| #include? | "ä".include?("a") | false | true | सावधान! |
| #index | "ä".index("a") | nil | 0 | सावधान! |
| #replace | "ä".replace("u") | "u" | "u" | OK |
| #insert | "ä".insert(1, "u") | "äu" | "aü" | सावधान! |
| #inspect | "ä".inspect | "\"ä\"" | "\"ä\"" | OK |
| #intern | "ä".intern | :ä | :ä | OK |
| #length | "ä".length | 1 | 2 | सावधान! |
| #ljust | "ä".ljust(3, "_") | "ä__" | "ä_" | सावधान! |
| #lstrip | " ä".lstrip | "ä" | "ä" | OK |
| #match | "ä".match("a") | nil | #<MatchData "a"> | सावधान! |
| #next | "ä".next | "ä" | "b̈" | सावधान! |
| #ord | "ä".ord | 97 | 97 | OK |
| #partition | "händ".partition("a") | ["händ"] | ["h", "a", "̈nd"] | सावधान! |
| #prepend | "ä".prepend("ä") | "ää" | "ää" | OK |
| #replace | "ä".replace("ẍ") | "ẍ" | "ẍ" | OK |
| #reverse | "händ".reverse | "dnäh" | "dn̈ah" | सावधान! |
| #rpartition | "händ".rpartition("a") | ["händ"] | ["h", "a", "̈nd"] | सावधान! |
| #rstrip | "line ".rstrip | "line" | "line " | सावधान! |
| #scrub | "ä".scrub | "ä" | "ä" | OK |
| #setbyte | s = "ä"; s.setbyte(0, "x".ord); s | "ẍ" | "ẍ" | OK |
| #size | "ä".size | 1 | 2 | सावधान! |
| #slice | "ä".slice(0) | "ä" | "a" | सावधान! |
| #split | "ä".split("a") | ["ä"] | ["", "̈"] | सावधान! |
| #squeeze | "ää".squeeze("ä") | "ä" | "ää" | सावधान! |
| #start_with? | "ä".start_with?("a") | false | true | सावधान! |
| #strip | " line ".strip | "line" | " line " | सावधान! |
| #sub | "ä".sub("a", "x") | "ä" | "ẍ" | सावधान! |
| #succ | "ä".succ | "b̈" | "b̈" | OK |
| #swapcase | "ä".swapcase | "Ä" | "Ä" | OK |
| #to_c | "१".to_c | (1+0i) | (0+0i) | सावधान! |
| #to_f | "१".to_f | 1.0 | 0.0 | सावधान! |
| #to_i | "१".to_i | 1 | 0 | सावधान! |
| #to_r | "१".to_r | (1/1) | (0/1) | सावधान! |
| #to_sym | "ä".to_sym | :ä | :ä | OK |
| #tr | "ä".tr("a", "b") | "ä" | "b̈" | सावधान! |
| #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 |