/* PSPP - a program for statistical analysis.
- Copyright (C) 1997-9, 2000, 2006, 2007, 2009, 2010, 2011 Free Software Foundation, Inc.
+ Copyright (C) 1997-9, 2000, 2006, 2007, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "libpspp/pool.h"
#include "libpspp/str.h"
#include "libpspp/string-array.h"
+#include "libpspp/ll.h"
#include "gl/intprops.h"
#include "gl/minmax.h"
/* A dictionary. */
struct dictionary
{
+ int ref_cnt;
struct vardict_info *var; /* Variables. */
size_t var_cnt, var_cap; /* Number of variables, capacity. */
struct caseproto *proto; /* Prototype for dictionary cases
struct mrset **mrsets; /* Multiple response sets. */
size_t n_mrsets; /* Number of multiple response sets. */
+ /* Whether variable names must be valid identifiers. Normally, this is
+ true, but sometimes a dictionary is prepared for external use
+ (e.g. output to a CSV file) where names don't have to be valid. */
+ bool names_must_be_ids;
+
char *encoding; /* Character encoding of string data */
const struct dict_callbacks *callbacks; /* Callbacks on dictionary
static void dict_unset_split_var (struct dictionary *, struct variable *);
static void dict_unset_mrset_var (struct dictionary *, struct variable *);
-void
-dict_set_encoding (struct dictionary *d, const char *enc)
-{
- if (enc)
- {
- free (d->encoding);
- d->encoding = xstrdup (enc);
- }
-}
-
+/* Returns the encoding for data in dictionary D. The return value is a
+ nonnull string that contains an IANA character set name. */
const char *
dict_get_encoding (const struct dictionary *d)
{
dict_id_is_valid (const struct dictionary *dict, const char *id,
bool issue_error)
{
- return id_is_valid (id, dict->encoding, issue_error);
+ return (!dict->names_must_be_ids
+ || id_is_valid (id, dict->encoding, issue_error));
}
void
dict_dump (const struct dictionary *d)
{
int i;
- for (i = 0 ; i < d->var_cnt ; ++i )
+ for (i = 0 ; i < d->var_cnt ; ++i)
{
const struct variable *v = d->var[i].var;
printf ("Name: %s;\tdict_idx: %zu; case_idx: %zu\n",
dest->cb_data = src->cb_data;
}
-/* Creates and returns a new dictionary. */
+/* Creates and returns a new dictionary with the specified ENCODING. */
struct dictionary *
-dict_create (void)
+dict_create (const char *encoding)
{
struct dictionary *d = xzalloc (sizeof *d);
+ d->encoding = xstrdup (encoding);
+ d->names_must_be_ids = true;
hmap_init (&d->name_map);
attrset_init (&d->attributes);
+ d->ref_cnt = 1;
+
return d;
}
+struct dictionary *
+dict_ref (struct dictionary *s)
+{
+ s->ref_cnt++;
+ return s;
+}
+
/* Creates and returns a (deep) copy of an existing
dictionary.
dictionary. If the new dictionary won't be used to access
cases produced with the old dictionary, then the new
dictionary's case indexes should be compacted with
- dict_compact_values to save space. */
+ dict_compact_values to save space.
+
+ Callbacks are not cloned. */
struct dictionary *
dict_clone (const struct dictionary *s)
{
struct dictionary *d;
size_t i;
- d = dict_create ();
+ d = dict_create (s->encoding);
+ dict_set_names_must_be_ids (d, dict_get_names_must_be_ids (s));
for (i = 0; i < s->var_cnt; i++)
{
d->split_cnt = s->split_cnt;
if (d->split_cnt > 0)
{
- d->split = xnmalloc (d->split_cnt, sizeof *d->split);
+ d->split = xnmalloc (d->split_cnt, sizeof *d->split);
for (i = 0; i < d->split_cnt; i++)
d->split[i] = dict_lookup_var_assert (d, var_get_name (s->split[i]));
}
for (i = 0; i < s->vector_cnt; i++)
d->vector[i] = vector_clone (s->vector[i], s, d);
- if ( s->encoding)
- d->encoding = xstrdup (s->encoding);
-
dict_set_attributes (d, dict_get_attributes (s));
for (i = 0; i < s->n_mrsets; i++)
{
/* FIXME? Should we really clear case_limit, label, documents?
Others are necessarily cleared by deleting all the variables.*/
- while (d->var_cnt > 0 )
+ while (d->var_cnt > 0)
{
dict_delete_var (d, d->var[d->var_cnt - 1].var);
}
attrset_clear (&d->attributes);
}
-/* Destroys the aux data for every variable in D, by calling
- var_clear_aux() for each variable. */
-void
-dict_clear_aux (struct dictionary *d)
+/* Clears a dictionary and destroys it. */
+static void
+_dict_destroy (struct dictionary *d)
{
- int i;
+ /* In general, we don't want callbacks occurring, if the dictionary
+ is being destroyed */
+ d->callbacks = NULL ;
- for (i = 0; i < d->var_cnt; i++)
- var_clear_aux (d->var[i].var);
+ dict_clear (d);
+ string_array_destroy (&d->documents);
+ hmap_destroy (&d->name_map);
+ attrset_destroy (&d->attributes);
+ dict_clear_mrsets (d);
+ free (d->encoding);
+ free (d);
}
-/* Clears a dictionary and destroys it. */
void
-dict_destroy (struct dictionary *d)
+dict_unref (struct dictionary *d)
{
- if (d != NULL)
- {
- /* In general, we don't want callbacks occuring, if the dictionary
- is being destroyed */
- d->callbacks = NULL ;
-
- dict_clear (d);
- hmap_destroy (&d->name_map);
- attrset_destroy (&d->attributes);
- dict_clear_mrsets (d);
- free (d->encoding);
- free (d);
- }
+ if (d == NULL)
+ return;
+ d->ref_cnt--;
+ assert (d->ref_cnt >= 0);
+ if (d->ref_cnt == 0)
+ _dict_destroy (d);
}
/* Returns the number of variables in D. */
}
static struct variable *
-add_var (struct dictionary *d, struct variable *v)
+add_var_with_case_index (struct dictionary *d, struct variable *v,
+ int case_index)
{
struct vardict_info *vardict;
+ assert (case_index >= d->next_value_idx);
+
/* Update dictionary. */
if (d->var_cnt >= d->var_cap)
{
vardict->dict = d;
vardict->var = v;
hmap_insert (&d->name_map, &vardict->name_node,
- hash_case_string (var_get_name (v), 0));
- vardict->case_index = d->next_value_idx;
+ utf8_hash_case_string (var_get_name (v), 0));
+ vardict->case_index = case_index;
var_set_vardict (v, vardict);
- if ( d->changed ) d->changed (d, d->changed_data);
- if ( d->callbacks && d->callbacks->var_added )
+ if (d->changed) d->changed (d, d->changed_data);
+ if (d->callbacks && d->callbacks->var_added)
d->callbacks->var_added (d, var_get_dict_index (v), d->cb_data);
- d->next_value_idx++;
invalidate_proto (d);
+ d->next_value_idx = case_index + 1;
return v;
}
+static struct variable *
+add_var (struct dictionary *d, struct variable *v)
+{
+ return add_var_with_case_index (d, v, d->next_value_idx);
+}
+
/* Creates and returns a new variable in D with the given NAME
and WIDTH. Returns a null pointer if the given NAME would
duplicate that of an existing variable in the dictionary. */
return add_var (d, new_var);
}
+struct variable *
+dict_clone_var_in_place_assert (struct dictionary *d,
+ const struct variable *old_var)
+{
+ assert (dict_lookup_var (d, var_get_name (old_var)) == NULL);
+ return add_var_with_case_index (d, var_clone (old_var),
+ var_get_case_index (old_var));
+}
+
/* Returns the variable named NAME in D, or a null pointer if no
variable has that name. */
struct variable *
struct vardict_info *vardict;
HMAP_FOR_EACH_WITH_HASH (vardict, struct vardict_info, name_node,
- hash_case_string (name, 0), &d->name_map)
+ utf8_hash_case_string (name, 0), &d->name_map)
{
struct variable *var = vardict->var;
- if (!strcasecmp (var_get_name (var), name))
+ if (!utf8_strcasecmp (var_get_name (var), name))
return var;
}
static void
reindex_var (struct dictionary *d, struct vardict_info *vardict)
{
- struct variable *var = vardict->var;
+ struct variable *old = (d->callbacks && d->callbacks->var_changed
+ ? var_clone (vardict->var)
+ : NULL);
+ struct variable *var = vardict->var;
var_set_vardict (var, vardict);
hmap_insert_fast (&d->name_map, &vardict->name_node,
vardict->name_node.hash);
- if ( d->changed ) d->changed (d, d->changed_data);
- if ( d->callbacks && d->callbacks->var_changed )
- d->callbacks->var_changed (d, var_get_dict_index (var), d->cb_data);
+ if (d->changed) d->changed (d, d->changed_data);
+ if (old)
+ {
+ d->callbacks->var_changed (d, var_get_dict_index (var), VAR_TRAIT_POSITION, old, d->cb_data);
+ var_destroy (old);
+ }
}
/* Sets the case_index in V's vardict to CASE_INDEX. */
{
int dict_index = var_get_dict_index (v);
const int case_index = var_get_case_index (v);
- const int width = var_get_width (v);
assert (dict_contains_var (d, v));
- /* Delete aux data. */
- var_clear_aux (v);
-
dict_unset_split_var (d, v);
dict_unset_mrset_var (d, v);
/* Free memory. */
var_clear_vardict (v);
- var_destroy (v);
- if ( d->changed ) d->changed (d, d->changed_data);
+ if (d->changed) d->changed (d, d->changed_data);
invalidate_proto (d);
- if (d->callbacks && d->callbacks->var_deleted )
- d->callbacks->var_deleted (d, dict_index, case_index, width, d->cb_data);
+ if (d->callbacks && d->callbacks->var_deleted)
+ d->callbacks->var_deleted (d, v, dict_index, case_index, d->cb_data);
+
+ var_destroy (v);
}
/* Deletes the COUNT variables listed in VARS from D. This is
/* Deletes the COUNT variables in D starting at index IDX. This
is unsafe; see the comment on dict_delete_var() for
- details. */
+ details. Deleting consecutive vars will result in less callbacks
+ compared to iterating over dict_delete_var.
+ A simple while loop over dict_delete_var will
+ produce (d->var_cnt - IDX) * COUNT variable changed callbacks
+ plus COUNT variable delete callbacks.
+ This here produces d->var_cnt - IDX variable changed callbacks
+ plus COUNT variable delete callbacks. */
void
dict_delete_consecutive_vars (struct dictionary *d, size_t idx, size_t count)
{
- /* FIXME: this can be done in O(count) time, but this algorithm
- is O(count**2). */
assert (idx + count <= d->var_cnt);
- while (count-- > 0)
- dict_delete_var (d, d->var[idx].var);
+ /* We need to store the variable and the corresponding case_index
+ for the delete callbacks later. We store them in a linked list.*/
+ struct delvar {
+ struct ll ll;
+ struct variable *var;
+ int case_index;
+ };
+ struct ll_list list = LL_INITIALIZER (list);
+
+ for (size_t i = idx; i < idx + count; i++)
+ {
+ struct delvar *dv = xmalloc (sizeof (struct delvar));
+ assert (dv);
+ struct variable *v = d->var[i].var;
+
+ dict_unset_split_var (d, v);
+ dict_unset_mrset_var (d, v);
+
+ if (d->weight == v)
+ dict_set_weight (d, NULL);
+
+ if (d->filter == v)
+ dict_set_filter (d, NULL);
+
+ dv->var = v;
+ dv->case_index = var_get_case_index (v);
+ ll_push_tail (&list, (struct ll *)dv);
+ }
+
+ dict_clear_vectors (d);
+
+ /* Remove variables from var array. */
+ unindex_vars (d, idx, d->var_cnt);
+ remove_range (d->var, d->var_cnt, sizeof *d->var, idx, count);
+ d->var_cnt -= count;
+
+ /* Reindexing will result variable-changed callback */
+ reindex_vars (d, idx, d->var_cnt);
+
+ invalidate_proto (d);
+ if (d->changed) d->changed (d, d->changed_data);
+
+ /* Now issue the variable delete callbacks and delete
+ the variables. The vardict is not valid at this point
+ anymore. That is the reason why we stored the
+ caseindex before reindexing. */
+ for (size_t vi = idx; vi < idx + count; vi++)
+ {
+ struct delvar *dv = (struct delvar *) ll_pop_head (&list);
+ var_clear_vardict (dv->var);
+ if (d->callbacks && d->callbacks->var_deleted)
+ d->callbacks->var_deleted (d, dv->var, vi, dv->case_index, d->cb_data);
+ var_destroy (dv->var);
+ free (dv);
+ }
}
/* Deletes scratch variables from dictionary D. */
/* FIXME: this can be done in O(count) time, but this algorithm
is O(count**2). */
- for (i = 0; i < d->var_cnt; )
+ for (i = 0; i < d->var_cnt;)
if (var_get_dict_class (d->var[i].var) == DC_SCRATCH)
dict_delete_var (d, d->var[i].var);
else
struct vardict_info *vardict = var_get_vardict (v);
var_clear_vardict (v);
var_set_name (v, new_name);
- vardict->name_node.hash = hash_case_string (new_name, 0);
+ vardict->name_node.hash = utf8_hash_case_string (new_name, 0);
var_set_vardict (v, vardict);
}
-/* Changes the name of V in D to name NEW_NAME. Assert-fails if
- a variable named NEW_NAME is already in D, except that
- NEW_NAME may be the same as V's existing name. */
-void
-dict_rename_var (struct dictionary *d, struct variable *v,
- const char *new_name)
+/* Tries to changes the name of V in D to name NEW_NAME. Returns true if
+ successful, false if a variable (other than V) with the given name already
+ exists in D. */
+bool
+dict_try_rename_var (struct dictionary *d, struct variable *v,
+ const char *new_name)
{
- assert (!strcasecmp (var_get_name (v), new_name)
- || dict_lookup_var (d, new_name) == NULL);
+ struct variable *conflict = dict_lookup_var (d, new_name);
+ if (conflict && v != conflict)
+ return false;
+ struct variable *old = var_clone (v);
unindex_var (d, var_get_vardict (v));
rename_var (v, new_name);
reindex_var (d, var_get_vardict (v));
if (settings_get_algorithm () == ENHANCED)
var_clear_short_names (v);
- if ( d->changed ) d->changed (d, d->changed_data);
- if ( d->callbacks && d->callbacks->var_changed )
- d->callbacks->var_changed (d, var_get_dict_index (v), d->cb_data);
+ if (d->changed) d->changed (d, d->changed_data);
+ if (d->callbacks && d->callbacks->var_changed)
+ d->callbacks->var_changed (d, var_get_dict_index (v), VAR_TRAIT_NAME, old, d->cb_data);
+
+ var_destroy (old);
+
+ return true;
+}
+
+/* Changes the name of V in D to name NEW_NAME. Assert-fails if
+ a variable named NEW_NAME is already in D, except that
+ NEW_NAME may be the same as V's existing name. */
+void
+dict_rename_var (struct dictionary *d, struct variable *v,
+ const char *new_name)
+{
+ bool ok UNUSED = dict_try_rename_var (d, v, new_name);
+ assert (ok);
}
/* Renames COUNT variables specified in VARS to the names given
char *name;
suffix[0] = '_';
- if (!str_format_26adic (i + 1, &suffix[1], sizeof suffix - 1))
+ if (!str_format_26adic (i + 1, true, &suffix[1], sizeof suffix - 1))
NOT_REACHED ();
name = utf8_encoding_concat (root, suffix, dict->encoding, 64);
return make_numeric_name (dict, num_start);
}
+/* Returns whether variable names must be valid identifiers. Normally, this is
+ true, but sometimes a dictionary is prepared for external use (e.g. output
+ to a CSV file) where names don't have to be valid. */
+bool
+dict_get_names_must_be_ids (const struct dictionary *d)
+{
+ return d->names_must_be_ids;
+}
+
+/* Sets whether variable names must be valid identifiers. Normally, this is
+ true, but sometimes a dictionary is prepared for external use (e.g. output
+ to a CSV file) where names don't have to be valid.
+
+ Changing this setting from false to true doesn't make the dictionary check
+ all the existing variable names, so it can cause an invariant violation. */
+void
+dict_set_names_must_be_ids (struct dictionary *d, bool names_must_be_ids)
+{
+ d->names_must_be_ids = names_must_be_ids;
+}
+
/* Returns the weighting variable in dictionary D, or a null
pointer if the dictionary is unweighted. */
struct variable *
else
{
double w = case_num (c, d->weight);
- if (w < 0.0 || var_is_num_missing (d->weight, w, MV_ANY))
- w = 0.0;
- if ( w == 0.0 && warn_on_invalid != NULL && *warn_on_invalid ) {
- *warn_on_invalid = false;
- msg (SW, _("At least one case in the data file had a weight value "
- "that was user-missing, system-missing, zero, or "
- "negative. These case(s) were ignored."));
- }
- return w;
+
+ return var_force_valid_weight (d->weight, w, warn_on_invalid);
}
}
+/* Returns the format to use for weights. */
+const struct fmt_spec *
+dict_get_weight_format (const struct dictionary *d)
+{
+ return d->weight ? var_get_print_format (d->weight) : &F_8_0;
+}
+
/* Sets the weighting variable of D to V, or turning off
weighting if V is a null pointer. */
void
d->weight = v;
if (d->changed) d->changed (d, d->changed_data);
- if ( d->callbacks && d->callbacks->weight_changed )
+ if (d->callbacks && d->callbacks->weight_changed)
d->callbacks->weight_changed (d,
v ? var_get_dict_index (v) : -1,
d->cb_data);
assert (v == NULL || dict_contains_var (d, v));
assert (v == NULL || var_is_numeric (v));
+ /* When a filter is set, we ref the dictionary.
+ This is because the GUI maintains a pointer
+ to the dict's variables, and the variables'
+ addresses change in the callback. */
+ if (d->filter == NULL && v != NULL)
+ {
+ d = dict_ref (d);
+ }
+
+ /* Deref the dict when a filter is removed. */
+ if (d->filter != NULL && v == NULL)
+ {
+ assert (d->ref_cnt > 0);
+ dict_unref (d);
+ }
+
d->filter = v;
if (d->changed) d->changed (d, d->changed_data);
- if ( d->callbacks && d->callbacks->filter_changed )
+ if (d->callbacks && d->callbacks->filter_changed)
d->callbacks->filter_changed (d,
v ? var_get_dict_index (v) : -1,
d->cb_data);
assert (cnt == 0 || split != NULL);
d->split_cnt = cnt;
- if ( cnt > 0 )
+ if (cnt > 0)
{
d->split = xnrealloc (d->split, cnt, sizeof *d->split) ;
memcpy (d->split, split, cnt * sizeof *d->split);
}
if (d->changed) d->changed (d, d->changed_data);
- if ( d->callbacks && d->callbacks->split_changed )
+ if (d->callbacks && d->callbacks->split_changed)
d->callbacks->split_changed (d, d->cb_data);
}
return d->label;
}
-/* Sets D's file label to LABEL, truncating it to a maximum of 60
- characters.
+/* Sets D's file label to LABEL, truncating it to at most 60 bytes in D's
+ encoding.
Removes D's label if LABEL is null or the empty string. */
void
dict_set_label (struct dictionary *d, const char *label)
{
free (d->label);
- d->label = label != NULL && label[0] != '\0' ? xstrndup (label, 60) : NULL;
+ if (label == NULL || label[0] == '\0')
+ d->label = NULL;
+ else
+ d->label = utf8_encoding_trunc (label, d->encoding, 60);
}
/* Returns the documents for D, as an UTF-8 encoded string_array. The
const char *s;
dict_clear_documents (d);
- for (s = new_docs; *s != '\0'; )
+ for (s = new_docs; *s != '\0';)
{
size_t len = strcspn (s, "\n");
char *line = xmemdup0 (s, len);
{
size_t i;
for (i = 0; i < d->vector_cnt; i++)
- if (!strcasecmp (vector_get_name (d->vector[i]), name))
+ if (!utf8_strcasecmp (vector_get_name (d->vector[i]), name))
return d->vector[i];
return NULL;
}
size_t i;
for (i = 0; i < dict->n_mrsets; i++)
- if (!strcasecmp (name, dict->mrsets[i]->name))
+ if (!utf8_strcasecmp (name, dict->mrsets[i]->name))
return i;
return SIZE_MAX;
assert (dict_contains_var (dict, var));
- for (i = 0; i < dict->n_mrsets; )
+ for (i = 0; i < dict->n_mrsets;)
{
struct mrset *mrset = dict->mrsets[i];
size_t j;
- for (j = 0; j < mrset->n_vars; )
+ for (j = 0; j < mrset->n_vars;)
if (mrset->vars[j] == var)
remove_element (mrset->vars, mrset->n_vars--,
sizeof *mrset->vars, j);
calling dict_set_attributes for D will also destroy D's
attribute set. */
struct attrset *
-dict_get_attributes (const struct dictionary *d)
+dict_get_attributes (const struct dictionary *d)
{
return CONST_CAST (struct attrset *, &d->attributes);
}
/* Returns true if D has at least one attribute in its attribute
set, false if D's attribute set is empty. */
bool
-dict_has_attributes (const struct dictionary *d)
+dict_has_attributes (const struct dictionary *d)
{
return attrset_count (&d->attributes) > 0;
}
-/* Called from variable.c to notify the dictionary that some property of
- the variable has changed */
+/* Called from variable.c to notify the dictionary that some property (indicated
+ by WHAT) of the variable has changed. OLDVAR is a copy of V as it existed
+ prior to the change. OLDVAR is destroyed by this function.
+*/
void
-dict_var_changed (const struct variable *v)
+dict_var_changed (const struct variable *v, unsigned int what, struct variable *oldvar)
{
- if ( var_has_vardict (v))
+ if (var_has_vardict (v))
{
const struct vardict_info *vardict = var_get_vardict (v);
struct dictionary *d = vardict->dict;
- if ( NULL == d)
+ if (NULL == d)
return;
- if (d->changed ) d->changed (d, d->changed_data);
- if ( d->callbacks && d->callbacks->var_changed )
- d->callbacks->var_changed (d, var_get_dict_index (v), d->cb_data);
- }
-}
-
-
-/* Called from variable.c to notify the dictionary that the variable's width
- has changed */
-void
-dict_var_resized (const struct variable *v, int old_width)
-{
- if ( var_has_vardict (v))
- {
- const struct vardict_info *vardict = var_get_vardict (v);
- struct dictionary *d;
-
- d = vardict->dict;
-
if (d->changed) d->changed (d, d->changed_data);
-
- invalidate_proto (d);
- if ( d->callbacks && d->callbacks->var_resized )
- d->callbacks->var_resized (d, var_get_dict_index (v), old_width,
- d->cb_data);
+ if (d->callbacks && d->callbacks->var_changed)
+ d->callbacks->var_changed (d, var_get_dict_index (v), what, oldvar, d->cb_data);
}
+ var_destroy (oldvar);
}
-/* Called from variable.c to notify the dictionary that the variable's display width
- has changed */
-void
-dict_var_display_width_changed (const struct variable *v)
-{
- if ( var_has_vardict (v))
- {
- const struct vardict_info *vardict = var_get_vardict (v);
- struct dictionary *d;
- d = vardict->dict;
-
- if (d->changed) d->changed (d, d->changed_data);
- if ( d->callbacks && d->callbacks->var_display_width_changed )
- d->callbacks->var_display_width_changed (d, var_get_dict_index (v), d->cb_data);
- }
-}
\f
/* Dictionary used to contain "internal variables". */
static struct dictionary *internal_dict;
dict_create_internal_var (int case_idx, int width)
{
if (internal_dict == NULL)
- internal_dict = dict_create ();
+ internal_dict = dict_create ("UTF-8");
for (;;)
{
valgrind --leak-check --show-reachable won't show internal_dict. */
if (dict_get_var_cnt (internal_dict) == 0)
{
- dict_destroy (internal_dict);
+ dict_unref (internal_dict);
internal_dict = NULL;
}
}