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
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!
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:
If we run this, we can see them:
The development server is answering with content-type
text/html
while the nice display is with content-typetext/xml
.Therefore, if
should have set that, it was not successful. IIRC a space after the colon ":" is required in HTTP header lines:
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: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 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 settingtext/html
.The
header()
function does not have a return value you could check the output with, but instead emits a warning:And the pairing
headers_sent()
function returns a boolean value whether or not headers have been already sent: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: