PHP 日本研討會 2024

多位元組字串函數

參考

多位元組字元編碼方案及其相關問題相當複雜,超出本文件範圍。 請參考以下網址和其他資源以取得有關這些主題的更多資訊。

目錄

新增註解

使用者貢獻的註解 35 則註解

69
deceze at gmail dot com
12 年前
請注意,所有關於註解中 mb_str_replace 的討論都相當無意義。 str_replace 在多位元組字串方面也能正常運作

<?php

$string
= '漢字はユニコード';
$needle = 'は';
$replace = 'Foo';

echo
str_replace($needle, $replace, $string);
// 輸出:漢字Fooユニコード

?>

常見的問題是,字串會被當作二進位字串來評估,這表示 PHP 完全沒有感知到編碼。如果你從「外部」(例如資料庫、POST 請求)取得值,且搜尋目標 (needle) 和字串 (haystack) 的編碼不同,就會產生問題。這通常表示原始碼儲存時的編碼,和你從「外部」接收到的編碼不同。因此,二進位的表示法不匹配,導致沒有任何事發生。
21
Eugene Murai
19 年前
PHP 可以輸入和輸出 Unicode,但和 Microsoft 的意思略有不同:當 Microsoft 說「Unicode」時,它不明確地指的是具有 BOM (FF FE = chr(255).chr(254)) 的小端 UTF-16,而 PHP 的「UTF-16」則是指具有 BOM 的大端。因此,PHP 似乎無法為 Microsoft Excel 輸出 Unicode CSV 檔案。解決這個問題很簡單:只需在 UTF-16LE 字串前面加上 BOM 即可。

範例

$unicode_str_for_Excel = chr(255).chr(254).mb_convert_encoding( $utf8_str, 'UTF-16LE', 'UTF-8');
13
mdoocy at u dot washington dot edu
17 年前
請注意,某些多位元組函式以 O(n) 時間執行,而不是像單一位元組函式那樣以常數時間執行。這包括任何需要存取特定索引的功能,因為在位元組數不一定與字元數匹配的字串中,無法進行隨機存取。受影響的函式包括:mb_substr()、mb_strstr()、mb_strcut()、mb_strpos() 等。
6
treilor at gmail dot com
10 年前
給那些將遵循 rawsrc at gmail dot com 建議的人一個小提示:mb_split 使用正規表示式,在這種情況下,使用內建函式 mb_ereg_replace 可能更有意義。
11
Anonymous
11 年前
另一個單行的 mb_trim() 函式

<?php
function mb_trim($string, $trim_chars = '\s'){
return
preg_replace('/^['.$trim_chars.']*(?U)(.*)['.$trim_chars.']*$/u', '\\1',$string);
}
$string = ' "some text." ';
echo
mb_trim($string, '\s".');
//some text
?>
4
mattr at telebody dot com
10 年前
關於 Daniel Rhodes 的 mb_punctuation_trim() 的簡短說明。
正規表示式修飾符 u 並不代表不貪婪 (ungreedy),而是表示模式是以 UTF-8 編碼。相反地,應該使用 U 修飾符來獲得不貪婪的行為。(我沒有測試過他的程式碼。)
請參閱 https://php.dev.org.tw/manual/en/reference.pcre.pattern.modifiers.php
5
Hayley Watson
6 年前
某些多位元組編碼可以安全地用於 str_replace() 之類的功能,而其他則不行。僅僅確保所有涉及的字串都使用相同的編碼是不夠的:顯然它們必須使用相同的編碼,但這還不夠。它必須是正確的編碼類型。

UTF-8 是安全的編碼之一,因為它在構成編碼文字的位元組字串中,對於每個編碼字元的開始和結束位置的定義是明確的。某些編碼是不安全的:文字中一個字元的最後幾個位元組,後接下一個字元的前幾個位元組,可能會一起構成一個有效的字元。str_replace() 對於「字元」、「字元編碼」或「編碼文字」一無所知。它只知道位元組字串。對 str_replace() 而言,具有兩個位元組編碼的兩個相鄰字元看起來只像是一個四個位元組的序列,它不會知道它不應該嘗試匹配中間的兩個位元組。

雖然可以在現實世界中找到 str_replace() 損壞文字的例子,但可以使用 HTML-ENTITIES 編碼來說明。它不是安全的編碼之一。所有傳遞給 str_replace() 的字串都是有效的 HTML-ENTITIES 編碼文字,因此滿足了「所有輸入都使用相同編碼」的規則。

文字是「x<y」。它由位元組字串 [78 26 6c 74 3b 79] 表示。請注意,文字有三個字元,但字串有六個位元組。

<?php

$string
= 'x&lt;y';
mb_internal_encoding('HTML-ENTITIES');

echo
"文字長度: ", mb_strlen($string), "\t字串長度: ", strlen($string), " ... ", $string, "\n";
// 三個字元,六個位元組;文字讀作「x<y」。

$newstring = str_replace('l', 'g', $string);
echo
"文字長度: ", mb_strlen($newstring), "\t字串長度: ", strlen($newstring), " ... ", $newstring, "\n";
// 三個字元,六個位元組,但現在文字讀作「x>y」;錯誤的字元被更改了。

$newstring = str_replace(';', ':', $string);
echo
"文字長度: ", mb_strlen($newstring), "\t字串長度: ", strlen($newstring), " ... ", $newstring, "\n";
// 現在即使是文字的長度也是錯誤的,而且文字已經損壞了。

?>

即使「l」和「;」都沒有出現在文字「x<y」中,str_replace() 仍然找到了並更改了位元組。在一個案例中,它將文字更改為「x>y」,而在另一個案例中,它完全破壞了編碼。

我想,如果你可以的話,這是另一個使用 UTF-8 的理由。
7
mitgath at gmail dot com
15 年前
根據
http://bugs.php.net/bug.php?id=21317
這是遺失的函式

<?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);
}
?>
8
roydukkey at roydukkey dot com
15 年前
這會是建立多位元組 substr_replace 函式的一種方法

<?php
function mb_substr_replace($output, $replace, $posOpen, $posClose) {
return
mb_substr($output, 0, $posOpen).$replace.mb_substr($output, $posClose+1);
}
?>
6
Ben XO
16 年前
PHP5 沒有 mb_trim(),所以這是我製作的一個。它的工作方式與 trim() 完全相同,但額外的好處是具有 PCRE 字元類別(當然,包括所有有用的 Unicode 字元類別,例如 \pZ)。

與我看到的其他解決此問題的方法不同,我希望模擬 trim() 的完整功能,特別是自訂字元列表的能力。

<?php
/**
* 以多位元組友善的方式,修剪字串頭尾(或兩者)的字元。
*
* 大致上,此函數的行為與 trim() 完全相同:例如,將 'abc' 作為 charlist 參數,會修剪字串中所有 'a'、'b' 和 'c' 字元,當然,額外的好處是您可以在 charlist 中放入 Unicode 字元。
*
* 我們使用 PCRE 字元類別以 Unicode 感知的方式進行修剪,因此我們必須跳脫 ^、\、- 和 ] 這些在此處具有特殊含義的字元。
* 如您所預期的,charlist 中的單個 \ 會被解釋為「修剪反斜線」(並適當地跳脫為雙 \\ )。在大多數情況下,您可以忽略此細節。
*
* 然而,作為一個額外的好處,我們也允許使用 PCRE 特殊字元類別(例如 '\s'),因為它們在處理 UCS 時非常有用。例如,'\pZ' 會比對 Unicode 中定義的每個「分隔符號」字元,包括不間斷空格和零寬度空格。
*
* 在字元類別中擁有兩個或多個相同的字元是沒有意義的,因此我們將字元列表中的雙 \ 解釋為 regex 中的單個 \,讓您可以安全地將普通字元與 PCRE 特殊類別混合使用。
*
* 使用此額外功能時*請小心*,因為 PHP 也會在 regex 看到它們之前將反斜線解釋為跳脫字元。因此,要在 regex 中指定 '\\s'(它將被轉換為用於修剪的特殊字元類別 '\s'),您通常必須在 PHP 程式碼中放入 *4* 個反斜線 - 正如您從 $charlist 的預設值中看到的那樣。
*
* @param string
* @param charlist 從字串頭尾移除的字元列表。
* @param boolean 是否修剪左側?
* @param boolean 是否修剪右側?
* @return String
*/
function mb_trim($string, $charlist='\\\\s', $ltrim=true, $rtrim=true)
{
$both_ends = $ltrim && $rtrim;

$char_class_inner = preg_replace(
array(
'/[\^\-\]\\\]/S', '/\\\{4}/S' ),
array(
'\\\\\\0', '\\' ),
$charlist
);

$work_horse = '[' . $char_class_inner . ']+';
$ltrim && $left_pattern = '^' . $work_horse;
$rtrim && $right_pattern = $work_horse . '$';

if(
$both_ends)
{
$pattern_middle = $left_pattern . '|' . $right_pattern;
}
elseif(
$ltrim)
{
$pattern_middle = $left_pattern;
}
else
{
$pattern_middle = $right_pattern;
}

return
preg_replace("/$pattern_middle/usSD", '', $string) );
}
?>
6
php at kamiware dot org
8 年前
str_replace 不是多位元組安全的。

這個烏克蘭語單字在下列程式碼中使用時會產生錯誤:відео

$rubishcharacters='[#|\[{}\]´`≠,;.:-\\_<>=*+"\'?()!§$&%';
$searchstring='відео';

$result = str_replace(str_split($rubishcharacters), ' ', $searchstring);
2
abidul dot rmdn at gmail dot com
5 年前
如果有一個大型專案,必須遷移到 MB 函數可能會有點痛苦。我們公司花了一段時間才完成,但後來我們寫了一個小腳本,並在一個小部落格中進行了解釋。
https://link.medium.com/25w1LronCX

這使得遷移到 mb_ 函數變得非常容易。
2
Daniel Rhodes
11 年前
這裡有一個簡單且方便的函數,可以從任何語言的 UTF-8 字串中刪除開頭和結尾的*標點符號*(或更具體地說,「非單字字元」)。(至少它對於日語和英語來說效果很好。)

/**
* 從字串的開頭和結尾修剪單位元組和多位元組標點符號
*
* @author Daniel Rhodes
* @note 我們希望第一個非單字擷取是貪婪的,但接著
* @note 我們希望點星號擷取(在最後一個非單字擷取之前)
* @note 是非貪婪的
*
* @param string $string UTF-8 輸入字串
* @return string 與 $string 相同,但刪除了開頭和結尾的標點符號
*/
function mb_punctuation_trim($string)
{
preg_match('/^[^\w]{0,}(.*?)[^\w]{0,}$/iu', $string, $matches); //大小寫不敏感的 'i' 和非貪婪的 'u'

if(count($matches) < 2)
{
//發生一些奇怪的錯誤,所以直接返回原始輸入
return $string;
}

return $matches[1];
}

希望您喜歡!
1
sakai at d4k dot net
15 年前
我希望這個 mb_str_replace 可以用於陣列。如果需要更改編碼,請事先使用 mb_internal_encoding()。

感謝 marc at ermshaus dot org 提供原始程式碼。

<?php

if(!function_exists('mb_str_replace')) {

function
mb_str_replace($search, $replace, $subject) {

if(
is_array($subject)) {
$ret = array();
foreach(
$subject as $key => $val) {
$ret[$key] = mb_str_replace($search, $replace, $val);
}
return
$ret;
}

foreach((array)
$search as $key => $s) {
if(
$s == '') {
continue;
}
$r = !is_array($replace) ? $replace : (array_key_exists($key, $replace) ? $replace[$key] : '');
$pos = mb_strpos($subject, $s);
while(
$pos !== false) {
$subject = mb_substr($subject, 0, $pos) . $r . mb_substr($subject, $pos + mb_strlen($s));
$pos = mb_strpos($subject, $s, $pos + mb_strlen($r));
}
}

return
$subject;

}

}

?>
5
rawsrc at gmail dot com
13 年前
嗨,

對於那些正在尋找 mb_str_replace 的人,這是一個簡單的函數
<?php
function mb_str_replace($needle, $replacement, $haystack) {
return
implode($replacement, mb_split($needle, $haystack));
}
?>
我還沒有找到更簡單的方法來進行 :-)
1
nzkiwi at NOSPAMmte dot biglobe dot ne dot jp
19 年前
一位朋友指出,mbstring 頁面上表 1 中的條目
「mbstring.http_input PHP_INI_ALL」似乎是錯誤的:在範例 4 上方,它說「沒有辦法從 PHP 腳本控制 HTTP 輸入字元轉換。要停用 HTTP 輸入字元轉換,必須在 php.ini 中完成」。
此外,該表顯示了舊的 PHP 版本預設值
;; 停用 HTTP 輸入轉換
mbstring.http_input = pass *但是*(對於 PHP 4.3.0 或更高版本)
;; 停用 HTTP 輸入轉換
mbstring.encoding_translation = Off
1
v dot r dot sanaty at gmail dot com
7 年前
substr_replace 函數的多位元組版本
(靈感來自 roydukkey 的筆記,並進行了一些修正)

function mb_substr_replace($string, $replacement, $start, $length){
return mb_substr($string, 0, $start).$replacement.mb_substr($string, $start+$length);
}
1
Daniel Rhodes
11 年前
這裡有一個簡單且方便的函數,可以從任何語言的 UTF-8 字串中刪除開頭和結尾的*標點符號*(或更具體地說,「非單字字元」)。(至少它對於日語和英語來說效果很好。)

/**
* 從字串的開頭和結尾修剪單位元組和多位元組標點符號
*
* @author Daniel Rhodes
* @note 我們希望第一個非單字擷取是貪婪的,但接著
* @note 我們希望點星號擷取(在最後一個非單字擷取之前)
* @note 是非貪婪的
*
* @param string $string UTF-8 輸入字串
* @return string 與 $string 相同,但刪除了開頭和結尾的標點符號
*/
function mb_punctuation_trim($string)
{
preg_match('/^[^\w]{0,}(.*?)[^\w]{0,}$/iu', $string, $matches); //大小寫不敏感的 'i' 和非貪婪的 'u'

if(count($matches) < 2)
{
//發生一些奇怪的錯誤,所以直接返回原始輸入
return $string;
}

return $matches[1];
}

希望您喜歡!
0
rr_news at live dot de
7 年前
來自 "mt at mediamedics dot nl" 的建議並不像負評所顯示的那樣糟糕。只有一個小錯誤,可以很容易地修正使其正常工作。
需要修改「for」的開頭,將 $i + $split_length 替換為 $i += $split_length。

以下是完整的可執行程式碼,並額外檢查以驗證該方法是否已存在

<?php
if ( !function_exists('mb_str_split') )
{
function
mb_str_split($string, $split_length = 1)
{
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');

$split_length = ($split_length <= 0) ? 1 : $split_length;

$mb_strlen = mb_strlen($string, 'utf-8');

$array = array();

for(
$i = 0; $i < $mb_strlen; $i += $split_length)
{
$array[] = mb_substr($string, $i, $split_length);
}

return
$array;
}
}
?>
0
efesar
13 年前
這個小型的 mb_trim 函數對我來說可以使用。

<?php
function mb_trim( $string )
{
$string = preg_replace( "/(^\s+)|(\s+$)/us", "", $string );

return
$string;
}
?>
0
johannesponader at dontspamme dot googlemail dot co
14 年前
請注意,當遷移程式碼來處理 UTF-8 編碼時,不僅此處提到的函數很有用,而且函數 htmlentities() 也必須更改為 htmlentities($var, ENT_COMPAT, "UTF-8") 或類似的形式。我沒有掃描手冊來確認,但可能還有一些函數需要像這樣進行調整。
0
marc at ermshaus dot org
16 年前
對 patrick at hexane dot org 的 mb_str_replace 函數的一個小修正。原始函數在 $replacement 包含 $needle 的情況下無法按預期工作。

<?php
function mb_str_replace($needle, $replacement, $haystack)
{
$needle_len = mb_strlen($needle);
$replacement_len = mb_strlen($replacement);
$pos = mb_strpos($haystack, $needle);
while (
$pos !== false)
{
$haystack = mb_substr($haystack, 0, $pos) . $replacement
. mb_substr($haystack, $pos + $needle_len);
$pos = mb_strpos($haystack, $needle, $pos + $replacement_len);
}
return
$haystack;
}
?>
0
patrick at hexane dot org
16 年前
我想知道為什麼沒有 mb_str_replace()。這是目前的一個替代方案

function mb_str_replace( $needle, $replacement, $haystack ) {
$needle_len = mb_strlen($needle);
$pos = mb_strpos( $haystack, $needle);
while (!($pos ===false)) {
$front = mb_substr( $haystack, 0, $pos );
$back = mb_substr( $haystack, $pos + $needle_len);
$haystack = $front.$replacement.$back;
$pos = mb_strpos( $haystack, $needle);
}
return $haystack;
}
0
chris at maedata dot com
17 年前
當匯入/上傳檔案時,Eugene Murai 在先前的評論中所寫的內容恰恰相反。例如,如果您使用「另存為 Unicode 文字」選項匯出 Excel 電子表格,您可以使用以下程式碼在上傳後將其轉換為 UTF-8

//如果 Windows 搞亂了,將檔案轉換為 UTF-8
$file = explode( "\n", mb_convert_encoding( trim( file_get_contents( $_FILES['file']['tmp_name'] ) ), 'UTF-8', 'UTF-16' ) );
0
pdezwart .at. snocap
18 年前
如果您嘗試模擬 .NET 中的 UnicodeEncoding.Unicode.GetBytes() 函數,您要使用的編碼是:UCS-2LE
0
daniel at softel dot jp
18 年前
請注意,儘管「多位元組」暗示了完全的國際化,但 mb_ API 是由一位日本人設計來支援日語的。

某些函數,例如 mb_convert_kana(),在日本語言環境之外是完全沒有意義的。

如果這些函數可以使用非日語的多位元組語言,應該算是「幸運」的。

我並非對 mb_ API 不敬,因為我每天都在使用它,並且我感謝它的實用性,但也許更好的名稱是 jp_ API。
0
Aardvark
18 年前
由於並非所有託管服務目前都支援多位元組函數集,因此可能仍然需要使用標準的單位元組函數來處理 Unicode 字串。以下連結中的函數 - http://www.kanolife.com/escape/2006/03/php-unicode-processing.html - 以範例說明如何執行此操作。雖然這僅涵蓋了 UTF-8,但標準的 PHP 函數「iconv」允許字串以其他編碼輸入或輸出時轉換為 UTF-8 或從 UTF-8 轉換。
0
peter kehl
18 年前
Eugene Murai 提供的 Excel 的 CSV 的 UTF-16LE 解決方案運作良好
$unicode_str_for_Excel = chr(255).chr(254).mb_convert_encoding( $utf8_str, 'UTF-16LE', 'UTF-8');

但是,Mac OS X 上的 Excel 無法正確識別欄位,並且會將整個列放入自己的儲存格中。為了修正此問題,請使用 TAB "\\t" 字元作為 CSV 分隔符號,而不是逗號或冒號。

您可能還想使用 HTTP 編碼標頭,例如
header( "Content-type: application/vnd.ms-excel; charset=UTF-16LE" );
0
Anonymous
19 年前
當 mbstring.func_overload 設定為 2 時,取得字串的位元組大小

<?php
function str_sizeof($string) {
return
count(preg_split("`.`", $string)) - 1 ;
}
?>

回答 peter albertsson,一旦您取得資料的位元組大小,您可以使用 $string[0] ... $string[$size-1] 來存取每個位元組,因為 [ 運算子不符合多位元組字串。
$string[0] ... $string[$size-1],因為 [ 運算子不符合多位元組字串。
-1
Daniel Rhodes
11 年前
這裡有一個簡單且方便的函數,可以從任何語言的 UTF-8 字串中刪除開頭和結尾的*標點符號*(或更具體地說,「非單字字元」)。(至少它對於日語和英語來說效果很好。)

/**
* 從字串的開頭和結尾修剪單位元組和多位元組標點符號
*
* @author Daniel Rhodes
* @note 我們希望第一個非單字擷取是貪婪的,但接著
* @note 我們希望點星號擷取(在最後一個非單字擷取之前)
* @note 是非貪婪的
*
* @param string $string UTF-8 輸入字串
* @return string 與 $string 相同,但刪除了開頭和結尾的標點符號
*/
function mb_punctuation_trim($string)
{
preg_match('/^[^\w]{0,}(.*?)[^\w]{0,}$/iu', $string, $matches); //大小寫不敏感的 'i' 和非貪婪的 'u'

if(count($matches) < 2)
{
//發生一些奇怪的錯誤,所以直接返回原始輸入
return $string;
}

return $matches[1];
}

希望您喜歡!
-1
hayk at mail dot ru
18 年前
從 PHP 5.1.0 和 PHP 4.4.2 開始,可以使用亞美尼亞語 ArmSCII-8 (ArmSCII-8, ArmSCII8, ARMSCII-8, ARMSCII8) 編碼。
-2
peter dot albertsson at spray dot se
19 年前
設定 mbstring.func_overload = 2 可能會破壞處理二進位資料的應用程式。

在設定 mbstring.func_overload = 2 和 mbstring.internal_encoding = UTF-8 之後,我甚至無法讀取二進位檔案並將其列印/echo 到輸出而不損壞它。
-2
mt at mediamedics dot nl
14 年前
str_split 函數 (https://php.dev.org.tw/manual/en/function.str-split.php) 的多位元組一對一替代方案

<?php
function mb_str_split($string, $split_length = 1){

mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');

$split_length = ($split_length <= 0) ? 1 : $split_length;

$mb_strlen = mb_strlen($string, 'utf-8');

$array = array();

for(
$i = 0; $i < $mb_strlen; $i + $split_length){

$array[] = mb_substr($string, $i, $split_length);
}

return
$array;

}
?>
-2
peter AT(no spam) dezzignz dot com
15 年前
到目前為止,在我的多位元組應用程式中,`trim()` 函式尚未出錯,但萬一需要真正的多位元組函式,這有一個。好處是,要移除的字元可以是空白字元或任何其他指定的字元,甚至是多位元組字元。

<?php

// 多位元組字串分割

function mbStringToArray ($str) {
if (empty(
$str)) return false;
$len = mb_strlen($str);
$array = array();
for (
$i = 0; $i < $len; $i++) {
$array[] = mb_substr($str, $i, 1);
}
return
$array;
}

// 移除兩端的 $rem

function mb_trim ($str, $rem = ' ') {
if (empty(
$str)) return false;
// 轉換為陣列
$arr = mbStringToArray($str);
$len = count($arr);
// 左側
for ($i = 0; $i < $len; $i++) {
if (
$arr[$i] === $rem) $arr[$i] = '';
else break;
}
// 右側
for ($i = $len-1; $i >= 0; $i--) {
if (
$arr[$i] === $rem) $arr[$i] = '';
else break;
}
// 轉換為字串
return implode ('', $arr);
}

?>
-4
motin at demomusic dot nu
17 年前
正如 peter dot albertsson at spray dot se 指出的,覆寫 `strlen` 可能會破壞處理二進位資料並依賴 `strlen` 來取得位元組長度的程式碼。

當以以下方式使用 `fwrite` 將字串填入檔案時,就會發生問題

`$len = strlen($data);`
`fwrite($fp, $data, $len);`

`fwrite` 將位元組數作為第三個參數,但 `mb_strlen` 返回字串中的字元數。由於多位元組字元每個的長度可能超過一個位元組,這將導致 `$data` 的最後幾個字元永遠不會寫入檔案。

在花費數小時調查為什麼 PEAR::Cache_Lite 無法運作之後,我發現了以上原因。

我嘗試使用單位元組函式,但它不起作用。無論如何發布在這裡,以防它能幫助其他人

/**
* PHP 單位元組函式模擬(不成功)
*
* 用法:sb_string(functionname, arg1, arg2, etc);
* 範例:sb_string("strlen", "tuöéä"); 返回 8 (應該...)
*/
`function sb_string() {`

`$arguments = func_get_args();`

`$func_overloading = ini_get("mbstring.func_overload");`

`ini_set("mbstring.func_overload", 0);`

`$ret = call_user_func_array(array_shift($arguments), $arguments);`

`ini_set("mbstring.func_overload", $func_overloading);`

`return $ret;`

}
To Top