【问题标题】:php mb_convert_case() keep words that are in uppercasephp mb_convert_case() 保留大写的单词
【发布时间】:2026-01-18 22:00:01
【问题描述】:

假设我有一个字符串“HET1200 文本字符串”,我需要将其更改为“HET1200 文本字符串”。编码将是 UTF-8。

我该怎么做?目前,我使用mb_convert_case($string, MB_CASE_TITLE, "UTF-8");,但这会将“HET1200”更改为“Het1200”。

我可以指定一个例外,但这不会是详尽无遗的。所以我宁愿所有大写的单词都保持大写。

谢谢:)

【问题讨论】:

    标签: php string utf-8 mbstring


    【解决方案1】:

    好的,让我们尝试尽可能接近地重新创建mb_convert_case,但只更改每个单词的第一个字符。

    mb_convert_case 实现的相关部分是这样的:

    int mode = 0; 
    
    for (i = 0; i < unicode_len; i+=4) {
        int res = php_unicode_is_prop(
            BE_ARY_TO_UINT32(&unicode_ptr[i]),
            UC_MN|UC_ME|UC_CF|UC_LM|UC_SK|UC_LU|UC_LL|UC_LT|UC_PO|UC_OS, 0);
        if (mode) {
            if (res) {
                UINT32_TO_BE_ARY(&unicode_ptr[i],
                    php_unicode_tolower(BE_ARY_TO_UINT32(&unicode_ptr[i]),
                        _src_encoding TSRMLS_CC));
            } else {
                mode = 0;
            }   
        } else {
            if (res) {
                mode = 1;
                UINT32_TO_BE_ARY(&unicode_ptr[i],
                    php_unicode_totitle(BE_ARY_TO_UINT32(&unicode_ptr[i]),
                        _src_encoding TSRMLS_CC));
            }
        }
    }
    

    基本上,它执行以下操作:

    • mode 设置为0mode 将确定我们是否在单词的第一个字符中。如果是0,我们是,否则,我们不是。
    • 遍历字符串的字符。
      • 确定它是什么类型的字符。
        • 如果是单词字符,请将res 设置为1。更具体地说,将其设置为1,如果它具有属性“标记,非间距”,“标记,封闭”,“其他,格式”,“字母,修饰符”,“符号,修饰符”,“字母,大写”、“字母,小写”,“字母,大写”,“标点符号,其他”或“其他,代理”。奇怪的是,“字母,其他”不包括在内。
      • 如果我们不在单词的开头
        • 如果我们在一个单词字符处,将其转换为小写 - 这是我们不想要的
        • 否则,我们不在单词字符处,我们将 mode 设置为 0 以表示我们正在移动到单词的开头。
      • 如果我们在一个单词的开头并且我们确实有一个单词字符
        • 将此字符转换为标题大小写
        • 表示我们不再位于单词的开头。

    mbstring 扩展似乎没有公开字符属性。这给我们留下了一个问题,因为我们没有很好的方法来确定一个角色是否具有 mb_convert_case 测试的 10 个属性中的任何一个。

    幸运的是,unicode character properties in regex 可以在这里救我们。

    mb_convert_case 的忠实再现没有转换成小写的问题变成:

    function mb_convert_case_utf8_variation($s) {
        $arr = preg_split("//u", $s, -1, PREG_SPLIT_NO_EMPTY);
        $result = "";
        $mode = false;
        foreach ($arr as $char) {
            $res = preg_match(
                '/\\p{Mn}|\\p{Me}|\\p{Cf}|\\p{Lm}|\\p{Sk}|\\p{Lu}|\\p{Ll}|'.
                '\\p{Lt}|\\p{Sk}|\\p{Cs}/u', $char) == 1;
            if ($mode) {
                if (!$res)
                    $mode = false;
            }
            elseif ($res) {
                $mode = true;
                $char = mb_convert_case($char, MB_CASE_TITLE, "UTF-8");
            }
            $result .= $char;
        }
    
        return $result;
    }
    

    测试:

    echo mb_convert_case_utf8_variation("HETÁ1200 Ááxt ítring uii");
    

    给予:

    HETÁ1200 Ááxt Ítring Uii

    【讨论】:

    • 一个字符类会比所有这些管道都干净。