From b0197669f5551911bc42c1bfc7dfdd3198ab070f Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Tue, 5 May 2026 15:55:41 +0200 Subject: [PATCH] [Type] Add streaming deserialization for vector Implements `read` functionality for `vector`, supporting both bracketed, quoted formats (e.g., `["a", "b"]`) and plain whitespace-separated tokens as a fallback. Includes robust error checking and failure reporting via `std::cerr`. Also adds comprehensive unit tests covering various input formats and expected parsing behaviors for string vectors. --- .../Type/src/sofa/type/vector_String.cpp | 123 ++++++++++++++++++ .../Type/src/sofa/type/vector_String.h | 1 + Sofa/framework/Type/test/vector_test.cpp | 59 +++++++++ 3 files changed, 183 insertions(+) diff --git a/Sofa/framework/Type/src/sofa/type/vector_String.cpp b/Sofa/framework/Type/src/sofa/type/vector_String.cpp index 1ef2bbb2b4b..31d889f0a87 100644 --- a/Sofa/framework/Type/src/sofa/type/vector_String.cpp +++ b/Sofa/framework/Type/src/sofa/type/vector_String.cpp @@ -47,6 +47,129 @@ SOFA_TYPE_API std::ostream& vector::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::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(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::read: unexpected end of stream, expected '\"' or ']'" << std::endl; + in.setstate(std::ios_base::failbit); + return in; + } + + char next = static_cast(in.peek()); + + if (next == ']') + { + in.get(); // consume ']' + break; + } + + if (next != '"') + { + std::cerr << "vector::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::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::read: unexpected end of stream after element" << std::endl; + in.setstate(std::ios_base::failbit); + return in; + } + + char sep = static_cast(in.peek()); + if (sep == ',') + { + in.get(); // consume ',' + } + else if (sep == ']') + { + // will be consumed on next loop iteration + } + else + { + std::cerr << "vector::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; diff --git a/Sofa/framework/Type/src/sofa/type/vector_String.h b/Sofa/framework/Type/src/sofa/type/vector_String.h index 74a6a37c6ed..adf387de9c0 100644 --- a/Sofa/framework/Type/src/sofa/type/vector_String.h +++ b/Sofa/framework/Type/src/sofa/type/vector_String.h @@ -23,4 +23,5 @@ #include template<> SOFA_TYPE_API std::ostream& sofa::type::vector::write(std::ostream& os) const; +template<> SOFA_TYPE_API std::istream& sofa::type::vector::read( std::istream& in ); diff --git a/Sofa/framework/Type/test/vector_test.cpp b/Sofa/framework/Type/test/vector_test.cpp index 6661f5f2ba0..003c0c2650a 100644 --- a/Sofa/framework/Type/test/vector_test.cpp +++ b/Sofa/framework/Type/test/vector_test.cpp @@ -317,3 +317,62 @@ TEST_P(vector_benchmark_int, benchmark) INSTANTIATE_TEST_SUITE_P(benchmark, vector_benchmark_int, ::testing::ValuesIn(benchvalues)); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// +/// TEST THE vector behavior +/// +//////////////////////////////////////////////////////////////////////////////////////////////////// +typedef vector_test 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> 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(); +}