HerrimanCoder
HerrimanCoder

Reputation: 7226

Customized ucwords fails when letter inside parens

I have a glorified ucwords function which allows my system to optionally allow/disallow words within a string to be all caps:

function NormalizeWords($str, $disallowAllUppercase = false){
    $parts = explode(' ', $str);

    if($disallowAllUppercase){
        $result = array_map(function($x){
            return ucwords(strtolower($x));
        }, $parts);
    }else{
        $result = array_map(function($x){
            if (!(strtoupper($x) == $x)) {
                return ucwords(strtolower($x));
            }
            return $x;
        }, $parts);
    }

    return implode(' ', $result);
}

This function works in almost all cases. Where it fails is when a word is inside parens (or any non-alphanumeric characters I would assume).

Here are some examples:

writeme(NormalizeWords("Make An Omelette (template)", true));
writeme(NormalizeWords("make an omelette (template)", true));
writeme(NormalizeWords("Make An Omelette - template", true));

Results:

Make An Omelette (template) - wrong
Make An Omelette (template) - wrong
Make An Omelette - Template - right

Expected Results:

Make An Omelette (Template)
Make An Omelette (Template)
Make An Omelette - Template

You can see that when a word/words touch parens or other characters, the first letter doesn't get forced to ucase. How can I fix my function to do this?

Upvotes: 1

Views: 40

Answers (3)

A l w a y s S u n n y
A l w a y s S u n n y

Reputation: 38542

One workaround is do it with simple and one liner mb_convert_case() function, by the way you can wrap it inside your NormalizeWords function.

<?php
$text= "Make (an) omelette (template)";
echo mb_convert_case($text, MB_CASE_TITLE, "UTF-8");
?>

WORKING DEMO: https://3v4l.org/NgX6W

Upvotes: 0

Shiz
Shiz

Reputation: 693

Or, you can temporarily replace the parens to unrecognizable words and then put it back after the NormalizeWords:

$str = 'make an omelette (template)';
$str = str_ireplace('(','unrgcswzbl ',$str); 
$str = str_ireplace(')','unrgcswzbl2 ',$str);
$str = NormalizeWords( $str,true);
$str = str_ireplace('unrgcswzbl ','',$str);
$str = str_ireplace('unrgcswzbl2 ','',$str);
echo $str;

Upvotes: 0

Nick
Nick

Reputation: 147216

By changing your function to use preg_split to split the string on any non-alphabetic characters, you can extract only the words for capitalisation. By using the PREG_SPLIT_DELIM_CAPTURE flag we retain all the non-alphabetic characters and we can then put the string back to together by using implode with no glue:

function NormalizeWords($str, $disallowAllUppercase = false){
    $parts = preg_split('/([^A-Za-z]+)/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    if($disallowAllUppercase){
        $result = array_map(function($x){
            return ucwords(strtolower($x));
        }, $parts);
    }else{
        $result = array_map(function($x){
            if (!(strtoupper($x) == $x)) {
                return ucwords(strtolower($x));
            }
            return $x;
        }, $parts);
    }
    return implode('', $result);
}

echo(NormalizeWords("Make AN Omelette (template)", false)) . PHP_EOL;
echo(NormalizeWords("make AN omelette (template)", true)) . PHP_EOL;
echo(NormalizeWords("Make An Omelette - template", true)) . PHP_EOL;

Output:

Make AN Omelette (Template)
Make An Omelette (Template)
Make An Omelette - Template

Demo on 3v4l.org

Note you can simplify your function by passing $disallowAllUppercase to the array_map function:

function NormalizeWords($str, $disallowAllUppercase = false){
    $parts = preg_split('/([^A-Za-z]+)/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    $result = array_map(function ($x) use ($disallowAllUppercase) {
        if ($disallowAllUppercase || strtoupper($x) != $x) {
            return ucwords(strtolower($x));
        }
        return $x;
    }, $parts);
    return implode('', $result);
}

Demo on 3v4l.org

Upvotes: 1

Related Questions