首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 5 SAX 解析 XML:SimpleDocumentParser 類別實作解析

     編輯:PHP 相關知識

事件驅動解析:SAX 解析器在 PHP 5 中的物件導向封裝

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 vs DOM vs XMLReader 比較

解析器模式記憶體速度適用場景
SAX (xml_parser)事件驅動 (推)低 (流式)快大型 XML、單向遍歷、記憶體受限
DOM (DOMDocument)樹狀結構 (拉)高 (全載入)中需隨機存取、修改、XPath 查詢、文件較小
XMLReader游標前進 (拉)低 (流式)快大型 XML、需控制解析流程、PHP 5.1+ 內建

XMLReader 現代替代範例

<?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();
?>

常見問題與解決

  1. 編碼錯誤:確保 XML 檔案宣告 <?xml version="1.0" encoding="UTF-8"?>,且內容確為 UTF-8。PHP SAX 內部以 UTF-8 處理,XML_OPTION_TARGET_ENCODING 僅影響輸出編碼。
  2. 命名空間處理:SAX 回呼中的 $name 包含前綴 (如 ns:tag),屬性陣列含 xmlns:ns。需自行維護命名空間上下文。
  3. 實體參考:&lt; 等實體會自動展開為字元資料,characterData 會收到展開後的字串。
  4. 大檔案記憶體:SAX 本身流式處理,但本範例建立完整物件樹會佔用記憶體。真正流式處理應在 startElement/endElement 即時處理、不保留樹結構。

最佳實踐建議

  • 新專案優先使用 XMLReader:API 更現代、支援 Schema 驗證、可切換至 DOM 進行局部深度處理。
  • 需 XPath/XSLT 時使用 DOMDocument + DOMXPath。
  • 解析前先驗證 XML 格式良善 (Well-formed),避免解析器拋出難以追蹤的錯誤。
  • 外部實體 (XXE) 防護:libxml_disable_entity_loader(true) (PHP 8.0+ 預設關閉)。

SAX 封裝教學展示了事件驅動解析的核心機制,理解其運作有助於選擇適合專案的 XML 處理策略。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved