Skip to content
Snippets Groups Projects
Commit b848e302 authored by Martyn Gigg's avatar Martyn Gigg
Browse files

Add a parser for a material object from XML.

Refs #16044
parent e48544bd
No related branches found
No related tags found
No related merge requests found
...@@ -55,6 +55,7 @@ set ( SRC_FILES ...@@ -55,6 +55,7 @@ set ( SRC_FILES
src/MaskedProperty.cpp src/MaskedProperty.cpp
src/Material.cpp src/Material.cpp
src/MaterialBuilder.cpp src/MaterialBuilder.cpp
src/MaterialXMLParser.cpp
src/Math/ChebyshevPolyFit.cpp src/Math/ChebyshevPolyFit.cpp
src/Math/Distributions/BoseEinsteinDistribution.cpp src/Math/Distributions/BoseEinsteinDistribution.cpp
src/Math/Distributions/ChebyshevPolynomial.cpp src/Math/Distributions/ChebyshevPolynomial.cpp
...@@ -204,6 +205,7 @@ set ( INC_FILES ...@@ -204,6 +205,7 @@ set ( INC_FILES
inc/MantidKernel/MaskedProperty.h inc/MantidKernel/MaskedProperty.h
inc/MantidKernel/Material.h inc/MantidKernel/Material.h
inc/MantidKernel/MaterialBuilder.h inc/MantidKernel/MaterialBuilder.h
inc/MantidKernel/MaterialXMLParser.h
inc/MantidKernel/Math/ChebyshevPolyFit.h inc/MantidKernel/Math/ChebyshevPolyFit.h
inc/MantidKernel/Math/Distributions/BoseEinsteinDistribution.h inc/MantidKernel/Math/Distributions/BoseEinsteinDistribution.h
inc/MantidKernel/Math/Distributions/ChebyshevPolynomial.h inc/MantidKernel/Math/Distributions/ChebyshevPolynomial.h
...@@ -352,6 +354,7 @@ set ( TEST_FILES ...@@ -352,6 +354,7 @@ set ( TEST_FILES
MaskedPropertyTest.h MaskedPropertyTest.h
MaterialBuilderTest.h MaterialBuilderTest.h
MaterialTest.h MaterialTest.h
MaterialXMLParserTest.h
MatrixPropertyTest.h MatrixPropertyTest.h
MatrixTest.h MatrixTest.h
MemoryTest.h MemoryTest.h
......
#ifndef MANTID_KERNEL_MATERIALXMLPARSER_H_
#define MANTID_KERNEL_MATERIALXMLPARSER_H_
#include "MantidKernel/DllConfig.h"
#include "MantidKernel/Material.h"
#include <istream>
namespace Poco {
namespace XML {
class Element;
}
}
namespace Mantid {
namespace Kernel {
/**
Read an XML definition of a Material and produce a new Material object
Copyright &copy; 2016 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge
National Laboratory & European Spallation Source
This file is part of Mantid.
Mantid is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
Mantid is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
File change history is stored at: <https://github.com/mantidproject/mantid>
Code Documentation is available at: <http://doxygen.mantidproject.org>
*/
class MANTID_KERNEL_DLL MaterialXMLParser {
public:
static constexpr const char *MATERIAL_TAG = "material";
Material parse(std::istream &istr) const;
Material parse(Poco::XML::Element *node) const;
};
} // namespace Kernel
} // namespace Mantid
#endif /* MANTID_KERNEL_MATERIALXMLPARSER_H_ */
...@@ -43,7 +43,10 @@ MaterialBuilder &MaterialBuilder::setFormula(const std::string &formula) { ...@@ -43,7 +43,10 @@ MaterialBuilder &MaterialBuilder::setFormula(const std::string &formula) {
throw std::runtime_error("MaterialBuilder::setFormula() - Atomic no. " throw std::runtime_error("MaterialBuilder::setFormula() - Atomic no. "
"already set, cannot use formula aswell."); "already set, cannot use formula aswell.");
} }
if (formula.empty()) {
throw std::invalid_argument(
"MaterialBuilder::setFormula() - Empty formula provided.");
}
typedef Material::ChemicalFormula ChemicalFormula; typedef Material::ChemicalFormula ChemicalFormula;
try { try {
m_formula = Mantid::Kernel::make_unique<ChemicalFormula>( m_formula = Mantid::Kernel::make_unique<ChemicalFormula>(
......
#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 void operator()(MaterialBuilder &builder,
const std::string &value) const = 0;
};
typedef std::unique_ptr<BuilderHandle> BuilderHandle_uptr;
// 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
typedef typename std::remove_const<
typename std::remove_reference<ArgType>::type>::type ValueType;
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;
};
typedef std::unordered_map<std::string, BuilderHandle_uptr> Handlers;
// 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;
typedef AutoPtr<Document> DocumentPtr;
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;
typedef AutoPtr<NamedNodeMap> NamedNodeMapPtr;
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
...@@ -123,6 +123,7 @@ public: ...@@ -123,6 +123,7 @@ public:
void test_Invalid_Formula_Throws_Error_When_Set() { void test_Invalid_Formula_Throws_Error_When_Set() {
MaterialBuilder builder; MaterialBuilder builder;
TS_ASSERT_THROWS(builder.setFormula(""), std::invalid_argument);
TS_ASSERT_THROWS(builder.setFormula("Al-2"), std::invalid_argument); TS_ASSERT_THROWS(builder.setFormula("Al-2"), std::invalid_argument);
} }
......
#ifndef MANTID_KERNEL_MATERIALXMLPARSERTEST_H_
#define MANTID_KERNEL_MATERIALXMLPARSERTEST_H_
#include <cxxtest/TestSuite.h>
#include "MantidKernel/MaterialXMLParser.h"
#include "Poco/AutoPtr.h"
#include "Poco/SAX/InputSource.h"
#include "Poco/DOM/DOMParser.h"
#include "Poco/DOM/Document.h"
#include "Poco/DOM/NodeList.h"
#include <sstream>
using Mantid::Kernel::MaterialXMLParser;
class MaterialXMLParserTest : public CxxTest::TestSuite {
public:
// This pair of boilerplate methods prevent the suite being created statically
// This means the constructor isn't called when running other tests
static MaterialXMLParserTest *createSuite() {
return new MaterialXMLParserTest();
}
static void destroySuite(MaterialXMLParserTest *suite) { delete suite; }
//----------------------------------------------------------------------------
// Success tests
//
// The assumption here is that the complex logic of building the material is
// tested by the MaterialBuilderTest. Therefore, here we just test that
// all of the attributes are handled.
//----------------------------------------------------------------------------
void test_Formula_Attribute() {
auto mat = parseMaterial("<material id=\"vanadium\" formula=\"V\"/>");
TS_ASSERT_EQUALS("vanadium", mat.name());
TS_ASSERT_DELTA(0.07223047, mat.numberDensity(), 1e-8);
}
void test_AtomicNumber_Attribute() {
auto mat = parseMaterial("<material id=\"n\" atomicnumber=\"28\"/>");
TS_ASSERT_DELTA(mat.totalScatterXSection(), 18.5, 0.0001);
TS_ASSERT_DELTA(mat.absorbXSection(), 4.49, 0.0001);
}
void test_MassNumber_Attribute() {
auto mat = parseMaterial(
"<material id=\"n\" atomicnumber=\"28\" massnumber=\"58\"/>");
TS_ASSERT_DELTA(mat.totalScatterXSection(), 26.1, 0.0001);
TS_ASSERT_DELTA(mat.absorbXSection(), 4.6, 0.0001);
}
void test_NumberDensity_Attribute() {
auto mat = parseMaterial("<material id=\"n\" atomicnumber=\"28\" "
"massnumber=\"58\" numberdensity=\"0.12\"/>");
TS_ASSERT_DELTA(0.12, mat.numberDensity(), 1e-04);
}
void test_ZParameter_And_UnitCellVolume_Attributes() {
auto mat = parseMaterial("<material id=\"a\" formula=\"Al2-O3\" "
"zparameter=\"6\" unitcellvol=\"253.54\"/>");
TS_ASSERT_DELTA(mat.numberDensity(), 0.1183245, 1e-07);
}
void test_MassDensity_Attribute() {
auto mat = parseMaterial("<material id=\"a\" formula=\"Al2-O3\" "
"massdensity=\"4\" />");
TS_ASSERT_DELTA(mat.numberDensity(), 0.0236252, 1e-07);
}
void test_TotalScattering_Attribute() {
auto mat = parseMaterial("<material id=\"a\" formula=\"Al2-O3\" "
"totalscatterxsec=\"18.1\"/>");
TS_ASSERT_DELTA(mat.totalScatterXSection(), 18.1, 1e-04);
}
void test_CoherentScattering_Attribute() {
auto mat = parseMaterial("<material id=\"a\" formula=\"Al2-O3\" "
"cohscatterxsec=\"4.6\"/>");
TS_ASSERT_DELTA(mat.cohScatterXSection(), 4.6, 1e-04);
}
void test_IncoherentScattering_Attribute() {
auto mat = parseMaterial("<material id=\"a\" formula=\"Al2-O3\" "
"incohscatterxsec=\"0.1\"/>");
TS_ASSERT_DELTA(mat.incohScatterXSection(), 0.1, 1e-04);
}
void test_Absorption_Attribute() {
auto mat = parseMaterial("<material id=\"a\" formula=\"Al2-O3\" "
"absorptionxsec=\"4.45\"/>");
TS_ASSERT_DELTA(mat.absorbXSection(), 4.45, 1e-04);
}
void test_Read_Valid_XML_Returns_Expected_Material_From_Stream_Source() {
const std::string xml = "<material id=\"vanadium\" formula=\"V\">"
"</material>";
std::istringstream instream(xml);
MaterialXMLParser parser;
auto matr = parser.parse(instream);
TS_ASSERT_EQUALS("vanadium", matr.name());
TS_ASSERT_DELTA(0.07223047, matr.numberDensity(), 1e-8);
}
//----------------------------------------------------------------------------
// Failure tests
//----------------------------------------------------------------------------
void test_Empty_Source_Throws_Error() {
std::string xml;
std::istringstream instream(xml);
MaterialXMLParser parser;
TS_ASSERT_THROWS(parser.parse(instream), std::invalid_argument);
}
void test_Missing_Or_Empty_Id_Tag_Throws_Error() {
TS_ASSERT_THROWS(parseMaterial("<material formula=\"V\">"
"</material>"),
std::invalid_argument);
TS_ASSERT_THROWS(parseMaterial("<material id=\"\" formula=\"V\">"
"</material>"),
std::invalid_argument);
}
void test_Unknown_Attribute_Throws_Error() {
TS_ASSERT_THROWS(parseMaterial("<material id=\"n\" atomic=\"28\">"
"</material>"),
std::runtime_error);
}
//----------------------------------------------------------------------------
// Non-test methods
//----------------------------------------------------------------------------
private:
Mantid::Kernel::Material parseMaterial(const std::string &text) {
using Poco::AutoPtr;
using namespace Poco::XML;
AutoPtr<Document> doc = createXMLDocument(text);
AutoPtr<NodeList> elements = doc->getElementsByTagName("material");
MaterialXMLParser parser;
return parser.parse(static_cast<Element *>(elements->item(0)));
}
Poco::AutoPtr<Poco::XML::Document>
createXMLDocument(const std::string &text) {
using Poco::AutoPtr;
using namespace Poco::XML;
std::istringstream instream(text);
InputSource src(instream);
DOMParser parser;
return parser.parse(&src);
}
};
#endif /* MANTID_KERNEL_MATERIALXMLPARSERTEST_H_ */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment