Skip to content
Snippets Groups Projects
InstrumentDefinitionParser.cpp 108 KiB
Newer Older
#include "MantidGeometry/Instrument/InstrumentDefinitionParser.h"
#include "MantidGeometry/Instrument/Detector.h"
#include "MantidGeometry/Instrument/ObjCompAssembly.h"
#include "MantidGeometry/Instrument/ReferenceFrame.h"
#include "MantidGeometry/Instrument/RectangularDetector.h"
#include "MantidGeometry/Instrument/XMLInstrumentParameter.h"
#include "MantidGeometry/Objects/ShapeFactory.h"
#include "MantidGeometry/Rendering/vtkGeometryCacheReader.h"
#include "MantidGeometry/Rendering/vtkGeometryCacheWriter.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/ChecksumHelper.h"
#include "MantidKernel/Logger.h"
#include "MantidKernel/ProgressBase.h"
#include "MantidKernel/UnitFactory.h"
#include "MantidKernel/Strings.h"
#include <Poco/Path.h>
#include <Poco/String.h>
#include <Poco/DOM/Document.h>
#include <Poco/DOM/DOMParser.h>
#include <Poco/DOM/DOMWriter.h>
#include <Poco/DOM/Element.h>
#include <Poco/DOM/NodeFilter.h>
#include <Poco/DOM/NodeIterator.h>
#include <Poco/DOM/NodeList.h>
#include <Poco/SAX/AttributesImpl.h>
#include <boost/make_shared.hpp>
#include <boost/assign/list_of.hpp>

using namespace Mantid;
using namespace Mantid::Kernel;
using Poco::XML::DOMParser;
using Poco::XML::Document;
using Poco::XML::Element;
using Poco::XML::Node;
using Poco::XML::NodeList;
using Poco::XML::NodeIterator;
using Poco::XML::NodeFilter;

namespace Mantid {
namespace Geometry {
namespace {
// initialize the static logger
Kernel::Logger g_log("InstrumentDefinitionParser");
}
//----------------------------------------------------------------------------------------------
/** Default Constructor - not very functional in this state
InstrumentDefinitionParser::InstrumentDefinitionParser()
    : m_xmlFile(boost::make_shared<NullIDFObject>()),
      m_cacheFile(boost::make_shared<NullIDFObject>()), m_pDoc(NULL),
      m_hasParameterElement_beenSet(false), m_haveDefaultFacing(false),
      m_deltaOffsets(false), m_angleConvertConst(1.0),
      m_indirectPositions(false), m_cachingOption(NoneApplied) {
  initialise("", "", "", "");
}
//----------------------------------------------------------------------------------------------
/** Constructor
 * @param filename :: IDF .xml path (full). This is needed mostly to find the
 *instrument geometry cache.
 * @param instName :: name of the instrument
 * @param xmlText :: XML contents of IDF
 */
InstrumentDefinitionParser::InstrumentDefinitionParser(
    const std::string &filename, const std::string &instName,
    const std::string &xmlText)
    : m_xmlFile(boost::make_shared<NullIDFObject>()),
      m_cacheFile(boost::make_shared<NullIDFObject>()), m_pDoc(NULL),
      m_hasParameterElement_beenSet(false), m_haveDefaultFacing(false),
      m_deltaOffsets(false), m_angleConvertConst(1.0),
      m_indirectPositions(false), m_cachingOption(NoneApplied) {
  initialise(filename, instName, xmlText, "");
}

//----------------------------------------------------------------------------------------------
/** Construct the XML parser based on an IDF xml and cached vtp file objects.
 *
 * @param xmlFile :: The xml file, here wrapped in a IDFObject
 * @param expectedCacheFile :: Expected vtp cache file
 * @param instName :: Instrument name
 * @param xmlText :: XML contents of IDF
 */
InstrumentDefinitionParser::InstrumentDefinitionParser(
    const IDFObject_const_sptr xmlFile,
    const IDFObject_const_sptr expectedCacheFile, const std::string &instName,
    const std::string &xmlText)
    : m_xmlFile(boost::make_shared<NullIDFObject>()),
      m_cacheFile(boost::make_shared<NullIDFObject>()), m_pDoc(NULL),
      m_hasParameterElement_beenSet(false), m_haveDefaultFacing(false),
      m_deltaOffsets(false), m_angleConvertConst(1.0),
      m_indirectPositions(false), m_cachingOption(NoneApplied) {
  initialise(xmlFile->getFileFullPathStr(), instName, xmlText,
             expectedCacheFile->getFileFullPathStr());

  m_cacheFile = expectedCacheFile;
}
//----------------------------------------------------------------------------------------------
/** Initialise method used in Constructor
 * @param filename :: IDF .xml path (full). This is needed mostly to find the
 *instrument geometry cache.
 * @param instName :: name of the instrument
 * @param xmlText :: XML contents of IDF
 * @param vtpFilename :: the path to the vtp file if you want to override the
 *default
void InstrumentDefinitionParser::initialise(const std::string &filename,
                                            const std::string &instName,
                                            const std::string &xmlText,
                                            const std::string &vtpFilename) {

  IDFObject_const_sptr xmlFile = boost::make_shared<const IDFObject>(filename);
  // Handle the parameters
  m_instName = instName;
  m_xmlFile = xmlFile;
  // Create our new instrument
  // We don't want the instrument name taken out of the XML file itself, it
  // should come from the filename (or the property)
  m_instrument = boost::make_shared<Instrument>(m_instName);
  // Save the XML file path and contents
  m_instrument->setFilename(filename);
  m_instrument->setXmlText(xmlText);

  // Use the filename to construct the cachefile name so that there is a 1:1 map
  // between a definition file & cache
  if (vtpFilename.empty()) {
    m_cacheFile = boost::make_shared<const IDFObject>(createVTPFileName());
    m_cacheFile = boost::make_shared<const IDFObject>(vtpFilename);
  }
//----------------------------------------------------------------------------------------------
/** Destructor
 */
InstrumentDefinitionParser::~InstrumentDefinitionParser() {}

//----------------------------------------------------------------------------------------------
/**
 * Handle used in the singleton constructor for instrument file should append
 *the value
 * file sha-1 checksum to determine if it is already in
 *memory so that
 * changes to the instrument file will cause file to be reloaded.
 *
 * @return a mangled name combining the filename and the checksum
 *attribute of the XML contents
 * */
std::string InstrumentDefinitionParser::getMangledName() {
  std::string retVal = "";
  // use the xml in preference if available
  auto xml = Poco::trim(m_instrument->getXmlText());
  if (!(xml.empty())) {
    std::string checksum = Kernel::ChecksumHelper::sha1FromString(xml);
    retVal = m_instName + checksum;
  } else if (this->m_xmlFile->exists()) { // Use the file
    retVal = m_xmlFile->getMangledName();
  }

  return retVal;
}

//----------------------------------------------------------------------------------------------
/** Lazy loads the document and returns a autopointer
 *
 * @return an autopointer to the xml document
 */
Poco::AutoPtr<Poco::XML::Document> InstrumentDefinitionParser::getDocument() {
  if (!m_pDoc) {
    // instantiate if not created
    if (m_instrument->getXmlText().empty()) {
      throw std::invalid_argument("Instrument XML string is empty");
    }
    // Set up the DOM parser and parse xml file
    DOMParser pParser;
    try {
      m_pDoc = pParser.parseString(m_instrument->getXmlText());
    } catch (Poco::Exception &exc) {
      throw std::invalid_argument(exc.displayText() + ". Unable to parse XML");
    } catch (...) {
      throw std::invalid_argument("Unable to parse XML");
    }
  return m_pDoc;
}

//----------------------------------------------------------------------------------------------
/** Fully parse the IDF XML contents and returns the instrument thus created
 *
 * @param prog :: Optional Progress reporter object. If NULL, no progress
 *reporting.
 * @return the instrument that was created
 */
Instrument_sptr
InstrumentDefinitionParser::parseXML(Kernel::ProgressBase *prog) {
  auto pDoc = getDocument();

  // Get pointer to root element
  Poco::XML::Element *pRootElem = pDoc->documentElement();

  if (!pRootElem->hasChildNodes()) {
    g_log.error("Instrument XML contains no root element.");
    throw Kernel::Exception::InstrumentDefinitionError(
        "No root element in XML instrument");
  }

  setValidityRange(pRootElem);
  readDefaults(pRootElem->getChildElement("defaults"));
  // create maps: isTypeAssembly and mapTypeNameToShape
  Geometry::ShapeFactory shapeCreator;

  const std::string filename = m_xmlFile->getFileFullPathStr();

  // Get all the type and component element pointers.
  std::vector<Element *> typeElems;
  std::vector<Element *> compElems;
  for (Node *pNode = pRootElem->firstChild(); pNode != 0;
       pNode = pNode->nextSibling()) {
    auto pElem = dynamic_cast<Element *>(pNode);
    if (pElem) {
      if (pElem->tagName() == "type")
        typeElems.push_back(pElem);
      else if (pElem->tagName() == "component")
        compElems.push_back(pElem);
    }
  }

  if (typeElems.empty()) {
    g_log.error("XML file: " + filename + "contains no type elements.");
    throw Kernel::Exception::InstrumentDefinitionError(
        "No type elements in XML instrument file", filename);
  // Collect some information about types for later use including:
  //  * populate directory getTypeElement
  //  * populate directory isTypeAssemply
  //  * create shapes for all none assembly components and store in
  //  mapTyepNameToShape
  //  * If 'Outline' attribute set for assembly add attribute object_created=no
  //  create shape for such assembly also later
  const size_t numberTypes = typeElems.size();
  for (size_t iType = 0; iType < numberTypes; ++iType) {
    Element *pTypeElem = typeElems[iType];
    std::string typeName = pTypeElem->getAttribute("name");

    // check if contain <combine-components-into-one-shape>. If this then such
    // types are adjusted after this loop has completed
    Poco::AutoPtr<NodeList> pNL_type_combine_into_one_shape =
        pTypeElem->getElementsByTagName("combine-components-into-one-shape");
    if (pNL_type_combine_into_one_shape->length() > 0) {
      continue;
    }

    // Each type in the IDF must be uniquely named, hence return error if type
    // has already been defined
    if (getTypeElement.find(typeName) != getTypeElement.end()) {
      g_log.error("XML file: " + filename +
                  "contains more than one type element named " + typeName);
      throw Kernel::Exception::InstrumentDefinitionError(
          "XML instrument file contains more than one type element named " +
              typeName,
          filename);
    }
    getTypeElement[typeName] = pTypeElem;

    // identify for now a type to be an assemble by it containing elements
    // with tag name 'component'
    Poco::AutoPtr<NodeList> pNL_local =
        pTypeElem->getElementsByTagName("component");
    if (pNL_local->length() == 0) {
      // for now try to create a geometry shape associated with every type
      // that does not contain any component elements
      mapTypeNameToShape[typeName] = shapeCreator.createShape(pTypeElem);
      mapTypeNameToShape[typeName]->setName(static_cast<int>(iType));
    } else {
      isTypeAssembly[typeName] = true;
      if (pTypeElem->hasAttribute("outline")) {
        pTypeElem->setAttribute("object_created", "no");
  // Deal with adjusting types containing <combine-components-into-one-shape>
  for (size_t iType = 0; iType < numberTypes; ++iType) {
    Element *pTypeElem = typeElems[iType];
    std::string typeName = pTypeElem->getAttribute("name");

    // In this loop only interested in types containing
    // <combine-components-into-one-shape>
    Poco::AutoPtr<NodeList> pNL_type_combine_into_one_shape =
        pTypeElem->getElementsByTagName("combine-components-into-one-shape");
    const unsigned long nelements = pNL_type_combine_into_one_shape->length();
    if (nelements == 0)
      continue;

    // Each type in the IDF must be uniquely named, hence return error if type
    // has already been defined
    if (getTypeElement.find(typeName) != getTypeElement.end()) {
      g_log.error("XML file: " + filename +
                  "contains more than one type element named " + typeName);
      throw Kernel::Exception::InstrumentDefinitionError(
          "XML instrument file contains more than one type element named " +
              typeName,
          filename);
    }
    getTypeElement[typeName] = pTypeElem;

    InstrumentDefinitionParser helper;
    helper.adjust(pTypeElem, isTypeAssembly, getTypeElement);

    isTypeAssembly[typeName] = false;

    mapTypeNameToShape[typeName] = shapeCreator.createShape(pTypeElem);
    mapTypeNameToShape[typeName]->setName(static_cast<int>(iType));
  // create m_hasParameterElement
  Poco::AutoPtr<NodeList> pNL_parameter =
      pRootElem->getElementsByTagName("parameter");

  unsigned long numParameter = pNL_parameter->length();
  m_hasParameterElement.reserve(numParameter);

  // It turns out that looping over all nodes and checking if their nodeName is
  // equal
  // to "parameter" is much quicker than looping over the pNL_parameter
  // NodeList.
  Poco::XML::NodeIterator it(pRootElem, Poco::XML::NodeFilter::SHOW_ELEMENT);
  Poco::XML::Node *pNode = it.nextNode();
  while (pNode) {
    if (pNode->nodeName() == "parameter") {
      Element *pParameterElem = static_cast<Element *>(pNode);
      m_hasParameterElement.push_back(
          static_cast<Element *>(pParameterElem->parentNode()));
    }
    pNode = it.nextNode();
  m_hasParameterElement_beenSet = true;

  // See if any parameters set at instrument level
  setLogfile(m_instrument.get(), pRootElem, m_instrument->getLogfileCache());

  //
  // do analysis for each top level component element
    prog->resetNumSteps(compElems.size(), 0.0, 1.0);

  for (size_t i = 0; i < compElems.size(); ++i) {
    if (prog)
      prog->report("Loading instrument Definition");

    const Element *pElem = compElems[i];
    {
      IdList idList; // structure to possibly be populated with detector IDs

      // Get all <location> and <locations> elements contained in component
      // element
      // just for the purpose of a IDF syntax check
      Poco::AutoPtr<NodeList> pNL_location =
          pElem->getElementsByTagName("location");
      Poco::AutoPtr<NodeList> pNL_locations =
          pElem->getElementsByTagName("locations");
      // do a IDF syntax check
      if (pNL_location->length() == 0 && pNL_locations->length() == 0) {
        g_log.error(std::string("A component element must contain at least one "
                                "<location> or <locations> element") +
                    " even if it is just an empty location element of the form "
                    "<location />");
        throw Kernel::Exception::InstrumentDefinitionError(
            std::string("A component element must contain at least one "
                        "<location> or <locations> element") +
                " even if it is just an empty location element of the form "
                "<location />",
            filename);
      }

      // Loop through all <location> and <locations> elements of this component
      // by looping
      // all the child nodes and then see if any of these nodes are either
      // <location> or
      // <locations> elements. Done this way order these locations are processed
      // is the
      // order they are listed in the IDF. The latter needed to get detector IDs
      // assigned
      // as expected
      for (Node *pNode = pElem->firstChild(); pNode != 0;
           pNode = pNode->nextSibling()) {
        auto pChildElem = dynamic_cast<Element *>(pNode);
        if (!pChildElem)
          continue;
        if (pChildElem->tagName() == "location") {
          // process differently depending on whether component is and
          // assembly or leaf
          if (isAssembly(pElem->getAttribute("type"))) {
            appendAssembly(m_instrument.get(), pChildElem, pElem, idList);
          } else {
            appendLeaf(m_instrument.get(), pChildElem, pElem, idList);
Harry Jeffery's avatar
Harry Jeffery committed
        } else if (pChildElem->tagName() == "locations") {
          // append <locations> elements in <locations>
          appendLocations(m_instrument.get(), pChildElem, pElem, idList);
      } // finished looping over all childs of this component
      // A check
      if (idList.counted != static_cast<int>(idList.vec.size())) {
        ss1 << idList.vec.size();
        ss2 << idList.counted;
        if (!pElem->hasAttribute("idlist")) {
          g_log.error("No detector ID list found for detectors of type " +
                      pElem->getAttribute("type"));
        } else if (idList.vec.size() == 0) {
          g_log.error("No detector IDs found for detectors in list " +
                      pElem->getAttribute("idlist") + "for detectors of type" +
                      pElem->getAttribute("type"));
          g_log.error(
              "The number of detector IDs listed in idlist named " +
              pElem->getAttribute("idlist") +
              " is larger than the number of detectors listed in type = " +
              pElem->getAttribute("type"));
        throw Kernel::Exception::InstrumentDefinitionError(
            "Number of IDs listed in idlist (=" + ss1.str() +
                ") is larger than the number of detectors listed in type = " +
                pElem->getAttribute("type") + " (=" + ss2.str() + ").",
            filename);
      idList.reset();
  // Don't need this anymore (if it was even used) so empty it out to save
  // memory
  m_tempPosHolder.clear();

  // Read in or create the geometry cache file
  m_cachingOption = setupGeometryCache();

  // Add/overwrite any instrument params with values specified in
  // <component-link> XML elements
  setComponentLinks(m_instrument, pRootElem);

  if (m_indirectPositions)
    createNeutronicInstrument();

  // And give back what we created
  return m_instrument;
}

//-----------------------------------------------------------------------------------------------------------------------
/** Assumes second argument is a XML location element and its parent is a
*component element
*  which is assigned to be an assembly. This method appends the parent component
*element of
*  the location element to the CompAssembly passed as the 1st arg. Note this
*method may call
*  itself, i.e. it may act recursively.
*
*  @param parent :: CompAssembly to append new component to
*  @param pLocElems ::  Poco::XML element that points to a locations element in
*an instrument description XML file, which optionally may be detached (meaning
*it is not required to be part of the DOM tree of the IDF)
*  @param pCompElem :: The Poco::XML \<component\> element that contains the
*\<locations\> element
*  @param idList :: The current IDList
*/
void InstrumentDefinitionParser::appendLocations(
    Geometry::ICompAssembly *parent, const Poco::XML::Element *pLocElems,
    const Poco::XML::Element *pCompElem, IdList &idList) {
  // create detached <location> elements from <locations> element
  Poco::AutoPtr<Document> pLocationsDoc = convertLocationsElement(pLocElems);
  // Get pointer to root element
  const Element *pRootLocationsElem = pLocationsDoc->documentElement();
  const bool assembly = isAssembly(pCompElem->getAttribute("type"));
  Poco::XML::Element *pElem =
      dynamic_cast<Poco::XML::Element *>(pRootLocationsElem->firstChild());

  while (pElem) {
    if (pElem->tagName() != "location") {
      pElem = dynamic_cast<Poco::XML::Element *>(pElem->nextSibling());
      continue;
    }

    if (assembly) {
      appendAssembly(parent, pElem, pCompElem, idList);
      appendLeaf(parent, pElem, pCompElem, idList);
    pElem = dynamic_cast<Poco::XML::Element *>(pElem->nextSibling());
}

//-----------------------------------------------------------------------------------------------------------------------
/** Save DOM tree to xml file. This method was initially added for testing
 *purpose
 *  but may be useful for other purposes. During the parsing of the DOM tree
 *  in parseXML() the tree may be modified, e.g. if
 *<combine-components-into-one-shape>
 *  is used.
 *
 *  @param outFilename :: Output filename
 */
void InstrumentDefinitionParser::saveDOM_Tree(std::string &outFilename) {
  Poco::XML::DOMWriter writer;
  writer.setNewLine("\n");
  writer.setOptions(Poco::XML::XMLWriter::PRETTY_PRINT);
  auto pDoc = getDocument();
  std::ofstream outFile(outFilename.c_str());
  writer.writeNode(outFile, pDoc);
  outFile.close();
}

//-----------------------------------------------------------------------------------------------------------------------
/** Set location (position) of comp as specified in XML location element.
 *
 *  @param comp :: To set position/location off
 *  @param pElem ::  Poco::XML element that points a \<location\> element, which
 *optionally may be detached (meaning it is not required to be part of the DOM
 *tree of the IDF)
 *  @param angleConvertConst :: constant for converting deg to rad
 *  @param deltaOffsets :: radial position offsets
 *
 *  @throw logic_error Thrown if second argument is not a pointer to a
 *'location' XML element
 */
void InstrumentDefinitionParser::setLocation(Geometry::IComponent *comp,
                                             const Poco::XML::Element *pElem,
                                             const double angleConvertConst,
                                             const bool deltaOffsets) {
  comp->setPos(
      getRelativeTranslation(comp, pElem, angleConvertConst, deltaOffsets));

  // Rotate coordinate system of this component
  if (pElem->hasAttribute("rot")) {
    double rotAngle =
        angleConvertConst *
        atof((pElem->getAttribute("rot")).c_str()); // assumed to be in degrees

    double axis_x = 0.0;
    double axis_y = 0.0;
    double axis_z = 1.0;

    if (pElem->hasAttribute("axis-x"))
      axis_x = atof((pElem->getAttribute("axis-x")).c_str());
    if (pElem->hasAttribute("axis-y"))
      axis_y = atof((pElem->getAttribute("axis-y")).c_str());
    if (pElem->hasAttribute("axis-z"))
      axis_z = atof((pElem->getAttribute("axis-z")).c_str());

    comp->rotate(Kernel::Quat(rotAngle, Kernel::V3D(axis_x, axis_y, axis_z)));
  // Check if sub-elements <trans> or <rot> of present - for now ignore these if
  // m_deltaOffset = true
  Element *pRecursive = NULL;
  Element *tElem = pElem->getChildElement("trans");
  Element *rElem = pElem->getChildElement("rot");
  bool stillTransElement = true;
  bool firstRound =
      true; // during first round below pRecursive has not been set up front
  while (stillTransElement) {
    // figure out if child element is <trans> or <rot> or none of these
    if (firstRound) {
      firstRound = false;
    } else {
      tElem = pRecursive->getChildElement("trans");
      rElem = pRecursive->getChildElement("rot");
    }
    if (tElem && rElem) {
      // if both a <trans> and <rot> child element present. Ignore <rot> element
      rElem = NULL;
    }
    if (!tElem && !rElem) {
      stillTransElement = false;
    }
    Kernel::V3D posTrans;
    if (tElem) {
      posTrans =
          getRelativeTranslation(comp, tElem, angleConvertConst, deltaOffsets);
      // to get the change in translation relative to current rotation of comp
      Geometry::CompAssembly compToGetRot;
      Geometry::CompAssembly compRot;
      compRot.setRot(comp->getRotation());
      compToGetRot.setParent(&compRot);
      compToGetRot.setPos(posTrans);
      // Apply translation
      comp->translate(compToGetRot.getPos());
      // for recursive action
      pRecursive = tElem;
    } // end translation
    if (rElem) {
      double rotAngle =
          angleConvertConst * atof((rElem->getAttribute("val"))
                                       .c_str()); // assumed to be in degrees
      double axis_x = 0.0;
      double axis_y = 0.0;
      double axis_z = 1.0;
      if (rElem->hasAttribute("axis-x"))
        axis_x = atof((rElem->getAttribute("axis-x")).c_str());
      if (rElem->hasAttribute("axis-y"))
        axis_y = atof((rElem->getAttribute("axis-y")).c_str());
      if (rElem->hasAttribute("axis-z"))
        axis_z = atof((rElem->getAttribute("axis-z")).c_str());

      comp->rotate(Kernel::Quat(rotAngle, Kernel::V3D(axis_x, axis_y, axis_z)));

      // for recursive action
      pRecursive = rElem;
    }

  } // end while
}

//-----------------------------------------------------------------------------------------------------------------------
/** Calculate the position of comp relative to its parent from info provided by
*\<location\> element.
*
*  @param comp :: To set position/location off
*  @param pElem ::  Poco::XML element that points a \<location\> element, which
*optionally may be detached (meaning it is not required to be part of the DOM
*tree of the IDF)
*  @param angleConvertConst :: constant for converting deg to rad
*  @param deltaOffsets :: radial position offsets
*
*  @return  Thrown if second argument is not a pointer to a 'location' XML
*element
*/
Kernel::V3D InstrumentDefinitionParser::getRelativeTranslation(
    const Geometry::IComponent *comp, const Poco::XML::Element *pElem,
    const double angleConvertConst, const bool deltaOffsets) {
  Kernel::V3D retVal; // position relative to parent

  // Polar coordinates can be labelled as (r,t,p) or (R,theta,phi)
  if (pElem->hasAttribute("r") || pElem->hasAttribute("t") ||
      pElem->hasAttribute("p") || pElem->hasAttribute("R") ||
      pElem->hasAttribute("theta") || pElem->hasAttribute("phi")) {
    double R = 0.0, theta = 0.0, phi = 0.0;

    if (pElem->hasAttribute("r"))
      R = atof((pElem->getAttribute("r")).c_str());
    if (pElem->hasAttribute("t"))
      theta = angleConvertConst * atof((pElem->getAttribute("t")).c_str());
    if (pElem->hasAttribute("p"))
      phi = angleConvertConst * atof((pElem->getAttribute("p")).c_str());

    if (pElem->hasAttribute("R"))
      R = atof((pElem->getAttribute("R")).c_str());
    if (pElem->hasAttribute("theta"))
      theta = angleConvertConst * atof((pElem->getAttribute("theta")).c_str());
    if (pElem->hasAttribute("phi"))
      phi = angleConvertConst * atof((pElem->getAttribute("phi")).c_str());

    if (deltaOffsets) {
      // In this case, locations given are radial offsets to the (radial)
      // position of the parent,
      // so need to do some extra calculation before they're stored internally
      // as x,y,z offsets.

      // Temporary vector to hold the parent's absolute position (will be 0,0,0
      // if no parent)
      Kernel::V3D parentPos;
      // Get the parent's absolute position (if the component has a parent)
      if (comp->getParent()) {
        std::map<const Geometry::IComponent *, SphVec>::iterator it;
        it = m_tempPosHolder.find(comp);
        SphVec parent;
        if (it == m_tempPosHolder.end())
          parent = m_tempPosHolder[comp->getParent().get()];
        else
          parent = it->second;

        // Add to the current component to get its absolute position
        R += parent.r;
        theta += parent.theta;
        phi += parent.phi;
        // Set the temporary V3D with the parent's absolute position
        parentPos.spherical(parent.r, parent.theta, parent.phi);
      }

      // Create a temporary vector that holds the absolute r,theta,phi position
      // Needed to make things work in situation when a parent object has a phi
      // value but a theta of zero
      SphVec tmp(R, theta, phi);
      // Add it to the map with the pointer to the Component object as key
      m_tempPosHolder[comp] = tmp;

      // Create a V3D and set its position to be the child's absolute position
      Kernel::V3D absPos;
      absPos.spherical(R, theta, phi);

      // Subtract the two V3D's to get what we want (child's relative position
      // in x,y,z)
      retVal = absPos - parentPos;
    } else {
      // In this case, the value given represents a vector from the parent to
      // the child
      retVal.spherical(R, theta, phi);
    }

  } else {
    double x = 0.0, y = 0.0, z = 0.0;

    if (pElem->hasAttribute("x"))
      x = atof((pElem->getAttribute("x")).c_str());
    if (pElem->hasAttribute("y"))
      y = atof((pElem->getAttribute("y")).c_str());
    if (pElem->hasAttribute("z"))
      z = atof((pElem->getAttribute("z")).c_str());

    retVal(x, y, z);
  }
  return retVal;
}

//-----------------------------------------------------------------------------------------------------------------------
/** Get parent \<component\> element of \<location\> element.
*
*  @param pLocElem ::  Poco::XML element that points a location element in the
*XML doc
*  @return Parent XML element to a location XML element
*
*  @throw logic_error Thrown if argument is not a child of component element
*/
Poco::XML::Element *InstrumentDefinitionParser::getParentComponent(
    const Poco::XML::Element *pLocElem) {
  if ((pLocElem->tagName()).compare("location") &&
      (pLocElem->tagName()).compare("locations")) {
    std::string tagname = pLocElem->tagName();
    g_log.error("Argument to function getParentComponent must be a pointer to "
                "an XML element with tag name location or locations.");
    throw std::logic_error(
        std::string("Argument to function getParentComponent must be a pointer "
                    "to an XML element") +
        "with tag name location or locations." + " The tag name is " + tagname);
  }
  // The location element is required to be a child of a component element. Get
  // this component element

  Node *pCompNode = pLocElem->parentNode();

  Element *pCompElem;
  if (pCompNode->nodeType() == 1) {
    pCompElem = static_cast<Element *>(pCompNode);
    if ((pCompElem->tagName()).compare("component")) {
      g_log.error("Argument to function getParentComponent must be a XML "
                  "element sitting inside a component element.");
      throw std::logic_error("Argument to function getParentComponent must be "
                             "a XML element sitting inside a component "
                             "element.");
    }
  } else {
    g_log.error("Argument to function getParentComponent must be a XML element "
                "whos parent is an element.");
    throw std::logic_error("Argument to function getParentComponent must be a "
                           "XML element whos parent is an element.");
  }
  return pCompElem;
}

//-----------------------------------------------------------------------------------------------------------------------
/** Get name of a location element. It will return the value of the attribute
 *'name', or the
 *  parent's name attribute, or the parent's type, if all else fails.
 *
 *  @param pElem ::  Poco::XML element that points to a \<location\> element,
 *which optionally may be detached (meaning it is not required to be part of the
 *DOM tree of the IDF)
 *  @param pCompElem :: The Poco::XML \<component\> element that contain the
 *location element, which may optionally be detached from the DOM tree also
 *  @return name of location element
 */
std::string InstrumentDefinitionParser::getNameOfLocationElement(
    const Poco::XML::Element *pElem, const Poco::XML::Element *pCompElem) {
  std::string retVal;

  if (pElem->hasAttribute("name"))
    retVal = pElem->getAttribute("name");
  else if (pCompElem->hasAttribute("name")) {
    retVal = pCompElem->getAttribute("name");
  } else {
    retVal = pCompElem->getAttribute("type");
  }
  return retVal;
}

//------------------------------------------------------------------------------------------------------------------------------
/** Checks the validity range in the IDF and adds it to the instrument object
 *  @param pRootElem A pointer to the root element of the instrument definition
 */
void InstrumentDefinitionParser::setValidityRange(
    const Poco::XML::Element *pRootElem) {
  const std::string filename = m_xmlFile->getFileFullPathStr();
  // check if IDF has valid-from and valid-to tags defined
  if (!pRootElem->hasAttribute("valid-from")) {
    throw Kernel::Exception::InstrumentDefinitionError(
        "<instrument> element must contain a valid-from tag", filename);
  } else {
    try {
      DateAndTime d(pRootElem->getAttribute("valid-from"));
      m_instrument->setValidFromDate(d);
    } catch (...) {
      throw Kernel::Exception::InstrumentDefinitionError(
          "The valid-from <instrument> tag must be a ISO8601 string", filename);
  if (!pRootElem->hasAttribute("valid-to")) {
    DateAndTime d = DateAndTime::getCurrentTime();
    m_instrument->setValidToDate(d);
    // Ticket #2335: no required valid-to date.
    // throw Kernel::Exception::InstrumentDefinitionError("<instrument> element
    // must contain a valid-to tag", filename);
  } else {
    try {
      DateAndTime d(pRootElem->getAttribute("valid-to"));
      m_instrument->setValidToDate(d);
    } catch (...) {
      throw Kernel::Exception::InstrumentDefinitionError(
          "The valid-to <instrument> tag must be a ISO8601 string", filename);
    }
  }
}

PointingAlong axisNameToAxisType(std::string &input) {
  PointingAlong direction;
  if (input.compare("x") == 0) {
    direction = X;
  } else if (input.compare("y") == 0) {
    direction = Y;
  } else {
    direction = Z;
  }
  return direction;
}

//-----------------------------------------------------------------------------------------------------------------------
/** Reads the contents of the \<defaults\> element to set member variables,
*  requires m_instrument to be already set
*  @param defaults :: points to the data read from the \<defaults\> element, can
* be null.
*/
void InstrumentDefinitionParser::readDefaults(Poco::XML::Element *defaults) {
  // Return without complaint, if there are no defaults
  if (!defaults)
    return;

  // Check whether spherical coordinates should be treated as offsets to parents
  // position
  std::string offsets;
  Element *offsetElement = defaults->getChildElement("offsets");
  if (offsetElement)
    offsets = offsetElement->getAttribute("spherical");
  if (offsets == "delta")
    m_deltaOffsets = true;

  // Check whether default facing is set
  Element *defaultFacingElement =
      defaults->getChildElement("components-are-facing");
  if (defaultFacingElement) {
    m_haveDefaultFacing = true;
    m_defaultFacing = parseFacingElementToV3D(defaultFacingElement);
  }
  // the default view is used by the instrument viewer to decide the angle to
  // display the instrument from on start up
  Element *defaultView = defaults->getChildElement("default-view");
  if (defaultView) {
    m_instrument->setDefaultViewAxis(defaultView->getAttribute("axis-view"));
    if (defaultView->hasAttribute("view")) {
      m_instrument->setDefaultView(defaultView->getAttribute("view"));
    }
  }
  // check if angle=radian has been set
  Element *angleUnit = defaults->getChildElement("angle");
  if (angleUnit) {
    if (angleUnit->getAttribute("unit") == "radian") {
      m_angleConvertConst = 180.0 / M_PI;
      std::map<std::string, std::string> &units =
          m_instrument->getLogfileUnit();
      units["angle"] = "radian";
    }
  }
  // Check if the IDF specifies that this is an indirect geometry instrument
  // that includes
  // both physical and 'neutronic' postions.
  // Any neutronic position tags will be ignored if this tag is missing
  if (defaults->getChildElement("indirect-neutronic-positions"))
    m_indirectPositions = true;
  /*
  Try to extract the reference frame information.
  */
  // Get the target xml element.
  Element *referenceFrameElement = defaults->getChildElement("reference-frame");
  // Extract if available
  if (referenceFrameElement) {
    using Poco::XML::XMLString;
    // Get raw xml values
    Element *upElement = referenceFrameElement->getChildElement("pointing-up");
    Element *alongElement =
        referenceFrameElement->getChildElement("along-beam");
    Element *handednessElement =
        referenceFrameElement->getChildElement("handedness");
    Element *originElement = referenceFrameElement->getChildElement("origin");

    // Defaults
    XMLString s_alongBeam("z");
    XMLString s_pointingUp("y");
    XMLString s_handedness("right");
    XMLString s_origin("");

    // Make extractions from sub elements where possible.
    if (alongElement) {
      s_alongBeam = alongElement->getAttribute("axis");
    }
    if (upElement) {
      s_pointingUp = upElement->getAttribute("axis");
    }
    if (handednessElement) {
      s_handedness = handednessElement->getAttribute("val");
    }
    if (originElement) {
      s_origin = originElement->getAttribute("val");
    }

    // Convert to input types
    PointingAlong alongBeam = axisNameToAxisType(s_alongBeam);
    PointingAlong pointingUp = axisNameToAxisType(s_pointingUp);
    Handedness handedness = s_handedness.compare("right") == 0 ? Right : Left;

    // Overwrite the default reference frame.
    m_instrument->setReferenceFrame(boost::make_shared<ReferenceFrame>(
        pointingUp, alongBeam, handedness, s_origin));
  }
}

std::vector<std::string> InstrumentDefinitionParser::buildExcludeList(
    const Poco::XML::Element *const location) {
  // check if <exclude> sub-elements for this location and create new exclude
  // list to pass on
  Poco::AutoPtr<NodeList> pNLexclude =
      location->getElementsByTagName("exclude");
  unsigned long numberExcludeEle = pNLexclude->length();
  std::vector<std::string> newExcludeList;
  for (unsigned long i = 0; i < numberExcludeEle; i++) {
    Element *pExElem = static_cast<Element *>(pNLexclude->item(i));
    if (pExElem->hasAttribute("sub-part"))
      newExcludeList.push_back(pExElem->getAttribute("sub-part"));
  }
  return newExcludeList;
}

//-----------------------------------------------------------------------------------------------------------------------
/** Assumes second argument is a XML location element and its parent is a
*component element
*  which is assigned to be an assembly. This method appends the parent component
*element of
*  the location element to the CompAssembly passed as the 1st arg. Note this
*method may call
*  itself, i.e. it may act recursively.
*
*  @param parent :: CompAssembly to append new component to
*  @param pLocElem ::  Poco::XML element that points to a location element in an