PHP, str_pad unicode issue

10,141

Solution 1

I think you need to look inside more php.net (here: http://php.net/str_pad#111147). But I changed it a bit.

Note: Don't forget to call this before mb_internal_encoding("utf-8");.

mb_internal_encoding("utf-8");

function str_pad_unicode($str, $pad_len, $pad_str = ' ', $dir = STR_PAD_RIGHT) {
    $str_len = mb_strlen($str);
    $pad_str_len = mb_strlen($pad_str);
    if (!$str_len && ($dir == STR_PAD_RIGHT || $dir == STR_PAD_LEFT)) {
        $str_len = 1; // @debug
    }
    if (!$pad_len || !$pad_str_len || $pad_len <= $str_len) {
        return $str;
    }

    $result = null;
    if ($dir == STR_PAD_BOTH) {
        $length = ($pad_len - $str_len) / 2;
        $repeat = ceil($length / $pad_str_len);
        $result = mb_substr(str_repeat($pad_str, $repeat), 0, floor($length))
                . $str
                . mb_substr(str_repeat($pad_str, $repeat), 0, ceil($length));
    } else {
        $repeat = ceil($str_len - $pad_str_len + $pad_len);
        if ($dir == STR_PAD_RIGHT) {
            $result = $str . str_repeat($pad_str, $repeat);
            $result = mb_substr($result, 0, $pad_len);
        } else if ($dir == STR_PAD_LEFT) {
            $result = str_repeat($pad_str, $repeat);
            $result = mb_substr($result, 0, 
                        $pad_len - (($str_len - $pad_str_len) + $pad_str_len))
                    . $str;
        }
    }

    return $result;
}

$t = STR_PAD_LEFT;
$s = '...';
$as = 'AO';
$ms = 'ÄÖ';
echo "<pre>\n";
for ($i = 3; $i <= 1000; $i++) {
    $s1 = str_pad($s, $i, $as, $t); // can not inculde unicode char!!!
    $s2 = str_pad_unicode($s, $i, $ms, $t);
    $l1 = strlen($s1);
    $l2 = mb_strlen($s2);
    echo "len $l1: $s1 \n";
    echo "len $l2: $s2 \n";
    echo "\n";
    if ($l1 != $l2) die("Fail!");
}
echo "</pre>";

Test here: http://codepad.viper-7.com/3jTEgt

Solution 2

You need a multibyte version of str_pad(), like below. It's inspired by the source code of str_pad().

function mb_str_pad($input, $pad_length, $pad_string = ' ', $pad_type = STR_PAD_RIGHT, $encoding = 'UTF-8')
{
    $input_length = mb_strlen($input, $encoding);
    $pad_string_length = mb_strlen($pad_string, $encoding);

    if ($pad_length <= 0 || ($pad_length - $input_length) <= 0) {
        return $input;
    }

    $num_pad_chars = $pad_length - $input_length;

    switch ($pad_type) {
        case STR_PAD_RIGHT:
            $left_pad = 0;
            $right_pad = $num_pad_chars;
            break;

        case STR_PAD_LEFT:
            $left_pad = $num_pad_chars;
            $right_pad = 0;
            break;

        case STR_PAD_BOTH:
            $left_pad = floor($num_pad_chars / 2);
            $right_pad = $num_pad_chars - $left_pad;
            break;
    }

    $result = '';
    for ($i = 0; $i < $left_pad; ++$i) {
        $result .= mb_substr($pad_string, $i % $pad_string_length, 1, $encoding);
    }
    $result .= $input;
    for ($i = 0; $i < $right_pad; ++$i) {
        $result .= mb_substr($pad_string, $i % $pad_string_length, 1, $encoding);
    }

    return $result;
}


$str = "nü";
$pad = "ü";

echo mb_str_pad($str, 5, $pad);

Solution 3

Try this (It may look like the one that failed, but this has an encoding check as well):

<?php 
function mb_str_pad ($input, $pad_length, $pad_string, $pad_style, $encoding="UTF-8") { 
   return str_pad($input, strlen($input)-mb_strlen($input,$encoding)+$pad_length, $pad_string, $pad_style); 
} 
?>

Source

Solution 4

My contribution to this.

/**
 * Multibyte String Pad
 *
 * Functionally, the equivalent of the standard str_pad function, but is capable of successfully padding multibyte strings.
 *
 * @param string $input The string to be padded.
 * @param int $length The length of the resultant padded string.
 * @param string $padding The string to use as padding. Defaults to space.
 * @param int $padType The type of padding. Defaults to STR_PAD_RIGHT.
 * @param string $encoding The encoding to use, defaults to UTF-8.
 *
 * @return string A padded multibyte string.
 */
function mb_str_pad($input, $length, $padding = ' ', $padType = STR_PAD_RIGHT, $encoding = 'UTF-8')
{
    $result = $input;
    if (($paddingRequired = $length - mb_strlen($input, $encoding)) > 0) {
        switch ($padType) {
            case STR_PAD_LEFT:
                $result =
                    mb_substr(str_repeat($padding, $paddingRequired), 0, $paddingRequired, $encoding).
                    $input;
                break;
            case STR_PAD_RIGHT:
                $result =
                    $input.
                    mb_substr(str_repeat($padding, $paddingRequired), 0, $paddingRequired, $encoding);
                break;
            case STR_PAD_BOTH:
                $leftPaddingLength = floor($paddingRequired / 2);
                $rightPaddingLength = $paddingRequired - $leftPaddingLength;
                $result =
                    mb_substr(str_repeat($padding, $leftPaddingLength), 0, $leftPaddingLength, $encoding).
                    $input.
                    mb_substr(str_repeat($padding, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
                break;
        }
    }

    return $result;
}

Unit test method

/**
 * @dataProvider provideDataForMbStrPad
 *
 * @param string $input
 * @param int $length
 * @param string $padding
 * @param int $padType
 * @param string $result
 */
public function testMbStrPad($input, $length, $padding, $padType, $result)
{
    $this->assertEquals($result, Strings::mbStrPad($input, $length, $padding, $padType));
}

Data provider for above unit test

public function provideDataForMbStrPad()
  {
      return [
          ['Nhiều byte string đệm', 0, ' ', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 0, ' ', STR_PAD_LEFT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 0, ' ', STR_PAD_RIGHT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 0, '充', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 0, '充', STR_PAD_LEFT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 0, '充', STR_PAD_RIGHT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 0, '煻充', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 20, ' ', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 20, ' ', STR_PAD_LEFT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 20, ' ', STR_PAD_RIGHT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 20, '充', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 20, '充', STR_PAD_LEFT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 20, '充', STR_PAD_RIGHT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 20, '煻充', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 21, ' ', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 21, ' ', STR_PAD_LEFT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 21, ' ', STR_PAD_RIGHT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 21, '充', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 21, '充', STR_PAD_LEFT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 21, '充', STR_PAD_RIGHT, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 21, '煻充', STR_PAD_BOTH, 'Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 22, ' ', STR_PAD_BOTH, 'Nhiều byte string đệm '],
          ['Nhiều byte string đệm', 22, ' ', STR_PAD_LEFT, ' Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 22, ' ', STR_PAD_RIGHT, 'Nhiều byte string đệm '],
          ['Nhiều byte string đệm', 22, '充', STR_PAD_BOTH, 'Nhiều byte string đệm充'],
          ['Nhiều byte string đệm', 22, '充', STR_PAD_LEFT, '充Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 22, '充', STR_PAD_RIGHT, 'Nhiều byte string đệm充'],
          ['Nhiều byte string đệm', 22, '煻充', STR_PAD_BOTH, 'Nhiều byte string đệm煻'],
          ['Nhiều byte string đệm', 23, ' ', STR_PAD_BOTH, ' Nhiều byte string đệm '],
          ['Nhiều byte string đệm', 23, ' ', STR_PAD_LEFT, '  Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 23, ' ', STR_PAD_RIGHT, 'Nhiều byte string đệm  '],
          ['Nhiều byte string đệm', 23, '充', STR_PAD_BOTH, '充Nhiều byte string đệm充'],
          ['Nhiều byte string đệm', 23, '充', STR_PAD_LEFT, '充充Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 23, '充', STR_PAD_RIGHT, 'Nhiều byte string đệm充充'],
          ['Nhiều byte string đệm', 23, '煻充', STR_PAD_BOTH, '煻Nhiều byte string đệm煻'],
          ['Nhiều byte string đệm', 24, ' ', STR_PAD_BOTH, ' Nhiều byte string đệm  '],
          ['Nhiều byte string đệm', 24, ' ', STR_PAD_LEFT, '   Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 24, ' ', STR_PAD_RIGHT, 'Nhiều byte string đệm   '],
          ['Nhiều byte string đệm', 24, '充', STR_PAD_BOTH, '充Nhiều byte string đệm充充'],
          ['Nhiều byte string đệm', 24, '充', STR_PAD_LEFT, '充充充Nhiều byte string đệm'],
          ['Nhiều byte string đệm', 24, '充', STR_PAD_RIGHT, 'Nhiều byte string đệm充充充'],
          ['Nhiều byte string đệm', 24, '煻充', STR_PAD_BOTH, '煻Nhiều byte string đệm煻充'],
          ['Nhiều byte string đệm', 25, '煻充', STR_PAD_BOTH, '煻充Nhiều byte string đệm煻充'],
          ['Nhiều byte string đệm', 26, '煻充', STR_PAD_BOTH, '煻充Nhiều byte string đệm煻充煻'],
      ];
  }

Solution 5

Notice: This is not exact answer to the original question from user2032610. But it will help in situatuation, if you don't need to fill up string with unicode character(s), but with spaces, dots, etc. (I put it here because I was searching for solution and couldn't find some simple one. It may help others in similar situation.)

  1. Simply use mb_strlen() to calculate real string length.
  2. Then echo the $str followed by str_pad pattern (dots, in this case).
$str = "nü";
echo "nü" . str_pad('', 5 - mb_strlen($str), ".");

result: nü...

Share:
10,141

Related videos on Youtube

Admin
Author by

Admin

Updated on June 11, 2022

Comments

  • Admin
    Admin almost 2 years

    I'm just trying to make fixed the $str to 5 characters, but couldn't.

    $str = "nü";
    echo str_pad($str, 5, "ü"); // give nüü
    

    I know that's an unicode issue and searched a lot but no luck. I tried somethings such as;

    echo str_pad($str, 4 + mb_strlen($s), $s);
    echo str_pad($str, 5 + mb_strlen($s), $s);
    

    Also I tried this http://www.php.net/manual/de/function.str-pad.php#89754 and saw this https://stackoverflow.com/a/11871948/362780.

    Any experience on this issue?

    Thanks.

  • K-Gun
    K-Gun over 11 years
    I tested your code and mb_str_pad("...", 4, "ÄÖ") gives ...ÄÖ, but str_pad_unicode("...", 4, "ÄÖ") gives ...Ä as expected.
  • K-Gun
    K-Gun over 11 years
    @Jack; I did, but this works just for STR_PAD_RIGHT: codepad.viper-7.com/eXnyld
  • Ja͢ck
    Ja͢ck over 11 years
    @Antony It was because pad string had more than one character; worked around it, but it would still break for a pad of .
  • Ja͢ck
    Ja͢ck over 11 years
    @qeremy Yeah, I was hoping of keeping it short, but it seems that it's just not stable unless completely worked out :( at least for pads of more than one character.
  • K-Gun
    K-Gun over 11 years
    @Jack No man, your code works well but when pad is "both" it fails, and my code fails when pad is "left" (God! This is PHP). I'm updating my answer for test codes.
  • beerwin
    beerwin over 11 years
    It would be fair to put a comment here after downvoting (if it was because i didn't indicate the source, sorry and thanks for placing it for me).
  • Ja͢ck
    Ja͢ck over 11 years
    I didn't downvote you, though perhaps it was downvoted because the code doesn't actually work.
  • user23127
    user23127 about 10 years
    Should $repeat = ceil($str_len - $pad_str_len + $pad_len); not be $repeat = ceil(($str_len - $pad_len)/$pad_str_len);? Your version works but is somewhat inefficient.
  • Wes
    Wes over 9 years
    this looks inefficient indeed :P I wrote a simpler one. check it out, I just posted it
  • YesThatIsMyName
    YesThatIsMyName about 5 years
    Please add some comments/descriptions to your answer to improve it.