From cf5e75010ed9fa9b8eb75a71d1419d40e1a8ddd8 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 13 Apr 2011 21:24:30 -0700 Subject: [PATCH] value-labels: Interpret \n as new-line in value labels. Bug #18497. --- doc/variables.texi | 4 ++ src/data/por-file-writer.c | 2 +- src/data/sys-file-writer.c | 7 ++- src/data/value-labels.c | 56 ++++++++++++++---- src/data/value-labels.h | 21 ++++++- src/data/variable.c | 17 +++--- src/language/dictionary/sys-file-info.c | 2 +- src/ui/gui/psppire-var-store.c | 3 +- src/ui/gui/text-data-import-dialog.c | 2 +- src/ui/gui/val-labs-dialog.c | 12 ++-- src/ui/gui/variable-info-dialog.c | 5 +- tests/language/dictionary/value-labels.at | 69 +++++++++++++++++++++++ 12 files changed, 167 insertions(+), 33 deletions(-) diff --git a/doc/variables.texi b/doc/variables.texi index dbb3f228..650c7d2c 100644 --- a/doc/variables.texi +++ b/doc/variables.texi @@ -454,6 +454,10 @@ To set up value labels for a set of variables, specify the variable names after a slash (@samp{/}), followed by a list of values and their associated labels, separated by spaces. +Value labels in output are normally broken into lines automatically. +Put @samp{\n} in a label string to force a line break at that point. +The label may still be broken into lines at additional points. + Before @cmd{VALUE LABELS} is executed, any existing value labels are cleared from the variables specified. Use @cmd{ADD VALUE LABELS} (@pxref{ADD VALUE LABELS}) to add value labels without clearing those diff --git a/src/data/por-file-writer.c b/src/data/por-file-writer.c index ea0f9dc8..3c475d30 100644 --- a/src/data/por-file-writer.c +++ b/src/data/por-file-writer.c @@ -419,7 +419,7 @@ write_value_labels (struct pfm_writer *w, const struct dictionary *dict) { const struct val_lab *vl = labels[i]; write_value (w, val_lab_get_value (vl), var_get_width (v)); - write_string (w, val_lab_get_label (vl)); + write_string (w, val_lab_get_escaped_label (vl)); } free (labels); } diff --git a/src/data/sys-file-writer.c b/src/data/sys-file-writer.c index b1cb7c22..d2522c23 100644 --- a/src/data/sys-file-writer.c +++ b/src/data/sys-file-writer.c @@ -532,7 +532,8 @@ write_value_labels (struct sfm_writer *w, struct variable *v, int idx) for (i = 0; i < n_labels; i++) { const struct val_lab *vl = labels[i]; - char *label = recode_string (var_get_encoding (v), UTF8, val_lab_get_label (vl), -1); + char *label = recode_string (var_get_encoding (v), UTF8, + val_lab_get_escaped_label (vl), -1); uint8_t len = MIN (strlen (label), 255); write_value (w, val_lab_get_value (vl), var_get_width (v)); @@ -782,7 +783,7 @@ write_long_string_value_labels (struct sfm_writer *w, size += 12 + strlen (var_get_name (var)); for (val_lab = val_labs_first (val_labs); val_lab != NULL; val_lab = val_labs_next (val_labs, val_lab)) - size += 8 + width + strlen (val_lab_get_label (val_lab)); + size += 8 + width + strlen (val_lab_get_escaped_label (val_lab)); } if (size == 0) return; @@ -811,7 +812,7 @@ write_long_string_value_labels (struct sfm_writer *w, for (val_lab = val_labs_first (val_labs); val_lab != NULL; val_lab = val_labs_next (val_labs, val_lab)) { - const char *label = val_lab_get_label (val_lab); + const char *label = val_lab_get_escaped_label (val_lab); size_t label_length = strlen (label); write_int (w, width); diff --git a/src/data/value-labels.c b/src/data/value-labels.c index 6d8f5175..6d7f19c1 100644 --- a/src/data/value-labels.c +++ b/src/data/value-labels.c @@ -58,7 +58,7 @@ val_labs_clone (const struct val_labs *vls) copy = val_labs_create (vls->width); HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels) - val_labs_add (copy, &label->value, label->label); + val_labs_add (copy, &label->value, label->escaped_label); return copy; } @@ -114,6 +114,7 @@ val_labs_clear (struct val_labs *vls) hmap_delete (&vls->labels, &label->node); value_destroy (&label->value, vls->width); intern_unref (label->label); + intern_unref (label->escaped_label); free (label); } } @@ -133,18 +134,48 @@ val_labs_count (const struct val_labs *vls) return vls == NULL ? 0 : hmap_count (&vls->labels); } +static void +set_label (struct val_lab *lab, const char *escaped_label) +{ + lab->escaped_label = intern_new (escaped_label); + if (strstr (escaped_label, "\\n") == NULL) + lab->label = intern_ref (lab->escaped_label); + else + { + struct string s; + const char *p; + + ds_init_empty (&s); + ds_extend (&s, intern_strlen (lab->escaped_label)); + for (p = escaped_label; *p != '\0'; p++) + { + char c = *p; + if (c == '\\' && p[1] == 'n') + { + c = '\n'; + p++; + } + ds_put_byte (&s, c); + } + lab->label = intern_new (ds_cstr (&s)); + ds_destroy (&s); + } +} + static void do_add_val_lab (struct val_labs *vls, const union value *value, - const char *label) + const char *escaped_label) { struct val_lab *lab = xmalloc (sizeof *lab); value_clone (&lab->value, value, vls->width); - lab->label = intern_new (label); + set_label (lab, escaped_label); hmap_insert (&vls->labels, &lab->node, value_hash (value, vls->width, 0)); } -/* If VLS does not already contain a value label for VALUE, adds - LABEL for it and returns true. Otherwise, returns false. */ +/* If VLS does not already contain a value label for VALUE, adds the UTF-8 + encoded LABEL for it and returns true. Otherwise, returns false. + + In LABEL, the two-byte sequence "\\n" is interpreted as a new-line. */ bool val_labs_add (struct val_labs *vls, const union value *value, const char *label) @@ -160,7 +191,9 @@ val_labs_add (struct val_labs *vls, const union value *value, } /* Sets LABEL as the value label for VALUE in VLS, replacing any - existing label for VALUE. */ + existing label for VALUE. + + In LABEL, the two-byte sequence "\\n" is interpreted as a new-line. */ void val_labs_replace (struct val_labs *vls, const union value *value, const char *label) @@ -169,7 +202,8 @@ val_labs_replace (struct val_labs *vls, const union value *value, if (vl != NULL) { intern_unref (vl->label); - vl->label = intern_new (label); + intern_unref (vl->escaped_label); + set_label (vl, label); } else do_add_val_lab (vls, value, label); @@ -182,12 +216,14 @@ val_labs_remove (struct val_labs *vls, struct val_lab *label) hmap_delete (&vls->labels, &label->node); value_destroy (&label->value, vls->width); intern_unref (label->label); + intern_unref (label->escaped_label); free (label); } -/* Searches VLS for a value label for VALUE. If successful, - returns the string used as the label; otherwise, returns a - null pointer. Returns a null pointer if VLS is null. */ +/* Searches VLS for a value label for VALUE. If successful, returns the string + used as the label, as a UTF-8 encoded string in a format suitable for + output. Otherwise, returns a null pointer. Returns a null pointer if VLS + is null. */ const char * val_labs_find (const struct val_labs *vls, const union value *value) { diff --git a/src/data/value-labels.h b/src/data/value-labels.h index c6d7f912..6c13ec9a 100644 --- a/src/data/value-labels.h +++ b/src/data/value-labels.h @@ -39,6 +39,7 @@ struct val_lab struct hmap_node node; /* Node in hash map. */ union value value; /* The value being labeled. */ const char *label; /* An interned string. */ + const char *escaped_label; /* An interned string. */ }; /* Returns the value in VL. The caller must not modify or free @@ -52,13 +53,27 @@ static inline const union value *val_lab_get_value (const struct val_lab *vl) return &vl->value; } -/* Returns the label in VL. The caller must not modify or free the returned - value. */ +/* Returns the label in VL as a UTF-8 encoded interned string, in a format + appropriate for use in output. The caller must not modify or free the + returned value. */ static inline const char * val_lab_get_label (const struct val_lab *vl) { return vl->label; } + +/* Returns the label in VL as a UTF-8 encoded interned string. Any new-line + characters in the label's usual output form are represented in the returned + string as the two-byte sequence "\\n". This form is used on the VALUE + LABELS command, in system and portable files, and passed to val_labs_add() + and val_labs_replace(). + + The caller must not modify or free the returned value. */ +static inline const char * +val_lab_get_escaped_label (const struct val_lab *vl) +{ + return vl->escaped_label; +} /* A set of value labels. */ struct val_labs @@ -77,7 +92,7 @@ size_t val_labs_count (const struct val_labs *); /* Looking up value labels. */ const char *val_labs_find (const struct val_labs *, const union value *); struct val_lab *val_labs_lookup (const struct val_labs *, - const union value *); + const union value *); /* Basic properties. */ size_t val_labs_count (const struct val_labs *); diff --git a/src/data/variable.c b/src/data/variable.c index 029e3f49..c83c31ad 100644 --- a/src/data/variable.c +++ b/src/data/variable.c @@ -427,9 +427,11 @@ alloc_value_labels (struct variable *v) v->val_labs = val_labs_create (v->width); } -/* Attempts to add a value label with the given VALUE and LABEL - to V. Returns true if successful, false otherwise (probably - due to an existing label). */ +/* Attempts to add a value label with the given VALUE and UTF-8 encoded LABEL + to V. Returns true if successful, false otherwise (probably due to an + existing label). + + In LABEL, the two-byte sequence "\\n" is interpreted as a new-line. */ bool var_add_value_label (struct variable *v, const union value *value, const char *label) @@ -438,9 +440,10 @@ var_add_value_label (struct variable *v, return val_labs_add (v->val_labs, value, label); } -/* Adds or replaces a value label with the given VALUE and LABEL +/* Adds or replaces a value label with the given VALUE and UTF-8 encoded LABEL to V. -*/ + + In LABEL, the two-byte sequence "\\n" is interpreted as a new-line. */ void var_replace_value_label (struct variable *v, const union value *value, const char *label) @@ -456,8 +459,8 @@ var_clear_value_labels (struct variable *v) var_set_value_labels (v, NULL); } -/* Returns the label associated with VALUE for variable V, - or a null pointer if none. */ +/* Returns the label associated with VALUE for variable V, as a UTF-8 string in + a format suitable for output, or a null pointer if none. */ const char * var_lookup_value_label (const struct variable *v, const union value *value) { diff --git a/src/language/dictionary/sys-file-info.c b/src/language/dictionary/sys-file-info.c index 7653e046..84a4d8e8 100644 --- a/src/language/dictionary/sys-file-info.c +++ b/src/language/dictionary/sys-file-info.c @@ -586,7 +586,7 @@ describe_variable (const struct variable *v, struct tab_table *t, int r, const struct val_lab *vl = labels[i]; tab_value (t, 1, r, TAB_NONE, &vl->value, v, NULL); - tab_text (t, 2, r, TAB_LEFT, val_lab_get_label (vl)); + tab_text (t, 2, r, TAB_LEFT, val_lab_get_escaped_label (vl)); r++; } free (labels); diff --git a/src/ui/gui/psppire-var-store.c b/src/ui/gui/psppire-var-store.c index 2a915afe..d495bd98 100644 --- a/src/ui/gui/psppire-var-store.c +++ b/src/ui/gui/psppire-var-store.c @@ -753,7 +753,8 @@ text_for_column (PsppireVarStore *vs, { gchar *const vstr = value_to_text (vl->value, dict, *write_spec); - return g_strdup_printf (_("{%s,`%s'}_"), vstr, val_lab_get_label (vl)); + return g_strdup_printf (_("{%s,`%s'}_"), vstr, + val_lab_get_escaped_label (vl)); } } } diff --git a/src/ui/gui/text-data-import-dialog.c b/src/ui/gui/text-data-import-dialog.c index 534cec9e..14a23a79 100644 --- a/src/ui/gui/text-data-import-dialog.c +++ b/src/ui/gui/text-data-import-dialog.c @@ -330,7 +330,7 @@ apply_dict (const struct dictionary *dict, struct string *s) ds_put_cstr (s, "\n "); syntax_gen_value (s, &vl->value, width, format); ds_put_byte (s, ' '); - syntax_gen_string (s, ss_cstr (val_lab_get_label (vl))); + syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl))); } free (labels); ds_put_cstr (s, ".\n"); diff --git a/src/ui/gui/val-labs-dialog.c b/src/ui/gui/val-labs-dialog.c index efeb548e..605be36f 100644 --- a/src/ui/gui/val-labs-dialog.c +++ b/src/ui/gui/val-labs-dialog.c @@ -1,5 +1,5 @@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2005, 2009 Free Software Foundation + Copyright (C) 2005, 2009, 2011 Free Software Foundation 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 @@ -261,7 +261,11 @@ get_selected_tuple (struct val_labs_dialog *dialog, if (valuep != NULL) *valuep = value; if (label != NULL) - *label = val_labs_find (dialog->labs, &value); + { + struct val_lab *vl = val_labs_lookup (dialog->labs, &value); + if (vl != NULL) + *label = val_lab_get_escaped_label (vl); + } } @@ -512,8 +516,8 @@ repopulate_dialog (struct val_labs_dialog *dialog) value_to_text (vl->value, dialog->dict, *var_get_write_format (dialog->pv)); - gchar *const text = g_strdup_printf (_("%s = `%s'"), - vstr, val_lab_get_label (vl)); + gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr, + val_lab_get_escaped_label (vl)); gtk_list_store_append (list_store, &iter); gtk_list_store_set (list_store, &iter, diff --git a/src/ui/gui/variable-info-dialog.c b/src/ui/gui/variable-info-dialog.c index d0b3eeae..088083f5 100644 --- a/src/ui/gui/variable-info-dialog.c +++ b/src/ui/gui/variable-info-dialog.c @@ -1,5 +1,5 @@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2007, 2009, 2010 Free Software Foundation + Copyright (C) 2007, 2009, 2010, 2011 Free Software Foundation 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 @@ -113,7 +113,8 @@ populate_text (PsppireDictView *treeview, gpointer data) gchar *const vstr = value_to_text (vl->value, dict, *var_get_print_format (var)); - g_string_append_printf (gstring, _("%s %s\n"), vstr, val_lab_get_label (vl)); + g_string_append_printf (gstring, _("%s %s\n"), + vstr, val_lab_get_escaped_label (vl)); g_free (vstr); } diff --git a/tests/language/dictionary/value-labels.at b/tests/language/dictionary/value-labels.at index feec9d86..4c9f4397 100644 --- a/tests/language/dictionary/value-labels.at +++ b/tests/language/dictionary/value-labels.at @@ -26,6 +26,75 @@ dt,Format: DATETIME20.0,,2 ]) AT_CLEANUP +AT_SETUP([VALUE LABELS with new-line]) +AT_DATA([value-labels.sps], [dnl +DATA LIST LIST NOTABLE /x. +VALUE LABELS x 1 'one' 2 'first line\nsecond line' 3 'three'. +BEGIN DATA. +1 +2 +3 +END DATA. +DISPLAY DICTIONARY. +FREQUENCIES x/STAT=NONE. +]) +AT_CHECK([pspp -O format=csv value-labels.sps], [0], [dnl +Variable,Description,,Position +x,Format: F8.2,,1 +,Measure: Scale,, +,Display Alignment: Right,, +,Display Width: 8,, +,1.00,one, +,2.00,first line\nsecond line, +,3.00,three, + +Table: x +Value Label,Value,Frequency,Percent,Valid Percent,Cum Percent +one,1.00,1,33.33,33.33,33.33 +"first line +second line",2.00,1,33.33,33.33,66.67 +three,3.00,1,33.33,33.33,100.00 +Total,,3,100.0,100.0, +]) +AT_CLEANUP + +AT_SETUP([VALUE LABELS with new-line in system file]) +AT_DATA([save.sps], [dnl +DATA LIST LIST NOTABLE /x. +VALUE LABELS x 1 'one' 2 'first line\nsecond line' 3 'three'. +BEGIN DATA. +1 +2 +3 +END DATA. +SAVE OUTFILE='value-labels.sav'. +]) +AT_CHECK([pspp -O format=csv save.sps]) +AT_DATA([get.sps], [dnl +GET FILE='value-labels.sav'. +DISPLAY DICTIONARY. +FREQUENCIES x/STAT=NONE. +]) +AT_CHECK([pspp -O format=csv get.sps], [0], [dnl +Variable,Description,,Position +x,Format: F8.2,,1 +,Measure: Scale,, +,Display Alignment: Right,, +,Display Width: 8,, +,1.00,one, +,2.00,first line\nsecond line, +,3.00,three, + +Table: x +Value Label,Value,Frequency,Percent,Valid Percent,Cum Percent +one,1.00,1,33.33,33.33,33.33 +"first line +second line",2.00,1,33.33,33.33,66.67 +three,3.00,1,33.33,33.33,100.00 +Total,,3,100.0,100.0, +]) +AT_CLEANUP + dnl Tests for a bug which caused VALUE LABELS to dnl crash when given invalid syntax. AT_SETUP([VALUE LABELS invalid syntax bug]) -- 2.30.2