diff --git a/subprojects/gstreamer/gst/gstvalue.c b/subprojects/gstreamer/gst/gstvalue.c index 137fe70cc3..11804a4d1d 100644 --- a/subprojects/gstreamer/gst/gstvalue.c +++ b/subprojects/gstreamer/gst/gstvalue.c @@ -8133,6 +8133,115 @@ gst_value_transform_object_string (const GValue * src_value, dest_value->data[0].v_pointer = str; } +/********* + * GStrv * + *********/ + +static gchar * +gst_value_serialize_strv (const GValue * value) +{ + const gchar **strv = g_value_get_boxed (value); + GString *str = g_string_new ("<"); + + while (*strv != NULL) { + const gchar *s = *strv; + + /* Add separator if it's not the first string */ + if (str->len > 1) + g_string_append_c (str, ','); + + g_string_append_c (str, '\"'); + + /* Escape \ to \\ and " to \" */ + while (*s != '\0') { + if (*s == '\"' || *s == '\\') + g_string_append_c (str, '\\'); + g_string_append_c (str, *s); + s++; + } + + g_string_append_c (str, '\"'); + + strv++; + } + + g_string_append_c (str, '>'); + + return g_string_free (str, FALSE); +} + +static gboolean +gst_value_deserialize_strv (GValue * dest, const gchar * s) +{ + /* If it's not starting with '<' assume it's a simple comma separated list + * with no escaping. Otherwise assume the format <"foo","bar"> with spaces + * allowed between delimiters and \ for escaping. */ + if (*s != '<') { + g_value_take_boxed (dest, g_strsplit (s, ",", -1)); + return TRUE; + } + s++; + + while (g_ascii_isspace (*s)) + s++; + + GPtrArray *strv = g_ptr_array_new_with_free_func (g_free); + while (*s != '>') { + if (*s != '\"') + goto error; + s++; + + /* Find string end and check if we need to unescape it */ + gboolean escaped = FALSE; + const gchar *start = s; + while (*s != '\"') { + if (*s == '\\') { + escaped = TRUE; + s++; + } + if (*s == '\0') + goto error; + s++; + } + s++; + + /* Always copy the whole string and unescape inplace */ + gchar *substr = g_strndup (start, s - start - 1); + if (escaped) { + gchar *p1 = substr; + const gchar *p2 = substr; + while (*p2 != '\0') { + if (*p2 == '\\') + p2++; + *p1 = *p2; + p1++; + p2++; + } + *p1 = '\0'; + } + + g_ptr_array_add (strv, substr); + + while (g_ascii_isspace (*s)) + s++; + + if (*s == ',') { + s++; + while (g_ascii_isspace (*s)) + s++; + } + } + + g_ptr_array_add (strv, NULL); + g_value_take_boxed (dest, g_ptr_array_free (strv, FALSE)); + + return TRUE; + +error: + g_ptr_array_free (strv, TRUE); + return FALSE; +} + static GTypeInfo _info = { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL, NULL, }; @@ -8363,10 +8472,12 @@ _priv_gst_value_initialize (void) REGISTER_SERIALIZATION (gst_bitmask_get_type (), bitmask); REGISTER_SERIALIZATION (gst_structure_get_type (), structure); REGISTER_SERIALIZATION (gst_flagset_get_type (), flagset); + REGISTER_SERIALIZATION (G_TYPE_GTYPE, gtype); REGISTER_SERIALIZATION_NO_COMPARE (gst_segment_get_type (), segment); REGISTER_SERIALIZATION_NO_COMPARE (gst_caps_features_get_type (), caps_features); + REGISTER_SERIALIZATION_NO_COMPARE (G_TYPE_STRV, strv); REGISTER_SERIALIZATION_COMPARE_ONLY (gst_allocation_params_get_type (), allocation_params); @@ -8374,26 +8485,18 @@ _priv_gst_value_initialize (void) REGISTER_SERIALIZATION_CONST (G_TYPE_DOUBLE, double); REGISTER_SERIALIZATION_CONST (G_TYPE_FLOAT, float); - REGISTER_SERIALIZATION_CONST (G_TYPE_STRING, string); REGISTER_SERIALIZATION_CONST (G_TYPE_BOOLEAN, boolean); REGISTER_SERIALIZATION_CONST (G_TYPE_ENUM, enum); - REGISTER_SERIALIZATION_CONST (G_TYPE_FLAGS, gflags); - REGISTER_SERIALIZATION_CONST (G_TYPE_INT, int); - REGISTER_SERIALIZATION_CONST (G_TYPE_INT64, int64); REGISTER_SERIALIZATION_CONST (G_TYPE_LONG, long); - REGISTER_SERIALIZATION_CONST (G_TYPE_UINT, uint); REGISTER_SERIALIZATION_CONST (G_TYPE_UINT64, uint64); REGISTER_SERIALIZATION_CONST (G_TYPE_ULONG, ulong); - REGISTER_SERIALIZATION_CONST (G_TYPE_UCHAR, uchar); - REGISTER_SERIALIZATION (G_TYPE_GTYPE, gtype); - REGISTER_SERIALIZATION_WITH_PSPEC (gst_value_list_get_type (), value_list); REGISTER_SERIALIZATION_WITH_PSPEC (gst_value_array_get_type (), value_array); diff --git a/subprojects/gstreamer/tests/check/gst/gstvalue.c b/subprojects/gstreamer/tests/check/gst/gstvalue.c index 6426948f2d..72bc4848a9 100644 --- a/subprojects/gstreamer/tests/check/gst/gstvalue.c +++ b/subprojects/gstreamer/tests/check/gst/gstvalue.c @@ -4001,6 +4001,68 @@ GST_START_TEST (test_serialize_deserialize_sample) GST_END_TEST; +GST_START_TEST (test_serialize_deserialize_strv) +{ + GValue v = G_VALUE_INIT; + g_value_init (&v, G_TYPE_STRV); + + fail_if (gst_value_deserialize (&v, "<")); + fail_if (gst_value_deserialize (&v, "< foo")); + fail_if (gst_value_deserialize (&v, "< \"foo")); + fail_if (gst_value_deserialize (&v, "< \"foo\\")); + fail_if (gst_value_deserialize (&v, "< \"foo\"")); + fail_if (gst_value_deserialize (&v, "< \"foo\",")); + + struct + { + const gchar *str; + const gchar *deserialized[3]; + const gchar *serialized; + } tests[] = { + {"", {NULL}, "<>"}, + {"foo", {"foo", NULL}, "<\"foo\">"}, + {"foo ", {"foo ", NULL}, "<\"foo \">"}, + {"foo ,", {"foo ", "", NULL}, "<\"foo \",\"\">"}, + {"foo , ", {"foo ", " ", NULL}, "<\"foo \",\" \">"}, + {"foo,bar", {"foo", "bar", NULL}, "<\"foo\",\"bar\">"}, + {"<>", {NULL}, "<>"}, + {"<\"\">", {"", NULL}, "<\"\">"}, + {"< \" \" > ", {" ", NULL}, "<\" \">"}, + {"<\"foo\",> ", {"foo", NULL}, "<\"foo\">"}, + {"<\"foo\" , > ", {"foo", NULL}, "<\"foo\">"}, + {"<\"foo\",\"bar\"> ", {"foo", "bar", NULL}, "<\"foo\",\"bar\">"}, + {"<\"foo\" , \"bar\"> ", {"foo", "bar", NULL}, "<\"foo\",\"bar\">"}, + {"<\"\\\"\\\\,<>\">", {"\"\\,<>", NULL}, "<\"\\\"\\\\,<>\">"}, + }; + + for (int i = 0; i < G_N_ELEMENTS (tests); i++) { + const gchar *str = tests[i].str; + const gchar *const *deserialized = tests[i].deserialized; + + /* Deserialize */ + if (!gst_value_deserialize (&v, str)) + fail ("Failed to deserialize %dth '%s'", i, str); + const gchar *const *strv = g_value_get_boxed (&v); + if (!g_strv_equal (strv, deserialized)) { + gchar *strv_str = g_strjoinv (", ", (gchar **) strv); + gchar *expected_str = g_strjoinv (", ", (gchar **) deserialized); + fail ("Deserialized %dth '%s' to '%s', expected '%s'", i, str, strv_str, + expected_str); + } + + /* Re-serialize */ + gchar *serialized = gst_value_serialize (&v); + if (!g_str_equal (serialized, tests[i].serialized)) + fail ("Serialized %dth '%s' to '%s', expected '%s'", i, str, serialized, + tests[i].serialized); + g_free (serialized); + + g_value_reset (&v); + } +} + +GST_END_TEST; + static Suite * gst_value_suite (void) { @@ -4063,6 +4125,7 @@ gst_value_suite (void) tcase_add_test (tc_chain, test_serialize_deserialize_caps_features); tcase_add_test (tc_chain, test_serialize_deserialize_tag_list); tcase_add_test (tc_chain, test_serialize_deserialize_sample); + tcase_add_test (tc_chain, test_serialize_deserialize_strv); return s; }