diff --git a/src/coreComponents/common/DataTypes.hpp b/src/coreComponents/common/DataTypes.hpp index e7dc90fa52c..6c81b4d58e7 100644 --- a/src/coreComponents/common/DataTypes.hpp +++ b/src/coreComponents/common/DataTypes.hpp @@ -61,6 +61,7 @@ #include #include #include +#include /** * top level geosx namespace contains all code that is specific to GEOSX @@ -129,6 +130,9 @@ using globalIndex = GEOSX_GLOBALINDEX_TYPE; /// String type. using string = std::string; +/// String type. +using string_view = std::string_view; + /// 32-bit floating point type. using real32 = float; /// 64-bit floating point type. diff --git a/src/coreComponents/common/Logger.hpp b/src/coreComponents/common/Logger.hpp index 546fabdaab0..c919d56c981 100644 --- a/src/coreComponents/common/Logger.hpp +++ b/src/coreComponents/common/Logger.hpp @@ -479,9 +479,9 @@ struct InputError : public std::runtime_error {} /** - * @brief Construct an InputError from an underlying exception. - * @param subException An exception to base this new one on. - * @param msgToInsert The error message. It will be inserted into the one inside of subException. + * @brief Constructs an InputError from an underlying exception. + * @param subException The exception on which the created one is based. + * @param msgToInsert The error message that will be inserted in the subException error message. */ InputError( std::exception const & subException, std::string const & msgToInsert ); }; diff --git a/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp b/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp index cb71d7ca81d..bf2e24ef63d 100644 --- a/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp +++ b/src/coreComponents/constitutive/unitTests/testDruckerPrager.cpp @@ -72,8 +72,7 @@ void testDruckerPragerDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -81,8 +80,8 @@ void testDruckerPragerDriver() GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); localIndex constexpr numElem = 2; @@ -189,8 +188,7 @@ void testDruckerPragerExtendedDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -198,8 +196,8 @@ void testDruckerPragerExtendedDriver() GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); localIndex constexpr numElem = 2; diff --git a/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp b/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp index 7cad35e245b..5592872d7e1 100644 --- a/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp +++ b/src/coreComponents/constitutive/unitTests/testElasticIsotropic.cpp @@ -71,7 +71,7 @@ TEST( ElasticIsotropicTests, testStateUpdatePoint ) ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -79,8 +79,8 @@ TEST( ElasticIsotropicTests, testStateUpdatePoint ) GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); dataRepository::Group disc( "discretization", &rootGroup ); diff --git a/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp b/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp index 1dbdacbb857..253971bccd1 100644 --- a/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp +++ b/src/coreComponents/constitutive/unitTests/testModifiedCamClay.cpp @@ -86,8 +86,7 @@ void testModifiedCamClayDriver() ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -95,8 +94,8 @@ void testModifiedCamClayDriver() GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); localIndex constexpr numElem = 2; diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index 23bee515331..346fb14073e 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -22,7 +22,10 @@ set( dataRepository_headers WrapperBase.hpp wrapperHelpers.hpp xmlWrapper.hpp - ) + DataContext.hpp + GroupContext.hpp + WrapperContext.hpp + ) # # Specify all sources @@ -35,7 +38,10 @@ set( dataRepository_sources Utilities.cpp WrapperBase.cpp xmlWrapper.cpp - ) + DataContext.cpp + GroupContext.cpp + WrapperContext.cpp + ) set( dependencyList ${parallelDeps} codingUtilities pugixml ) @@ -56,11 +62,11 @@ blt_add_library( NAME dataRepository HEADERS ${dataRepository_headers} DEPENDS_ON ${dependencyList} OBJECT ${GEOSX_BUILD_OBJ_LIBS} - ) + ) target_include_directories( dataRepository PUBLIC ${CMAKE_SOURCE_DIR}/coreComponents ) -geosx_add_code_checks(PREFIX dataRepository ) +geosx_add_code_checks( PREFIX dataRepository ) if( GEOS_ENABLE_TESTS ) diff --git a/src/coreComponents/dataRepository/DataContext.cpp b/src/coreComponents/dataRepository/DataContext.cpp new file mode 100644 index 00000000000..8ddf77b3e94 --- /dev/null +++ b/src/coreComponents/dataRepository/DataContext.cpp @@ -0,0 +1,107 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file DataContext.cpp + */ + +#include "DataContext.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +DataContext::DataContext( string const & targetName ): + m_targetName( targetName ) +{} + +std::ostream & operator<<( std::ostream & os, DataContext const & ctx ) +{ + os << ctx.toString(); + return os; +} + +DataContext::ToStringInfo::ToStringInfo( string const & targetName, string const & filePath, size_t line ): + m_targetName( targetName ), + m_filePath( filePath ), + m_line( line ) +{} +DataContext::ToStringInfo::ToStringInfo( string const & targetName ): + m_targetName( targetName ) +{} + + +/** + * @return the node 'name' attribute if it exists, return the node tag name otherwise. + * @param node the target node. + */ +string getNodeName( xmlWrapper::xmlNode const & node ) +{ + xmlWrapper::xmlAttribute const nameAtt = node.attribute( "name" ); + if( !nameAtt.empty() ) + { + return string( node.attribute( "name" ).value() ); + } + else + { + return string( node.name() ); + } +} + +DataFileContext::DataFileContext( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ): + DataContext( getNodeName( targetNode ) ), + m_typeName( targetNode.name() ), + m_filePath( nodePos.filePath ), + m_line( nodePos.line ), + m_offsetInLine( nodePos.offsetInLine ), + m_offset( nodePos.offset ) +{} + +DataFileContext::DataFileContext( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlAttribute const & att, + xmlWrapper::xmlAttributePos const & attPos ): + DataContext( getNodeName( targetNode ) + '/' + att.name() ), + m_typeName( att.name() ), + m_filePath( attPos.filePath ), + m_line( attPos.line ), + m_offsetInLine( attPos.offsetInLine ), + m_offset( attPos.offset ) +{} + +string DataFileContext::toString() const +{ + if( m_line != xmlWrapper::xmlDocument::npos ) + { + return GEOS_FMT( "{} ({}, l.{})", m_targetName, splitPath( m_filePath ).second, m_line ); + } + else if( m_offset != xmlWrapper::xmlDocument::npos ) + { + return GEOS_FMT( "{} ({}, offset {})", m_targetName, splitPath( m_filePath ).second, m_offset ); + } + else + { + return GEOS_FMT( "{} (Source file not found)", m_targetName ); + } +} + +DataContext::ToStringInfo DataFileContext::getToStringInfo() const +{ return ToStringInfo( m_targetName, m_filePath, m_line ); } + + + +} /* namespace dataRepository */ +} /* namespace geos */ diff --git a/src/coreComponents/dataRepository/DataContext.hpp b/src/coreComponents/dataRepository/DataContext.hpp new file mode 100644 index 00000000000..6b30face341 --- /dev/null +++ b/src/coreComponents/dataRepository/DataContext.hpp @@ -0,0 +1,226 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file DataContext.hpp + */ + +#ifndef GEOS_DATAREPOSITORY_DATACONTEXT_HPP_ +#define GEOS_DATAREPOSITORY_DATACONTEXT_HPP_ + +#include "common/DataTypes.hpp" +#include "common/Logger.hpp" +#include "xmlWrapper.hpp" +#include "common/Format.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +/** + * @class DataContext + * + * DataContext is an abstract class storing contextual information on an object: + * - Either its line position in a file (if applicable, implementation in DataFileContext), + * - or its location in the data hierarchy (implementation in GroupContext and WrapperContext). + * Typically, the target object contains an unique_ptr< DataContext > instance of this class. + */ +class DataContext +{ +public: + + /** + * @brief Construct a new DataContext object. + * @param targetName the target object name + */ + DataContext( string const & targetName ); + + /** + * @brief Destroy the DataContext object + */ + virtual ~DataContext() {} + + /** + * @return A string that mention all the known informations to retrieve from where the target + * object comes from. + */ + virtual string toString() const = 0; + + /** + * @return Get the target object name + */ + string getTargetName() const + { return m_targetName; } + /** + * @brief Insert contextual information in the provided stream. + */ + friend std::ostream & operator<<( std::ostream & os, const DataContext & ctx ); + +protected: + // GroupContext & WrapperContext are friend class to be able to access to the protected method on other instances. + friend class GroupContext; + friend class WrapperContext; + + /// @see getObjectName() + string const m_targetName; + + /// This struct exposes the raw data of a DataContext instance that toString() need in order to format it. + /// This struct lifetime depends on that of the source DataContext. The DataContext is considered constant. + struct ToStringInfo + { + /// the targetName of the DataContext + string m_targetName; + /// the file path of the DataFileContext, if it exists (an empty string otherwise) + string m_filePath; + /// the file line of the DataFileContext, if it exists (an empty string otherwise) + size_t m_line = xmlWrapper::xmlDocument::npos; + + /** + * @brief Construct a new ToStringInfo object from a DataContext that has input file info. + * @param targetName the target name + * @param filePath the input file path where the target is declared. + * @param line the line in the file where the target is declared. + */ + ToStringInfo( string const & targetName, string const & filePath, size_t line ); + /** + * @brief Construct a new ToStringInfo object from a DataContext that has no input file info. + * @param targetName the target name. + */ + ToStringInfo( string const & targetName ); + /** + * @return true if a location has been found to declare the target in an input file. + */ + bool hasInputFileInfo() const + { return !m_filePath.empty() && m_line != xmlWrapper::xmlDocument::npos; } + }; + + /** + * @brief This method exposes the raw data of a DataContext, in order to access and format it + * (notably in toString() implementations that need to access other DataContext instances). + * @return a ToStringInfo struct that contains the raw data contained in this DataContext instance. + */ + virtual ToStringInfo getToStringInfo() const = 0; + +}; + +/** + * @class DataFileContext + * + * Stores information to retrieve where a target object has been declared in the input source + * file (e.g. XML). + */ +class DataFileContext final : public DataContext +{ +public: + + /** + * @brief Construct the file context of a Group from an xml node. + * @param targetNode the target object xml node + * @param nodePos the target object xml node position + */ + DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ); + /** + * @brief Construct the file context of a Group from an xml node. + * @param targetNode the xml node containing the xml attribute + * @param att the target object xml attribute + * @param attPos the target object xml attribute position + */ + DataFileContext( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlAttribute const & att, + xmlWrapper::xmlAttributePos const & attPos ); + + /** + * @return the target object name followed by the the file and line declaring it. + */ + string toString() const override; + + /** + * @return the type name in the source file (XML node tag name / attribute name). + */ + string getTypeName() const + { return m_typeName; } + + /** + * @return the source file path where the target object has been declared. + */ + string getFilePath() const + { return m_filePath; } + + /** + * @return the line (starting from 1) where the target object has been declared in the source file. + */ + size_t getLine() const + { return m_line; } + + /** + * @return the character offset in the line (starting from 1) where the target object has been + * declared in the source file. + */ + size_t getOffsetInLine() const + { return m_offsetInLine; } + + /** + * @return the character offset (starting from 0) in the source file path where the target object has been + * declared. + */ + size_t getOffset() const + { return m_offset; } + +private: + + /// @see getTypeName() + string const m_typeName; + /// @see getFilePath() + string const m_filePath; + /// @see getLine() + size_t const m_line; + /// @see getLineOffset() + size_t const m_offsetInLine; + /// @see getOffset() + size_t const m_offset; + + /** + * @copydoc DataContext::getToStringInfo() + */ + ToStringInfo getToStringInfo() const override; + +}; + + +} /* namespace dataRepository */ +} /* namespace geos */ + + + +/** + * @brief Formatter to be able to directly use a DataContext as a GEOS_FMT() argument. + * Inherits from formatter to reuse its parse() method. + */ +template<> +struct GEOS_FMT_NS::formatter< geos::dataRepository::DataContext > : GEOS_FMT_NS::formatter< std::string > +{ + /** + * @brief Format the specified DataContext to a string. + * @param dataContext the DataContext object to format + * @param ctx formatting state consisting of the formatting arguments and the output iterator + * @return iterator to the output buffer + */ + auto format( geos::dataRepository::DataContext const & dataContext, format_context & ctx ) + { + return GEOS_FMT_NS::formatter< std::string >::format( dataContext.toString(), ctx ); + } +}; + +#endif /* GEOS_DATAREPOSITORY_DATACONTEXT_HPP_ */ diff --git a/src/coreComponents/dataRepository/Group.cpp b/src/coreComponents/dataRepository/Group.cpp index 6e8f7f34acd..4373abe653b 100644 --- a/src/coreComponents/dataRepository/Group.cpp +++ b/src/coreComponents/dataRepository/Group.cpp @@ -18,6 +18,7 @@ #include "codingUtilities/StringUtilities.hpp" #include "codingUtilities/Utilities.hpp" #include "common/TimingMacros.hpp" +#include "GroupContext.hpp" #if defined(GEOSX_USE_PYGEOSX) #include "python/PyGroupType.hpp" #endif @@ -31,7 +32,7 @@ Group::Group( string const & name, Group * const parent ): Group( name, parent->getConduitNode() ) { - GEOS_ERROR_IF( parent == nullptr, "Should not be null." ); + GEOS_ERROR_IF( parent == nullptr, "Should not be null (for Group named " << name << ")." ); m_parent = parent; } @@ -47,7 +48,8 @@ Group::Group( string const & name, m_logLevel( 0 ), m_restart_flags( RestartFlags::WRITE_AND_READ ), m_input_flags( InputFlags::INVALID ), - m_conduitNode( rootNode[ name ] ) + m_conduitNode( rootNode[ name ] ), + m_dataContext( std::make_unique< GroupContext >( *this ) ) {} Group::~Group() @@ -71,7 +73,8 @@ WrapperBase & Group::registerWrapper( std::unique_ptr< WrapperBase > wrapper ) void Group::deregisterWrapper( string const & name ) { - GEOS_ERROR_IF( !hasWrapper( name ), "Wrapper " << name << " doesn't exist." ); + GEOS_ERROR_IF( !hasWrapper( name ), + "Wrapper " << name << " doesn't exist in Group" << getDataContext() << '.' ); m_wrappers.erase( name ); m_conduitNode.remove( name ); } @@ -125,15 +128,16 @@ void Group::reserve( indexType const newSize ) string Group::getPath() const { - // In the Conduit node heirarchy everything begins with 'Problem', we should change it so that + // In the Conduit node hierarchy everything begins with 'Problem', we should change it so that // the ProblemManager actually uses the root Conduit Node but that will require a full rebaseline. string const noProblem = getConduitNode().path().substr( stringutilities::cstrlen( dataRepository::keys::ProblemManager ) ); return noProblem.empty() ? "/" : noProblem; } -void Group::processInputFileRecursive( xmlWrapper::xmlNode & targetNode ) +void Group::processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, + xmlWrapper::xmlNode & targetNode ) { - xmlWrapper::addIncludedXML( targetNode ); + xmlDocument.addIncludedXML( targetNode ); // Handle the case where the node was imported from a different input file // Set the path prefix to make sure all relative Path variables are interpreted correctly @@ -141,8 +145,7 @@ void Group::processInputFileRecursive( xmlWrapper::xmlNode & targetNode ) xmlWrapper::xmlAttribute filePath = targetNode.attribute( xmlWrapper::filePathString ); if( filePath ) { - Path::pathPrefix() = splitPath( filePath.value() ).first; - targetNode.remove_attribute( filePath ); + Path::pathPrefix() = getAbsolutePath( splitPath( filePath.value() ).first ); } // Loop over the child nodes of the targetNode @@ -159,8 +162,11 @@ void Group::processInputFileRecursive( xmlWrapper::xmlNode & targetNode ) { // Make sure child names are not duplicated GEOS_ERROR_IF( std::find( childNames.begin(), childNames.end(), childName ) != childNames.end(), - GEOS_FMT( "Error: An XML block cannot contain children with duplicated names ({}/{}). ", - getPath(), childName ) ); + GEOS_FMT( "Error: An XML block cannot contain children with duplicated names.\n" + "Error detected at node {} with name = {} ({}:l.{})", + childNode.path(), childName, xmlDocument.getFilePath(), + xmlDocument.getNodePosition( childNode ).line ) ); + childNames.emplace_back( childName ); } @@ -172,22 +178,31 @@ void Group::processInputFileRecursive( xmlWrapper::xmlNode & targetNode ) } if( newChild != nullptr ) { - newChild->processInputFileRecursive( childNode ); + newChild->processInputFileRecursive( xmlDocument, childNode ); } } - processInputFile( targetNode ); + processInputFile( xmlDocument, targetNode ); // Restore original prefix once the node is processed Path::pathPrefix() = oldPrefix; } -void Group::processInputFile( xmlWrapper::xmlNode const & targetNode ) +void Group::processInputFile( xmlWrapper::xmlDocument const & xmlDocument, + xmlWrapper::xmlNode const & targetNode ) { + xmlWrapper::xmlNodePos nodePos = xmlDocument.getNodePosition( targetNode ); + + if( nodePos.isFound() ) + { + m_dataContext = std::make_unique< DataFileContext >( targetNode, nodePos ); + } + + std::set< string > processedAttributes; for( std::pair< string const, WrapperBase * > & pair : m_wrappers ) { - if( pair.second->processInputFile( targetNode ) ) + if( pair.second->processInputFile( targetNode, nodePos ) ) { processedAttributes.insert( pair.first ); } @@ -196,13 +211,14 @@ void Group::processInputFile( xmlWrapper::xmlNode const & targetNode ) for( xmlWrapper::xmlAttribute attribute : targetNode.attributes() ) { string const attributeName = attribute.name(); - if( attributeName != "name" && attributeName != "xmlns:xsi" && attributeName != "xsi:noNamespaceSchemaLocation" ) + if( !xmlWrapper::isFileMetadataAttribute( attributeName ) ) { GEOS_THROW_IF( processedAttributes.count( attributeName ) == 0, - GEOS_FMT( "XML Node '{}' with name='{}' contains unused attribute '{}'.\n" + GEOS_FMT( "XML Node at '{}' with name={} contains unused attribute '{}'.\n" "Valid attributes are:\n{}\nFor more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.path(), targetNode.attribute( "name" ).value(), attributeName, dumpInputOptions() ), + targetNode.path(), m_dataContext->toString(), attributeName, + dumpInputOptions() ), InputError ); } } @@ -241,14 +257,16 @@ Group * Group::createChild( string const & childKey, string const & childName ) void Group::printDataHierarchy( integer const indent ) { + GEOS_LOG( string( indent, '\t' ) << getName() << " : " << LvArray::system::demangleType( *this ) ); for( auto & view : wrappers() ) { - GEOS_LOG( string( indent, '\t' ) << view.second->getName() << ", " << LvArray::system::demangleType( view.second ) ); + GEOS_LOG( string( indent, '\t' ) << "-> " << view.second->getName() << " : " + << LvArray::system::demangleType( *view.second ) ); } + GEOS_LOG( string( indent, '\t' ) ); for( auto & group : m_subGroups ) { - GEOS_LOG( string( indent, '\t' ) << group.first << ':' ); group.second->printDataHierarchy( indent + 1 ); } } @@ -380,7 +398,7 @@ localIndex Group::packImpl( buffer_unit_type * & buffer, } else { - GEOS_ERROR( "Wrapper " << wrapperName << " not found in Group " << getName() << "." ); + GEOS_ERROR( "Wrapper " << wrapperName << " not found in Group " << getDataContext() << "." ); } } diff --git a/src/coreComponents/dataRepository/Group.hpp b/src/coreComponents/dataRepository/Group.hpp index cbd1726e6d8..745a8d1c31f 100644 --- a/src/coreComponents/dataRepository/Group.hpp +++ b/src/coreComponents/dataRepository/Group.hpp @@ -334,7 +334,7 @@ class Group { Group * const child = m_subGroups[ key ]; GEOS_THROW_IF( child == nullptr, - "Group " << getPath() << " has no child named " << key << std::endl + "Group " << getDataContext() << " has no child named " << key << std::endl << dumpSubGroupsNames(), std::domain_error ); @@ -349,7 +349,7 @@ class Group { Group const * const child = m_subGroups[ key ]; GEOS_THROW_IF( child == nullptr, - "Group " << getPath() << " has no child named " << key << std::endl + "Group " << getDataContext() << " has no child named " << key << std::endl << dumpSubGroupsNames(), std::domain_error ); @@ -760,10 +760,13 @@ class Group /** * @brief Recursively read values using ProcessInputFile() from the input - * file and put them into the wrapped values for this group. + * file and put them into the wrapped values for this group. + * Also add the includes content to the xmlDocument when `Include` nodes are encountered. + * @param[in] xmlDocument the XML document that contains the targetNode * @param[in] targetNode the XML node that to extract input values from. */ - void processInputFileRecursive( xmlWrapper::xmlNode & targetNode ); + void processInputFileRecursive( xmlWrapper::xmlDocument & xmlDocument, + xmlWrapper::xmlNode & targetNode ); /** * @brief Recursively call postProcessInput() to apply post processing after @@ -1086,7 +1089,7 @@ class Group { WrapperBase const * const wrapper = m_wrappers[ key ]; GEOS_THROW_IF( wrapper == nullptr, - "Group " << getPath() << " has no wrapper named " << key << std::endl + "Group " << getDataContext() << " has no wrapper named " << key << std::endl << dumpWrappersNames(), std::domain_error ); @@ -1101,7 +1104,7 @@ class Group { WrapperBase * const wrapper = m_wrappers[ key ]; GEOS_THROW_IF( wrapper == nullptr, - "Group " << getPath() << " has no wrapper named " << key << std::endl + "Group " << getDataContext() << " has no wrapper named " << key << std::endl << dumpWrappersNames(), std::domain_error ); @@ -1300,6 +1303,24 @@ class Group */ string getPath() const; + /** + * @return DataContext object that that stores contextual information on this group that can be + * used in output messages. + */ + DataContext const & getDataContext() const + { return *m_dataContext; } + + /** + * @return DataContext object that that stores contextual information on a wrapper contained by + * this group that can be used in output messages. + * @tparam KEY The lookup type. + * @param key The value used to lookup the wrapper. + * @throw std::domain_error if the wrapper doesn't exist. + */ + template< typename KEY > + DataContext const & getWrapperDataContext( KEY key ) const + { return getWrapperBase< KEY >( key ).getDataContext(); } + /** * @brief Access the group's parent. * @return reference to parent Group @@ -1307,7 +1328,7 @@ class Group */ Group & getParent() { - GEOS_THROW_IF( m_parent == nullptr, "Group at " << getPath() << " does not have a parent.", std::domain_error ); + GEOS_THROW_IF( m_parent == nullptr, "Group at " << getDataContext() << " does not have a parent.", std::domain_error ); return *m_parent; } @@ -1316,10 +1337,16 @@ class Group */ Group const & getParent() const { - GEOS_THROW_IF( m_parent == nullptr, "Group at " << getPath() << " does not have a parent.", std::domain_error ); + GEOS_THROW_IF( m_parent == nullptr, "Group at " << getDataContext() << " does not have a parent.", std::domain_error ); return *m_parent; } + /** + * @return true if this group has a parent. + */ + bool hasParent() const + { return m_parent != nullptr; } + /** * @brief Get the group's index within its parent group * @return integral index of current group within its parent @@ -1497,7 +1524,8 @@ class Group * wrapped values for this group. * @param[in] targetNode the XML node that to extract input values from. */ - virtual void processInputFile( xmlWrapper::xmlNode const & targetNode ); + virtual void processInputFile( xmlWrapper::xmlDocument const & xmlDocument, + xmlWrapper::xmlNode const & targetNode ); Group const & getBaseGroupByPath( string const & path ) const; @@ -1560,6 +1588,10 @@ class Group /// Reference to the conduit::Node that mirrors this group conduit::Node & m_conduitNode; + /// A DataContext object used to provide contextual information on this Group, + /// if it is created from an input XML file, the line or offset in that file. + std::unique_ptr< DataContext > m_dataContext; + }; /** diff --git a/src/coreComponents/dataRepository/GroupContext.cpp b/src/coreComponents/dataRepository/GroupContext.cpp new file mode 100644 index 00000000000..a3d52b269b0 --- /dev/null +++ b/src/coreComponents/dataRepository/GroupContext.cpp @@ -0,0 +1,60 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file GroupContext.cpp + */ + +#include "GroupContext.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +GroupContext::GroupContext( Group & group, string const & objectName ): + DataContext( objectName ), + m_group( group ) +{} +GroupContext::GroupContext( Group & group ): + GroupContext( group, group.getName() ) +{} + +string GroupContext::toString() const +{ + std::vector< ToStringInfo > parentsInfo; + for( Group const * group = &m_group; group->hasParent(); group = &group->getParent() ) + { + parentsInfo.push_back( group->getDataContext().getToStringInfo() ); + } + + std::ostringstream path; + auto lastFileInfo = std::find_if( parentsInfo.begin(), parentsInfo.end(), + []( ToStringInfo const & i ) { return i.hasInputFileInfo(); } ); + for( auto info = parentsInfo.rbegin(); info != parentsInfo.rend(); ++info ) + { + path << ( std::prev( info.base() ) == lastFileInfo ? // Is `info` pointing to the last file info? + GEOS_FMT( "/{}({},l.{})", info->m_targetName, info->m_filePath, info->m_line ) : + GEOS_FMT( "/{}", info->m_targetName )); + } + return path.str(); +} + +DataContext::ToStringInfo GroupContext::getToStringInfo() const +{ return ToStringInfo( m_targetName ); } + + +} /* namespace dataRepository */ +} /* namespace geos */ diff --git a/src/coreComponents/dataRepository/GroupContext.hpp b/src/coreComponents/dataRepository/GroupContext.hpp new file mode 100644 index 00000000000..327f00f5251 --- /dev/null +++ b/src/coreComponents/dataRepository/GroupContext.hpp @@ -0,0 +1,80 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file GroupContext.hpp + */ + +#ifndef GEOS_DATAREPOSITORY_GROUPCONTEXT_HPP_ +#define GEOS_DATAREPOSITORY_GROUPCONTEXT_HPP_ + +#include "DataContext.hpp" +#include "Group.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +/** + * @class GroupContext + * + * Helps to know where a Group is in the hierarchy. + * See DataContext class for more info. + */ +class GroupContext : public DataContext +{ +public: + + /** + * @brief Construct a new GroupContext object + * @param group The reference to the Group related to this GroupContext. + */ + GroupContext( Group & group ); + + /** + * @return the reference to the Group related to this GroupContext. + */ + Group const & getGroup() const; + +protected: + + /** + * @brief Construct a new GroupContext object + * @param group The reference to the Group related to this GroupContext. + * @param objectName Target object name. + */ + GroupContext( Group & group, string const & objectName ); + + /// The reference to the Group related to this GroupContext. + Group & m_group; + +private: + + /** + * @return the group path with the file & line of the first parent for which this information exists. + */ + string toString() const override; + /** + * @copydoc DataContext::getToStringInfo() + */ + ToStringInfo getToStringInfo() const override; +}; + + +} /* namespace dataRepository */ +} /* namespace geos */ + +#endif /* GEOS_DATAREPOSITORY_GROUPCONTEXT_HPP_ */ diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 99d17451b27..dcb413c6fe9 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -606,7 +606,8 @@ class Wrapper final : public WrapperBase return ss.str(); } - virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode ) override + virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) override { InputFlags const inputFlag = getInputFlag(); if( inputFlag >= InputFlags::OPTIONAL ) @@ -620,10 +621,11 @@ class Wrapper final : public WrapperBase targetNode, inputFlag == InputFlags::REQUIRED ); GEOS_THROW_IF( !m_successfulReadFromInput, - GEOS_FMT( "XML Node '{}' with name='{}' is missing required attribute '{}'." - "Available options are:\n{}\nFor more details, please refer to documentation at:\n" + GEOS_FMT( "XML Node {} ({}) with name={} is missing required attribute '{}'." + "Available options are:\n {}\n For more details, please refer to documentation at:\n" "http://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/userGuide/Index.html", - targetNode.path(), targetNode.attribute( "name" ).value(), getName(), dumpInputOptions( true ) ), + targetNode.name(), nodePos.toString(), targetNode.attribute( "name" ).value(), + getName(), dumpInputOptions( true ) ), InputError ); } else @@ -636,9 +638,12 @@ class Wrapper final : public WrapperBase } catch( std::exception const & ex ) { - processInputException( ex, targetNode ); + processInputException( ex, targetNode, nodePos ); } + if( m_successfulReadFromInput ) + createDataContext( targetNode, nodePos ); + return true; } diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index ac0cf085add..c3d9a94938a 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -18,6 +18,7 @@ #include "Group.hpp" #include "RestartFlags.hpp" +#include "WrapperContext.hpp" namespace geos @@ -37,7 +38,8 @@ WrapperBase::WrapperBase( string const & name, m_successfulReadFromInput( false ), m_description(), m_registeringObjects(), - m_conduitNode( parent.getConduitNode()[ name ] ) + m_conduitNode( parent.getConduitNode()[ name ] ), + m_dataContext( std::make_unique< WrapperContext >( *this ) ) {} @@ -60,7 +62,7 @@ void WrapperBase::copyWrapperAttributes( WrapperBase const & source ) string WrapperBase::getPath() const { - // In the Conduit node heirarchy everything begins with 'Problem', we should change it so that + // In the Conduit node hierarchy everything begins with 'Problem', we should change it so that // the ProblemManager actually uses the root Conduit Node but that will require a full rebaseline. string const noProblem = m_conduitNode.path().substr( std::strlen( dataRepository::keys::ProblemManager ) - 1 ); return noProblem.empty() ? "/" : noProblem; @@ -104,16 +106,43 @@ int WrapperBase::setTotalviewDisplay() const } #endif +void WrapperBase::createDataContext( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) +{ + xmlWrapper::xmlAttribute att = targetNode.attribute( m_name.c_str() ); + xmlWrapper::xmlAttributePos attPos = nodePos.getAttributeLine( m_name ); + if( nodePos.isFound() && attPos.isFound() && !att.empty() ) + { + m_dataContext = std::make_unique< DataFileContext >( targetNode, att, attPos ); + } +} + void WrapperBase::processInputException( std::exception const & ex, - xmlWrapper::xmlNode const & targetNode ) const + xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) const { xmlWrapper::xmlAttribute const & attribute = targetNode.attribute( getName().c_str() ); string const inputStr = string( attribute.value() ); - string subExStr = string( "***** XML parsing error in " ) + targetNode.path() + - " (name=" + targetNode.attribute( "name" ).value() + ")/" + attribute.name() + - "\n***** Input value: '" + inputStr + "'\n"; + xmlWrapper::xmlAttributePos const attPos = nodePos.getAttributeLine( getName() ); + std::ostringstream oss; + string const exStr = ex.what(); + + oss << "***** XML parsing error at node "; + if( nodePos.isFound() ) + { + string const & filePath = attPos.isFound() ? attPos.filePath : nodePos.filePath; + int const line = attPos.isFound() ? attPos.line : nodePos.line; + oss << "named " << m_parent->getName() << ", attribute " << getName() + << " (" << splitPath( filePath ).second << ", l." << line << ")."; + } + else + { + oss << targetNode.path() << " (name=" << targetNode.attribute( "name" ).value() << ")/" << getName(); + } + oss << "\n***** Input value: '" << inputStr << '\''; + oss << ( exStr[0]=='\n' ? exStr : "'\n" + exStr ); - throw InputError( subExStr + ex.what() ); + throw InputError( oss.str() ); } diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index 10528952e82..540a8901b10 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -24,6 +24,7 @@ #include "xmlWrapper.hpp" #include "RestartFlags.hpp" #include "HistoryDataSpec.hpp" +#include "DataContext.hpp" #if defined(GEOSX_USE_PYGEOSX) #include "LvArray/src/python/python.hpp" @@ -179,9 +180,11 @@ class WrapperBase /** * @brief Initialize the wrapper from the input xml node. * @param targetNode the xml node to initialize from. - * @return True iff the wrapper initialized itself from the file. + * @param nodePos the target node position, typically obtained with xmlDocument::getNodePosition(). + * @return True if the wrapper initialized itself from the file. */ - virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode ) = 0; + virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) = 0; /** * @brief Push the data in the wrapper into a Conduit blueprint field. @@ -412,6 +415,25 @@ class WrapperBase */ string getPath() const; + /** + * @return DataContext object that that stores contextual information on this group that can be + * used in output messages. + */ + DataContext const & getDataContext() const + { return *m_dataContext; } + + /** + * @return the group that contains this Wrapper. + */ + Group & getParent() + { return *m_parent; } + + /** + * @copydoc getParent() + */ + Group const & getParent() const + { return *m_parent; } + /** * @brief Set the InputFlag of the wrapper. * @param input the new InputFlags value @@ -612,12 +634,22 @@ class WrapperBase /// @endcond + /** + * @brief Sets the m_dataContext to a DataFileContext by retrieving the attribute file line. + * @param targetNode the node containing this wrapper source attribute. + * @param nodePos the xml node position of the node + */ + void createDataContext( xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ); + /** * @brief Helper method to process an exception that has been thrown during xml parsing. * @param ex The caught exception. * @param targetNode The node from which this Group is interpreted. + * @param nodePos the target node position. */ - void processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode ) const; + void processInputException( std::exception const & ex, xmlWrapper::xmlNode const & targetNode, + xmlWrapper::xmlNodePos const & nodePos ) const; protected: @@ -651,6 +683,9 @@ class WrapperBase /// A reference to the corresponding conduit::Node. conduit::Node & m_conduitNode; + /// A DataContext object that can helps to contextualize this Group. + std::unique_ptr< DataContext > m_dataContext; + private: /** diff --git a/src/coreComponents/dataRepository/WrapperContext.cpp b/src/coreComponents/dataRepository/WrapperContext.cpp new file mode 100644 index 00000000000..b9aa00ff731 --- /dev/null +++ b/src/coreComponents/dataRepository/WrapperContext.cpp @@ -0,0 +1,42 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file WrapperContext.cpp + */ + +#include "WrapperContext.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +WrapperContext::WrapperContext( WrapperBase & wrapper ): + GroupContext( wrapper.getParent(), wrapper.getParent().getName() + '/' + wrapper.getName() ), + m_typeName( wrapper.getName() ) +{} + +string WrapperContext::toString() const +{ + ToStringInfo const info = m_group.getDataContext().getToStringInfo(); + return info.hasInputFileInfo() ? + GEOS_FMT( "{} ({}, l.{})", m_targetName, info.m_filePath, info.m_line ) : + GEOS_FMT( "{}/{}", m_group.getDataContext().toString(), m_typeName ); +} + + +} /* namespace dataRepository */ +} /* namespace geos */ diff --git a/src/coreComponents/dataRepository/WrapperContext.hpp b/src/coreComponents/dataRepository/WrapperContext.hpp new file mode 100644 index 00000000000..d948a0334b9 --- /dev/null +++ b/src/coreComponents/dataRepository/WrapperContext.hpp @@ -0,0 +1,62 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2018-2020 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2020 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2018-2020 TotalEnergies + * Copyright (c) 2019- GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file WrapperContext.hpp + */ + +#ifndef GEOS_DATAREPOSITORY_WRAPPERCONTEXT_HPP_ +#define GEOS_DATAREPOSITORY_WRAPPERCONTEXT_HPP_ + +#include "GroupContext.hpp" +#include "WrapperBase.hpp" + +namespace geos +{ +namespace dataRepository +{ + + +/** + * @class WrapperContext + * + * Dedicated implementation of GroupContext for Wrapper. + * See DataContext class for more info. + */ +class WrapperContext final : public GroupContext +{ +public: + + /** + * @brief Construct a new WrapperContext object + * @param wrapper the target Wrapper object + */ + WrapperContext( WrapperBase & wrapper ); + +private: + + string const m_typeName; + + /** + * @return the parent group DataContext followed by the wrapper name. + */ + string toString() const override; + +}; + + +} /* namespace dataRepository */ +} /* namespace geos */ + +#endif /* GEOS_DATAREPOSITORY_WRAPPERCONTEXT_HPP_ */ diff --git a/src/coreComponents/dataRepository/xmlWrapper.cpp b/src/coreComponents/dataRepository/xmlWrapper.cpp index 98fc9b7514f..2efec4ca383 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.cpp +++ b/src/coreComponents/dataRepository/xmlWrapper.cpp @@ -16,6 +16,8 @@ * @file xmlWrapper.cpp */ +#include + #include "xmlWrapper.hpp" #include "codingUtilities/StringUtilities.hpp" @@ -71,12 +73,36 @@ template void stringToInputVariable( Tensor< real32, 3 > & target, string const template void stringToInputVariable( Tensor< real64, 3 > & target, string const & inputValue ); template void stringToInputVariable( Tensor< real64, 6 > & target, string const & inputValue ); -void addIncludedXML( xmlNode & targetNode, int const level ) +/** + * @brief Adds the filePath and character offset info on the node in filePathString + * and charOffsetString attributes. This function allow to keep track of the source + * filename & offset of each node. + * @param targetNode the target node to add the informations on. + * @param filePath the absolute path of the xml file containing the node. + */ +void addNodeFileInfo( xmlNode targetNode, string const & filePath ) +{ + // we keep the file path and the character offset on each node so we keep track of these + // informations, even if the nodes are manipulated within the xml hierarchy. + targetNode.append_attribute( filePathString ).set_value( filePath.c_str() ); + targetNode.append_attribute( charOffsetString ).set_value( targetNode.offset_debug() ); + + for( xmlNode subNode : targetNode.children() ) + { + addNodeFileInfo( subNode, filePath ); + } +} +/** + * @brief Returns true if the addNodeFileInfo() command has been called of the specified node. + */ +bool xmlDocument::hasNodeFileInfo() const +{ return !getFirstChild().attribute( filePathString ).empty(); } + +void xmlDocument::addIncludedXML( xmlNode & targetNode, int const level ) { GEOS_THROW_IF( level > 100, "XML include level limit reached, please check input for include loops", InputError ); - xmlNode const rootNode = targetNode.root(); - string const currentFilePath = rootNode.child( filePathString ).attribute( filePathString ).value(); + string const currentFilePath = targetNode.attribute( filePathString ).value(); // Schema currently allows a single unique , but a non-validating file may include multiple for( xmlNode includedNode : targetNode.children( includedListTag ) ) @@ -98,33 +124,32 @@ void addIncludedXML( xmlNode & targetNode, int const level ) }(); xmlDocument includedXmlDocument; - xmlResult const result = includedXmlDocument.load_file( includedFilePath.c_str() ); - GEOS_THROW_IF( !result, GEOS_FMT( "Errors found while parsing included XML file {}\nDescription: {}\nOffset: {}", - includedFilePath, result.description(), result.offset ), InputError ); + xmlResult const result = includedXmlDocument.loadFile( includedFilePath, hasNodeFileInfo() ); + GEOS_THROW_IF( !result, GEOS_FMT( "Errors found while parsing included XML file {}\n" + "Description: {}\nOffset: {}", + includedFilePath, result.description(), result.offset ), + InputError ); + // All included files must contain a root node that must match the target node. // Currently, schema only allows tags at the top level (inside ). // We then proceed to merge each nested node from included file with the one in main. - xmlNode includedRootNode = includedXmlDocument.first_child(); + xmlNode includedRootNode = includedXmlDocument.getFirstChild(); GEOS_THROW_IF_NE_MSG( string( includedRootNode.name() ), string( targetNode.name() ), "Included document root does not match the including XML node", InputError ); // Process potential includes in the included file to allow nesting - includedXmlDocument.append_child( filePathString ).append_attribute( filePathString ).set_value( includedFilePath.c_str() ); addIncludedXML( includedRootNode, level + 1 ); // Add each top level tag of imported document to current // This may result in repeated XML blocks, which will be implicitly merged when processed for( xmlNode importedNode : includedRootNode.children() ) { - // Save path to current file on the node, in order to handle relative paths later - if( !importedNode.attribute( filePathString ) ) - { - importedNode.append_attribute( filePathString ).set_value( includedFilePath.c_str() ); - } targetNode.append_copy( importedNode ); } + + m_originalBuffers[includedXmlDocument.getFilePath()] = includedXmlDocument.getOriginalBuffer(); } } @@ -151,7 +176,7 @@ string buildMultipleInputXML( string_array const & inputFileList, if( MpiWrapper::commRank() == 0 ) { xmlWrapper::xmlDocument compositeTree; - xmlWrapper::xmlNode compositeRoot = compositeTree.append_child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode compositeRoot = compositeTree.appendChild( dataRepository::keys::ProblemManager ); xmlWrapper::xmlNode includedRoot = compositeRoot.append_child( includedListTag ); for( auto & fileName: inputFileList ) @@ -160,7 +185,7 @@ string buildMultipleInputXML( string_array const & inputFileList, fileNode.append_attribute( "name" ) = fileName.c_str(); } - compositeTree.save_file( inputFileName.c_str() ); + compositeTree.saveFile( inputFileName ); } // Everybody else has to wait before attempting to read @@ -169,6 +194,253 @@ string buildMultipleInputXML( string_array const & inputFileList, return inputFileName; } +bool isFileMetadataAttribute( string const & name ) +{ + static const std::set< string > fileMetadataAttributes { + "name", "xmlns:xsi", "xsi:noNamespaceSchemaLocation", xmlWrapper::filePathString, xmlWrapper::charOffsetString + }; + return fileMetadataAttributes.find( name ) != fileMetadataAttributes.end(); +} + +constexpr size_t xmlDocument::npos; +size_t documentId=0; + +xmlDocument::xmlDocument(): + pugiDocument(), + m_rootFilePath( "CodeIncludedXML" + std::to_string( documentId++ ) ) +{} + +xmlResult xmlDocument::loadString( string_view content, bool loadNodeFileInfo ) +{ + xmlResult result = pugiDocument.load_buffer( content.data(), content.size(), + pugi::parse_default, pugi::encoding_auto ); + + // keeping a copy of original buffer to allow line retrieval + if( loadNodeFileInfo ) + { + m_originalBuffers.clear(); + m_originalBuffers[m_rootFilePath] = content; + + addNodeFileInfo( getFirstChild(), m_rootFilePath ); + } + + return result; +} +xmlResult xmlDocument::loadFile( string const & path, bool loadNodeFileInfo ) +{ + xmlResult result = pugiDocument.load_file( path.c_str(), pugi::parse_default, pugi::encoding_auto ); + m_rootFilePath = getAbsolutePath( path ); + + // keeping a copy of original buffer to allow line retrieval + if( loadNodeFileInfo ) + { + std::ifstream t( path ); + std::stringstream buffer; + buffer << t.rdbuf(); + + m_originalBuffers.clear(); + m_originalBuffers[m_rootFilePath] = string( buffer.str() ); + + addNodeFileInfo( getFirstChild(), getAbsolutePath( m_rootFilePath ) ); + } + + return result; +} + +xmlNode xmlDocument::appendChild( string const & name ) +{ return pugiDocument.append_child( name.c_str() ); } + +xmlNode xmlDocument::appendChild( xmlNodeType type ) +{ return pugiDocument.append_child( type ); } + +bool xmlDocument::saveFile( string const & path ) const +{ return pugiDocument.save_file( path.c_str() ); } + +string const & xmlDocument::getFilePath() const +{ return m_rootFilePath; } + +xmlNode xmlDocument::getFirstChild() const +{ return pugiDocument.first_child(); } + +xmlNode xmlDocument::getChild( string const & name ) const +{ return pugiDocument.child( name.c_str() ); } + +string const & xmlDocument::getOriginalBuffer() const +{ return m_originalBuffers.find( m_rootFilePath )->second; } + +string const * xmlDocument::getOriginalBuffer( string const & filePath ) const +{ + map< string, string >::const_iterator it = m_originalBuffers.find( filePath ); + return it != m_originalBuffers.cend() ? &it->second : nullptr; +} + +map< string, string > const & xmlDocument::getOriginalBuffers() const +{ return m_originalBuffers; } + +xmlNodePos xmlDocument::getNodePosition( xmlNode const & node ) const +{ + size_t line = npos; + size_t offsetInLine = npos; + size_t offset = npos; + xmlAttribute filePathAtt = node.attribute( filePathString ); + xmlAttribute charOffsetAtt = node.attribute( charOffsetString ); + string filePath; + + if( filePathAtt && charOffsetAtt ) + { + filePath = string( filePathAtt.value() ); + offset = std::atoi( charOffsetAtt.value() ); + + auto sourceBuffer = m_originalBuffers.find( filePath ); + string nodeName = node.name(); + + if( sourceBuffer!=m_originalBuffers.end() && + offset > 0 && offset + nodeName.size() < sourceBuffer->second.size() && + sourceBuffer->second.substr( offset, nodeName.size() ) == nodeName ) + { + line = std::count( sourceBuffer->second.begin(), sourceBuffer->second.begin() + offset, '\n' ) + 1; + offsetInLine = offset - sourceBuffer->second.rfind( '\n', offset ); + } + else + { + std::ostringstream oss; + oss << "Node from file=" << filePath << ", path=" << node.path() << ", offset=" << offset; + oss << " could not be found: "; + oss << ( sourceBuffer == m_originalBuffers.end() ? + ( filePathAtt.empty() ? "Source file not found." : "Source file info not loaded") : + ( offset > 0 && offset + nodeName.size() < sourceBuffer->second.size() ? + "The offset doesn't lead to this node." : + "Offset out of bounds." ) + ); + oss << "\nSource files list:"; + for( auto const & buffer : m_originalBuffers ) + oss << "\n " << buffer.first << " buffer size = " << std::to_string( buffer.second.size() ); + + throw InputError( oss.str() ); + } + } + + return xmlNodePos( *this, filePath, line, offsetInLine, offset ); +} + +xmlNodePos::xmlNodePos( xmlDocument const & document_, string const & filePath_, size_t line_, + size_t offsetInLine_, size_t offset_ ): + xmlAttributePos( filePath_, line_, offsetInLine_, offset_ ), + document( document_ ) +{} + +bool xmlNodePos::isFound() const +{ return line != xmlDocument::npos; } + +size_t findTagEnd( string const & xmlBuffer, size_t const offset ) +{ + bool outOfQuotes = true; + auto bufferEnd = xmlBuffer.cend(); + for( auto it = xmlBuffer.cbegin() + offset; it != bufferEnd; ++it ) + { + if( *it == '"' && *(it - 1) != '\\' ) + { + outOfQuotes = !outOfQuotes; + } + else if( outOfQuotes && *it == '>' ) + { + return it - xmlBuffer.cbegin(); + } + } + return string::npos; +} +size_t findAttribute( string const & attName, string const & xmlBuffer, size_t const tagBegin, size_t const tagEnd ) +{ + if( !attName.empty()) + { + size_t searchStart = tagBegin; + try + { + std::smatch m; + // As pugixml doesn't expose a way to get the attribute line or character offset, a regex + // seem like a suficient solution for GEOS XMLs. + // The regex search for the attribute name followed by an '=', eventually separated by spaces + if( std::regex_search( xmlBuffer.cbegin() + searchStart, xmlBuffer.cbegin() + tagEnd, + m, std::regex( attName + "\\s*=\\s*\"" ))) + { + size_t candidatePos = m.position() + searchStart; + string previousString = xmlBuffer.substr( tagBegin, candidatePos - tagBegin ); + // We must be out of value surrounding quotes: the number of previous quotes '"' should be even + // (ignoring the inner quotes preceded by '\\') + size_t surroundingQuotesCount = 0; + size_t quotePos = 0; + while((quotePos = previousString.find( '"', quotePos + 1 )) != string::npos ) + { + if( previousString[quotePos - 1] != '\\' ) + ++surroundingQuotesCount; + } + + if(((surroundingQuotesCount % 1) == 0)) + { + return candidatePos; + } + searchStart = candidatePos + attName.size(); + } + } + catch( std::regex_error const & ) + {} + } + return xmlDocument::npos; +} + +xmlAttributePos xmlNodePos::getAttributeLine( string const & attName ) const +{ + string const * buffer = document.getOriginalBuffer( filePath ); + size_t attOffset = offset; + size_t attLine = line; + size_t attOffsetInLine = offsetInLine; + + if( isFound() && buffer != nullptr && offset < buffer->size() ) + { + size_t tagEnd = findTagEnd( *buffer, offset ); + if( tagEnd != string::npos ) + { + attOffset = findAttribute( attName, *buffer, offset, tagEnd ); + if( attOffset != string::npos ) + { + attLine = line + std::count( buffer->cbegin() + offset, buffer->cbegin() + attOffset, '\n' ); + attOffsetInLine = attOffset - buffer->rfind( '\n', attOffset ); + } + } + } + + return xmlAttributePos( filePath, attLine, attOffsetInLine, attOffset ); +} + +xmlAttributePos::xmlAttributePos( string const & filePath_, size_t line_, size_t offsetInLine_, + size_t offset_ ): + filePath( filePath_ ), + line( line_ ), + offsetInLine( offsetInLine_ ), + offset( offset_ ) +{} + +bool xmlAttributePos::isFound() const +{ return offset != xmlDocument::npos; } + +string xmlAttributePos::toString() const +{ + if( line != xmlDocument::npos ) + { + return splitPath( filePath ).second + ", l." + std::to_string( line ); + } + else if( offset != xmlDocument::npos ) + { + // line hasn't been found, we output the character offset. + return splitPath( filePath ).second + ", offset " + std::to_string( offset ); + } + else + { + // offset hasn't been found, filename is probably wrong too, we just output an error. + return "Source file not found"; + } +} + } /* namespace xmlWrapper */ } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/xmlWrapper.hpp b/src/coreComponents/dataRepository/xmlWrapper.hpp index ef4b525b627..4396dae97fc 100644 --- a/src/coreComponents/dataRepository/xmlWrapper.hpp +++ b/src/coreComponents/dataRepository/xmlWrapper.hpp @@ -46,20 +46,225 @@ namespace geos */ namespace xmlWrapper { -/// Alias for the type of xml document. -using xmlDocument = pugi::xml_document; /// Alias for the type of the result from an xml parse attempt. using xmlResult = pugi::xml_parse_result; /// Alias for the type of an xml node. +/// An xmlNode behave as a pointer object: passing it by value to a function which modify it +/// will modify the original xmlNode object. using xmlNode = pugi::xml_node; /// Alias for the type of an xml attribute. +/// An xmlAttribute behave as a pointer object: passing it by value to a function which modify it +/// will modify the original xmlAttribute object. using xmlAttribute = pugi::xml_attribute; /// Alias for the type variant of an xml node. -using xmlTypes = pugi::xml_node_type; +using xmlNodeType = pugi::xml_node_type; + +class xmlDocument; + +/** + * @struct xmlAttributePos + * + * Stores the source file path, and position in the file of a xml attribute. + */ +struct xmlAttributePos +{ + /// Path of the file containing this element + string filePath; + /// Line where the element is defined. Start at 1. + /// Default value is xmlDocument::npos + size_t const line; + /// Character offset of this element in the line that contains it (starting from 1) + /// Equals to xmlDocument::npos if it couldn't be determined. + size_t const offsetInLine; + /// Character offset of this element in the file that contains it (starting from 0) + /// Equals to xmlDocument::npos if it couldn't be determined. + size_t const offset; + + /** + * @brief Constructor of this struct. + * @param filePath the path of the original xml file containing this attribute + * @param line Line where the element is defined. Start at 1. + * @param offsetInLine Character offset of this element in the line that contains it (starting from 1) + * @param offset Character offset of this element in the file that contains it (starting from 0) + */ + xmlAttributePos( string const & filePath, size_t line, size_t offsetInLine, size_t offset ); + /** + * @return false if the position is undefined. + */ + bool isFound() const; + /** + * @return a string containing the file name and position in the file. + */ + string toString() const; +}; + +/** + * @struct xmlNodePos + * + * Used to retrieve the position of dataRepository::Wrapper in XML + */ +struct xmlNodePos : xmlAttributePos +{ + /// Reference of the main xmlDocument that contains all original file buffers. + xmlDocument const & document; + + /** + * @brief Constructor of this struct. + * @param document an xml document containing this node, or including a file which includes it + * @param filePath the path of the original xml file containing this node + * @param line Line where the node is defined. Start at 1. + * @param offsetInLine Character offset of this node in the line that contains it (starting from 1) + * @param offset Character offset of this node in the file that contains it (starting from 0) + */ + xmlNodePos( xmlDocument const & document, string const & filePath, size_t line, size_t offsetInLine, size_t offset ); + /** + * @return false if the position is undefined. + */ + bool isFound() const; + /** + * @brief Compute the xmlAttributePos of an xml attribute + * @param attName the name of the attribute to locate + * @return an xmlAttributePos object that represents the position of the target node. + */ + xmlAttributePos getAttributeLine( string const & attName ) const; +}; + +/** + * @class xmlDocument + * + * Wrapper class for the type of xml document. + * This class exists to intercept file / string loading methods, and to keep the loaded buffers, + * in order to retrieve the source file and line of nodes and attributes. + */ +class xmlDocument +{ +public: + /// Error value for when an offset / line position is undefined. + static constexpr size_t npos = string::npos; + + /** + * @brief Construct an empty xmlDocument that waits to load something. + */ + xmlDocument(); + /** + * @brief non-copyable + */ + xmlDocument( const xmlDocument & ) = delete; + /** + * @brief move constructor + */ + xmlDocument( xmlDocument && ) = default; + + /** + * @return the first child of this document (typically in GEOS, the Problem node) + */ + xmlNode getFirstChild() const; + /** + * @return a child with the specified name + * @param name the tag name of the node to find + */ + xmlNode getChild( string const & name ) const; + /** + * @return the original file buffer loaded during the last load_X() call on this object. + */ + string const & getOriginalBuffer() const; + /** + * @return If the specified file at the "filePath" is the loaded document of the instance, + * or one of its includes, returns its original buffer as a string. + * Returns nullptr if filePath is not a loaded and available document. + * @param filePath the path of the file which buffer must be returned. + */ + string const * getOriginalBuffer( string const & filePath ) const; + /** + * @return a map containing the original buffers of the document and its includes, indexed by file path. + */ + map< string, string > const & getOriginalBuffers() const; + /** + * @return If loadFile() has been loaded, returns the path of the source file. + * If another load method has been called, it returns a generated unique value. + */ + string const & getFilePath() const; + /** + * @brief If the node file information are loaded, compute the position of a node. + * @param node the node to locate + * @return an xmlNodePos object that represents the position of the target node. + * @throws an InputError if the node position is not found and source file is loaded. + */ + xmlNodePos getNodePosition( xmlWrapper::xmlNode const & node ) const; + + /** + * @brief Load document from zero-terminated string. No encoding conversions are applied. + * Free any previously loaded xml tree. + * Wrapper of pugi::xml_document::loadBuffer() method. + * @param content the string containing the document content + * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. + * @return an xmlResult object representing the parsing resulting status. + */ + xmlResult loadString( string_view content, bool loadNodeFileInfo = false ); + + /** + * @brief Load document from file. Free any previously loaded xml tree. + * Wrapper of pugi::xml_document::loadBuffer() method. + * @param path the path of an xml file to load. + * @param loadNodeFileInfo Load the node source file info, allowing getNodePosition() to work. + * @return an xmlResult object representing the parsing resulting status. + */ + xmlResult loadFile( string const & path, bool loadNodeFileInfo = false ); + + /** + * @brief Add a root element to the document + * @param name the tag name of the node to add + * @return the added node + */ + xmlNode appendChild( string const & name ); + /** + * @brief Add a root element to the document + * @param type the type of the node to add to the root of the document. + * As an exemple, node_declaration is useful to add the "" node. + * @return the added node + */ + xmlNode appendChild( xmlNodeType type = xmlNodeType::node_element ); + + /** + * @brief Save the XML to a file + * @param path the file path + * @return true if the file has successfuly been saved + * @return false otherwise + */ + bool saveFile( string const & path ) const; + + /** + * @brief Function to add xml nodes from included files. + * @param targetNode the node for which to look for included children specifications + * @param level include tree level used to detect circular includes + * + * This function looks for a "Included" node under the targetNode, loops over all subnodes under the "Included" + * node, and then parses the file specified in those subnodes taking all the nodes in the file and adding them to + * the targetNode. + * Each found includes are added to xmlDocument::m_originalBuffers. + */ + void addIncludedXML( xmlNode & targetNode, int level = 0 ); + + /** + * @return True if loadNodeFileInfo was true during the last load_X call. + */ + bool hasNodeFileInfo() const; + +private: + /// original xml_document object that this class aims to wrap + pugi::xml_document pugiDocument; + + /// Used to retrieve node positions as pugixml buffer is private and processed. + map< string, string > m_originalBuffers; + /// @see getFilePath() + string m_rootFilePath; + /// @see hasNodeFileInfo() + bool m_hasNodeFileInfo; +}; /** * @brief constexpr variable to hold name for inserting the file path into the xml file. @@ -69,28 +274,25 @@ using xmlTypes = pugi::xml_node_type; */ constexpr char const filePathString[] = "__filePath__"; +/** + * @brief constexpr variable to hold node character offset from the start of the xml file. + * + * This is used because we would like the option to hold the offset in the xml structure. + * The name is uglified with underscores to avoid collisions with real attribute names. + */ +constexpr char const charOffsetString[] = "__charOffset__"; + /// XML tag name for included sections constexpr char const includedListTag[] = "Included"; /// XML tag name for included files constexpr char const includedFileTag[] = "File"; -/** - * @brief Function to add xml nodes from included files. - * @param targetNode the node for which to look for included children specifications - * @param level include tree level used to detect circular includes - * - * This function looks for a "Included" node under the targetNode, loops over all subnodes under the "Included" - * node, and then parses the file specified in those subnodes taking all the nodes in the file and adding them to - * the targetNode. - */ -void addIncludedXML( xmlNode & targetNode, int level = 0 ); - /** * @brief Function to handle multiple input xml files. * @param inputFileList the list of input xml files * @param outputDir the output directory to place the composite input file in - * @return inputFileName the input xml file name + * @return inputFilePath the input xml file name * * This function checks for multiple xml files, and will build * a new input xml file with an included block if neccesary @@ -98,6 +300,12 @@ void addIncludedXML( xmlNode & targetNode, int level = 0 ); string buildMultipleInputXML( string_array const & inputFileList, string const & outputDir = {} ); +/** + * @return true if the attribute with the specified name declares metadata relative to the xml + * @param name the name of an attribute + */ +bool isFileMetadataAttribute( string const & name ); + /** * @name String to variable parsing. * diff --git a/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp b/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp index 521eff2dc5e..75a59bf3edb 100644 --- a/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp +++ b/src/coreComponents/fieldSpecification/FieldSpecificationBase.cpp @@ -101,9 +101,10 @@ void FieldSpecificationBase::setMeshObjectPath( Group const & meshBodies ) { m_meshObjectPaths = std::make_unique< MeshObjectPath >( m_objectPath, meshBodies ); } - catch( InputError const & e ) + catch( std::exception const & e ) { - throw InputError( e, getName() + " has a wrong objectPath: " + m_objectPath + "\n" ); + throw InputError( e, getWrapperDataContext( viewKeyStruct::objectPathString() ).toString() + + " is a wrong objectPath: " + m_objectPath + "\n" ); } } diff --git a/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp b/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp index 19d31dc6ed1..b0fed66e8eb 100644 --- a/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp +++ b/src/coreComponents/fileIO/timeHistory/HistoryCollectionBase.cpp @@ -120,9 +120,10 @@ dataRepository::Group const * HistoryCollectionBase::getTargetObject( DomainPart } } ); - GEOS_ERROR_IF( !bodyFound, + GEOS_THROW_IF( !bodyFound, GEOS_FMT( "MeshBody ({}) is specified, but not found.", - targetTokens[0] ) ); + targetTokens[0] ), + std::domain_error ); } string const meshBodyName = targetTokens[0]; @@ -149,9 +150,10 @@ dataRepository::Group const * HistoryCollectionBase::getTargetObject( DomainPart } } ); - GEOS_ERROR_IF( !levelFound, + GEOS_THROW_IF( !levelFound, GEOS_FMT( "MeshLevel ({}) is specified, but not found.", - targetTokens[1] ) ); + targetTokens[1] ), + std::domain_error ); } } else if( !meshBody.getMeshLevels().hasGroup< MeshLevel >( targetTokens[1] ) ) @@ -201,9 +203,9 @@ dataRepository::Group const * HistoryCollectionBase::getTargetObject( DomainPart return targetGroup; } } - catch( std::domain_error const & e ) + catch( std::exception const & e ) { - throw InputError( e, getName() + " has a wrong objectPath: " + objectPath + "\n" ); + throw InputError( e, getDataContext().toString() + " has a wrong objectPath: " + objectPath + "\n" ); } } diff --git a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp index ff602df971d..a98d2391735 100644 --- a/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKPVDWriter.cpp @@ -24,11 +24,11 @@ VTKPVDWriter::VTKPVDWriter( string fileName ): m_fileName( std::move( fileName ) ) { // Declaration of XML version - auto declarationNode = m_pvdFile.append_child( pugi::node_declaration ); + auto declarationNode = m_pvdFile.appendChild( pugi::node_declaration ); declarationNode.append_attribute( "version" ) = "1.0"; // Declaration of the node VTKFile - auto vtkFileNode = m_pvdFile.append_child( "VTKFile" ); + auto vtkFileNode = m_pvdFile.appendChild( "VTKFile" ); vtkFileNode.append_attribute( "type" ) = "Collection"; vtkFileNode.append_attribute( "version" ) = "0.1"; @@ -42,12 +42,12 @@ void VTKPVDWriter::setFileName( string fileName ) void VTKPVDWriter::save() const { - m_pvdFile.save_file( m_fileName.c_str() ); + m_pvdFile.saveFile( m_fileName ); } void VTKPVDWriter::addData( real64 time, string const & filePath ) const { - auto collectionNode = m_pvdFile.child( "VTKFile" ).child( "Collection" ); + auto collectionNode = m_pvdFile.getChild( "VTKFile" ).child( "Collection" ); auto dataSetNode = collectionNode.append_child( "DataSet" ); dataSetNode.append_attribute( "timestep" ) = time; dataSetNode.append_attribute( "file" ) = filePath.c_str(); @@ -55,7 +55,7 @@ void VTKPVDWriter::addData( real64 time, string const & filePath ) const void VTKPVDWriter::reinitData() const { - auto collectionNode = m_pvdFile.child( "VTKFile" ).child( "Collection" ); + auto collectionNode = m_pvdFile.getChild( "VTKFile" ).child( "Collection" ); while( collectionNode.remove_child( "DataSet" ) ) {} diff --git a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp index 48bebc54232..b37ad3d79e1 100644 --- a/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp +++ b/src/coreComponents/fileIO/vtk/VTKVTMWriter.cpp @@ -26,11 +26,11 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) : m_filePath( std::move( filePath ) ) { // Declaration of XML version - auto declarationNode = m_document.append_child( pugi::node_declaration ); + auto declarationNode = m_document.appendChild( xmlWrapper::xmlNodeType::node_declaration ); declarationNode.append_attribute( "version" ) = "1.0"; // Declaration of the node VTKFile - auto vtkFileNode = m_document.append_child( "VTKFile" ); + auto vtkFileNode = m_document.appendChild( "VTKFile" ); vtkFileNode.append_attribute( "type" ) = "vtkMultiBlockDataSet"; vtkFileNode.append_attribute( "version" ) = "1.0"; @@ -39,7 +39,7 @@ VTKVTMWriter::VTKVTMWriter( string filePath ) void VTKVTMWriter::write() const { - m_document.save_file( m_filePath.c_str() ); + m_document.saveFile( m_filePath ); } void VTKVTMWriter::addDataSet( std::vector< string > const & blockPath, diff --git a/src/coreComponents/mainInterface/ProblemManager.cpp b/src/coreComponents/mainInterface/ProblemManager.cpp index fb32f776437..45587ea34c3 100644 --- a/src/coreComponents/mainInterface/ProblemManager.cpp +++ b/src/coreComponents/mainInterface/ProblemManager.cpp @@ -385,13 +385,10 @@ void ProblemManager::parseInputFile() // Load preprocessed xml file xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult const xmlResult = xmlDocument.load_file( inputFileName.c_str() ); + xmlWrapper::xmlResult const xmlResult = xmlDocument.loadFile( inputFileName, true ); GEOS_THROW_IF( !xmlResult, GEOS_FMT( "Errors found while parsing XML file {}\nDescription: {}\nOffset: {}", inputFileName, xmlResult.description(), xmlResult.offset ), InputError ); - // Add path information to the file - xmlDocument.append_child( xmlWrapper::filePathString ).append_attribute( xmlWrapper::filePathString ).set_value( inputFileName.c_str() ); - // Parse the results parseXMLDocument( xmlDocument ); } @@ -401,7 +398,7 @@ void ProblemManager::parseInputString( string const & xmlString ) { // Load preprocessed xml file xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlString.c_str(), xmlString.length() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( xmlString, true ); GEOS_THROW_IF( !xmlResult, GEOS_FMT( "Errors found while parsing XML string\nDescription: {}\nOffset: {}", xmlResult.description(), xmlResult.offset ), InputError ); @@ -410,18 +407,18 @@ void ProblemManager::parseInputString( string const & xmlString ) } -void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument const & xmlDocument ) +void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ) { // Extract the problem node and begin processing the user inputs - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( this->getName().c_str() ); - processInputFileRecursive( xmlProblemNode ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( this->getName().c_str() ); + processInputFileRecursive( xmlDocument, xmlProblemNode ); // The objects in domain are handled separately for now { DomainPartition & domain = getDomainPartition(); ConstitutiveManager & constitutiveManager = domain.getGroup< ConstitutiveManager >( groupKeys.constitutiveManager ); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); - constitutiveManager.processInputFileRecursive( topLevelNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); // Open mesh levels MeshManager & meshManager = this->getGroup< MeshManager >( groupKeys.meshManager ); @@ -441,7 +438,7 @@ void ProblemManager::parseXMLDocument( xmlWrapper::xmlDocument const & xmlDocume { ElementRegionManager & elementManager = meshLevel.getElemManager(); Group * newRegion = elementManager.createChild( regionNode.name(), regionName ); - newRegion->processInputFileRecursive( regionNode ); + newRegion->processInputFileRecursive( xmlDocument, regionNode ); } ); } } diff --git a/src/coreComponents/mainInterface/ProblemManager.hpp b/src/coreComponents/mainInterface/ProblemManager.hpp index f1178d6538f..627b43219e0 100644 --- a/src/coreComponents/mainInterface/ProblemManager.hpp +++ b/src/coreComponents/mainInterface/ProblemManager.hpp @@ -41,7 +41,7 @@ class CellBlockManagerABC; /** * @class ProblemManager - * @brief This is the class handling the operation flow of the problem being ran in GEOSX + * @brief This is the class handling the operation flow of the problem being ran in GEOS */ class ProblemManager : public dataRepository::Group { @@ -119,10 +119,11 @@ class ProblemManager : public dataRepository::Group void parseInputString( string const & xmlString ); /** - * @brief Parses the input xml document + * @brief Parses the input xml document. Also add the includes content to the xmlDocument when + * `Include` nodes are encountered. * @param xmlDocument The parsed xml document handle */ - void parseXMLDocument( xmlWrapper::xmlDocument const & xmlDocument ); + void parseXMLDocument( xmlWrapper::xmlDocument & xmlDocument ); /** * @brief Generates numerical meshes used throughout the code diff --git a/src/coreComponents/mesh/ElementRegionBase.cpp b/src/coreComponents/mesh/ElementRegionBase.cpp index c368faade86..707b5470a6a 100644 --- a/src/coreComponents/mesh/ElementRegionBase.cpp +++ b/src/coreComponents/mesh/ElementRegionBase.cpp @@ -62,9 +62,10 @@ string ElementRegionBase::verifyMeshBodyName( Group const & meshBodies, { meshBodyName = onlyMeshBodyName; } - GEOS_ERROR_IF_NE_MSG( onlyMeshBodyName, + GEOS_THROW_IF_NE_MSG( onlyMeshBodyName, meshBodyName, - "MeshBody specified does not match MeshBody in hierarchy." ); + "MeshBody specified does not match MeshBody in hierarchy.", + InputError ); } else { @@ -76,9 +77,10 @@ string ElementRegionBase::verifyMeshBodyName( Group const & meshBodies, meshBodyFound = true; } } ); - GEOS_ERROR_IF( !meshBodyFound, + GEOS_THROW_IF( !meshBodyFound, "There are multiple MeshBodies in this problem, but the " - "specified MeshBody name "<"; xmlWrapper::xmlDocument schemaTree; - schemaTree.load_string( schemaBase.c_str()); - xmlWrapper::xmlNode schemaRoot = schemaTree.child( "xsd:schema" ); + schemaTree.loadString( schemaBase ); + xmlWrapper::xmlNode schemaRoot = schemaTree.getChild( "xsd:schema" ); // Build the simple schema types GEOS_LOG_RANK_0( " Basic datatypes" ); @@ -65,7 +65,7 @@ void ConvertDocumentationToSchema( string const & fname, // Write the schema to file GEOS_LOG_RANK_0( " Saving file" ); - schemaTree.save_file( fname.c_str()); + schemaTree.saveFile( fname ); GEOS_LOG_RANK_0( " Done!" ); } @@ -233,7 +233,7 @@ void SchemaConstruction( Group & group, commentString += " => " + stringutilities::join( registrars.begin(), registrars.end(), ", " ); } - xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlTypes::node_comment ); + xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlNodeType::node_comment ); commentNode.set_value( commentString.c_str()); @@ -289,7 +289,7 @@ void SchemaConstruction( Group & group, // Only add this attribute if not present if( targetTypeDefNode.find_child_by_attribute( "xsd:attribute", "name", "name" ).empty()) { - xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlTypes::node_comment ); + xmlWrapper::xmlNode commentNode = targetTypeDefNode.append_child( xmlWrapper::xmlNodeType::node_comment ); commentNode.set_value( "name => A name is required for any non-unique nodes" ); xmlWrapper::xmlNode attributeNode = targetTypeDefNode.append_child( "xsd:attribute" ); diff --git a/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp b/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp index 153afd688a9..972449c8b96 100644 --- a/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp +++ b/src/coreComponents/unitTests/constitutiveTests/testDamage.cpp @@ -46,8 +46,7 @@ TEST( DamageTests, testDamageSpectral ) ""; xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), - inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -55,8 +54,8 @@ TEST( DamageTests, testDamageSpectral ) GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.child( "Constitutive" ); - constitutiveManager.processInputFileRecursive( xmlConstitutiveNode ); + xmlWrapper::xmlNode xmlConstitutiveNode = xmlDocument.getChild( "Constitutive" ); + constitutiveManager.processInputFileRecursive( xmlDocument, xmlConstitutiveNode ); constitutiveManager.postProcessInputRecursive(); localIndex constexpr numElem = 2; diff --git a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp index 70851514fd4..71db7a13ddd 100644 --- a/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp +++ b/src/coreComponents/unitTests/dataRepositoryTests/testGroupPath.cpp @@ -106,14 +106,52 @@ TEST( testGroupPath, testGlobalPaths ) catch( const std::domain_error & e ) { static constexpr auto expectedMsg = "***** Controlling expression (should be false): child == nullptr\n" - "***** Rank 0: Group /Mesh has no child named mesh2\n" + "***** Rank 0: Group Mesh (CodeIncludedXML0, l.10) has no child named mesh2\n" "The children of Mesh are: { mesh1 }"; // checks if the exception contains the expected message - ASSERT_TRUE( string( e.what() ).find( expectedMsg ) != string::npos ); + GEOS_ERROR_IF_EQ_MSG( string( e.what() ).find( expectedMsg ), string::npos, + "The error message was not containing the expected sequence.\n" << + " Error message :\n" << e.what() << + " expected sequence :\n" << expectedMsg ); trowHappened = true; } // checks if the exception has been thrown as expected ASSERT_TRUE( trowHappened ); + + auto const testGroupContextString = [&]( string const & groupPath, string const & ctxString ) + { + Group const * const groupToTest = &problem.getGroupByPath( groupPath ); + ASSERT_NE( groupToTest, nullptr ); + ASSERT_STREQ( groupToTest->getDataContext().toString().c_str(), + ctxString.c_str() ); + }; + auto const testWrapperContextString = [&]( string const & groupPath, string const & wrapperName, + string const & ctxString ) + { + Group const * const containingGroup = &problem.getGroupByPath( groupPath ); + ASSERT_NE( containingGroup, nullptr ); + WrapperBase const * const wrapperToTest = &containingGroup->getWrapperBase( wrapperName ); + ASSERT_NE( wrapperToTest, nullptr ); + ASSERT_STREQ( wrapperToTest->getDataContext().toString().c_str(), + ctxString.c_str() ); + }; + + // check if the DataContext string of a Group and a Wrapper declared in the XML is formatted as expected + testGroupContextString( "/Mesh/mesh1", + "mesh1 (CodeIncludedXML0, l.11)" ); + testWrapperContextString( "/Mesh/mesh1", "xCoords", + "mesh1/xCoords (CodeIncludedXML0, l.14)" ); + + // check if the DataContext string of implicitly created Groups are formatted as expected + testGroupContextString( "/Solvers/lagsolve/NonlinearSolverParameters", + "/Solvers/lagsolve(CodeIncludedXML0,l.4)/NonlinearSolverParameters" ); + testGroupContextString( "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2/elementSubRegions", + "/domain/MeshBodies/mesh1/meshLevels/Level0/ElementRegions/elementRegionsGroup/Region2(CodeIncludedXML0,l.37)/elementSubRegions" ); + // check if the DataContext string of implicitly created Wrappers are formatted as expected + testWrapperContextString( "/Mesh/mesh1", "positionTolerance", + "mesh1/positionTolerance (CodeIncludedXML0, l.11)" ); + testWrapperContextString( "/Solvers/lagsolve/NonlinearSolverParameters", "newtonMaxIter", + "/Solvers/lagsolve(CodeIncludedXML0,l.4)/NonlinearSolverParameters/newtonMaxIter" ); } int main( int argc, char * * argv ) diff --git a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp index ef73cc18453..eebfa0a114c 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testCompFlowUtils.hpp @@ -183,7 +183,7 @@ void fillNumericalJacobian( arrayView1d< real64 const > const & residual, void setupProblemFromXML( ProblemManager & problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( xmlInput ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -199,21 +199,21 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm commandLine.registerWrapper< integer >( problemManager.viewKeys.xPartitionsOverride.key() ). setApplyDefaultValue( mpiSize ); - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); - problemManager.processInputFileRecursive( xmlProblemNode ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); + problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); DomainPartition & domain = problemManager.getDomainPartition(); constitutive::ConstitutiveManager & constitutiveManager = domain.getConstitutiveManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); - constitutiveManager.processInputFileRecursive( topLevelNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); MeshManager & meshManager = problemManager.getGroup< MeshManager >( problemManager.groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); topLevelNode = xmlProblemNode.child( elementManager.getName().c_str()); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); problemManager.problemSetup(); problemManager.applyInitialConditions(); diff --git a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp index 3343059c625..2d9a1ee6ea1 100644 --- a/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp +++ b/src/coreComponents/unitTests/fluidFlowTests/testSingleFlowUtils.hpp @@ -75,7 +75,7 @@ void fillNumericalJacobian( arrayView1d< real64 const > const & residual, void setupProblemFromXML( ProblemManager & problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( xmlInput ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -91,21 +91,21 @@ void setupProblemFromXML( ProblemManager & problemManager, char const * const xm commandLine.registerWrapper< integer >( problemManager.viewKeys.xPartitionsOverride.key() ). setApplyDefaultValue( mpiSize ); - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); - problemManager.processInputFileRecursive( xmlProblemNode ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); + problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); DomainPartition & domain = problemManager.getDomainPartition(); constitutive::ConstitutiveManager & constitutiveManager = domain.getConstitutiveManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( constitutiveManager.getName().c_str()); - constitutiveManager.processInputFileRecursive( topLevelNode ); + constitutiveManager.processInputFileRecursive( xmlDocument, topLevelNode ); MeshManager & meshManager = problemManager.getGroup< MeshManager >( problemManager.groupKeys.meshManager ); meshManager.generateMeshLevels( domain ); ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); topLevelNode = xmlProblemNode.child( elementManager.getName().c_str()); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); problemManager.problemSetup(); problemManager.applyInitialConditions(); diff --git a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp index 2f697f8a324..e63c90f7631 100644 --- a/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp +++ b/src/coreComponents/unitTests/linearAlgebraTests/testDofManagerUtils.hpp @@ -38,7 +38,7 @@ namespace testing void setupProblemFromXML( ProblemManager * const problemManager, char const * const xmlInput ) { xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( xmlInput, strlen( xmlInput ) ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( xmlInput ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); @@ -52,8 +52,8 @@ void setupProblemFromXML( ProblemManager * const problemManager, char const * co commandLine.registerWrapper< integer >( problemManager->viewKeys.xPartitionsOverride.key() ). setApplyDefaultValue( mpiSize ); - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); - problemManager->processInputFileRecursive( xmlProblemNode ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); + problemManager->processInputFileRecursive( xmlDocument, xmlProblemNode ); // Open mesh levels DomainPartition & domain = problemManager->getDomainPartition(); @@ -62,7 +62,7 @@ void setupProblemFromXML( ProblemManager * const problemManager, char const * co ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager->problemSetup(); diff --git a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp index 84c559e49a8..ab91b136ee2 100644 --- a/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp +++ b/src/coreComponents/unitTests/meshTests/testMeshGeneration.cpp @@ -99,12 +99,12 @@ class MeshGenerationTest : public ::testing::Test maxCoordInX, maxCoordInY, maxCoordInZ, numElemsInX, numElemsInY, numElemsInZ ); xmlWrapper::xmlDocument xmlDocument; - xmlWrapper::xmlResult xmlResult = xmlDocument.load_buffer( inputStream.c_str(), inputStream.size() ); + xmlWrapper::xmlResult xmlResult = xmlDocument.loadString( inputStream ); ASSERT_TRUE( xmlResult ); - xmlWrapper::xmlNode xmlProblemNode = xmlDocument.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = xmlDocument.getChild( dataRepository::keys::ProblemManager ); ProblemManager & problemManager = getGlobalState().getProblemManager(); - problemManager.processInputFileRecursive( xmlProblemNode ); + problemManager.processInputFileRecursive( xmlDocument, xmlProblemNode ); // Open mesh levels DomainPartition & domain = problemManager.getDomainPartition(); @@ -113,7 +113,7 @@ class MeshGenerationTest : public ::testing::Test ElementRegionManager & elementManager = domain.getMeshBody( 0 ).getBaseDiscretization().getElemManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( xmlDocument, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/meshTests/testVTKImport.cpp b/src/coreComponents/unitTests/meshTests/testVTKImport.cpp index 5a0de657cca..ebf8173b8bc 100644 --- a/src/coreComponents/unitTests/meshTests/testVTKImport.cpp +++ b/src/coreComponents/unitTests/meshTests/testVTKImport.cpp @@ -38,14 +38,14 @@ void TestMeshImport( string const & meshFilePath, V const & validate ) { string const meshNode = GEOS_FMT( R"()", meshFilePath ); xmlWrapper::xmlDocument xmlDocument; - xmlDocument.load_buffer( meshNode.c_str(), meshNode.size() ); - xmlWrapper::xmlNode xmlMeshNode = xmlDocument.child( "Mesh" ); + xmlDocument.loadString( meshNode ); + xmlWrapper::xmlNode xmlMeshNode = xmlDocument.getChild( "Mesh" ); conduit::Node node; Group root( "root", node ); MeshManager meshManager( "mesh", &root ); - meshManager.processInputFileRecursive( xmlMeshNode ); + meshManager.processInputFileRecursive( xmlDocument, xmlMeshNode ); meshManager.postProcessInputRecursive(); DomainPartition domain( "domain", &root ); meshManager.generateMeshes( domain ); diff --git a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp index ce89cfb1b1c..1120fd74bec 100644 --- a/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp +++ b/src/coreComponents/unitTests/virtualElementTests/testConformingVirtualElementOrder1.cpp @@ -280,19 +280,19 @@ TEST( ConformingVirtualElementOrder1, hexahedra ) ""; xmlWrapper::xmlDocument inputFile; - xmlWrapper::xmlResult xmlResult = inputFile.load_buffer( inputStream.c_str(), inputStream.size()); + xmlWrapper::xmlResult xmlResult = inputFile.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); GEOS_LOG_RANK_0( "Error description: " << xmlResult.description()); GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlProblemNode = inputFile.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = inputFile.getChild( dataRepository::keys::ProblemManager ); GeosxState state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ); ProblemManager & problemManager = state.getProblemManager(); - problemManager.processInputFileRecursive( xmlProblemNode ); + problemManager.processInputFileRecursive( inputFile, xmlProblemNode ); // Open mesh levels DomainPartition & domain = problemManager.getDomainPartition(); @@ -302,7 +302,7 @@ TEST( ConformingVirtualElementOrder1, hexahedra ) MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elementManager = mesh.getElemManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( inputFile, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); @@ -333,19 +333,19 @@ TEST( ConformingVirtualElementOrder1, wedges ) " " ""; xmlWrapper::xmlDocument inputFile; - xmlWrapper::xmlResult xmlResult = inputFile.load_buffer( inputStream.c_str(), inputStream.size()); + xmlWrapper::xmlResult xmlResult = inputFile.loadString( inputStream ); if( !xmlResult ) { GEOS_LOG_RANK_0( "XML parsed with errors!" ); GEOS_LOG_RANK_0( "Error description: " << xmlResult.description()); GEOS_LOG_RANK_0( "Error offset: " << xmlResult.offset ); } - xmlWrapper::xmlNode xmlProblemNode = inputFile.child( dataRepository::keys::ProblemManager ); + xmlWrapper::xmlNode xmlProblemNode = inputFile.getChild( dataRepository::keys::ProblemManager ); GeosxState state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ); ProblemManager & problemManager = state.getProblemManager(); - problemManager.processInputFileRecursive( xmlProblemNode ); + problemManager.processInputFileRecursive( inputFile, xmlProblemNode ); // Open mesh levels DomainPartition & domain = problemManager.getDomainPartition(); @@ -355,7 +355,7 @@ TEST( ConformingVirtualElementOrder1, wedges ) MeshLevel & mesh = domain.getMeshBody( 0 ).getBaseDiscretization(); ElementRegionManager & elementManager = mesh.getElemManager(); xmlWrapper::xmlNode topLevelNode = xmlProblemNode.child( elementManager.getName().c_str() ); - elementManager.processInputFileRecursive( topLevelNode ); + elementManager.processInputFileRecursive( inputFile, topLevelNode ); elementManager.postProcessInputRecursive(); problemManager.problemSetup(); diff --git a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp index 47a8e30bc53..88fed5c4a7e 100644 --- a/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp +++ b/src/coreComponents/unitTests/xmlTests/testXMLFile.cpp @@ -18,13 +18,19 @@ #include "mainInterface/GeosxState.hpp" #include "fieldSpecification/FieldSpecificationManager.hpp" #include "fieldSpecification/FieldSpecificationBase.hpp" +#include "dataRepository/Group.hpp" +#include "dataRepository/DataContext.hpp" // TPL includes #include #include +#include using namespace geos; +using namespace geos::dataRepository; +using namespace xmlWrapper; +// Tests if the xml file parsing works with one file, one file with nested includes, and multiple files TEST( testXML, testXMLFile ) { geos::ProblemManager & problemManager = geos::getGlobalState().getProblemManager(); @@ -35,6 +41,191 @@ TEST( testXML, testXMLFile ) EXPECT_TRUE( problemManager.getFieldSpecificationManager().hasGroup< FieldSpecificationBase >( "v0" ) ); } +/** + * @brief Returns the xml nodes and attribute in hierarchy that GEOS is supposed to read in + * the Group hierarchy. + * @param document root xml document (which potentially contains includes). + * @param targetNode the function will be called on this node and its children. + * @param elementNames the list of xml elements names with the form "GroupName" or + * "GroupName/WrapperName". The node name is supposed to be the name attribute value, it it exists, + * or the tag name. + */ +void getElementsRecursive( xmlDocument const & document, xmlNode const & targetNode, + std::set< string > & elementNames ) +{ + // Store here every node name that only exist in the xml (and not in the Group hierarchy). + static const std::set< string > xmlOnlyNodes { + "ElementRegions" + }; + + // The Group name will be the name attribute value, or the node tag name if the name attribute + // doesn't exist. + string const groupName = [&]() { + xmlAttribute nameAtt = targetNode.attribute( "name" ); + return nameAtt ? string( nameAtt.value() ) : string( targetNode.name() ); + }(); + + if( xmlOnlyNodes.find( groupName ) == xmlOnlyNodes.end() ) + { + elementNames.emplace( groupName ); + + for( xmlAttribute att : targetNode.attributes() ) + { + if( !isFileMetadataAttribute( att.name() ) ) + { + elementNames.emplace( groupName + '/' + att.name() ); + } + } + } + + for( xmlNode subNode : targetNode.children() ) + { + getElementsRecursive( document, subNode, elementNames ); + } +} +/** + * @brief Calls a lambda for the DataContext of this Group and the ones of its children Group and + * Wrapper if they can be casted to a DataFileContext type. + * @tparam LAMBDA the type of functor to call. The functor takes in parameter a DataContext of + * TARGET_DC type. + * @param func the functor to call for each DataFileContext of the group and its children + */ +template< typename FUNC > +void forAllDataFileContext( Group const & group, FUNC func ) +{ + if( auto * groupCtx = dynamic_cast< DataFileContext const * >( &group.getDataContext() ) ) + { + func( *groupCtx ); + } + for( auto const & wrapperIterator : group.wrappers() ) + { + auto * wrapperCtx = dynamic_cast< DataFileContext const * >( &wrapperIterator.second->getDataContext() ); + if( wrapperCtx ) + { + func( *wrapperCtx ); + } + } + for( auto subGroup : group.getSubGroups() ) + { + forAllDataFileContext( *subGroup.second, func ); + } +} +/** + * @brief Verify if the DataFileContext data correctly locates from where the object comes from (in the + * correct document/include, at the correct line and at the correct character offset). + * @param document root xml document (which potentially contains includes). + * @param fileContext the DataFileContext to verify. + */ +void verifyDataFileContext( DataFileContext const & fileContext, + xmlDocument const & document, + std::set< string > & verifications ) +{ + verifications.emplace( fileContext.getTargetName() ); + + string const & strToVerify = fileContext.getTypeName(); + string const & errInfo = "Verifying " + strToVerify + " in " + fileContext.toString(); + + // verifying if all DataFileContext data have been found + EXPECT_FALSE( fileContext.getFilePath().empty() ) << errInfo; + EXPECT_FALSE( fileContext.getTargetName().empty() ) << errInfo; + EXPECT_FALSE( fileContext.getTypeName().empty() ) << errInfo; + EXPECT_NE( fileContext.getOffset(), xmlDocument::npos ) << errInfo; + EXPECT_NE( fileContext.getLine(), xmlDocument::npos ) << errInfo; + EXPECT_NE( fileContext.getOffsetInLine(), xmlDocument::npos ) << errInfo; + + // will crash if the source buffer specified by fileContext.getFilePath() isn't available + string const * buffer = document.getOriginalBuffer( fileContext.getFilePath() ); + EXPECT_NE( buffer, nullptr ) << errInfo; + + size_t curLine = 1; + bool lineFound = false; + + // Does fileContext.getOffset() locates the object? + EXPECT_LT( fileContext.getOffset() + strToVerify.size(), buffer->size() ) << errInfo; + EXPECT_EQ( strToVerify, buffer->substr( fileContext.getOffset(), strToVerify.size() ) ) << errInfo; + + // Were trying to reach the line return by DataFileContext::getLine() + for( size_t offset=0; + offset < buffer->size() && curLine < fileContext.getLine(); + ++offset ) + { + if( (*buffer)[offset] == '\n' ) + { + curLine++; + } + + // the theorical line has been reach! + if( curLine == fileContext.getLine() ) + { + // Does fileContext.getLine() and fileContext.getOffsetInLine() locates the object? + EXPECT_LT( offset + fileContext.getOffsetInLine() + strToVerify.size(), + buffer->size() ) << errInfo; + EXPECT_EQ( strToVerify, + buffer->substr( offset + fileContext.getOffsetInLine(), strToVerify.size() ) ) << errInfo; + lineFound = true; + } + } + // does the fileContext line has been reached? + EXPECT_TRUE( lineFound ); +} + +/** + * @brief Returns the element that exists in setB but not in setA. + */ +std::set< string > getDifference( std::set< string > const & setA, + std::set< string > const & setB ) +{ + std::set< string > result; + std::set_difference( setA.cbegin(), setA.cend(), + setB.cbegin(), setB.cend(), + std::inserter( result, result.begin() ) ); + return result; +} + +// Tests +// - if the line information of each nodes and attributes can be all retrieved, +// - if the resulting Group & Wrapper hierarchy matches with the input xml documents and includes hierarchy. +TEST( testXML, testXMLFileLines ) +{ + xmlDocument xmlDoc; + ProblemManager & problemManager = getGlobalState().getProblemManager(); + + { + problemManager.parseCommandLineInput(); + Group & commandLine = problemManager.getGroup( problemManager.groupKeys.commandLine ); + string const & inputFileName = commandLine.getReference< string >( problemManager.viewKeys.inputFileName ); + xmlDoc.loadFile( inputFileName, true ); + problemManager.parseXMLDocument( xmlDoc ); + } + + GEOS_LOG( "Loaded files : " ); + for( auto const & buffer: xmlDoc.getOriginalBuffers() ) + { + GEOS_LOG( " " << buffer.first << " (" << buffer.second.size() << " chars)" ); + } + + std::set< string > expectedElements; + getElementsRecursive( xmlDoc, xmlDoc.getFirstChild(), expectedElements ); + + std::set< string > verifiedElements; + forAllDataFileContext( problemManager, [&]( DataFileContext const & ctx ) + { + verifyDataFileContext( ctx, xmlDoc, verifiedElements ); + } ); + + std::set< string > const notFound = getDifference( expectedElements, verifiedElements ); + EXPECT_TRUE( notFound.empty() ) << "Info : There should not exist xml element that were not in " + "the Group hierarchy.\nElements not found in Group hierarchy : {" + << stringutilities::join( notFound, "," ) << "}"; + + std::set< string > const notExpected = getDifference( verifiedElements, expectedElements ); + EXPECT_TRUE( notExpected.empty() ) << "Info : There should not exist an object in the Group " + "hierarchy that contains a DataFileContext but which were not " + "declared in the Xml.\nElements not found in XML hierarchy : {" + << stringutilities::join( notExpected, "," ) << "}"; +} + + int main( int argc, char * * argv ) { ::testing::InitGoogleTest( &argc, argv );