]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
utf8: add utf8_to_utf16() helper 10173/head
authorLennart Poettering <lennart@poettering.net>
Mon, 25 Jun 2018 17:17:42 +0000 (19:17 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 25 Sep 2018 13:57:47 +0000 (15:57 +0200)
src/basic/utf8.c
src/basic/utf8.h
src/test/test-utf8.c

index 7dc84c1157ec06829764fefee20ae5e24818f6b2..e0d1949dc7f7527533eee527d113b22d2b70886f 100644 (file)
@@ -366,6 +366,79 @@ char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */) {
         return r;
 }
 
+size_t utf16_encode_unichar(char16_t *out, char32_t c) {
+
+        /* Note that this encodes as little-endian. */
+
+        switch (c) {
+
+        case 0 ... 0xd7ffU:
+        case 0xe000U ... 0xffffU:
+                out[0] = htole16(c);
+                return 1;
+
+        case 0x10000U ... 0x10ffffU:
+                c -= 0x10000U;
+                out[0] = htole16((c >> 10) + 0xd800U);
+                out[1] = htole16((c & 0x3ffU) + 0xdc00U);
+                return 2;
+
+        default: /* A surrogate (invalid) */
+                return 0;
+        }
+}
+
+char16_t *utf8_to_utf16(const char *s, size_t length) {
+        char16_t *n, *p;
+        size_t i;
+        int r;
+
+        assert(s);
+
+        n = new(char16_t, length + 1);
+        if (!n)
+                return NULL;
+
+        p = n;
+
+        for (i = 0; i < length;) {
+                char32_t unichar;
+                size_t e;
+
+                e = utf8_encoded_expected_len(s + i);
+                if (e <= 1) /* Invalid and single byte characters are copied as they are */
+                        goto copy;
+
+                if (i + e > length) /* sequence longer than input buffer, then copy as-is */
+                        goto copy;
+
+                r = utf8_encoded_to_unichar(s + i, &unichar);
+                if (r < 0) /* sequence invalid, then copy as-is */
+                        goto copy;
+
+                p += utf16_encode_unichar(p, unichar);
+                i += e;
+                continue;
+
+        copy:
+                *(p++) = htole16(s[i++]);
+        }
+
+        *p = 0;
+        return n;
+}
+
+size_t char16_strlen(const char16_t *s) {
+        size_t n = 0;
+
+        assert(s);
+
+        while (*s != 0)
+                n++, s++;
+
+        return n;
+}
+
 /* expected size used to encode one unicode char */
 static int utf8_unichar_to_encoded_len(char32_t unichar) {
 
index 13c48b0978cbe6947e0763f67d6f06626f84a473..69a816e125f8d11de0b2f500c69272ebfa9330cc 100644 (file)
@@ -25,7 +25,12 @@ char *utf8_escape_invalid(const char *s);
 char *utf8_escape_non_printable(const char *str);
 
 size_t utf8_encode_unichar(char *out_utf8, char32_t g);
+size_t utf16_encode_unichar(char16_t *out, char32_t c);
+
 char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */);
+char16_t *utf8_to_utf16(const char *s, size_t length);
+
+size_t char16_strlen(const char16_t *s);
 
 int utf8_encoded_valid_unichar(const char *str);
 int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar);
index d35daf53dca326548df5b1bf0aad4f4dba8ae165..9849530ac88c33c0ac03d177da9114224a2d51fc 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "alloc-util.h"
 #include "string-util.h"
+#include "strv.h"
 #include "utf8.h"
 #include "util.h"
 
@@ -87,15 +88,25 @@ static void test_utf8_escaping_printable(void) {
 }
 
 static void test_utf16_to_utf8(void) {
-        char *a = NULL;
-        const uint16_t utf16[] = { htole16('a'), htole16(0xd800), htole16('b'), htole16(0xdc00), htole16('c'), htole16(0xd801), htole16(0xdc37) };
-        const char utf8[] = { 'a', 'b', 'c', 0xf0, 0x90, 0x90, 0xb7, 0 };
+        const char16_t utf16[] = { htole16('a'), htole16(0xd800), htole16('b'), htole16(0xdc00), htole16('c'), htole16(0xd801), htole16(0xdc37) };
+        static const char utf8[] = { 'a', 'b', 'c', 0xf0, 0x90, 0x90, 0xb7 };
+        _cleanup_free_ char16_t *b = NULL;
+        _cleanup_free_ char *a = NULL;
 
-        a = utf16_to_utf8(utf16, 14);
+        /* Convert UTF-16 to UTF-8, filtering embedded bad chars */
+        a = utf16_to_utf8(utf16, sizeof(utf16));
         assert_se(a);
-        assert_se(streq(a, utf8));
+        assert_se(memcmp(a, utf8, sizeof(utf8)) == 0);
+
+        /* Convert UTF-8 to UTF-16, and back */
+        b = utf8_to_utf16(utf8, sizeof(utf8));
+        assert_se(b);
 
         free(a);
+        a = utf16_to_utf8(b, char16_strlen(b) * 2);
+        assert_se(a);
+        assert_se(strlen(a) == sizeof(utf8));
+        assert_se(memcmp(a, utf8, sizeof(utf8)) == 0);
 }
 
 static void test_utf8_n_codepoints(void) {
@@ -116,6 +127,28 @@ static void test_utf8_console_width(void) {
         assert_se(utf8_console_width("\xF1") == (size_t) -1);
 }
 
+static void test_utf8_to_utf16(void) {
+        const char *p;
+
+        FOREACH_STRING(p,
+                       "abc",
+                       "zażółcić gęślą jaźń",
+                       "串",
+                       "",
+                       "…👊🔪💐…") {
+
+                _cleanup_free_ char16_t *a = NULL;
+                _cleanup_free_ char *b = NULL;
+
+                a = utf8_to_utf16(p, strlen(p));
+                assert_se(a);
+
+                b = utf16_to_utf8(a, char16_strlen(a) * 2);
+                assert_se(b);
+                assert_se(streq(p, b));
+        }
+}
+
 int main(int argc, char *argv[]) {
         test_utf8_is_valid();
         test_utf8_is_printable();
@@ -127,6 +160,7 @@ int main(int argc, char *argv[]) {
         test_utf16_to_utf8();
         test_utf8_n_codepoints();
         test_utf8_console_width();
+        test_utf8_to_utf16();
 
         return 0;
 }