2015-11-24 30 views
6

ho queste 2 stringhe UTF-8:Rubino, problemi che confrontano le stringhe con caratteri UTF-8

a = "N\u01b0\u0303" 
b = "N\u1eef" 

Hanno un aspetto piuttosto diverso, ma lo sono lo stesso una volta che vengono resi:

irb(main):039:0> puts "#{a} - #{b}" 
Nữ - Nữ 

La versione a è quella che ho memorizzato nel DB. La versione b è quella che viene dal browser in una richiesta POST, non so perché il browser sta inviando una combinazione diversa di caratteri UTF8, e non sta succedendo sempre, non posso riprodurre il problema in il mio ambiente di sviluppo, avviene in produzione e in una percentuale delle richieste totali.

il caso è che cerco di confrontare entrambi, ma ritornano false:

irb(main):035:0> a == b 
=> false 

ho provato cose diverse, come costringendo codifica:

irb(main):022:0> c.force_encoding("UTF-8") == a.force_encoding("UTF-8") 
=> false 

Un altro fatto interessante è :

irb(main):005:0> a.chars 
=> ["N", "ư", "̃"] 
irb(main):006:0> b.chars 
=> ["N", "ữ"] 

Come posso confrontare questo tipo di stringhe?

+0

Ottiene un e b dallo stesso browser e os? Mi sembra un problema specifico per il rendering di caratteri di browser/os. Probabilmente si può provare la tabella di sostituzione spot e quindi effettuare la sostituzione inversa. – Cyrill

risposta

8

Questo è un problema con Unicode equivalence.

La versione a della stringa è costituito dal carattere ư (U + 01B0: alfabeto latino U CON CORNO), seguita da U + 0303 ABBINAMENTO TILDE. Questo secondo carattere, come suggerisce il nome, è un combining character, che quando il rendering è combinato con il personaggio precedente produce il glifo finale.

La versione b della stringa utilizza il carattere (U + 1EEF, alfabeto latino U CON CORNO E TILDE) che è un singolo carattere, ed è equivalente alla combinazione precedente, ma utilizza una diversa sequenza di byte per rappresentarlo.

Per confrontare queste stringhe è necessario normalizzare, in modo che entrambe utilizzino le stesse sequenze di byte per questi tipi di caratteri. Le versioni correnti di Ruby hanno questo integrato (nelle versioni precedenti era necessario utilizzare una libreria di terze parti).

Così Attualmente hai

a == b 

che è false, ma se lo fai

a.unicode_normalize == b.unicode_normalize 

si dovrebbe ottenere true.

Se si utilizza una versione precedente di Ruby, ci sono un paio di opzioni.Rails ha un metodo normalize come parte del suo sostegno più byte, quindi se si utilizza Rails si può fare:

a.mb_chars.normalize == b.mb_chars.normalize 

o forse qualcosa di simile:

ActiveSupport::Multibyte::Unicode.normalize(a) == ActiveSupport::Multibyte::Unicode.normalize(b) 

Se non si utilizza Rails, allora si poteva guardare il unicode_utils gem, e fare qualcosa di simile:

UnicodeUtils.nfkc(a) == UnicodeUtils.nfkc(b) 

(nfkc si riferisce alla forma di normalizzazione, è la stessa della DEFA nelle altre tecniche.)

Esistono vari modi per normalizzare le stringhe Unicode (ad es. se si utilizzano le versioni scomposte o combinate) e in questo esempio viene utilizzata solo l'impostazione predefinita. Lascerò la ricerca delle differenze per te.

+0

Sto usando Ruby 2.0.0p247 che sembra non aver integrato questo modulo. È consigliata una libreria di terze parti? Ho trovato [questo] (https://github.com/rubysl/rubysl-unicode_normalize) ma nessun avvio su Github e inoltre ho problemi a installarlo. – fguillen

+0

@fguillen Ho aggiornato per risposta con alcuni suggerimenti. La tua domanda è taggata con Rails, quindi l'utilizzo del supporto di Rails sarebbe probabilmente la soluzione migliore qui a mio avviso. – matt

+0

hai ragione, non avevo pensato al modulo interno Unicode di Rails.Ho aggiunto un esempio per questo escenario alla tua risposta, correggilo se non è giusto. – fguillen

3

Si può vedere questi sono caratteri distinti. First e second. Nel primo caso, utilizza un modificatore "combining tilde".

Wikipedia ha una sezione su questo:

sequenze punto di codice che sono definiti come canonicamente equivalenti si presume di avere lo stesso aspetto e significato quando vengono stampate o visualizzate. Ad esempio, il punto di codice U + 006E (il carattere minuscolo latino "n") seguito da U + 0303 (la combinazione di tilde "◌") è definito da Unicode per essere canonicamente equivalente al singolo punto di codice U + 00F1 (il minuscolo lettera "ñ" dell'alfabeto spagnolo). Pertanto, tali sequenze dovrebbero essere visualizzate nello stesso modo, dovrebbero essere trattate allo stesso modo da applicazioni come i nomi alfabetici o la ricerca e possono essere sostituite l'una dall'altra.

e

Lo standard definisce anche una procedura di testo normalizzazione, chiamato Unicode normalizzazione, che sostituisce sequenze equivalenti di caratteri in modo che due testi equivalenti saranno ridotti per la stessa sequenza di punti di codice , chiamato modulo di normalizzazione o forma normale del testo originale.

Sembra che Ruby supporta questa normalizzazione, ma only as of Ruby 2.2:

http://ruby-doc.org/stdlib-2.2.0/libdoc/unicode_normalize/rdoc/String.html

a = "N\u01b0\u0303".unicode_normalize 
b = "N\u1eef".unicode_normalize 

a == b # true 

In alternativa, se si sta utilizzando Ruby on Rails, sembra che vi sia un built-in method per la normalizzazione.