skip to Main Content

I am upgrading a page from CI3 to CI4. The page has a button to open a new tab which displays XML data. For some reason the CI4 version is showing the XML as plain text. I copied the source of each of the XML pages and compared them, and the only difference I see are the URLs (one page points to production and one to development). The CI4 page does seem to be formatted as XML in the source code. Why isn’t the CI4 page showing as formatted XML?

Link to CI3 XML: https://www.uvm.edu/femc/data/archive/project/bear-brook-watershed-maine/dataset/vegetation-size-categories-corresponding-plot-size/eml_xmlview

Link to 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

Here is the code used to create the CI4 page:

in the controller:

    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','[email protected]');
                        $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);
    }

in the view:

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

echo $sxe
?>

2

Answers


  1. Chosen as BEST ANSWER

    Use CI4 to set headers: https://codeigniter4.github.io/userguide/outgoing/response.html#setting-headers

    I added the following to the controller and that fixed it!

    $this->response->setHeader('Content-Type', 'text/xml'); 
    

  2. The browser is not "seeing" that it is XML. Let me explain:

    The content (body) is the same, so diffing it does not show any differences.

    However if we diff the response headers, we can make the difference visible:

    # the two URLs to compare
    a="https://dev.vmc.w3.uvm.edu/xana/CI4/data/archive/project/bear-brook-watershed-maine/dataset/vegetation-size-categories-corresponding-plot-size/eml_xmlview"
    b="https://www.uvm.edu/femc/data/archive/project/bear-brook-watershed-maine/dataset/vegetation-size-categories-corresponding-plot-size/eml_xmlview"
    
    # first 6 lines of the side-by side diff
    diff -y <(curl -si "$a") <(curl -si "$b") | head -n 6
    

    If we run this, we can see them:

    HTTP/2 200                                  HTTP/2 200 
    server: nginx/1.27.3                        server: nginx/1.27.3
    date: Thu, 16 Jan 2025 14:20:28 GMT         date: Thu, 16 Jan 2025 14:20:28 GMT
    content-type: text/html; charset=UTF-8    | content-type: text/xml;charset=UTF-8
    x-powered-by: PHP/8.1.29                  | vary: Accept-Encoding
    expires: Thu, 19 Nov 1981 08:52:00 GMT      expires: Thu, 19 Nov 1981 08:52:00 GMT
    

    The development server is answering with content-type text/html while the nice display is with content-type text/xml.

    Therefore, if

    header ("Content-Type:text/xml");
    

    should have set that, it was not successful. IIRC a space after the colon ":" is required in HTTP header lines:

    header ("Content-Type: text/xml");
                          #
    

    You can also add the charset=UTF-8 attribute/value to it. The space is not strictly necessary there, but it makes it more readable for humans:

    header ("Content-Type: text/xml; charset=UTF-8");
                          #          #############
    

    But this is likely not the issue as I now have looked up, the space after the colon is optional. It’s recommended thought for readability.

    Therefore, we now conclude the whole function call is not in effect, lets see it in the condition:

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

    If this code is getting executed then $dump must not be empty to have the call in effect and headers must not be already sent to get the Content-Type header set.

    Henceforth we assume that either $dump is empty or headers have already been send and the response is served with the default setting text/html.

    The header() function does not have a return value you could check the output with, but instead emits a warning:

    on failure to schedule the header to be sent, header() issues an E_WARNING level error.

    And the pairing headers_sent() function returns a boolean value whether or not headers have been already sent:

    if(!$dump){
    
        headers_sent($filename, $line)
        and throw new Error(sprintf('headers_sent(%s, %s)', $filename, $line));
    
        set_error_handler(fn ($type, $message, $file, $line)
        => throw new ErrorException($message, $type, $type, $file, $line));
    
        header ("Content-Type:text/xml");
    }
    

    Unless $dump is not empty this should throw an error if a) headers have already been sent or b) an error (incl. warnings) was triggered after setting the handler.

    As the code is now throwing check with CI error and exception handling I’m not fluent with it.

    Otherwise follow PHPs error log for any messages and traces and/or watch for HTTP 500 status codes and/or with display errors enabled the verbatim response body.


    References:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search