一個簡單的XML與數組之間的轉換


     xml是網絡使用最多的數據交換格式,所以,不掌握怎么操作它,又有蛋疼的了。

     php中可以操作xml的類/函數很多,個人認為最簡單的是SimpleXMLElement這個類,它的使用就跟其名字一樣:簡單。當然要想全面自如的操作xml,還得借助其他的類。SimpleXMLElement主要是對xml的進行節點的添加和獲取,以及輸出整個xml文本內容,但是對於實現一個簡單的與數組之間的內容轉換,已經足夠了。

     比如我們現在在接一個sdk,對方接口傳過來的是一個簡單的xml格式數據,我們需要取到它,並作一些處理,最后又返回給它,而且返回的仍是xml格式。能讓我們處理的游刃有余的格式,當然是數組為最好的。

     首先聲明,這個方法能處理的xml的格式比較特殊(待會兒會說這個代碼不能處理的情況),如下面這樣的:

      <response>

         <title>entity</title>

         <name>toy</name>

         <price>32.00</price>

         <date>2014-01-03 15:25:36</date>

      </response>

     首先它必須包含在一個總的開閉標簽內,當然,這是xml必須格式。然后它的元素名必須是非數字字符串,而且還不能有重復標簽元素,限制得很死,所以只能作為局部簡單的數據交換,真正說要傳送復雜的xml文件,玩不轉。

     在SimpleXMLElement這個類中,有個addChild方法,功能是添加xml節點,添加時可以返回指向該節點對象的變量。SimpleXMLElement類的構造函數或者是simplexml_load_string、simplexml_load_file方法,允許我們從另外一個文件或者從一個xml字符串來構造一個SimpleXMLElement類,它的asXML則方法返回一個標准化好的xml格式字符串數據。比如現在有一個空的、字符串xml格式數據"<xml><a>hello</a></xml>",使用addChild方法是這樣:

    $str = '<xml><a>hello</a></xml>';
    $sxe = simplexml_load_string($str);
    if($sxe){
        $cha = $sxe->addChild('cha');  // 添加一個空的標簽元素
        $cha->addChild('person', 'Jack');
        $cha->addChild('person', 'Perter');  // 添加兩個person標簽,值為對應的第二個參數
        echo $sxe->asXML();
    }

      以asXML格式輸出是這么個樣子

       

      全堆在一塊兒了,asXML方法只返回節點的文本內容,即節點的字符串值,所以看不出xml結構。右鍵查看源代碼或者chrome審查元素就能看到它的原貌:

      

     <xml>元素是整個xml數據的根節點,一開始里邊只有<a>元素,<cha>元素和它的兩個子元素<person>是通過代碼添加上去的,使用的就是addChild方法。

     轉換的大致思路是,從數組轉為xml時,以鍵作為元素名,對應的值作為該元素的值, 反過來從xml轉為數組,以元素名作為數組的鍵,元素值作為對應的值。存在的問題是,xml不允許純數字作為元素名,如果數組中有數字或數字字符串作為鍵名的,轉為xml時會失敗;從xml轉數組時,若是xml中同一代節點(處在同一級別)中有相同元素名的(<a type="ok">aaa</a>,我喜歡將a稱為元素名,aaa稱為元素值,type稱為屬性名,ok則稱為屬性值),后邊的元素值將覆蓋前面的元素的值,這也是開頭說的的一個傳遞數組的小例子時,里邊沒有相同元素名的元素。

  由於xml轉數組時,元素名一般不為數字,為字符串又相同時,可能導致數組的鍵名相同,值遭到覆蓋(除非相同的轉為數組),所以一般去請求別人的接口拿數據時,每個字段的元素名都不一樣,代表不同的含義,這時可以忽略這些可能出現的bug。

      1. 數組轉xml

      首先foreach數組,取得鍵和值,如果值是標量(當然一般也不會傳遞對象等等類型,除非序列化等),直接將元素加入SimpleXMLElement對象,元素名是鍵名,元素值時鍵對應的值;如果值是數組,則先加入一個空的元素,元素名是鍵名,然后它對應的數組再次按照前面的步驟處理,很明顯需要遞歸。

     

<?php
    /** 
      數組轉為XML
      @param arr array
      @param arr NULL|SimpleXMLElement
      return string       
    */
    function arrayToXml($arr, $sxe = NULL){
        $str = '<xmlData></xmlData>';   
        if($sxe instanceOf SimpleXMLElement){
            $xmlDoc = $sxe;
        }
        else{
            $xmlDoc = new SimpleXMLElement($str);
        }
        
        foreach($arr as $key => $val){
        if(is_array($val)){
            $child = $xmlDoc->addChild($key);
            arrayToXml($val, $child);
        }
        else{
            $xmlDoc->addChild($key, $val);
        }
        return $xmlDoc->asXML();
    }
    //測試
    $arr = array('a'=>'aaa', 'b'=>'bbb',  'c'=>array('d'=>1, 'e'=>2, 'f'=>'fff'));
    $xml = arrayToXml($arr);
    echo $xml.'<br/>';

     效果:

          

      <b>元素被處理成了HTML標簽。如果要做點改進的話,一是可以處理下數組中鍵為數字或數字字符串的情況,比如同一給一個名字num,因為xml數據中允許多個相同的元素名存在;二是我們本地的編輯器在碼代碼的時候可能編碼不是對方需要的,可以做一下編碼轉換,再傳輸給對方,下面是改進后的

<?php
    /** 
      數組轉為XML
      @param arr array
      @param arr NULL|SimpleXMLElement
      return string       
     */
    function arrayToXml($arr, $sxe = NULL){
        $str = '<xmlData></xmlData>';    
        if($sxe instanceOf SimpleXMLElement){
            $xmlDoc = $sxe;
        }
        else{
            $xmlDoc = new SimpleXMLElement($str);
        }
        
        foreach($arr as $key => $val){
            if(is_array($val)){
            
                if(is_numeric($key)){  // 一般來說XML中的標簽都是帶字母的字符串
                    $child = $xmlDoc->addChild('num');  // 添加新標簽,並返回指向該新標簽的變量
                }
                else{
                    $child = $xmlDoc->addChild($key);
                }
                arrayToXml($val, $child);  // 以當前子節點為對象,在該節點基礎上插入子數組中的元素
            }
            else{
                $val = mb_convert_encoding($val, 'UTF-8');
                if(is_numeric($key)){
                    $xmlDoc->addChild('num', $val);
                }
                else{
                    $xmlDoc->addChild($key, $val);
                }
            }
        }
        return $xmlDoc->asXML();
    }
    //測試
    $arr = array('a'=>'aaa', 5, '1'=>'bbb',  'c'=>array(1, 2, 'f'=>'fff'));
    $xml = arrayToXml($arr);
    echo $xml.'<br/>';

    效果:

        

     可以看到數字鍵名均替換成了num元素名,xml相同的元素名不會覆蓋。

     2. xml轉數組

  還是先拿簡單的說,比如需要轉化的xml數據結構很簡單,子節點都只有一層:

<response>
    <order_id>123</order_id>
    <cost>68</cost>
</response>

  這時大可不必用什么遍歷,而是先轉對象再取對象屬性,或者強制轉為數組。

    $xmlObj = simplexml_load_string('<response><order_id>123</order_id><cost>68</cost></response>');
    $arr = get_object_vars($xmlObj);  // 取對象屬性
    echo 'arr=><pre>';
    var_dump($arr);
    $arr2 = (array)$xmlObj;  // 或者,強制轉換為數組
    // echo 'arr2=><pre>';
    // var_dump($arr2);

    如果需要處理的xml字符串不像上面那么簡單, 或者你想玩點稍微復雜點的,則需要遍歷xml元素節點,取它們的元素名和元素值等一步步處理。SimpleXMLElement類本身不具備遍歷的方法,可以用到它的子類SimpleXMLIterator,一看名字就知道這個類專為迭代、遍歷准備的。它不但集成SimpleXMLElement,還實現了遞歸迭代器和統計等接口,在遍歷一個xml對象時,主要用到一下幾個方法:

     current(void)    返回當前元素

     key(viod)     返回當前元素名

     hasChildren(void)     判斷當前元素是否有子元素

     valid(void)   判斷當前當前指向元素是否有效,遍歷到頭指向對象最后一個元素的下一個則無效

     next(void)    指向下一個元素

     rewind(void)    設置指向對象最開頭的元素

     需要注意的是,指向對象的第一個元素,是指向如下邊中的<a>,而不是<xml>,<xml>是將整個xml數據包含起來的元素,這點需要注意,當然若想仔細學習下xml,還得專門看看它的定義使用,當然還有很多別的可以設置的地方

     <xml>

     <a>a</a>

     <b>b</b>

     </xml>

     轉換思路是,如果傳進來的不是對象(最開始肯定是傳遞一個xml字符串進來),先創建SimpleXMLIterator對象,然后遍歷該對象元素,如果該元素沒有子元素,將它的元素名作為鍵,元素值作為對應值放入數組;如果有子元素,將它的元素名取出來,作為鍵,而將該子元素變量傳入該方法, 遞歸返回,代碼如下    

<?php
    /**
     XML轉數組
     @param xml string|SimpleXMLIterator
     return array
     */
    function xmlToArray($xml){
        $ret = array();
        if($xml instanceOf SimpleXMLElement){
            $xmlDoc = $xml;
        }
        else{
            $xmlDoc = simplexml_load_string($xml, 'SimpleXMLIterator');
            if(!$xmlDoc){      // xml字符串格式有問題
                return null;
            }
        }
        
        for($xmlDoc->rewind(); $xmlDoc->valid(); $xmlDoc->next()){
            $key = $xmlDoc->key();       // 獲取標簽名
            $val = $xmlDoc->current();   // 獲取當前標簽
            if($xmlDoc->hasChildren()){     // 如果有子元素
                $ret[$key] = xmlToArray($val);  // 子元素變量遞歸處理返回
            }
            else{
                $ret[$key] = (string)$val;   
            }
        }
        return $ret;
    }
    $xmlstr = '<xml><name>cat</name><color>black</color><feature><weight>1.0kg</weight><height>0.25m</height><hobby>sleep</hobby></feature></xml>';
    $array = xmlToArray($xmlstr);
    echo 'xml to array=><pre>';
    print_r($array);

    打印結果:

     

  但是只用SimpleXMLElement對象實際上也能夠做到,主要用到

  children:獲取xml元素的子節點,比如上邊名為xml的元素的子節點,不只一個,雖然它返回的仍是一個SimpleXMLElement對象,但是這個對象是可迭代的,既可以用foreach遍歷每一個子節點。同時,如果是單個Element節點,這個函數返回空,此時就可以取它的元素值了作為數組元素的值返回了,直接轉為string即可。

  getName:獲取元素名,這樣可以得到轉為數組中鍵名  

<?php
    function xmlToArr($xml) 
    {
        if (!($xml->children()))  // 單個子元素,轉為字符串返回
        {
            return (string) $xml;
        }
        // 直接遍歷子元素對象
        foreach ($xml->children() as $child)
        {
            $name = $child->getName();  // 獲取元素名
            if (count($xml->$name) ==1 )
            {
                $element[$name] = xmlToArr($child);
            } 
            else  // 如果同名子元素有多個,裝在一個數組里邊
            {
                $element[][$name] =xmlToArr($child);
            }
        }
        return $element;
    }                                

  上邊這個方法將同名子節點保存成了數組,也可以按自己的需要以其他的方式組織,需要傳入一個SimpleXMLElement對象。

  當然以上僅僅是簡單的使用方式,xml實際上還有命名空間、名稱前綴等等,還是得看看xml是則么定義的

     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM