You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CI3升级至CI4后SimpleXMLElement生成XML显示为纯文本问题

CI4中XML无法格式化展示的问题排查与解决

问题描述

将页面从CodeIgniter 3升级到CodeIgniter 4后,页面中用于在新标签页展示XML数据的按钮出现异常:CI4版本的XML内容以纯文本形式显示,但查看页面源码确认内容确实是XML格式,仅URL与CI3版本不同。

相关页面地址:

  • CI3 XML页面:https://www.uvm.edu/femc/data/archive/project/bear-brook-watershed-maine/dataset/vegetation-size-categories-corresponding-plot-size/eml_xmlview
  • CI4 XML页面:https://dev.vmc.w3.uvm.edu/xana/CI4/data/archive/project/bear-brook-watershed-maine/dataset/vegetation-size-categories-corresponding-plot-size/eml_xmlview

当前CI4实现代码

控制器代码

public function eml_xmlview($projecttitle = NULL, $datasettitle = NULL) {

    //Get project and dataset ids from web title, if no match show 404
    $pid=$this->common->get_itemid('project', $projecttitle);
    $did=$this->common->get_itemid('dataset', $datasettitle);
      if($pid==-1){
        show_404();
    }
    
    $published=($this->common->check_publish('tblProject', 'fldPublish','pkProjectID',$pid) AND $this->common->check_publish('tblDataset', 'fldPublishMetadata','pkDatasetID',$did));
    //Check if the person coming to the management section is allowed to manage this particular project
    $canmanage=$this->dataset_model->can_manage_dataset($pid,$did,'permission_project_datasets'); //Check whether person logged in can view unpublished data
    //Find out if this project should be hidden - basically, check if the project is published, or the person is admin, or the person can manage it
    $show=($published OR $canmanage);
    if(!$show){
       show_404();
    }
    

    $dataset = $this->dataset_model->get_dataset($pid, $did);
    $project = (ENVIRONMENT=="production") ? $this->project_model->get_projectverbose($pid) : $this->project_model->get_projectverbose($pid, FALSE);  
    $fields = $this->dataset_model->get_fields($did);
    $version = 1; //Placeholder in case we want to add version to package ids later
    if (!empty($project)){
        $project=array_pop($project);
    }
          
   
    $emlstart = <<<XML
<eml:eml system="vmc"
xmlns:eml="eml://ecoinformatics.org/eml-2.1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="eml://ecoinformatics.org/eml-2.1.1 eml.xsd"></eml:eml>
XML;
    
    $sxe = new SimpleXMLElement($emlstart);
    $sxe->addAttribute('packageId' , 'vmc.' . $pid . '.' . $did.'.'.$version);

        $emldata = $sxe->addChild('dataset');
            $pbl = $emldata->addChild('publisher');
                $mtp = $pbl->addChild('metadataProvider');
                    $mtp->addChild('organizationName','Forest Ecosystem Monitoring Cooperative');
                    $mtp->addChild('phone','(802) 656-0683');
                    $mtp->addChild('electronicMailAddress','femc@uvm.edu');
                    $mtp->addChild('onlineUrl','www.uvm.edu/femc');
                        $adr = $mtp->addChild('address');
                        $adr->addChild('deliveryPoint','705 Spear Street');
                        $adr->addChild('city','South Burlington');
                        $adr->addChild('administrativeArea','Vermont');
                        $adr->addChild('postalCode','05403');
                        $adr->addChild('country','United States of America');
            //Add project organizations        
            $orgsQry = $this->db->query("SELECT * FROM (SELECT * FROM tblOrganization 
                                        JOIN tblProjectOrganization on tblOrganization.pkOrganizationID = tblProjectOrganization.fkOrganizationID
                                        WHERE fkProjectID = $pid) t 
                                        GROUP BY t.fkOrganizationID, t.enuOrganizationRole
                                        ORDER BY enuOrganizationRole, fldStartDate;");        
            $orgrolesAr = array();
            foreach($orgsQry->getResult() as $row) {  
                $orgrolesAr["$row->enuOrganizationRole"][] = "$row->fldName1 $row->fldName2";
            }

            foreach($orgrolesAr as $role=>$orgAr) {
                $ap=$emldata->addChild('associatedParty');

                foreach($orgAr as $index=>$orgName) {
                    $ap->addChild('organizationName',$this->common->sanitizeForXML($orgName));
                }
                $ap->addChild('role',$role);
            }
        
                        
            //Project-level 
            if($project['enuProjectType'] == 'research'){
                $prj = $emldata->addChild('researchProject');
            }
            else{
                $prj = $emldata->addChild('project');
            }
            $prj->addChild('title', $this->common->sanitizeForXML($project['fldProjectName']));
            $prj->addChild('abstract')->addChild('para', $this->common->sanitizeForXML($project['fldProjectDescription']));  
            $prj->addChild('shortName',$this->common->sanitizeForXML($project['fldProjectName']));
                $prs = $prj->addChild('personnel');
                if(!empty($project['people'])){
                    foreach($project['people'] as $person){ 
                        if($person['enuRole']=='principal-investigator'){
                            $prn = $prs->addChild('principalInvestigator');
                        }
                        else{
                            $prn = $prs->addChild('contentProvider');
                        }
                            $prn->addChild('surName',$this->common->sanitizeForXML($person['fldLastName']));
                            $prn->addChild('givenName',$this->common->sanitizeForXML($person['fldFirstName']));      
                    }
                }
                if(!empty($project['tags'])){
                    $kws = $prj->addChild('keywordSet');
                    foreach($project['tags'] as $tag){
                        $kws->addChild('keyword',$this->common->sanitizeForXML($tag['fldTag']));
                    }
                }  
         
            //Dataset-level
            $dataTable = $emldata->addChild('dataTable');
            $dataTable->addChild('title', $this->common->sanitizeForXML($dataset['fldName']));
                $cvr = $dataTable->addChild('coverage');
                $cvr->addAttribute('scope','document');
                    $tmp = $cvr->addChild('temporalCoverage');
                    $tmp->addAttribute('scope','document');
                        $rng = $tmp->addChild('rangeOfDates');
                            if($dataset['fldStartDate'] && $dataset['fldStartDate'] != '0000-00-00'){
                                $rng->addChild('beginDate')->addChild('calendarDate',$dataset['fldStartDate']);
                            }                               
                            if($dataset['fldEndDate'] && $dataset['fldEndDate'] != '0000-00-00'){
                                $rng->addChild('endDate')->addChild('calendarDate',$dataset['fldEndDate']);
                            }  
            $dataTable->addChild('description',$this->common->sanitizeForXML($dataset['fldDescription']));
            $dataTable->addChild('purpose',$this->common->sanitizeForXML($dataset['fldPurpose']));
            $dataTable->addChild('shortName',$this->common->sanitizeForXML($dataset['fldDatabaseTableName']));
                $phy = $dataTable->addChild('physical');
                    $phy->addChild('objectName','VMC.' . $pid . '.' . $did);
                        $dfo = $phy->addChild('dataFormat');
                        $dfo->addChild('formatName','mySQL');
                    $phy->addChild('citation',$this->common->sanitizeForXML($dataset['fldDatasetCitation']));
                    $phy->addChild('distibution')->addChild('online')->addChild('url', site_url('data/archive/project/'.$this->common->sanitizeForXML($project['fldWebTitle']).'/dataset/'.$this->common->sanitizeForXML($dataset['fldWebTitle'])));
                $atl = $dataTable->addChild('attributeList');
                    foreach($fields as $field){
                        $att = $atl->addChild('attribute');
                            $att->addChild('attributeName',$this->common->sanitizeForXML($field['fldFieldName']));
                            if($field['fldCaption']){
                                $att->addChild('attributeLabel',$this->common->sanitizeForXML($field['fldCaption']));
                            }
                            if($field['fldFieldDescription']){
                                $att->addChild('attributeDefinition',$this->common->sanitizeForXML($field['fldFieldDescription']));
                            }
                            if($field['enuDataType']){
                                $att->addChild('storageType',$this->common->sanitizeForXML($field['enuDataType']));
                            }
                            if($field['enuMeasurementType']){
                                $mst = $att->addChild('measurementType');
                                    $typ = $mst->addChild(strtolower($this->common->sanitizeForXML($field['enuMeasurementType'])));
                                        if($field['enuUnit']){
                                            $typ->addChild('unit')->addChild('standardUnit',$this->common->sanitizeForXML($field['enuUnit']));
                                        }
                                        if($field['fldPrecision']){
                                            $typ->addChild('precision',$this->common->sanitizeForXML($field['fldPrecision']));
                                        }
                                        if($field['enuFormatString']){
                                            $typ->addChild('formatString',$this->common->sanitizeForXML($field['enuFormatString']));
                                        }
                                        if($field['enuNumberType']){
                                            $typ->addChild('numericDomain')->addChild('numberType',$this->common->sanitizeForXML($field['enuNumberType']));
                                        }
                                            if($field['fldMinimum'] || $field['fldMaximum']){
                                                $bdg = $typ->addChild('BoundsGroup');
                                                    $bds = $bdg->addChild('bounds');
                                                    $bds->addChild('minimum',$field['fldMinimum']);
                                                    $bds->addChild('maximum',$field['fldMaximum']);
                                            }
                            }
                            if($field['fldEnumList'] && $field['fkLookupID']){
                                $list = $this->dataset_model->get_list($field['fkLookupID']);
                                if(!empty($list)){
                                    $grouplist=array();
                                    list($grouplist['list'],$grouplist['listtype'],$grouplist['listname'])=$list;
                                }
                                switch ($grouplist['listtype']){
                                    case "custom":
                                        $enu = $att->addChild('enumeratedDomain');
                                        foreach($grouplist['list'] as $pairing){
                                            $cdd = $enu->addChild('codeDefinition');
                                                $cdd->addChild('code', $this->common->sanitizeForXML($pairing['fldItem']));
                                                $cdd->addChild('definition', $this->common->sanitizeForXML($pairing['fldDescription']));
                                        }
                                        break;
                                    case "species":
                                        $enu = $att->addChild('enumeratedDomain');
                                        $speciesCoverage = $dataTable->addChild('coverage');
                                        $tmp = $speciesCoverage->addChild('taxonomicCoverage');
                                        foreach($grouplist['list'] as $pairing){
                                            $cdd = $enu->addChild('codeDefinition');
                                                $cdd->addChild('code', $this->common->sanitizeForXML($pairing['fldOriginalCode']));
                                                $cdd->addChild('definition', $this->common->sanitizeForXML($pairing['enuTaxonomicSystem'].' #'.$pairing['fldTaxonomicID'].', '.$pairing['fldCommonName'].' ('.$pairing['fldGenus'].' '.$pairing['fldSpecies'].')'));
                                        }
                                        

                                        break;
                                    case "location":
                                        $enu = $att->addChild('enumeratedDomain');
                                        foreach($grouplist['list'] as $pairing){
                                            $cdd = $enu->addChild('codeDefinition');
                                                $cdd->addChild('code', $this->common->sanitizeForXML($pairing['fldOriginalCode']));
                                                if (!empty($pairing['descLocationType'])){
                                                    $cdd->addChild('definition', $this->common->sanitizeForXML($pairing['descLocationType'].' '.$pairing['fldDescription']));
                                                }else{
                                                    $cdd->addChild('definition', $this->common->sanitizeForXML($pairing['fldDescription']));
                                                }
                                        }
                                        break;
                                    default:
                                        break;
                                        
                                }
                            }
                    }
            //$dataTable->addChild('license',$dataset['enuDataLicense']);
            
    $sxe->saveXML();       
    $data['sxe'] = $sxe->asXML();
    $data['dump']=false;
    echo view('project/dataset/eml_xmlview', $data);
}

视图代码

<?php 
if(!$dump){
    header ("Content-Type:text/xml");    
}

echo $sxe
?>

问题根源

核心问题在于CI4和CI3的响应头处理机制不同:

  • CI3中直接在视图里使用header()设置响应头通常能生效,因为输出控制较为宽松。
  • CI4的响应系统由Response类统一管理,视图加载前可能已经有部分输出(比如框架的初始化内容),此时再用原生header()设置Content-Type会失效,浏览器无法识别内容为XML,只能以纯文本展示。

解决方案

方案1:使用CI4官方Response类输出XML

修改控制器的最后部分,去掉视图加载逻辑,直接用Response类设置响应头并输出XML内容:

// 替换原来的以下代码:
// $data['sxe'] = $sxe->asXML();
// $data['dump']=false;
// echo view('project/dataset/eml_xmlview', $data);

// 改为:
$response = service('response');
// 设置正确的XML响应头
$response->setHeader('Content-Type', 'text/xml; charset=utf-8');
// 输出XML内容
$response->setBody($sxe->asXML());
// 返回响应对象
return $response;

同时可以删除对应的视图文件,或者清空视图内容(因为不再需要视图处理输出)。

方案2:检查并消除额外输出干扰

确保所有PHP文件(控制器、模型、公共文件等)的开头<?php标签前没有空格、换行或其他字符,这些额外输出会导致CI4无法正确设置响应头。

方案3:验证响应头

使用浏览器开发者工具(Network标签)查看CI4页面的响应头,确认Content-Type是否为text/xml。如果仍然是text/html,说明还有其他代码覆盖了响应头,需排查全局中间件、过滤器等是否修改了响应设置。

内容的提问来源于stack exchange,提问作者xanabobana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 01:27:32