str: Make str_format_26adic() able to use lowercase.
[pspp] / src / libpspp / str.c
index ba4a26f1426ac51552dd31885d5cdfff5fea321d..3fa2fbe9a75fbe48d0ed75dc48a1b32916b0ef88 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2009, 2010, 2011, 2012, 2014 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
@@ -28,6 +28,8 @@
 #include "libpspp/message.h"
 #include "libpspp/pool.h"
 
+#include "gl/c-ctype.h"
+#include "gl/c-vasnprintf.h"
 #include "gl/relocatable.h"
 #include "gl/minmax.h"
 #include "gl/xalloc.h"
@@ -232,26 +234,33 @@ str_copy_buf_trunc (char *dst, size_t dst_size,
   dst[dst_len] = '\0';
 }
 
-/* Converts each byte in S to uppercase. */
+/* Converts each byte in S to uppercase.
+
+   This is suitable only for ASCII strings.  Use utf8_to_upper() for UTF-8
+   strings.*/
 void
 str_uppercase (char *s)
 {
   for (; *s != '\0'; s++)
-    *s = toupper ((unsigned char) *s);
+    *s = c_toupper ((unsigned char) *s);
 }
 
-/* Converts each byte in S to lowercase. */
+/* Converts each byte in S to lowercase.
+
+   This is suitable only for ASCII strings.  Use utf8_to_lower() for UTF-8
+   strings.*/
 void
 str_lowercase (char *s)
 {
   for (; *s != '\0'; s++)
-    *s = tolower ((unsigned char) *s);
+    *s = c_tolower ((unsigned char) *s);
 }
 
 /* Converts NUMBER into a string in 26-adic notation in BUFFER,
-   which has room for SIZE bytes.  Returns true if successful,
-   false if NUMBER, plus a trailing null, is too large to fit in
-   the available space.
+   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.
 
    26-adic notation is "spreadsheet column numbering": 1 = A, 2 =
    B, 3 = C, ... 26 = Z, 27 = AA, 28 = AB, 29 = AC, ...
@@ -263,24 +272,32 @@ str_lowercase (char *s)
    For more information, see
    http://en.wikipedia.org/wiki/Bijective_numeration. */
 bool
-str_format_26adic (unsigned long int number, 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";
   size_t length = 0;
 
   while (number-- > 0)
     {
       if (length >= size)
-        return false;
-      buffer[length++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[number % 26];
+        goto overflow;
+      buffer[length++] = alphabet[number % 26];
       number /= 26;
     }
 
   if (length >= size)
-    return false;
+    goto overflow;
   buffer[length] = '\0';
 
   buf_reverse (buffer, length);
   return true;
+
+overflow:
+  if (length > 0)
+    buffer[0] = '\0';
+  return false;
 }
 
 /* Sets the SIZE bytes starting at BLOCK to C,
@@ -347,15 +364,16 @@ ss_realloc (struct substring *ss, size_t size)
   ss->string = xrealloc (ss->string, size);
 }
 
-/* Makes a pool_alloc_unaligned()'d copy of the contents of OLD
-   in POOL, and stores it in NEW. */
+/* Makes a pool_alloc_unaligned()'d, null-terminated copy of the contents of
+   OLD in POOL, and stores it in NEW. */
 void
 ss_alloc_substring_pool (struct substring *new, struct substring old,
                          struct pool *pool)
 {
-  new->string = pool_alloc_unaligned (pool, old.length);
+  new->string = pool_alloc_unaligned (pool, old.length + 1);
   new->length = old.length;
   memcpy (new->string, old.string, old.length);
+  new->string[old.length] = '\0';
 }
 
 /* Allocates room for a CNT-byte string in NEW in POOL. */
@@ -485,11 +503,15 @@ bool
 ss_tokenize (struct substring ss, struct substring delimiters,
              size_t *save_idx, struct substring *token)
 {
+  bool found_token;
+
   ss_advance (&ss, *save_idx);
   *save_idx += ss_ltrim (&ss, delimiters);
   ss_get_bytes (&ss, ss_cspan (ss, delimiters), token);
-  *save_idx += ss_length (*token) + 1;
-  return ss_length (*token) > 0;
+
+  found_token = ss_length (*token) > 0;
+  *save_idx += ss_length (*token) + found_token;
+  return found_token;
 }
 
 /* Removes the first CNT bytes from SS. */
@@ -1454,6 +1476,30 @@ ds_put_uninit (struct string *st, size_t incr)
   return end;
 }
 
+/* Moves the bytes in ST following offset OFS + OLD_LEN in ST to offset OFS +
+   NEW_LEN and returns the byte at offset OFS.  The first min(OLD_LEN, NEW_LEN)
+   bytes at the returned position are unchanged; if NEW_LEN > OLD_LEN then the
+   following NEW_LEN - OLD_LEN bytes are initially indeterminate.
+
+   The intention is that the caller should write NEW_LEN bytes at the returned
+   position, to effectively replace the OLD_LEN bytes previously at that
+   position. */
+char *
+ds_splice_uninit (struct string *st,
+                  size_t ofs, size_t old_len, size_t new_len)
+{
+  if (new_len != old_len)
+    {
+      if (new_len > old_len)
+        ds_extend (st, ds_length (st) + (new_len - old_len));
+      memmove (ds_data (st) + (ofs + new_len),
+               ds_data (st) + (ofs + old_len),
+               ds_length (st) - (ofs + old_len));
+      st->ss.length += new_len - old_len;
+    }
+  return ds_data (st) + ofs;
+}
+
 /* Formats FORMAT as a printf string and appends the result to ST. */
 void
 ds_put_format (struct string *st, const char *format, ...)
@@ -1465,22 +1511,36 @@ ds_put_format (struct string *st, const char *format, ...)
   va_end (args);
 }
 
-/* Formats FORMAT as a printf string and appends the result to ST. */
+/* Formats FORMAT as a printf string as if in the C locale and appends the result to ST. */
 void
-ds_put_vformat (struct string *st, const char *format, va_list args_)
+ds_put_c_format (struct string *st, const char *format, ...)
+{
+  va_list args;
+
+  va_start (args, format);
+  ds_put_c_vformat (st, format, args);
+  va_end (args);
+}
+
+
+/* Formats FORMAT as a printf string, using fmt_func (a snprintf like function) 
+   and appends the result to ST. */
+static void
+ds_put_vformat_int (struct string *st, const char *format, va_list args_,
+                   int (*fmt_func) (char *, size_t, const char *, va_list))
 {
   int avail, needed;
   va_list args;
 
   va_copy (args, args_);
   avail = st->ss.string != NULL ? st->capacity - st->ss.length + 1 : 0;
-  needed = vsnprintf (st->ss.string + st->ss.length, avail, format, args);
+  needed = fmt_func (st->ss.string + st->ss.length, avail, format, args);
   va_end (args);
 
   if (needed >= avail)
     {
       va_copy (args, args_);
-      vsprintf (ds_put_uninit (st, needed), format, args);
+      fmt_func (ds_put_uninit (st, needed), needed + 1, format, args);
       va_end (args);
     }
   else
@@ -1493,13 +1553,36 @@ ds_put_vformat (struct string *st, const char *format, va_list args_)
           avail = st->capacity - st->ss.length + 1;
 
           va_copy (args, args_);
-          needed = vsnprintf (ds_end (st), avail, format, args);
+          needed = fmt_func (ds_end (st), avail, format, args);
           va_end (args);
         }
       st->ss.length += needed;
     }
 }
 
+
+static int
+vasnwrapper (char *str, size_t size,  const char *format, va_list ap)
+{
+  c_vasnprintf (str, &size, format, ap);
+  return size;
+}
+
+/* Formats FORMAT as a printf string and appends the result to ST. */
+void
+ds_put_vformat (struct string *st, const char *format, va_list args_)
+{
+  ds_put_vformat_int (st, format, args_, vsnprintf);
+}
+
+/* Formats FORMAT as a printf string, as if in the C locale, 
+   and appends the result to ST. */
+void
+ds_put_c_vformat (struct string *st, const char *format, va_list args_)
+{
+  ds_put_vformat_int (st, format, args_, vasnwrapper);
+}
+
 /* Appends byte CH to ST. */
 void
 ds_put_byte (struct string *st, int ch)
@@ -1514,6 +1597,13 @@ ds_put_byte_multiple (struct string *st, int ch, size_t cnt)
   memset (ds_put_uninit (st, cnt), ch, cnt);
 }
 
+/* Appends Unicode code point UC to ST in UTF-8 encoding. */
+void
+ds_put_unichar (struct string *st, ucs4_t uc)
+{
+  ds_extend (st, ds_length (st) + 6);
+  st->ss.length += u8_uctomb (CHAR_CAST (uint8_t *, ds_end (st)), uc, 6);
+}
 
 /* If relocation has been enabled, replace ST,
    with its relocated version */