PHP - Улучшить функцию капитализации текста

У меня есть функция, которая использует строку с заглавной буквы:

function capitalize_sentence($text)
    {
      $output =  preg_replace_callback('/([.!?])\s*(\w)/', function ($matches) {
            return strtoupper($matches[1] . ' ' . $matches[2]);
        }, ucfirst(strtolower($text)));
        return $output;
    }

Когда у меня есть такая простая строка:

$text = 'hello. this works !';
var_dump($text);

$text = capitalize_sentence($text);
var_dump($text);die;

это работает хорошо:

string 'hello.this works !' (length=18) 

string 'Hello. This works !' (length=19)

Но иногда в моем коде строка выглядит так (с некоторыми тегами):

$text = '<span>hello.</span> this <b>works</b> !';
var_dump($text);

$text = capitalize_sentence($text);
var_dump($text);die;

Что дает мне это (как вы можете видеть, первые слова не пишутся с заглавной буквы...):

string '<span>hello.</span> this <b>works</b> !' (length=39)

string '<span>hello.</span> this <b>works</b> !' (length=39)

Как улучшить мой код? Мне нужно «убежать» <tags>, не удаляя их, но использовать первое слово с большой буквы, как в первом примере....

Мне нужен такой вывод:

string '<span>Hello.</span> This <b>works</b> !' (length=39)

Спасибо !


person Zagloo    schedule 31.03.2015    source источник
comment
Каков ваш ожидаемый результат?   -  person Avinash Raj    schedule 31.03.2015
comment
Я думаю, вы не можете сделать это с помощью регулярного выражения. Напишите небольшой лексический анализатор   -  person tutankhamun    schedule 31.03.2015
comment
@AvinashRaj : '‹span›Привет.‹/span› Это ‹b›работает‹/b› !»   -  person Zagloo    schedule 31.03.2015
comment
@tutankhamun как это сделать?   -  person Zagloo    schedule 31.03.2015
comment
Пожалуйста, не забывайте указывать источники.   -  person w35l3y    schedule 01.04.2015


Ответы (3)


Попробуй это:

function ucSentence($str) {
    $len = strlen($str);
    $flagNeedUC = TRUE; // start of sentence flag
    $flagTag = FALSE;   // inside tag flag
    $endOfSentence = array('.', '!', '?');
    for ($ix = 0; $ix < $len; $ix += 1) {
        if ($flagTag) {
            if ('>' === $str{$ix}) { // resolve end tag
                $flagTag = FALSE;
            }
        } else {
            if (in_array($str{$ix}, $endOfSentence)) { // resolve end sentence
                $flagNeedUC = TRUE;
            } elseif ('<' === $str{$ix}) { // resolve start tag
                $flagTag = TRUE;
            } elseif (ctype_alpha($str{$ix}) && $flagNeedUC) { // resolve first char after sentence end
                $flagNeedUC = FALSE;
                $str{$ix} = strtoupper($str{$ix});
            }
        }
    }
    return $str;
}
echo ucSentence('<span><b>hello. </b></span> this <b>works</b> !');

Он печатает <span><b>Hello. </b></span> This <b>works</b>

ОБНОВЛЕНИЕ специально для @w35l3y :)

Я добавил значение передаваемого атрибута. Он распознает несколько форм значения атрибута, которые встречаются в диком Интернете: <tag attr="value">, <tag attr='value'> и <tag attr=value attr=value>.

function ucSentence($str) {
    $len = strlen($str);
    $flagNeedUC = TRUE; // start of sentence flag
    $flagTag = FALSE;   // inside tag flag
    $stageAttr = FALSE;  // inside attribute value
    $endOfSentence = array('.', '!', '?');
    for ($ix = 0; $ix < $len; $ix += 1) {
        if ($flagTag) {
            if ($stageAttr) {
                if ('=' === $stageAttr) {
                    if ('"' === $str{$ix}) {
                        $stageAttr = '"';
                    } elseif ('\'' === $str{$ix}) {
                        $stageAttr = '\'';
                    } else {
                        $stageAttr = ' >';                        
                    }
                } elseif (strpos($stageAttr, $str{$ix}) !== FALSE) {
                    if ('>' === $str{$ix}) {
                        $flagTag = FALSE;
                    }
                    $stageAttr = FALSE;
                }
            } else {
                if ('>' === $str{$ix}) { // resolve end tag
                    $flagTag = FALSE;
                } elseif ('=' === $str{$ix}) {
                    $stageAttr = '=';
                }
            }
        } else {
            if (in_array($str{$ix}, $endOfSentence)) { // resolve end sentence
                $flagNeedUC = TRUE;
            } elseif ('<' === $str{$ix}) { // resolve start tag
                $flagTag = TRUE;
            } elseif (ctype_alpha($str{$ix}) && $flagNeedUC) { // resolve first char after sentence end
                $flagNeedUC = FALSE;
                $str{$ix} = strtoupper($str{$ix});
            }
        }
    }
    return $str;
}

$testArr = array(
    '<span><b>hello. </b></span> this <b>works</b> !',
    'test. <span title="jane <3 john"> <b>hello. </b></span> this <b>works</b> !',
    'test! <span title="hover -> here"> <b>hello. </b></span> this <b>works</b> !',
    'test <span title="jane <3 john"> <b>hello. </b></span> this <b>works</b> !',
    'test? <span title="hover -> here"> <b>hello. </b></span> this <b>works</b> !',
    'test <span title="hover -> here"> <b>hello. </b></span> this <b>works</b> !',
    'test. <span title=\'hover -> here\'> <b>hello. </b></span> this <b>works</b> !',
    'test. <span title=jane<3john data=jane> <b>hello. </b></span> this <b>works</b> !',
);
foreach ($testArr as $num => $testStr) {
    printf("[%d] %s\n", $num, ucSentence($testStr));
}

Он печатает:

[0] <span><b>Hello. </b></span> This <b>works</b> !
[1] Test. <span title="jane <3 john"> <b>Hello. </b></span> This <b>works</b> !
[2] Test! <span title="hover -> here"> <b>Hello. </b></span> This <b>works</b> !
[3] Test <span title="jane <3 john"> <b>hello. </b></span> This <b>works</b> !
[4] Test? <span title="hover -> here"> <b>Hello. </b></span> This <b>works</b> !
[5] Test <span title="hover -> here"> <b>hello. </b></span> This <b>works</b> !
[6] Test. <span title='hover -> here'> <b>Hello. </b></span> This <b>works</b> !
[7] Test. <span title=jane<3john data=jane> <b>Hello. </b></span> This <b>works</b> !
person tutankhamun    schedule 31.03.2015
comment
@tutankhamun, а как насчет <span title="jane <3 john"><b>hello. </b></span> this <b>works</b> ! или <span title="hover -> here"><b>hello. </b></span> this <b>works</b> !? - person w35l3y; 01.04.2015
comment
Я думаю, что < и > должны быть заменены на объекты &lt; и &gt; в HTML. Или, если вы получили его из дикого Интернета, вы можете избежать его до и после вызова функции. - person tutankhamun; 01.04.2015
comment
Хм. Бежать - плохой способ. Теги тоже будут экранированы. Хорошим способом является дополнительное условие для значений атрибутов. Я думаю, что нет решения для очень плохого html i. e.<span title=hover -> here><b>hello. </b></span> но браузерам это тоже не понравится. - person tutankhamun; 01.04.2015

Попробуйте это обновление, в котором я добавил больше условий и немного изменил замену:

 $output =  preg_replace_callback('/((?:^|[.!?])(?:<[^>]*?>)?)(\s*)(\w)/', function ($matches) {
        return $matches[1] . $matches[2] . strtoupper($matches[3]);
    }, ucfirst(strtolower($text)));

Выводит <span>Hello.</span> This <b>works</b> !.

person Wiktor Stribiżew    schedule 31.03.2015
comment
Но я боюсь, что регулярное выражение - плохое решение этой проблемы. А как насчет '<span>hello. </span> this <b>works</b>!' и ' <span>hello.</span> <span>this <b>works</b> </span>!'? - person tutankhamun; 01.04.2015
comment
Это явно улучшение stackoverflow.com/a/5383786/157873, но я согласился с @tutankhamun, как упоминалось здесь: stackoverflow.com/questions/5383471/ - person w35l3y; 01.04.2015

Это немного измененная версия @tutankhamun, которая предотвращает использование заглавных букв после точек в адресах электронной почты или URL-адресах (или в любое другое время, когда после символов конца предложения (. ! ?) нет пробела).

function sentenceCase($str) {
    $len = strlen($str);
    $flagNeedUC = TRUE; // start of sentence flag
    $flagTag = FALSE;   // inside tag flag
    $stageAttr = FALSE;  // inside attribute value
    $lastChar = NULL;
    $endOfSentence = array('.', '!', '?');
    for ($ix = 0; $ix < $len; $ix += 1) {
        if ($flagTag) {
            if ($stageAttr) {
                if ('=' === $stageAttr) {
                    if ('"' === $str{$ix}) {
                        $stageAttr = '"';
                    } elseif ('\'' === $str{$ix}) {
                        $stageAttr = '\'';
                    } else {
                        $stageAttr = ' >';
                    }
                } elseif (strpos($stageAttr, $str{$ix}) !== FALSE) {
                    if ('>' === $str{$ix}) {
                        $flagTag = FALSE;
                    }
                    $stageAttr = FALSE;
                }
            } else {
                if ('>' === $str{$ix}) { // resolve end tag
                    $flagTag = FALSE;
                } elseif ('=' === $str{$ix}) {
                    $stageAttr = '=';
                }
            }
        } else {
            if (in_array($str{$ix}, $endOfSentence)) { // resolve end sentence
                $flagNeedUC = TRUE;
            } elseif ('<' === $str{$ix}) { // resolve start tag
                $flagTag = TRUE;
            } elseif (ctype_alpha($str{$ix}) && $flagNeedUC) { // resolve first char after sentence end
                $flagNeedUC = FALSE;
                if (!in_array($lastChar, $endOfSentence)) $str{$ix} = strtoupper($str{$ix});
            }
        }
        $lastChar = $str{$ix};
    }
    return $str;
}
person adrian    schedule 03.08.2019