spreadsheet-reader: Merge duplicate code with libpspp/str.h.
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 3 Jun 2023 17:02:28 +0000 (10:02 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 3 Jun 2023 17:10:13 +0000 (10:10 -0700)
The tree had two functions for 26-adic spreadsheet number formatting.  This
eliminates the duplication and moves the corresponding parsing function
into str.h as well.

src/data/dictionary.c
src/data/short-names.c
src/data/spreadsheet-reader.c
src/data/spreadsheet-reader.h
src/libpspp/str.c
src/libpspp/str.h
src/output/pivot-table.c
src/ui/gui/psppire-import-spreadsheet.c
tests/libpspp/str-test.c
tests/libpspp/stringi-map-test.c
tests/libpspp/stringi-set-test.c

index 10768ce9216f391a47e7f86b681608efad6f3d22..653ed941f1becfb7d94f1dd99e260ba8b4245539 100644 (file)
@@ -1168,13 +1168,11 @@ make_hinted_name (const struct dictionary *dict, const char *hint)
       for (i = 0; i < ULONG_MAX; i++)
         {
           char suffix[INT_BUFSIZE_BOUND (i) + 1];
-          char *name;
 
           suffix[0] = '_';
-          if (!str_format_26adic (i + 1, true, &suffix[1], sizeof suffix - 1))
-            NOT_REACHED ();
+          str_format_26adic (i + 1, true, &suffix[1], sizeof suffix - 1);
 
-          name = utf8_encoding_concat (root, suffix, dict->encoding, 64);
+          char *name = utf8_encoding_concat (root, suffix, dict->encoding, 64);
           if (var_name_is_insertable (dict, name))
             {
               free (root);
index ff6b4ff7739d0793a6f2b4c8bbb314b21281a7d7..78fc0f7325d32c6220f9c61320be50d81e8a5860 100644 (file)
@@ -62,7 +62,7 @@ assign_short_name (struct variable *v, size_t i,
       else
         {
           suffix[0] = '_';
-          str_format_26adic (trial, true, &suffix[1], sizeof suffix - 1);
+          str_format_26adic__ (trial, true, &suffix[1], sizeof suffix - 1);
         }
 
       /* Set name. */
index a16b430a52367fd9a670516f04b8eb0e4457e9e0..32021fa46c282f8b84ec0630879e7b5ffa9b995e 100644 (file)
 
 #include "spreadsheet-reader.h"
 
-#include <libpspp/assertion.h>
-#include "gnumeric-reader.h"
-#include "ods-reader.h"
-
-#include <libpspp/str.h>
 #include <stdio.h>
 #include <string.h>
-#include <gl/xalloc.h>
-#include <gl/c-xvasprintf.h>
 #include <stdlib.h>
 
+#include "data/gnumeric-reader.h"
+#include "data/ods-reader.h"
+#include "libpspp/assertion.h"
+#include "libpspp/str.h"
+
+#include "gl/xalloc.h"
+#include "gl/c-xvasprintf.h"
+
 struct spreadsheet *
 spreadsheet_ref (struct spreadsheet *s)
 {
@@ -89,115 +90,18 @@ spreadsheet_get_cell (struct spreadsheet *s, int n, int row, int column)
 }
 
 
-#define RADIX 26
-
-static void
-reverse (char *s, int len)
-{
-  int i;
-  for (i = 0; i < len / 2; ++i)
-    {
-      char tmp = s[len - i - 1];
-      s[len - i -1] = s[i];
-      s[i] = tmp;
-    }
-}
-
-
-/* Convert a string, which is an integer encoded in base26
-   IE, A=0, B=1, ... Z=25 to the integer it represents.
-   ... except that in this scheme, digits with an exponent
-   greater than 1 are implicitly incremented by 1, so
-   AA  = 0 + 1*26, AB = 1 + 1*26,
-   ABC = 2 + 2*26 + 1*26^2 ....
-   On error, this function returns -1
-*/
-int
-ps26_to_int (const char *str)
-{
-  int i;
-  int multiplier = 1;
-  int result = 0;
-  int len = strlen (str);
-
-  for (i = len - 1 ; i >= 0; --i)
-    {
-      char c = str[i];
-      if (c < 'A' || c > 'Z')
-       return -1;
-      int mantissa = (c - 'A');
-
-      assert (mantissa >= 0);
-      assert (mantissa < RADIX);
-
-      if (i != len - 1)
-       mantissa++;
-
-      result += mantissa * multiplier;
-      multiplier *= RADIX;
-    }
-
-  return result;
-}
-
-/* Convert an integer, which must be non-negative,
-   to pseudo base 26.
-   The caller must free the return value when no longer required.  */
-char *
-int_to_ps26 (int i)
-{
-  char *ret = NULL;
-
-  int lower = 0;
-  long long int base = RADIX;
-  int exp = 1;
-
-  if (i < 0)
-    return NULL;
-
-  while (i > lower + base - 1)
-    {
-      lower += base;
-      base *= RADIX;
-      assert (base > 0);
-      exp++;
-    }
-
-  i -= lower;
-  i += base;
-
-  ret = xmalloc (exp + 1);
-
-  exp = 0;
-  do
-    {
-      ret[exp++] = (i % RADIX) + 'A';
-      i /= RADIX;
-    }
-  while (i > 1);
-
-  ret[exp]='\0';
-
-  reverse (ret, exp);
-  return ret;
-}
-
-
 char *
 create_cell_ref (int col0, int row0)
 {
-  char *cs0 ;
-  char *s ;
-
-  if (col0 < 0) return NULL;
-  if (row0 < 0) return NULL;
-
-  cs0 = int_to_ps26 (col0);
-  s =  c_xasprintf ("%s%d", cs0, row0 + 1);
+  if (col0 < 0 || row0 < 0)
+    return NULL;
 
-  free (cs0);
+  char s[F26ADIC_STRLEN_MAX + INT_STRLEN_BOUND (row0) + 1];
+  str_format_26adic (col0 + 1, true, s, sizeof s);
+  size_t len = strlen (s);
+  snprintf (s + len, sizeof s - len, "%d", row0 + 1);
 
-  return s;
+  return xstrdup (s);
 }
 
 char *
@@ -236,10 +140,8 @@ convert_cell_ref (const char *ref,
   if (n != 4)
     return false;
 
-  str_uppercase (startcol);
-  *col0 = ps26_to_int (startcol);
-  str_uppercase (stopcol);
-  *coli = ps26_to_int (stopcol);
+  *col0 = str_parse_26adic (startcol);
+  *coli = str_parse_26adic (stopcol);
   *row0 = startrow - 1;
   *rowi = stoprow - 1 ;
 
index d7783bab7fdf12eb88ae6697e6c12ffd987370d5..8e13d03b13cd8c8c18b53ac3df26648ed504d4c8 100644 (file)
@@ -39,8 +39,6 @@ struct spreadsheet_read_options
   int asw ;                /* The width of string variables in the created dictionary */
 };
 
-int ps26_to_int (const char *str);
-char * int_to_ps26 (int);
 
 bool convert_cell_ref (const char *ref,
                       int *col0, int *row0,
index f85844076bc7a2833f3674feecd5eee2d67408f4..a85d1fb9efc9fb8a1af8052fd1226d65e65ffa80 100644 (file)
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <unistr.h>
 
+#include "libpspp/assertion.h"
 #include "libpspp/cast.h"
 #include "libpspp/i18n.h"
 #include "libpspp/message.h"
@@ -262,10 +263,14 @@ str_lowercase (char *s)
    which has room for SIZE bytes.  Uses uppercase if UPPERCASE is
    true, otherwise lowercase, Returns true if successful, false
    if NUMBER, plus a trailing null, is too large to fit in the
-   available space.
+   available space.  If SIZE is at least F26ADIC_STRLEN_MAX + 1,
+   the number is guaranteed to fit.  Even if the number doesn't
+   fit, if SIZE > 0, the characters that do fit, if any, will be
+   null-terminated.
 
    26-adic notation is "spreadsheet column numbering": 1 = A, 2 =
-   B, 3 = C, ... 26 = Z, 27 = AA, 28 = AB, 29 = AC, ...
+   B, 3 = C, ... 26 = Z, 27 = AA, 28 = AB, 29 = AC, ...  Zero is
+   the empty string.
 
    26-adic notation is the special case of a k-adic numeration
    system (aka bijective base-k numeration) with k=26.  In k-adic
@@ -274,8 +279,8 @@ str_lowercase (char *s)
    For more information, see
    http://en.wikipedia.org/wiki/Bijective_numeration. */
 bool
-str_format_26adic (unsigned long int number, bool uppercase,
-                   char buffer[], size_t size)
+str_format_26adic__ (unsigned long int number, bool uppercase,
+                     char buffer[], size_t size)
 {
   const char *alphabet
     = uppercase ? "ABCDEFGHIJKLMNOPQRSTUVWXYZ" : "abcdefghijklmnopqrstuvwxyz";
@@ -302,6 +307,50 @@ overflow:
   return false;
 }
 
+/* Like str_format_26adic__(), but SIZE must be big enough that it cannot
+   fail. */
+void
+str_format_26adic (unsigned long int number, bool uppercase,
+                   char buffer[], size_t size)
+{
+  assert (size >= F26ADIC_STRLEN_MAX + 1);
+  if (!str_format_26adic__ (number, uppercase, buffer, size))
+    NOT_REACHED ();
+}
+
+/* Returns the value of 26-adic string STR.  See str_format_26adic() for a
+   definition of 26-adic.
+
+   On error, this function returns -1. */
+int
+str_parse_26adic (const char *str)
+{
+  enum { RADIX = 26 };
+
+  int multiplier = 1;
+  int result = 0;
+
+  size_t len = strlen (str);
+  for (size_t i = 0; i < len; i++)
+    {
+      if (result >= INT_MAX / RADIX)
+        return -1;
+
+      char c = str[len - i - 1];
+      int digit = (c >= 'A' && c <= 'Z' ? c - 'A'
+                   : c >= 'a' && c <= 'z' ? c - 'a'
+                   : -1);
+      if (digit < 0)
+       return -1;
+      assert (digit >= 0 && digit < RADIX);
+
+      result += (digit + (i > 0)) * multiplier;
+      multiplier *= RADIX;
+    }
+
+  return result;
+}
+
 /* Copies IN to buffer OUT with size OUT_SIZE, appending a null terminator.  If
    IN is too long for OUT, or if IN contains a new-line, replaces the tail with
    "...".
index 77ba625decd2b0a15a0edd8a17a3dbd97186e06e..2fbbf510b37568cdc78ae4a568cee25e0a1039f6 100644 (file)
@@ -18,6 +18,7 @@
 #define str_h 1
 
 #include <assert.h>
+#include <limits.h>
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdint.h>
@@ -30,6 +31,7 @@
 #include "xstrndup.h"
 #include "xvasprintf.h"
 
+#include "gl/verify.h"
 #include "gl/xalloc.h"
 \f
 /* Miscellaneous. */
@@ -51,8 +53,15 @@ void str_copy_buf_trunc (char *, size_t, const char *, size_t);
 void str_uppercase (char *);
 void str_lowercase (char *);
 
-bool str_format_26adic (unsigned long int number, bool uppercase,
-                        char buffer[], size_t);
+/* Maximum number of digits needed to express ULONG_MAX in 26-adic notation. */
+#define F26ADIC_STRLEN_MAX 14
+verify (ULONG_MAX <= UINT64_MAX);
+
+bool str_format_26adic__ (unsigned long int number, bool uppercase,
+                          char buffer[], size_t);
+void str_format_26adic  (unsigned long int number, bool uppercase,
+                         char buffer[], size_t);
+int str_parse_26adic (const char *str);
 
 void str_ellipsize (struct substring in, char *out, size_t out_size);
 
index 9251621676f2dd630ebde735ba955fba3b0b7f56..a8f70ca6232c21fcc091d35348087f49e7346906 100644 (file)
@@ -1547,7 +1547,7 @@ pivot_footnote_format_marker (const struct pivot_footnote *f,
     ds_put_format (s, "%zu", f->idx + 1);
   else
     {
-      char text[INT_BUFSIZE_BOUND (size_t)];
+      char text[F26ADIC_STRLEN_MAX + 1];
       str_format_26adic (f->idx + 1, false, text, sizeof text);
       ds_put_cstr (s, text);
     }
index 6e8bd155c36e02a4d28e70bf36c9cd890b96288e..9423e30ddf4eeaef22f8d42be7ec57e24d4c135b 100644 (file)
@@ -21,6 +21,7 @@
 #include "psppire-import-spreadsheet.h"
 #include "builder-wrapper.h"
 
+#include "libpspp/assertion.h"
 #include "libpspp/misc.h"
 #include "psppire-spreadsheet-model.h"
 #include "psppire-spreadsheet-data-model.h"
@@ -33,9 +34,9 @@
 static void
 set_column_header_label (GtkWidget *button, int i, gpointer user_data)
 {
-  gchar *x = int_to_ps26 (i);
+  char x[F26ADIC_STRLEN_MAX + 1];
+  str_format_26adic (i + 1, true, x, sizeof x);
   gtk_button_set_label (GTK_BUTTON (button), x);
-  g_free (x);
 }
 
 static void do_selection_update (PsppireImportAssistant *ia);
@@ -146,19 +147,19 @@ static gboolean
 column_output (GtkSpinButton *sb, gpointer unused)
 {
   gint value = gtk_spin_button_get_value_as_int (sb);
-  char *text = int_to_ps26 (value);
-  if (text == NULL)
+  if (value < 0)
     return FALSE;
 
+  char text[F26ADIC_STRLEN_MAX + 1];
+  str_format_26adic (value + 1, true, text, sizeof text);
   gtk_entry_set_text (GTK_ENTRY (sb), text);
-  free (text);
 
   return TRUE;
 }
 
 /* Interprets the SBs text as 1 based instead of zero based.  */
 static gint
-row_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
+row_input (GtkSpinButton *sb, gdouble *new_value, gpointer unused)
 {
   const char *text = gtk_entry_get_text (GTK_ENTRY (sb));
   gdouble value = g_strtod (text, NULL) - 1;
@@ -166,7 +167,7 @@ row_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
   if (value < 0)
     return FALSE;
 
-  memcpy (new_value, &value, sizeof (value));
+  *new_value = value;
 
   return TRUE;
 }
@@ -175,15 +176,15 @@ row_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
 /* Interprets the SBs text of the form A, B, C etc and
    sets NEW_VALUE as a double.  */
 static gint
-column_input (GtkSpinButton *sb, gpointer new_value, gpointer unused)
+column_input (GtkSpinButton *sb, gdouble *new_value, gpointer unused)
 {
   const char *text = gtk_entry_get_text (GTK_ENTRY (sb));
-  double value = ps26_to_int (text);
+  double value = str_parse_26adic (text);
 
   if (value < 0)
     return FALSE;
 
-  memcpy (new_value, &value, sizeof (value));
+  *new_value = value;
 
   return TRUE;
 }
index f5897d72029c051ac03870aab63f62bb52b639ca..16caa5ca1e2d9800c23456c3d9995fa6addd9fc8 100644 (file)
@@ -32,7 +32,7 @@ check_die (void)
 static void
 check_26adic (unsigned long int number, const char *expected_string)
 {
-  char string[8];
+  char string[F26ADIC_STRLEN_MAX + 1];
   str_format_26adic (number, true, string, sizeof string);
   if (strcmp (string, expected_string))
     {
index 499e8b33b6d8385772d6421fedad57ff95d7d6c7..47d6384b84ddb38d3d6c284b5f8ed165fdd1cd82 100644 (file)
@@ -92,8 +92,9 @@ get_string (int idx)
   s = &string_table[idx];
   if (*s == NULL)
     {
-      *s = xmalloc (16);
-      str_format_26adic (idx + 1, true, *s, 16);
+      size_t size = F26ADIC_STRLEN_MAX + 1;
+      *s = xmalloc (size);
+      str_format_26adic (idx + 1, true, *s, size);
     }
   return *s;
 }
index fd2645b51f74ae0b3106698d6793bb5503b65460..2346ccaef5187867d064c8bad75491b89ba1bed2 100644 (file)
@@ -79,8 +79,9 @@ make_string (int value)
   s = &string_table[value];
   if (*s == NULL)
     {
-      *s = xmalloc (16);
-      str_format_26adic (value + 1, true, *s, 16);
+      size_t size = F26ADIC_STRLEN_MAX + 1;
+      *s = xmalloc (size);
+      str_format_26adic (value + 1, true, *s, size);
     }
   return *s;
 }