PHP中的"可靠" SMS Unicode和GSM编码
(更新了一点)
必须说,我对使用PHP进行国际化的经验不是很丰富,而且大量的搜索并没有真正提供我想要的答案。
我需要找到一种可靠的方法,将仅将"相关的"文本转换为Unicode,以使用PHP发送SMS消息(只是暂时的,而使用C#重写服务),显然,当前发送的消息已发送作为纯文本。
我可以想象将所有内容都转换为Unicode字符集(与使用标准GSM字符集相对),但这意味着所有消息都将限制为70个字符(而不是160个字符)。
因此,我想我的真正问题是:检测消息是否为Unicode编码的最可靠方法是什么,因此仅在绝对必要时(例如非拉丁语言字符)才需要这样做?
添加的信息:
好的,所以我花了一整天的时间进行这项工作,但我仍然没有比开始时更深入的工作了(这肯定是由于我完全缺乏字符集转换能力)。因此,这是修改后的方案:
我有来自外部来源的短信,此外部来源以纯文本+ Unicode斜杠转义字符向我提供回复。例如。 "显示的"文本:
Let's test ??ü éàè ??? ????? ??????
返回值:
Let's test \u00f6\u00e4\u00fc \u00e9\u00e0\u00e8 \u05d0\u05d9\u05df \u05ea\u05de\u05d9\u05db\u05d4 \u05d1\u05e2\u05d1\u05e8\u05d9\u05ea
现在,我可以以纯文本,GSM 03.38或者Unicode格式发送到我的SMS提供商。显然,将以上内容作为纯文本发送会导致丢失许多字符(我的提供者将其替换为空格),我需要根据存在的内容进行调整。我要执行的操作如下:
- 如果所有文本都在GSM 03.38代码页中,请按原样发送。 (以上所有希伯来语字符都属于此类别,但需要进行转换。)
- 否则,将其转换为Unicode,然后通过多条消息发送(因为Unicode的限制是70个字符,而不是SMS的160个字符)。
就像我在上面说的那样,我对用PHP做到这一点感到很困惑(由于内置了一些简单的转换函数,所以问题不大),但是很可能我只是在这里遗漏了明显的问题。我也找不到用于PHP的7位编码的任何预制转换类,而我自己尝试转换字符串并将其发送的尝试似乎是徒劳的。
任何帮助将不胜感激。
解决方案
回答
PHP6将具有更好的unicode支持,但是我们可以使用一些功能。
我的第一个想法是" mb_convert_encoding",但是正如我们所说的那样,这会将消息缩短到70个字符,因此也许我们可以将其与" mb_detect_encoding"结合使用?
请参阅:多字节函数
回答
我知道这不是PHP代码,但无论如何我都认为有帮助。这就是我在编写的应用程序中执行的操作,该应用程序检测它是否可以作为GSM 03.38发送(我们可以对纯文本执行类似的操作)。它有两个转换表,一个用于普通GSM,一个用于扩展。然后是一个循环遍历所有字符的函数,以检查是否可以转换。
#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; }
回答
为了在概念上先于机制而处理,如果有任何明显的道歉,可以将字符串定义为Unicode字符序列,Unicode是一个数据库,该数据库为每个我们可能输入的字符提供一个称为代码点的ID号需要一起工作。 GSM-338包含Unicode字符的子集,因此我们要做的是从字符串中提取一组代码点,并检查该代码点是否包含在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; } }
这样就留下了功能codepoints($ string)的定义,该定义未内置在PHP中。 PHP将字符串理解为字节序列,而不是Unicode字符序列。弥合鸿沟的最佳方法是尽快将字符串放入UTF8,并尽可能将其保留在UTF8中,只要在与外部系统打交道时就必须使用其他编码,但要隔离到接口的转换到该系统,并且仅在内部处理utf8.
我们可以在http://hsivonen.iki.fi/php-utf8/中找到在utf8中的php字符串和代码点序列之间进行转换所需的函数,这就是codepoints()函数。
如果我们要从提供Unicode斜杠转义字符的外部源获取数据("让我们测试\ u00f6 \ u00e4 \ u00fc ..."),则应将该字符串转义格式转换为utf8. 我不知道这样做的功能,如果找不到,则是字符串/正则表达式处理+使用hsivonen.iki.fi函数的问题,例如当我们按\ u00f6时,它与代码点0xf6的utf8表示形式相同。
回答
尽管这是一个老话题,但我最近不得不解决一个非常相似的问题,并希望发布我的答案。 PHP代码有些简单。它从一个数组中的大量GSM有效字符代码开始,然后使用ord($ string)函数简单地检查当前字符是否在该数组中,该函数返回所传递字符串的第一个字符的ascii值。这是我用来验证字符串是否价值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;