+static const char *
+skip_spaces(const char *p)
+{
+ return p + strspn(p, " ");
+}
+
+static char *
+parse_atom_token(const char **s, enum ovsdb_atomic_type type,
+ union ovsdb_atom *atom)
+{
+ char *token, *error;
+
+ error = ovsdb_token_parse(s, &token);
+ if (!error) {
+ error = ovsdb_atom_from_string(atom, type, token);
+ free(token);
+ }
+ return error;
+}
+
+
+static char *
+parse_key_value(const char **s, const struct ovsdb_type *type,
+ union ovsdb_atom *key, union ovsdb_atom *value)
+{
+ const char *start = *s;
+ char *error;
+
+ error = parse_atom_token(s, type->key_type, key);
+ if (!error && type->value_type != OVSDB_TYPE_VOID) {
+ if (**s == '=') {
+ (*s)++;
+ error = parse_atom_token(s, type->value_type, value);
+ } else {
+ error = xasprintf("%s: syntax error at \"%c\" expecting \"=\"",
+ start, **s);
+ }
+ if (error) {
+ ovsdb_atom_destroy(key, type->key_type);
+ }
+ }
+ return error;
+}
+
+static void
+free_key_value(const struct ovsdb_type *type,
+ union ovsdb_atom *key, union ovsdb_atom *value)
+{
+ ovsdb_atom_destroy(key, type->key_type);
+ if (type->value_type != OVSDB_TYPE_VOID) {
+ ovsdb_atom_destroy(value, type->value_type);
+ }
+}
+
+/* Initializes 'datum' as a datum of the given 'type', parsing its contents
+ * from 's'. The format of 's' is a series of space or comma separated atoms
+ * or, for a map, '='-delimited pairs of atoms. Each atom must in a format
+ * acceptable to ovsdb_atom_from_string(). Optionally, a set may be enclosed
+ * in "[]" or a map in "{}"; for an empty set or map these punctuators are
+ * required. */
+char *
+ovsdb_datum_from_string(struct ovsdb_datum *datum,
+ const struct ovsdb_type *type, const char *s)
+{
+ bool is_map = ovsdb_type_is_map(type);
+ struct ovsdb_error *dberror;
+ const char *p;
+ int end_delim;
+ char *error;
+
+ ovsdb_datum_init_empty(datum);
+
+ /* Swallow a leading delimiter if there is one. */
+ p = skip_spaces(s);
+ if (*p == (is_map ? '{' : '[')) {
+ end_delim = is_map ? '}' : ']';
+ p = skip_spaces(p + 1);
+ } else if (!*p) {
+ if (is_map) {
+ return xstrdup("use \"{}\" to specify the empty map");
+ } else {
+ return xstrdup("use \"[]\" to specify the empty set");
+ }
+ } else {
+ end_delim = 0;
+ }
+
+ while (*p && *p != end_delim) {
+ union ovsdb_atom key, value;
+
+ if (ovsdb_token_is_delim(*p)) {
+ error = xasprintf("%s: unexpected \"%c\" parsing %s",
+ s, *p, ovsdb_type_to_english(type));
+ goto error;
+ }
+
+ /* Add to datum. */
+ error = parse_key_value(&p, type, &key, &value);
+ if (error) {
+ goto error;
+ }
+ ovsdb_datum_add_unsafe(datum, &key, &value, type);
+ free_key_value(type, &key, &value);
+
+ /* Skip optional white space and comma. */
+ p = skip_spaces(p);
+ if (*p == ',') {
+ p = skip_spaces(p + 1);
+ }
+ }
+
+ if (*p != end_delim) {
+ error = xasprintf("%s: missing \"%c\" at end of data", s, end_delim);
+ goto error;
+ }
+ if (end_delim) {
+ p = skip_spaces(p + 1);
+ if (*p) {
+ error = xasprintf("%s: trailing garbage after \"%c\"",
+ s, end_delim);
+ goto error;
+ }
+ }
+
+ if (datum->n < type->n_min) {
+ error = xasprintf("%s: %u %s specified but the minimum number is %u",
+ s, datum->n, is_map ? "pair(s)" : "value(s)",
+ type->n_min);
+ goto error;
+ } else if (datum->n > type->n_max) {
+ error = xasprintf("%s: %u %s specified but the maximum number is %u",
+ s, datum->n, is_map ? "pair(s)" : "value(s)",
+ type->n_max);
+ goto error;
+ }
+
+ dberror = ovsdb_datum_sort(datum, type);
+ if (dberror) {
+ ovsdb_error_destroy(dberror);
+ if (ovsdb_type_is_map(type)) {
+ error = xasprintf("%s: map contains duplicate key", s);
+ } else {
+ error = xasprintf("%s: set contains duplicate value", s);
+ }
+ goto error;
+ }
+
+ return NULL;
+
+error:
+ ovsdb_datum_destroy(datum, type);
+ ovsdb_datum_init_empty(datum);
+ return error;
+}
+
+/* Appends to 'out' the 'datum' (with the given 'type') in a format acceptable
+ * to ovsdb_datum_from_string(). */
+void
+ovsdb_datum_to_string(const struct ovsdb_datum *datum,
+ const struct ovsdb_type *type, struct ds *out)
+{
+ bool is_map = ovsdb_type_is_map(type);
+ size_t i;
+
+ if (type->n_max > 1 || !datum->n) {
+ ds_put_char(out, is_map ? '{' : '[');
+ }
+ for (i = 0; i < datum->n; i++) {
+ if (i > 0) {
+ ds_put_cstr(out, ", ");
+ }
+
+ ovsdb_atom_to_string(&datum->keys[i], type->key_type, out);
+ if (is_map) {
+ ds_put_char(out, '=');
+ ovsdb_atom_to_string(&datum->values[i], type->value_type, out);
+ }
+ }
+ if (type->n_max > 1 || !datum->n) {
+ ds_put_char(out, is_map ? '}' : ']');
+ }
+}
+