XML 中包含特殊字符(<、>、&、'、")、HTML 片段、JSON、腳本代碼時,直接作為文本節點會被轉義導致可讀性差、解析麻煩。CDATA 區塊(<![CDATA[ ... ]]>)可保持原始內容不被解析器處理。本文演示 DOMDocument::createCDATASection 正確用法與常見陷阱。
<?php
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
// 建立根元素
$root = $dom->createElement('data');
$dom->appendChild($root);
// 普通文本節點(會自動轉義特殊字符)
$textNode = $dom->createTextNode('AT&T <tag> "quotes"');
$root->appendChild($textNode);
// CDATA 區塊(原樣保留,不轉義)
$cdata = $dom->createCDATASection('<script>alert("Hello & World")</script>');
$root->appendChild($cdata);
// 屬性節點(不支援 CDATA,需自行轉義)
$attr = $dom->createAttribute('json');
$attr->value = json_encode(['key' => 'value with & special']); // 屬性值自動轉義
$root->appendChild($attr);
// 輸出
header('Content-Type: application/xml; charset=utf-8');
echo $dom->saveXML();
/*
<?xml version="1.0" encoding="UTF-8"?>
<data>AT&T <tag> "quotes"<![CDATA[<script>alert("Hello & World")</script>]]><json json="{"key":"value with & special"}"/>
</data>
*/
?>
<?php
function buildArticleXml(array $article): string {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$root = $dom->createElement('article');
$dom->appendChild($root);
// 簡單字段用文本節點
$title = $dom->createElement('title', $article['title']);
$root->appendChild($title);
// 富文本內容用 CDATA
$content = $dom->createElement('content');
$cdata = $dom->createCDATASection($article['html_content']);
$content->appendChild($cdata);
$root->appendChild($content);
// 結構化數據用 JSON + CDATA(避免雙重轉義)
$meta = $dom->createElement('meta');
$json = json_encode($article['meta'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$meta->appendChild($dom->createCDATASection($json));
$root->appendChild($meta);
return $dom->saveXML();
}
// 測試
$article = [
'title' => 'PHP & XML',
'html_content' => '<h1>標題</h1><p>包含 & 特殊字符</p>',
'meta' => ['author' => '張三', 'tags' => ['php', 'xml'], 'views' => 123],
];
header('Content-Type: application/xml; charset=utf-8');
echo buildArticleXml($article);
?>
| 場景 | 處理方式 | 原因 |
|---|---|---|
內容含 ]]> | 拆分為多個 CDATA:<![CDATA[...]]]]><![CDATA[>...]]> | CDATA 內部不能出現結束標記 |
| 屬性值 | 不可用 CDATA,用 createAttribute 自動轉義 | XML 規範屬性值不支援 CDATA |
| JSON 序列化 | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES + CDATA | 避免 Unicode 轉義、斜槓轉義,保持可讀 |
| 大文本流式輸出 | XMLWriter + writeCData() | DOMDocument 需全量構建樹,記憶體佔用大 |
<?php
function streamArticleXml(array $article): void {
header('Content-Type: application/xml; charset=utf-8');
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('article');
$writer->writeElement('title', $article['title']);
$writer->startElement('content');
$writer->writeCData($article['html_content']);
$writer->endElement(); // content
$writer->startElement('meta');
$json = json_encode($article['meta'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$writer->writeCData($json);
$writer->endElement(); // meta
$writer->endElement(); // article
$writer->endDocument();
echo $writer->outputMemory();
}
?>
<?php
$xml = '<data><![CDATA[<b>Bold</b>]]></data>';
// simplexml_load_string 預設合併 CDATA 為文本
$simple = simplexml_load_string($xml);
echo (string)$simple->data; // 直接輸出:Bold
// DOMDocument 保留 CDATA 節點類型
$dom = new DOMDocument();
$dom->loadXML($xml);
foreach ($dom->documentElement->childNodes as $node) {
if ($node->nodeType === XML_CDATA_SECTION_NODE) {
echo "CDATA 內容: " . $node->data; // 獲取原始內容
}
}
?>
正確使用 CDATA 能讓 XML 既符合規範又保持業務內容完整性,是構建高質量數據交換介面的關鍵技能。