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

PHP Array2XML 類:陣列轉 XML 完整實作與進階應用

     編輯:PHP 相關知識

PHP Array2XML 類:陣列轉 XML 完整實作與進階應用

陣列與 XML 互轉是 API 串接、資料匯出、設定檔處理的常見需求。本文提供一個輕量級 Array2xml 類別,支援多維陣列遞迴轉換、屬性設定、編碼指定,並說明進階擴充方向。

基礎實作

<?php
class Array2xml {
    private $xml = '';
    private $encoding = 'utf-8';
    private $rootElement = 'root';
    private $numericKeyPrefix = 'item';
    private $attrSeparator = ' '; // 鍵值中用空格分隔屬性,如 "id name='value'"

    public function __construct($encoding = 'utf-8', $rootElement = 'root') {
        $this->encoding = $encoding;
        $this->rootElement = $rootElement;
    }

    /**
     * 主進入點
     * @param array  $array   要轉換的陣列
     * @param string $root    根元素名稱 (可覆蓋建構子設定)
     * @return string XML 字串
     */
    public function array2xml($array, $root = null) {
        $root = $root ?? $this->rootElement;
        $this->xml = '<?xml version="1.0" encoding="' . $this->encoding . '"?>';
        $this->xml .= $this->_array2xml($array, $root);
        return $this->xml;
    }

    /**
     * 遞迴轉換核心
     * @param mixed  $data     陣列或標量值
     * @param string $parentKey 父層鍵名 (用於建立元素名稱)
     * @return string XML 片段
     */
    private function _array2xml($data, $parentKey = '') {
        $xml = '';
        
        if (is_array($data)) {
            foreach ($data as $key => $value) {
                // 處理數字索引:轉為 ...
                if (is_numeric($key)) {
                    $elementName = $this->numericKeyPrefix;
                    $attributes = ' id="' . $key . '"';
                } else {
                    // 字串鍵名:支援 "tag attr='val'" 格式設定屬性
                    $parts = explode($this->attrSeparator, $key, 2);
                    $elementName = $parts[0];
                    $attributes = isset($parts[1]) ? ' ' . $parts[1] : '';
                }
                
                $xml .= "<{$elementName}{$attributes}>";
                $xml .= $this->_array2xml($value, $elementName);
                $xml .= "</{$elementName}>";
            }
        } elseif (is_bool($data)) {
            $xml .= $data ? 'true' : 'false';
        } elseif (is_null($data)) {
            $xml .= ''; // 或 <nil/>
        } else {
            // 標量值:需 CDATA 區塊保護特殊字元
            $xml .= '<![CDATA[' . $data . ']]>';
        }
        
        return $xml;
    }

    /** 輸出並送出 HTTP 標頭 */
    public function output($array, $root = null) {
        header('Content-Type: application/xml; charset=' . $this->encoding);
        echo $this->array2xml($array, $root);
    }
    
    /** 儲存至檔案 */
    public function save($array, $filename, $root = null) {
        $xml = $this->array2xml($array, $root);
        return file_put_contents($filename, $xml);
    }
}
?>

使用範例

基本陣列轉換

<?php
$converter = new Array2xml('utf-8', 'response');

$data = [
    'status' => 'success',
    'code'   => 200,
    'user'   => [
        'id'       => 1001,
        'username' => 'john_doe',
        'email'    => 'john@example.com',
        'roles'    => ['admin', 'editor'],
        'profile'  => [
            'nickname' => 'John',
            'avatar'   => 'https://example.com/avatar.jpg'
        ]
    ],
    'items' => [
        ['id'=>1, 'name'=>'Item A', 'price'=>100],
        ['id'=>2, 'name'=>'Item B', 'price'=>200],
    ]
];

echo $converter->array2xml($data);

輸出結果

<?xml version="1.0" encoding="utf-8"?>
<response>
    <status><![CDATA[success]]></status>
    <code><![CDATA[200]]></code>
    <user>
        <id><![CDATA[1001]]></id>
        <username><![CDATA[john_doe]]></username>
        <email><![CDATA[john@example.com]]></email>
        <roles>
            <item id="0"><![CDATA[admin]]></item>
            <item id="1"><![CDATA[editor]]></item>
        </roles>
        <profile>
            <nickname><![CDATA[John]]></nickname>
            <avatar><![CDATA[https://example.com/avatar.jpg]]></avatar>
        </profile>
    </user>
    <items>
        <item id="0">
            <id><![CDATA[1]]></id>
            <name><![CDATA[Item A]]></name>
            <price><![CDATA[100]]></price>
        </item>
        <item id="1">
            <id><![CDATA[2]]></id>
            <name><![CDATA[Item B]]></name>
            <price><![CDATA[200]]></price>
        </item>
    </items>
</response>

屬性支援範例

$data = [
    'product id="P1001" category="electronics"' => [
        'name'  => 'Smartphone',
        'price' => 29999,
        'specs' => [
            'spec name="screen"' => '6.5 inch OLED',
            'spec name="cpu"'     => 'Snapdragon 8 Gen 2',
        ]
    ]
];

// 輸出:
// 
//   
//   
//     
//     
//   
// 

進階擴充:XML2Array 反向解析

<?php
class Xml2Array {
    /**
     * XML 字串轉陣列
     * @param string $xml XML 字串
     * @param bool   $attributesAsPrefix 屬性是否加前綴區分
     * @return array
     */
    public static function parse($xml, $attributesAsPrefix = true) {
        $parser = xml_parser_create('UTF-8');
        xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
        xml_parse_into_struct($parser, $xml, $values, $index);
        xml_parser_free($parser);
        
        if (empty($values)) return [];
        
        $stack = [];
        $result = [];
        $current = &$result;
        
        foreach ($values as $val) {
            $tag = $val['tag'];
            $type = $val['type']; // open, close, complete, cdata
            $attrs = $val['attributes'] ?? [];
            $value = $val['value'] ?? '';
            
            if ($type === 'open') {
                $newNode = [];
                if ($attributesAsPrefix && $attrs) {
                    foreach ($attrs as $ak => $av) {
                        $newNode['@' . $ak] = $av;
                    }
                }
                $current[$tag][] = &$newNode;
                $stack[] = &$current;
                $current = &$current[$tag][count($current[$tag]) - 1];
            } elseif ($type === 'complete' || $type === 'cdata') {
                $node = $value;
                if ($attributesAsPrefix && $attrs) {
                    $node = ['@value' => $value];
                    foreach ($attrs as $ak => $av) {
                        $node['@' . $ak] = $av;
                    }
                }
                $current[$tag][] = $node;
            } elseif ($type === 'close') {
                $current = &$stack[count($stack) - 1];
                array_pop($stack);
            }
        }
        
        // 簡化單一元素陣列
        return self::simplify($result);
    }
    
    private static function simplify($data) {
        if (is_array($data)) {
            foreach ($data as $k => &$v) {
                if (is_array($v) && count($v) === 1 && isset($v[0]) && !isset($v['@value'])) {
                    $v = $v[0];
                }
                if (is_array($v)) {
                    $v = self::simplify($v);
                }
            }
        }
        return $data;
    }
}

// 使用
$xml = '<user id="1001"><name>John</name></user>';
$array = Xml2Array::parse($xml);
// 結果:['user' => [['@id'=>'1001', 'name'=>'John']]]

實務應用場景

場景說明關鍵點
API 回應RESTful API 支援 XML 格式Content-Type: application/xml;CDATA 保護
資料匯出報表、備份匯出 XML 檔案大資料量用 XMLWriter 串流寫入
設定檔系統設定以 XML 儲存結構固定,易驗證
第三方整合支付、物流、簡訊商 API需符合對方 XSD Schema
Sitemap 產生SEO 用 sitemap.xml定期重建,ping 搜尋引擎

效能考量:大資料量改用 XMLWriter

<?php
function exportLargeDatasetToXml($dataIterator, $filename) {
    $writer = new XMLWriter();
    $writer->openUri($filename);
    $writer->setIndent(true);
    $writer->setIndentString('  ');
    $writer->startDocument('1.0', 'UTF-8');
    $writer->startElement('dataset');
    
    foreach ($dataIterator as $row) {
        $writer->startElement('row');
        foreach ($row as $key => $value) {
            $writer->writeElement($key, $value);
        }
        $writer->endElement(); // row
    }
    
    $writer->endElement(); // dataset
    $writer->endDocument();
    $writer->flush();
    // 記憶體用量恆定,不隨資料量增長
}
?>

常見問題與解決

Q: 特殊字元 (<, >, &) 會破壞 XML 結構?
A: 使用 CDATA 區塊包裹所有文字內容,或配合 htmlspecialchars() 轉義。本類別採用 CDATA 方案。
Q: 如何產生 XML 宣告以外的 DTD/Schema 參考?
A: 在 startDocument 後加入 $writer->writeDtdElement(...) 或手動拼接 DOCTYPE 行。
Q: 命名空間 (xmlns) 如何處理?
A: XMLWriter::startElementNs() 支援命名空間;陣列轉換需擴充鍵名格式如 {ns}tag。
Q: 陣列鍵名包含非法 XML 元素字元 (如空格、數字開頭)?
A: 本類別支援屬性語法繞過;或預處理鍵名替換非法字元 (如 preg_replace('/[^a-zA-Z0-9_-]/', '_', $key))。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved