str: Make str_format_26adic() able to use lowercase.
[pspp] / src / libpspp / str.c
index e34c150df1c32506522155482c3285433c021c99..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. */
@@ -427,6 +445,20 @@ ss_chomp_byte (struct substring *ss, char c)
     return false;
 }
 
+/* If SS ends with SUFFIX, removes it and returns true.
+   Otherwise, returns false without changing the string. */
+bool
+ss_chomp (struct substring *ss, struct substring suffix)
+{
+  if (ss_ends_with (*ss, suffix))
+    {
+      ss->length -= suffix.length;
+      return true;
+    }
+  else
+    return false;
+}
+
 /* Divides SS into tokens separated by any of the DELIMITERS.
    Each call replaces TOKEN by the next token in SS, or by an
    empty string if no tokens remain.  Returns true if a token was
@@ -471,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. */
@@ -655,6 +691,15 @@ ss_last (struct substring ss)
   return ss.length > 0 ? (unsigned char) ss.string[ss.length - 1] : EOF;
 }
 
+/* Returns true if SS ends with SUFFIX, false otherwise. */
+bool
+ss_ends_with (struct substring ss, struct substring suffix)
+{
+  return (ss.length >= suffix.length
+          && !memcmp (&ss.string[ss.length - suffix.length], suffix.string,
+                      suffix.length));
+}
+
 /* Returns the number of contiguous bytes at the beginning
    of SS that are in SKIP_SET. */
 size_t
@@ -1041,6 +1086,14 @@ ds_chomp_byte (struct string *st, char c)
   return ss_chomp_byte (&st->ss, c);
 }
 
+/* If ST ends with SUFFIX, removes it and returns true.
+   Otherwise, returns false without modifying ST. */
+bool
+ds_chomp (struct string *st, struct substring suffix)
+{
+  return ss_chomp (&st->ss, suffix);
+}
+
 /* Divides ST into tokens separated by any of the DELIMITERS.
    Each call replaces TOKEN by the next token in ST, or by an
    empty string if no tokens remain.  Returns true if a token was
@@ -1180,6 +1233,13 @@ ds_last (const struct string *st)
   return ss_last (ds_ss (st));
 }
 
+/* Returns true if ST ends with SUFFIX, false otherwise. */
+bool
+ds_ends_with (const struct string *st, struct substring suffix)
+{
+  return ss_ends_with (st->ss, suffix);
+}
+
 /* Returns the number of consecutive bytes at the beginning
    of ST that are in SKIP_SET. */
 size_t
@@ -1416,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, ...)
@@ -1427,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
@@ -1455,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)
@@ -1476,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 */