Factored character encoders out to separate files (iconv system).
[yaz-moved-to-github.git] / src / iconv_encode_iso_8859_1.c
diff --git a/src/iconv_encode_iso_8859_1.c b/src/iconv_encode_iso_8859_1.c
new file mode 100644 (file)
index 0000000..f847186
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 1995-2008, Index Data ApS
+ * See the file LICENSE for details.
+ *
+ */
+/**
+ * \file
+ * \brief ISO-8859-1 encoding / decoding
+ *
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#if HAVE_ICONV_H
+#include <iconv.h>
+#endif
+
+#include <yaz/xmalloc.h>
+#include <yaz/nmem.h>
+#include "iconv-p.h"
+
+struct encoder_data
+{
+    unsigned long compose_char;
+};
+
+
+
+static struct {
+    unsigned long x1, x2;
+    unsigned y;
+} latin1_comb[] = {
+    { 'A', 0x0300, 0xc0}, /* LATIN CAPITAL LETTER A WITH GRAVE */
+    { 'A', 0x0301, 0xc1}, /* LATIN CAPITAL LETTER A WITH ACUTE */
+    { 'A', 0x0302, 0xc2}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
+    { 'A', 0x0303, 0xc3}, /* LATIN CAPITAL LETTER A WITH TILDE */
+    { 'A', 0x0308, 0xc4}, /* LATIN CAPITAL LETTER A WITH DIAERESIS */
+    { 'A', 0x030a, 0xc5}, /* LATIN CAPITAL LETTER A WITH RING ABOVE */
+    /* no need for 0xc6      LATIN CAPITAL LETTER AE */
+    { 'C', 0x0327, 0xc7}, /* LATIN CAPITAL LETTER C WITH CEDILLA */
+    { 'E', 0x0300, 0xc8}, /* LATIN CAPITAL LETTER E WITH GRAVE */
+    { 'E', 0x0301, 0xc9}, /* LATIN CAPITAL LETTER E WITH ACUTE */
+    { 'E', 0x0302, 0xca}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
+    { 'E', 0x0308, 0xcb}, /* LATIN CAPITAL LETTER E WITH DIAERESIS */
+    { 'I', 0x0300, 0xcc}, /* LATIN CAPITAL LETTER I WITH GRAVE */
+    { 'I', 0x0301, 0xcd}, /* LATIN CAPITAL LETTER I WITH ACUTE */
+    { 'I', 0x0302, 0xce}, /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
+    { 'I', 0x0308, 0xcf}, /* LATIN CAPITAL LETTER I WITH DIAERESIS */
+    { 'N', 0x0303, 0xd1}, /* LATIN CAPITAL LETTER N WITH TILDE */
+    { 'O', 0x0300, 0xd2}, /* LATIN CAPITAL LETTER O WITH GRAVE */
+    { 'O', 0x0301, 0xd3}, /* LATIN CAPITAL LETTER O WITH ACUTE */
+    { 'O', 0x0302, 0xd4}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
+    { 'O', 0x0303, 0xd5}, /* LATIN CAPITAL LETTER O WITH TILDE */
+    { 'O', 0x0308, 0xd6}, /* LATIN CAPITAL LETTER O WITH DIAERESIS */
+    /* omitted:    0xd7      MULTIPLICATION SIGN */
+    /* omitted:    0xd8      LATIN CAPITAL LETTER O WITH STROKE */
+    { 'U', 0x0300, 0xd9}, /* LATIN CAPITAL LETTER U WITH GRAVE */
+    { 'U', 0x0301, 0xda}, /* LATIN CAPITAL LETTER U WITH ACUTE */
+    { 'U', 0x0302, 0xdb}, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
+    { 'U', 0x0308, 0xdc}, /* LATIN CAPITAL LETTER U WITH DIAERESIS */
+    { 'Y', 0x0301, 0xdd}, /* LATIN CAPITAL LETTER Y WITH ACUTE */
+    /* omitted:    0xde      LATIN CAPITAL LETTER THORN */
+    /* omitted:    0xdf      LATIN SMALL LETTER SHARP S */
+    { 'a', 0x0300, 0xe0}, /* LATIN SMALL LETTER A WITH GRAVE */
+    { 'a', 0x0301, 0xe1}, /* LATIN SMALL LETTER A WITH ACUTE */
+    { 'a', 0x0302, 0xe2}, /* LATIN SMALL LETTER A WITH CIRCUMFLEX */
+    { 'a', 0x0303, 0xe3}, /* LATIN SMALL LETTER A WITH TILDE */
+    { 'a', 0x0308, 0xe4}, /* LATIN SMALL LETTER A WITH DIAERESIS */
+    { 'a', 0x030a, 0xe5}, /* LATIN SMALL LETTER A WITH RING ABOVE */
+    /* omitted:    0xe6      LATIN SMALL LETTER AE */
+    { 'c', 0x0327, 0xe7}, /* LATIN SMALL LETTER C WITH CEDILLA */
+    { 'e', 0x0300, 0xe8}, /* LATIN SMALL LETTER E WITH GRAVE */
+    { 'e', 0x0301, 0xe9}, /* LATIN SMALL LETTER E WITH ACUTE */
+    { 'e', 0x0302, 0xea}, /* LATIN SMALL LETTER E WITH CIRCUMFLEX */
+    { 'e', 0x0308, 0xeb}, /* LATIN SMALL LETTER E WITH DIAERESIS */
+    { 'i', 0x0300, 0xec}, /* LATIN SMALL LETTER I WITH GRAVE */
+    { 'i', 0x0301, 0xed}, /* LATIN SMALL LETTER I WITH ACUTE */
+    { 'i', 0x0302, 0xee}, /* LATIN SMALL LETTER I WITH CIRCUMFLEX */
+    { 'i', 0x0308, 0xef}, /* LATIN SMALL LETTER I WITH DIAERESIS */
+    /* omitted:    0xf0      LATIN SMALL LETTER ETH */
+    { 'n', 0x0303, 0xf1}, /* LATIN SMALL LETTER N WITH TILDE */
+    { 'o', 0x0300, 0xf2}, /* LATIN SMALL LETTER O WITH GRAVE */
+    { 'o', 0x0301, 0xf3}, /* LATIN SMALL LETTER O WITH ACUTE */
+    { 'o', 0x0302, 0xf4}, /* LATIN SMALL LETTER O WITH CIRCUMFLEX */
+    { 'o', 0x0303, 0xf5}, /* LATIN SMALL LETTER O WITH TILDE */
+    { 'o', 0x0308, 0xf6}, /* LATIN SMALL LETTER O WITH DIAERESIS */
+    /* omitted:    0xf7      DIVISION SIGN */
+    /* omitted:    0xf8      LATIN SMALL LETTER O WITH STROKE */
+    { 'u', 0x0300, 0xf9}, /* LATIN SMALL LETTER U WITH GRAVE */
+    { 'u', 0x0301, 0xfa}, /* LATIN SMALL LETTER U WITH ACUTE */
+    { 'u', 0x0302, 0xfb}, /* LATIN SMALL LETTER U WITH CIRCUMFLEX */
+    { 'u', 0x0308, 0xfc}, /* LATIN SMALL LETTER U WITH DIAERESIS */
+    { 'y', 0x0301, 0xfd}, /* LATIN SMALL LETTER Y WITH ACUTE */
+    /* omitted:    0xfe      LATIN SMALL LETTER THORN */
+    { 'y', 0x0308, 0xff}, /* LATIN SMALL LETTER Y WITH DIAERESIS */
+    
+    { 0, 0, 0}
+};
+
+int yaz_iso_8859_1_lookup_y(unsigned long v,
+                            unsigned long *x1, unsigned long *x2)
+{
+    if (v >= 0xc0 && v <= 0xff) /* optimization. min and max .y values */
+    {
+        int i;
+        for (i = 0; latin1_comb[i].x1; i++)
+        {
+            if (v == latin1_comb[i].y)
+            {
+                *x1 = latin1_comb[i].x1;
+                *x2 = latin1_comb[i].x2;
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+int yaz_iso_8859_1_lookup_x12(unsigned long x1, unsigned long x2,
+                              unsigned long *y)
+{
+    /* For MARC8s we try to get a Latin-1 page code out of it */
+    int i;
+    for (i = 0; latin1_comb[i].x1; i++)
+        if (x2 == latin1_comb[i].x2 && x1 == latin1_comb[i].x1)
+        {
+            *y = latin1_comb[i].y;
+            return 1;
+        }
+    return 0;
+}
+
+static size_t write_iso_8859_1(yaz_iconv_t cd, yaz_iconv_encoder_t e,
+                               unsigned long x,
+                               char **outbuf, size_t *outbytesleft)
+{
+    struct encoder_data *w = e->data;
+    /* list of two char unicode sequence that, when combined, are
+       equivalent to single unicode chars that can be represented in
+       ISO-8859-1/Latin-1.
+       Regular iconv on Linux at least does not seem to convert these,
+       but since MARC-8 to UTF-8 generates these composed sequence
+       we get a better chance of a successful MARC-8 -> ISO-8859-1
+       conversion */
+    unsigned char *outp = (unsigned char *) *outbuf;
+
+    if (w->compose_char)
+    {
+        int i;
+        for (i = 0; latin1_comb[i].x1; i++)
+            if (w->compose_char == latin1_comb[i].x1 && x == latin1_comb[i].x2)
+            {
+                x = latin1_comb[i].y;
+                break;
+            }
+        if (*outbytesleft < 1)
+        {  /* no room. Retain compose_char and bail out */
+            yaz_iconv_set_errno(cd, YAZ_ICONV_E2BIG);
+            return (size_t)(-1);
+        }
+        if (!latin1_comb[i].x1) 
+        {   /* not found. Just write compose_char */
+            *outp++ = (unsigned char) w->compose_char;
+            (*outbytesleft)--;
+            *outbuf = (char *) outp;
+        }
+        /* compose_char used so reset it. x now holds current char */
+        w->compose_char = 0;
+    }
+
+    if (x > 32 && x < 127 && w->compose_char == 0)
+    {
+        w->compose_char = x;
+        return 0;
+    }
+    else if (x > 255 || x < 1)
+    {
+        yaz_iconv_set_errno(cd, YAZ_ICONV_EILSEQ);
+        return (size_t) -1;
+    }
+    else if (*outbytesleft < 1)
+    {
+        yaz_iconv_set_errno(cd, YAZ_ICONV_E2BIG);
+        return (size_t)(-1);
+    }
+    *outp++ = (unsigned char) x;
+    (*outbytesleft)--;
+    *outbuf = (char *) outp;
+    return 0;
+}
+
+static size_t flush_iso_8859_1(yaz_iconv_t cd, yaz_iconv_encoder_t e,
+                               char **outbuf, size_t *outbytesleft)
+{
+    struct encoder_data *w = e->data;
+    if (w->compose_char)
+    {
+        unsigned char *outp = (unsigned char *) *outbuf;
+        if (*outbytesleft < 1)
+        {
+            yaz_iconv_set_errno(cd, YAZ_ICONV_E2BIG);
+            return (size_t)(-1);
+        }
+        *outp++ = (unsigned char) w->compose_char;
+        (*outbytesleft)--;
+        *outbuf = (char *) outp;
+        w->compose_char = 0;
+    }
+    return 0;
+}
+
+
+void init_iso_8859_1(yaz_iconv_encoder_t e)
+{
+    struct encoder_data *w = e->data;
+    w->compose_char = 0;
+}
+
+void destroy_iso_8859_1(yaz_iconv_encoder_t e)
+{
+    xfree(e->data);
+}
+
+yaz_iconv_encoder_t yaz_iso_8859_1_encoder(const char *tocode,
+                                           yaz_iconv_encoder_t e)
+    
+{
+    if (!yaz_matchstr(tocode, "iso88591"))
+    {
+        struct encoder_data *data = xmalloc(sizeof(*data));
+        e->data = data;
+        e->write_handle = write_iso_8859_1;
+        e->flush_handle = flush_iso_8859_1;
+        e->init_handle = init_iso_8859_1;
+        e->destroy_handle = destroy_iso_8859_1;
+        return e;
+    }
+    return 0;
+}
+
+
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */