From 5356f078a56207b8886e0b04126257530e8923ae Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 26 Jan 2026 13:37:07 +0100 Subject: [PATCH] #13435 Improve string list parsing for backslashes and quotes Enhanced parsing in PdmFieldScriptingCapabilityIOHandler to correctly handle backslashes and escape sequences in both quoted and unquoted strings. Unquoted Windows paths now preserve backslashes, while quoted strings process escape sequences as expected. Added unit tests to verify correct handling of both cases. Add tests for Windows-style path handling in rips API Introduce test_windows_paths.py with two pytest tests to verify correct handling of Windows-style file paths (backslashes) in the rips Python API. --- .../cafPdmFieldScriptingCapability.cpp | 28 ++++++++--- .../cafPdmFieldSerializationTest.cpp | 48 +++++++++++++++++++ .../Python/rips/tests/test_windows_paths.py | 47 ++++++++++++++++++ 3 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 GrpcInterface/Python/rips/tests/test_windows_paths.py diff --git a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.cpp b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.cpp index ea67f65178..f82f03f9a7 100644 --- a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.cpp +++ b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.cpp @@ -68,22 +68,36 @@ void PdmFieldScriptingCapabilityIOHandler::writeToField( QString& while ( !inputStream.atEnd() ) { currentChar = errorMessageContainer->readCharWithLineNumberCount( inputStream ); - if ( !currentChar.isNull() && currentChar != QChar( '\\' ) ) + + if ( currentChar.isNull() ) { - if ( currentChar == QChar( '"' ) ) // End Quote + // Null character - add as is + accumulatedFieldValue += currentChar; + } + else if ( currentChar == QChar( '"' ) ) + { + // End Quote - reached end of string + validStringEnd = true; + break; + } + else if ( currentChar == QChar( '\\' ) && stringsAreQuoted ) + { + // Process escape sequence only when strings are quoted + // Read the next character after the backslash + if ( !inputStream.atEnd() ) { - // Reached end of string - validStringEnd = true; - break; + currentChar = errorMessageContainer->readCharWithLineNumberCount( inputStream ); + accumulatedFieldValue += currentChar; } else { - accumulatedFieldValue += currentChar; + // Backslash at end of input - add it as is + accumulatedFieldValue += QChar( '\\' ); } } else { - currentChar = errorMessageContainer->readCharWithLineNumberCount( inputStream ); + // Regular character (including backslashes when strings are not quoted) accumulatedFieldValue += currentChar; } } diff --git a/Fwk/AppFwk/cafPdmScripting/cafPdmScripting_UnitTests/cafPdmFieldSerializationTest.cpp b/Fwk/AppFwk/cafPdmScripting/cafPdmScripting_UnitTests/cafPdmFieldSerializationTest.cpp index fb80dd28cd..8277a71600 100644 --- a/Fwk/AppFwk/cafPdmScripting/cafPdmScripting_UnitTests/cafPdmFieldSerializationTest.cpp +++ b/Fwk/AppFwk/cafPdmScripting/cafPdmScripting_UnitTests/cafPdmFieldSerializationTest.cpp @@ -97,6 +97,54 @@ TEST( PdmFieldSerialization, StringListQuoted ) EXPECT_STREQ( "B-4H", destination[1].toStdString().c_str() ); } +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- +TEST( PdmFieldSerialization, StringListWithBackslashes ) +{ + // Test that Windows paths with backslashes are properly preserved when stringsAreQuoted = false + // This is the typical case when paths are sent from Python via gRPC + QString source = R"([C:\Users\file.txt, D:\Data\project.txt])"; + + QTextStream stream( &source ); + std::vector destination; + + caf::PdmScriptIOMessages messages; + bool stringsAreQuoted = false; // Strings in lists from Python are not quoted + + caf::PdmFieldScriptingCapabilityIOHandler>::writeToField( destination, + stream, + &messages, + stringsAreQuoted ); + + EXPECT_EQ( (size_t)2, destination.size() ); + EXPECT_STREQ( "C:\\Users\\file.txt", destination[0].toStdString().c_str() ); + EXPECT_STREQ( "D:\\Data\\project.txt", destination[1].toStdString().c_str() ); +} + +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- +TEST( PdmFieldSerialization, StringListQuotedWithEscapedBackslashes ) +{ + // When strings ARE quoted (e.g., from command files), backslashes should be escaped + // and the escape sequences should be processed + QString source = R"(["C:\\Users\\file.txt", "D:\\Data\\project.txt"])"; + + QTextStream stream( &source ); + std::vector destination; + + caf::PdmScriptIOMessages messages; + bool stringsAreQuoted = true; + + caf::PdmFieldScriptingCapabilityIOHandler>::writeToField( destination, + stream, + &messages, + stringsAreQuoted ); + + EXPECT_EQ( (size_t)2, destination.size() ); + EXPECT_STREQ( "C:\\Users\\file.txt", destination[0].toStdString().c_str() ); + EXPECT_STREQ( "D:\\Data\\project.txt", destination[1].toStdString().c_str() ); +} + //-------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------- TEST( PdmFieldSerialization, ValueList ) diff --git a/GrpcInterface/Python/rips/tests/test_windows_paths.py b/GrpcInterface/Python/rips/tests/test_windows_paths.py new file mode 100644 index 0000000000..5142577c8c --- /dev/null +++ b/GrpcInterface/Python/rips/tests/test_windows_paths.py @@ -0,0 +1,47 @@ +import sys +import os +import platform + +sys.path.insert(1, os.path.join(sys.path[0], "../../")) +import rips + +import dataroot + +import pytest + +pytestmark = pytest.mark.skipif( + platform.system() != "Windows", reason="Windows-specific path test" +) + + +def test_import_well_paths_with_backslashes(rips_instance, initialize_test): + """Test that Windows-style paths with backslashes are correctly handled. + + This test verifies the fix for issue #13435 where backslashes in Windows paths + were incorrectly interpreted as escape characters when passed from Python to + ResInsight via gRPC. + """ + case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC" + case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=case_path) + assert len(case.grids()) == 2 + + # Build paths using forward slashes first, then convert to Windows-style backslashes + well_path_a = os.path.abspath( + os.path.join(case_root_path, "wellpath_a.dev") + ).replace("/", "\\") + well_path_b = os.path.abspath( + os.path.join(case_root_path, "wellpath_b.dev") + ).replace("/", "\\") + + # The paths now contain backslashes (e.g., "C:\path\to\wellpath_a.dev") + # This tests the fix for issue #13435 where backslashes were incorrectly + # interpreted as escape characters + well_path_files = [well_path_a, well_path_b] + + well_path_names = rips_instance.project.import_well_paths(well_path_files) + assert len(well_path_names) == 2 + wells = rips_instance.project.well_paths() + assert len(wells) == 2 + assert wells[0].name == "Well Path A" + assert wells[1].name == "Well Path B"