2
\$\begingroup\$

I needed to create a custom trim() function instead of using existing ltrim or rtrim due to below problems.

Here are problem with existing functions.

$string = '<ul><li>This</li></ul>';

var_dump(ltrim($string, '<br>'));

// Output :: string(21) "ul><li>This</li></ul>"

Fiddle.

As you can see ltrim trimming < brace because <br> not found I think (Not sure 100%).

Here created function,

function trimString($string, $replaceWith = ' ', $type = 'ltrim')
{
    $type = (!empty($type) && in_array($type, ['ltrim', 'rtrim'])) ? $type : 'ltrim';
    if (!empty($string) && !empty($replaceWith)) {
        // Like ltrim
        if ($type == 'ltrim') {
            if (strpos($string, $replaceWith) === 0) {
                $string = substr_replace($string, '', 0, strlen($replaceWith));
            }
        } elseif ($type == 'rtrim') {
            // Like rtrim
            $reverceString    = strrev($string);
            $replaceWithCount = strlen($replaceWith);
            $count            = 1;
            for ($i = 0; $i < strlen($string); $i++){
                if ($count <= $replaceWithCount) {
                    $newString .= $reverceString[$i];
                }
                $count++;

                if ($count == $replaceWith) {
                    break;
                }
            }
            if (!empty($newString)) {
                if (strrev($newString) == $replaceWith) {
                    $string = strrev(strReplaceFirst($reverceString, strrev($replaceWith), ''));
                }
            }
        }
    }

    return $string;
}

function strReplaceFirst($content, $from, $to)
{
    if (!empty($content) && !empty($from)) {
        $from = '/'.preg_quote($from, '/').'/';

        return preg_replace($from, $to, $content, 1);
    }

    return $content;
}

Fiddle.

So, it is working properly for me! But I still don't know if it's the proper way to do that or not? Or it's side effect and time consumption.

\$\endgroup\$
4
  • \$\begingroup\$ The proper way to trim a string depends on its use. This is similar to case-folding on a case-insensitive filesystem and "newline trimming" in some Perl input loops. What's your use? \$\endgroup\$
    – DannyNiu
    Mar 11, 2020 at 12:31
  • \$\begingroup\$ The stock trimming functions trim bytes, not strings. Is your goal to remove particular substring(s) at the beginning or the end of the input to your trim functions? \$\endgroup\$
    – DannyNiu
    Mar 11, 2020 at 12:38
  • \$\begingroup\$ @DannyNiu yes my goal is to remove particular needle from string fron beginning or from the end. \$\endgroup\$ Mar 11, 2020 at 13:49
  • 1
    \$\begingroup\$ If you read carefully documentation of ltrim, you will understand why does it do what you saw. Second parameter is "character mask" it trims any of characters in that string. That means your call will trim any b, r, < and >. \$\endgroup\$
    – K.H.
    Mar 12, 2020 at 14:03

3 Answers 3

4
\$\begingroup\$

I wonder if you did a quick Google search before rolling your own solution. Despite being implemented via different languages, they all give the same general regex solution.

As you have discovered, trim() functions treat the 2nd parameter as a "character mask", this means that it will greedily remove any consecutive characters from the beginning/end of the string from that "list of characters" (<br> is the same as rb><).

If you need a utility function and require the same passed-in parameters:

function trimString($input, $find, $replacement = ' ', $type = 'ltrim') {
    $patterns = [
        'ltrim' => '^(?:' . preg_quote($find, '/') . ')+',
        'rtrim' => '(?:' . preg_quote($find, '/') . ')+$'
    ];
    $pattern = $patterns[$type] ?? implode('|', $patterns);
    return preg_replace('/' . $pattern . '/i', $replacement, $input);
}

If you want to be able to pass replacement strings which contain regex to the custom function like <br\s*/?>, then you can remove the preg_quote() calls entirely or set a flag/argument in the custim function which determines whether the quoting call should be implemented. This means that the responsiblity of quoting any special characters is transferred from the custom function to whatever layer is calling the custom function.

If you want to use trim() before or after the execution of this custom function, that makes perfect sense too -- removing whitespace characters is exactly what it is mean to do.

If your requirements keep extending and you have the intention of removing HTML tags from a valid HTML document, then regex is probably not the right path to go down. Regex is DOM-unaware and will be vulnerable to breakage in fringe case scenarios -- a legitimate DOM parser would be my strong recommendation in that case.

\$\endgroup\$
3
\$\begingroup\$

PHP is hell of a language to see other people working (abusing) with.

I'll assume the initial goal is to remove line-break elements at the beginning and the end of strings intended for output.

I'd suggest a single-purpose function like:

function trimbr($s)
{
    return preg_replace('{^(<br\s*/*>)*|(<br\s*/*>)*$}i', "", $s);
}

The reason is as follow:

  1. Regex-based string modification runs much much faster than character iterating methods.

  2. Over-generalizing our trim function may not be as useful as it initially seems to be.

  3. Since we're restraining ourselves from generalizing, we now aim for a minimal functionality and implementation.

Point 1 and 3 are easy to understand I suppose.

As for 2, we can put some stock regex substrings in the function - these substrings should be commonly-needed, and regex-safe.

\$\endgroup\$
2
\$\begingroup\$

Sticking to what you state as your goal in the comment

my goal is to remove particular needle from string fron beginning or from the end

rather than use any assumptions about manipulating html tags (which should be done in DOMDocument or a similar DOM parser), then I will stick to this.

The problem in your case is that any of the trim() functions take a list of characters to trim, so they are considered individually rather than as a string in itself (which is what the search functions use).

This code just considers the first (or last) set of characters equivalent to the length of the needle and compares this with the needle itself. If it matches, then just substr() the haystack to remove this many characters off the string...

function ltrimString( string $haystack, string $needle )   {
    if ( substr($haystack, 0, strlen($needle)) === $needle )   {
        $haystack = substr($haystack, strlen($needle));
    }
    return $haystack;
}

function rtrimString( string $haystack, string $needle )   {
    if ( substr($haystack, -strlen($needle)) === $needle )   {
        $haystack = substr($haystack, 0, -strlen($needle));
    }
    return $haystack;
}

some examples...

echo '<ul><li>This</li></ul>=' .
    ltrimString('<ul><li>This</li></ul>', '<br>') . PHP_EOL;

// <ul><li>This</li></ul>=<ul><li>This</li></ul>

echo '<br><ul><li>This</li></ul><br>=' .
    ltrimString('<br><ul><li>This</li></ul><br>', '<br>') . PHP_EOL;

// <br><ul><li>This</li></ul><br>=<ul><li>This</li></ul><br>

echo '<ul><li>This</li></ul>=' .
    rtrimString('<ul><li>This</li></ul>', '<br>') . PHP_EOL;

// <ul><li>This</li></ul>=<ul><li>This</li></ul>

echo '<br><ul><li>This</li></ul><br>=' .
    rtrimString('<br><ul><li>This</li></ul><br>', '<br>') . PHP_EOL;

// <br><ul><li>This</li></ul><br>=<br><ul><li>This</li></ul>

The one thing I have not coded for is repeating chunks, this could be achieved by changing the if() to a while(). For example

function ltrimString( string $haystack, string $needle )   {
    while ( substr($haystack, 0, strlen($needle)) === $needle )   {
        $haystack = substr($haystack, strlen($needle));
    }
    return $haystack;
}

would produce...

<br><br><br><br><ul><li>This</li></ul><br>=<ul><li>This</li></ul><br>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.