2010-09-29 8 views
11

La función wordwrap() de PHP no funciona correctamente para cadenas de varios bytes como UTF-8.Función de saludo múltiple de byte seguro() para UTF-8

Existen algunos ejemplos de funciones seguras de mb en los comentarios, pero con algunos datos de prueba diferentes, todos parecen tener algunos problemas.

La función debe tomar exactamente los mismos parámetros que wordwrap().

Específicamente asegurarse de que funciona a:

  • cortadas mitad de una palabra, si $cut = true, no corte mitad de una palabra de lo contrario
  • No inserte espacios adicionales en palabras, si $break = ' '
  • también trabajar para $break = "\n"
  • trabajo para ASCII, y todos UTF-8 válidos
+0

Los dos métodos [ 's ($ str) -> truncado ($ longitud, $ quiebre)'] (https://github.com/delight-im/PHP-Str/blob/8fd0c608d5496d43adaa899642c1cce047e076dc/src /Str.php#L233) y ['s ($ str) -> truncateSafely ($ length, $ break)'] (https://github.com/delight-im/PHP-Str/blob/8fd0c608d5496d43adaa899642c1cce047e076dc/src/ Str.php # L246) hacen exactamente eso, como se encuentra en [esta biblioteca independiente] (https://github.com/delight-im/PHP-Str). El primero es para '$ cut = true' y el segundo para' $ cut = false'. Son seguros para Unicode. – caw

Respuesta

-2

Ésta parece funcionar bien ...

function mb_wordwrap($str, $width = 75, $break = "\n", $cut = false, $charset = null) { 
    if ($charset === null) $charset = mb_internal_encoding(); 

    $pieces = explode($break, $str); 
    $result = array(); 
    foreach ($pieces as $piece) { 
     $current = $piece; 
     while ($cut && mb_strlen($current) > $width) { 
     $result[] = mb_substr($current, 0, $width, $charset); 
     $current = mb_substr($current, $width, 2048, $charset); 
     } 
     $result[] = $current; 
    } 
    return implode($break, $result); 
} 
+0

no debería $ romper ser más bien PHP_EOL? Por lo que sería multiplataforma? – ThatGuy

+1

mmm. También no divide palabras largas. – ThatGuy

+0

¿Por qué explotas la cadena? usando líneas de salto? ¿No debería estar usando espacios en su lugar (para dividir palabras)? –

-1

Aquí está mi propia att empt en una función que pasó algunas de mis propias pruebas, aunque no puedo prometer que sea 100% perfecta, así que publique una mejor si ve un problema.

/** 
* Multi-byte safe version of wordwrap() 
* Seems to me like wordwrap() is only broken on UTF-8 strings when $cut = true 
* @return string 
*/ 
function wrap($str, $len = 75, $break = " ", $cut = true) { 
    $len = (int) $len; 

    if (empty($str)) 
     return ""; 

    $pattern = ""; 

    if ($cut) 
     $pattern = '/([^'.preg_quote($break).']{'.$len.'})/u'; 
    else 
     return wordwrap($str, $len, $break); 

    return preg_replace($pattern, "\${1}".$break, $str); 
} 
+0

'wordwrap()' se ajusta solo a un caracter de espacio cuando '$ cut' es' false'. Esta es la razón por la que funciona para UTF-8, que está diseñado para ser compatible con versiones anteriores: los caracteres no definidos en ASCII están codificados con el conjunto de bits más alto, evitando la colisión con caracteres ASCII, incluido el espacio. – Archimedix

+0

¿Puedes aclarar? 'wordwrap()' no funciona para UTF-8, por ejemplo. No estoy seguro de lo que quieres decir con "envuelve solo en un espacio ..." – philfreo

+1

prueba tu función en esta cadena: проверка проверка – Yaroslav

5
/** 
* wordwrap for utf8 encoded strings 
* 
* @param string $str 
* @param integer $len 
* @param string $what 
* @return string 
* @author Milian Wolff <[email protected]> 
*/ 

function utf8_wordwrap($str, $width, $break, $cut = false) { 
    if (!$cut) { 
     $regexp = '#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){'.$width.',}\b#U'; 
    } else { 
     $regexp = '#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){'.$width.'}#'; 
    } 
    if (function_exists('mb_strlen')) { 
     $str_len = mb_strlen($str,'UTF-8'); 
    } else { 
     $str_len = preg_match_all('/[\x00-\x7F\xC0-\xFD]/', $str, $var_empty); 
    } 
    $while_what = ceil($str_len/$width); 
    $i = 1; 
    $return = ''; 
    while ($i < $while_what) { 
     preg_match($regexp, $str,$matches); 
     $string = $matches[0]; 
     $return .= $string.$break; 
     $str = substr($str, strlen($string)); 
     $i++; 
    } 
    return $return.$str; 
} 

Tiempo total: 0,0020880699 es buen momento :)

+0

Si no es' $ cut', esta función tiene un defecto. No se ajustará antes si es posible (que es lo que ['wordwrap'] (http://php.net/wordwrap) haría. [Ver demostración] (http://codepad.viper-7.com/zxE64Z. php53_t). No es una solución, pero una respuesta relacionada tiene otra [Wordgerap Regex] (http://stackoverflow.com/q/2682861/367456#2689242). – hakre

+0

Este comportamiento es diferente de 'wordwrap()', con respecto a los espacios. – mpyw

+0

Este funciona cuando cut = true en texto simplificado chino – exiang

14

no he encontrado ningún código de trabajo para mí. Esto es lo que he escrito. Para mí está funcionando, pensé que probablemente no era el más rápido.

function mb_wordwrap($str, $width = 75, $break = "\n", $cut = false) { 
    $lines = explode($break, $str); 
    foreach ($lines as &$line) { 
     $line = rtrim($line); 
     if (mb_strlen($line) <= $width) 
      continue; 
     $words = explode(' ', $line); 
     $line = ''; 
     $actual = ''; 
     foreach ($words as $word) { 
      if (mb_strlen($actual.$word) <= $width) 
       $actual .= $word.' '; 
      else { 
       if ($actual != '') 
        $line .= rtrim($actual).$break; 
       $actual = $word; 
       if ($cut) { 
        while (mb_strlen($actual) > $width) { 
         $line .= mb_substr($actual, 0, $width).$break; 
         $actual = mb_substr($actual, $width); 
        } 
       } 
       $actual .= ' '; 
      } 
     } 
     $line .= trim($actual); 
    } 
    return implode($break, $lines); 
} 
+4

Este es el que funciona correctamente – Danack

+0

Funcionó bien para mí también! –

+0

Lo he estado utilizando durante algunos años, pero no en exceso. De todos modos, incluí esta función en una php class Puse como una esencia en github bajo MIT y solo necesito verificar que está bien - https://gist.github.com/AliceWonderMiscreations/7694e8aa644cf1b1fc3910b1c949e092 –

0

Aquí está la función multibyte wordwrap que he codificado inspirándome en otros encontrados en Internet.

function mb_wordwrap($long_str, $width = 75, $break = "\n", $cut = false) { 
    $long_str = html_entity_decode($long_str, ENT_COMPAT, 'UTF-8'); 
    $width -= mb_strlen($break); 
    if ($cut) { 
     $short_str = mb_substr($long_str, 0, $width); 
     $short_str = trim($short_str); 
    } 
    else { 
     $short_str = preg_replace('/^(.{1,'.$width.'})(?:\s.*|$)/', '$1', $long_str); 
     if (mb_strlen($short_str) > $width) { 
      $short_str = mb_substr($short_str, 0, $width); 
     } 
    } 
    if (mb_strlen($long_str) != mb_strlen($short_str)) { 
     $short_str .= $break; 
    } 
    return $short_str; 
} 

'No se olvide de configurar PHP para usar UTF-8 con:

ini_set('default_charset', 'UTF-8'); 
mb_internal_encoding('UTF-8'); 
mb_regex_encoding('UTF-8'); 

espero que esto ayudará. Guillaume

1

Como ninguna respuesta manejaba cada caso de uso, aquí hay algo que sí lo hace. El código está basado en Drupal’s AbstractStringWrapper::wordWrap.

<?php 

/** 
* Wraps any string to a given number of characters. 
* 
* This implementation is multi-byte aware and relies on {@link 
* http://www.php.net/manual/en/book.mbstring.php PHP's multibyte 
* string extension}. 
* 
* @see wordwrap() 
* @link https://api.drupal.org/api/drupal/core%21vendor%21zendframework%21zend-stdlib%21Zend%21Stdlib%21StringWrapper%21AbstractStringWrapper.php/function/AbstractStringWrapper%3A%3AwordWrap/8 
* @param string $string 
* The input string. 
* @param int $width [optional] 
* The number of characters at which <var>$string</var> will be 
* wrapped. Defaults to <code>75</code>. 
* @param string $break [optional] 
* The line is broken using the optional break parameter. Defaults 
* to <code>"\n"</code>. 
* @param boolean $cut [optional] 
* If the <var>$cut</var> is set to <code>TRUE</code>, the string is 
* always wrapped at or before the specified <var>$width</var>. So if 
* you have a word that is larger than the given <var>$width</var>, it 
* is broken apart. Defaults to <code>FALSE</code>. 
* @return string 
* Returns the given <var>$string</var> wrapped at the specified 
* <var>$width</var>. 
*/ 
function mb_wordwrap($string, $width = 75, $break = "\n", $cut = false) { 
    $string = (string) $string; 
    if ($string === '') { 
    return ''; 
    } 

    $break = (string) $break; 
    if ($break === '') { 
    trigger_error('Break string cannot be empty', E_USER_ERROR); 
    } 

    $width = (int) $width; 
    if ($width === 0 && $cut) { 
    trigger_error('Cannot force cut when width is zero', E_USER_ERROR); 
    } 

    if (strlen($string) === mb_strlen($string)) { 
    return wordwrap($string, $width, $break, $cut); 
    } 

    $stringWidth = mb_strlen($string); 
    $breakWidth = mb_strlen($break); 

    $result = ''; 
    $lastStart = $lastSpace = 0; 

    for ($current = 0; $current < $stringWidth; $current++) { 
    $char = mb_substr($string, $current, 1); 

    $possibleBreak = $char; 
    if ($breakWidth !== 1) { 
     $possibleBreak = mb_substr($string, $current, $breakWidth); 
    } 

    if ($possibleBreak === $break) { 
     $result .= mb_substr($string, $lastStart, $current - $lastStart + $breakWidth); 
     $current += $breakWidth - 1; 
     $lastStart = $lastSpace = $current + 1; 
     continue; 
    } 

    if ($char === ' ') { 
     if ($current - $lastStart >= $width) { 
     $result .= mb_substr($string, $lastStart, $current - $lastStart) . $break; 
     $lastStart = $current + 1; 
     } 

     $lastSpace = $current; 
     continue; 
    } 

    if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { 
     $result .= mb_substr($string, $lastStart, $current - $lastStart) . $break; 
     $lastStart = $lastSpace = $current; 
     continue; 
    } 

    if ($current - $lastStart >= $width && $lastStart < $lastSpace) { 
     $result .= mb_substr($string, $lastStart, $lastSpace - $lastStart) . $break; 
     $lastStart = $lastSpace = $lastSpace + 1; 
     continue; 
    } 
    } 

    if ($lastStart !== $current) { 
    $result .= mb_substr($string, $lastStart, $current - $lastStart); 
    } 

    return $result; 
} 

?>