+#include <unigbrk.h>
+
+#include "libpspp/assertion.h"
+#include "libpspp/hmapx.h"
+#include "libpspp/hash-functions.h"
+#include "libpspp/pool.h"
+#include "libpspp/str.h"
+#include "libpspp/version.h"
+
+#include "gl/c-strcase.h"
+#include "gl/localcharset.h"
+#include "gl/xalloc.h"
+#include "gl/relocatable.h"
+#include "gl/xstrndup.h"
+
+struct converter
+ {
+ char *tocode;
+ char *fromcode;
+ iconv_t conv;
+ int error;
+ };
+
+static char *default_encoding;
+static struct hmapx map;
+
+/* A wrapper around iconv_open */
+static struct converter *
+create_iconv__ (const char* tocode, const char* fromcode)
+{
+ size_t hash;
+ struct hmapx_node *node;
+ struct converter *converter;
+ assert (fromcode);
+
+ hash = hash_string (tocode, hash_string (fromcode, 0));
+ HMAPX_FOR_EACH_WITH_HASH (converter, node, hash, &map)
+ if (!strcmp (tocode, converter->tocode)
+ && !strcmp (fromcode, converter->fromcode))
+ return converter;
+
+ converter = xmalloc (sizeof *converter);
+ converter->tocode = xstrdup (tocode);
+ converter->fromcode = xstrdup (fromcode);
+ converter->conv = iconv_open (tocode, fromcode);
+ converter->error = converter->conv == (iconv_t) -1 ? errno : 0;
+ hmapx_insert (&map, converter, hash);
+
+ return converter;
+}
+
+static iconv_t
+create_iconv (const char* tocode, const char* fromcode)
+{
+ struct converter *converter;
+
+ converter = create_iconv__ (tocode, fromcode);
+
+ /* I don't think it's safe to translate this string or to use messaging
+ as the converters have not yet been set up */
+ if (converter->error && strcmp (tocode, fromcode))
+ {
+ fprintf (stderr,
+ "Warning: "
+ "cannot create a converter for `%s' to `%s': %s\n",
+ fromcode, tocode, strerror (converter->error));
+ converter->error = 0;
+ }
+
+ return converter->conv;
+}
+
+/* Converts the single byte C from encoding FROM to TO, returning the first
+ byte of the result.
+
+ This function probably shouldn't be used at all, but some code still does
+ use it. */
+char
+recode_byte (const char *to, const char *from, char c)
+{
+ char x;
+ char *s = recode_string (to, from, &c, 1);
+ x = s[0];
+ free (s);
+ return x;
+}
+
+/* Similar to recode_string_pool, but allocates the returned value on the heap
+ instead of in a pool. It is the caller's responsibility to free the
+ returned value. */
+char *
+recode_string (const char *to, const char *from,
+ const char *text, int length)
+{
+ return recode_string_pool (to, from, text, length, NULL);
+}
+
+/* Returns the length, in bytes, of the string that a similar recode_string()
+ call would return. */
+size_t
+recode_string_len (const char *to, const char *from,
+ const char *text, int length)
+{
+ char *s = recode_string (to, from, text, length);
+ size_t len = strlen (s);
+ free (s);
+ return len;
+}
+
+/* Uses CONV to convert the INBYTES starting at IP into the OUTBYTES starting
+ at OP, and appends a null terminator to the output.
+
+ Returns the output length if successful, -1 if the output buffer is too
+ small. */
+static ssize_t
+try_recode (iconv_t conv,
+ const char *ip, size_t inbytes,
+ char *op_, size_t outbytes)
+{
+ /* FIXME: Need to ensure that this char is valid in the target encoding */
+ const char fallbackchar = '?';
+ char *op = op_;
+
+ /* Put the converter into the initial shift state, in case there was any
+ state information left over from its last usage. */
+ iconv (conv, NULL, 0, NULL, 0);
+
+ while (iconv (conv, (ICONV_CONST char **) &ip, &inbytes,
+ &op, &outbytes) == -1)
+ switch (errno)
+ {
+ case EINVAL:
+ if (outbytes < 2)
+ return -1;
+ *op++ = fallbackchar;
+ *op = '\0';
+ return op - op_;
+
+ case EILSEQ:
+ if (outbytes == 0)
+ return -1;
+ *op++ = fallbackchar;
+ outbytes--;
+ ip++;
+ inbytes--;
+ break;
+
+ case E2BIG:
+ return -1;
+
+ default:
+ /* should never happen */
+ fprintf (stderr, "Character conversion error: %s\n", strerror (errno));
+ NOT_REACHED ();
+ break;
+ }
+
+ if (outbytes == 0)
+ return -1;