Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
123 changes: 123 additions & 0 deletions Sofa/framework/Type/src/sofa/type/vector_String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,129 @@ SOFA_TYPE_API std::ostream& vector<std::string>::write(std::ostream& os) const
return os;
}

/// Input stream
/// Parses the format produced by write(): ["elem1", "elem2", ...]
/// Falls back to plain whitespace-separated tokens if no leading '[' is found.
/// Sets stream to fail state and writes to std::cerr on bad formatting.
template<>
SOFA_TYPE_API std::istream& vector<std::string>::read( std::istream& in )
{
this->clear();

// Skip leading whitespace
in >> std::ws;

if (in.eof())
return in; // empty input is valid

char c = static_cast<char>(in.peek());

if (c == '[')
{
// --- Bracketed quoted format: ["elem1", "elem2", ...] ---
in.get(); // consume '['

in >> std::ws;
if (in.peek() == ']')
{
in.get(); // consume ']', empty array
return in;
}

while (true)
{
in >> std::ws;

if (!in.good())
{
std::cerr << "vector<string>::read: unexpected end of stream, expected '\"' or ']'" << std::endl;
in.setstate(std::ios_base::failbit);
return in;
}

char next = static_cast<char>(in.peek());

if (next == ']')
{
in.get(); // consume ']'
break;
}

if (next != '"')
{
std::cerr << "vector<string>::read: expected '\"' but got '" << next << "'" << std::endl;
in.setstate(std::ios_base::failbit);
return in;
}

in.get(); // consume opening '"'

std::string token;
bool closed = false;
char ch;
while (in.get(ch))
{
if (ch == '"')
{
closed = true;
break;
}
token += ch;
}

if (!closed)
{
std::cerr << "vector<string>::read: unterminated quoted string" << std::endl;
in.setstate(std::ios_base::failbit);
return in;
}

this->push_back(token);

// After the closing quote: expect ',' or ']'
in >> std::ws;
if (!in.good())
{
std::cerr << "vector<string>::read: unexpected end of stream after element" << std::endl;
in.setstate(std::ios_base::failbit);
return in;
}

char sep = static_cast<char>(in.peek());
if (sep == ',')
{
in.get(); // consume ','
}
else if (sep == ']')
{
// will be consumed on next loop iteration
}
else
{
std::cerr << "vector<string>::read: expected ',' or ']' but got '" << sep << "'" << std::endl;
in.setstate(std::ios_base::failbit);
return in;
}
}
}
else
{
// --- Plain whitespace-separated fallback ---
std::string token;
while (in >> token)
{
this->push_back(token);
}
if (in.rdstate() & std::ios_base::eofbit)
{
in.clear();
}
}

return in;
}


} // namespace sofa::type

template class SOFA_TYPE_API sofa::type::vector<std::string>;
1 change: 1 addition & 0 deletions Sofa/framework/Type/src/sofa/type/vector_String.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@
#include <sofa/type/vector_T.h>

template<> SOFA_TYPE_API std::ostream& sofa::type::vector<std::string>::write(std::ostream& os) const;
template<> SOFA_TYPE_API std::istream& sofa::type::vector<std::string>::read( std::istream& in );

59 changes: 59 additions & 0 deletions Sofa/framework/Type/test/vector_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,62 @@ TEST_P(vector_benchmark_int, benchmark)
INSTANTIATE_TEST_SUITE_P(benchmark,
vector_benchmark_int,
::testing::ValuesIn(benchvalues));

////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// TEST THE vector<std::string> behavior
///
////////////////////////////////////////////////////////////////////////////////////////////////////
typedef vector_test<std::string> vector_test_string;
TEST_P(vector_test_string, checkReadWriteBehavior)
{
this->checkVector(GetParam()) ;
}
// Test cases for string read/write functionality.
// write() format: ["elem1", "elem2", ...]
// read() supports:
// - Bracketed quoted format (bijective with write): ["elem1", "elem2", ...]
// - Plain whitespace-separated fallback (no brackets)
std::vector<std::vector<std::string>> stringvalues={
/// Empty input -> empty vector -> write outputs []
{"", "[]", "None"},

/// Single quoted element (bracketed format, round-trip)
{"[\"apple\"]", "[\"apple\"]", "None"},

/// Single quoted element containing spaces (round-trip)
{"[\"hello world test\"]", "[\"hello world test\"]", "None"},

/// Multiple quoted elements (round-trip)
{"[\"one\", \"two\", \"three\"]", "[\"one\", \"two\", \"three\"]", "None"},

/// Empty array (round-trip)
{"[]", "[]", "None"},

/// Plain whitespace-separated fallback (no brackets): each token is an element
{"one two three", "[\"one\", \"two\", \"three\"]", "None"},

/// Single plain token
{"single_word", "[\"single_word\"]", "None"},

/// Bad format: opening bracket but no closing bracket -> Error
{"[\"unterminated\"", "[\"unterminated\"]", "Error"},

/// Bad format: bracket with no opening quote -> Error
{"[badtoken]", "[]", "Error"},

/// Bad format: unterminated quoted string -> Error
{"[\"not closed", "[]", "Error"},

/// Bad format: missing separator between elements -> Error
{"[\"one\" \"two\"]", "[\"one\"]", "Error"},
};

INSTANTIATE_TEST_SUITE_P(checkReadWriteBehavior,
vector_test_string,
::testing::ValuesIn(stringvalues));

TEST_F(vector_test_string, checkRebind)
{
this->checkRebind();
}
Loading