diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimKeywordFactory.cpp b/ApplicationLibCode/ProjectDataModel/Jobs/RimKeywordFactory.cpp index e39aa14013..754d4e20f2 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimKeywordFactory.cpp +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimKeywordFactory.cpp @@ -836,6 +836,54 @@ namespace } return result; } + + //-------------------------------------------------------------------------------------------------- + /// Shorten a parser item name to at most maxWidth characters so long names (e.g. + /// "CONNECTION_TRANSMISSIBILITY_FACTOR") do not widen their column beyond the data. Each + /// underscore-separated token is truncated to progressively shorter prefixes ("CONN_TRAN_FACT", + /// "CON_TRA_FAC", ...), falling back to token initials ("CTF") and finally a hard cut. + //-------------------------------------------------------------------------------------------------- + std::string shortenItemName( const std::string& name, size_t maxWidth ) + { + if ( name.size() <= maxWidth ) return name; + + std::vector tokens; + std::stringstream tokenStream( name ); + std::string token; + while ( std::getline( tokenStream, token, '_' ) ) + { + if ( !token.empty() ) tokens.push_back( token ); + } + if ( tokens.empty() ) return name.substr( 0, maxWidth ); + + auto joinPrefixes = [&tokens]( size_t prefixLength ) + { + std::string joined; + for ( const auto& t : tokens ) + { + if ( !joined.empty() ) joined += "_"; + joined += t.substr( 0, prefixLength ); + } + return joined; + }; + + size_t longestToken = 0; + for ( const auto& t : tokens ) + longestToken = std::max( longestToken, t.size() ); + + for ( size_t prefixLength = longestToken - 1; prefixLength >= 2; --prefixLength ) + { + std::string candidate = joinPrefixes( prefixLength ); + if ( candidate.size() <= maxWidth ) return candidate; + } + + std::string initials; + for ( const auto& t : tokens ) + initials += t.front(); + if ( initials.size() <= maxWidth ) return initials; + + return name.substr( 0, maxWidth ); + } } // namespace //-------------------------------------------------------------------------------------------------- @@ -897,12 +945,21 @@ QString deckKeywordToAlignedString( const Opm::DeckKeyword& keyword ) const size_t numCols = header.size(); std::vector width( numCols, 0 ); - for ( size_t c = 0; c < numCols; ++c ) - width[c] = header[c].size(); for ( const auto& row : rows ) for ( size_t c = 0; c < numCols; ++c ) width[c] = std::max( width[c], row[c].size() ); + // Long parser item names (e.g. "CONNECTION_TRANSMISSIBILITY_FACTOR") would otherwise pad + // every data row to the header width and push lines past the 132-character limit enforced + // by some simulators. Shorten each header to the width of its data, letting narrow columns + // grow to a small minimum so the abbreviations stay readable. + constexpr size_t minHeaderWidth = 8; + for ( size_t c = 0; c < numCols; ++c ) + { + header[c] = shortenItemName( header[c], std::max( width[c], minHeaderWidth ) ); + width[c] = std::max( width[c], header[c].size() ); + } + // Header comment line: "--" occupies the same two columns as the data-row indent so the // header names line up with the values below them. if ( numCols > 0 ) diff --git a/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp b/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp index 1accd05b3a..f9c76fb2e1 100644 --- a/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp +++ b/ApplicationLibCode/UnitTests/RifOpmFlowDeckFile-Test.cpp @@ -15,6 +15,7 @@ #include "opm/input/eclipse/Deck/DeckKeyword.hpp" #include "opm/input/eclipse/Deck/DeckRecord.hpp" #include "opm/input/eclipse/Parser/ParserKeywords/B.hpp" +#include "opm/input/eclipse/Parser/ParserKeywords/C.hpp" #include #include @@ -575,3 +576,39 @@ TEST( RifOpmFlowDeckFileTest, RemoveAndInsertKeywordAtSectionStart ) ASSERT_NE( nextIt, keywords.end() ); EXPECT_EQ( "BCPROP", *nextIt ) << "BCPROP should be the first keyword inside SCHEDULE"; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RimKeywordFactoryTest, DeckKeywordToAlignedStringShortensLongHeaders ) +{ + using C = Opm::ParserKeywords::COMPDAT; + + Opm::DeckKeyword kw( ( Opm::ParserKeywords::COMPDAT() ) ); + + std::vector items; + items.push_back( RifOpmDeckTools::item( C::WELL::itemName, std::string( "WELL-1" ) ) ); + items.push_back( RifOpmDeckTools::item( C::I::itemName, 12 ) ); + items.push_back( RifOpmDeckTools::item( C::J::itemName, 34 ) ); + items.push_back( RifOpmDeckTools::item( C::K1::itemName, 5 ) ); + items.push_back( RifOpmDeckTools::item( C::K2::itemName, 7 ) ); + items.push_back( RifOpmDeckTools::item( C::STATE::itemName, std::string( "OPEN" ) ) ); + items.push_back( RifOpmDeckTools::defaultItem( C::SAT_TABLE::itemName ) ); + items.push_back( RifOpmDeckTools::item( C::CONNECTION_TRANSMISSIBILITY_FACTOR::itemName, 0.1234567891 ) ); + items.push_back( RifOpmDeckTools::item( C::DIAMETER::itemName, 0.216 ) ); + items.push_back( RifOpmDeckTools::defaultItem( C::Kh::itemName ) ); + items.push_back( RifOpmDeckTools::item( C::SKIN::itemName, 0.0 ) ); + items.push_back( RifOpmDeckTools::defaultItem( C::D_FACTOR::itemName ) ); + items.push_back( RifOpmDeckTools::item( C::DIR::itemName, std::string( "Z" ) ) ); + kw.addRecord( Opm::DeckRecord{ std::move( items ) } ); + + QString text = RimKeywordFactory::deckKeywordToAlignedString( kw ); + + // Long parser item names must be abbreviated so they do not widen the columns (issue #14136). + EXPECT_FALSE( text.contains( "CONNECTION_TRANSMISSIBILITY_FACTOR" ) ); + + for ( const QString& line : text.split( '\n' ) ) + { + EXPECT_LE( line.size(), 132 ) << "Line exceeds 132 characters: " << line.toStdString(); + } +}