Enhorabuena por encontrar la solución.
Te cuento lo que he hecho.
He instalado la base de datos, he puesto las variables que te indico antes en el archivo my.cnf. Volver a arrancar, y con eso, esas variables que indicas ya deberían aparecer a utf8.
Ahora el programa.
Uno de los temas peliagudos es el módulo IO::All. Parece que ahorra mucho trabajo, pero... es un poco monstruoso.
Te pongo un código parecido al tuyo, pero reduciendo al mínimo lo que necesitas, para que veas que tienes más opciones.
Using perl Syntax Highlighting
#!/usr/bin/env perl
use v5.18;
use utf8; # usamos caracteres utf8 en este código fuente
use open qw(:std :utf8); # las entradas/salidas estándar en utf8
use LWP::Simple;
use Encode;
use JSON;
use DBI;
use Data::Dumper;
## Recuperamos información
# Está en utf8, pero Perl la recibe como una cadena de bytes, sin saber qué codificación es. Cree que es latin1
my $contenido = get(
'https://eu.api.battle.net/wow/data/character/classes?locale=es_ES&apikey=5gdrgfafqdnkryj8tqafqxsdr4qamdcq'
);
#say "[$contenido]";
#say (utf8::is_utf8($contenido) ? "sí":"no"); # no
## Conversión a JSON
# Además de pasarlo a JSON, indicamos que está en UTF-8. El resultado es una estructura, con caracteres en utf8
my $json = decode_json $contenido; # suponemos $contenido es texto JSON codificado en UTF8
#print Dumper($json); # ¡cuidado! Data::Dumper saca caracteres escapados latin1
#my $name = $json->{'classes'}[1]{'name'}; # Prueba
#say "[$name]"; # Paladín
#say (utf8::is_utf8($name) ? "sí":"no"); # sí
## Conexión a la base de datos
my ($dbname,$dbhost,$dbuser,$dbpass) = ('blizzardrankings', 'localhost', 'user', 'pass');
my $db = DBI->connect("DBI:mysql:$dbname:$dbhost", "$dbuser", "$dbpass",
{
mysql_enable_utf8mb4 => 1,
}
) or die "Imposible conectar con la DB";
## Destrucción de la tabla, si existe
my $sth;
$sth= $db->prepare("DROP TABLE IF EXISTS `wow_classes_es`");
$sth->execute() or die "imposible borrar la tabla";
$sth->finish;
## Creacion de la tabla, si no existe
$sth = $db->prepare("
CREATE TABLE IF NOT EXISTS `blizzardrankings`.`wow_classes_es` (
`id` INT(2) NOT NULL,
`name` VARCHAR(32) NOT NULL,
`power_type` VARCHAR(32) NOT NULL,
`mask` INT(32) NOT NULL
) ENGINE = InnoDB CHARSET=utf8mb4;
");
$sth->execute() or die "imposible crear la tabla";
$sth->finish;
## Ingreso en la base de datos
my @classes = @{$json->{'classes'}};
for my $clase_ref (@classes) {
my $name = $clase_ref->{'name' };
my $power = $clase_ref->{'powerType'};
say "$name\t$power\t", (utf8::is_utf8($name) ? "sí":"no"); # sí
$sth = $db->prepare("
INSERT INTO wow_classes_es VALUES (
'$clase_ref->{'id'}' ,
'$name' ,
'$power' ,
'$clase_ref->{'mask'}'
)"
);
$sth->execute() or die "imposible hacer la consulta";
$sth->finish;
}
## Desconexión de la BD
$db->disconnect;
say "";
## Conexión a la base de datos
$db = DBI->connect("DBI:mysql:$dbname:$dbhost", "$dbuser", "$dbpass",
{
mysql_enable_utf8mb4 => 1,
}
) or die "Imposible conectar con la DB";
## Preparar la consulta
$sth = $db->prepare("
SELECT * FROM `wow_classes_es`;
");
$sth->execute() or die "imposible hacer la consulta";
$" = "\t|";
#binmode STDOUT, ':encoding(latin1)'; # método alternativo. Ver más abajo
while (my @fila = $sth->fetchrow_array) {
# Perl no sabe que esta recibiendo datos en utf8, asi que los decodificamos
$fila[1] = decode_utf8($fila[1]) ; # `name`
$fila[2] = decode_utf8($fila[2]) ; # `power`
say "@fila"; # la salida será vuelta a codificar a UTF8 por acción del "use open"
# Parece un poco raro todo esto, ¿no?
#
# Estamos recibiendo datos en UF8, los decodificamos a Unicode, y luego en la salida, "use open", vuelven a
# codificarse en UTF8 para la terminal.
#
# Hay otra forma, desde luego:
#
# Quitamos o comentamos la línea del "use open".
# Perl seguirá sacando el contenido de las variables como si fuese un flujo de bytes.
# Ahora comentamos las líneas decode_utf8() de este while() para no hacer ninguna codificación.
# Lo que sale por el say() son los datos en bruto de la base de datos. Como YA están en UTF8, pues
# eso es justo lo que recibe la terminal.
}
$sth->finish;
## Desconexión de la BD
$db->disconnect;
Coloreado en 0.005 segundos, usando
GeSHi 1.0.8.4
Este programa saca esto:
Using text Syntax Highlighting
Guerrero rage no
Paladín mana sí
Cazador focus no
Pícaro energy sí
Sacerdote mana no
Caballero de la Muerte runic-power no
Chamán mana sí
Mago mana no
Brujo mana no
Monje energy no
Druida mana no
Cazador de demonios fury no
1 |Guerrero |rage |1
2 |Paladín |mana |2
3 |Cazador |focus |4
4 |Pícaro |energy |8
5 |Sacerdote |mana |16
6 |Caballero de la Muerte |runic-power |32
7 |Chamán |mana |64
8 |Mago |mana |128
9 |Brujo |mana |256
10 |Monje |energy |512
11 |Druida |mana |1024
12 |Cazador de demonios |fury |2048
Coloreado en 0.000 segundos, usando
GeSHi 1.0.8.4
Puntos importantes:
* En vez de
IO::All voy a usar
LWP::Simple. El resultado es exactamente igual: se baja el json, sin decodificar nada
* El módulo JSON tiene una función llamada
decode_json que hace todo: decodifica desde UTF8 y pasa el json a estructura Perl
* Luego hacemos la conexión con la opción
mysql_enable_utf8mb4 => 1 en el connect()
* Hacemos el DROP y el CREATE.
¡Ojo! Fíjate que lo he simplificado bastante
* Luego hacemos el INSERT. Como los datos que hay dentro de la estructura ya están en UTF8, se los pasamos a la base de datos
directamente. Supongo que ya te has dado cuenta que $power no estaba en su sitio
* Cerramos la BD, y la abrimos otra vez para ver cómo acceder a ella
* Hacemos la consulta y luego el bucle, recuperando fila por fila, y aquí es donde se pone la cosa interesante...
* Ves en el código que lo que recibo de la BD lo decodifico de UTF8 a Unicode interno. Y luego, por medio del say(), los datos se vuelven a codificar a UTF8 (por efecto del "use open" al principio del programa).
Como te pongo en los comentarios,
esto parece raro. ¿Decodificamos y luego a la salida los datos vuelven a codificarse?
Bueno, tenemos que hacerlo así porque
Perl no sabe que lo que recibe de la BD está en UTF8.
Podríamos...
hacer un atajo: si la salida estándar STDOUT no estuviera en :utf8, entonces podríamos enchufar los datos que recibimos de la BD a la terminal (que suponemos en UTF8).
Esto lo podemos hacer de varias formas, pero depende de lo que necesitemos en todo el programa. Es decir, si el "use open" no nos sirve para mucho, pues lo podemos quitar, por lo que entonces ya no necesitamos los decode_utf8() del bucle while(), y mandamos los datos a terminal de forma directa.
Si pensamos que "use open" no es muy útil (y en realidad así lo es), podemos crear un atajo, justo antes del bucle while():
binmode STDOUT, ':encoding(latin1)';
De esta manera, le decimos a Perl que la salida estándar vuelve a ser latin1, que es lo mismo que decir que no queremos que haga ninguna transformación de los datos. Comentamos las líneas decode_utf8(), y ya está: los datos con UTF8 desde la BD sale a la terminal directamente. Si la terminal está en UTF8, pues ya los vemos bien.