Конвертировать = C3 = B6 в ö при условии UTF-8 - PullRequest
0 голосов
/ 23 января 2019

Итак, я пытаюсь прочитать электронное письмо, оно закодировано в цитируемой печатной форме и поэтому содержит, например:

=C3=B6

Это должно быть преобразовано в

ö

Итак, я получаю, что c3b6 - это шестнадцатеричное представление значения utf-8 ö, но я не могу понять, как преобразовать char * str = '=C3=B6' в char * str 'ö'.

Я использую Linux, но перенесу код на Windows, поэтому для этого я хочу мультиплатформенное решение.

Как я могу это сделать?

Ответы [ 3 ]

0 голосов
/ 23 января 2019

Что-то, чтобы начать OP.

Разобрать строку "=C3=B6", ища 2 байта в виде шестнадцатеричных символов. Затем сформируйте строку для печати (и, надеюсь, printf будет интерпретироваться как UTF-8) - Соответствующие компиляторы с printf("%s", ...) имеют «Никаких специальных условий для многобайтовых символов». YMMV .

#include "stdio.h"
int main() {
  char * str = "=C3=B6";
  printf("%s\n", str);
  printf("1 %s\n", "ö");
  printf("2 %s\n", "\xC3\xB6");
  unsigned char a[3] = { 0 };
  if (sscanf("=c3=b6", "=%hhx=%hhx", &a[0], &a[1]) == 2) {
    printf("3 %s\n", a);
  }
  return 0;
}

выход

=C3=B6
1 ö
2 ö
3 ö
0 голосов
/ 24 января 2019

Декодирование цитируемая для печати строка включает в себя три вещи:

  • Игнорировать мягкие переводы строк. Это =, за которыми следует символ новой строки.

  • Преобразование =, за которым следуют две шестнадцатеричные цифры, в символ, код которого соответствует шестнадцатеричному значению

Существует три основных подхода к декодированию данных:

  1. Входной фильтр. Вместо, например fgetc(), вы используете функцию, которая читает и декодирует вводимый в кавычки ввод.

  2. Преобразование в новый буфер. См. convert() функцию в Крейг Эстейс ответ на этот же вопрос.

  3. Преобразование на месте. Это возможно, потому что каждая действительная строка в кавычках, пригодная для печати, имеет длину не менее той же самой декодированной.


Входной фильтр. Для простоты, давайте посмотрим на один символ за раз. (Обратите внимание, что многие символы UTF-8 длиннее, чем один символ.)

Во-первых, нам нужна вспомогательная функция для преобразования шестнадцатеричных цифровых символов в их соответствующие арифметические значения:

static inline int hex_digit(const int c)
{
    switch (c) {
    case '0':           return  0;
    case '1':           return  1;
    case '2':           return  2;
    case '3':           return  3;
    case '4':           return  4;
    case '5':           return  5;
    case '6':           return  6;
    case '7':           return  7;
    case '8':           return  8;
    case '9':           return  9;
    case 'A': case 'a': return 10;
    case 'B': case 'b': return 11;
    case 'C': case 'c': return 12;
    case 'D': case 'd': return 13;
    case 'E': case 'e': return 14;
    case 'F': case 'f': return 15;
    default:            return -1;
    }
}

В большинстве случаев вы также можете написать это как

static inline int hex_digit(const int c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    else
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    else
    if (c >= 'a' && c <= 'F')
        return c - 'a' + 10;
    else
        return -1;
}

или даже как

static signed char  hex_digit_value[UCHAR_MAX + 1];

static inline int hex_digit(const int c)
{
    return hex_digit_value[(unsigned char)c];
}

static inline void init_hex_digit_values(void)
{
    int  i;
    for (i = 0; i <= UCHAR_MAX; i++)
        hex_digit_value[i] = -1;

    hex_digit_value['0'] = 0;
    hex_digit_value['1'] = 1;
    hex_digit_value['2'] = 2;
    hex_digit_value['3'] = 3;
    hex_digit_value['4'] = 4;
    hex_digit_value['5'] = 5;
    hex_digit_value['6'] = 6;
    hex_digit_value['7'] = 7;
    hex_digit_value['8'] = 8;
    hex_digit_value['9'] = 9;
    hex_digit_value['A'] = hex_digit_value['a'] = 10;
    hex_digit_value['B'] = hex_digit_value['b'] = 11;
    hex_digit_value['C'] = hex_digit_value['c'] = 12;
    hex_digit_value['D'] = hex_digit_value['d'] = 13;
    hex_digit_value['E'] = hex_digit_value['e'] = 14;
    hex_digit_value['F'] = hex_digit_value['f'] = 15;
}

, где init_hex_digit_values() вызывается один раз в начале программы. Я предпочитаю первую форму, так как она самая переносимая, но вторая форма - это то, что вы обычно видите.

Третья форма, использующая массив hex_digit_value[], является примером преждевременной оптимизации. В некоторых случаях он может быть немного быстрее, чем другие (но различия определенно слишком малы, чтобы иметь значение на практике), но это может быть полезно, если нужно поддерживать сильно различающиеся однобайтовые наборы символов (например, EBDIC и ASCII) используя тот же код.

Во-первых, чтение декодированного символа из потока (файла или дескриптора), который содержит данные для печати в кавычках:

int get_quoted_printable_char(FILE *from)
{
    int  c, c2, hi, lo;

    /* Paranoid check. */
    if (!from || ferror(from) || feof(from))
        return EOF;

    while (1) {

        c = fgetc(from);
        if (c != '=')
            return c;

        /* Soft newline? */
        c = fgetc(from);
        if (c == '\n')
            continue;

        /* '=' at the end of input? */
        if (c == EOF)
            return EOF;

        hi = hex_digit(c);
        if (hi < 0) {
            /* Invalid input; emit '=' instead. */
            ungetc(c, from);
            return '=';
        }

        c2 = fgetc(from);
        if (c2 == EOF) {
            /* Invalid input; emit '=' <c> instead. */
            ungetc(c, from);
            return '=';
        }

        low = hex_digit(c2);
        if (lo < 0) {
            /* Invalid input; try to emit '=' <c> <c2> instead. */
            ungetc(c2, from);
            ungetc(c, from);
            return '=';
        }

        return low + 16 * high;
    }
}

Цикл существует в случае, если на входе имеется более одной последовательной мягкой новой строки. Такого не должно быть на самом деле, но если это произошло, мы хотим игнорировать их все.

Если вы хотите скопировать цитируемый поток для печати в файл, вам понадобится только вышеперечисленное и, например,

int save(FILE *source, const char *filename)
{
    FILE  *target;
    int    c;

    if (!source || ferror(source))
        return -1;  /* Invalid source handle */

    if (!filename || !*filename)
        return -2;  /* Invalid filename */

    target = fopen(filename, "w");
    if (!target)
        return -3;  /* Cannot open filename for writing */

    while (1) {
        c = get_quoted_printable_char(source);
        if (c == EOF)
            break;

        if (fputc(c, target) == EOF)
            break;
    }

    if (!feof(source) || ferror(source)) {
        fclose(target);
        remove(filename);
        return -4; /* Error reading source. */
    }
    if (fclose(source)) {
        fclose(target);
        remove(filename);
        return -4; /* Error closing source (delayed read error). */
    }

    if (ferror(target) || fflush(target)) {
        fclose(target);
        remove(filename);
        return -5; /* Write error */
    }
    if (fclose(target)) {
        remove(filename);
        return -5; /* Error closing target; delayed write error */
    }

    /* Success. */
    return 0;
}

, который особенно осторожен против ошибок чтения и записи. Он не слишком быстрый, потому что он использует библиотеку C для буферизации ввода, но он также не слишком медленный. Тот факт, что он не использует какие-либо явные буферы (полагаясь на стандартную библиотеку C, чтобы решить, как буферизовать исходный код и записываемый файл), в целом делает его вполне приемлемым.

Преобразование в новый буфер или на место довольно похоже:

size_t  decode_quoted_printable(char *dst, const char *src)
{
    const char *const origin = dst;

    /* Neither pointer may be NULL. src == dst is okay, however. */
    if (!dst || !src) {
        errno = EINVAL;
        return 0;
    }

    /* Copy loop. */
    while (*src)
        if (*src == '=') {
            if (src[1] == '\0') {
                /* '=' at the end of string. Skipped. */
                break;
            } else
            if (src[1] == '\n') {
                /* Soft newline. Skip both =\n and =\n\r newlines. */
                if (src[2] == '\r')
                    src += 3;
                else
                    src += 2;
            } else
            if (src[1] == '\r') {
                /* Soft newline. Skip both =\r and =\r\n newlines. */
                if (src[2] == '\n')
                    src += 3;
                else
                    src += 2;
            } else {
                const int  hi = hex_digit((unsigned char)(src[1]));
                const int  lo = hex_digit((unsigned char)(src[2]));
                if (hi >= 0 && lo >= 0) {
                    *(dst++) = lo + 16*hi;
                    src += 3;
                } else {
                    /* Error in input format. We are permissive,
                       and reproduce the erroneous `=XY` as-is. */
                    *(dst++) = *(src++);
                }
            }
        } else
        if (*src == '\n') {
            if (src[1] == '\r')
                src += 2;
            else
                src += 1;
            *(dst++) = '\n';
        } else
        if (*src == '\r') {
            if (src[1] == '\n')
                src += 2;
            else
                src += 1;
            *(dst++) = '\n';
        } else
           *(dst++) = *(src++);

    /* Terminate result to make it a string. */
    *dst = '\0';

    /* Just in case the source was an empty string, we clear
       errno to zero.  This also means we always set errno,
       which is a bit rare, but makes the use of this function
       easy: errno is nonzero iff there was an error. */
    errno = 0;
    return (size_t)(dst - origin);
}

Обратите внимание, что поскольку строковые литералы не могут быть изменены, вы не можете сделать char *data = "foo"; decode_quoted_printable(foo, foo);.

Однако вы можете сделать char data[] = "foo"; decode_quoted_printable(foo, foo);, потому что он объявляет массив символов, который просто инициализируется строкой "foo".

Обратите внимание, что вышеуказанная функция также выполняет автоматическое универсальное преобразование новой строки. Таким образом, он поддерживает все четыре соглашения о новой строке \r\n, \n\r, \r и \n и преобразует их все в стандартные символы новой строки C \n.

Целевой буфер должен быть по крайней мере таким же, как исходный буфер, и вы можете использовать тот же целевой буфер, что и исходный, если он изменчив (не является литеральной строкой или указывает на литеральную строку).

Отличие от get-one-decoded-symbol от потокового подхода состоит в том, что последний требует, чтобы весь контент находился в буфере в памяти. Является ли это плюсом или минусом, зависит от контекста.

0 голосов
/ 23 января 2019

Вот кое-что, с чего следует начать.

Я проверил это, и, похоже, оно работает для того, что вы дали.Он имеет некоторую проверку ошибок, но не много.

#include <stdio.h>

// hexnib -- convert ascii hex digit to binary value
int
hexnib(int chr)
{

    chr &= 0xFF;

    do {
        if ((chr >= '0') && (chr <= '9')) {
            chr -= '0';
            break;
        }

        if ((chr >= 'A') && (chr <= 'F')) {
            chr -= 'A';
            chr += 10;
            break;
        }

        // error ...
    } while (0);

    return chr;
}

void
convert(char *utf8,const char *quo)
{
    int chr;
    int acc;

    while (1) {
        chr = *quo++;
        if (chr == 0)
            break;

        // handle ordinary char (i.e. _not_ start of =XY)
        if (chr != '=') {
            *utf8++ = chr;
            continue;
        }

        // hex value accumulator
        acc = 0;

        // get X value
        chr = *quo++;
        if (chr == 0)
            break;

        // convert to binary
        chr = hexnib(chr);
        acc <<= 8;
        acc |= chr;

        // get Y value
        chr = *quo++;
        if (chr == 0)
            break;

        // convert to binary
        chr = hexnib(chr);
        acc <<= 8;
        acc |= chr;

        // store utf sequence
        *utf8++ = acc;
    }

    // store end of string
    *utf8 = 0;
}

int
main(int argc,char **argv)
{
    char *fname;
    FILE *fi;
    char ibuf[1000];
    char obuf[1000];

    --argc;
    ++argv;

    fname = *argv;
    if (fname != NULL)
        fi = fopen(fname,"r");
    else
        fi = stdin;

    while (1) {
        char *cp = fgets(ibuf,sizeof(ibuf),fi);
        if (cp == NULL)
            break;

        convert(obuf,ibuf);

        fputs(obuf,stdout);
    }

    if (fname != NULL)
        fclose(fi);

    return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...