Newer
Older
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source,
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidKernel/NexusDescriptor.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 isReadable 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");
// 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'};
if (NexusDescriptor::HDF5SignatureSize !=
std::fread(static_cast<void *>(&buffer), sizeof(unsigned char),
NexusDescriptor::HDF5SignatureSize, fileHandle)) {
throw std::runtime_error("Error while reading file");
}
// 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::isReadable(const std::string &filename,
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(nullptr) {
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");
}
try {
initialize(filename);
} catch (::NeXus::Exception &e) {
throw std::invalid_argument(
"NexusDescriptor::initialize - File '" + filename +
"' does not look like a HDF file.\n Error was: " + e.what());
NexusDescriptor::~NexusDescriptor() {}
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 "";
}
/**
* @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 {
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 = std::make_unique<::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.emplace(rootPath, className);
}
if (level == 0) {
auto attrInfos = file.getAttrInfos();
for (auto &attrInfo : attrInfos) {
m_rootAttrs.insert(attrInfo.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 =
std::string(rootPath).append("/").append(entryName);
if (entryClass == "SDS" || entryClass == "ILL_data_scan_vars") {
pmap.emplace(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);
} // namespace Mantid