value-labels: Interpret \n as new-line in value labels.
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 14 Apr 2011 04:24:30 +0000 (21:24 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 14 Apr 2011 04:24:30 +0000 (21:24 -0700)
Bug #18497.

12 files changed:
doc/variables.texi
src/data/por-file-writer.c
src/data/sys-file-writer.c
src/data/value-labels.c
src/data/value-labels.h
src/data/variable.c
src/language/dictionary/sys-file-info.c
src/ui/gui/psppire-var-store.c
src/ui/gui/text-data-import-dialog.c
src/ui/gui/val-labs-dialog.c
src/ui/gui/variable-info-dialog.c
tests/language/dictionary/value-labels.at

index dbb3f228b3e770fab4e8909f86b3824b9f2ca2cb..650c7d2c6143d05acb34af577a1458d82b4c227d 100644 (file)
@@ -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
index ea0f9dc88bfe98bebbbd2422881c664d2b76fbaa..3c475d30b58d83b9e0217e88d8d22d05afe4383b 100644 (file)
@@ -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);
     }
index b1cb7c22c0dcd5e7a1343041b66d75fcac28cd36..d2522c23f82d1093be8edcea269c5841fc3fde69 100644 (file)
@@ -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);
index 6d8f517510c59f62f7b29b852f4dd1107e01da45..6d7f19c17d7ee191862b29c454c7ef62540a8ad5 100644 (file)
@@ -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);
 }
 \f
+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)
 {
index c6d7f91201baeb426e73465e305ecd78c6aab4a7..6c13ec9a5088b4104f898603aa130c9edf3613e0 100644 (file)
@@ -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;
+}
 \f
 /* 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 *);
index 029e3f49dd74739f6c6a3f57b98f7300bdbc7f4b..c83c31ad72fb4d4dc21b1b001b5df176be8715d2 100644 (file)
@@ -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)
 {
index 7653e046bd7ea4b5f361f4eae47957fab5eff422..84a4d8e87c604cf64040ad2805ddb2a36b4f41e3 100644 (file)
@@ -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);
index 2a915afe149d6288a8af80c2ff201712ee63058a..d495bd98dd16d174a72f5ab9d9a211087394b11b 100644 (file)
@@ -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));
            }
          }
       }
index 534cec9e0884450bc4a3bfc49200c9b0d428ef15..14a23a79846cbfebc9ce7a15c94699ccc7846113 100644 (file)
@@ -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");
index efeb548e29d61060c5a781c1057716cff103fdab..605be36f7d608b991fcc0aeb5beef89e35f3e5f2 100644 (file)
@@ -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,
index d0b3eeaebf37e99b953537f208e8f8ce26c4f9fd..088083f56b03ab99738a104ec61f08d6b8173568 100644 (file)
@@ -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);
        }
index feec9d8627fa67149d787fea79ac62d90b585bb7..4c9f43976d52b69a70e9f3db7d5f280dfb8584e9 100644 (file)
@@ -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])