Skip to content
Snippets Groups Projects
NexusDescriptor.cpp 9.97 KiB
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"
// clang-format off
#include <nexus/NeXusFile.hpp>
#include <nexus/NeXusException.hpp>
// clang-format on

#include <Poco/File.h>
#include <Poco/Path.h>

#include <cstring>
#include <string>
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");
  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'};
  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;
}
} // namespace
//---------------------------------------------------------------------------------------------------------------------------
// 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,
                                 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(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() {}
/// Returns the name & type of the first entry in the file
const std::pair<std::string, std::string> &
NexusDescriptor::firstEntryNameType() const {
  return m_firstEntryNameType;
}
/**
 * @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;
}
/**
 * @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);
  }
  file.closeGroup();
}
} // namespace Kernel