''Confiable'' SMS codificación Unicode y GSM en PHP
(6)
( Actualizado un poco )
No tengo mucha experiencia con la internacionalización usando PHP, debe decirse, y una gran cantidad de búsquedas realmente no proporcionaron las respuestas que estaba buscando.
Necesito encontrar una forma confiable de convertir solo texto ''relevante'' a Unicode para enviar un mensaje SMS, usando PHP (solo temporalmente, mientras un servicio se reescribe usando C #) - obviamente, los mensajes enviados en este momento son enviado como texto sin formato.
Podría concebiblemente convertir todo al juego de caracteres Unicode (en lugar de utilizar el juego de caracteres GSM estándar), pero eso significaría que todos los mensajes estarían limitados a 70 caracteres (en lugar de 160).
Entonces, supongo que mi verdadera pregunta es: ¿cuál es la forma más confiable de detectar el requisito de que un mensaje sea codificado en Unicode, por lo que solo tengo que hacerlo cuando sea absolutamente necesario (por ejemplo, para caracteres que no sean de idioma latino)?
Información agregada:
Está bien, así que me he pasado la mañana trabajando en esto, y todavía estoy más avanzado que cuando comencé (sin duda debido a mi total falta de competencia en lo que respecta a la conversión del juego de caracteres). Así que aquí está el escenario revisado:
Tengo mensajes de texto SMS provenientes de una fuente externa, esta fuente externa me proporciona las respuestas en texto sin formato + caracteres unicode con barras escapadas. Por ejemplo, el texto ''mostrado'':
Probemos öäü éàè אין תמיכה בעברית
Devoluciones:
Probemos / u00f6 / u00e4 / u00fc / u00e9 / u00e0 / u00e8 / u05d0 / u05d9 / u05df / u05ea / u05de / u05d9 / u05db / u05d4 / u05d1 / u05e2 / u05d1 / u05e8 / u05d9 / u05ea
Ahora puedo enviarlo a mi proveedor de SMS en texto plano, GSM 03.38 o Unicode. Obviamente, enviar lo anterior como texto sin formato da como resultado una gran cantidad de caracteres faltantes (son reemplazados por espacios por mi proveedor) - Necesito adaptarme en relación con el contenido que hay. Lo que quiero hacer con esto es lo siguiente:
Si todo el texto está dentro de la página de códigos GSM 03.38 , envíela como está. (Todos menos los caracteres hebreos anteriores se ajustan a esta categoría, pero deben convertirse).
De lo contrario, conviértalo a Unicode y envíelo a varios mensajes (ya que el límite de Unicode es de 70 caracteres y no de 160 para un SMS).
Como dije antes, estoy perplejo al hacer esto en PHP (C # no era un gran problema debido a algunas funciones de conversión simples integradas), pero es bastante probable que me esté perdiendo lo obvio, aquí. Tampoco pude encontrar clases de conversión prefabricadas para la codificación de 7 bits en PHP, y mis intentos de convertir la cadena yo mismo y enviarla parecían inútiles.
Cualquier ayuda sería muy apreciada.
function is_gsm0338( $utf8_string ) {
$gsm0338 = array(
''@'',''Δ'','' '',''0'',''¡'',''P'',''¿'',''p'',
''£'',''_'',''!'',''1'',''A'',''Q'',''a'',''q'',
''$'',''Φ'',''"'',''2'',''B'',''R'',''b'',''r'',
''¥'',''Γ'',''#'',''3'',''C'',''S'',''c'',''s'',
''è'',''Λ'',''¤'',''4'',''D'',''T'',''d'',''t'',
''é'',''Ω'',''%'',''5'',''E'',''U'',''e'',''u'',
''ù'',''Π'',''&'',''6'',''F'',''V'',''f'',''v'',
''ì'',''Ψ'',''/''',''7'',''G'',''W'',''g'',''w'',
''ò'',''Σ'',''('',''8'',''H'',''X'',''h'',''x'',
''Ç'',''Θ'','')'',''9'',''I'',''Y'',''i'',''y'',
"/n",''Ξ'',''*'','':'',''J'',''Z'',''j'',''z'',
''Ø'',"/x1B",''+'','';'',''K'',''Ä'',''k'',''ä'',
''ø'',''Æ'','','',''<'',''L'',''Ö'',''l'',''ö'',
"/r",''æ'',''-'',''='',''M'',''Ñ'',''m'',''ñ'',
''Å'',''ß'',''.'',''>'',''N'',''Ü'',''n'',''ü'',
''å'',''É'',''/'',''?'',''O'',''§'',''o'',''à''
);
$len = mb_strlen( $utf8_string, ''UTF-8'');
for( $i=0; $i < $len; $i++)
if (!in_array(mb_substr($utf8_string,$i,1,''UTF-8''), $gsm0338))
return false;
return true;
}
Sé que esto no es código php, pero creo que podría ayudar de todos modos. Así es como lo hago en una aplicación que escribí para detectar si es posible enviarlo como GSM 03.38 (podría hacer algo similar para texto sin formato). Tiene dos tablas de traducción, una para GSM normal y una para el extendido. Y luego una función que recorre todos los caracteres comprobando si se puede convertir.
#define UCS2_TO_GSM_LOOKUP_TABLE_SIZE 0x100
#define NON_GSM 0x80
#define UCS2_GCL_RANGE 24
#define UCS2_GREEK_CAPITAL_LETTER_ALPHA 0x0391
#define EXTEND 0x001B
// note that the ` character is mapped to '' so that all characters that can be typed on
// a standard north american keyboard can be converted to the GSM default character set
static unsigned char Ucs2ToGsm[UCS2_TO_GSM_LOOKUP_TABLE_SIZE] =
{ /*+0x0 +0x1 +0x2 +0x3 +0x4 +0x5 +0x6 +0x7*/
/*0x00*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0x08*/ NON_GSM, NON_GSM, 0x0a, NON_GSM, NON_GSM, 0x0d, NON_GSM, NON_GSM,
/*0x10*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0x18*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0x20*/ 0x20, 0x21, 0x22, 0x23, 0x02, 0x25, 0x26, 0x27,
/*0x28*/ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
/*0x30*/ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
/*0x38*/ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
/*0x40*/ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
/*0x48*/ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
/*0x50*/ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
/*0x58*/ 0x58, 0x59, 0x5a, EXTEND, EXTEND, EXTEND, EXTEND, 0x11,
/*0x60*/ 0x27, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
/*0x68*/ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
/*0x70*/ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
/*0x78*/ 0x78, 0x79, 0x7a, EXTEND, EXTEND, EXTEND, EXTEND, NON_GSM,
/*0x80*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0x88*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0x90*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0x98*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0xa0*/ NON_GSM, 0x40, NON_GSM, 0x01, 0x24, 0x03, NON_GSM, 0x5f,
/*0xa8*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0xb0*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM,
/*0xb8*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, 0x60,
/*0xc0*/ NON_GSM, NON_GSM, NON_GSM, NON_GSM, 0x5b, 0x0e, 0x1c, 0x09,
/*0xc8*/ NON_GSM, 0x1f, NON_GSM, NON_GSM, NON_GSM, NON_GSM, NON_GSM, 0x60,
/*0xd0*/ NON_GSM, 0x5d, NON_GSM, NON_GSM, NON_GSM, NON_GSM, 0x5c, NON_GSM,
/*0xd8*/ 0x0b, NON_GSM, NON_GSM, NON_GSM, 0x5e, NON_GSM, NON_GSM, 0x1e,
/*0xe0*/ 0x7f, NON_GSM, NON_GSM, NON_GSM, 0x7b, 0x0f, 0x1d, NON_GSM,
/*0xe8*/ 0x04, 0x05, NON_GSM, NON_GSM, 0x07, NON_GSM, NON_GSM, NON_GSM,
/*0xf0*/ NON_GSM, 0x7d, 0x08, NON_GSM, NON_GSM, NON_GSM, 0x7c, NON_GSM,
/*0xf8*/ 0x0c, 0x06, NON_GSM, NON_GSM, 0x7e, NON_GSM, NON_GSM, NON_GSM
};
static unsigned char Ucs2GclToGsm[UCS2_GCL_RANGE + 1] =
{
/*0x0391*/ 0x41, // Alpha A
/*0x0392*/ 0x42, // Beta B
/*0x0393*/ 0x13, // Gamma
/*0x0394*/ 0x10, // Delta
/*0x0395*/ 0x45, // Epsilon E
/*0x0396*/ 0x5A, // Zeta Z
/*0x0397*/ 0x48, // Eta H
/*0x0398*/ 0x19, // Theta
/*0x0399*/ 0x49, // Iota I
/*0x039a*/ 0x4B, // Kappa K
/*0x039b*/ 0x14, // Lambda
/*0x039c*/ 0x4D, // Mu M
/*0x039d*/ 0x4E, // Nu N
/*0x039e*/ 0x1A, // Xi
/*0x039f*/ 0x4F, // Omicron O
/*0x03a0*/ 0X16, // Pi
/*0x03a1*/ 0x50, // Rho P
/*0x03a2*/ NON_GSM,
/*0x03a3*/ 0x18, // Sigma
/*0x03a4*/ 0x54, // Tau T
/*0x03a5*/ 0x59, // Upsilon Y
/*0x03a6*/ 0x12, // Phi
/*0x03a7*/ 0x58, // Chi X
/*0x03a8*/ 0x17, // Psi
/*0x03a9*/ 0x15 // Omega
};
bool Gsm0338Encoding::IsNotGSM( wchar_t szUnicodeChar )
{
bool result = true;
if( szUnicodeChar < UCS2_TO_GSM_LOOKUP_TABLE_SIZE )
{
result = ( Ucs2ToGsm[szUnicodeChar] == NON_GSM );
}
else if( (szUnicodeChar >= UCS2_GREEK_CAPITAL_LETTER_ALPHA) &&
(szUnicodeChar <= (UCS2_GREEK_CAPITAL_LETTER_ALPHA + UCS2_GCL_RANGE)) )
{
result = ( Ucs2GclToGsm[szUnicodeChar - UCS2_GREEK_CAPITAL_LETTER_ALPHA] == NON_GSM );
}
else if( szUnicodeChar == 0x20AC ) // €
{
result = false;
}
return result;
}
bool Gsm0338Encoding::IsGSM( const std::wstring& str )
{
bool result = true;
if( std::find_if( str.begin(), str.end(), IsNotGSM ) != str.end() )
{
result = false;
}
return result;
}
Para tratarlo conceptualmente antes de entrar en mecanismos, y disculpas si algo de esto es obvio, una cadena se puede definir como una secuencia de caracteres Unicode, Unicode es una base de datos que da un número de identificación conocido como punto de código para cada personaje que puedas necesito trabajar con. GSM-338 contiene un subconjunto de los caracteres Unicode, por lo que lo que está haciendo es extraer un conjunto de puntos de código de la cadena y verificar si ese conjunto está contenido en GSM-338.
// second column of http://unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT
$gsm338_codepoints = array(0x0040, 0x0000, ..., 0x00fc, 0x00e0)
$can_use_gsm338 = true;
foreach(codepoints($mystring) as $codepoint){
if(!in_array($codepoint, $gsm338_codepoints)){
$can_use_gsm338 = false;
break;
}
}
Eso deja la definición de los puntos de código de la función ($ string), que no está incorporada en PHP. PHP entiende que una cadena es una secuencia de bytes en lugar de una secuencia de caracteres Unicode. La mejor manera de cerrar la brecha es poner tus cadenas en UTF8 lo más rápido que puedas y mantenerlas en UTF8 todo el tiempo que puedas; tendrás que usar otras codificaciones cuando trabajes con sistemas externos, pero aíslas la conversión al interactuar con ese sistema y tratar solo con utf8 internamente.
Las funciones que necesita convertir entre cadenas php en utf8 y secuencias de puntos de código se pueden encontrar en http://hsivonen.iki.fi/php-utf8/ , así que esa es su función codepoints ().
Si está tomando datos de una fuente externa que le da caracteres escapados de barras Unicode ("Probemos / u00f6 / u00e4 / u00fc ..."), ese formato de escape de cadena debe convertirse a utf8. No sé de una función para hacer esto, si no se puede encontrar uno, es una cuestión de procesamiento de cadenas / expresiones regulares + el uso de las funciones hsivonen.iki.fi, por ejemplo, cuando toca / u00f6, reemplace con la representación utf8 del punto de código 0xf6.
Aunque este es un hilo viejo, recientemente tuve que resolver un problema muy similar y quería publicar mi respuesta. El código PHP es algo simple. Comienza con una gran cantidad de códigos de caracteres válidos GSM en una matriz, luego simplemente comprueba si el carácter actual está en esa matriz usando la función ord ($ string) que devuelve el valor ascii del primer carácter de la cadena pasada. Aquí está el código que uso para validar si una cadena vale GSM.
$valid_gsm_keycodes = Array(
0x0040, 0x0394, 0x0020, 0x0030, 0x00a1, 0x0050, 0x00bf, 0x0070,
0x00a3, 0x005f, 0x0021, 0x0031, 0x0041, 0x0051, 0x0061, 0x0071,
0x0024, 0x03a6, 0x0022, 0x0032, 0x0042, 0x0052, 0x0062, 0x0072,
0x00a5, 0x0393, 0x0023, 0x0033, 0x0043, 0x0053, 0x0063, 0x0073,
0x00e8, 0x039b, 0x00a4, 0x0034, 0x0035, 0x0044, 0x0054, 0x0064, 0x0074,
0x00e9, 0x03a9, 0x0025, 0x0045, 0x0045, 0x0055, 0x0065, 0x0075,
0x00f9, 0x03a0, 0x0026, 0x0036, 0x0046, 0x0056, 0x0066, 0x0076,
0x00ec, 0x03a8, 0x0027, 0x0037, 0x0047, 0x0057, 0x0067, 0x0077,
0x00f2, 0x03a3, 0x0028, 0x0038, 0x0048, 0x0058, 0x0068, 0x0078,
0x00c7, 0x0398, 0x0029, 0x0039, 0x0049, 0x0059, 0x0069, 0x0079,
0x000a, 0x039e, 0x002a, 0x003a, 0x004a, 0x005a, 0x006a, 0x007a,
0x00d8, 0x001b, 0x002b, 0x003b, 0x004b, 0x00c4, 0x006b, 0x00e4,
0x00f8, 0x00c6, 0x002c, 0x003c, 0x004c, 0x00d6, 0x006c, 0x00f6,
0x000d, 0x00e6, 0x002d, 0x003d, 0x004d, 0x00d1, 0x006d, 0x00f1,
0x00c5, 0x00df, 0x002e, 0x003e, 0x004e, 0x00dc, 0x006e, 0x00fc,
0x00e5, 0x00c9, 0x002f, 0x003f, 0x004f, 0x00a7, 0x006f, 0x00e0 );
for($i = 0; $i < strlen($string); $i++) {
if(!in_array($string[$i], $valid_gsm_keycodes)) return false;
}
return true;
PHP6 tendrá mejor soporte Unicode, pero hay algunas funciones que puede usar.
Lo primero que pensé fue en mb_convert_encoding
pero, como dijiste, acortará los mensajes a 70 caracteres, por lo que quizás puedas usar esto junto con mb_detect_encoding
.
preg_match(''/^[/x0A/x0C/x0D/x20-/x5F/x61-/x7E/xA0/xA1/xA3-/xA5/xA7''.
''/xBF/xC4-/xC6/xC9/xD1/xD6/xD8/xDC/xDF/xE0/xE4-/xE9/xEC/xF1''.
''/xF2/xF6/xF8/xF9/xFC''.
json_decode(''"/u0393/u0394/u0398/u039B/u039E/u03A0/u03A3/u03A6/u03A8/u03A9/u20AC"'').
'']*$/u'', $text)
o
preg_match(''/^[/x0A/x0C/x0D/x20-/x5F/x61-/x7E/xA0/xA1/xA3-/xA5/xA7/xBF/xC4-/xC6/xC9/xD1/xD6/xD8/xDC/xDF/xE0/xE4-/xE9/xEC/xF1/xF2/xF6/xF8/xF9/xFCΓΔΘΛΞΠΣΦΨΩ€]*$/u'', $text)