maint: update the mbsalign module
authorPádraig Brady <P@draigBrady.com>
Mon, 15 Mar 2010 14:04:31 +0000 (14:04 +0000)
committerPádraig Brady <P@draigBrady.com>
Fri, 19 Mar 2010 19:23:45 +0000 (19:23 +0000)
* gl/lib/mbsalign.c (mbsalign):  Support the MBA_UNIBYTE_FALLBACK
flag which reverts to unibyte mode if one can't allocate memory
or if there are invalid multibyte characters present.
Note memory is no longer dynamically allocated in unibyte mode so
one can assume that mbsalign() will not return an error if this
flag is present.  Don't calculate twice, the number of spaces,
when centering.  Suppress a signed/unsigned comparison warning.
(ambsalign): A new wrapper function to dynamically allocate
the minimum memory required to hold the aligned string.
* gl/lib/mbsalign.h: Add the MBA_UNIBYTE_FALLBACK flag and
also document others that may be implemented in future.
(ambsalign): A prototype for the new wrapper.
* gl/tests/test-mbsalign.c (main): New test program.
* gl/modules/mbsalign-tests: A new index to reference the tests.
* .x-sc_program_name: Exclude test-mbsalign.c from this check.

.x-sc_program_name
gl/lib/mbsalign.c
gl/lib/mbsalign.h
gl/modules/mbsalign-tests [new file with mode: 0644]
gl/tests/test-mbsalign.c [new file with mode: 0644]

index eb4855f..ace3c8f 100644 (file)
@@ -1,2 +1,3 @@
 gl/lib/randint.c
 lib/euidaccess-stat.c
+gl/tests/test-mbsalign.c
index be25956..5b55ec2 100644 (file)
@@ -32,6 +32,7 @@
 #endif
 
 /* Replace non printable chars.
+   Note \t and \n etc. are non printable.
    Return 1 if replacement made, 0 otherwise.  */
 
 static bool
@@ -119,17 +120,17 @@ mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces)
    ALIGNMENT specifies whether to left- or right-justify or to center.
    If SRC requires more than *WIDTH columns, truncate it to fit.
    When centering, the number of trailing spaces may be one less than the
-   number of leading spaces. The FLAGS parameter is unused at present.
+   number of leading spaces.
    Return the length in bytes required for the final result, not counting
    the trailing NUL.  A return value of DEST_SIZE or larger means there
    wasn't enough space.  DEST will be NUL terminated in any case.
    Return (size_t) -1 upon error (invalid multi-byte sequence in SRC,
-   or malloc failure).
+   or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
    Update *WIDTH to indicate how many columns were used before padding.  */
 
 size_t
 mbsalign (const char *src, char *dest, size_t dest_size,
-          size_t *width, mbs_align_t align, int flags _UNUSED_PARAMETER_)
+          size_t *width, mbs_align_t align, int flags)
 {
   size_t ret = -1;
   size_t src_size = strlen (src) + 1;
@@ -149,12 +150,22 @@ mbsalign (const char *src, char *dest, size_t dest_size,
     {
       size_t src_chars = mbstowcs (NULL, src, 0);
       if (src_chars == (size_t) -1)
-        goto mbsalign_cleanup;
+        {
+          if (flags & MBA_UNIBYTE_FALLBACK)
+            goto mbsalign_unibyte;
+          else
+            goto mbsalign_cleanup;
+        }
       src_chars += 1; /* make space for NUL */
       str_wc = malloc (src_chars * sizeof (wchar_t));
       if (str_wc == NULL)
-        goto mbsalign_cleanup;
-      if (mbstowcs (str_wc, src, src_chars) > 0)
+        {
+          if (flags & MBA_UNIBYTE_FALLBACK)
+            goto mbsalign_unibyte;
+          else
+            goto mbsalign_cleanup;
+        }
+      if (mbstowcs (str_wc, src, src_chars) != 0)
         {
           str_wc[src_chars - 1] = L'\0';
           wc_enabled = true;
@@ -165,27 +176,30 @@ mbsalign (const char *src, char *dest, size_t dest_size,
 
   /* If we transformed or need to truncate the source string
      then create a modified copy of it.  */
-  if (conversion || (n_cols > *width))
+  if (wc_enabled && (conversion || (n_cols > *width)))
     {
-      newstr = malloc (src_size);
-      if (newstr == NULL)
-        goto mbsalign_cleanup;
-      str_to_print = newstr;
-      if (wc_enabled)
+        newstr = malloc (src_size);
+        if (newstr == NULL)
         {
-          n_cols = wc_truncate (str_wc, *width);
-          n_used_bytes = wcstombs (newstr, str_wc, src_size);
-        }
-      else
-        {
-          n_cols = *width;
-          n_used_bytes = n_cols;
-          memcpy (newstr, src, n_cols);
-          newstr[n_cols] = '\0';
+          if (flags & MBA_UNIBYTE_FALLBACK)
+            goto mbsalign_unibyte;
+          else
+            goto mbsalign_cleanup;
         }
+        str_to_print = newstr;
+        n_cols = wc_truncate (str_wc, *width);
+        n_used_bytes = wcstombs (newstr, str_wc, src_size);
+    }
+
+mbsalign_unibyte:
+
+  if (n_cols > *width) /* Unibyte truncation required.  */
+    {
+      n_cols = *width;
+      n_used_bytes = n_cols;
     }
 
-  if (*width > n_cols)
+  if (*width > n_cols) /* Padding required.  */
     n_spaces = *width - n_cols;
 
   /* indicate to caller how many cells needed (not including padding).  */
@@ -197,16 +211,11 @@ mbsalign (const char *src, char *dest, size_t dest_size,
   /* Write as much NUL terminated output to DEST as possible.  */
   if (dest_size != 0)
     {
+      size_t start_spaces, end_spaces;
       char *dest_end = dest + dest_size - 1;
-      size_t start_spaces = n_spaces / 2 + n_spaces % 2;
-      size_t end_spaces = n_spaces / 2;
 
       switch (align)
         {
-        case MBS_ALIGN_CENTER:
-          start_spaces = n_spaces / 2 + n_spaces % 2;
-          end_spaces = n_spaces / 2;
-          break;
         case MBS_ALIGN_LEFT:
           start_spaces = 0;
           end_spaces = n_spaces;
@@ -215,10 +224,16 @@ mbsalign (const char *src, char *dest, size_t dest_size,
           start_spaces = n_spaces;
           end_spaces = 0;
           break;
+        case MBS_ALIGN_CENTER:
+        default:
+          start_spaces = n_spaces / 2 + n_spaces % 2;
+          end_spaces = n_spaces / 2;
+          break;
         }
 
       dest = mbs_align_pad (dest, dest_end, start_spaces);
-      dest = mempcpy(dest, str_to_print, MIN (n_used_bytes, dest_end - dest));
+      size_t space_left = dest_end - dest;
+      dest = mempcpy (dest, str_to_print, MIN (n_used_bytes, space_left));
       mbs_align_pad (dest, dest_end, end_spaces);
     }
 
@@ -229,3 +244,39 @@ mbsalign_cleanup:
 
   return ret;
 }
+
+/* A wrapper around mbsalign() to dynamically allocate the
+   minimum amount of memory to store the result.
+   Return NULL on failure.  */
+
+char *
+ambsalign (const char *src, size_t *width, mbs_align_t align, int flags)
+{
+  size_t orig_width = *width;
+  size_t size = *width;         /* Start with enough for unibyte mode.  */
+  size_t req = size;
+  char *buf = NULL;
+
+  while (req >= size)
+    {
+      size = req + 1;           /* Space for NUL.  */
+      char *nbuf = realloc (buf, size);
+      if (nbuf == NULL)
+        {
+          free (buf);
+          buf = NULL;
+          break;
+        }
+      buf = nbuf;
+      *width = orig_width;
+      req = mbsalign (src, buf, size, width, align, flags);
+      if (req == (size_t) -1)
+        {
+          free (buf);
+          buf = NULL;
+          break;
+        }
+    }
+
+  return buf;
+}
index a4ec693..41bd490 100644 (file)
 
 typedef enum { MBS_ALIGN_LEFT, MBS_ALIGN_RIGHT, MBS_ALIGN_CENTER } mbs_align_t;
 
+enum {
+  /* Use unibyte mode for invalid multibyte strings or
+     or when heap memory is exhausted.  */
+  MBA_UNIBYTE_FALLBACK = 0x0001,
+
+#if 0 /* Other possible options.  */
+  /* Skip invalid multibyte chars rather than failing  */
+  MBA_IGNORE_INVALID   = 0x0002,
+
+  /* Align multibyte strings using "figure space" (\u2007)  */
+  MBA_USE_FIGURE_SPACE = 0x0004,
+
+  /* Don't add any padding  */
+  MBA_TRUNCATE_ONLY    = 0x0008,
+
+  /* Don't truncate  */
+  MBA_PAD_ONLY         = 0x0010,
+#endif
+};
+
 size_t
 mbsalign (const char *src, char *dest, size_t dest_size,
           size_t *width, mbs_align_t align, int flags);
+
+char *
+ambsalign (const char *src, size_t *width, mbs_align_t align, int flags);
diff --git a/gl/modules/mbsalign-tests b/gl/modules/mbsalign-tests
new file mode 100644 (file)
index 0000000..8e0d138
--- /dev/null
@@ -0,0 +1,11 @@
+Files:
+tests/test-mbsalign.c
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-mbsalign
+check_PROGRAMS += test-mbsalign
diff --git a/gl/tests/test-mbsalign.c b/gl/tests/test-mbsalign.c
new file mode 100644 (file)
index 0000000..9f89357
--- /dev/null
@@ -0,0 +1,93 @@
+/* Test that mbsalign works as advertised.
+   Copyright (C) 2010 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
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Pádraig Brady.  */
+
+#include <config.h>
+
+#include "mbsalign.h"
+#include "macros.h"
+#include <stdlib.h>
+#include <locale.h>
+
+int
+main (void)
+{
+  char dest[4 * 16 + 1];
+  size_t width, n;
+
+  /* Test unibyte truncation.  */
+  width = 4;
+  n = mbsalign ("t\tés", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0);
+  ASSERT (n == 4);
+
+  /* Test center alignment.  */
+  width = 4;
+  n = mbsalign ("es", dest, sizeof dest, &width, MBS_ALIGN_CENTER, 0);
+  ASSERT (*dest == ' ' && *(dest + n - 1) == ' ');
+
+  if (setlocale (LC_ALL, "en_US.UTF8"))
+    {
+      /* Check invalid input is flagged.  */
+      width = 4;
+      n = mbsalign ("t\xe1\xe2s", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0);
+      ASSERT (n == (size_t) -1);
+
+      /* Check invalid input is treated as unibyte  */
+      width = 4;
+      n = mbsalign ("t\xe1\xe2s", dest, sizeof dest, &width,
+                    MBS_ALIGN_LEFT, MBA_UNIBYTE_FALLBACK);
+      ASSERT (n == 4);
+
+      /* Test multibyte center alignment.  */
+      width = 4;
+      n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_CENTER, 0);
+      ASSERT (*dest == ' ' && *(dest + n - 1) == ' ');
+
+      /* Test multibyte left alignment.  */
+      width = 4;
+      n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0);
+      ASSERT (*(dest + n - 1) == ' ' && *(dest + n - 2) == ' ');
+
+      /* Test multibyte right alignment.  */
+      width = 4;
+      n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_RIGHT, 0);
+      ASSERT (*(dest) == ' ' && *(dest + 1) == ' ');
+
+      /* multibyte multicell truncation.  */
+      width = 4;                /* cells */
+      n = mbsalign ("日月火水", dest, sizeof dest, &width,
+                    MBS_ALIGN_LEFT, 0);
+      ASSERT (n == 6);          /* 2 characters */
+
+      /* multibyte unicell truncation.  */
+      width = 3;                /* cells */
+      n = mbsalign ("¹²³⁴", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0);
+      ASSERT (n == 6);          /* 3 characters */
+
+      /* Check independence from dest buffer. */
+      width = 4;                /* cells */
+      n = mbsalign ("¹²³⁴", dest, 0, &width, MBS_ALIGN_LEFT, 0);
+      ASSERT (n == 9);          /* 4 characters */
+
+      /* Check that width is updated with cells required before padding.  */
+      width = 4;                /* cells */
+      n = mbsalign ("¹²³", dest, 0, &width, MBS_ALIGN_LEFT, 0);
+      ASSERT (width == 3);
+    }
+
+  return 0;
+}