d3767b
From 29117b2d07af00f4d4b87cf778e4294588ab1a83 Mon Sep 17 00:00:00 2001
d3767b
From: Kamil Dudka <kdudka@redhat.com>
d3767b
Date: Thu, 1 Dec 2016 15:10:04 +0100
d3767b
Subject: [PATCH] coreutils-i18n.patch
d3767b
d3767b
TODO: merge upstream
d3767b
---
d3767b
 lib/linebuffer.h            |   8 +
d3767b
 src/fold.c                  | 308 ++++++++++++++++--
d3767b
 src/join.c                  | 359 ++++++++++++++++++---
d3767b
 src/pr.c                    | 443 ++++++++++++++++++++++---
d3767b
 src/sort.c                  | 764 +++++++++++++++++++++++++++++++++++++++++---
d3767b
 src/uniq.c                  | 265 ++++++++++++++-
d3767b
 tests/i18n/sort.sh          |  29 ++
d3767b
 tests/local.mk              |   2 +
d3767b
 tests/misc/expand.pl        |  42 +++
d3767b
 tests/misc/fold.pl          |  50 ++-
d3767b
 tests/misc/join.pl          |  50 +++
d3767b
 tests/misc/sort-mb-tests.sh |  45 +++
d3767b
 tests/misc/sort-merge.pl    |  42 +++
d3767b
 tests/misc/sort.pl          |  40 ++-
d3767b
 tests/misc/unexpand.pl      |  39 +++
d3767b
 tests/misc/uniq.pl          |  55 ++++
d3767b
 tests/pr/pr-tests.pl        |  49 +++
d3767b
 17 files changed, 2430 insertions(+), 160 deletions(-)
d3767b
 create mode 100755 tests/i18n/sort.sh
d3767b
 create mode 100755 tests/misc/sort-mb-tests.sh
d3767b
d3767b
diff --git a/lib/linebuffer.h b/lib/linebuffer.h
d3767b
index 64181af..9b8fe5a 100644
d3767b
--- a/lib/linebuffer.h
d3767b
+++ b/lib/linebuffer.h
d3767b
@@ -21,6 +21,11 @@
d3767b
 
d3767b
 # include <stdio.h>
d3767b
 
d3767b
+/* Get mbstate_t.  */
d3767b
+# if HAVE_WCHAR_H
d3767b
+#  include <wchar.h>
d3767b
+# endif
d3767b
+
d3767b
 /* A 'struct linebuffer' holds a line of text. */
d3767b
 
d3767b
 struct linebuffer
d3767b
@@ -28,6 +33,9 @@ struct linebuffer
d3767b
   size_t size;                  /* Allocated. */
d3767b
   size_t length;                /* Used. */
d3767b
   char *buffer;
d3767b
+# if HAVE_WCHAR_H
d3767b
+  mbstate_t state;
d3767b
+# endif
d3767b
 };
d3767b
 
d3767b
 /* Initialize linebuffer LINEBUFFER for use. */
d3767b
diff --git a/src/fold.c b/src/fold.c
d3767b
index 8cd0d6b..d23edd5 100644
d3767b
--- a/src/fold.c
d3767b
+++ b/src/fold.c
d3767b
@@ -22,12 +22,34 @@
d3767b
 #include <getopt.h>
d3767b
 #include <sys/types.h>
d3767b
 
d3767b
+/* Get mbstate_t, mbrtowc(), wcwidth().  */
d3767b
+#if HAVE_WCHAR_H
d3767b
+# include <wchar.h>
d3767b
+#endif
d3767b
+
d3767b
+/* Get iswprint(), iswblank(), wcwidth().  */
d3767b
+#if HAVE_WCTYPE_H
d3767b
+# include <wctype.h>
d3767b
+#endif
d3767b
+
d3767b
 #include "system.h"
d3767b
 #include "die.h"
d3767b
 #include "error.h"
d3767b
 #include "fadvise.h"
d3767b
 #include "xdectoint.h"
d3767b
 
d3767b
+/* MB_LEN_MAX is incorrectly defined to be 1 in at least one GCC
d3767b
+      installation; work around this configuration error.  */
d3767b
+#if !defined MB_LEN_MAX || MB_LEN_MAX < 2
d3767b
+# undef MB_LEN_MAX
d3767b
+# define MB_LEN_MAX 16
d3767b
+#endif
d3767b
+
d3767b
+/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t.  */
d3767b
+#if HAVE_MBRTOWC && defined mbstate_t
d3767b
+# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
d3767b
+#endif
d3767b
+
d3767b
 #define TAB_WIDTH 8
d3767b
 
d3767b
 /* The official name of this program (e.g., no 'g' prefix).  */
d3767b
@@ -35,20 +57,41 @@
d3767b
 
d3767b
 #define AUTHORS proper_name ("David MacKenzie")
d3767b
 
d3767b
+#define FATAL_ERROR(Message)                                            \
d3767b
+  do                                                                    \
d3767b
+    {                                                                   \
d3767b
+      error (0, 0, (Message));                                          \
d3767b
+      usage (2);                                                        \
d3767b
+    }                                                                   \
d3767b
+  while (0)
d3767b
+
d3767b
+enum operating_mode
d3767b
+{
d3767b
+  /* Fold texts by columns that are at the given positions. */
d3767b
+  column_mode,
d3767b
+
d3767b
+  /* Fold texts by bytes that are at the given positions. */
d3767b
+  byte_mode,
d3767b
+
d3767b
+  /* Fold texts by characters that are at the given positions. */
d3767b
+  character_mode,
d3767b
+};
d3767b
+
d3767b
+/* The argument shows current mode. (Default: column_mode) */
d3767b
+static enum operating_mode operating_mode;
d3767b
+
d3767b
 /* If nonzero, try to break on whitespace. */
d3767b
 static bool break_spaces;
d3767b
 
d3767b
-/* If nonzero, count bytes, not column positions. */
d3767b
-static bool count_bytes;
d3767b
-
d3767b
 /* If nonzero, at least one of the files we read was standard input. */
d3767b
 static bool have_read_stdin;
d3767b
 
d3767b
-static char const shortopts[] = "bsw:0::1::2::3::4::5::6::7::8::9::";
d3767b
+static char const shortopts[] = "bcsw:0::1::2::3::4::5::6::7::8::9::";
d3767b
 
d3767b
 static struct option const longopts[] =
d3767b
 {
d3767b
   {"bytes", no_argument, NULL, 'b'},
d3767b
+  {"characters", no_argument, NULL, 'c'},
d3767b
   {"spaces", no_argument, NULL, 's'},
d3767b
   {"width", required_argument, NULL, 'w'},
d3767b
   {GETOPT_HELP_OPTION_DECL},
d3767b
@@ -76,6 +119,7 @@ Wrap input lines in each FILE, writing to standard output.\n\
d3767b
 
d3767b
       fputs (_("\
d3767b
   -b, --bytes         count bytes rather than columns\n\
d3767b
+  -c, --characters    count characters rather than columns\n\
d3767b
   -s, --spaces        break at spaces\n\
d3767b
   -w, --width=WIDTH   use WIDTH columns instead of 80\n\
d3767b
 "), stdout);
d3767b
@@ -93,7 +137,7 @@ Wrap input lines in each FILE, writing to standard output.\n\
d3767b
 static size_t
d3767b
 adjust_column (size_t column, char c)
d3767b
 {
d3767b
-  if (!count_bytes)
d3767b
+  if (operating_mode != byte_mode)
d3767b
     {
d3767b
       if (c == '\b')
d3767b
         {
d3767b
@@ -116,30 +160,14 @@ adjust_column (size_t column, char c)
d3767b
    to stdout, with maximum line length WIDTH.
d3767b
    Return true if successful.  */
d3767b
 
d3767b
-static bool
d3767b
-fold_file (char const *filename, size_t width)
d3767b
+static void
d3767b
+fold_text (FILE *istream, size_t width, int *saved_errno)
d3767b
 {
d3767b
-  FILE *istream;
d3767b
   int c;
d3767b
   size_t column = 0;		/* Screen column where next char will go. */
d3767b
   size_t offset_out = 0;	/* Index in 'line_out' for next char. */
d3767b
   static char *line_out = NULL;
d3767b
   static size_t allocated_out = 0;
d3767b
-  int saved_errno;
d3767b
-
d3767b
-  if (STREQ (filename, "-"))
d3767b
-    {
d3767b
-      istream = stdin;
d3767b
-      have_read_stdin = true;
d3767b
-    }
d3767b
-  else
d3767b
-    istream = fopen (filename, "r");
d3767b
-
d3767b
-  if (istream == NULL)
d3767b
-    {
d3767b
-      error (0, errno, "%s", quotef (filename));
d3767b
-      return false;
d3767b
-    }
d3767b
 
d3767b
   fadvise (istream, FADVISE_SEQUENTIAL);
d3767b
 
d3767b
@@ -169,6 +197,15 @@ fold_file (char const *filename, size_t width)
d3767b
               bool found_blank = false;
d3767b
               size_t logical_end = offset_out;
d3767b
 
d3767b
+              /* If LINE_OUT has no wide character,
d3767b
+                 put a new wide character in LINE_OUT
d3767b
+                 if column is bigger than width. */
d3767b
+              if (offset_out == 0)
d3767b
+                {
d3767b
+                  line_out[offset_out++] = c;
d3767b
+                  continue;
d3767b
+                }
d3767b
+
d3767b
               /* Look for the last blank. */
d3767b
               while (logical_end)
d3767b
                 {
d3767b
@@ -215,11 +252,221 @@ fold_file (char const *filename, size_t width)
d3767b
       line_out[offset_out++] = c;
d3767b
     }
d3767b
 
d3767b
-  saved_errno = errno;
d3767b
+  *saved_errno = errno;
d3767b
 
d3767b
   if (offset_out)
d3767b
     fwrite (line_out, sizeof (char), (size_t) offset_out, stdout);
d3767b
 
d3767b
+}
d3767b
+
d3767b
+#if HAVE_MBRTOWC
d3767b
+static void
d3767b
+fold_multibyte_text (FILE *istream, size_t width, int *saved_errno)
d3767b
+{
d3767b
+  char buf[MB_LEN_MAX + BUFSIZ];  /* For spooling a read byte sequence. */
d3767b
+  size_t buflen = 0;        /* The length of the byte sequence in buf. */
d3767b
+  char *bufpos = buf;         /* Next read position of BUF. */
d3767b
+  wint_t wc;                /* A gotten wide character. */
d3767b
+  size_t mblength;        /* The byte size of a multibyte character which shows
d3767b
+                           as same character as WC. */
d3767b
+  mbstate_t state, state_bak;        /* State of the stream. */
d3767b
+  int convfail = 0;                /* 1, when conversion is failed. Otherwise 0. */
d3767b
+
d3767b
+  static char *line_out = NULL;
d3767b
+  size_t offset_out = 0;        /* Index in `line_out' for next char. */
d3767b
+  static size_t allocated_out = 0;
d3767b
+
d3767b
+  int increment;
d3767b
+  size_t column = 0;
d3767b
+
d3767b
+  size_t last_blank_pos;
d3767b
+  size_t last_blank_column;
d3767b
+  int is_blank_seen;
d3767b
+  int last_blank_increment = 0;
d3767b
+  int is_bs_following_last_blank;
d3767b
+  size_t bs_following_last_blank_num;
d3767b
+  int is_cr_after_last_blank;
d3767b
+
d3767b
+#define CLEAR_FLAGS                                \
d3767b
+   do                                                \
d3767b
+     {                                                \
d3767b
+        last_blank_pos = 0;                        \
d3767b
+        last_blank_column = 0;                        \
d3767b
+        is_blank_seen = 0;                        \
d3767b
+        is_bs_following_last_blank = 0;                \
d3767b
+        bs_following_last_blank_num = 0;        \
d3767b
+        is_cr_after_last_blank = 0;                \
d3767b
+     }                                                \
d3767b
+   while (0)
d3767b
+
d3767b
+#define START_NEW_LINE                        \
d3767b
+   do                                        \
d3767b
+     {                                        \
d3767b
+      putchar ('\n');                        \
d3767b
+      column = 0;                        \
d3767b
+      offset_out = 0;                        \
d3767b
+      CLEAR_FLAGS;                        \
d3767b
+    }                                        \
d3767b
+   while (0)
d3767b
+
d3767b
+  CLEAR_FLAGS;
d3767b
+  memset (&state, '\0', sizeof(mbstate_t));
d3767b
+
d3767b
+  for (;; bufpos += mblength, buflen -= mblength)
d3767b
+    {
d3767b
+      if (buflen < MB_LEN_MAX && !feof (istream) && !ferror (istream))
d3767b
+        {
d3767b
+          memmove (buf, bufpos, buflen);
d3767b
+          buflen += fread (buf + buflen, sizeof(char), BUFSIZ, istream);
d3767b
+          bufpos = buf;
d3767b
+        }
d3767b
+
d3767b
+      if (buflen < 1)
d3767b
+        break;
d3767b
+
d3767b
+      /* Get a wide character. */
d3767b
+      state_bak = state;
d3767b
+      mblength = mbrtowc ((wchar_t *)&wc, bufpos, buflen, &state);
d3767b
+
d3767b
+      switch (mblength)
d3767b
+        {
d3767b
+        case (size_t)-1:
d3767b
+        case (size_t)-2:
d3767b
+          convfail++;
d3767b
+          state = state_bak;
d3767b
+          /* Fall through. */
d3767b
+
d3767b
+        case 0:
d3767b
+          mblength = 1;
d3767b
+          break;
d3767b
+        }
d3767b
+
d3767b
+rescan:
d3767b
+      if (operating_mode == byte_mode)                        /* byte mode */
d3767b
+        increment = mblength;
d3767b
+      else if (operating_mode == character_mode)        /* character mode */
d3767b
+        increment = 1;
d3767b
+      else                                                /* column mode */
d3767b
+        {
d3767b
+          if (convfail)
d3767b
+            increment = 1;
d3767b
+          else
d3767b
+            {
d3767b
+              switch (wc)
d3767b
+                {
d3767b
+                case L'\n':
d3767b
+                  fwrite (line_out, sizeof(char), offset_out, stdout);
d3767b
+                  START_NEW_LINE;
d3767b
+                  continue;
d3767b
+
d3767b
+                case L'\b':
d3767b
+                  increment = (column > 0) ? -1 : 0;
d3767b
+                  break;
d3767b
+
d3767b
+                case L'\r':
d3767b
+                  increment = -1 * column;
d3767b
+                  break;
d3767b
+
d3767b
+                case L'\t':
d3767b
+                  increment = 8 - column % 8;
d3767b
+                  break;
d3767b
+
d3767b
+                default:
d3767b
+                  increment = wcwidth (wc);
d3767b
+                  increment = (increment < 0) ? 0 : increment;
d3767b
+                }
d3767b
+            }
d3767b
+        }
d3767b
+
d3767b
+      if (column + increment > width && break_spaces && last_blank_pos)
d3767b
+        {
d3767b
+          fwrite (line_out, sizeof(char), last_blank_pos, stdout);
d3767b
+          putchar ('\n');
d3767b
+
d3767b
+          offset_out = offset_out - last_blank_pos;
d3767b
+          column = column - last_blank_column + ((is_cr_after_last_blank)
d3767b
+              ? last_blank_increment : bs_following_last_blank_num);
d3767b
+          memmove (line_out, line_out + last_blank_pos, offset_out);
d3767b
+          CLEAR_FLAGS;
d3767b
+          goto rescan;
d3767b
+        }
d3767b
+
d3767b
+      if (column + increment > width && column != 0)
d3767b
+        {
d3767b
+          fwrite (line_out, sizeof(char), offset_out, stdout);
d3767b
+          START_NEW_LINE;
d3767b
+          goto rescan;
d3767b
+        }
d3767b
+
d3767b
+      if (allocated_out < offset_out + mblength)
d3767b
+        {
d3767b
+          line_out = X2REALLOC (line_out, &allocated_out);
d3767b
+        }
d3767b
+
d3767b
+      memcpy (line_out + offset_out, bufpos, mblength);
d3767b
+      offset_out += mblength;
d3767b
+      column += increment;
d3767b
+
d3767b
+      if (is_blank_seen && !convfail && wc == L'\r')
d3767b
+        is_cr_after_last_blank = 1;
d3767b
+
d3767b
+      if (is_bs_following_last_blank && !convfail && wc == L'\b')
d3767b
+        ++bs_following_last_blank_num;
d3767b
+      else
d3767b
+        is_bs_following_last_blank = 0;
d3767b
+
d3767b
+      if (break_spaces && !convfail && iswblank (wc))
d3767b
+        {
d3767b
+          last_blank_pos = offset_out;
d3767b
+          last_blank_column = column;
d3767b
+          is_blank_seen = 1;
d3767b
+          last_blank_increment = increment;
d3767b
+          is_bs_following_last_blank = 1;
d3767b
+          bs_following_last_blank_num = 0;
d3767b
+          is_cr_after_last_blank = 0;
d3767b
+        }
d3767b
+    }
d3767b
+
d3767b
+  *saved_errno = errno;
d3767b
+
d3767b
+  if (offset_out)
d3767b
+    fwrite (line_out, sizeof (char), (size_t) offset_out, stdout);
d3767b
+
d3767b
+}
d3767b
+#endif
d3767b
+
d3767b
+/* Fold file FILENAME, or standard input if FILENAME is "-",
d3767b
+   to stdout, with maximum line length WIDTH.
d3767b
+   Return 0 if successful, 1 if an error occurs. */
d3767b
+
d3767b
+static bool
d3767b
+fold_file (char const *filename, size_t width)
d3767b
+{
d3767b
+  FILE *istream;
d3767b
+  int saved_errno;
d3767b
+
d3767b
+  if (STREQ (filename, "-"))
d3767b
+    {
d3767b
+      istream = stdin;
d3767b
+      have_read_stdin = 1;
d3767b
+    }
d3767b
+  else
d3767b
+    istream = fopen (filename, "r");
d3767b
+
d3767b
+  if (istream == NULL)
d3767b
+    {
d3767b
+      error (0, errno, "%s", filename);
d3767b
+      return 1;
d3767b
+    }
d3767b
+
d3767b
+  /* Define how ISTREAM is being folded. */
d3767b
+#if HAVE_MBRTOWC
d3767b
+  if (MB_CUR_MAX > 1)
d3767b
+    fold_multibyte_text (istream, width, &saved_errno);
d3767b
+  else
d3767b
+#endif
d3767b
+    fold_text (istream, width, &saved_errno);
d3767b
+
d3767b
   if (ferror (istream))
d3767b
     {
d3767b
       error (0, saved_errno, "%s", quotef (filename));
d3767b
@@ -252,7 +499,8 @@ main (int argc, char **argv)
d3767b
 
d3767b
   atexit (close_stdout);
d3767b
 
d3767b
-  break_spaces = count_bytes = have_read_stdin = false;
d3767b
+  operating_mode = column_mode;
d3767b
+  break_spaces = have_read_stdin = false;
d3767b
 
d3767b
   while ((optc = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
d3767b
     {
d3767b
@@ -261,7 +509,15 @@ main (int argc, char **argv)
d3767b
       switch (optc)
d3767b
         {
d3767b
         case 'b':		/* Count bytes rather than columns. */
d3767b
-          count_bytes = true;
d3767b
+          if (operating_mode != column_mode)
d3767b
+            FATAL_ERROR (_("only one way of folding may be specified"));
d3767b
+          operating_mode = byte_mode;
d3767b
+          break;
d3767b
+
d3767b
+        case 'c':
d3767b
+          if (operating_mode != column_mode)
d3767b
+            FATAL_ERROR (_("only one way of folding may be specified"));
d3767b
+          operating_mode = character_mode;
d3767b
           break;
d3767b
 
d3767b
         case 's':		/* Break at word boundaries. */
d3767b
diff --git a/src/join.c b/src/join.c
d3767b
index 98b461c..9990f38 100644
d3767b
--- a/src/join.c
d3767b
+++ b/src/join.c
d3767b
@@ -22,19 +22,33 @@
d3767b
 #include <sys/types.h>
d3767b
 #include <getopt.h>
d3767b
 
d3767b
+/* Get mbstate_t, mbrtowc(), mbrtowc(), wcwidth().  */
d3767b
+#if HAVE_WCHAR_H
d3767b
+# include <wchar.h>
d3767b
+#endif
d3767b
+
d3767b
+/* Get iswblank(), towupper.  */
d3767b
+#if HAVE_WCTYPE_H
d3767b
+# include <wctype.h>
d3767b
+#endif
d3767b
+
d3767b
 #include "system.h"
d3767b
 #include "die.h"
d3767b
 #include "error.h"
d3767b
 #include "fadvise.h"
d3767b
 #include "hard-locale.h"
d3767b
 #include "linebuffer.h"
d3767b
-#include "memcasecmp.h"
d3767b
 #include "quote.h"
d3767b
 #include "stdio--.h"
d3767b
 #include "xmemcoll.h"
d3767b
 #include "xstrtol.h"
d3767b
 #include "argmatch.h"
d3767b
 
d3767b
+/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t.  */
d3767b
+#if HAVE_MBRTOWC && defined mbstate_t
d3767b
+# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
d3767b
+#endif
d3767b
+
d3767b
 /* The official name of this program (e.g., no 'g' prefix).  */
d3767b
 #define PROGRAM_NAME "join"
d3767b
 
d3767b
@@ -136,10 +150,12 @@ static struct outlist outlist_head;
d3767b
 /* Last element in 'outlist', where a new element can be added.  */
d3767b
 static struct outlist *outlist_end = &outlist_head;
d3767b
 
d3767b
-/* Tab character separating fields.  If negative, fields are separated
d3767b
-   by any nonempty string of blanks, otherwise by exactly one
d3767b
-   tab character whose value (when cast to unsigned char) equals TAB.  */
d3767b
-static int tab = -1;
d3767b
+/* Tab character separating fields.  If NULL, fields are separated
d3767b
+   by any nonempty string of blanks.  */
d3767b
+static char *tab = NULL;
d3767b
+
d3767b
+/* The number of bytes used for tab. */
d3767b
+static size_t tablen = 0;
d3767b
 
d3767b
 /* If nonzero, check that the input is correctly ordered. */
d3767b
 static enum
d3767b
@@ -276,13 +292,14 @@ xfields (struct line *line)
d3767b
   if (ptr == lim)
d3767b
     return;
d3767b
 
d3767b
-  if (0 <= tab && tab != '\n')
d3767b
+  if (tab != NULL)
d3767b
     {
d3767b
+      unsigned char t = tab[0];
d3767b
       char *sep;
d3767b
-      for (; (sep = memchr (ptr, tab, lim - ptr)) != NULL; ptr = sep + 1)
d3767b
+      for (; (sep = memchr (ptr, t, lim - ptr)) != NULL; ptr = sep + 1)
d3767b
         extract_field (line, ptr, sep - ptr);
d3767b
     }
d3767b
-  else if (tab < 0)
d3767b
+   else
d3767b
     {
d3767b
       /* Skip leading blanks before the first field.  */
d3767b
       while (field_sep (*ptr))
d3767b
@@ -306,6 +323,147 @@ xfields (struct line *line)
d3767b
   extract_field (line, ptr, lim - ptr);
d3767b
 }
d3767b
 
d3767b
+#if HAVE_MBRTOWC
d3767b
+static void
d3767b
+xfields_multibyte (struct line *line)
d3767b
+{
d3767b
+  char *ptr = line->buf.buffer;
d3767b
+  char const *lim = ptr + line->buf.length - 1;
d3767b
+  wchar_t wc = 0;
d3767b
+  size_t mblength = 1;
d3767b
+  mbstate_t state, state_bak;
d3767b
+
d3767b
+  memset (&state, 0, sizeof (mbstate_t));
d3767b
+
d3767b
+  if (ptr >= lim)
d3767b
+    return;
d3767b
+
d3767b
+  if (tab != NULL)
d3767b
+    {
d3767b
+      char *sep = ptr;
d3767b
+      for (; ptr < lim; ptr = sep + mblength)
d3767b
+	{
d3767b
+	  sep = ptr;
d3767b
+	  while (sep < lim)
d3767b
+	    {
d3767b
+	      state_bak = state;
d3767b
+	      mblength = mbrtowc (&wc, sep, lim - sep + 1, &state);
d3767b
+
d3767b
+	      if (mblength == (size_t)-1 || mblength == (size_t)-2)
d3767b
+		{
d3767b
+		  mblength = 1;
d3767b
+		  state = state_bak;
d3767b
+		}
d3767b
+	      mblength = (mblength < 1) ? 1 : mblength;
d3767b
+
d3767b
+	      if (mblength == tablen && !memcmp (sep, tab, mblength))
d3767b
+		break;
d3767b
+	      else
d3767b
+		{
d3767b
+		  sep += mblength;
d3767b
+		  continue;
d3767b
+		}
d3767b
+	    }
d3767b
+
d3767b
+	  if (sep >= lim)
d3767b
+	    break;
d3767b
+
d3767b
+	  extract_field (line, ptr, sep - ptr);
d3767b
+	}
d3767b
+    }
d3767b
+  else
d3767b
+    {
d3767b
+      /* Skip leading blanks before the first field.  */
d3767b
+      while(ptr < lim)
d3767b
+      {
d3767b
+        state_bak = state;
d3767b
+        mblength = mbrtowc (&wc, ptr, lim - ptr + 1, &state);
d3767b
+
d3767b
+        if (mblength == (size_t)-1 || mblength == (size_t)-2)
d3767b
+          {
d3767b
+            mblength = 1;
d3767b
+            state = state_bak;
d3767b
+            break;
d3767b
+          }
d3767b
+        mblength = (mblength < 1) ? 1 : mblength;
d3767b
+
d3767b
+        if (!iswblank(wc) && wc != '\n')
d3767b
+          break;
d3767b
+        ptr += mblength;
d3767b
+      }
d3767b
+
d3767b
+      do
d3767b
+	{
d3767b
+	  char *sep;
d3767b
+	  state_bak = state;
d3767b
+	  mblength = mbrtowc (&wc, ptr, lim - ptr + 1, &state);
d3767b
+	  if (mblength == (size_t)-1 || mblength == (size_t)-2)
d3767b
+	    {
d3767b
+	      mblength = 1;
d3767b
+	      state = state_bak;
d3767b
+	      break;
d3767b
+	    }
d3767b
+	  mblength = (mblength < 1) ? 1 : mblength;
d3767b
+
d3767b
+	  sep = ptr + mblength;
d3767b
+	  while (sep < lim)
d3767b
+	    {
d3767b
+	      state_bak = state;
d3767b
+	      mblength = mbrtowc (&wc, sep, lim - sep + 1, &state);
d3767b
+	      if (mblength == (size_t)-1 || mblength == (size_t)-2)
d3767b
+		{
d3767b
+		  mblength = 1;
d3767b
+		  state = state_bak;
d3767b
+		  break;
d3767b
+		}
d3767b
+	      mblength = (mblength < 1) ? 1 : mblength;
d3767b
+
d3767b
+	      if (iswblank (wc) || wc == '\n')
d3767b
+		break;
d3767b
+
d3767b
+	      sep += mblength;
d3767b
+	    }
d3767b
+
d3767b
+	  extract_field (line, ptr, sep - ptr);
d3767b
+	  if (sep >= lim)
d3767b
+	    return;
d3767b
+
d3767b
+	  state_bak = state;
d3767b
+	  mblength = mbrtowc (&wc, sep, lim - sep + 1, &state);
d3767b
+	  if (mblength == (size_t)-1 || mblength == (size_t)-2)
d3767b
+	    {
d3767b
+	      mblength = 1;
d3767b
+	      state = state_bak;
d3767b
+	      break;
d3767b
+	    }
d3767b
+	  mblength = (mblength < 1) ? 1 : mblength;
d3767b
+
d3767b
+	  ptr = sep + mblength;
d3767b
+	  while (ptr < lim)
d3767b
+	    {
d3767b
+	      state_bak = state;
d3767b
+	      mblength = mbrtowc (&wc, ptr, lim - ptr + 1, &state);
d3767b
+	      if (mblength == (size_t)-1 || mblength == (size_t)-2)
d3767b
+		{
d3767b
+		  mblength = 1;
d3767b
+		  state = state_bak;
d3767b
+		  break;
d3767b
+		}
d3767b
+	      mblength = (mblength < 1) ? 1 : mblength;
d3767b
+
d3767b
+	      if (!iswblank (wc) && wc != '\n')
d3767b
+		break;
d3767b
+
d3767b
+	      ptr += mblength;
d3767b
+	    }
d3767b
+	}
d3767b
+      while (ptr < lim);
d3767b
+    }
d3767b
+
d3767b
+  extract_field (line, ptr, lim - ptr);
d3767b
+}
d3767b
+#endif
d3767b
+
d3767b
 static void
d3767b
 freeline (struct line *line)
d3767b
 {
d3767b
@@ -327,56 +485,133 @@ keycmp (struct line const *line1, struct line const *line2,
d3767b
         size_t jf_1, size_t jf_2)
d3767b
 {
d3767b
   /* Start of field to compare in each file.  */
d3767b
-  char *beg1;
d3767b
-  char *beg2;
d3767b
-
d3767b
-  size_t len1;
d3767b
-  size_t len2;		/* Length of fields to compare.  */
d3767b
+  char *beg[2];
d3767b
+  char *copy[2];
d3767b
+  size_t len[2]; 	/* Length of fields to compare.  */
d3767b
   int diff;
d3767b
+  int i, j;
d3767b
+  int mallocd = 0;
d3767b
 
d3767b
   if (jf_1 < line1->nfields)
d3767b
     {
d3767b
-      beg1 = line1->fields[jf_1].beg;
d3767b
-      len1 = line1->fields[jf_1].len;
d3767b
+      beg[0] = line1->fields[jf_1].beg;
d3767b
+      len[0] = line1->fields[jf_1].len;
d3767b
     }
d3767b
   else
d3767b
     {
d3767b
-      beg1 = NULL;
d3767b
-      len1 = 0;
d3767b
+      beg[0] = NULL;
d3767b
+      len[0] = 0;
d3767b
     }
d3767b
 
d3767b
   if (jf_2 < line2->nfields)
d3767b
     {
d3767b
-      beg2 = line2->fields[jf_2].beg;
d3767b
-      len2 = line2->fields[jf_2].len;
d3767b
+      beg[1] = line2->fields[jf_2].beg;
d3767b
+      len[1] = line2->fields[jf_2].len;
d3767b
     }
d3767b
   else
d3767b
     {
d3767b
-      beg2 = NULL;
d3767b
-      len2 = 0;
d3767b
+      beg[1] = NULL;
d3767b
+      len[1] = 0;
d3767b
     }
d3767b
 
d3767b
-  if (len1 == 0)
d3767b
-    return len2 == 0 ? 0 : -1;
d3767b
-  if (len2 == 0)
d3767b
+  if (len[0] == 0)
d3767b
+    return len[1] == 0 ? 0 : -1;
d3767b
+  if (len[1] == 0)
d3767b
     return 1;
d3767b
 
d3767b
   if (ignore_case)
d3767b
     {
d3767b
-      /* FIXME: ignore_case does not work with NLS (in particular,
d3767b
-         with multibyte chars).  */
d3767b
-      diff = memcasecmp (beg1, beg2, MIN (len1, len2));
d3767b
+#ifdef HAVE_MBRTOWC
d3767b
+      if (MB_CUR_MAX > 1)
d3767b
+      {
d3767b
+        size_t mblength;
d3767b
+        wchar_t wc, uwc;
d3767b
+        mbstate_t state, state_bak;
d3767b
+
d3767b
+        memset (&state, '\0', sizeof (mbstate_t));
d3767b
+
d3767b
+        for (i = 0; i < 2; i++)
d3767b
+          {
d3767b
+            mallocd = 1;
d3767b
+            copy[i] = xmalloc (len[i] + 1);
d3767b
+            memset (copy[i], '\0',len[i] + 1);
d3767b
+
d3767b
+            for (j = 0; j < MIN (len[0], len[1]);)
d3767b
+              {
d3767b
+                state_bak = state;
d3767b
+                mblength = mbrtowc (&wc, beg[i] + j, len[i] - j, &state);
d3767b
+
d3767b
+                switch (mblength)
d3767b
+                  {
d3767b
+                  case (size_t) -1:
d3767b
+                  case (size_t) -2:
d3767b
+                    state = state_bak;
d3767b
+                    /* Fall through */
d3767b
+                  case 0:
d3767b
+                    mblength = 1;
d3767b
+                    break;
d3767b
+
d3767b
+                  default:
d3767b
+                    uwc = towupper (wc);
d3767b
+
d3767b
+                    if (uwc != wc)
d3767b
+                      {
d3767b
+                        mbstate_t state_wc;
d3767b
+                        size_t mblen;
d3767b
+
d3767b
+                        memset (&state_wc, '\0', sizeof (mbstate_t));
d3767b
+                        mblen = wcrtomb (copy[i] + j, uwc, &state_wc);
d3767b
+                        assert (mblen != (size_t)-1);
d3767b
+                      }
d3767b
+                    else
d3767b
+                      memcpy (copy[i] + j, beg[i] + j, mblength);
d3767b
+                  }
d3767b
+                j += mblength;
d3767b
+              }
d3767b
+            copy[i][j] = '\0';
d3767b
+          }
d3767b
+      }
d3767b
+      else
d3767b
+#endif
d3767b
+      {
d3767b
+        for (i = 0; i < 2; i++)
d3767b
+          {
d3767b
+            mallocd = 1;
d3767b
+            copy[i] = xmalloc (len[i] + 1);
d3767b
+
d3767b
+            for (j = 0; j < MIN (len[0], len[1]); j++)
d3767b
+              copy[i][j] = toupper (beg[i][j]);
d3767b
+
d3767b
+            copy[i][j] = '\0';
d3767b
+          }
d3767b
+      }
d3767b
     }
d3767b
   else
d3767b
     {
d3767b
-      if (hard_LC_COLLATE)
d3767b
-        return xmemcoll (beg1, len1, beg2, len2);
d3767b
-      diff = memcmp (beg1, beg2, MIN (len1, len2));
d3767b
+      copy[0] = beg[0];
d3767b
+      copy[1] = beg[1];
d3767b
     }
d3767b
 
d3767b
+  if (hard_LC_COLLATE)
d3767b
+    {
d3767b
+      diff = xmemcoll ((char *) copy[0], len[0], (char *) copy[1], len[1]);
d3767b
+
d3767b
+      if (mallocd)
d3767b
+        for (i = 0; i < 2; i++)
d3767b
+          free (copy[i]);
d3767b
+
d3767b
+      return diff;
d3767b
+    }
d3767b
+  diff = memcmp (copy[0], copy[1], MIN (len[0], len[1]));
d3767b
+
d3767b
+  if (mallocd)
d3767b
+    for (i = 0; i < 2; i++)
d3767b
+      free (copy[i]);
d3767b
+
d3767b
+
d3767b
   if (diff)
d3767b
     return diff;
d3767b
-  return len1 < len2 ? -1 : len1 != len2;
d3767b
+  return len[0] - len[1];
d3767b
 }
d3767b
 
d3767b
 /* Check that successive input lines PREV and CURRENT from input file
d3767b
@@ -468,6 +703,11 @@ get_line (FILE *fp, struct line **linep, int which)
d3767b
     }
d3767b
   ++line_no[which - 1];
d3767b
 
d3767b
+#if HAVE_MBRTOWC
d3767b
+  if (MB_CUR_MAX > 1)
d3767b
+    xfields_multibyte (line);
d3767b
+  else
d3767b
+#endif
d3767b
   xfields (line);
d3767b
 
d3767b
   if (prevline[which - 1])
d3767b
@@ -563,21 +803,28 @@ prfield (size_t n, struct line const *line)
d3767b
 
d3767b
 /* Output all the fields in line, other than the join field.  */
d3767b
 
d3767b
+#define PUT_TAB_CHAR							\
d3767b
+  do									\
d3767b
+    {									\
d3767b
+      (tab != NULL) ?							\
d3767b
+	fwrite(tab, sizeof(char), tablen, stdout) : putchar (' ');	\
d3767b
+    }									\
d3767b
+  while (0)
d3767b
+
d3767b
 static void
d3767b
 prfields (struct line const *line, size_t join_field, size_t autocount)
d3767b
 {
d3767b
   size_t i;
d3767b
   size_t nfields = autoformat ? autocount : line->nfields;
d3767b
-  char output_separator = tab < 0 ? ' ' : tab;
d3767b
 
d3767b
   for (i = 0; i < join_field && i < nfields; ++i)
d3767b
     {
d3767b
-      putchar (output_separator);
d3767b
+      PUT_TAB_CHAR;
d3767b
       prfield (i, line);
d3767b
     }
d3767b
   for (i = join_field + 1; i < nfields; ++i)
d3767b
     {
d3767b
-      putchar (output_separator);
d3767b
+      PUT_TAB_CHAR;
d3767b
       prfield (i, line);
d3767b
     }
d3767b
 }
d3767b
@@ -588,7 +835,6 @@ static void
d3767b
 prjoin (struct line const *line1, struct line const *line2)
d3767b
 {
d3767b
   const struct outlist *outlist;
d3767b
-  char output_separator = tab < 0 ? ' ' : tab;
d3767b
   size_t field;
d3767b
   struct line const *line;
d3767b
 
d3767b
@@ -622,7 +868,7 @@ prjoin (struct line const *line1, struct line const *line2)
d3767b
           o = o->next;
d3767b
           if (o == NULL)
d3767b
             break;
d3767b
-          putchar (output_separator);
d3767b
+          PUT_TAB_CHAR;
d3767b
         }
d3767b
       putchar (eolchar);
d3767b
     }
d3767b
@@ -1099,20 +1345,43 @@ main (int argc, char **argv)
d3767b
 
d3767b
         case 't':
d3767b
           {
d3767b
-            unsigned char newtab = optarg[0];
d3767b
+            char *newtab = NULL;
d3767b
+            size_t newtablen;
d3767b
+            newtab = xstrdup (optarg);
d3767b
+#if HAVE_MBRTOWC
d3767b
+            if (MB_CUR_MAX > 1)
d3767b
+              {
d3767b
+                mbstate_t state;
d3767b
+
d3767b
+                memset (&state, 0, sizeof (mbstate_t));
d3767b
+                newtablen = mbrtowc (NULL, newtab,
d3767b
+                                     strnlen (newtab, MB_LEN_MAX),
d3767b
+                                     &state);
d3767b
+                if (newtablen == (size_t) 0
d3767b
+                    || newtablen == (size_t) -1
d3767b
+                    || newtablen == (size_t) -2)
d3767b
+                  newtablen = 1;
d3767b
+              }
d3767b
+            else
d3767b
+#endif
d3767b
+              newtablen = 1;
d3767b
             if (! newtab)
d3767b
-              newtab = '\n'; /* '' => process the whole line.  */
d3767b
+              newtab = (char*)"\n"; /* '' => process the whole line.  */
d3767b
             else if (optarg[1])
d3767b
               {
d3767b
-                if (STREQ (optarg, "\\0"))
d3767b
-                  newtab = '\0';
d3767b
-                else
d3767b
-                  die (EXIT_FAILURE, 0, _("multi-character tab %s"),
d3767b
-                       quote (optarg));
d3767b
+                if (newtablen == 1 && newtab[1])
d3767b
+                {
d3767b
+                  if (STREQ (newtab, "\\0"))
d3767b
+                     newtab[0] = '\0';
d3767b
+                }
d3767b
+              }
d3767b
+            if (tab != NULL && strcmp (tab, newtab))
d3767b
+              {
d3767b
+                free (newtab);
d3767b
+                die (EXIT_FAILURE, 0, _("incompatible tabs"));
d3767b
               }
d3767b
-            if (0 <= tab && tab != newtab)
d3767b
-              die (EXIT_FAILURE, 0, _("incompatible tabs"));
d3767b
             tab = newtab;
d3767b
+            tablen = newtablen;
d3767b
           }
d3767b
           break;
d3767b
 
d3767b
diff --git a/src/pr.c b/src/pr.c
d3767b
index 26f221f..633f50e 100644
d3767b
--- a/src/pr.c
d3767b
+++ b/src/pr.c
d3767b
@@ -311,6 +311,24 @@
d3767b
 
d3767b
 #include <getopt.h>
d3767b
 #include <sys/types.h>
d3767b
+
d3767b
+/* Get MB_LEN_MAX.  */
d3767b
+#include <limits.h>
d3767b
+/* MB_LEN_MAX is incorrectly defined to be 1 in at least one GCC
d3767b
+   installation; work around this configuration error.  */
d3767b
+#if !defined MB_LEN_MAX || MB_LEN_MAX == 1
d3767b
+# define MB_LEN_MAX 16
d3767b
+#endif
d3767b
+
d3767b
+/* Get MB_CUR_MAX.  */
d3767b
+#include <stdlib.h>
d3767b
+
d3767b
+/* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>.  */
d3767b
+/* Get mbstate_t, mbrtowc(), wcwidth().  */
d3767b
+#if HAVE_WCHAR_H
d3767b
+# include <wchar.h>
d3767b
+#endif
d3767b
+
d3767b
 #include "system.h"
d3767b
 #include "die.h"
d3767b
 #include "error.h"
d3767b
@@ -324,6 +342,18 @@
d3767b
 #include "xstrtol.h"
d3767b
 #include "xdectoint.h"
d3767b
 
d3767b
+/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t.  */
d3767b
+#if HAVE_MBRTOWC && defined mbstate_t
d3767b
+# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0)
d3767b
+#endif
d3767b
+
d3767b
+#ifndef HAVE_DECL_WCWIDTH
d3767b
+"this configure-time declaration test was not run"
d3767b
+#endif
d3767b
+#if !HAVE_DECL_WCWIDTH
d3767b
+extern int wcwidth ();
d3767b
+#endif
d3767b
+
d3767b
 /* The official name of this program (e.g., no 'g' prefix).  */
d3767b
 #define PROGRAM_NAME "pr"
d3767b
 
d3767b
@@ -416,7 +446,20 @@ struct COLUMN
d3767b
 
d3767b
 typedef struct COLUMN COLUMN;
d3767b
 
d3767b
-static int char_to_clump (char c);
d3767b
+/* Funtion pointers to switch functions for single byte locale or for
d3767b
+   multibyte locale. If multibyte functions do not exist in your sysytem,
d3767b
+   these pointers always point the function for single byte locale. */
d3767b
+static void (*print_char) (char c);
d3767b
+static int (*char_to_clump) (char c);
d3767b
+
d3767b
+/* Functions for single byte locale. */
d3767b
+static void print_char_single (char c);
d3767b
+static int char_to_clump_single (char c);
d3767b
+
d3767b
+/* Functions for multibyte locale. */
d3767b
+static void print_char_multi (char c);
d3767b
+static int char_to_clump_multi (char c);
d3767b
+
d3767b
 static bool read_line (COLUMN *p);
d3767b
 static bool print_page (void);
d3767b
 static bool print_stored (COLUMN *p);
d3767b
@@ -428,6 +471,7 @@ static void add_line_number (COLUMN *p);
d3767b
 static void getoptnum (const char *n_str, int min, int *num,
d3767b
                        const char *errfmt);
d3767b
 static void getoptarg (char *arg, char switch_char, char *character,
d3767b
+                       int *character_length, int *character_width,
d3767b
                        int *number);
d3767b
 static void print_files (int number_of_files, char **av);
d3767b
 static void init_parameters (int number_of_files);
d3767b
@@ -441,7 +485,6 @@ static void store_char (char c);
d3767b
 static void pad_down (unsigned int lines);
d3767b
 static void read_rest_of_line (COLUMN *p);
d3767b
 static void skip_read (COLUMN *p, int column_number);
d3767b
-static void print_char (char c);
d3767b
 static void cleanup (void);
d3767b
 static void print_sep_string (void);
d3767b
 static void separator_string (const char *optarg_S);
d3767b
@@ -453,7 +496,7 @@ static COLUMN *column_vector;
d3767b
    we store the leftmost columns contiguously in buff.
d3767b
    To print a line from buff, get the index of the first character
d3767b
    from line_vector[i], and print up to line_vector[i + 1]. */
d3767b
-static char *buff;
d3767b
+static unsigned char *buff;
d3767b
 
d3767b
 /* Index of the position in buff where the next character
d3767b
    will be stored. */
d3767b
@@ -557,7 +600,7 @@ static int chars_per_column;
d3767b
 static bool untabify_input = false;
d3767b
 
d3767b
 /* (-e) The input tab character. */
d3767b
-static char input_tab_char = '\t';
d3767b
+static char input_tab_char[MB_LEN_MAX] = "\t";
d3767b
 
d3767b
 /* (-e) Tabstops are at chars_per_tab, 2*chars_per_tab, 3*chars_per_tab, ...
d3767b
    where the leftmost column is 1. */
d3767b
@@ -567,7 +610,10 @@ static int chars_per_input_tab = 8;
d3767b
 static bool tabify_output = false;
d3767b
 
d3767b
 /* (-i) The output tab character. */
d3767b
-static char output_tab_char = '\t';
d3767b
+static char output_tab_char[MB_LEN_MAX] = "\t";
d3767b
+
d3767b
+/* (-i) The byte length of output tab character. */
d3767b
+static int output_tab_char_length = 1;
d3767b
 
d3767b
 /* (-i) The width of the output tab. */
d3767b
 static int chars_per_output_tab = 8;
d3767b
@@ -637,7 +683,13 @@ static int line_number;
d3767b
 static bool numbered_lines = false;
d3767b
 
d3767b
 /* (-n) Character which follows each line number. */
d3767b
-static char number_separator = '\t';
d3767b
+static char number_separator[MB_LEN_MAX] = "\t";
d3767b
+
d3767b
+/* (-n) The byte length of the character which follows each line number. */
d3767b
+static int number_separator_length = 1;
d3767b
+
d3767b
+/* (-n) The character width of the character which follows each line number. */
d3767b
+static int number_separator_width = 0;
d3767b
 
d3767b
 /* (-n) line counting starts with 1st line of input file (not with 1st
d3767b
    line of 1st page printed). */
d3767b
@@ -690,6 +742,7 @@ static bool use_col_separator = false;
d3767b
    -a|COLUMN|-m is a 'space' and with the -J option a 'tab'. */
d3767b
 static char const *col_sep_string = "";
d3767b
 static int col_sep_length = 0;
d3767b
+static int col_sep_width = 0;
d3767b
 static char *column_separator = (char *) " ";
d3767b
 static char *line_separator = (char *) "\t";
d3767b
 
d3767b
@@ -851,6 +904,13 @@ separator_string (const char *optarg_S)
d3767b
     integer_overflow ();
d3767b
   col_sep_length = len;
d3767b
   col_sep_string = optarg_S;
d3767b
+
d3767b
+#if HAVE_MBRTOWC
d3767b
+  if (MB_CUR_MAX > 1)
d3767b
+    col_sep_width = mbswidth (col_sep_string, 0);
d3767b
+  else
d3767b
+#endif
d3767b
+    col_sep_width = col_sep_length;
d3767b
 }
d3767b
 
d3767b
 int
d3767b
@@ -875,6 +935,21 @@ main (int argc, char **argv)
d3767b
 
d3767b
   atexit (close_stdout);
d3767b
 
d3767b
+/* Define which functions are used, the ones for single byte locale or the ones
d3767b
+   for multibyte locale. */
d3767b
+#if HAVE_MBRTOWC
d3767b
+  if (MB_CUR_MAX > 1)
d3767b
+    {
d3767b
+      print_char = print_char_multi;
d3767b
+      char_to_clump = char_to_clump_multi;
d3767b
+    }
d3767b
+  else
d3767b
+#endif
d3767b
+    {
d3767b
+      print_char = print_char_single;
d3767b
+      char_to_clump = char_to_clump_single;
d3767b
+    }
d3767b
+
d3767b
   n_files = 0;
d3767b
   file_names = (argc > 1
d3767b
                 ? xnmalloc (argc - 1, sizeof (char *))
d3767b
@@ -951,8 +1026,12 @@ main (int argc, char **argv)
d3767b
           break;
d3767b
         case 'e':
d3767b
           if (optarg)
d3767b
-            getoptarg (optarg, 'e', &input_tab_char,
d3767b
-                       &chars_per_input_tab);
d3767b
+            {
d3767b
+              int dummy_length, dummy_width;
d3767b
+
d3767b
+              getoptarg (optarg, 'e', input_tab_char, &dummy_length,
d3767b
+                         &dummy_width, &chars_per_input_tab);
d3767b
+            }
d3767b
           /* Could check tab width > 0. */
d3767b
           untabify_input = true;
d3767b
           break;
d3767b
@@ -965,8 +1044,12 @@ main (int argc, char **argv)
d3767b
           break;
d3767b
         case 'i':
d3767b
           if (optarg)
d3767b
-            getoptarg (optarg, 'i', &output_tab_char,
d3767b
-                       &chars_per_output_tab);
d3767b
+            {
d3767b
+              int dummy_width;
d3767b
+
d3767b
+              getoptarg (optarg, 'i', output_tab_char, &output_tab_char_length,
d3767b
+                         &dummy_width, &chars_per_output_tab);
d3767b
+            }
d3767b
           /* Could check tab width > 0. */
d3767b
           tabify_output = true;
d3767b
           break;
d3767b
@@ -984,8 +1067,8 @@ main (int argc, char **argv)
d3767b
         case 'n':
d3767b
           numbered_lines = true;
d3767b
           if (optarg)
d3767b
-            getoptarg (optarg, 'n', &number_separator,
d3767b
-                       &chars_per_number);
d3767b
+            getoptarg (optarg, 'n', number_separator, &number_separator_length,
d3767b
+                       &number_separator_width, &chars_per_number);
d3767b
           break;
d3767b
         case 'N':
d3767b
           skip_count = false;
d3767b
@@ -1010,6 +1093,7 @@ main (int argc, char **argv)
d3767b
           /* Reset an additional input of -s, -S dominates -s */
d3767b
           col_sep_string = "";
d3767b
           col_sep_length = 0;
d3767b
+          col_sep_width = 0;
d3767b
           use_col_separator = true;
d3767b
           if (optarg)
d3767b
             separator_string (optarg);
d3767b
@@ -1165,10 +1249,45 @@ getoptnum (const char *n_str, int min, int *num, const char *err)
d3767b
    a number. */
d3767b
 
d3767b
 static void
d3767b
-getoptarg (char *arg, char switch_char, char *character, int *number)
d3767b
+getoptarg (char *arg, char switch_char, char *character, int *character_length,
d3767b
+           int *character_width, int *number)
d3767b
 {
d3767b
   if (!ISDIGIT (*arg))
d3767b
-    *character = *arg++;
d3767b
+    {
d3767b
+#ifdef HAVE_MBRTOWC
d3767b
+      if (MB_CUR_MAX > 1)        /* for multibyte locale. */
d3767b
+        {
d3767b
+          wchar_t wc;
d3767b
+          size_t mblength;
d3767b
+          int width;
d3767b
+          mbstate_t state = {'\0'};
d3767b
+
d3767b
+          mblength = mbrtowc (&wc, arg, strnlen(arg, MB_LEN_MAX), &state);
d3767b
+
d3767b
+          if (mblength == (size_t)-1 || mblength == (size_t)-2)
d3767b
+            {
d3767b
+              *character_length = 1;
d3767b
+              *character_width = 1;
d3767b
+            }
d3767b
+          else
d3767b
+            {
d3767b
+              *character_length = (mblength < 1) ? 1 : mblength;
d3767b
+              width = wcwidth (wc);
d3767b
+              *character_width = (width < 0) ? 0 : width;
d3767b
+            }
d3767b
+
d3767b
+          strncpy (character, arg, *character_length);
d3767b
+          arg += *character_length;
d3767b
+        }
d3767b
+      else                        /* for single byte locale. */
d3767b
+#endif
d3767b
+        {
d3767b
+          *character = *arg++;
d3767b
+          *character_length = 1;
d3767b
+          *character_width = 1;
d3767b
+        }
d3767b
+    }
d3767b
+
d3767b
   if (*arg)
d3767b
     {
d3767b
       long int tmp_long;
d3767b
@@ -1190,6 +1309,11 @@ static void
d3767b
 init_parameters (int number_of_files)
d3767b
 {
d3767b
   int chars_used_by_number = 0;
d3767b
+  int mb_len = 1;
d3767b
+#if HAVE_MBRTOWC
d3767b
+  if (MB_CUR_MAX > 1)
d3767b
+    mb_len = MB_LEN_MAX;
d3767b
+#endif
d3767b
 
d3767b
   lines_per_body = lines_per_page - lines_per_header - lines_per_footer;
d3767b
   if (lines_per_body <= 0)
d3767b
@@ -1227,7 +1351,7 @@ init_parameters (int number_of_files)
d3767b
           else
d3767b
             col_sep_string = column_separator;
d3767b
 
d3767b
-          col_sep_length = 1;
d3767b
+          col_sep_length = col_sep_width = 1;
d3767b
           use_col_separator = true;
d3767b
         }
d3767b
       /* It's rather pointless to define a TAB separator with column
d3767b
@@ -1257,11 +1381,11 @@ init_parameters (int number_of_files)
d3767b
              + TAB_WIDTH (chars_per_input_tab, chars_per_number);   */
d3767b
 
d3767b
       /* Estimate chars_per_text without any margin and keep it constant. */
d3767b
-      if (number_separator == '\t')
d3767b
+      if (number_separator[0] == '\t')
d3767b
         number_width = (chars_per_number
d3767b
                         + TAB_WIDTH (chars_per_default_tab, chars_per_number));
d3767b
       else
d3767b
-        number_width = chars_per_number + 1;
d3767b
+        number_width = chars_per_number + number_separator_width;
d3767b
 
d3767b
       /* The number is part of the column width unless we are
d3767b
          printing files in parallel. */
d3767b
@@ -1270,7 +1394,7 @@ init_parameters (int number_of_files)
d3767b
     }
d3767b
 
d3767b
   int sep_chars, useful_chars;
d3767b
-  if (INT_MULTIPLY_WRAPV (columns - 1, col_sep_length, &sep_chars))
d3767b
+  if (INT_MULTIPLY_WRAPV (columns - 1, col_sep_width, &sep_chars))
d3767b
     sep_chars = INT_MAX;
d3767b
   if (INT_SUBTRACT_WRAPV (chars_per_line - chars_used_by_number, sep_chars,
d3767b
                           &useful_chars))
d3767b
@@ -1293,7 +1417,7 @@ init_parameters (int number_of_files)
d3767b
      We've to use 8 as the lower limit, if we use chars_per_default_tab = 8
d3767b
      to expand a tab which is not an input_tab-char. */
d3767b
   free (clump_buff);
d3767b
-  clump_buff = xmalloc (MAX (8, chars_per_input_tab));
d3767b
+  clump_buff = xmalloc (mb_len * MAX (8, chars_per_input_tab));
d3767b
 }
d3767b
 
d3767b
 /* Open the necessary files,
d3767b
@@ -1399,7 +1523,7 @@ init_funcs (void)
d3767b
 
d3767b
   /* Enlarge p->start_position of first column to use the same form of
d3767b
      padding_not_printed with all columns. */
d3767b
-  h = h + col_sep_length;
d3767b
+  h = h + col_sep_width;
d3767b
 
d3767b
   /* This loop takes care of all but the rightmost column. */
d3767b
 
d3767b
@@ -1433,7 +1557,7 @@ init_funcs (void)
d3767b
         }
d3767b
       else
d3767b
         {
d3767b
-          h = h_next + col_sep_length;
d3767b
+          h = h_next + col_sep_width;
d3767b
           h_next = h + chars_per_column;
d3767b
         }
d3767b
     }
d3767b
@@ -1724,9 +1848,9 @@ static void
d3767b
 align_column (COLUMN *p)
d3767b
 {
d3767b
   padding_not_printed = p->start_position;
d3767b
-  if (col_sep_length < padding_not_printed)
d3767b
+  if (col_sep_width < padding_not_printed)
d3767b
     {
d3767b
-      pad_across_to (padding_not_printed - col_sep_length);
d3767b
+      pad_across_to (padding_not_printed - col_sep_width);
d3767b
       padding_not_printed = ANYWHERE;
d3767b
     }
d3767b
 
d3767b
@@ -2001,13 +2125,13 @@ store_char (char c)
d3767b
       /* May be too generous. */
d3767b
       buff = X2REALLOC (buff, &buff_allocated);
d3767b
     }
d3767b
-  buff[buff_current++] = c;
d3767b
+  buff[buff_current++] = (unsigned char) c;
d3767b
 }
d3767b
 
d3767b
 static void
d3767b
 add_line_number (COLUMN *p)
d3767b
 {
d3767b
-  int i;
d3767b
+  int i, j;
d3767b
   char *s;
d3767b
   int num_width;
d3767b
 
d3767b
@@ -2024,22 +2148,24 @@ add_line_number (COLUMN *p)
d3767b
       /* Tabification is assumed for multiple columns, also for n-separators,
d3767b
          but 'default n-separator = TAB' hasn't been given priority over
d3767b
          equal column_width also specified by POSIX. */
d3767b
-      if (number_separator == '\t')
d3767b
+      if (number_separator[0] == '\t')
d3767b
         {
d3767b
           i = number_width - chars_per_number;
d3767b
           while (i-- > 0)
d3767b
             (p->char_func) (' ');
d3767b
         }
d3767b
       else
d3767b
-        (p->char_func) (number_separator);
d3767b
+        for (j = 0; j < number_separator_length; j++)
d3767b
+          (p->char_func) (number_separator[j]);
d3767b
     }
d3767b
   else
d3767b
     /* To comply with POSIX, we avoid any expansion of default TAB
d3767b
        separator with a single column output. No column_width requirement
d3767b
        has to be considered. */
d3767b
     {
d3767b
-      (p->char_func) (number_separator);
d3767b
-      if (number_separator == '\t')
d3767b
+      for (j = 0; j < number_separator_length; j++)
d3767b
+        (p->char_func) (number_separator[j]);
d3767b
+      if (number_separator[0] == '\t')
d3767b
         output_position = POS_AFTER_TAB (chars_per_output_tab,
d3767b
                           output_position);
d3767b
     }
d3767b
@@ -2198,7 +2324,7 @@ print_white_space (void)
d3767b
   while (goal - h_old > 1
d3767b
          && (h_new = POS_AFTER_TAB (chars_per_output_tab, h_old)) <= goal)
d3767b
     {
d3767b
-      putchar (output_tab_char);
d3767b
+      fwrite (output_tab_char, sizeof(char), output_tab_char_length, stdout);
d3767b
       h_old = h_new;
d3767b
     }
d3767b
   while (++h_old <= goal)
d3767b
@@ -2218,6 +2344,7 @@ print_sep_string (void)
d3767b
 {
d3767b
   char const *s = col_sep_string;
d3767b
   int l = col_sep_length;
d3767b
+  int not_space_flag;
d3767b
 
d3767b
   if (separators_not_printed <= 0)
d3767b
     {
d3767b
@@ -2229,6 +2356,7 @@ print_sep_string (void)
d3767b
     {
d3767b
       for (; separators_not_printed > 0; --separators_not_printed)
d3767b
         {
d3767b
+          not_space_flag = 0;
d3767b
           while (l-- > 0)
d3767b
             {
d3767b
               /* 3 types of sep_strings: spaces only, spaces and chars,
d3767b
@@ -2242,12 +2370,15 @@ print_sep_string (void)
d3767b
                 }
d3767b
               else
d3767b
                 {
d3767b
+                  not_space_flag = 1;
d3767b
                   if (spaces_not_printed > 0)
d3767b
                     print_white_space ();
d3767b
                   putchar (*s++);
d3767b
-                  ++output_position;
d3767b
                 }
d3767b
             }
d3767b
+          if (not_space_flag)
d3767b
+            output_position += col_sep_width;
d3767b
+
d3767b
           /* sep_string ends with some spaces */
d3767b
           if (spaces_not_printed > 0)
d3767b
             print_white_space ();
d3767b
@@ -2275,7 +2406,7 @@ print_clump (COLUMN *p, int n, char *clump)
d3767b
    required number of tabs and spaces. */
d3767b
 
d3767b
 static void
d3767b
-print_char (char c)
d3767b
+print_char_single (char c)
d3767b
 {
d3767b
   if (tabify_output)
d3767b
     {
d3767b
@@ -2299,6 +2430,74 @@ print_char (char c)
d3767b
   putchar (c);
d3767b
 }
d3767b
 
d3767b
+#ifdef HAVE_MBRTOWC
d3767b
+static void
d3767b
+print_char_multi (char c)
d3767b
+{
d3767b
+  static size_t mbc_pos = 0;
d3767b
+  static char mbc[MB_LEN_MAX] = {'\0'};
d3767b
+  static mbstate_t state = {'\0'};
d3767b
+  mbstate_t state_bak;
d3767b
+  wchar_t wc;
d3767b
+  size_t mblength;
d3767b
+  int width;
d3767b
+
d3767b
+  if (tabify_output)
d3767b
+    {
d3767b
+      state_bak = state;
d3767b
+      mbc[mbc_pos++] = c;
d3767b
+      mblength = mbrtowc (&wc, mbc, mbc_pos, &state);
d3767b
+
d3767b
+      while (mbc_pos > 0)
d3767b
+        {
d3767b
+          switch (mblength)
d3767b
+            {
d3767b
+            case (size_t)-2:
d3767b
+              state = state_bak;
d3767b
+              return;
d3767b
+
d3767b
+            case (size_t)-1:
d3767b
+              state = state_bak;
d3767b
+              ++output_position;
d3767b
+              putchar (mbc[0]);
d3767b
+              memmove (mbc, mbc + 1, MB_CUR_MAX - 1);
d3767b
+              --mbc_pos;
d3767b
+              break;
d3767b
+
d3767b
+            case 0:
d3767b
+              mblength = 1;
d3767b
+
d3767b
+            default:
d3767b
+              if (wc == L' ')
d3767b
+                {
d3767b
+                  memmove (mbc, mbc + mblength, MB_CUR_MAX - mblength);
d3767b
+                  --mbc_pos;
d3767b
+                  ++spaces_not_printed;
d3767b
+                  return;
d3767b
+                }
d3767b
+              else if (spaces_not_printed > 0)
d3767b
+                print_white_space ();
d3767b
+
d3767b
+              /* Nonprintables are assumed to have width 0, except L'\b'. */
d3767b
+              if ((width = wcwidth (wc)) < 1)
d3767b
+                {
d3767b
+                  if (wc == L'\b')
d3767b
+                    --output_position;
d3767b
+                }
d3767b
+              else
d3767b
+                output_position += width;
d3767b
+
d3767b
+              fwrite (mbc, sizeof(char), mblength, stdout);
d3767b
+              memmove (mbc, mbc + mblength, MB_CUR_MAX - mblength);
d3767b
+              mbc_pos -= mblength;
d3767b
+            }
d3767b
+        }
d3767b
+      return;
d3767b
+    }
d3767b
+  putchar (c);
d3767b
+}
d3767b
+#endif
d3767b
+
d3767b
 /* Skip to page PAGE before printing.
d3767b
    PAGE may be larger than total number of pages. */
d3767b
 
d3767b
@@ -2476,9 +2675,9 @@ read_line (COLUMN *p)
d3767b
           align_empty_cols = false;
d3767b
         }
d3767b
 
d3767b
-      if (col_sep_length < padding_not_printed)
d3767b
+      if (col_sep_width < padding_not_printed)
d3767b
         {
d3767b
-          pad_across_to (padding_not_printed - col_sep_length);
d3767b
+          pad_across_to (padding_not_printed - col_sep_width);
d3767b
           padding_not_printed = ANYWHERE;
d3767b
         }
d3767b
 
d3767b
@@ -2547,7 +2746,7 @@ print_stored (COLUMN *p)
d3767b
   COLUMN *q;
d3767b
 
d3767b
   int line = p->current_line++;
d3767b
-  char *first = &buff[line_vector[line]];
d3767b
+  unsigned char *first = &buff[line_vector[line]];
d3767b
   /* FIXME
d3767b
      UMR: Uninitialized memory read:
d3767b
      * This is occurring while in:
d3767b
@@ -2559,7 +2758,7 @@ print_stored (COLUMN *p)
d3767b
      xmalloc        [xmalloc.c:94]
d3767b
      init_store_cols [pr.c:1648]
d3767b
      */
d3767b
-  char *last = &buff[line_vector[line + 1]];
d3767b
+  unsigned char *last = &buff[line_vector[line + 1]];
d3767b
 
d3767b
   pad_vertically = true;
d3767b
 
d3767b
@@ -2579,9 +2778,9 @@ print_stored (COLUMN *p)
d3767b
         }
d3767b
     }
d3767b
 
d3767b
-  if (col_sep_length < padding_not_printed)
d3767b
+  if (col_sep_width < padding_not_printed)
d3767b
     {
d3767b
-      pad_across_to (padding_not_printed - col_sep_length);
d3767b
+      pad_across_to (padding_not_printed - col_sep_width);
d3767b
       padding_not_printed = ANYWHERE;
d3767b
     }
d3767b
 
d3767b
@@ -2594,8 +2793,8 @@ print_stored (COLUMN *p)
d3767b
   if (spaces_not_printed == 0)
d3767b
     {
d3767b
       output_position = p->start_position + end_vector[line];
d3767b
-      if (p->start_position - col_sep_length == chars_per_margin)
d3767b
-        output_position -= col_sep_length;
d3767b
+      if (p->start_position - col_sep_width == chars_per_margin)
d3767b
+        output_position -= col_sep_width;
d3767b
     }
d3767b
 
d3767b
   return true;
d3767b
@@ -2614,7 +2813,7 @@ print_stored (COLUMN *p)
d3767b
    number of characters is 1.) */
d3767b
 
d3767b
 static int
d3767b
-char_to_clump (char c)
d3767b
+char_to_clump_single (char c)
d3767b
 {
d3767b
   unsigned char uc = c;
d3767b
   char *s = clump_buff;
d3767b
@@ -2624,10 +2823,10 @@ char_to_clump (char c)
d3767b
   int chars;
d3767b
   int chars_per_c = 8;
d3767b
 
d3767b
-  if (c == input_tab_char)
d3767b
+  if (c == input_tab_char[0])
d3767b
     chars_per_c = chars_per_input_tab;
d3767b
 
d3767b
-  if (c == input_tab_char || c == '\t')
d3767b
+  if (c == input_tab_char[0] || c == '\t')
d3767b
     {
d3767b
       width = TAB_WIDTH (chars_per_c, input_position);
d3767b
 
d3767b
@@ -2708,6 +2907,164 @@ char_to_clump (char c)
d3767b
   return chars;
d3767b
 }
d3767b
 
d3767b
+#ifdef HAVE_MBRTOWC
d3767b
+static int
d3767b
+char_to_clump_multi (char c)
d3767b
+{
d3767b
+  static size_t mbc_pos = 0;
d3767b
+  static char mbc[MB_LEN_MAX] = {'\0'};
d3767b
+  static mbstate_t state = {'\0'};
d3767b
+  mbstate_t state_bak;
d3767b
+  wchar_t wc;
d3767b
+  size_t mblength;
d3767b
+  int wc_width;
d3767b
+  register char *s = clump_buff;
d3767b
+  register int i, j;
d3767b
+  char esc_buff[4];
d3767b
+  int width;
d3767b
+  int chars;
d3767b
+  int chars_per_c = 8;
d3767b
+
d3767b
+  state_bak = state;
d3767b
+  mbc[mbc_pos++] = c;
d3767b
+  mblength = mbrtowc (&wc, mbc, mbc_pos, &state);
d3767b
+
d3767b
+  width = 0;
d3767b
+  chars = 0;
d3767b
+  while (mbc_pos > 0)
d3767b
+    {
d3767b
+      switch (mblength)
d3767b
+        {
d3767b
+        case (size_t)-2:
d3767b
+          state = state_bak;
d3767b
+          return 0;
d3767b
+
d3767b
+        case (size_t)-1:
d3767b
+          state = state_bak;
d3767b
+          mblength = 1;
d3767b
+
d3767b
+          if (use_esc_sequence || use_cntrl_prefix)
d3767b
+            {
d3767b
+              width = +4;
d3767b
+              chars = +4;
d3767b
+              *s++ = '\\';
d3767b
+              sprintf (esc_buff, "%03o", (unsigned char) mbc[0]);
d3767b
+              for (i = 0; i <= 2; ++i)
d3767b
+                *s++ = (int) esc_buff[i];
d3767b
+            }
d3767b
+          else
d3767b
+            {
d3767b
+              width += 1;
d3767b
+              chars += 1;
d3767b
+              *s++ = mbc[0];
d3767b
+            }
d3767b
+          break;
d3767b
+
d3767b
+        case 0:
d3767b
+          mblength = 1;
d3767b
+                /* Fall through */
d3767b
+
d3767b
+        default:
d3767b
+          if (memcmp (mbc, input_tab_char, mblength) == 0)
d3767b
+            chars_per_c = chars_per_input_tab;
d3767b
+
d3767b
+          if (memcmp (mbc, input_tab_char, mblength) == 0 || c == '\t')
d3767b
+            {
d3767b
+              int  width_inc;
d3767b
+
d3767b
+              width_inc = TAB_WIDTH (chars_per_c, input_position);
d3767b
+              width += width_inc;
d3767b
+
d3767b
+              if (untabify_input)
d3767b
+                {
d3767b
+                  for (i = width_inc; i; --i)
d3767b
+                    *s++ = ' ';
d3767b
+                  chars += width_inc;
d3767b
+                }
d3767b
+              else
d3767b
+                {
d3767b
+                  for (i = 0; i <  mblength; i++)
d3767b
+                    *s++ = mbc[i];
d3767b
+                  chars += mblength;
d3767b
+                }
d3767b
+            }
d3767b
+          else if ((wc_width = wcwidth (wc)) < 1)
d3767b
+            {
d3767b
+              if (use_esc_sequence)
d3767b
+                {
d3767b
+                  for (i = 0; i < mblength; i++)
d3767b
+                    {
d3767b
+                      width += 4;
d3767b
+                      chars += 4;
d3767b
+                      *s++ = '\\';
d3767b
+                      sprintf (esc_buff, "%03o", (unsigned char) mbc[i]);
d3767b
+                      for (j = 0; j <= 2; ++j)
d3767b
+                        *s++ = (int) esc_buff[j];
d3767b
+                    }
d3767b
+                }
d3767b
+              else if (use_cntrl_prefix)
d3767b
+                {
d3767b
+                  if (wc < 0200)
d3767b
+                    {
d3767b
+                      width += 2;
d3767b
+                      chars += 2;
d3767b
+                      *s++ = '^';
d3767b
+                      *s++ = wc ^ 0100;
d3767b
+                    }
d3767b
+                  else
d3767b
+                    {
d3767b
+                      for (i = 0; i < mblength; i++)
d3767b
+                        {
d3767b
+                          width += 4;
d3767b
+                          chars += 4;
d3767b
+                          *s++ = '\\';
d3767b
+                          sprintf (esc_buff, "%03o", (unsigned char) mbc[i]);
d3767b
+                          for (j = 0; j <= 2; ++j)
d3767b
+                            *s++ = (int) esc_buff[j];
d3767b
+                        }
d3767b
+                    }
d3767b
+                }
d3767b
+              else if (wc == L'\b')
d3767b
+                {
d3767b
+                  width += -1;
d3767b
+                  chars += 1;
d3767b
+                  *s++ = c;
d3767b
+                }
d3767b
+              else
d3767b
+                {
d3767b
+                  width += 0;
d3767b
+                  chars += mblength;
d3767b
+                  for (i = 0; i < mblength; i++)
d3767b
+                    *s++ = mbc[i];
d3767b
+                }
d3767b
+            }
d3767b
+          else
d3767b
+            {
d3767b
+              width += wc_width;
d3767b
+              chars += mblength;
d3767b
+              for (i = 0; i < mblength; i++)
d3767b
+                *s++ = mbc[i];
d3767b
+            }
d3767b
+        }
d3767b
+      memmove (mbc, mbc + mblength, MB_CUR_MAX - mblength);
d3767b
+      mbc_pos -= mblength;
d3767b
+    }
d3767b
+
d3767b
+  /* Too many backspaces must put us in position 0 -- never negative. */
d3767b
+  if (width < 0 && input_position == 0)
d3767b
+    {
d3767b
+      chars = 0;
d3767b
+      input_position = 0;
d3767b
+    }
d3767b
+  else if (width < 0 && input_position <= -width)
d3767b
+    input_position = 0;
d3767b
+  else
d3767b
+   input_position += width;
d3767b
+
d3767b
+  return chars;
d3767b
+}
d3767b
+#endif
d3767b
+
d3767b
 /* We've just printed some files and need to clean up things before
d3767b
    looking for more options and printing the next batch of files.
d3767b
 
d3767b
diff --git a/src/sort.c b/src/sort.c
d3767b
index 6d2eec5..f189a0d 100644
d3767b
--- a/src/sort.c
d3767b
+++ b/src/sort.c
d3767b
@@ -29,6 +29,14 @@
d3767b
 #include <sys/wait.h>
d3767b
 #include <signal.h>
d3767b
 #include <assert.h>
d3767b
+#if HAVE_WCHAR_H
d3767b
+# include <wchar.h>
d3767b
+#endif
d3767b
+/* Get isw* functions. */
d3767b
+#if HAVE_WCTYPE_H
d3767b
+# include <wctype.h>
d3767b
+#endif
d3767b
+
d3767b
 #include "system.h"
d3767b
 #include "argmatch.h"
d3767b
 #include "die.h"
d3767b
@@ -169,14 +177,39 @@ static int decimal_point;
d3767b
 /* Thousands separator; if -1, then there isn't one.  */
d3767b
 static int thousands_sep;
d3767b
 
d3767b
+/* True if -f is specified.  */
d3767b
+static bool folding;
d3767b
+
d3767b
 /* Nonzero if the corresponding locales are hard.  */
d3767b
 static bool hard_LC_COLLATE;
d3767b
-#if HAVE_NL_LANGINFO
d3767b
+#if HAVE_LANGINFO_CODESET
d3767b
 static bool hard_LC_TIME;
d3767b
 #endif
d3767b
 
d3767b
 #define NONZERO(x) ((x) != 0)
d3767b
 
d3767b
+/* get a multibyte character's byte length. */
d3767b
+#define GET_BYTELEN_OF_CHAR(LIM, PTR, MBLENGTH, STATE)                        \
d3767b
+  do                                                                        \
d3767b
+    {                                                                        \
d3767b
+      wchar_t wc;                                                        \
d3767b
+      mbstate_t state_bak;                                                \
d3767b
+                                                                        \
d3767b
+      state_bak = STATE;                                                \
d3767b
+      mblength = mbrtowc (&wc, PTR, LIM - PTR, &STATE);                        \
d3767b
+                                                                        \
d3767b
+      switch (MBLENGTH)                                                        \
d3767b
+        {                                                                \
d3767b
+        case (size_t)-1:                                                \
d3767b
+        case (size_t)-2:                                                \
d3767b
+          STATE = state_bak;                                                \
d3767b
+                /* Fall through. */                                        \
d3767b
+        case 0:                                                                \
d3767b
+          MBLENGTH = 1;                                                        \
d3767b
+      }                                                                        \
d3767b
+    }                                                                        \
d3767b
+  while (0)
d3767b
+
d3767b
 /* The kind of blanks for '-b' to skip in various options. */
d3767b
 enum blanktype { bl_start, bl_end, bl_both };
d3767b
 
d3767b
@@ -350,13 +383,11 @@ static bool reverse;
d3767b
    they were read if all keys compare equal.  */
d3767b
 static bool stable;
d3767b
 
d3767b
-/* If TAB has this value, blanks separate fields.  */
d3767b
-enum { TAB_DEFAULT = CHAR_MAX + 1 };
d3767b
-
d3767b
-/* Tab character separating fields.  If TAB_DEFAULT, then fields are
d3767b
+/* Tab character separating fields.  If tab_length is 0, then fields are
d3767b
    separated by the empty string between a non-blank character and a blank
d3767b
    character. */
d3767b
-static int tab = TAB_DEFAULT;
d3767b
+static char tab[MB_LEN_MAX + 1];
d3767b
+static size_t tab_length = 0;
d3767b
 
d3767b
 /* Flag to remove consecutive duplicate lines from the output.
d3767b
    Only the last of a sequence of equal lines will be output. */
d3767b
@@ -814,6 +845,46 @@ reap_all (void)
d3767b
     reap (-1);
d3767b
 }
d3767b
 
d3767b
+/* Function pointers. */
d3767b
+static void
d3767b
+(*inittables) (void);
d3767b
+static char *
d3767b
+(*begfield) (const struct line*, const struct keyfield *);
d3767b
+static char *
d3767b
+(*limfield) (const struct line*, const struct keyfield *);
d3767b
+static void
d3767b
+(*skipblanks) (char **ptr, char *lim);
d3767b
+static int
d3767b
+(*getmonth) (char const *, size_t, char **);
d3767b
+static int
d3767b
+(*keycompare) (const struct line *, const struct line *);
d3767b
+static int
d3767b
+(*numcompare) (const char *, const char *);
d3767b
+
d3767b
+/* Test for white space multibyte character.
d3767b
+   Set LENGTH the byte length of investigated multibyte character. */
d3767b
+#if HAVE_MBRTOWC
d3767b
+static int
d3767b
+ismbblank (const char *str, size_t len, size_t *length)
d3767b
+{
d3767b
+  size_t mblength;
d3767b
+  wchar_t wc;
d3767b
+  mbstate_t state;
d3767b
+
d3767b
+  memset (&state, '\0', sizeof(mbstate_t));
d3767b
+  mblength = mbrtowc (&wc, str, len, &state);
d3767b
+
d3767b
+  if (mblength == (size_t)-1 || mblength == (size_t)-2)
d3767b
+    {
d3767b
+      *length = 1;
d3767b
+      return 0;
d3767b
+    }
d3767b
+
d3767b
+  *length = (mblength < 1) ? 1 : mblength;
d3767b
+  return iswblank (wc) || wc == '\n';
d3767b
+}
d3767b
+#endif
d3767b
+
d3767b
 /* Clean up any remaining temporary files.  */
d3767b
 
d3767b
 static void
d3767b
@@ -1264,7 +1335,7 @@ zaptemp (char const *name)
d3767b
   free (node);
d3767b
 }
d3767b
 
d3767b
-#if HAVE_NL_LANGINFO
d3767b
+#if HAVE_LANGINFO_CODESET
d3767b
 
d3767b
 static int
d3767b
 struct_month_cmp (void const *m1, void const *m2)
d3767b
@@ -1279,7 +1350,7 @@ struct_month_cmp (void const *m1, void const *m2)
d3767b
 /* Initialize the character class tables. */
d3767b
 
d3767b
 static void
d3767b
-inittables (void)
d3767b
+inittables_uni (void)
d3767b
 {
d3767b
   size_t i;
d3767b
 
d3767b
@@ -1291,7 +1362,7 @@ inittables (void)
d3767b
       fold_toupper[i] = toupper (i);
d3767b
     }
d3767b
 
d3767b
-#if HAVE_NL_LANGINFO
d3767b
+#if HAVE_LANGINFO_CODESET
d3767b
   /* If we're not in the "C" locale, read different names for months.  */
d3767b
   if (hard_LC_TIME)
d3767b
     {
d3767b
@@ -1373,6 +1444,84 @@ specify_nmerge (int oi, char c, char const *s)
d3767b
     xstrtol_fatal (e, oi, c, long_options, s);
d3767b
 }
d3767b
 
d3767b
+#if HAVE_MBRTOWC
d3767b
+static void
d3767b
+inittables_mb (void)
d3767b
+{
d3767b
+  int i, j, k, l;
d3767b
+  char *name, *s, *lc_time, *lc_ctype;
d3767b
+  size_t s_len, mblength;
d3767b
+  char mbc[MB_LEN_MAX];
d3767b
+  wchar_t wc, pwc;
d3767b
+  mbstate_t state_mb, state_wc;
d3767b
+
d3767b
+  lc_time = setlocale (LC_TIME, "");
d3767b
+  if (lc_time)
d3767b
+    lc_time = xstrdup (lc_time);
d3767b
+
d3767b
+  lc_ctype = setlocale (LC_CTYPE, "");
d3767b
+  if (lc_ctype)
d3767b
+    lc_ctype = xstrdup (lc_ctype);
d3767b
+
d3767b
+  if (lc_time && lc_ctype)
d3767b
+    /* temporarily set LC_CTYPE to match LC_TIME, so that we can convert
d3767b
+     * the names of months to upper case */
d3767b
+    setlocale (LC_CTYPE, lc_time);
d3767b
+
d3767b
+  for (i = 0; i < MONTHS_PER_YEAR; i++)
d3767b
+    {
d3767b
+      s = (char *) nl_langinfo (ABMON_1 + i);
d3767b
+      s_len = strlen (s);
d3767b
+      monthtab[i].name = name = (char *) xmalloc (s_len + 1);
d3767b
+      monthtab[i].val = i + 1;
d3767b
+
d3767b
+      memset (&state_mb, '\0', sizeof (mbstate_t));
d3767b
+      memset (&state_wc, '\0', sizeof (mbstate_t));
d3767b
+
d3767b
+      for (j = 0; j < s_len;)
d3767b
+        {
d3767b
+          if (!ismbblank (s + j, s_len - j, &mblength))
d3767b
+            break;
d3767b
+          j += mblength;
d3767b
+        }
d3767b
+
d3767b
+      for (k = 0; j < s_len;)
d3767b
+        {
d3767b
+          mblength = mbrtowc (&wc, (s + j), (s_len - j), &state_mb);
d3767b
+          assert (mblength != (size_t)-1 && mblength != (size_t)-2);
d3767b
+          if (mblength == 0)
d3767b
+            break;
d3767b
+
d3767b
+          pwc = towupper (wc);
d3767b
+          if (pwc == wc)
d3767b
+            {
d3767b
+              memcpy (mbc, s + j, mblength);
d3767b
+              j += mblength;
d3767b
+            }
d3767b
+          else
d3767b
+            {
d3767b
+              j += mblength;
d3767b
+              mblength = wcrtomb (mbc, pwc, &state_wc);
d3767b
+              assert (mblength != (size_t)0 && mblength != (size_t)-1);
d3767b
+            }
d3767b
+
d3767b
+          for (l = 0; l < mblength; l++)
d3767b
+            name[k++] = mbc[l];
d3767b
+        }
d3767b
+      name[k] = '\0';
d3767b
+    }
d3767b
+  qsort ((void *) monthtab, MONTHS_PER_YEAR,
d3767b
+      sizeof (struct month), struct_month_cmp);
d3767b
+
d3767b
+  if (lc_time && lc_ctype)
d3767b
+    /* restore the original locales */
d3767b
+    setlocale (LC_CTYPE, lc_ctype);
d3767b
+
d3767b
+  free (lc_ctype);
d3767b
+  free (lc_time);
d3767b
+}
d3767b
+#endif
d3767b
+
d3767b
 /* Specify the amount of main memory to use when sorting.  */
d3767b
 static void
d3767b
 specify_sort_size (int oi, char c, char const *s)
d3767b
@@ -1604,7 +1753,7 @@ buffer_linelim (struct buffer const *buf)
d3767b
    by KEY in LINE. */
d3767b
 
d3767b
 static char *
d3767b
-begfield (struct line const *line, struct keyfield const *key)
d3767b
+begfield_uni (const struct line *line, const struct keyfield *key)
d3767b
 {
d3767b
   char *ptr = line->text, *lim = ptr + line->length - 1;
d3767b
   size_t sword = key->sword;
d3767b
@@ -1613,10 +1762,10 @@ begfield (struct line const *line, struct keyfield const *key)
d3767b
   /* The leading field separator itself is included in a field when -t
d3767b
      is absent.  */
d3767b
 
d3767b
-  if (tab != TAB_DEFAULT)
d3767b
+  if (tab_length)
d3767b
     while (ptr < lim && sword--)
d3767b
       {
d3767b
-        while (ptr < lim && *ptr != tab)
d3767b
+        while (ptr < lim && *ptr != tab[0])
d3767b
           ++ptr;
d3767b
         if (ptr < lim)
d3767b
           ++ptr;
d3767b
@@ -1642,11 +1791,70 @@ begfield (struct line const *line, struct keyfield const *key)
d3767b
   return ptr;
d3767b
 }
d3767b
 
d3767b
+#if HAVE_MBRTOWC
d3767b
+static char *
d3767b
+begfield_mb (const struct line *line, const struct keyfield *key)
d3767b
+{
d3767b
+  int i;
d3767b
+  char *ptr = line->text, *lim = ptr + line->length - 1;
d3767b
+  size_t sword = key->sword;
d3767b
+  size_t schar = key->schar;
d3767b
+  size_t mblength;
d3767b
+  mbstate_t state;
d3767b
+
d3767b
+  memset (&state, '\0', sizeof(mbstate_t));
d3767b
+
d3767b
+  if (tab_length)
d3767b
+    while (ptr < lim && sword--)
d3767b
+      {
d3767b
+        while (ptr < lim && memcmp (ptr, tab, tab_length) != 0)
d3767b
+          {
d3767b
+            GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
d3767b
+            ptr += mblength;
d3767b
+          }
d3767b
+        if (ptr < lim)
d3767b
+          {
d3767b
+            GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
d3767b
+            ptr += mblength;
d3767b
+          }
d3767b
+      }
d3767b
+  else
d3767b
+    while (ptr < lim && sword--)
d3767b
+      {
d3767b
+        while (ptr < lim && ismbblank (ptr, lim - ptr, &mblength))
d3767b
+          ptr += mblength;
d3767b
+        if (ptr < lim)
d3767b
+          {
d3767b
+            GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
d3767b
+            ptr += mblength;
d3767b
+          }
d3767b
+        while (ptr < lim && !ismbblank (ptr, lim - ptr, &mblength))
d3767b
+          ptr += mblength;
d3767b
+      }
d3767b
+
d3767b
+  if (key->skipsblanks)
d3767b
+    while (ptr < lim && ismbblank (ptr, lim - ptr, &mblength))
d3767b
+      ptr += mblength;
d3767b
+
d3767b
+  for (i = 0; i < schar; i++)
d3767b
+    {
d3767b
+      GET_BYTELEN_OF_CHAR (lim, ptr, mblength, state);
d3767b
+
d3767b
+      if (ptr + mblength > lim)
d3767b
+        break;
d3767b
+      else
d3767b
+        ptr += mblength;
d3767b
+    }
d3767b
+