Skip to content
Snippets Groups Projects
MaterialXMLParser.cpp 7.54 KiB
Newer Older
#include "MantidKernel/MaterialXMLParser.h"
#include "MantidKernel/MaterialBuilder.h"

#include "Poco/DOM/AutoPtr.h"
#include "Poco/DOM/DOMParser.h"
#include "Poco/DOM/Document.h"
#include "Poco/DOM/NamedNodeMap.h"
#include "Poco/DOM/NodeIterator.h"
#include "Poco/DOM/NodeFilter.h"
#include "Poco/SAX/InputSource.h"
#include "Poco/SAX/SAXException.h"

#include <boost/lexical_cast.hpp>

#include <sstream>
#include <type_traits>
#include <unordered_map>

namespace Mantid {
namespace Kernel {

// -----------------------------------------------------------------------------
// Anonymous methods
// -----------------------------------------------------------------------------
namespace {

/*
 * Define the list of known attributes along with a map to the relevant
 * MaterialBuilder method to call. This avoids a long list of if/else
 * statements when parsing the xml.
 *
 * The mapping uses a template type, TypedBuilderHandle, to store the method
 * on the MaterialBuilder that should be called and the template type indicates
 * the argument type of the method call. The struct automatically extracts
 * the expected value type from the given string based on the template type.
 */

// Known attributes
const char *ID_ATT = "id";
const char *FORMULA_ATT = "formula";
const char *ATOMNUM_ATT = "atomicnumber";
const char *MASSNUM_ATT = "massnumber";
const char *NDENSITY_ATT = "numberdensity";
const char *ZPARAM_ATT = "zparameter";
const char *CELLVOL_ATT = "unitcellvol";
const char *MASSDENS_ATT = "massdensity";
const char *TOTSC_ATT = "totalscatterxsec";
const char *COHSC_ATT = "cohscatterxsec";
const char *INCOHSC_ATT = "incohscatterxsec";
const char *ABSORB_ATT = "absorptionxsec";

// Base type to put in a hash
struct BuilderHandle {
  virtual ~BuilderHandle() = default;
  virtual void operator()(MaterialBuilder &builder,
                          const std::string &value) const = 0;
};
using BuilderHandle_uptr = std::unique_ptr<BuilderHandle>;

// Pointer to member function on MaterialBuilder
template <typename ArgType>
using BuilderMethod = MaterialBuilder &(MaterialBuilder::*)(ArgType);

// Template type where ArgType indicates the expected argument type including
// const & ref qualifiers
template <typename ArgType>
struct TypedBuilderHandle final : public BuilderHandle {
  // Remove const/reference qualifiers from ArgType
  using ValueType = typename std::remove_const<
      typename std::remove_reference<ArgType>::type>::type;
  explicit TypedBuilderHandle(BuilderMethod<ArgType> m)
      : BuilderHandle(), m_method(m) {}

  void operator()(MaterialBuilder &builder,
                  const std::string &value) const override {
    auto typedVal = boost::lexical_cast<ValueType>(value);
    (builder.*m_method)(typedVal);
  }

private:
  BuilderMethod<ArgType> m_method;
};

using Handlers = std::unordered_map<std::string, BuilderHandle_uptr>;

// Insert a handle into the given map
template <typename ArgType>
void insertHandle(Handlers *hash, const std::string &name,
                  BuilderMethod<ArgType> m) {
  hash->insert(std::make_pair(
      name, BuilderHandle_uptr(new TypedBuilderHandle<ArgType>(m))));
}

// Find the appropriate handler for a given attribute
const BuilderHandle &findHandle(const std::string &name) {
  static Handlers handles;
  if (handles.empty()) {
    insertHandle(&handles, ID_ATT, &MaterialBuilder::setName);
    insertHandle(&handles, FORMULA_ATT, &MaterialBuilder::setFormula);
    insertHandle(&handles, ATOMNUM_ATT, &MaterialBuilder::setAtomicNumber);
    insertHandle(&handles, MASSNUM_ATT, &MaterialBuilder::setMassNumber);
    insertHandle(&handles, NDENSITY_ATT, &MaterialBuilder::setNumberDensity);
    insertHandle(&handles, ZPARAM_ATT, &MaterialBuilder::setZParameter);
    insertHandle(&handles, CELLVOL_ATT, &MaterialBuilder::setUnitCellVolume);
    insertHandle(&handles, MASSDENS_ATT, &MaterialBuilder::setMassDensity);
    insertHandle(&handles, TOTSC_ATT,
                 &MaterialBuilder::setTotalScatterXSection);
    insertHandle(&handles, COHSC_ATT, &MaterialBuilder::setCoherentXSection);
    insertHandle(&handles, INCOHSC_ATT,
                 &MaterialBuilder::setIncoherentXSection);
    insertHandle(&handles, ABSORB_ATT, &MaterialBuilder::setAbsorptionXSection);
  }
  auto iter = handles.find(name);
  if (iter != handles.end())
    return *(iter->second);
  else
    throw std::runtime_error("Unknown material attribute '" + name + "'");
}

/**
  * Set a value on the builder base on the attribute name and the defined
  * member function
  * @param builder A pointer to the builder to update
  * @param attr The attribute name
  * @param value The value in the attribute
  */
void addToBuilder(MaterialBuilder *builder, const std::string &attr,
                  const std::string &value) {
  // Find the appropriate member function on the builder and set the value
  // We need 3 maps for the 3 allowable value types for the builder member
  // functions
  const auto &setter = findHandle(attr);
  setter(*builder, value);
}
}

// -----------------------------------------------------------------------------
// Public methods
// -----------------------------------------------------------------------------

/**
 * Takes a stream that is assumed to contain a single complete material
 * definition,
 * reads the definition and produces a new Material object. If many definitions
 * are present then the first one is read
 * @param istr A reference to a stream
 * @return A new Material object
 */
Material MaterialXMLParser::parse(std::istream &istr) const {
  using namespace Poco::XML;
  using DocumentPtr = AutoPtr<Document>;

  InputSource src(istr);
  DOMParser parser;
  // Do not use auto here or anywhereas the Poco API returns raw pointers
  // but in some circumstances requires AutoPtrs to manage the memory
  DocumentPtr doc;
  try {
    doc = parser.parse(&src);
  } catch (SAXParseException &exc) {
    std::ostringstream os;
    os << "MaterialXMLReader::read() - Error parsing stream as XML: "
       << exc.what();
    throw std::invalid_argument(os.str());
  }

  Element *rootElement = doc->documentElement();

  // Iterating is apparently much faster than getElementsByTagName
  NodeIterator nodeIter(rootElement, NodeFilter::SHOW_ELEMENT);
  Node *node = nodeIter.nextNode();
  Material matr;
  bool found(false);
  while (node) {
    if (node->nodeName() == MATERIAL_TAG) {
      matr = parse(static_cast<Element *>(node));
      found = true;
      break;
    }
    node = nodeIter.nextNode();
  }
  if (!found) {
    throw std::invalid_argument(
        "MaterialXMLReader::read() - No material tags found.");
  }
  return matr;
}

/**
 * Takes a pointer to an XML node that is assumed to point at a "material"
 * tag.
 * It reads the definition and produces a new Material object.
 * @param element A pointer to an Element node that is a "material" tag
 * @return A new Material object
 */
Material MaterialXMLParser::parse(Poco::XML::Element *element) const {
  using namespace Poco::XML;
  using NamedNodeMapPtr = AutoPtr<NamedNodeMap>;
  NamedNodeMapPtr attrs = element->attributes();
  const auto id = attrs->getNamedItem(ID_ATT);
  if (!id || id->nodeValue().empty()) {
    throw std::invalid_argument("MaterialXMLReader::read() - No 'id' tag found "
                                "or emptry string provided.");
  }
  attrs->removeNamedItem(ID_ATT);

  MaterialBuilder builder;
  builder.setName(id->nodeValue());
  const auto nattrs = attrs->length();
  for (unsigned long i = 0; i < nattrs; ++i) {
    Node *node = attrs->item(i);
    addToBuilder(&builder, node->nodeName(), node->nodeValue());
  }
  return builder.build();
}

} // namespace Kernel
} // namespace Mantid