mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
#13510 Add exclusion support to range selection parsing
Range selection functions now support exclusion tokens (e.g., "!4", "!5-7") for omitting values or ranges. UI placeholder texts and documentation updated to reflect new syntax. Comprehensive tests added for exclusion cases, including edge scenarios. Open-ended exclusion ranges are safely ignored in min/max variant. This enhances flexibility and precision for value selection.
This commit is contained in:
1 parent
71eb461f13
commit
f1f25ca760
5 files changed
+269
-7
No files matched your search
@@ -387,6 +387,7 @@ std::set<int> RiaStdStringTools::valuesFromRangeSelection( const std::string& s
|
||||
try
|
||||
{
|
||||
std::set<int> result;
|
||||
std::set<int> exclusions;
|
||||
std::istringstream stringStream( s );
|
||||
std::string token;
|
||||
|
||||
@@ -394,6 +395,18 @@ std::set<int> RiaStdStringTools::valuesFromRangeSelection( const std::string& s
|
||||
{
|
||||
token = RiaStdStringTools::trimString( token );
|
||||
|
||||
// Check if this is an exclusion token
|
||||
bool isExclusion = false;
|
||||
if ( !token.empty() && token[0] == '!' )
|
||||
{
|
||||
isExclusion = true;
|
||||
token = token.substr( 1 ); // Remove the '!' prefix
|
||||
token = RiaStdStringTools::trimString( token );
|
||||
}
|
||||
|
||||
// Skip empty tokens
|
||||
if ( token.empty() ) continue;
|
||||
|
||||
std::istringstream tokenStream( token );
|
||||
int startIndex, endIndex;
|
||||
char dash, colon;
|
||||
@@ -421,16 +434,36 @@ std::set<int> RiaStdStringTools::valuesFromRangeSelection( const std::string& s
|
||||
|
||||
for ( int i = startIndex; i <= endIndex; i += step )
|
||||
{
|
||||
result.insert( i );
|
||||
if ( isExclusion )
|
||||
{
|
||||
exclusions.insert( i );
|
||||
}
|
||||
else
|
||||
{
|
||||
result.insert( i );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.insert( startIndex );
|
||||
if ( isExclusion )
|
||||
{
|
||||
exclusions.insert( startIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
result.insert( startIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove excluded values from result
|
||||
for ( int excluded : exclusions )
|
||||
{
|
||||
result.erase( excluded );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch ( const std::exception& e )
|
||||
@@ -457,6 +490,7 @@ std::set<int> RiaStdStringTools::valuesFromRangeSelection( const std::string& s,
|
||||
try
|
||||
{
|
||||
std::set<int> result;
|
||||
std::set<int> exclusions;
|
||||
std::stringstream stringStream( s );
|
||||
std::string token;
|
||||
|
||||
@@ -464,10 +498,28 @@ std::set<int> RiaStdStringTools::valuesFromRangeSelection( const std::string& s,
|
||||
{
|
||||
token = RiaStdStringTools::trimString( token );
|
||||
|
||||
// Check if this is an exclusion token
|
||||
bool isExclusion = false;
|
||||
if ( !token.empty() && token[0] == '!' )
|
||||
{
|
||||
isExclusion = true;
|
||||
token = token.substr( 1 ); // Remove the '!' prefix
|
||||
token = RiaStdStringTools::trimString( token );
|
||||
}
|
||||
|
||||
// Skip empty tokens
|
||||
if ( token.empty() ) continue;
|
||||
|
||||
// Check for range
|
||||
size_t dashPos = token.find( '-' );
|
||||
if ( dashPos != std::string::npos )
|
||||
{
|
||||
// For exclusions, don't allow open-ended ranges
|
||||
if ( isExclusion && ( dashPos == 0 || dashPos == token.size() - 1 ) )
|
||||
{
|
||||
continue; // Skip invalid exclusion ranges
|
||||
}
|
||||
|
||||
int startIndex = ( dashPos == 0 ) ? minVal : std::stoi( token.substr( 0, dashPos ) );
|
||||
int endIndex = ( dashPos == token.size() - 1 ) ? maxVal : std::stoi( token.substr( dashPos + 1 ) );
|
||||
if ( startIndex > endIndex )
|
||||
@@ -477,16 +529,36 @@ std::set<int> RiaStdStringTools::valuesFromRangeSelection( const std::string& s,
|
||||
}
|
||||
for ( int i = startIndex; i <= endIndex; ++i )
|
||||
{
|
||||
result.insert( i );
|
||||
if ( isExclusion )
|
||||
{
|
||||
exclusions.insert( i );
|
||||
}
|
||||
else
|
||||
{
|
||||
result.insert( i );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for individual numbers
|
||||
result.insert( std::stoi( token ) );
|
||||
if ( isExclusion )
|
||||
{
|
||||
exclusions.insert( std::stoi( token ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
result.insert( std::stoi( token ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove excluded values from result
|
||||
for ( int excluded : exclusions )
|
||||
{
|
||||
result.erase( excluded );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch ( const std::exception& e )
|
||||
|
||||
@@ -66,10 +66,14 @@ public:
|
||||
static std::string removeHtmlTags( const std::string& s );
|
||||
|
||||
// Convert the string "1,2,5-8,10" to {1, 2, 5, 6, 7, 8, 10}
|
||||
// Supports exclusion with "!" prefix: "1-10, !4" to {1, 2, 3, 5, 6, 7, 8, 9, 10}
|
||||
// Supports exclusion of ranges: "1-10, !5-7" to {1, 2, 3, 4, 8, 9, 10}
|
||||
static std::set<int> valuesFromRangeSelection( const std::string& s );
|
||||
|
||||
// Convert the range string with support for open ended expressions. minimum and maximum value will be used to limit the ranges.
|
||||
// The input "-3,5-8,10-", min:1, max:12 will produce {1, 2, 3, 5, 6, 7, 8, 10, 11, 12}
|
||||
// Supports exclusion with "!" prefix: "-10, !4", min:1, max:20 will produce {1, 2, 3, 5, 6, 7, 8, 9, 10}
|
||||
// Supports exclusion of ranges: "-10, !5-7", min:1, max:20 will produce {1, 2, 3, 4, 8, 9, 10}
|
||||
static std::set<int> valuesFromRangeSelection( const std::string& s, int minimumValue, int maximumValue );
|
||||
|
||||
// Create a string from a set of values. {1, 2, 3, 5, 6, 7, 8, 10, 11, 12} will be converted to "1, 2, 3, 5-8, 10-12"
|
||||
|
||||
@@ -337,7 +337,7 @@ RicRecursiveFileSearchDialog::RicRecursiveFileSearchDialog( QWidget* parent, con
|
||||
connect( m_treeViewFilterLineEdit, &QLineEdit::returnPressed, m_treeViewFilterButton, &QPushButton::click );
|
||||
connect( m_treeViewFilterLineEdit, &QLineEdit::textEdited, m_treeViewFilterButton, &QPushButton::click );
|
||||
|
||||
m_treeViewFilterLineEdit->setPlaceholderText( "Select Realizations: 1, 5-7, 9-18:3" );
|
||||
m_treeViewFilterLineEdit->setPlaceholderText( "Select Realizations: 1, 5-7, !4, 9-18:3" );
|
||||
|
||||
m_treeViewFilterLabel->setVisible( false );
|
||||
m_treeViewFilterButton->setVisible( false );
|
||||
|
||||
@@ -61,7 +61,12 @@ RimEnsembleFileSet::RimEnsembleFileSet()
|
||||
CAF_PDM_InitObject( "Ensemble", ":/SummaryEnsemble.svg", "", "" );
|
||||
|
||||
CAF_PDM_InitField( &m_pathPattern, "PathPattern", QString(), "Path Pattern", "", "", "" );
|
||||
CAF_PDM_InitField( &m_realizationSubSet, "RealizationSubSet", QString(), "Realization Filter", "", "", "" );
|
||||
CAF_PDM_InitField( &m_realizationSubSet,
|
||||
"RealizationSubSet",
|
||||
QString(),
|
||||
"Realization Filter",
|
||||
"",
|
||||
"Specify realization numbers. Example: 1-5, 8, 11-20, !4 will include 1, 2, 3, 5, 8, 11-20" );
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_ensembleInfo, "EnsembleInfo", "Info" );
|
||||
m_ensembleInfo.registerGetMethod( this, &RimEnsembleFileSet::ensembleInfo );
|
||||
@@ -280,7 +285,7 @@ void RimEnsembleFileSet::defineEditorAttribute( const caf::PdmFieldHandle* field
|
||||
{
|
||||
if ( auto lineEdAttr = dynamic_cast<caf::PdmUiLineEditorAttribute*>( attribute ) )
|
||||
{
|
||||
lineEdAttr->placeholderText = "E.g. 0,1,4-6. Use '*' for all.";
|
||||
lineEdAttr->placeholderText = "E.g. 0,1,4-10,!6. Use '*' for all.";
|
||||
}
|
||||
}
|
||||
else if ( field == &m_ensembleInfo )
|
||||
|
||||
@@ -318,6 +318,187 @@ TEST( RiaStdStringToolsTest, TestInvalidRangeStrings )
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RiaStdStringToolsTest, ValuesFromRangeSelectionWithExclusion )
|
||||
{
|
||||
{
|
||||
// Test basic exclusion with single value
|
||||
std::string testString = "4-5, 10";
|
||||
std::set<int> expectedValues = { 4, 5, 10 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion with range
|
||||
std::string testString = "1-10, !5-7";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 8, 9, 10 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion with single values
|
||||
std::string testString = "1-10, !5, !7";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 6, 8, 9, 10 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion with spaces
|
||||
std::string testString = "1-10, !5-7";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 8, 9, 10 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion with step values
|
||||
std::string testString = "1-10:2, !5";
|
||||
std::set<int> expectedValues = { 1, 3, 7, 9 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion when value is not in the set
|
||||
std::string testString = "1-5, !10";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 5 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test multiple exclusion ranges
|
||||
std::string testString = "1-20, !3-5, !15-17";
|
||||
std::set<int> expectedValues = { 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 18, 19, 20 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RiaStdStringToolsTest, ValuesFromRangeSelectionWithExclusionMinMax )
|
||||
{
|
||||
int minimumValue = 1;
|
||||
int maximumValue = 20;
|
||||
|
||||
{
|
||||
// Test exclusion with open ended ranges
|
||||
std::string testString = "-10, !5-7";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 8, 9, 10 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString, minimumValue, maximumValue );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion with multiple ranges
|
||||
std::string testString = "1-20, !5-7, !15-17";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 8, 9, 10, 11, 12, 13, 14, 18, 19, 20 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString, minimumValue, maximumValue );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion with open-ended start range
|
||||
std::string testString = "10-, !15-17";
|
||||
std::set<int> expectedValues = { 10, 11, 12, 13, 14, 18, 19, 20 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString, minimumValue, maximumValue );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test invalid open-ended exclusion ranges (should be ignored)
|
||||
std::string testString = "1-20, !-5, !15-";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString, minimumValue, maximumValue );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RiaStdStringToolsTest, ValuesFromRangeSelectionEdgeCases )
|
||||
{
|
||||
{
|
||||
// Test empty exclusion token (should be ignored)
|
||||
std::string testString = "1-10, !";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test empty exclusion token with spaces (should be ignored)
|
||||
std::string testString = "1-10, ! ";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion-only input (should return empty set)
|
||||
std::string testString = "!5-7";
|
||||
std::set<int> expectedValues = {};
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test overlapping exclusions
|
||||
std::string testString = "1-10, !3-7, !5-9";
|
||||
std::set<int> expectedValues = { 1, 2, 10 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
|
||||
{
|
||||
// Test exclusion with step values in the exclusion range
|
||||
std::string testString = "1-20, !5-15:2";
|
||||
std::set<int> expectedValues = { 1, 2, 3, 4, 6, 8, 10, 12, 14, 16, 17, 18, 19, 20 };
|
||||
|
||||
auto actualValues = RiaStdStringTools::valuesFromRangeSelection( testString );
|
||||
|
||||
ASSERT_EQ( expectedValues, actualValues );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user