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是則么定義的