Newer
Older
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidDataHandling/SaveCanSAS1D.h"
Gigg, Martyn Anthony
committed
#include "MantidAPI/FileProperty.h"
Steve Williams
committed
#include "MantidKernel/Exception.h"
#include "MantidGeometry/IComponent.h"
Russell Taylor
committed
#include "MantidAPI/WorkspaceValidators.h"
Sofia Antony
committed
#include "MantidKernel/MantidVersion.h"
Gigg, Martyn Anthony
committed
#include "MantidAPI/Run.h"
#include <boost/shared_ptr.hpp>
Sofia Antony
committed
//-----------------------------------------------------------------------------
Steve Williams
committed
using namespace Mantid::Kernel;
using namespace Mantid::Geometry;
Steve Williams
committed
using namespace Mantid::API;
namespace Mantid
{
Steve Williams
committed
namespace DataHandling
{
Steve Williams
committed
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(SaveCanSAS1D)
Janik Zikovsky
committed
/// Sets documentation strings for this algorithm
void SaveCanSAS1D::initDocs()
{
this->setWikiSummary("Save a file in the canSAS 1-D format ");
this->setOptionalMessage("Save a file in the canSAS 1-D format");
}
Janik Zikovsky
committed
/// constructor
SaveCanSAS1D::SaveCanSAS1D()
Steve Williams
committed
{}
Steve Williams
committed
/// destructor
SaveCanSAS1D::~SaveCanSAS1D()
{}
Janik Zikovsky
committed
/// Overwrites Algorithm method.
void SaveCanSAS1D::init()
{
declareProperty(new API::WorkspaceProperty<>("InputWorkspace", "", Kernel::Direction::Input,
new API::WorkspaceUnitValidator<>("MomentumTransfer")),
"The input workspace, which must be in units of Q");
declareProperty(new API::FileProperty("Filename", "", API::FileProperty::Save, ".xml"),
"The name of the xml file to save");
Steve Williams
committed
std::vector<std::string> radiation_source;
radiation_source.push_back("Spallation Neutron Source");
radiation_source.push_back("Pulsed Reactor Neutron Source");
radiation_source.push_back("Reactor Neutron Source");
radiation_source.push_back("Synchrotron X-ray Source");
radiation_source.push_back("Pulsed Muon Source");
radiation_source.push_back("Rotating Anode X-ray");
radiation_source.push_back("Fixed Tube X-ray");
radiation_source.push_back("neutron");
radiation_source.push_back("x-ray");
radiation_source.push_back("muon");
radiation_source.push_back("electron");
declareProperty("RadiationSource", "Spallation Neutron Source", new Kernel::ListValidator(
Janik Zikovsky
committed
radiation_source));
Steve Williams
committed
declareProperty("Append", false, "If true the output file is not overwritten but appended to");
}
/** Is called when the input workspace was actually a group, it sets the
Janik Zikovsky
committed
* for all group members after the first so that the whole group is saved
Janik Zikovsky
committed
* @param alg :: pointer to the algorithm
* @param propertyName :: name of the property
* @param propertyValue :: value of the property
* @param perioidNum :: period number
Janik Zikovsky
committed
*/
Steve Williams
committed
void SaveCanSAS1D::setOtherProperties(API::IAlgorithm* alg, const std::string & propertyName,const std::string& propertyValue, int perioidNum)
{
// call the base class method
Algorithm::setOtherProperties(alg,propertyName,propertyValue,perioidNum);
Steve Williams
committed
if( ( propertyName == "Append") && ( perioidNum > 1 ) )
Janik Zikovsky
committed
{
Steve Williams
committed
alg->setPropertyValue(propertyName, "1");
Janik Zikovsky
committed
}
Steve Williams
committed
}
/// Overwrites Algorithm method
void SaveCanSAS1D::exec()
{
m_workspace = getProperty("InputWorkspace");
if( ! m_workspace )
{
throw std::invalid_argument("Invalid inputworkspace ,Error in SaveCanSAS1D");
}
Steve Williams
committed
if (m_workspace->getNumberHistograms() > 1)
Janik Zikovsky
committed
{
throw std::invalid_argument("Error in SaveCanSAS1D - more than one histogram.");
}
Steve Williams
committed
// write xml manually as the user requires a specific format were the placement of new line characters is controled
//and this can't be done in using the stylesheet part in Poco or libXML
prepareFileToWriteEntry();
Steve Williams
committed
m_outFile << "\n\t<SASentry name=\"" << m_workspace->getName() << "\">";
Steve Williams
committed
std::string sasTitle;
createSASTitleElement(sasTitle);
m_outFile<<sasTitle;
Steve Williams
committed
std::string sasRun;
createSASRunElement(sasRun);
m_outFile<<sasRun;
Steve Williams
committed
std::string dataUnit = m_workspace->YUnitLabel();
//look for xml special characters and replace with entity refrence
searchandreplaceSpecialChars(dataUnit);
Steve Williams
committed
std::string sasData;
createSASDataElement(sasData);
m_outFile<<sasData;
Steve Williams
committed
std::string sasSample;
createSASSampleElement(sasSample);
m_outFile<<sasSample;
Steve Williams
committed
std::string sasInstr="\n\t\t<SASinstrument>";
m_outFile<<sasInstr;
std::string sasInstrName="\n\t\t\t<name>";
std::string instrname=m_workspace->getInstrument()->getName();
//look for xml special characters and replace with entity refrence
searchandreplaceSpecialChars(instrname);
sasInstrName+=instrname;
sasInstrName+="</name>";
m_outFile<<sasInstrName;
Steve Williams
committed
std::string sasSource;
createSASSourceElement(sasSource);
m_outFile<<sasSource;
Steve Williams
committed
std::string sasCollimation="\n\t\t\t<SAScollimation/>";
m_outFile<<sasCollimation;
Steve Williams
committed
try
{
std::string sasDet;
createSASDetectorElement(sasDet);
m_outFile<<sasDet;
}
catch(Kernel::Exception::NotFoundError&)
{
m_outFile.close();
throw;
}
catch(std::runtime_error& )
{
m_outFile.close();
throw ;
}
Steve Williams
committed
sasInstr="\n\t\t</SASinstrument>";
m_outFile<<sasInstr;
Steve Williams
committed
std::string sasProcess;
createSASProcessElement(sasProcess);
m_outFile<<sasProcess;
Steve Williams
committed
std::string sasNote="\n\t\t<SASnote>";
sasNote+="\n\t\t</SASnote>";
m_outFile<<sasNote;
Steve Williams
committed
m_outFile << "\n\t</SASentry>";
m_outFile << "\n</SASroot>";
m_outFile.close();
}
/** Opens the output file and either moves the file pointer to beyond the last
Janik Zikovsky
committed
* entry or blanks the file and writes a header
* @throw logic_error if append was selected but end of an entry tag couldn't be found
* @throw FileError if there was a problem writing to the file
*/
Steve Williams
committed
void SaveCanSAS1D::prepareFileToWriteEntry()
{
//reduce error handling code by making file access errors throw
m_outFile.exceptions(std::ios::eofbit|std::ios::failbit|std::ios::badbit);
Steve Williams
committed
const std::string fileName = getPropertyValue("FileName");
bool append(getProperty("Append"));
Steve Williams
committed
// write xml manually as the user requires a specific format were the placement of new line characters is controled
//and this can't be done in using the stylesheet part in Poco or libXML
Steve Williams
committed
if (append)
Steve Williams
committed
{
Steve Williams
committed
append = openForAppending(fileName);
}
Steve Williams
committed
if (append)
{
findEndofLastEntry();
}
else
{
writeHeader(fileName);
}
}
/** opens the named file if possible or returns false
Janik Zikovsky
committed
* @param filename
Janik Zikovsky
committed
:: * @return true if the file was opened successfully and isn't empty
Janik Zikovsky
committed
*/
Steve Williams
committed
bool SaveCanSAS1D::openForAppending(const std::string & filename)
{
try
{
m_outFile.open(filename.c_str(), std::ios::out | std::ios::in);
//check if the file already has data
m_outFile.seekg(0, std::ios::end);
if ( m_outFile.tellg() > 0 )
Steve Williams
committed
{
Steve Williams
committed
//a file exists with data leave the file open and state that appending should be possible
return true;
Steve Williams
committed
}
}
Steve Williams
committed
catch (std::fstream::failure)
Steve Williams
committed
{
Steve Williams
committed
g_log.information() << "File " << filename << " couldn't be opened for a appending, will try to create the file\n";
Steve Williams
committed
}
Steve Williams
committed
m_outFile.clear();
if (m_outFile.is_open())
Steve Williams
committed
{
Steve Williams
committed
m_outFile.close();
Steve Williams
committed
}
Steve Williams
committed
return false;
Steve Williams
committed
}
/** Moves to the end of the last entry in the file, after <SASentry>
Janik Zikovsky
committed
* before </SASroot>
* @throw fstream::failure if the read or write commands couldn't complete
* @throw logic_error if the tag at the end of the last entry couldn't be found
*/
Steve Williams
committed
void SaveCanSAS1D::findEndofLastEntry()
{
static const int LAST_TAG_LEN = 11;
static const char LAST_TAG[LAST_TAG_LEN+1] = "</SASentry>";
Steve Williams
committed
// UNCERT should be less than the length of a SASentry
static const int UNCERT = 20;
Steve Williams
committed
const int rootTagLen = static_cast<int>(std::string("</SASroot>").length());
Steve Williams
committed
try
Steve Williams
committed
{
Steve Williams
committed
//move to the place _near_ the end of the file where the data will be appended to
m_outFile.seekg(-LAST_TAG_LEN-rootTagLen, std::ios::end);
char test_tag[LAST_TAG_LEN+1];
m_outFile.read(test_tag, LAST_TAG_LEN);
//check we're in the correct place in the file
if ( std::string(test_tag,LAST_TAG_LEN)!=std::string(LAST_TAG,LAST_TAG_LEN) )
Steve Williams
committed
{
Steve Williams
committed
//we'll allow some extra charaters so there is some variablity in where the tag might be found
bool tagFound(false);
for ( int i = 1; i < UNCERT; ++i )
Steve Williams
committed
{
Steve Williams
committed
//together this seek and read move the file pointer back on byte at a time and read
m_outFile.seekg( -i-LAST_TAG_LEN-rootTagLen, std::ios::end);
m_outFile.read(test_tag, LAST_TAG_LEN);
std::string read = std::string(test_tag, LAST_TAG_LEN);
if ( read == std::string(LAST_TAG,LAST_TAG_LEN) )
{
tagFound = true;
break;
}
}
if ( ! tagFound )
{
throw std::logic_error("Couldn't find the end of the existing data, missing </SASentry> tag");
Steve Williams
committed
}
}
Steve Williams
committed
// prepare to write to the place found by reading
m_outFile.seekp(m_outFile.tellg(), std::ios::beg);
}
catch (std::fstream::failure)
{
// give users more explaination about no being able to read their files
throw std::logic_error("Trouble reading existing data in the output file, are you appending to an invalid CanSAS1D file?");
Steve Williams
committed
}
}
/** Write xml header tags including the root element and starting the SASentry
Janik Zikovsky
committed
* element
Janik Zikovsky
committed
* @param fileName :: the name of the file to write to
Janik Zikovsky
committed
* @throw FileError if the file can't be opened or writen to
*/
Steve Williams
committed
void SaveCanSAS1D::writeHeader(const std::string & fileName)
{
Steve Williams
committed
try
{
m_outFile.open(fileName.c_str(), std::ios::out | std::ios::trunc);
//write the file header
m_outFile << "<?xml version=\"1.0\"?>\n"
Janik Zikovsky
committed
<< "<?xml-stylesheet type=\"text/xsl\" href=\"cansasxml-html.xsl\" ?>\n";
Steve Williams
committed
std::string sasroot="";
createSASRootElement(sasroot);
m_outFile<<sasroot;
}
catch (std::fstream::failure)
{
throw Exception::FileError("Error opening the output file for writing", fileName);
}
Steve Williams
committed
}
Janik Zikovsky
committed
/** This method search for xml special characters in the input string
* and replaces this with xml entity reference
Janik Zikovsky
committed
*@param input :: -input string
Janik Zikovsky
committed
*/
void SaveCanSAS1D::searchandreplaceSpecialChars(std::string &input)
{
std::string specialchars="&<>'\"";
std::string::size_type searchIndex=0;
std::string::size_type findIndex;
for(std::string::size_type i=0;i<specialchars.size();++i)
{
while(searchIndex<input.length())
Janik Zikovsky
committed
findIndex=input.find(specialchars[i],searchIndex);
if(findIndex!=std::string::npos)
Janik Zikovsky
committed
searchIndex=findIndex+1;
//replace with xml entity refrence
replacewithEntityReference(input,findIndex);
Janik Zikovsky
committed
}
else
Janik Zikovsky
committed
searchIndex=0;
Janik Zikovsky
committed
}
Janik Zikovsky
committed
}
Janik Zikovsky
committed
/** This method retrieves the character at index and if it's a xml
* special character replaces with XML entity reference.
Janik Zikovsky
committed
* @param input :: -input string
* @param index :: position of the special character in the input string
Janik Zikovsky
committed
*/
void SaveCanSAS1D::replacewithEntityReference(std::string& input, const std::string::size_type& index)
{
std::basic_string <char>::reference str=input.at(index);
switch(str)
{
case '&':
input.replace(index,1,"&");
break;
case '<':
input.replace(index,1,"<");
break;
case '>':
input.replace(index,1,">");
break;
case '\'':
input.replace(index,1,"'");
break;
case '\"':
input.replace(index,1,""");
break;
}
}
Janik Zikovsky
committed
/** This method creates an XML element named "SASroot"
Janik Zikovsky
committed
* @param rootElem :: xml root element string
Janik Zikovsky
committed
*/
void SaveCanSAS1D::createSASRootElement(std::string& rootElem)
{
rootElem="<SASroot version=\"1.0\"";
rootElem +="\n\t\t xmlns=\"cansas1d/1.0\"";
rootElem+="\n\t\t xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";
rootElem+="\n\t\t xsi:schemaLocation=\"cansas1d/1.0 http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd\">";
}
Steve Williams
committed
Janik Zikovsky
committed
/** This method creates an XML element named "Title"
Janik Zikovsky
committed
* @param sasTitle :: string for title element in the xml
Janik Zikovsky
committed
*/
void SaveCanSAS1D::createSASTitleElement(std::string& sasTitle)
{
std::string title=m_workspace->getTitle();
//look for xml special characters and replace with entity refrence
searchandreplaceSpecialChars(title);
sasTitle="\n\t\t<Title>";
sasTitle+=title;
sasTitle+="</Title>";
}
Steve Williams
committed
Janik Zikovsky
committed
/** This method creates an XML element named "Run"
Janik Zikovsky
committed
* @param sasRun :: string for run element in the xml
Janik Zikovsky
committed
*/
void SaveCanSAS1D::createSASRunElement(std::string& sasRun)
{
//initialise the run number to an empty string, this may or may not be changed later
std::string run;
if( m_workspace->run().hasProperty("run_number") )
{
Kernel::Property *logP = m_workspace->run().getLogData("run_number");
run = logP->value();
}
else
{
g_log.debug() << "Didn't find RunNumber log in workspace. Writing <Run></Run> to the CANSAS file\n";
}
Janik Zikovsky
committed
searchandreplaceSpecialChars(run);
Janik Zikovsky
committed
sasRun="\n\t\t<Run>";
sasRun+=run;
sasRun+="</Run>";
}
Janik Zikovsky
committed
/** This method creates an XML element named "SASdata"
Janik Zikovsky
committed
* @param sasData :: string for sasdata element in the xml
Janik Zikovsky
committed
*/
void SaveCanSAS1D::createSASDataElement(std::string& sasData)
{
std::string dataUnit = m_workspace->YUnitLabel();
//look for xml special characters and replace with entity refrence
searchandreplaceSpecialChars(dataUnit);
Janik Zikovsky
committed
sasData="\n\t\t<SASdata>";
//outFile<<sasData;
std::string sasIData;
std::string sasIBlockData;
std::string sasIHistData;
for (size_t i = 0; i < m_workspace->getNumberHistograms(); ++i)
Janik Zikovsky
committed
{
const MantidVec& xdata = m_workspace->readX(i);
const MantidVec& ydata = m_workspace->readY(i);
const MantidVec& edata = m_workspace->readE(i);
const MantidVec& dxdata = m_workspace->readDx(i);
Janik Zikovsky
committed
const bool isHistogram = m_workspace->isHistogramData();
Janik Zikovsky
committed
//x data is the QData in xml.If histogramdata take the mean
double intensity = isHistogram ? (xdata[j] + xdata[j + 1]) / 2 : xdata[j];
double dx = isHistogram ? (dxdata[j] + dxdata[j + 1]) / 2 : dxdata[j];
Janik Zikovsky
committed
std::stringstream x;
x << intensity;
std::stringstream dx_str;
dx_str << dx;
Janik Zikovsky
committed
sasIData="\n\t\t\t<Idata><Q unit=\"1/A\">";
sasIData+=x.str();
sasIData+="</Q>";
sasIData+="<Qdev unit=\"1/A\">";
sasIData+=dx_str.str();
sasIData+="</Qdev>";
Janik Zikovsky
committed
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
sasIData+="<I unit=";
sasIData+="\"";
sasIData+=dataUnit;
sasIData+="\">";
//// workspace Y data is the I data in the xml file
std::stringstream y;
y << (ydata[j]);
sasIData+=y.str();
sasIData+="</I>";
// workspace error data is the Idev data in the xml file
std::stringstream e;
e << edata[j];
sasIData+="<Idev unit=";
sasIData+="\"";
sasIData+=dataUnit;
sasIData+="\">";
sasIData+=e.str();
sasIData+="</Idev>";
sasIData+="</Idata>";
// outFile<<sasIData;
sasIBlockData+=sasIData;
}
sasIHistData+=sasIBlockData;
}
sasData+=sasIHistData;
Janik Zikovsky
committed
sasData+="\n\t\t</SASdata>";
}
Janik Zikovsky
committed
/** This method creates an XML element named "SASsample"
Janik Zikovsky
committed
* @param sasSample :: string for sassample element in the xml
Janik Zikovsky
committed
*/
void SaveCanSAS1D::createSASSampleElement(std::string &sasSample)
{
sasSample="\n\t\t<SASsample>";
//outFile<<sasSample;
std::string sasSampleId="\n\t\t\t<ID>";
std::string sampleid=m_workspace->getTitle();
//look for xml special characters and replace with entity refrence
searchandreplaceSpecialChars(sampleid);
sasSampleId+=sampleid;
sasSampleId+="</ID>";
sasSample+=sasSampleId;
//outFile<<sasSampleId;
sasSample+="\n\t\t</SASsample>";
}
Janik Zikovsky
committed
/** This method creates an XML element named "SASsource"
Janik Zikovsky
committed
* @param sasSource :: string for sassource element in the xml
Janik Zikovsky
committed
*/
void SaveCanSAS1D::createSASSourceElement(std::string& sasSource )
{
sasSource="\n\t\t\t<SASsource>";
//outFile<<sasSource;
std::string radiation_source = getPropertyValue("RadiationSource");
Janik Zikovsky
committed
std::string sasrad="\n\t\t\t\t<radiation>";
sasrad+=radiation_source;
sasrad+="</radiation>";
sasSource+=sasrad;
//outFile<<sasrad;
sasSource+="\n\t\t\t</SASsource>";
Janik Zikovsky
committed
}
/** This method creates an XML element named "SASdetector"
Janik Zikovsky
committed
* @param sasDet :: string for sasdetector element in the xml
Janik Zikovsky
committed
*/
void SaveCanSAS1D::createSASDetectorElement(std::string& sasDet)
{
sasDet="\n\t\t\t<SASdetector>";
//outFile<<sasDet;
Janik Zikovsky
committed
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
std::string detectorName ;
Geometry::IDetector_const_sptr detgroup;
try
{
// Get the detector object (probably a group) that goes with the result spectrum
detgroup = m_workspace->getDetector(0);
const int id = detgroup->getID(); //id of the first detector in the group
// Now make sure we've got an individual detector object
Geometry::IDetector_const_sptr det = m_workspace->getInstrument()->getDetector(id);
// Get all its ancestors
const std::vector<boost::shared_ptr<const IComponent> > ancs = det->getAncestors();
// The one we want is the penultimate one
// Shouldn't ever happen, but protect against detector having no ancestors
if (ancs.size() > 1)
detectorName = ancs[ancs.size()-2]->getName();
else
detectorName = det->getName();
//look for xml special characters and replace with entity refrence
searchandreplaceSpecialChars(detectorName);
}
catch(Kernel::Exception::NotFoundError&)
{
throw;
}
catch(std::runtime_error& )
{
throw ;
}
Janik Zikovsky
committed
std::string sasDetname="\n\t\t\t\t<name>";
sasDetname+=detectorName;
sasDetname+="</name>";
sasDet+=sasDetname;
Janik Zikovsky
committed
//outFile<<sasDetname;
std::string sasDetUnit="\n\t\t\t\t<SDD unit=\"m\">";
Janik Zikovsky
committed
std::stringstream sdd;
double distance = detgroup->getDistance(*m_workspace->getInstrument()->getSample());
sdd << distance;
Janik Zikovsky
committed
sasDetUnit+=sdd.str();
sasDetUnit+="</SDD>";
//outFile<<sasDetUnit;
sasDet+=sasDetUnit;
sasDet+="\n\t\t\t</SASdetector>";
//outFile<<sasDet;
}
Janik Zikovsky
committed
/** This method creates an XML element named "SASprocess"
Janik Zikovsky
committed
* @param sasProcess :: string for sasprocess element in the xml
Janik Zikovsky
committed
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
*/
void SaveCanSAS1D::createSASProcessElement(std::string& sasProcess)
{
sasProcess="\n\t\t<SASprocess>";
//outFile<<sasProcess;
std::string sasProcname="\n\t\t\t<name>";
sasProcname+="Mantid generated CanSAS1D XML";
sasProcname+="</name>";
sasProcess+=sasProcname;
//outFile<<sasProcname;
time_t date;
time(&date);
std::tm* t;
t=localtime(&date);
char temp [25];
strftime (temp,25,"%d-%b-%Y %H:%M:%S",localtime(&date));
std::string sasDate(temp);
std::string sasProcdate="\n\t\t\t<date>";
sasProcdate+=sasDate;
sasProcdate+="</date>";
sasProcess+=sasProcdate;
std::string sasProcsvn="\n\t\t\t<term name=\"svn\">";
Russell Taylor
committed
sasProcsvn+=MantidVersion::version();
Janik Zikovsky
committed
sasProcsvn+="</term>";
sasProcess+=sasProcsvn;
const API::Run& run= m_workspace->run();
std::string user_file("");
if( run.hasProperty("UserFile") )
{
user_file = run.getLogData("UserFile")->value();
Janik Zikovsky
committed
else
{
g_log.warning()<< "Run does not contain \"UserFile\" information. A blank entry will be written." <<std::endl;
}
std::string sasProcuserfile="\n\t\t\t<term name=\"user_file\">";
sasProcuserfile+=user_file;
sasProcuserfile+="</term>";
//outFile<<sasProcuserfile;
sasProcess+=sasProcuserfile;
sasProcess+="\n\t\t</SASprocess>";
}
}