Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
20b501c
add functions to collect included XML files
kdrienCG Mar 20, 2026
3db8b1a
add archiveInputDeck function
kdrienCG Mar 20, 2026
8afead3
call archiveInputDeck() in ProblemManager
kdrienCG Mar 20, 2026
25c325b
modify archive directory name
kdrienCG Mar 20, 2026
1cf33d5
add tests for collectIncluded functions
kdrienCG Mar 24, 2026
a4b35d0
fix typo in archiveInputDeck documentation
kdrienCG Mar 24, 2026
c873915
fix typo in collectIncludedRecursive documentation
kdrienCG Mar 24, 2026
6efbe14
add missing header includes
kdrienCG Mar 24, 2026
140aa51
add filter on collectIncluded iteration
kdrienCG Mar 25, 2026
9ed7c03
add MPI rank 0 condition for archiveInputDeck call
kdrienCG Mar 25, 2026
5c30635
add output directory invariant for archiveInputDeck
kdrienCG Mar 25, 2026
0c5fb2d
add tests for archiveInputDeck
kdrienCG Mar 25, 2026
bf1ca66
modify archive's logic to flatten inputs
kdrienCG Apr 10, 2026
6b35d1b
strip metadata attributes from the archived XML
kdrienCG Apr 10, 2026
a61d8c7
sort XML attributes in the archived XML
kdrienCG Apr 10, 2026
8738084
copy schema.xsd to the archive
kdrienCG Apr 10, 2026
93e2806
uncrustify
kdrienCG Apr 10, 2026
dbfa0fb
relocate archiveInputDeck call to generate the XSD schema
kdrienCG Apr 15, 2026
5a7c196
add command line option to trigger the archiving
kdrienCG Apr 16, 2026
d745bf6
add levels to archiving command line option
kdrienCG Apr 16, 2026
f8acdb4
Merge branch 'develop' into feature/kdrienCG/archiveInputDeck
kdrienCG Apr 16, 2026
c97394d
remove surrounding characters in a comment
kdrienCG Apr 16, 2026
aa5a851
set default archive strategy level to 1
kdrienCG Apr 17, 2026
7041f73
remove XSD schema generation
kdrienCG Apr 17, 2026
135e3a9
relocate archiving in the ProblemManager
kdrienCG Apr 17, 2026
377ebdf
log archive's creation
kdrienCG Apr 17, 2026
d498048
add option to copy the XSD schema
kdrienCG May 6, 2026
6e229b8
modify order of schema candidates
kdrienCG May 12, 2026
c388276
add missing comma
kdrienCG May 19, 2026
81084b8
remove collectIncluded* methods
kdrienCG May 19, 2026
4eb17c7
add archive command line parameter to quick start example
kdrienCG May 19, 2026
2413b78
add documentation to the Input Files page
kdrienCG May 22, 2026
8ca1a6c
set default level to 0
kdrienCG May 22, 2026
83e59d2
add range check for -a level parameter
kdrienCG May 22, 2026
5db3b4d
Merge branch 'develop' into feature/kdrienCG/archiveInputDeck
kdrienCG May 22, 2026
02a276d
continue instead of returning when current XSD candidate fails to copy
kdrienCG Jun 23, 2026
e203ced
modify archive condition to skip archive levels below 0
kdrienCG Jun 23, 2026
6ac9d27
add note to call archiving on a single MPI rank
kdrienCG Jun 23, 2026
6d1a0ee
Merge branch 'develop' into feature/kdrienCG/archiveInputDeck
kdrienCG Jun 24, 2026
b05f6cb
add tests
kdrienCG Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/coreComponents/common/initializeEnvironment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ struct CommandLineOptions

/// Print memory usage in data repository
real64 printMemoryUsage = -1.0;

/// Set the archiving level
integer archiveInputDeck = 1;
Comment thread
kdrienCG marked this conversation as resolved.
Outdated
};

/**
Expand Down
211 changes: 211 additions & 0 deletions src/coreComponents/dataRepository/unitTests/testXmlWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
#include "dataRepository/xmlWrapper.hpp"
#include "common/format/EnumStrings.hpp"

#include <filesystem>
#include <fstream>

using namespace geos;

TEST( testXmlWrapper, array3d_errors )
Expand Down Expand Up @@ -501,6 +504,214 @@ TEST( testXmlWrapper, testGroupNamesArrayFormats )
}
}

class CollectIncludedTest : public ::testing::Test
{
protected:
std::filesystem::path m_tempDir;

void SetUp() override
{
m_tempDir = std::filesystem::temp_directory_path() / "geos_collectIncluded_test";
std::filesystem::create_directories( m_tempDir );
}

void TearDown() override
{
std::filesystem::remove_all( m_tempDir );
}

string filePath( string const & filename )
{
return ( m_tempDir / filename ).string();
}

void writeXML( string const & filename, string const & content )
{
std::ofstream f( filePath( filename ) );
f << content;
}
};


TEST_F( CollectIncludedTest, collectIncluded_noIncludes )
{
writeXML( "base.xml", "<Problem>"
"</Problem>" );

auto result = xmlWrapper::collectIncluded( filePath( "base.xml" ) );

EXPECT_TRUE( result.empty() );
}

TEST_F( CollectIncludedTest, collectIncluded_singleInclude )
{
writeXML( "child.xml", "<Problem>"
"</Problem>" );
writeXML( "base.xml", "<Problem>"
" <Included>"
" <File name=\"child.xml\"/>"
" </Included>"
"</Problem>" );

auto result = xmlWrapper::collectIncluded( filePath( "base.xml" ) );

EXPECT_NE( result.find( filePath( "child.xml" ) ), result.end() );
}

TEST_F( CollectIncludedTest, collectIncluded_multipleIncludes )
{
writeXML( "child1.xml", "<Problem>"
"</Problem>" );
writeXML( "child2.xml", "<Problem>"
"</Problem>" );
writeXML( "base.xml", "<Problem>"
" <Included>"
" <File name=\"child1.xml\"/>"
" <File name=\"child2.xml\"/>"
" </Included>"
"</Problem>" );

auto result = xmlWrapper::collectIncluded( filePath( "base.xml" ) );

EXPECT_NE( result.find( filePath( "child1.xml" ) ), result.end() );
EXPECT_NE( result.find( filePath( "child2.xml" ) ), result.end() );
}

TEST_F( CollectIncludedTest, collectIncluded_emptyNameAttribute )
{
writeXML( "base.xml", "<Problem>"
" <Included>"
" <File name=\"\"/>"
" </Included>"
"</Problem>" );

std::set< string > result;

EXPECT_ANY_THROW( xmlWrapper::collectIncluded( filePath( "base.xml" ), result ) );

EXPECT_TRUE( result.empty() );
}

TEST_F( CollectIncludedTest, collectIncluded_existingEntriesKept )
{
std::set< string > existingCollection;
existingCollection.insert( "/somewhere/thereisanalreadyexistingxmlfile.xml" );
writeXML( "base.xml", "<Problem>"
"</Problem>" );

xmlWrapper::collectIncluded( filePath( "base.xml" ), existingCollection );

EXPECT_NE( existingCollection.find( "/somewhere/thereisanalreadyexistingxmlfile.xml" ),
existingCollection.end() );
}


TEST_F( CollectIncludedTest, collectIncludedRecursive_noIncludes )
{
writeXML( "base.xml", "<Problem>"
"</Problem>" );

auto result = xmlWrapper::collectIncludedRecursive( filePath( "base.xml" ) );

EXPECT_EQ( result.size(), 1 ); // size 1 because collectIncludedRecursive collects the base file
}

TEST_F( CollectIncludedTest, collectIncludedRecursive_singleInclude )
{
writeXML( "child.xml", "<Problem>"
"</Problem>" );
writeXML( "base.xml", "<Problem>"
" <Included>"
" <File name=\"child.xml\"/>"
" </Included>"
"</Problem>" );

auto result = xmlWrapper::collectIncludedRecursive( filePath( "base.xml" ) );

EXPECT_NE( result.find( filePath( "child.xml" ) ), result.end() );
}

TEST_F( CollectIncludedTest, collectIncludedRecursive_multipleIncludes )
{
writeXML( "child1.xml", "<Problem>"
"</Problem>" );
writeXML( "child2.xml", "<Problem>"
"</Problem>" );
writeXML( "base.xml", "<Problem>"
" <Included>"
" <File name=\"child1.xml\"/>"
" <File name=\"child2.xml\"/>"
" </Included>"
"</Problem>" );

auto result = xmlWrapper::collectIncludedRecursive( filePath( "base.xml" ) );

EXPECT_NE( result.find( filePath( "child1.xml" ) ), result.end() );
EXPECT_NE( result.find( filePath( "child2.xml" ) ), result.end() );
}

TEST_F( CollectIncludedTest, collectIncludedRecursive_simpleRecursive )
{
writeXML( "child.xml", "<Problem>"
"</Problem>" );

writeXML( "middle.xml", "<Problem>"
" <Included>"
" <File name=\"child.xml\"/>"
" </Included>"
"</Problem>" );

writeXML( "base.xml", "<Problem>"
" <Included>"
" <File name=\"middle.xml\"/>"
" </Included>"
"</Problem>" );

auto result = xmlWrapper::collectIncludedRecursive( filePath( "base.xml" ) );

EXPECT_NE( result.find( filePath( "middle.xml" ) ), result.end() );
EXPECT_NE( result.find( filePath( "child.xml" ) ), result.end() );
}

TEST_F( CollectIncludedTest, collectIncludedRecursive_cyclePrevention )
{
writeXML( "cycle.xml", "<Problem>"
" <Included>"
" <File name=\"cycle.xml\"/>"
" </Included>"
"</Problem>" );

auto result = xmlWrapper::collectIncludedRecursive( filePath( "cycle.xml" ) );

EXPECT_NE( result.find( filePath( "cycle.xml" ) ), result.end() );
}

TEST_F( CollectIncludedTest, collectIncludedRecursive_noDuplicates )
{
writeXML( "base.xml", "<Problem>"
" <Included>"
" <File name=\"base.xml\"/>"
" </Included>"
"</Problem>" );

auto result = xmlWrapper::collectIncludedRecursive( filePath( "base.xml" ) );

EXPECT_EQ( result.size(), 1 ); // collectIncludedRecursive collects the base file
}

TEST_F( CollectIncludedTest, collectIncludedRecursive_existingEntriesKept )
{
std::set< string > existingCollection;
existingCollection.insert( "/somewhere/thereisanalreadyexistingxmlfile.xml" );
writeXML( "base.xml", "<Problem>"
"</Problem>" );

xmlWrapper::collectIncludedRecursive( filePath( "base.xml" ), existingCollection );

EXPECT_NE( existingCollection.find( "/somewhere/thereisanalreadyexistingxmlfile.xml" ),
existingCollection.end() );
}


int main( int argc, char * argv[] )
{
Expand Down
86 changes: 86 additions & 0 deletions src/coreComponents/dataRepository/xmlWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "common/format/StringUtilities.hpp"
#include "common/MpiWrapper.hpp"
#include "dataRepository/KeyNames.hpp"
#include "common/Path.hpp"

namespace geos
{
Expand Down Expand Up @@ -268,6 +269,91 @@ string buildMultipleInputXML( string_array const & inputFileList,
return inputFileName;
}

void collectIncluded( string const & filePath,
Comment thread
kdrienCG marked this conversation as resolved.
Outdated
std::set< string > & collection )
{
xmlDocument doc;
xmlResult result = doc.loadFile( filePath );
GEOS_THROW_IF( !result,
GEOS_FMT( "Could not load XML file '{}': {}", filePath, result.description() ),
InputError );
xmlNode rootNode = doc.getFirstChild();

string const currentDir = splitPath( filePath ).first;

for( auto & includedNode : rootNode.children( includedListTag ) )
{
for( auto & fileNode : includedNode.children( includedFileTag ) )
{
string const fileName = fileNode.attribute( "name" ).value();

GEOS_THROW_IF( fileName.empty(),
GEOS_FMT( "An included file entry in '{}' has an empty or missing 'name' attribute.", filePath ),
InputError );

string absolutePath = isAbsolutePath( fileName )
? getAbsolutePath( fileName )
: getAbsolutePath( joinPath( currentDir, fileName ) );
collection.insert( absolutePath );
}
}
}

std::set< string > collectIncluded( string const & filePath )
{
std::set< string > collection;
collectIncluded( filePath, collection );
return collection;
}

void collectIncludedRecursive( string const & filePath,
std::set< string > & collection )
{
// We want absolute paths
string const absFilePath = getAbsolutePath( filePath );

if( collection.count( absFilePath ) > 0 )
{
return;
}
collection.insert( absFilePath );

xmlDocument doc;
xmlResult result = doc.loadFile( absFilePath );
GEOS_THROW_IF( !result,
GEOS_FMT( "Could not load XML file '{}': {}", filePath, result.description() ),
InputError );
xmlNode rootNode = doc.getFirstChild();

string const currentDir = splitPath( filePath ).first;

for( auto & includedNode : rootNode.children( includedListTag ) )
{
for( auto & fileNode : includedNode.children( includedFileTag ) )
{
string const includedFilePath = fileNode.attribute( "name" ).value();

if( includedFilePath.empty() )
{
continue;
}

string includedAbsPath = isAbsolutePath( includedFilePath )
? getAbsolutePath( includedFilePath )
: getAbsolutePath( joinPath( currentDir, includedFilePath ) );
collectIncludedRecursive( includedAbsPath,
collection );
}
}
}

std::set< string > collectIncludedRecursive( string const & filePath )
{
std::set< string > collection;
collectIncludedRecursive( filePath, collection );
return collection;
}

bool isFileMetadataAttribute( string const & name )
{
static const std::set< string > fileMetadataAttributes {
Expand Down
44 changes: 44 additions & 0 deletions src/coreComponents/dataRepository/xmlWrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,50 @@ constexpr char const includedFileTag[] = "File";
string buildMultipleInputXML( string_array const & inputFileList,
string const & outputDir = {} );

/**
* @brief Collect the absolute paths of XML files directly included
* by a given xml file
* @param[in] filePath absolute path of the xml file to inspect
* @param[inout] collection collection to append with absolute file paths
*
* Only one level of inclusion is collected (files included by the included
* files are not added). See collectIncludedRecursive if you want this behavior.
* Duplicate entries are not inserted in @p collection
*/
void collectIncluded( string const & filePath,
std::set< string > & collection );

/**
* @brief Collect the absolute paths of XML files directly included
* by a given xml file
* @param[in] filePath absolute path of the xml file to inspect
* @return a collection of absolute paths
*
* Only one level of inclusion is collected (files included by the included
* files are not added). See collectIncludedRecursive if you want this behavior.
* Duplicate entries are not inserted in @p collection
*/
std::set< string > collectIncluded( string const & filePath );

/**
* @brief Recursively collect the absolute paths of an XML file and all XML
* files it includes
* @param[in] filePath absolute path of the root XML file
* @param[inout] collection collection to append with absolute file paths
* of every visited file (including @p filePath itself)
*/
void collectIncludedRecursive( string const & filePath,
std::set< string > & collection );

/**
* @brief Recursively collect the absolute paths of an XML file and all XML
* files it includes
* @param[in] filePath absolute path of the root XML file
* @return a collection of absolute paths of every visited file (including
* @p filePath itself)
*/
std::set< string > collectIncludedRecursive( string const & filePath );

/**
* @return true if the attribute with the specified name declares metadata relative to the xml
* @param name the name of an attribute
Expand Down
Loading
Loading