Trabajar con símbolos y páginas de códigos

Dado que las cadenas están formadas por caracteres, a veces es necesario o simplemente más conveniente manipular caracteres individuales o grupos de caracteres de una cadena a nivel de los códigos de sus enteros. Por ejemplo: usted necesita leer o sustituir caracteres de uno en uno o convertirlos en arrays de códigos de enteros para su transmisión a través de protocolos de comunicación o en interfaces de programación de terceros de bibliotecas dinámicas DLL. En todos estos casos, pasar cadenas como texto puede ir acompañado de diversas dificultades:

  • garantizar la codificación correcta (de las que hay muchas, y su elección depende de la configuración regional del sistema operativo, los ajustes del programa, la configuración de los servidores con los que se comunica, etc.);
  • la conversión de los caracteres de lenguas nacionales desde la codificación de texto local a Unicode y viceversa;
  • la asignación y anulación de la asignación de memoria de forma unificada.

El uso de arrays con códigos de enteros (aunque tal uso produce en realidad una representación binaria y no textual de la cadena) simplifica estos problemas.

La API de MQL5 proporciona un conjunto de funciones para operar con caracteres individuales o sus grupos, teniendo en cuenta las características de codificación.

Las cadenas en MQL5 contienen caracteres en codificación Unicode de dos bytes, lo que proporciona una compatibilidad universal para toda la variedad de alfabetos nacionales en una única (pero muy grande) tabla de caracteres. Dos bytes permiten codificar 65535 elementos.

El tipo de carácter por defecto es ushort. No obstante, si es necesario, la cadena puede convertirse en una secuencia de caracteres uchar de un solo byte en una codificación lingüística específica. Esta conversión puede ir acompañada de la pérdida de cierta información (en particular, las letras que no figuran en la tabla de caracteres localizados pueden «perder» diéresis o incluso «convertirse» en algún tipo de carácter sustitutivo: según el contexto, puede mostrarse de forma diferente, pero normalmente aparece como ' ?' o un carácter cuadrado).

Para evitar problemas con textos que puedan contener caracteres arbitrarios, se recomienda utilizar siempre Unicode. Se puede hacer una excepción si algunos servicios o programas externos que deben integrarse con su programa MQL no admiten Unicode, o si el texto ha sido concebido desde el principio para almacenar un conjunto limitado de caracteres (por ejemplo, sólo números y letras latinas).

Al convertir a/desde caracteres de un solo byte, la API de MQL5 utiliza la codificación ANSI por defecto, dependiendo de la configuración actual de Windows. No obstante, el desarrollador puede especificar una tabla de códigos diferente (véanse otras funciones CharArrayToString, StringToCharArray).

En el archivo StringSymbols.mq5 se ofrecen ejemplos de utilización de las funciones descritas más abajo.

bool StringSetCharacter(string &variable, int position, ushort character)

La función cambia el carácter en position al valor character en la cadena variable pasada. El número debe estar comprendido entre 0 y la longitud de la cadena (StringLen) menos 1.

Si el carácter que se va a escribir es 0, especifica un nuevo final de línea (actúa como un cero terminal), es decir, la longitud de la línea pasa a ser igual a position. El tamaño del búfer asignado a la línea no cambia.

Si el parámetro position es igual a la longitud de la cadena y el carácter que se está escribiendo no es igual a 0, entonces el carácter se añade a la cadena y su longitud se incrementa en 1. Esto equivale a la expresión variable += ShortToString(character).

La función devuelve true si se completa con éxito, o false en caso de error.

void OnStart()
{
   string numbers = "0123456789";
   PRT(numbers);
   PRT(StringSetCharacter(numbers70));   // cut off at the 7th character
   PRT(numbers);                             // 0123456
   PRT(StringSetCharacter(numbersStringLen(numbers), '*')); // add '*'
   PRT(numbers);                             // 0123456*
   ...
}

 

ushort StringGetCharacter(string value, int position)

La función devuelve el código del carácter situado en la posición especificada de la cadena. El número de posición debe estar comprendido entre 0 y la longitud de la cadena (StringLen) menos 1. En caso de error, la función devolverá 0.

La función equivale a escribir utilizando el operador '[]': value[position].

   string numbers = "0123456789";
   PRT(StringGetCharacter(numbers5));      // 53 = code '5'
   PRT(numbers[5]);                          // 53 - is the same 

 

string CharToString(uchar code)

La función convierte el código ANSI de un carácter en una cadena de un solo carácter. Dependiendo de la página de códigos de Windows configurada, la mitad superior de los códigos (superior a 127) puede generar letras diferentes (el estilo de los caracteres es diferente, mientras que el código sigue siendo el mismo). Por ejemplo, el símbolo con el código 0xB8 (184 en decimal) indica una cedilla (gancho inferior) en los idiomas de Europa occidental, mientras que en la lengua rusa aquí se encuentra la letra «ё». He aquí otro ejemplo:

   PRT(CharToString(0xA9));   // "©"
  PRT(CharToString(0xE6));   // "æ", "ж", or another character
                             // depending on your Windows locale

 

string ShortToString(ushort code)

La función convierte el código Unicode de un carácter en una cadena de un solo carácter. Para el parámetro code puede utilizar un literal o un entero. Por ejemplo, la letra griega mayúscula «sigma» (el signo de la suma en las fórmulas matemáticas) puede especificarse como 0x3A3 o 'Σ'.

   PRT(ShortToString(0x3A3)); // "Σ"
   PRT(ShortToString('Σ'));   // "Σ"

 

int StringToShortArray(const string text, ushort &array[], int start = 0, int count = -1)

La función convierte una cadena en una secuencia de códigos de caracteres ushort que se copian en la ubicación especificada del array: empezando por el elemento numerado start (0 por defecto, es decir, el principio del array) y en la cantidad de count.

Nota: el parámetro start se refiere a la posición en el array, no en la cadena. Si desea convertir parte de una cadena, primero debe extraerla utilizando la función StringSubstr.

Si el parámetro count es igual a -1 (o WHOLE_ARRAY), se copian todos los caracteres hasta el final de la cadena (incluido el nulo terminal) o los caracteres de acuerdo con el tamaño del array, si es de tamaño fijo.

En el caso de un array dinámico, su tamaño se incrementará automáticamente si es necesario. Si el tamaño de un array dinámico es mayor que la longitud de la cadena, no se reduce el tamaño del array.

Para copiar caracteres sin un nulo de terminación debe llamar explícitamente a StringLen como argumento count. En caso contrario, la longitud del array será 1 más que la longitud de la cadena (y 0 en el último elemento).

La función devuelve el número de caracteres copiados.

   ...
   ushort array1[], array2[]; // dynamic arrays 
   ushort text[5];            // fixed size array 
   string alphabet = "ABCDEАБВГД";
   // copy with the terminal '0'
   PRT(StringToShortArray(alphabetarray1)); // 11
   ArrayPrint(array1); // 65   66   67   68   69 1040 1041 1042 1043 1044    0
   // copy without the terminal '0'
   PRT(StringToShortArray(alphabetarray20StringLen(alphabet))); // 10
   ArrayPrint(array2); // 65   66   67   68   69 1040 1041 1042 1043 1044
   // copy to a fixed array 
   PRT(StringToShortArray(alphabettext)); // 5
   ArrayPrint(text); // 65 66 67 68 69
   // copy beyond the previous limits of the array 
   // (elements [11-19] will be random)
   PRT(StringToShortArray(alphabetarray220)); // 11
   ArrayPrint(array2);
   /*
   [ 0]    65    66    67    68    69  1040  1041  1042
         1043  1044     0     0     0     0     0 14245
   [16] 15102 37754 48617 54228    65    66    67    68
           69  1040  1041  1042  1043  1044     0
   */

Tenga en cuenta que, si la posición para copiar supera el tamaño del array, los elementos intermedios se asignarán pero no se inicializarán. En consecuencia, pueden contener datos aleatorios (resaltados en amarillo más arriba).

string ShortArrayToString(const ushort &array[], int start = 0, int count = -1)

La función convierte parte del array con códigos de caracteres en una cadena. El rango de los elementos del array se establece mediante los parámetros start y count, es decir, la posición inicial y la cantidad, respectivamente. El parámetro start debe estar comprendido entre 0 y el número de elementos del array menos 1. Si count es igual a -1 (o WHOLE_ARRAY) se copian todos los elementos hasta el final del array o hasta el primer elemento nulo.

Utilizando el mismo ejemplo de StringSymbols.mq5, vamos a intentar convertir un array en la cadena array2, que tiene un tamaño de 30.

   ...
   string s = ShortArrayToString(array2030);
   PRT(s); // "ABCDEАБВГД", additional random characters may appear here

Dado que en el array array2 la cadena «ABCDEABCD» se copió dos veces, y concretamente, la primera vez al principio y la segunda vez en el desplazamiento 20, los caracteres intermedios serán aleatorios y podrán formar una cadena más larga que la nuestra.

int StringToCharArray(const string text, uchar &array[], int start = 0, int count = -1, uint codepage = CP_ACP)

La función convierte la cadena text en una secuencia de caracteres de un solo byte que se copian en la ubicación especificada del array: empezando por el elemento numerado start (0 por defecto, es decir, el principio del array) y en la cantidad de count. El proceso de copia convierte los caracteres de Unicode a la página de códigos seleccionada codepage (por defecto, CP_ACP, que significa el idioma del sistema operativo Windows (más adelante encontrará más información al respecto)).

Si el parámetro count es igual a -1 (o WHOLE_ARRAY), se copian todos los caracteres hasta el final de la cadena (incluido el nulo terminal) o de acuerdo con el tamaño del array, si es de tamaño fijo.

En el caso de un array dinámico, su tamaño se incrementará automáticamente si es necesario. Si el tamaño de un array dinámico es mayor que la longitud de la cadena, no se reduce el tamaño del array.

Para copiar caracteres sin un nulo de terminación debe llamar explícitamente a StringLen como argumento count.

La función devuelve el número de caracteres copiados.

Consulte la lista de páginas de códigos válidas para el parámetro codepage en la documentación. Estas son algunas de las páginas de códigos ANSI más utilizadas:

Idioma

Código

Alfabeto latino de Europa central

1250

Cirílico

1251

Alfabeto latino de Europa occidental

1252

Griego

1253

Turco

1254

Hebreo

1255

Árabe

1256

Báltico

1257

Así, en ordenadores con idiomas de Europa occidental, CP_ACP es 1252, y, por ejemplo, en ordenadores con ruso, es 1251.

Durante el proceso de conversión, algunos caracteres pueden convertirse con pérdida de información, ya que la tabla Unicode es mucho mayor que ANSI (cada tabla de códigos ANSI tiene 256 caracteres).

En este sentido, CP_UTF8 reviste especial importancia entre todas las constantes CP_***. Permite conservar adecuadamente los caracteres nacionales mediante la codificación de longitud variable: el array resultante sigue almacenando bytes, pero cada carácter nacional puede abarcar varios bytes, escritos en un formato especial. Por ello, la longitud del array puede ser significativamente mayor que la longitud de la cadena. La codificación UTF-8 se utiliza ampliamente en Internet y en diversos programas informáticos. Por cierto: UTF significa «Unicode Transformation Format» (formato de transformación Unicode), y existen otras modificaciones, en particular UTF-16 y UTF-32.

Veremos un ejemplo para StringToCharArray una vez que nos familiaricemos con la función «inversa» CharArrayToString: su trabajo debe demostrarse conjuntamente.

string CharArrayToString(const uchar &array[], int start = 0, int count = -1, uint codepage = CP_ACP)

La función convierte un array de bytes o parte de él en una cadena. El array debe contener caracteres en una codificación específica. El rango de los elementos del array se establece mediante los parámetros start y count, es decir, la posición inicial y la cantidad, respectivamente. El parámetro start debe estar comprendido entre 0 y el número de elementos del array. Cuando count es igual a -1 (o WHOLE_ARRAY) se copian todos los elementos hasta el final del array o hasta el primer elemento nulo.

Veamos cómo funcionan las funciones StringToCharArray y CharArrayToString con diferentes caracteres nacionales con diferentes configuraciones de página de códigos. Para ello se ha preparado un script de prueba StringCodepages.mq5.

Se utilizarán dos líneas como sujetos de prueba, en ruso y en alemán:

void OnStart()
{
   Print("Locales");
   uchar bytes1[], bytes2[];
 
   string german = "straßenführung";
   string russian = "Russian text";
   ...

Las copiaremos en los arrays bytes1 y bytes2, y luego las recuperaremos como cadenas.

En primer lugar, convirtamos el texto alemán utilizando la página de códigos europeos 1252.

   ...
   StringToCharArray(germanbytes10WHOLE_ARRAY1252);
   ArrayPrint(bytes1);
   // 115 116 114  97 223 101 110 102 252 104 114 117 110 103   0

En las copias europeas de Windows, esto equivale a la llamada a una función más sencilla con parámetros por defecto, porque allí CP_ACP = 1252:

   StringToCharArray(germanbytes1);

A continuación, restauramos el texto del array con la siguiente llamada y nos aseguramos de que todo coincide con el original:

   ...
   PRT(CharArrayToString(bytes10WHOLE_ARRAY1252));
   // CharArrayToString(bytes1,0,WHOLE_ARRAY,1252)='straßenführung'

Ahora vamos a intentar convertir el texto ruso en la misma codificación europea (o puede llamar a StringToCharArray(english, bytes2) en el entorno de Windows donde CP_ACP está configurado en 1252 como página de códigos por defecto):

   ...
   StringToCharArray(russianbytes20WHOLE_ARRAY1252);
   ArrayPrint(bytes2);
   // 63 63 63 63 63 63 63 32 63 63 63 63 63  0

Aquí ya puede ver que hubo un problema durante la conversión porque 1252 no tiene cirílico. Restaurar una cadena a partir de un array muestra claramente la esencia:

   ...
   PRT(CharArrayToString(bytes20WHOLE_ARRAY1252));
   // CharArrayToString(bytes2,0,WHOLE_ARRAY,1252)='??????? ?????'

Repitamos el experimento en un entorno ruso condicional, es decir, convertiremos ambas cadenas de ida y vuelta utilizando la página de códigos cirílicos 1251.

   ...
   StringToCharArray(russianbytes20WHOLE_ARRAY1251);
   // on Russian Windows, this call is equivalent to a simpler one
   // StringToCharArray(russian, bytes2);
   // because CP_ACP = 1251
   ArrayPrint(bytes2); // this time the character codes are meaningful
   // 208 243 241 241 234 232 233  32 210 229 234 241 242   0
   
   // restore the string and make sure it matches the original
   PRT(CharArrayToString(bytes20WHOLE_ARRAY1251));
   // CharArrayToString(bytes2,0,WHOLE_ARRAY,1251)='Русский Текст'
   
   // and for the German text...
   StringToCharArray(germanbytes10WHOLE_ARRAY1251);
   ArrayPrint(bytes1);
   // 115 116 114  97  63 101 110 102 117 104 114 117 110 103   0
   // if we compare this content of bytes1 with the previous version,
   // it's easy to see that a couple of characters are affected; here's what happened:
   // 115 116 114  97 223 101 110 102 252 104 114 117 110 103   0
   
   // restore the string to see the differences visually:
   PRT(CharArrayToString(bytes10WHOLE_ARRAY1251));
   // CharArrayToString(bytes1,0,WHOLE_ARRAY,1251)='stra?enfuhrung'
   // specific German characters were corrupted

Se hace así evidente la fragilidad de las codificaciones de un solo byte.

Por último, activemos la codificación CP_UTF8 para ambas cadenas de prueba. Esta parte del ejemplo funcionará de forma estable independientemente de la configuración de Windows.

   ...
   StringToCharArray(germanbytes10WHOLE_ARRAYCP_UTF8);
   ArrayPrint(bytes1);
   // 115 116 114  97 195 159 101 110 102 195 188 104 114 117 110 103   0
   PRT(CharArrayToString(bytes10WHOLE_ARRAYCP_UTF8));
   // CharArrayToString(bytes1,0,WHOLE_ARRAY,CP_UTF8)='straßenführung'
   
   StringToCharArray(russianbytes20WHOLE_ARRAYCP_UTF8);
   ArrayPrint(bytes2);
   // 208 160 209 131 209 129 209 129 208 186 208 184 208 185
   //  32 208 162 208 181 208 186 209 129 209 130   0
   PRT(CharArrayToString(bytes20WHOLE_ARRAYCP_UTF8));
   // CharArrayToString(bytes2,0,WHOLE_ARRAY,CP_UTF8)='Русский Текст'

Observe que ambas cadenas codificadas en UTF-8 requieren arrays más grandes que las ANSI. Además, el array con el texto ruso se ha hecho 2 veces más largo, porque ahora todas las letras ocupan 2 bytes. Quienes lo deseen pueden encontrar detalles en fuentes abiertas sobre cómo funciona exactamente la codificación UTF-8. En el contexto de este libro, es importante para nosotros que la API de MQL5 proporcione funciones ya hechas con las que trabajar.