aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2010-01-04 20:49:58 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2010-01-04 20:49:58 +0100
commit28055028a709020ba7eb44f9e5037d0a952b51d6 (patch)
treebb5dc7052f04e66ad74bbbcc9917d638cd603b24
parentd2b1ba6fdee59645763e915ade3ec55e67d5839a (diff)
downloadbusybox-w32-28055028a709020ba7eb44f9e5037d0a952b51d6.tar.gz
busybox-w32-28055028a709020ba7eb44f9e5037d0a952b51d6.tar.bz2
busybox-w32-28055028a709020ba7eb44f9e5037d0a952b51d6.zip
fold: unicode support. Based on a patch by Tomas Heinrich <heinrich.tomas@gmail.com>
General Unicode support is tweaked to expose unicode_status. function old new delta init_unicode - 77 +77 write2stdout - 19 +19 adjust_column 68 71 +3 unicode_status - 1 +1 unicode_is_enabled 1 - -1 grep_main 780 773 -7 fold_main 619 552 -67 check_unicode_in_env 77 - -77 ------------------------------------------------------------------------------ (add/remove: 3/2 grow/shrink: 1/2 up/down: 100/-152) Total: -52 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--coreutils/df.c2
-rw-r--r--coreutils/expand.c2
-rw-r--r--coreutils/fold.c161
-rw-r--r--coreutils/ls.c2
-rw-r--r--include/unicode.h18
-rw-r--r--libbb/lineedit.c2
-rw-r--r--libbb/progress.c2
-rw-r--r--libbb/unicode.c46
-rw-r--r--modutils/lsmod.c2
-rw-r--r--networking/udhcp/dumpleases.c2
-rwxr-xr-xtestsuite/fold.tests49
11 files changed, 183 insertions, 105 deletions
diff --git a/coreutils/df.c b/coreutils/df.c
index 83794ad88..bcde78393 100644
--- a/coreutils/df.c
+++ b/coreutils/df.c
@@ -58,7 +58,7 @@ int df_main(int argc UNUSED_PARAM, char **argv)
58 const char *disp_units_hdr = NULL; 58 const char *disp_units_hdr = NULL;
59 char *chp; 59 char *chp;
60 60
61 check_unicode_in_env(); 61 init_unicode();
62 62
63#if ENABLE_FEATURE_HUMAN_READABLE && ENABLE_FEATURE_DF_FANCY 63#if ENABLE_FEATURE_HUMAN_READABLE && ENABLE_FEATURE_DF_FANCY
64 opt_complementary = "k-mB:m-Bk:B-km"; 64 opt_complementary = "k-mB:m-Bk:B-km";
diff --git a/coreutils/expand.c b/coreutils/expand.c
index e08eb1d0a..649b4c175 100644
--- a/coreutils/expand.c
+++ b/coreutils/expand.c
@@ -144,7 +144,7 @@ int expand_main(int argc UNUSED_PARAM, char **argv)
144 "all\0" No_argument "a" 144 "all\0" No_argument "a"
145 ; 145 ;
146#endif 146#endif
147 check_unicode_in_env(); 147 init_unicode();
148 148
149 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) { 149 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
150 IF_FEATURE_EXPAND_LONG_OPTIONS(applet_long_options = expand_longopts); 150 IF_FEATURE_EXPAND_LONG_OPTIONS(applet_long_options = expand_longopts);
diff --git a/coreutils/fold.c b/coreutils/fold.c
index 56a346680..cbea31fad 100644
--- a/coreutils/fold.c
+++ b/coreutils/fold.c
@@ -9,8 +9,8 @@
9 9
10 Licensed under the GPL v2 or later, see the file LICENSE in this tarball. 10 Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
11*/ 11*/
12
13#include "libbb.h" 12#include "libbb.h"
13#include "unicode.h"
14 14
15/* Must match getopt32 call */ 15/* Must match getopt32 call */
16#define FLAG_COUNT_BYTES 1 16#define FLAG_COUNT_BYTES 1
@@ -20,39 +20,53 @@
20/* Assuming the current column is COLUMN, return the column that 20/* Assuming the current column is COLUMN, return the column that
21 printing C will move the cursor to. 21 printing C will move the cursor to.
22 The first column is 0. */ 22 The first column is 0. */
23static int adjust_column(int column, char c) 23static int adjust_column(unsigned column, char c)
24{ 24{
25 if (!(option_mask32 & FLAG_COUNT_BYTES)) { 25 if (option_mask32 & FLAG_COUNT_BYTES)
26 if (c == '\b') { 26 return ++column;
27 if (column > 0) 27
28 column--; 28 if (c == '\t')
29 } else if (c == '\r') 29 return column + 8 - column % 8;
30
31 if (c == '\b') {
32 if ((int)--column < 0)
30 column = 0; 33 column = 0;
31 else if (c == '\t') 34 }
32 column = column + 8 - column % 8; 35 else if (c == '\r')
33 else /* if (isprint(c)) */ 36 column = 0;
37 else { /* just a printable char */
38 if (unicode_status != UNICODE_ON /* every byte is a new char */
39 || (c & 0xc0) != 0x80 /* it isn't a 2nd+ byte of a Unicode char */
40 ) {
34 column++; 41 column++;
35 } else 42 }
36 column++; 43 }
37 return column; 44 return column;
38} 45}
39 46
47/* Note that this function can write NULs, unlike fputs etc. */
48static void write2stdout(const void *buf, unsigned size)
49{
50 fwrite(buf, 1, size, stdout);
51}
52
40int fold_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 53int fold_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
41int fold_main(int argc UNUSED_PARAM, char **argv) 54int fold_main(int argc UNUSED_PARAM, char **argv)
42{ 55{
43 char *line_out = NULL; 56 char *line_out = NULL;
44 int allocated_out = 0; 57 const char *w_opt = "80";
45 char *w_opt; 58 unsigned width;
46 int width = 80; 59 smallint exitcode = EXIT_SUCCESS;
47 int i; 60
48 int errs = 0; 61 init_unicode();
49 62
50 if (ENABLE_INCLUDE_SUSv2) { 63 if (ENABLE_INCLUDE_SUSv2) {
51 /* Turn any numeric options into -w options. */ 64 /* Turn any numeric options into -w options. */
65 int i;
52 for (i = 1; argv[i]; i++) { 66 for (i = 1; argv[i]; i++) {
53 char const *a = argv[i]; 67 const char *a = argv[i];
54 68 if (*a == '-') {
55 if (*a++ == '-') { 69 a++;
56 if (*a == '-' && !a[1]) /* "--" */ 70 if (*a == '-' && !a[1]) /* "--" */
57 break; 71 break;
58 if (isdigit(*a)) 72 if (isdigit(*a))
@@ -62,8 +76,7 @@ int fold_main(int argc UNUSED_PARAM, char **argv)
62 } 76 }
63 77
64 getopt32(argv, "bsw:", &w_opt); 78 getopt32(argv, "bsw:", &w_opt);
65 if (option_mask32 & FLAG_WIDTH) 79 width = xatou_range(w_opt, 1, 10000);
66 width = xatoul_range(w_opt, 1, 10000);
67 80
68 argv += optind; 81 argv += optind;
69 if (!*argv) 82 if (!*argv)
@@ -72,79 +85,81 @@ int fold_main(int argc UNUSED_PARAM, char **argv)
72 do { 85 do {
73 FILE *istream = fopen_or_warn_stdin(*argv); 86 FILE *istream = fopen_or_warn_stdin(*argv);
74 int c; 87 int c;
75 int column = 0; /* Screen column where next char will go. */ 88 unsigned column = 0; /* Screen column where next char will go */
76 int offset_out = 0; /* Index in 'line_out' for next char. */ 89 unsigned offset_out = 0; /* Index in 'line_out' for next char */
77 90
78 if (istream == NULL) { 91 if (istream == NULL) {
79 errs |= EXIT_FAILURE; 92 exitcode = EXIT_FAILURE;
80 continue; 93 continue;
81 } 94 }
82 95
83 while ((c = getc(istream)) != EOF) { 96 while ((c = getc(istream)) != EOF) {
84 if (offset_out + 1 >= allocated_out) { 97 /* We grow line_out in chunks of 0x1000 bytes */
85 allocated_out += 1024; 98 if ((offset_out & 0xfff) == 0) {
86 line_out = xrealloc(line_out, allocated_out); 99 line_out = xrealloc(line_out, offset_out + 0x1000);
87 } 100 }
88 101 rescan:
102 line_out[offset_out] = c;
89 if (c == '\n') { 103 if (c == '\n') {
90 line_out[offset_out++] = c; 104 write2stdout(line_out, offset_out + 1);
91 fwrite(line_out, sizeof(char), (size_t) offset_out, stdout);
92 column = offset_out = 0; 105 column = offset_out = 0;
93 continue; 106 continue;
94 } 107 }
95 rescan:
96 column = adjust_column(column, c); 108 column = adjust_column(column, c);
109 if (column <= width || offset_out == 0) {
110 /* offset_out == 0 case happens
111 * with small width (say, 1) and tabs.
112 * The very first tab already goes to column 8,
113 * but we must not wrap it */
114 offset_out++;
115 continue;
116 }
97 117
98 if (column > width) { 118 /* This character would make the line too long.
99 /* This character would make the line too long. 119 * Print the line plus a newline, and make this character
100 Print the line plus a newline, and make this character 120 * start the next line */
101 start the next line. */ 121 if (option_mask32 & FLAG_BREAK_SPACES) {
102 if (option_mask32 & FLAG_BREAK_SPACES) { 122 unsigned i;
103 /* Look for the last blank. */ 123 unsigned logical_end;
104 int logical_end; 124
105 125 /* Look for the last blank. */
106 for (logical_end = offset_out - 1; logical_end >= 0; logical_end--) { 126 for (logical_end = offset_out - 1; (int)logical_end >= 0; logical_end--) {
107 if (isblank(line_out[logical_end])) { 127 if (!isblank(line_out[logical_end]))
108 break; 128 continue;
109 } 129
130 /* Found a space or tab.
131 * Output up to and including it, and start a new line */
132 logical_end++;
133 /*line_out[logical_end] = '\n'; - NO! this nukes one buffered character */
134 write2stdout(line_out, logical_end);
135 putchar('\n');
136 /* Move the remainder to the beginning of the next line.
137 * The areas being copied here might overlap. */
138 memmove(line_out, line_out + logical_end, offset_out - logical_end);
139 offset_out -= logical_end;
140 for (column = i = 0; i < offset_out; i++) {
141 column = adjust_column(column, line_out[i]);
110 } 142 }
111 if (logical_end >= 0) { 143 goto rescan;
112 /* Found a blank. Don't output the part after it. */
113 logical_end++;
114 fwrite(line_out, sizeof(char), (size_t) logical_end, stdout);
115 bb_putchar('\n');
116 /* Move the remainder to the beginning of the next line.
117 The areas being copied here might overlap. */
118 memmove(line_out, line_out + logical_end, offset_out - logical_end);
119 offset_out -= logical_end;
120 for (column = i = 0; i < offset_out; i++) {
121 column = adjust_column(column, line_out[i]);
122 }
123 goto rescan;
124 }
125 }
126 if (offset_out == 0) {
127 line_out[offset_out++] = c;
128 continue;
129 } 144 }
130 line_out[offset_out++] = '\n'; 145 /* No blank found, wrap will split the overlong word */
131 fwrite(line_out, sizeof(char), (size_t) offset_out, stdout);
132 column = offset_out = 0;
133 goto rescan;
134 } 146 }
135 147 /* Output what we accumulated up to now, and start a new line */
136 line_out[offset_out++] = c; 148 line_out[offset_out] = '\n';
137 } 149 write2stdout(line_out, offset_out + 1);
150 column = offset_out = 0;
151 goto rescan;
152 } /* while (not EOF) */
138 153
139 if (offset_out) { 154 if (offset_out) {
140 fwrite(line_out, sizeof(char), (size_t) offset_out, stdout); 155 write2stdout(line_out, offset_out);
141 } 156 }
142 157
143 if (fclose_if_not_stdin(istream)) { 158 if (fclose_if_not_stdin(istream)) {
144 bb_simple_perror_msg(*argv); /* Avoid multibyte problems. */ 159 bb_simple_perror_msg(*argv);
145 errs |= EXIT_FAILURE; 160 exitcode = EXIT_FAILURE;
146 } 161 }
147 } while (*++argv); 162 } while (*++argv);
148 163
149 fflush_stdout_and_exit(errs); 164 fflush_stdout_and_exit(exitcode);
150} 165}
diff --git a/coreutils/ls.c b/coreutils/ls.c
index 13c863c6e..5ea3a0b27 100644
--- a/coreutils/ls.c
+++ b/coreutils/ls.c
@@ -945,7 +945,7 @@ int ls_main(int argc UNUSED_PARAM, char **argv)
945 945
946 INIT_G(); 946 INIT_G();
947 947
948 check_unicode_in_env(); 948 init_unicode();
949 949
950 all_fmt = LIST_SHORT | 950 all_fmt = LIST_SHORT |
951 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD)); 951 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
diff --git a/include/unicode.h b/include/unicode.h
index e0061478d..9f27657df 100644
--- a/include/unicode.h
+++ b/include/unicode.h
@@ -5,10 +5,17 @@
5#ifndef UNICODE_H 5#ifndef UNICODE_H
6#define UNICODE_H 1 6#define UNICODE_H 1
7 7
8enum {
9 UNICODE_UNKNOWN = 0,
10 UNICODE_OFF = 1,
11 UNICODE_ON = 2,
12};
13
8#if !ENABLE_FEATURE_ASSUME_UNICODE 14#if !ENABLE_FEATURE_ASSUME_UNICODE
9 15
10# define bb_mbstrlen(string) strlen(string) 16# define bb_mbstrlen(string) strlen(string)
11# define check_unicode_in_env() ((void)0) 17# define unicode_status UNICODE_OFF
18# define init_unicode() ((void)0)
12 19
13#else 20#else
14 21
@@ -18,16 +25,19 @@ size_t bb_mbstrlen(const char *string) FAST_FUNC;
18 25
19# include <wchar.h> 26# include <wchar.h>
20# include <wctype.h> 27# include <wctype.h>
21# define check_unicode_in_env() ((void)0) 28extern uint8_t unicode_status;
29void init_unicode(void) FAST_FUNC;
22 30
23# else 31# else
24 32
25/* Crude "locale support" which knows only C and Unicode locales */ 33/* Crude "locale support" which knows only C and Unicode locales */
26 34
27# if !ENABLE_FEATURE_CHECK_UNICODE_IN_ENV 35# if !ENABLE_FEATURE_CHECK_UNICODE_IN_ENV
28# define check_unicode_in_env() ((void)0) 36# define unicode_status UNICODE_ON
37# define init_unicode() ((void)0)
29# else 38# else
30void check_unicode_in_env(void) FAST_FUNC; 39extern uint8_t unicode_status;
40void init_unicode(void) FAST_FUNC;
31# endif 41# endif
32 42
33# undef MB_CUR_MAX 43# undef MB_CUR_MAX
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index c73e6b712..b73f1d6da 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -1763,7 +1763,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
1763 return len; 1763 return len;
1764 } 1764 }
1765 1765
1766 check_unicode_in_env(); 1766 init_unicode();
1767 1767
1768// FIXME: audit & improve this 1768// FIXME: audit & improve this
1769 if (maxsize > MAX_LINELEN) 1769 if (maxsize > MAX_LINELEN)
diff --git a/libbb/progress.c b/libbb/progress.c
index f6f26922c..3a245ae6c 100644
--- a/libbb/progress.c
+++ b/libbb/progress.c
@@ -78,7 +78,7 @@ void FAST_FUNC bb_progress_update(bb_progress_t *p,
78 } 78 }
79 79
80#if ENABLE_FEATURE_ASSUME_UNICODE 80#if ENABLE_FEATURE_ASSUME_UNICODE
81 check_unicode_in_env(); 81 init_unicode();
82 /* libbb candidate? */ 82 /* libbb candidate? */
83 { 83 {
84 wchar_t wbuf21[21]; 84 wchar_t wbuf21[21];
diff --git a/libbb/unicode.c b/libbb/unicode.c
index 544528acd..9d316df04 100644
--- a/libbb/unicode.c
+++ b/libbb/unicode.c
@@ -7,7 +7,9 @@
7 * Licensed under GPL version 2, see file LICENSE in this tarball for details. 7 * Licensed under GPL version 2, see file LICENSE in this tarball for details.
8 */ 8 */
9#include "libbb.h" 9#include "libbb.h"
10# include "unicode.h" 10#include "unicode.h"
11
12uint8_t unicode_status;
11 13
12size_t FAST_FUNC bb_mbstrlen(const char *string) 14size_t FAST_FUNC bb_mbstrlen(const char *string)
13{ 15{
@@ -17,32 +19,38 @@ size_t FAST_FUNC bb_mbstrlen(const char *string)
17 return width; 19 return width;
18} 20}
19 21
20#if !ENABLE_LOCALE_SUPPORT 22#if ENABLE_LOCALE_SUPPORT
23
24/* Unicode support using libc */
25
26void FAST_FUNC init_unicode(void)
27{
28 /* In unicode, this is a one character string */
29 static const char unicode_0x394[] = { 0xce, 0x94, 0 };
30
31 if (unicode_status != UNICODE_UNKNOWN)
32 return;
33
34 unicode_status = bb_mbstrlen(unicode_0x394) == 1 ? UNICODE_ON : UNICODE_OFF;
35}
36
37#else
21 38
22/* Crude "locale support" which knows only C and Unicode locales */ 39/* Crude "locale support" which knows only C and Unicode locales */
23 40
24/* unicode_is_enabled: 41# if ENABLE_FEATURE_CHECK_UNICODE_IN_ENV
25 * 0: not known yet, 42void FAST_FUNC init_unicode(void)
26 * 1: not unicode (IOW: assuming one char == one byte)
27 * 2: unicode
28 */
29# if !ENABLE_FEATURE_CHECK_UNICODE_IN_ENV
30# define unicode_is_enabled 2
31# else
32static smallint unicode_is_enabled;
33void FAST_FUNC check_unicode_in_env(void)
34{ 43{
35 char *lang; 44 char *lang;
36 45
37 if (unicode_is_enabled) 46 if (unicode_status != UNICODE_UNKNOWN)
38 return; 47 return;
39 unicode_is_enabled = 1;
40 48
49 unicode_status = UNICODE_OFF;
41 lang = getenv("LANG"); 50 lang = getenv("LANG");
42 if (!lang || !(strstr(lang, ".utf") || strstr(lang, ".UTF"))) 51 if (!lang || !(strstr(lang, ".utf") || strstr(lang, ".UTF")))
43 return; 52 return;
44 53 unicode_status = UNICODE_ON;
45 unicode_is_enabled = 2;
46} 54}
47# endif 55# endif
48 56
@@ -85,7 +93,7 @@ static size_t wcrtomb_internal(char *s, wchar_t wc)
85 93
86size_t FAST_FUNC wcrtomb(char *s, wchar_t wc, mbstate_t *ps UNUSED_PARAM) 94size_t FAST_FUNC wcrtomb(char *s, wchar_t wc, mbstate_t *ps UNUSED_PARAM)
87{ 95{
88 if (unicode_is_enabled != 2) { 96 if (unicode_status != UNICODE_ON) {
89 *s = wc; 97 *s = wc;
90 return 1; 98 return 1;
91 } 99 }
@@ -97,7 +105,7 @@ size_t FAST_FUNC wcstombs(char *dest, const wchar_t *src, size_t n)
97{ 105{
98 size_t org_n = n; 106 size_t org_n = n;
99 107
100 if (unicode_is_enabled != 2) { 108 if (unicode_status != UNICODE_ON) {
101 while (n) { 109 while (n) {
102 wchar_t c = *src++; 110 wchar_t c = *src++;
103 *dest++ = c; 111 *dest++ = c;
@@ -137,7 +145,7 @@ size_t FAST_FUNC mbstowcs(wchar_t *dest, const char *src, size_t n)
137{ 145{
138 size_t org_n = n; 146 size_t org_n = n;
139 147
140 if (unicode_is_enabled != 2) { 148 if (unicode_status != UNICODE_ON) {
141 while (n) { 149 while (n) {
142 unsigned char c = *src++; 150 unsigned char c = *src++;
143 151
diff --git a/modutils/lsmod.c b/modutils/lsmod.c
index 582503b66..20d9cf1a8 100644
--- a/modutils/lsmod.c
+++ b/modutils/lsmod.c
@@ -49,7 +49,7 @@ int lsmod_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
49# if ENABLE_FEATURE_ASSUME_UNICODE 49# if ENABLE_FEATURE_ASSUME_UNICODE
50 size_t name_len; 50 size_t name_len;
51# endif 51# endif
52 check_unicode_in_env(); 52 init_unicode();
53 53
54 printf("%-24sSize Used by", "Module"); 54 printf("%-24sSize Used by", "Module");
55 check_tainted(); 55 check_tainted();
diff --git a/networking/udhcp/dumpleases.c b/networking/udhcp/dumpleases.c
index 017c801e6..97605f089 100644
--- a/networking/udhcp/dumpleases.c
+++ b/networking/udhcp/dumpleases.c
@@ -43,7 +43,7 @@ int dumpleases_main(int argc UNUSED_PARAM, char **argv)
43 43
44 applet_long_options = dumpleases_longopts; 44 applet_long_options = dumpleases_longopts;
45#endif 45#endif
46 check_unicode_in_env(); 46 init_unicode();
47 47
48 opt_complementary = "=0:a--r:r--a"; 48 opt_complementary = "=0:a--r:r--a";
49 opt = getopt32(argv, "arf:", &file); 49 opt = getopt32(argv, "arf:", &file);
diff --git a/testsuite/fold.tests b/testsuite/fold.tests
index ca0e511c1..17721a180 100755
--- a/testsuite/fold.tests
+++ b/testsuite/fold.tests
@@ -7,8 +7,53 @@
7# testing "test name" "options" "expected result" "file input" "stdin" 7# testing "test name" "options" "expected result" "file input" "stdin"
8 8
9testing "fold -s" "fold -w 7 -s" \ 9testing "fold -s" "fold -w 7 -s" \
10 "123456\n\t\nasdf" \ 10 "123456\n\t\nasdf" \
11 "" \
12 "123456\tasdf" \
13
14testing "fold -w1" "fold -w1" \
15 "q\nq\n \nw\n \ne\ne\ne\n \nr\n \nt\nt\nt\nt\n \ny" \
16 "" \
17 "qq w eee r tttt y" \
18
19testing "fold with NULs" "fold -sw22" \
20 "\
21The NUL is here:>\0< \n\
22and another one is \n\
23here:>\0< - they must \n\
24be preserved
25" \
26 "" \
27 "The NUL is here:>\0< and another one \
28is here:>\0< - they must be preserved
29" \
30
31# The text was taken from English and Ukrainian wikipedia pages
32testing "fold -sw66 with unicode input" "fold -sw66" \
33 "\
34The Andromeda Galaxy (pronounced /ænˈdrɒmədə/, also known as \n\
35Messier 31, M31, or NGC224; often referred to as the Great \n\
36Andromeda Nebula in older texts) is a spiral galaxy approximately \n\
372,500,000 light-years (1.58×10^11 AU) away in the constellation \n\
38Andromeda. It is the nearest spiral galaxy to our own, the Milky \n\
39Way.\n\
40Галактика або Туманність Андромеди (також відома як M31 за \n\
41каталогом Мессьє та NGC224 за Новим загальним каталогом) — \n\
42спіральна галактика, що знаходиться на відстані приблизно у 2,5 \n\
43мільйони світлових років від нашої планети у сузір'ї Андромеди. \n\
44На початку ХХІ ст. в центрі галактики виявлено чорну дірку." \
11 "" \ 45 "" \
12 "123456\tasdf" \ 46 "\
47The Andromeda Galaxy (pronounced /ænˈdrɒmədə/, also known as \
48Messier 31, M31, or NGC224; often referred to as the Great \
49Andromeda Nebula in older texts) is a spiral galaxy approximately \
502,500,000 light-years (1.58×10^11 AU) away in the constellation \
51Andromeda. It is the nearest spiral galaxy to our own, the Milky \
52Way.
53Галактика або Туманність Андромеди (також відома як M31 за \
54каталогом Мессьє та NGC224 за Новим загальним каталогом) — \
55спіральна галактика, що знаходиться на відстані приблизно у 2,5 \
56мільйони світлових років від нашої планети у сузір'ї Андромеди. \
57На початку ХХІ ст. в центрі галактики виявлено чорну дірку." \
13 58
14exit $FAILCOUNT 59exit $FAILCOUNT