PHP处理XML

版权声明:转载请标注源地址。 https://blog.csdn.net/rkexy/article/details/72547782

工作中遇到用PHP处理XML的相关东西。刚刚学PHP,代码中有很多值得优化的地方。
主要运用simpleXML处理。写了一个通用化的XML处理代码。


Xmlcreater.php

<?php
/**
*   @author:    Even
*   @version:   1.0.1
*   @date:      2017.5.17
*/


define('LOAD_XMLFILE_ERROR',-1);
define('FILE_IO_ERROR', -2);

define('XML_HEADER','<?xml version="1.0" encoding="utf-8" ?>');


class Xmlcreater{

    protected $m_arrayXMLData = null;
    protected $m_errorMessage = null;
    protected $m_objXML          = null;   /*This is only part of the XML input object. */
    protected $m_rootName    = '';
    protected $m_xmlFilePath  = '';
    //protected $m_xmlString    = '';      /*This is only part of the XML output string.*/
    protected $m_xmlFileObj   = null;

    /*There must be a parameter for the time being.*/
    public function __construct(){
        $argNum = func_num_args();
        switch($argNum){
            case 0:
                $this->m_xmlFilePath = '';
                break;
            case 1:
                $this->m_xmlFilePath = func_get_arg(0);
                break;
        }

        /*Load XML File.*/
        try{
            if (!file_exists($this->m_xmlFilePath)) {  
                $this->m_xmlFileObj = fopen($this->m_xmlFilePath,'w+');
                fwrite($this->m_xmlFileObj,'<a> </a>');
                fclose($this->m_xmlFileObj);
                $this->m_xmlFileObj = null;
            }
            libxml_disable_entity_loader(false);  
            $this->m_objXML = simplexml_load_file($this->m_xmlFilePath,'SimpleXMLElement', LIBXML_NOCDATA); 
        }
        catch(Exception $e){
            $this->m_errorMessage = LOAD_XMLFILE_ERROR;
        }
    }

    public function __destruct() {
        if(null != $this->m_xmlFileObj){
            fclose($this->m_xmlFileObj);
            $this->m_xmlFileObj = null;
        }

    }


    /*Get Data From Database For Create XML File.*/
    public function getData(){
        $this->m_arrayXMLData = $this->xmlToArray($this->m_objXML);
        return $this->m_arrayXMLData;
    }

    public function getRootName(){
        return $this->m_rootName;
    }

    public function setRootName($strName){
        $this->m_rootName = $strName;
    }

    /**
    *   @param: Source Data Array, XML Root Name, The XML Object.
    *   @return String: XML String. 
    *   @summary: 
    *       The #3 Parament Only For Recursive Functions, We Can Use $dataArray And $rootName When We Call This Function.
    *
    */
    public function arrayToXML($dataArray, $rootName, &$xmlObj = null ){
        $ptrArray   = null;
        $ptrElement = null;

        /*Create XML Object.*/
        if(null == $xmlObj){
            $tempXml = XML_HEADER.'<'.$rootName.'></'.$rootName.'>';
            $xmlObj  = simplexml_load_string($tempXml);
        }

        if(null != $rootName){
            $dataArray = $dataArray[$rootName];
        }

        /*Add Attributes.*/
        if(array_key_exists('@attributes',$dataArray)) {
            foreach ($dataArray['@attributes'] as $keyAttr => $valueAttr) {
                $xmlObj->addAttribute($keyAttr,$valueAttr);
            }
        }

        /*Add Element.*/
        //CHECKIT: if(is_array($valueEle));
        foreach ($dataArray as $keyEle => $valueEle) {
            if($keyEle != '@attributes'){
                if(is_array($valueEle)){
                    $ptrArray = $valueEle;
                    $ptrElement = $xmlObj->addChild($keyEle);
                }
                else{
                    $ptrElement = $xmlObj->addChild($keyEle,$valueEle);
                } 
                $this->arrayToXML($ptrArray,null,$ptrElement);
            }
        }/*End of foreach.*/

        return $xmlObj->saveXML();
    }   /*End Of Function arrayToXML.*/

    /**
    *   @param: The XML String Or Object.
    *   @return: XML Object.
    *
    */
    private function checkValueType(&$xml){
        $valueType = gettype($xml);
        switch($valueType){
            case 'string':
                return simplexml_load_string($xml);
            case 'object':
                return $xml;
            default:
                return null;
        }
    }


    /**
    *   @param: Simple XML Object / XML String.
    *   @return: 
    *       a.An Array Of XML Data.
    *       b.If XML String Is Not A Standard XML, Return Null Or Generate An Exception.
    *   @summary: 
    *       XML Object To Array Value.
    *
    */
    public function xmlToArray($xml){
        $xml = $this->checkValueType($xml);
        $this->m_arrayXMLData = json_decode(json_encode($xml),true);
        return $this->m_arrayXMLData;
    }   /*End Of Function xmlToArray.*/


    public function createXmlFile($fileInfo, $xmlString){
        $tempRes = null;
        try{
            /*Create File.*/
            $this->m_xmlFileObj = fopen($fileInfo,"w");
            //chmod($this->m_xmlFileObj, 0757);
            /*Write File.*/
            $tempRes = fwrite($this->m_xmlFileObj,$xmlString);
            return $tempRes;
        }
        catch(Exception $e){
            return FILE_IO_ERROR;
        }
    }

    public function getXmlString($simplexmlObj){
        return $simplexmlObj->saveXML();
    }

    public function getXmlObject($xmlString){
        return simplexml_load_string($xmlString);
    }

    /**
    *   @param:
    *   @return: Attributes Array.
    */
    public function getAttributes($xml,$elementName){
        $xmlObject = $this->checkValueType($xml);
        $arrayAttributes = array();

        if($xmlObject->getName() == $elementName){
            return $xmlObject->attributes();
        }

        foreach($xmlObject->children() as $child){
            if($child->getName() == $elementName){
                foreach ($child->attributes() as $key => $value) {
                    $arrayAttributes[$key] = (string)$value;
                }
                return $arrayAttributes;
            }
            else{
                return $this->getAttributes($child,$elementName);
            }
        }
    }

    /**
    *   @return: Simple XML Object.
    */
    public function getElement($xml,$elementName){
        $xmlObject = $this->checkValueType($xml);
        if($elementName == $xmlObject->getName()){
            return $xmlObject;
        }

        foreach($xmlObject->children() as $child){
            if($child->getName() == $elementName){
                return $child;
            }
            else{
                return $this->getElement($child,$elementName);
            }
        }
    }

    public function addChildXml(&$xml, $fatherName, $childName, $value){
        $xmlObject = $this->checkValueType($xml);
        if($fatherName != null){
            $xmlObject = $this->getElement($xmlObject, $fatherName);
        }
        $xmlObject->addChild($childName,$value);
        if(is_object($xml))	{$xml = $xmlObject; }
        elseif (is_string($xml)) {$xml = $xmlObject->saveXML(); }
    }
}


下面贴上一个测试类,嗯……可能无法运行,因为这里用到了其它的数据(RangUtil.php)。

Slavexml.php

<?php
/**
*   @author:    Even
*   @version:   1.0.2
*   @date:      2017.5.19
*/
include_once('Xmlcreater.php');
include_once($_SERVER['DOCUMENT_ROOT'].'/application/libraries/RangUtil.php');
/*
array(2) { 
    ["@attributes"]=> array(5) { 
        ["name"]=> string(16) "modbus_rtu_slave" 
        ["exename"]=> string(16) "modbus_rtu_slave" 
        ["port"]=> string(7) "RS485-1" 
        ["addr"]=> string(2) "10" 
        ["status"]=> string(1) "3" 
    } 
    ["bus_info"]=> array(1) { 
        ["bus"]=> array(1) { 
            ["@attributes"]=> array(4) { 
            ["baudrate"]=> string(4) "9600" 
            ["databits"]=> string(1) "8" 
            ["paritybit"]=> string(4) "none" 
            ["stopbits"]=> string(1) "1"
            } 
        } 
    }
 }
*/
define("XML_FILE_NAME",'modbus_rtu_slave');
define("XML_FILE_PATH",$_SERVER['DOCUMENT_ROOT'].'/upplugin/rtu_slave/');

class Slavexml extends Xmlcreater{
    private $templateArray = array();
    private $xmlFormat    = array();

    public function __construct(){
        $rangUtil = new RangUtil();
        $this->m_rootName = 'template';
        $this->m_xmlFilePath = XML_FILE_PATH.XML_FILE_NAME.'.xml';
        parent::__construct($this->m_xmlFilePath);

        $this->templateArray = array(
        'template'=>array(
            '@attributes' => array('name'=>'', 'exename'=>'', 'port'=>'', 'addr'=>'', 'status'=>''),
            'bus_info'=>array(
                'bus'=>array(
                    '@attributes'=>array('baudrate'=>'', 'databits'=>'', 'paritybit'=>'', 'stopbits'=>'' )
                )
            )
        ));
        $this->xmlFormat = array('name'=> array(XML_FILE_NAME) , 'exename'=> array(XML_FILE_NAME),
            'port'=> $rangUtil->m_dataLimit['portarray'],
            'addr'=> array("options" => array("min_range"=>1, "max_range"=>247)),
            'status'=>array(0,1,3),
            'baudrate'=>$rangUtil->m_dataLimit['baudratearray'],
            'databits'=>$rangUtil->m_dataLimit['databitsarray'],
            'paritybit'=>$rangUtil->m_dataLimit['paritybitarray'],
            'stopbits'=>$rangUtil->m_dataLimit['stopbitarray']
            );
        /*Initializate Class Memery Value.*/
        $this->templateArray = array_replace($this->templateArray,$this->xmlToArray($this->m_objXML));
    }

    public function setTemplateArray($arrayTemplateAttr,$arrayBusAttr){
        $this->templateArray['template']['@attributes'] = 
            array_replace($this->templateArray['template']['@attributes'], $arrayTemplateAttr);

        $this->templateArray['template']['bus_info']['bus']['@attributes'] = 
            array_replace($this->templateArray['template']['bus_info']['bus']['@attributes'], $arrayBusAttr);
    }

    public function getXmlStatus(){
        $tempXmlObject = simplexml_load_file($this->m_xmlFilePath);
        $tempAttributesArray = $this->getAttributes($tempXmlObject,'template');
        return $tempAttributesArray['status'];
    }

    public function setXmlStatus($paraStatus){
        $this->templateArray['template']['@attributes']['status'] = $paraStatus;
        $this->createSlaveXml();
    }

    public function getTemplateArray(){
        return $this->templateArray;
    }

    public function createSlaveXml(){
        $xmlString = $this->arrayToXML($this->templateArray,$this->m_rootName);
        $this->createXmlFile($this->m_xmlFilePath,$xmlString);
    }

    private function checkData($key,$value){
        if($key == 'addr'){ 
            if(!filter_var($value, FILTER_VALIDATE_INT, $this->xmlFormat['addr'])){
                return -3;
            }
            else{
                return 0;
            }
        }
        else{
            if(!array_key_exists($key,$this->xmlFormat)){
                return -6;
            }
            elseif(false === array_search($value,$this->xmlFormat[$key])){
                return -5;
            }
            else return 0;
        }

    }


    /*Not Call Callback Function That If $callback Is Null.*/
    private function arrayDiff($srcArray,$tagArray,$callbackFunc){
        $errorInfo = array('result'=> 'OK', 'location'=> null,'reason' => null);


        if(count($srcArray) != count($tagArray)){
            $errorInfo['result'] = -2;
            $errorInfo['location'] = null;
            $errorInfo['reason'] = 'Lack Of Items Or Too Much. Near By '.key($tagArray);
            return $errorInfo;
        }

        foreach ($tagArray as $key => $value) {
            if(array_key_exists($key,$srcArray)){
                if(is_array($value)){
                    return $this->arrayDiff($srcArray[$key],$value,'$this->checkData');
                }
                else{
                    /*$value Is Not An Array.*/
                    if($callbackFunc == null){
                        /*Don't Check Data.*/
                        continue;
                    }
                    else{
                        /*Check Data.*/
                        $ret = eval('return '.$callbackFunc.'(\''.$key.'\',\''.$value.'\');');
                        switch($ret){
                            case 0:  
                                break;
                            case -3: 
//                              $errorInfo['result'] = -3;
//                              $errorInfo['location'] = $key;
//                              $errorInfo['reason'] = 'addr Is Beyond Range.';
//                              return $errorInfo;
//                              break;
                            case -4:
                                $errorInfo['result'] = -4;
                                $errorInfo['location'] = $key;
                                //$errorInfo['reason'] = 'addr Is Not Intager.';
                                $errorInfo['reason'] = 'addr ERROR.';
                                return $errorInfo;
                                break;
                            case -5:
                                $errorInfo['result'] = -5;
                                $errorInfo['location'] = $key;
                                $errorInfo['reason'] = $key.': '.$value.' Is Not A Correct Value.';
                                return $errorInfo;
                                break;
                            case -6:
                                $errorInfo['result'] = -6;
                                $errorInfo['location'] = $key;
                                $errorInfo['reason'] = $key.': The keys of the error';
                                return $errorInfo;
                                break;
                        }
                    }
                }
            }
            else{
                $errorInfo['result'] = -1;
                $errorInfo['location'] = $key;
                $errorInfo['reason'] = 'Did Not Exist \" '.$key.'\".';
                return $errorInfo;
            }
        }
        return $errorInfo;
    }


    public function checkXml($xmlString){

        $errorInfo = array('result'=>null,'reason'=>null);
        $xmlArray  = array();

        $xmlObject = @simplexml_load_string($xmlString);
        if(!$xmlObject){
            /*It Is Not A Standard XML String.*/
            $errorInfo['result'] = 'FAULT';
            $errorInfo['reason'] = 'It Is Not A Standard XML.';
            return $errorInfo;
        }

        $xmlArray = $this->xmlToArray($xmlObject);
        $xmlArray = array($this->m_rootName => $xmlArray);
        $errorInfo = $this->arrayDiff($this->templateArray,$xmlArray,'$this->checkData');
        return $errorInfo;
    }
}

其中,RangUtil.php长这个样子。

RangUtil.php

<?php

/**
 * Created by PhpStorm.
 * User: wangyue
 * Date: 2017/4/14
 * Time: 15:56
 */
class RangUtil
{
    public function __construct() {
    }
    public $bustypearray= array('DL/T 645-1997','DL/T 645-2007','ModBus','CJ/T 188-2004');
    public $baudratearray=array('300','600','1200','2400','4800','9600','19200','38400','57600');
    public $databitsarray=array('5','6','7','8');
    public $paritybitarray=array('no','even','odd','space','mark');
    public $stopbitarray=array('1','2');
    public $addrarray=array(17,18,19,20,21,22,23,24,25,26,27,28,29,30,31);

    /*Used For Slave.*/
    public $m_dataLimit = array(
        'baudratearray' => array(300,600,1200,2400,4800,9600,19200,38400,57600),
        'databitsarray' => array(5,6,7,8),
        'paritybitarray'=> array('no','even','odd'),
        'stopbitarray'  => array(1,2),
        'portarray'     => array(1,2,3,4)
        );

}

写完之后,我发现了一个比较令我尴尬的地方,关于arrayToXMLxmlToArray两个函数。
arrayToXML生成的XML是有Root Name的,而xmlToArray返回出来的数组,没有Root Name。如果我有心情,我一定要改掉它——至少现在没有——其实很好改:

return $this->m_arrayXMLData;

改为:

return array($this->m_rootName => $this->m_arrayXMLData);

———-17.6.13 更新———-

在Xmlcreater中存在一个BUG,就是一旦XML中存在标签相同的该怎么办?
一开始我没有考虑这种情况。所以,我把代码稍微改了一下。

    /**
    *   @param: Source Data Array, XML Root Name, The XML Object.
    *   @return String: XML String. 
    *   @summary: 
    *       The #3 And #4 Parament Only For Recursive Functions, We Can Use $dataArray And $rootName When We Call This Function.
    *
    */
   function arrayToXML($dataArray, $rootName, &$xmlObj = null, $tagKey = null){
    if($xmlObj === null){
        $rootName = key($dataArray);
        $xmlObj  = simplexml_load_string(XML_HEADER."<$rootName></$rootName>");
        $dataArray = $dataArray[$rootName];
    }

    /*Add Attributes.*/
    if(array_key_exists('@attributes',$dataArray)) {
        foreach ($dataArray['@attributes'] as $keyAttr => $valueAttr) {
            $xmlObj->addAttribute($keyAttr,$valueAttr);
        }
    }

    /*Add Element.*/
    global $tempKey;
    foreach($dataArray as $keyEle=>$valueEle){
        if($keyEle !== '@attributes'){
            if(!is_numeric($keyEle)){
                if(is_array($valueEle)){
                    $ptrArray = $valueEle;
                    if(!is_numeric(key($valueEle))){
                        $ptrElement = $xmlObj->addChild($keyEle);
                    }
                    else{
                        $tempKey = $keyEle;
                        $ptrElement = $xmlObj;
                    }
                    $this->arrayToXML($ptrArray,null,$ptrElement);
                }
                else{
                    $ptrElement = $xmlObj->addChild($keyEle,$valueEle);
                }
            }
            else{
                $ptrElement = $xmlObj->addChild($tempKey);
                $this->arrayToXML($valueEle,null,$ptrElement,$tempKey);
            }
        }
    }

    unset($tempKey);
    /*Return XML String.*/
    return $xmlObj->saveXML();

}   /*End Of Function arrayToXML.*/

在递归中,我多加了一个参数,把父节点的名字传过去。这不影响外部的调用。
我对这个函数进行了单独的测试:

/*样本数组.*/
$dataArray = array('template'=>
    array(
        '@attributes' => array('id'=>1,'name'=>'Lunacia'),
        'device_groups' => array(
            'device' => array(
                array(
                    '@attributes' => array('id'=>2, 'guid'=>'sda8yd8agd8a', 'name'=>'Luna')),
                array(
                    '@attributes' =>array('id'=>3, 'guid'=>'eda8yd6af8a', 'name'=>'Lun')),
                array(
                    '@attributes' =>array('id'=>4, 'guid'=>'541413f8a', 'name'=>'Lu'))
            ),
            'device_own'=>array(
                array(
                    '@attributes'=>array('class'=>23, 'gread' => 2 , 'color' =>'green')),
                array(
                    '@attributes' =>array('class'=>43,'gread'=>6 , 'color'=> 'black'))
            ),
            'device_distinct'=>array(
                '@attributes' => array('a'=>12,'b'=>'asd'),
                'value'=>'100'
            )
        )
    )
);

测试结果:


<?xml version="1.0" encoding="utf-8"?>
<template id="1" name="Lunacia">
    <device_groups>
        <device id="2" guid="sda8yd8agd8a" name="Luna"/>
        <device id="3" guid="eda8yd6af8a" name="Lun"/>
        <device id="4" guid="541413f8a" name="Lu"/>
        <device_own class="23" gread="2" color="green"/>
        <device_own class="43" gread="6" color="black"/>
        <device_distinct a="12" b="asd">
            <value>100</value>
        </device_distinct>
    </device_groups>
</template>

但这只解决了其中一个问题,另一个问题在XML转数组中。
设想有下面两种XML:

<root>
    <a id=1></a>
    <a id=2></a>
</root>
<root>
    <a id=3></a>
</root>

那么,我用xmlToArray函数将这两个XML转为数组,会出现什么效果?

array(
    'root'=>array(
        'a'=>array(
            array('@attributes'=>array('id'=>1)),
            array('@attributes'=>array('id'=>2))
        )
    )
)
array(
    'root'=>array(
        'a'=>array('@attributes'=>array('id'=>1))
    )
)

第二个数组并不是我所期待的结果。也就是说,当XML某个标签允许重复出现并且在某种情况下出现1次的时候,结果并不是“准确”的,这种结果完全不满足我以后对这个XML操作的需求。所以,需要对xmlToArray进行重做:

    /**
    *   @param: Simple XML Object / XML String.
    *   @return: 
    *       a.An Array Of XML Data.
    *       b.If XML String Is Not A Standard XML, Return Null Or Generate An Exception.
    *   @summary: 
    *       XML Object To Array Value.
    *
    */
    private function _xmlToArray($xml){
        $xml = $this->checkValueType($xml);
        $this->m_arrayXMLData = json_decode(json_encode($xml),true);
        return array($this->m_rootName => $this->m_arrayXMLData);
    }   /*End Of Function _xmlToArray.*/


    public function xmlToArray($xml, $multKeys = array(), &$resArray = null){
        if($multKeys === array()){
            return $this->_xmlToArray($xml);
        }

        $xmlObj = $this->checkValueType($xml);

        if($resArray === null){
            $resArray = array();
            $_ptrArr = &$resArray;
        }

        /*Add Attirbutes.*/
        foreach($xmlObj->attributes() as $key => $value){
            $resArray['@attributes'][$key] = (string)$value;
        }

        /*Add Children.*/
        foreach($xmlObj->children() as $child){
            $lable = $child->getName();

            /*Move Pointer.*/
            if(array_search($lable,$multKeys) !== false){
                if(!array_key_exists($lable,$resArray) || !is_array($resArray[$lable]) )
                    $resArray[$lable] = array();
                array_push($resArray[$lable],array());

                end($resArray[$lable]);
                $_ptrArr = &$resArray[$lable][key($resArray[$lable])];
            }
            else{
                $resArray[$lable] = array();
                $_ptrArr = &$resArray[$lable];
            }

            /*Add Values.*/
            if($child->count() === 0){
                if((string)$child !== ''){
                    $_ptrArr = (string)$child;
                }
            }
            else{

            }
            $this->xmlToArray($child,$multKeys,$_ptrArr);

        }
        return array($this->m_rootName => $resArray);

    }/*End Of Function xmlToArray*/

第二个参数是一个数组,里面放置的是XML中可能多次出现的标签名,比如array('a','device')。默认为空数组,此时会调用以前的xmlToArray函数。第三个参数在调用中不使用,仅仅用于递归。


我还增加了一个函数,并在构造函数中做了一点修改。

    public function openXmlFile($filePath){
        libxml_disable_entity_loader(false);  
        try{
            $objXML = @simplexml_load_file($filePath,'SimpleXMLElement', LIBXML_NOCDATA);
            if($objXML === false){
                throw new Exception("Open XML Error.");
            }
            else{
                return $objXML;
            }
        }
        catch(Exception $e){
            return LOAD_XMLFILE_ERROR;
        }
    }

上面这个函数要放在构造函数的前面,因为构造函数中要使用它。

    /*There must be 2 parameter for the time being.*/
    public function __construct(){
        $argNum = func_num_args();
        $tempXmlString = '<a> </a>';
        switch($argNum){
            case 0:
                $this->m_xmlFilePath = '';
                break;
            case 1:
                $this->m_xmlFilePath = func_get_arg(0);
                break;
            case 2:
                $this->m_xmlFilePath = func_get_arg(0);
                $tempXmlString = func_get_arg(1);
                break;
        }

        /*Load XML File.*/
        try{
            if (!file_exists($this->m_xmlFilePath)) {  
                $this->m_xmlFileObj = fopen($this->m_xmlFilePath,'w+');
                fwrite($this->m_xmlFileObj, $tempXmlString);
                fclose($this->m_xmlFileObj);
                $this->m_xmlFileObj = null;
            }
            $this->m_objXML = $this->openXmlFile($this->m_xmlFilePath);
        }
        catch(Exception $e){
            $this->m_errorMessage = LOAD_XMLFILE_ERROR;
        }
    }

嗯,就这样。

猜你喜欢

转载自blog.csdn.net/rkexy/article/details/72547782
今日推荐