PHP処理XML
72762 ワード
仕事中にPHPでXMLを処理しているものに遭遇.PHPを習ったばかりで、コードには最適化に値するところがたくさんあります.主にsimpleXML処理を用いている.汎用化されたXML処理コードを書きました.
Xmlcreater.php
ここには他のデータ(RangUtil.php)が使われているので、テストクラスが貼られています.
Slavexml.php
そのうち、RangUtil.phpはこんな感じです.
RangUtil.php
書き終わった後、arrayToXMLとxmlToArrayの2つの関数について気まずいところを見つけました.ArrayToXMLが生成したXMLにはRoot Nameがあり、xmlToArrayが返す配列にはRoot Nameはありません.もし私が気持ちがあれば、私は必ずそれを変えなければなりません.少なくとも今はありません.実はよく直します.
変更後:
———17.6.13更新———
XmlcreaterにBUGが存在するのは、XMLにラベルが同じであればどうすればいいのでしょうか.最初はこのような状況を考えていませんでした.だから、コードを少し変更しました.
再帰では、親ノードの名前を渡すパラメータを追加しました.これは外部の呼び出しに影響しません.この関数を個別にテストしました.
テスト結果:
しかし、これは1つの問題だけを解決し、もう1つの問題はXML回転配列にあります.次の2つのXMLを想定します.
では、xmlToArray関数でこの2つのXMLを配列に変換すると、どのような効果が得られますか?
2番目の配列は私が期待していた結果ではありません.つまり、XMLのラベルが重複して出現することを許可し、ある場合に1回出現する場合、結果は「正確」ではなく、この結果は私の今後のXML操作に対する需要を完全に満たしていない.したがって、xmlToArrayをやり直す必要があります.
2番目のパラメータは配列で、中にはXMLで複数回現れる可能性のあるラベル名、例えば
また、関数を追加し、コンストラクション関数で少し修正しました.
上の関数は、コンストラクション関数で使用されるため、コンストラクション関数の前に配置します.
うん、そう.
Xmlcreater.php
/**
* @author: Even
* @version: 1.0.1
* @date: 2017.5.17
*/
define('LOAD_XMLFILE_ERROR',-1);
define('FILE_IO_ERROR', -2);
define('XML_HEADER','');
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,' ');
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
/**
* @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
/**
* 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)
);
}
書き終わった後、arrayToXMLとxmlToArrayの2つの関数について気まずいところを見つけました.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>");
$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'
)
)
)
);
テスト結果:
<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>100value>
device_distinct>
device_groups>
template>
しかし、これは1つの問題だけを解決し、もう1つの問題はXML回転配列にあります.次の2つのXMLを想定します.
<root>
<a id=1>a>
<a id=2>a>
root>
<root>
<a id=3>a>
root>
では、xmlToArray関数でこの2つの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))
)
)
2番目の配列は私が期待していた結果ではありません.つまり、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*/
2番目のパラメータは配列で、中にはXMLで複数回現れる可能性のあるラベル名、例えば
array('a','device')
が配置されています.デフォルトは空の配列で、以前のxmlToArray関数が呼び出されます.3番目のパラメータは呼び出しでは使用されず、再帰にのみ使用されます.また、関数を追加し、コンストラクション関数で少し修正しました.
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 = ' ';
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;
}
}
うん、そう.