2009-07-03

Upcase con acentos

En ruby no se calculan los cambios minúscula mayúscula para las letras acentuadas. Por ejemplo:

irb(main):002:0> puts "áa".upcase
áA
=> nil

Para que ande un poco mejor (y tome las letras acentuadas de un montón de lenguajes), encontré un hack que en 10 líneas de código hace que puedan pasarse a mínusculas y mayúsculas los caracteres acentuados. Usando que todas las letras minúsculas y mayúsculas acentuadas están todas juntas, armé un par de gsubs que resuelven el problema.
class String
alias_method :old_rapidito_upcase, :upcase
def upcase
self.gsub( /\303[\240-\277]/ ) do
|match|
match[0].chr + (match[1] - 040).chr
end.old_rapidito_upcase
end

alias_method :old_rapidito_downcase, :downcase
def downcase
self.gsub( /\303[\200-\237]/ ) do
|match|
match[0].chr + (match[1] + 040).chr
end.old_rapidito_downcase
end
end

Lo que hago acá es reemplazar las funciones upcase y downcase para que manejen los acentos, sumando o restando 32 (40 en octal) al segundo byte. Pongo este código en lang_hacks.rb y sale todo con fritas. Mirando el irb
irb(main):001:0> require 'lang_hacks'
=> true
irb(main):002:0> "aÁ".upcase
=> "A\303\201"
irb(main):003:0> puts "aÁ".upcase

=> nil

Espero que les sirva.
Happy hacking,
Aureliano.

7 comentarios:

Mordejai dijo...

No sé por dónde empezar a decir lo que está mal... :-)

Lamentablemente, estas son las cosas que impiden a lenguajes como Ruby convertirse en mainstream: un pésimo soporte de i18n.

Sería interesante ver cómo se puede usar IronRuby para "enganchar" los métodos a System.String.ToUpper y ToLower de .NET

aurelianito dijo...

El manejo de unicode es algo que van a arreglar en Ruby 2.0. Es complicado porque rompe la retrocompatibilidad. O sea, hay programas escritos para Ruby 1.X que no van a andar en Ruby 2.0.
No tengo ni idea de como anda el upcase en IronRuby, pero lo probé en JRuby hace un rato y anda igual que en Ruby común (supongo que para mantener compatibilidad con los programas que ya están escritos).
Y sí, el manejo de unicode en Ruby 1.8 deja muuuuuucho que desear. Ojalá fuera como el manejo de Java (del cuál .Net se copió el suyo, como casi todo lo demás).
De todas maneras, lo interesante es que un hack para emparchar el problema me llevó menos de 20 líneas de código.

Mordejai dijo...

No puede ser que en 2009 estén planeando en arreglar la versión siguiente algo que Java resolvió hace 14 años...

.NET es un poco más flexible que Java en el tema i18n.

El problema del parche ese es que sirve sólo para el español. Un soporte correcto de i18n cambia las reglas de acuerdo al lenguaje.
Ver: Dotted and dotless I

aurelianito dijo...

Dejá de trollear. El problema de la i18n es un bardo en todos lados (en .net también).
El problema es que cosas que intuitivamente consideramos universales, como el alfabeto, como pasar de mayúsculas a minúsculas, como se dice una fecha, que unidades se usan para medir distancia/ peso/ volumen/ fuerza, cuanto lugar ocupa un label, etc, en realidad no son universales.
Otro problema asociado es que pensar que son universales hace que algunos problemas sean más fáciles de resolver (por ejemplo, si te olvidás del quilombo de las íes turcas hacer uppercas y lowercase es más fácil) y entonces, para algunos programas, es mejor que tu framework asuma que son universales.
Por otro lado, algunos de los cambios que hacen falta para hacer i18n bien son disrruptivos. Por lo tanto, es muy complicado hacerlos sobre lenguajes que son usados para hacer cosas, ya que estos arreglos rompen programas, frameworks, etc.
Por último, también hay cosas en los que las plataformas de scripting (como ruby, php o python que tienen problemas similares) son mucho mejores que java o .net.
Igualmente, este thread se está yendo del foco que quería darle al post, que es: "hackié uppercase y lowercase y anda para castellano, portugués, italiano y francés con 10 líneas de código".
Trata de cambiar System.String.ToUpper y ToLower de .NET (y no digas que no hace falta porque invento un idioma nuevo ahora).

Mordejai dijo...

I'm not trolling. Entiendo tu fanatismo con Ruby, que me parece un lenguaje extremadamente potente, pero minimizar sus defectos no es la mejor forma de promoverlo.

La i18n realmente NO es un problema en .NET. Para alimentar tu anti-netismo, te diría que es una de las pocas cosas que hicieron realmente bien :-)

Y sí, está claro que en lenguajes como Ruby, Smalltalk y otros, que te permiten modificar clases "core" en tiempo real, es sencillo hacer modificaciones que en lenguajes más estáticos son imposibles.

Respecto a tu potencial idioma nuevo, funcionaría a la perfección en .NET, ya que ToUpper y ToLower tienen un overload que recibe un CultureInfo. Se llama Dependency Injection eso :-)

El CultureInfo tiene, entre otras cosas, un TextInfo que es el que realmente se encarga de hacer el ToUpper.

El asumir que determinadas cosas son universales para simplificar (lease, ahorrarse el tiempo que lleva hacer las cosas bien) es un error cultural, y podríamos atribuirlo a que los proyectos FOSS tienen menos recursos que Sun o Microsoft.

aurelianito dijo...

Me quedé pensando....
¿Qué diferencia hay entre cambiar el culture info y monekypatchear donde hace falta? Quizás en .Net hace falta hacer algo más perfecto de una porque el entorno no te deja hacer las correcciones cuando las necesitás. O sea, es mucho menos ágil.
Aureliano.
PD: Si querés seguir trolleando, podés hacerlo en el post que acabo de hacer ahora.

Mordejai dijo...

"¿Qué diferencia hay entre cambiar el culture info y monekypatchear donde hace falta?"

La misma que hay entre tener una arquitectura adecuada y tener una maraña de spaghetti donde todo hay que hacerlo ad-hoc y con copy-paste.
La i18n es un tema complejo, que requiere una solución bien pensada, que además sea fácilmente extensible para nuevas culturas. El monkeypatching es parte del problema, no de la solución.

"Quizás en .Net hace falta hacer algo más perfecto de una porque el entorno no te deja hacer las correcciones cuando las necesitás."

FUD. En .NET tenían que hacer algo apto para el desarrollo multilingüe y diseñaron una arquitectura acorde.
En Ruby, al ser un producto independiente, asumieron que se podían dar el lujo de tener un soporte paupérrimo de esto.

"O sea, es mucho menos ágil."

¿MENOS ágil?
Supongamos que tenés que hacer una aplicación que se pueda usar en cualquier cultura. Aún dejando de lado la traducción, ¿vos pensás que ponerte a estudiar las 20000 culturas del mundo con sus respectivas reglas y monkeypatchear todas las funciones de strings de la plataforma rezando por no romper nada es "agil"?


El otro post SÍ es un buen ejemplo de dinamismo porque resolvés el problema de forma completa y elegante.

Cada herramienta tiene una aplicación. Ser ágil es saber elegir la que más me ayuda en cada situación.