
本文详解如何正确解析形如 1,"3,4",5,6,"7,8,9",10,11 的混合格式字符串,将其按语义分割为独立字段(保留引号内逗号),并逐行输出,避免简单 explode() 导致的错误切分。
本文详解如何正确解析形如 `1,"3,4",5,6,"7,8,9",10,11` 的混合格式字符串,将其按语义分割为独立字段(保留引号内逗号),并逐行输出,避免简单 `explode()` 导致的错误切分。
在 PHP 开发中,处理 CSV 风格但未严格遵循 RFC 4180 标准的字符串(如含嵌套逗号的双引号字段)是一个常见痛点。直接使用 explode(',', $str) 会错误地将 "3,4" 拆成 "3 和 4",破坏数据完整性。根本原因在于:逗号是否作为分隔符,取决于其是否处于双引号上下文中——这本质上是一个状态机问题,需跟踪当前是否处于引号内。
以下是一个健壮、可复用的 tokenize() 函数,采用「状态机」思想实现精准解析:
<?php header('Content-type: text/plain');
function tokenize($str)
{
$state = "normal"; // 当前状态:normal(普通文本)或 quotes(引号内)
$tokens = [];
$current = ""; // 当前正在构建的字段内容
for ($i = 0; $i < strlen($str); $i++) {
$c = $str[$i];
if ($state === "normal") {
if ($c === ',') {
// 遇到逗号且不在引号内 → 当前字段结束
if ($current !== "") {
$tokens[] = $current;
$current = "";
}
continue;
}
if ($c === '"') {
// 进入引号模式,清空 current(引号本身不保留)
$state = "quotes";
$current = "";
continue;
}
$current .= $c; // 普通字符直接追加
} elseif ($state === "quotes") {
if ($c === '"') {
// 遇到结束引号 → 字段完成,返回 normal 状态
$state = "normal";
$tokens[] = $current;
$current = "";
continue;
}
$current .= $c; // 引号内所有字符(包括逗号)均保留
}
}
// 处理末尾未被逗号终止的字段(如字符串结尾无逗号)
if ($current !== "") {
$tokens[] = $current;
}
return $tokens;
}
// 示例输入
$str = '1,"3,4",5,6,"7,8,9",10,11';
$result = tokenize($str);
// 逐行输出结果
foreach ($result as $item) {
echo $item . "\n";
}
?>
运行输出:
1 3,4 5 6 7,8,9 10 11
✅ 关键设计说明:
-
状态驱动:仅通过
normal/quotes两个状态即可准确区分分隔符与内容; - 边界安全:自动处理开头/结尾无逗号、连续逗号、空字段等边缘情况;
-
零依赖:不依赖
fgetcsv()或外部库,适用于纯字符串解析场景; -
可扩展性强:如需支持转义引号(
""表示一个"),只需在quotes状态下增加对""的检测逻辑。
⚠️ 注意事项:
- 此实现假设引号成对出现且无嵌套(符合标准 CSV 规范);若存在非法格式(如单引号未闭合),函数将按实际字符流解析,不抛出异常;
- 对于生产环境中的复杂 CSV 文件(含换行、多字节字符、BOM 等),仍推荐使用内置
str_getcsv()(PHP ≥ 5.3.0)或fgetcsv(),它们已针对 RFC 4180 做了充分优化; - 若需兼容单引号或自定义分隔符,可将
$state扩展为对象,并将分隔符、引号字符作为参数传入。
掌握这种基于状态机的手动解析方法,不仅能解决当前问题,更能加深对词法分析本质的理解,是构建 DSL 解析器、配置文件读取器等工具的重要基础。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











