PHP 5 提供 xml_parser_create() 等底層 SAX API,本文展示將其封裝為 SimpleDocumentParser 類別,解析 XML 建立 DOM 風格的物件樹 (SimpleDocumentRoot、SimpleDocumentObject)。
<?php
/**
* SAX 解析器封裝:解析 XML 檔案並建立 SimpleDocumentObject 樹
*/
class SimpleDocumentParser {
private $domRootObject = null;
private $currentNode = null;
private $currentName = null;
private $currentValue = '';
private $currentAttributes = [];
public function getSimpleDocument() {
return $this->domRootObject;
}
public function parse($filePath) {
$xmlParser = xml_parser_create();
xml_parser_set_option($xmlParser, XML_OPTION_CASE_FOLDING, 0); // 保留大小寫
xml_parser_set_option($xmlParser, XML_OPTION_SKIP_WHITE, 1); // 跳過空白節點
xml_parser_set_option($xmlParser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
xml_set_object($xmlParser, $this);
xml_set_element_handler($xmlParser, [$this, 'startElement'], [$this, 'endElement']);
xml_set_character_data_handler($xmlParser, [$this, 'characterData']);
$content = file_get_contents($filePath);
if (!xml_parse($xmlParser, $content)) {
$errCode = xml_get_error_code($xmlParser);
$errLine = xml_get_current_line_number($xmlParser);
xml_parser_free($xmlParser);
throw new Exception(sprintf("XML 錯誤: %s at line %d", xml_error_string($errCode), $errLine));
}
xml_parser_free($xmlParser);
}
// 開始標籤處理
public function startElement($parser, $name, $attrs) {
$this->currentName = $name;
$this->currentAttributes = $attrs;
$this->currentValue = '';
$newNode = new SimpleDocumentObject($name, $attrs);
if ($this->currentNode === null) {
$this->domRootObject = $newNode;
} else {
$this->currentNode->addChild($newNode);
}
$this->currentNode = $newNode;
}
// 字元資料處理
public function characterData($parser, $data) {
$this->currentValue .= $data;
if ($this->currentNode) {
$this->currentNode->setContent($this->currentValue);
}
}
// 結束標籤處理
public function endElement($parser, $name) {
if ($this->currentNode && $this->currentNode->getParent()) {
$this->currentNode = $this->currentNode->getParent();
}
$this->currentValue = '';
}
}
// 節點物件類別 (簡化版)
class SimpleDocumentObject {
private $name;
private $attributes = [];
private $content = '';
private $children = [];
private $parent = null;
public function __construct($name, $attrs = []) {
$this->name = $name;
$this->attributes = $attrs;
}
public function addChild(SimpleDocumentObject $child) {
$child->parent = $this;
$this->children[] = $child;
}
public function setContent($content) { $this->content = trim($content); }
public function getContent() { return $this->content; }
public function getName() { return $this->name; }
public function getAttributes() { return $this->attributes; }
public function getChildren() { return $this->children; }
public function getParent() { return $this->parent; }
public function getAttribute($name) { return $this->attributes[$name] ?? null; }
}
// 使用範例
$parser = new SimpleDocumentParser();
$parser->parse('data.xml');
$doc = $parser->getSimpleDocument();
// 遍歷樹結構
function dumpNode(SimpleDocumentObject $node, $indent = 0) {
echo str_repeat(' ', $indent) . "<{$node->getName()}>";
if ($node->getAttributes()) echo ' ' . json_encode($node->getAttributes());
if ($node->getContent()) echo ' ' . htmlspecialchars($node->getContent());
echo "\n";
foreach ($node->getChildren() as $child) {
dumpNode($child, $indent + 1);
}
}
dumpNode($doc);
?>
| 解析器 | 模式 | 記憶體 | 速度 | 適用場景 |
|---|---|---|---|---|
| SAX (xml_parser) | 事件驅動 (推) | 低 (流式) | 快 | 大型 XML、單向遍歷、記憶體受限 |
| DOM (DOMDocument) | 樹狀結構 (拉) | 高 (全載入) | 中 | 需隨機存取、修改、XPath 查詢、文件較小 |
| XMLReader | 游標前進 (拉) | 低 (流式) | 快 | 大型 XML、需控制解析流程、PHP 5.1+ 內建 |
<?php
$reader = new XMLReader();
$reader->open('large.xml');
while ($reader->read()) {
if ($reader->nodeType == XMLReader::ELEMENT && $reader->name === 'item') {
// 擴展為 DOM 節點進行局部處理
$dom = new DOMDocument();
$node = $dom->importNode($reader->expand(), true);
// 處理 $node...
echo $node->getAttribute('id') . ': ' . $node->textContent . "\n";
}
}
$reader->close();
?>
<?xml version="1.0" encoding="UTF-8"?>,且內容確為 UTF-8。PHP SAX 內部以 UTF-8 處理,XML_OPTION_TARGET_ENCODING 僅影響輸出編碼。$name 包含前綴 (如 ns:tag),屬性陣列含 xmlns:ns。需自行維護命名空間上下文。< 等實體會自動展開為字元資料,characterData 會收到展開後的字串。startElement/endElement 即時處理、不保留樹結構。XMLReader:API 更現代、支援 Schema 驗證、可切換至 DOM 進行局部深度處理。DOMDocument + DOMXPath。libxml_disable_entity_loader(true) (PHP 8.0+ 預設關閉)。SAX 封裝教學展示了事件驅動解析的核心機制,理解其運作有助於選擇適合專案的 XML 處理策略。