PHP使用preg_replace_callback清理Word XML变量间空标签
我太懂这种痛苦了!Word的XML格式真的会把简单的变量拆得七零八落——一会儿在字符间插空<w:r>节点,一会儿把一个完整的${variablename}拆成好几个<w:t>标签,直接用正则根本抓不到完整的变量。我之前处理过类似的需求,下面是一套经过验证的完善方案:
第一步:先清理XML中的冗余节点,合并拆分的文本
Word为了处理格式细节,经常会把连续文本拆分散布在多个<w:t>标签里,还会插入一堆空的XML节点。我们先用DOMDocument来清理这些冗余内容,把变量的字符尽量合并成连续的文本:
$dom = new DOMDocument(); libxml_use_internal_errors(true); // 忽略Word XML的格式警告(毕竟Word生成的XML经常不那么标准) $dom->loadXML($xml); libxml_clear_errors(); $xpath = new DOMXPath($dom); // 1. 清理空的<w:t>节点,以及空的<w:r>父节点 foreach ($xpath->query('//w:t[normalize-space(.)=""]') as $emptyTextNode) { $parentRun = $emptyTextNode->parentNode; $emptyTextNode->parentNode->removeChild($emptyTextNode); // 如果父节点<w:r>现在没有子节点了,直接删掉这个空段落 if ($parentRun->childNodes->length === 0) { $parentRun->parentNode->removeChild($parentRun); } } // 2. 合并同一段落内连续的文本节点 foreach ($xpath->query('//w:p') as $paragraph) { $combinedText = ''; $nodesToReplace = []; // 遍历段落内的所有<w:r>节点,收集文本 foreach ($paragraph->childNodes as $node) { if ($node->nodeName === 'w:r') { $textNodes = $xpath->query('./w:t', $node); if ($textNodes->length > 0) { $combinedText .= $textNodes->item(0)->nodeValue; $nodesToReplace[] = $node; } } } // 如果收集到了合并后的文本,替换掉原来的分散节点 if (!empty($combinedText) && count($nodesToReplace) > 0) { $newRun = $dom->createElement('w:r'); $newText = $dom->createElement('w:t', $combinedText); $newRun->appendChild($newText); // 插入新节点到第一个要替换的节点位置 $nodesToReplace[0]->parentNode->insertBefore($newRun, $nodesToReplace[0]); // 删除原来的分散节点 foreach ($nodesToReplace as $oldNode) { $oldNode->parentNode->removeChild($oldNode); } } } // 把处理后的DOM转回XML字符串 $cleanedXml = $dom->saveXML();
第二步:正则匹配并替换变量
现在XML里的变量已经是连续的${variablename}格式了,就可以用preg_replace_callback来完成替换:
// 假设用户输入的变量存在这个数组里 $userVariables = [ 'name' => 'John', 'email' => 'john@example.com', 'age' => '30' ]; $finalXml = preg_replace_callback( '/\$\{([a-zA-Z0-9_]+)\}/', // 匹配变量名,支持字母、数字和下划线 function($matches) use ($userVariables) { $varName = $matches[1]; // 如果变量存在就替换,否则保留原变量格式 return isset($userVariables[$varName]) ? $userVariables[$varName] : $matches[0]; }, $cleanedXml );
额外优化:处理Word的特殊字符实体
有时候Word会把$这类字符转成HTML实体(比如$),导致正则匹配失败。可以在清理XML之前先把实体转成普通字符:
// 转义HTML实体,确保$、{、}都是普通字符 $xml = html_entity_decode($xml, ENT_QUOTES, 'UTF-8');
为什么不用纯正则处理?
XML的结构太灵活了,纯正则很容易误匹配到XML属性里的${xxx}(比如某个标签的属性值刚好包含类似格式的内容)。用DOMDocument处理可以确保我们只操作文本节点里的内容,安全性和可靠性都更高。
内容的提问来源于stack exchange,提问作者Mark Portbury
相关产品推荐
相关产品推荐

