2009-09-08 16 views
8

En PHP < 6, ¿cuál es la mejor manera de dividir una cadena en una matriz de caracteres Unicode? Si la entrada no es necesariamente UTF-8?¿Cuál es la mejor manera de dividir una cadena en una matriz de caracteres Unicode en PHP?

Quiero saber si el conjunto de caracteres Unicode en una cadena de entrada es un subconjunto de otro conjunto de caracteres Unicode.

¿Por qué no se ejecuta directamente para la familia de funciones mb_, ya que las primeras dos respuestas no?

+1

¿Se da cuenta de que la comparación de caracteres Unicode no es trivial, dependiendo del tipo de comparación que desee? Por ejemplo, puede escribir ü como U + 00DC o como U + 0075 U + 0308. – derobert

+0

Sí, me doy cuenta de eso. Si se convirtió en un problema, entonces necesitaría transformar la entrada a una de las formas normales Unicode antes de la división. – joeforker

Respuesta

14

podría utilizar la 'U' modificador con expresiones regulares PCRE; ver Pattern Modifiers (donde se cita):

u (PCRE8)

Este modificador se enciende funcionalidad adicional de PCRE que es incompatible con Perl. Las cadenas de patrón se tratan como UTF-8. Este modificador está disponible desde PHP 4.1.0 o superior en Unix y desde PHP 4.2.3 en win32. La validez de UTF-8 del patrón se comprueba desde PHP 4.3.5.

Por ejemplo, teniendo en cuenta este código:

header('Content-type: text/html; charset=UTF-8'); // So the browser doesn't make our lives harder 
$str = "abc 文字化け, efg"; 

$results = array(); 
preg_match_all('/./', $str, $results); 
var_dump($results[0]); 

obtendrá un resultado inservible:

array 
    0 => string 'a' (length=1) 
    1 => string 'b' (length=1) 
    2 => string 'c' (length=1) 
    3 => string ' ' (length=1) 
    4 => string '�' (length=1) 
    5 => string '�' (length=1) 
    6 => string '�' (length=1) 
    7 => string '�' (length=1) 
    8 => string '�' (length=1) 
    9 => string '�' (length=1) 
    10 => string '�' (length=1) 
    11 => string '�' (length=1) 
    12 => string '�' (length=1) 
    13 => string '�' (length=1) 
    14 => string '�' (length=1) 
    15 => string '�' (length=1) 
    16 => string ',' (length=1) 
    17 => string ' ' (length=1) 
    18 => string 'e' (length=1) 
    19 => string 'f' (length=1) 
    20 => string 'g' (length=1) 

Pero, con este código:

header('Content-type: text/html; charset=UTF-8'); // So the browser doesn't make our lives harder 
$str = "abc 文字化け, efg"; 

$results = array(); 
preg_match_all('/./u', $str, $results); 
var_dump($results[0]); 

(Observe la 'u' al final de la expresión regular)

se obtiene lo que quiere:

array 
    0 => string 'a' (length=1) 
    1 => string 'b' (length=1) 
    2 => string 'c' (length=1) 
    3 => string ' ' (length=1) 
    4 => string '文' (length=3) 
    5 => string '字' (length=3) 
    6 => string '化' (length=3) 
    7 => string 'け' (length=3) 
    8 => string ',' (length=1) 
    9 => string ' ' (length=1) 
    10 => string 'e' (length=1) 
    11 => string 'f' (length=1) 
    12 => string 'g' (length=1) 

Esperanza esto ayuda :-)

+0

+1 buen ejemplo detallado! :) –

+0

@Shadi Almosri: gracias :-) –

5

Prueba esto:

preg_match_all('/./u', $text, $array); 
+0

+1 ¡Eso es inteligente! – Gumbo

1

Si por alguna razón el camino expresión regular no es suficiente para ti. Una vez escribí el Zend_Locale_UTF8 que está abandonado, pero podría ayudarte si decides hacerlo por tu cuenta.

En particular, eche un vistazo a la clase Zend_Locale_UTF8_PHP5_String que lee en cadenas Unicode y para trabajar con ellas las divide en caracteres únicos (que pueden consistir obviamente en varios bytes).

EDITAR: acabo relaized que SVN navegador de ZF se ha reducido por lo copié los métodos importantes para mayor comodidad:

/** 
* Returns the UTF-8 code sequence as an array for any given $string. 
* 
* @access protected 
* @param string|integer $string 
* @return array 
*/ 
protected function _decode($string) { 

    $string  = (string) $string; 
    $length  = strlen($string); 
    $sequence = array(); 

    for ($i=0; $i<$length;) { 
     $bytes  = $this->_characterBytes($string, $i); 
     $ord  = $this->_ord($string, $bytes, $i); 

     if ($ord !== false) 
      $sequence[] = $ord; 

     if ($bytes === false) 
      $i++; 
     else 
      $i += $bytes; 
    } 

    return $sequence; 

} 

/** 
* Returns the UTF-8 code of a character. 
* 
* @see http://en.wikipedia.org/wiki/UTF-8#Description 
* @access protected 
* @param string $string 
* @param integer $bytes 
* @param integer $position 
* @return integer 
*/ 
protected function _ord(&$string, $bytes = null, $pos=0) 
{ 
    if (is_null($bytes)) 
     $bytes = $this->_characterBytes($string); 

    if (strlen($string) >= $bytes) { 

     switch ($bytes) { 
      case 1: 
       return ord($string[$pos]); 
       break; 

      case 2: 
       return ((ord($string[$pos]) & 0x1f) << 6) + 
         ((ord($string[$pos+1]) & 0x3f)); 
       break; 

      case 3: 
       return ((ord($string[$pos]) & 0xf) << 12) + 
         ((ord($string[$pos+1]) & 0x3f) << 6) + 
         ((ord($string[$pos+2]) & 0x3f)); 
       break; 

      case 4: 
       return ((ord($string[$pos]) & 0x7) << 18) + 
         ((ord($string[$pos+1]) & 0x3f) << 12) + 
         ((ord($string[$pos+1]) & 0x3f) << 6) + 
         ((ord($string[$pos+2]) & 0x3f)); 
       break; 

      case 0: 
      default: 
       return false; 
     } 
    } 

    return false; 
} 
/** 
* Returns the number of bytes of the $position-th character. 
* 
* @see http://en.wikipedia.org/wiki/UTF-8#Description 
* @access protected 
* @param string $string 
* @param integer $position 
*/ 
protected function _characterBytes(&$string, $position = 0) { 
    $char  = $string[$position]; 
    $charVal = ord($char); 

    if (($charVal & 0x80) === 0) 
     return 1; 

    elseif (($charVal & 0xe0) === 0xc0) 
     return 2; 

    elseif (($charVal & 0xf0) === 0xe0) 
     return 3; 

    elseif (($charVal & 0xf8) === 0xf0) 
     return 4; 
    /* 
    elseif (($charVal & 0xfe) === 0xf8) 
     return 5; 
    */ 

    return false; 
} 
0

yo era capaz de escribir una solución utilizando mb_*, incluyendo un viaje a UTF -16 y la espalda en un intento probablemente tonta para acelerar la cadena de indexación:

$japanese2 = mb_convert_encoding($japanese, "UTF-16", "UTF-8"); 
$length = mb_strlen($japanese2, "UTF-16"); 
for($i=0; $i<$length; $i++) { 
    $char = mb_substr($japanese2, $i, 1, "UTF-16"); 
    $utf8 = mb_convert_encoding($char, "UTF-8", "UTF-16"); 
    print $utf8 . "\n"; 
} 

Debo suerte evitando mb_internal_encoding y simplemente especificando todo en el EAC h mb_* llamada. Estoy seguro de que terminaré usando la solución preg.

6

Ligeramente más simple que preg_match_all:

preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY) 

Esto le da vuelta una matriz 1-dimensional de caracteres. No es necesario un objeto coincidente.

+0

Esta respuesta es la que tiene más sentido, es decir, lógicamente el objetivo es dividir, no nos importa emparejar cada carácter (aunque lo mismo se puede hacer en el fondo). Estaba a punto de responder la pregunta con su solución, pero con una pequeña diferencia: el límite (tercer parámetro) puede tener 'NULL' en lugar de' -1' porque '' -1', '0' o' NULL' significa " sin límite "y, como es estándar en PHP, puede usar' NULL' para [saltar al parámetro flags] (http://php.net/manual/en/function.preg-split.php) ». – Armfoot

Cuestiones relacionadas