Newer
Older
#include "MantidKernel/NexusDescriptor.h"
#include "MantidKernel/Exception.h"
#include <nexus/NeXusFile.hpp>
#include <nexus/NeXusException.hpp>
#include <Poco/File.h>
#include <Poco/Path.h>
#include <cstring>
namespace Mantid
{
namespace Kernel
{
//---------------------------------------------------------------------------------------------------------------------------
// static NexusDescriptor constants
//---------------------------------------------------------------------------------------------------------------------------
/// Size of HDF magic number
const size_t NexusDescriptor::HDFMagicSize = 4;
/// HDF cookie that is stored in the first 4 bytes of the file.
const unsigned char NexusDescriptor::HDFMagic[4] = {'\016','\003','\023','\001'}; // From HDF4::hfile.h
/// Size of HDF5 signature
size_t NexusDescriptor::HDF5SignatureSize = 8;
/// signature identifying a HDF5 file.
const unsigned char NexusDescriptor::HDF5Signature[8] = { 137, 'H', 'D', 'F', '\r', '\n', '\032', '\n' };
namespace
{
//---------------------------------------------------------------------------------------------------------------------------
// Anonymous helper methods to use isHDF methods to use an open file handle
//---------------------------------------------------------------------------------------------------------------------------
/**
* Currently simply checks for the HDF signatures and returns true if one of them is found
* @param fileHandle A file handled opened and pointing at the start of the file. On return the
* fileHandle is left at the start of the file
* @param version One of the NexusDescriptor::Version enumerations specifying the required version
* @return True if the file is considered hierarchical, false otherwise
*/
bool isHDFHandle(FILE *fileHandle, NexusDescriptor::Version version)
{
if(!fileHandle) throw std::invalid_argument("HierarchicalFileDescriptor::isHierarchical - Invalid file handle");
bool result(false);
// HDF4 check requires 4 bytes, HDF5 check requires 8 bytes
// Use same buffer and waste a few bytes if only checking HDF4
unsigned char buffer[8] = {'0','0','0','0','0','0','0','0'};
std::fread(static_cast<void*>(&buffer), sizeof(unsigned char), NexusDescriptor::HDF5SignatureSize, fileHandle);
// Number of bytes read doesn't matter as if it is not enough then the memory simply won't match
// as the buffer has been "zeroed"
if(version == NexusDescriptor::Version5 || version == NexusDescriptor::AnyVersion )
result = (std::memcmp(&buffer, &NexusDescriptor::HDF5Signature, NexusDescriptor::HDF5SignatureSize) == 0);
if(!result && (version == NexusDescriptor::Version4 || version == NexusDescriptor::AnyVersion) )
result = (std::memcmp(&buffer, &NexusDescriptor::HDFMagic, NexusDescriptor::HDFMagicSize) == 0);
}
// Return file stream to start of file
std::rewind(fileHandle);
return result;
}
}
//---------------------------------------------------------------------------------------------------------------------------
// static NexusDescriptor methods
//---------------------------------------------------------------------------------------------------------------------------
/**
* Checks for the HDF signatures and returns true if one of them is found
* @param filename A string filename to check
* @param version One of the NexusDescriptor::Version enumerations specifying the required version
* @return True if the file is considered hierarchical, false otherwise
*/
bool NexusDescriptor::isHDF(const std::string & filename, const Version version)
{
FILE *fd = fopen(filename.c_str(), "rb");
if(!fd)
{
throw std::invalid_argument("HierarchicalFileDescriptor::isHierarchical - Unable to open file '" + filename + "'");
}
const bool result = isHDFHandle(fd, version); // use anonymous helper
fclose(fd);
return result;
}
//---------------------------------------------------------------------------------------------------------------------------
// NexusDescriptor public methods
//---------------------------------------------------------------------------------------------------------------------------
/**
* Constructs the wrapper
* @param filename A string pointing to an existing file
* @throws std::invalid_argument if the file is not identified to be hierarchical. This currently
* involves simply checking for the signature if a HDF file at the start of the file
*/
NexusDescriptor::NexusDescriptor(const std::string & filename)
: m_filename(), m_extension(), m_firstEntryNameType(),
m_rootAttrs(), m_pathsToTypes(), m_file(NULL)
{
if(filename.empty())
{
throw std::invalid_argument("NexusDescriptor() - Empty filename '" + filename + "'");
}
if(!Poco::File(filename).exists())
{
throw std::invalid_argument("NexusDescriptor() - File '" + filename + "' does not exist");
Gigg, Martyn Anthony
committed
try
{
initialize(filename);
}
catch(::NeXus::Exception &)
{
throw std::invalid_argument("NexusDescriptor::initialize - File '" + filename + "' does not look like a HDF file.");
Gigg, Martyn Anthony
committed
}
Gigg, Martyn Anthony
committed
/**
*/
NexusDescriptor::~NexusDescriptor()
Gigg, Martyn Anthony
committed
{
delete m_file;
Gigg, Martyn Anthony
committed
}
/// Returns the name & type of the first entry in the file
const std::pair<std::string,std::string> & NexusDescriptor::firstEntryNameType() const
{
return m_firstEntryNameType;
}
Gigg, Martyn Anthony
committed
/**
* @param name The name of an attribute
* @return True if the attribute exists, false otherwise
*/
bool NexusDescriptor::hasRootAttr(const std::string &name) const
{
return (m_rootAttrs.count(name) == 1);
}
/**
* @param path A string giving a path using UNIX-style path separators (/), e.g. /raw_data_1, /entry/bank1
* @return True if the path exists in the file, false otherwise
*/
bool NexusDescriptor::pathExists(const std::string& path) const
return (m_pathsToTypes.find(path) != m_pathsToTypes.end());
}
/**
* @param path A string giving a path using UNIX-style path separators (/), e.g. /raw_data_1, /entry/bank1
* @param type A string specifying the required type
* @return True if the path exists in the file, false otherwise
*/
bool NexusDescriptor::pathOfTypeExists(const std::string& path, const std::string & type) const
auto it = m_pathsToTypes.find(path);
if(it != m_pathsToTypes.end())
return (it->second == type);
else return false;
Gigg, Martyn Anthony
committed
}
/**
* @param type A string specifying the required type
* @return path A string giving a path using UNIX-style path separators (/), e.g. /raw_data_1, /entry/bank1
*/
std::string NexusDescriptor::pathOfType(const std::string & type) const
{
auto iend = m_pathsToTypes.end();
for (auto it = m_pathsToTypes.begin(); it != iend; ++it)
{
if (type == it->second)
return it->first;
}
return "";
}
Gigg, Martyn Anthony
committed
/**
* @param classType A string name giving a class type
* @return True if the type exists in the file, false otherwise
*/
bool NexusDescriptor::classTypeExists(const std::string & classType) const
Gigg, Martyn Anthony
committed
{
auto iend = m_pathsToTypes.end();
for(auto it = m_pathsToTypes.begin(); it != iend; ++it)
{
if(classType == it->second) return true;
}
return false;
}
//---------------------------------------------------------------------------------------------------------------------------
// NexusDescriptor private methods
//---------------------------------------------------------------------------------------------------------------------------
/**
* Creates the internal cached structure of the file as a tree of nodes
*/
void NexusDescriptor::initialize(const std::string& filename)
{
m_filename = filename;
m_extension = "." + Poco::Path(filename).getExtension();
m_file = new ::NeXus::File(this->filename());
m_file->openPath("/");
m_rootAttrs.clear();
m_pathsToTypes.clear();
walkFile(*m_file, "", "", m_pathsToTypes,0);
}
/**
* Cache the structure in the given maps
* @param file An open NeXus File object
* @param rootPath The current path that is open in the file
* @param className The class of the current open path
* @param pmap [Out] An output map filled with mappings of path->type
* @param level An integer defining the current level in the file
*/
void NexusDescriptor::walkFile(::NeXus::File & file,const std::string & rootPath, const std::string & className,
std::map<std::string, std::string> & pmap, int level)
{
if (!rootPath.empty())
pmap.insert(std::make_pair(rootPath, className));
auto attrInfos = file.getAttrInfos();
for(size_t i = 0; i < attrInfos.size(); ++i)
{
m_rootAttrs.insert(attrInfos[i].name);
}
auto dirents = file.getEntries();
auto itend = dirents.end();
for (auto it = dirents.begin(); it != itend; ++it)
{
const std::string & entryName = it->first;
const std::string & entryClass = it->second;
const std::string entryPath = rootPath + "/" + entryName;
if(entryClass == "SDS")
{
//tmap.insert(std::make_pair(entryClass, entryPath));
pmap.insert(std::make_pair(entryPath, entryClass));
}
else if(entryClass == "CDF0.0")
{
// Do nothing with this
}
else
{
if(level == 0) m_firstEntryNameType = (*it); //copy first entry name & type
file.openGroup(entryName, entryClass);
walkFile(file, entryPath, entryClass, pmap,level+1);
}
}
file.closeGroup();
} // namespace Kernel
} // namespace Mantid