1/* Java format strings. 2 Copyright (C) 2001-2004, 2006-2007 Free Software Foundation, Inc. 3 Written by Bruno Haible <haible@clisp.cons.org>, 2001. 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 17 18#ifdef HAVE_CONFIG_H 19# include <config.h> 20#endif 21#include <alloca.h> 22 23#include <stdbool.h> 24#include <stdlib.h> 25#include <string.h> 26 27#include "format.h" 28#include "c-ctype.h" 29#include "xalloc.h" 30#include "xmalloca.h" 31#include "xvasprintf.h" 32#include "format-invalid.h" 33#include "gettext.h" 34 35#define _(str) gettext (str) 36 37/* Java format strings are described in java/text/MessageFormat.html. 38 See also the ICU documentation class_MessageFormat.html. 39 40 messageFormatPattern := string ( "{" messageFormatElement "}" string )* 41 42 messageFormatElement := argument { "," elementFormat } 43 44 elementFormat := "time" { "," datetimeStyle } 45 | "date" { "," datetimeStyle } 46 | "number" { "," numberStyle } 47 | "choice" { "," choiceStyle } 48 49 datetimeStyle := "short" 50 | "medium" 51 | "long" 52 | "full" 53 | dateFormatPattern 54 55 numberStyle := "currency" 56 | "percent" 57 | "integer" 58 | numberFormatPattern 59 60 choiceStyle := choiceFormatPattern 61 62 dateFormatPattern see SimpleDateFormat.applyPattern 63 64 numberFormatPattern see DecimalFormat.applyPattern 65 66 choiceFormatPattern see ChoiceFormat constructor 67 68 In strings, literal curly braces can be used if quoted between single 69 quotes. A real single quote is represented by ''. 70 71 If a pattern is used, then unquoted braces in the pattern, if any, must 72 match: that is, "ab {0} de" and "ab '}' de" are ok, but "ab {0'}' de" and 73 "ab } de" are not. 74 75 The argument is a number from 0 to 9, which corresponds to the arguments 76 presented in an array to be formatted. 77 78 It is ok to have unused arguments in the array. 79 80 Adding a dateFormatPattern / numberFormatPattern / choiceFormatPattern 81 to an elementFormat is equivalent to creating a SimpleDateFormat / 82 DecimalFormat / ChoiceFormat and use of setFormat. For example, 83 84 MessageFormat form = 85 new MessageFormat("The disk \"{1}\" contains {0,choice,0#no files|1#one file|2#{0,number} files}."); 86 87 is equivalent to 88 89 MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}."); 90 form.setFormat(1, // Number of {} occurrence in the string! 91 new ChoiceFormat(new double[] { 0, 1, 2 }, 92 new String[] { "no files", "one file", 93 "{0,number} files" })); 94 95 Note: The behaviour of quotes inside a choiceFormatPattern is not clear. 96 Example 1: 97 "abc{1,choice,0#{1,number,00';'000}}def" 98 JDK 1.1.x: exception 99 JDK 1.3.x: behaves like "abc{1,choice,0#{1,number,00;000}}def" 100 Example 2: 101 "abc{1,choice,0#{1,number,00';'}}def" 102 JDK 1.1.x: interprets the semicolon as number suffix 103 JDK 1.3.x: behaves like "abc{1,choice,0#{1,number,00;}}def" 104 */ 105 106enum format_arg_type 107{ 108 FAT_NONE, 109 FAT_OBJECT, /* java.lang.Object */ 110 FAT_NUMBER, /* java.lang.Number */ 111 FAT_DATE /* java.util.Date */ 112}; 113 114struct numbered_arg 115{ 116 unsigned int number; 117 enum format_arg_type type; 118}; 119 120struct spec 121{ 122 unsigned int directives; 123 unsigned int numbered_arg_count; 124 unsigned int allocated; 125 struct numbered_arg *numbered; 126}; 127 128 129/* Forward declaration of local functions. */ 130static bool date_format_parse (const char *format); 131static bool number_format_parse (const char *format); 132static bool choice_format_parse (const char *format, struct spec *spec, 133 char **invalid_reason); 134 135 136/* Quote handling: 137 - When we see a single-quote, ignore it, but toggle the quoting flag. 138 - When we see a double single-quote, ignore the first of the two. 139 Assumes local variables format, quoting. */ 140#define HANDLE_QUOTE \ 141 if (*format == '\'' && *++format != '\'') \ 142 quoting = !quoting; 143 144/* Note that message_format_parse and choice_format_parse are mutually 145 recursive. This is because MessageFormat can use some ChoiceFormats, 146 and a ChoiceFormat is made up from several MessageFormats. */ 147 148/* Return true if a format is a valid messageFormatPattern. 149 Extracts argument type information into spec. */ 150static bool 151message_format_parse (const char *format, char *fdi, struct spec *spec, 152 char **invalid_reason) 153{ 154 const char *const format_start = format; 155 bool quoting = false; 156 157 for (;;) 158 { 159 HANDLE_QUOTE; 160 if (!quoting && *format == '{') 161 { 162 unsigned int depth; 163 const char *element_start; 164 const char *element_end; 165 size_t n; 166 char *element_alloced; 167 char *element; 168 unsigned int number; 169 enum format_arg_type type; 170 171 FDI_SET (format, FMTDIR_START); 172 spec->directives++; 173 174 element_start = ++format; 175 depth = 0; 176 for (; *format != '\0'; format++) 177 { 178 if (*format == '{') 179 depth++; 180 else if (*format == '}') 181 { 182 if (depth == 0) 183 break; 184 else 185 depth--; 186 } 187 } 188 if (*format == '\0') 189 { 190 *invalid_reason = 191 xstrdup (_("The string ends in the middle of a directive: found '{' without matching '}'.")); 192 FDI_SET (format - 1, FMTDIR_ERROR); 193 return false; 194 } 195 element_end = format++; 196 197 n = element_end - element_start; 198 element = element_alloced = (char *) xmalloca (n + 1); 199 memcpy (element, element_start, n); 200 element[n] = '\0'; 201 202 if (!c_isdigit (*element)) 203 { 204 *invalid_reason = 205 xasprintf (_("In the directive number %u, '{' is not followed by an argument number."), spec->directives); 206 FDI_SET (format - 1, FMTDIR_ERROR); 207 freea (element_alloced); 208 return false; 209 } 210 number = 0; 211 do 212 { 213 number = 10 * number + (*element - '0'); 214 element++; 215 } 216 while (c_isdigit (*element)); 217 218 type = FAT_OBJECT; 219 if (*element == '\0') 220 ; 221 else if (strncmp (element, ",time", 5) == 0 222 || strncmp (element, ",date", 5) == 0) 223 { 224 type = FAT_DATE; 225 element += 5; 226 if (*element == '\0') 227 ; 228 else if (*element == ',') 229 { 230 element++; 231 if (strcmp (element, "short") == 0 232 || strcmp (element, "medium") == 0 233 || strcmp (element, "long") == 0 234 || strcmp (element, "full") == 0 235 || date_format_parse (element)) 236 ; 237 else 238 { 239 *invalid_reason = 240 xasprintf (_("In the directive number %u, the substring \"%s\" is not a valid date/time style."), spec->directives, element); 241 FDI_SET (format - 1, FMTDIR_ERROR); 242 freea (element_alloced); 243 return false; 244 } 245 } 246 else 247 { 248 *element = '\0'; 249 element -= 4; 250 *invalid_reason = 251 xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element); 252 FDI_SET (format - 1, FMTDIR_ERROR); 253 freea (element_alloced); 254 return false; 255 } 256 } 257 else if (strncmp (element, ",number", 7) == 0) 258 { 259 type = FAT_NUMBER; 260 element += 7; 261 if (*element == '\0') 262 ; 263 else if (*element == ',') 264 { 265 element++; 266 if (strcmp (element, "currency") == 0 267 || strcmp (element, "percent") == 0 268 || strcmp (element, "integer") == 0 269 || number_format_parse (element)) 270 ; 271 else 272 { 273 *invalid_reason = 274 xasprintf (_("In the directive number %u, the substring \"%s\" is not a valid number style."), spec->directives, element); 275 FDI_SET (format - 1, FMTDIR_ERROR); 276 freea (element_alloced); 277 return false; 278 } 279 } 280 else 281 { 282 *element = '\0'; 283 element -= 6; 284 *invalid_reason = 285 xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element); 286 FDI_SET (format - 1, FMTDIR_ERROR); 287 freea (element_alloced); 288 return false; 289 } 290 } 291 else if (strncmp (element, ",choice", 7) == 0) 292 { 293 type = FAT_NUMBER; /* because ChoiceFormat extends NumberFormat */ 294 element += 7; 295 if (*element == '\0') 296 ; 297 else if (*element == ',') 298 { 299 element++; 300 if (choice_format_parse (element, spec, invalid_reason)) 301 ; 302 else 303 { 304 FDI_SET (format - 1, FMTDIR_ERROR); 305 freea (element_alloced); 306 return false; 307 } 308 } 309 else 310 { 311 *element = '\0'; 312 element -= 6; 313 *invalid_reason = 314 xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element); 315 FDI_SET (format - 1, FMTDIR_ERROR); 316 freea (element_alloced); 317 return false; 318 } 319 } 320 else 321 { 322 *invalid_reason = 323 xasprintf (_("In the directive number %u, the argument number is not followed by a comma and one of \"%s\", \"%s\", \"%s\", \"%s\"."), spec->directives, "time", "date", "number", "choice"); 324 FDI_SET (format - 1, FMTDIR_ERROR); 325 freea (element_alloced); 326 return false; 327 } 328 freea (element_alloced); 329 330 if (spec->allocated == spec->numbered_arg_count) 331 { 332 spec->allocated = 2 * spec->allocated + 1; 333 spec->numbered = (struct numbered_arg *) xrealloc (spec->numbered, spec->allocated * sizeof (struct numbered_arg)); 334 } 335 spec->numbered[spec->numbered_arg_count].number = number; 336 spec->numbered[spec->numbered_arg_count].type = type; 337 spec->numbered_arg_count++; 338 339 FDI_SET (format - 1, FMTDIR_END); 340 } 341 /* The doc says "ab}de" is invalid. Even though JDK accepts it. */ 342 else if (!quoting && *format == '}') 343 { 344 FDI_SET (format, FMTDIR_START); 345 *invalid_reason = 346 xstrdup (_("The string starts in the middle of a directive: found '}' without matching '{'.")); 347 FDI_SET (format, FMTDIR_ERROR); 348 return false; 349 } 350 else if (*format != '\0') 351 format++; 352 else 353 break; 354 } 355 356 return true; 357} 358 359/* Return true if a format is a valid dateFormatPattern. */ 360static bool 361date_format_parse (const char *format) 362{ 363 /* Any string is valid. Single-quote starts a quoted section, to be 364 terminated at the next single-quote or string end. Double single-quote 365 gives a single single-quote. Non-quoted ASCII letters are first grouped 366 into blocks of equal letters. Then each block (e.g. 'yyyy') is 367 interpreted according to some rules. */ 368 return true; 369} 370 371/* Return true if a format is a valid numberFormatPattern. */ 372static bool 373number_format_parse (const char *format) 374{ 375 /* Pattern Syntax: 376 pattern := pos_pattern{';' neg_pattern} 377 pos_pattern := {prefix}number{suffix} 378 neg_pattern := {prefix}number{suffix} 379 number := integer{'.' fraction}{exponent} 380 prefix := '\u0000'..'\uFFFD' - special_characters 381 suffix := '\u0000'..'\uFFFD' - special_characters 382 integer := min_int | '#' | '#' integer | '#' ',' integer 383 min_int := '0' | '0' min_int | '0' ',' min_int 384 fraction := '0'* '#'* 385 exponent := 'E' '0' '0'* 386 Notation: 387 X* 0 or more instances of X 388 { X } 0 or 1 instances of X 389 X | Y either X or Y 390 X..Y any character from X up to Y, inclusive 391 S - T characters in S, except those in T 392 Single-quote starts a quoted section, to be terminated at the next 393 single-quote or string end. Double single-quote gives a single 394 single-quote. 395 */ 396 bool quoting = false; 397 bool seen_semicolon = false; 398 399 HANDLE_QUOTE; 400 for (;;) 401 { 402 /* Parse prefix. */ 403 while (*format != '\0' 404 && !(!quoting && (*format == '0' || *format == '#'))) 405 { 406 if (format[0] == '\\') 407 { 408 if (format[1] == 'u' 409 && c_isxdigit (format[2]) 410 && c_isxdigit (format[3]) 411 && c_isxdigit (format[4]) 412 && c_isxdigit (format[5])) 413 format += 6; 414 else 415 format += 2; 416 } 417 else 418 format += 1; 419 HANDLE_QUOTE; 420 } 421 422 /* Parse integer. */ 423 if (!(!quoting && (*format == '0' || *format == '#'))) 424 return false; 425 while (!quoting && *format == '#') 426 { 427 format++; 428 HANDLE_QUOTE; 429 if (!quoting && *format == ',') 430 { 431 format++; 432 HANDLE_QUOTE; 433 } 434 } 435 while (!quoting && *format == '0') 436 { 437 format++; 438 HANDLE_QUOTE; 439 if (!quoting && *format == ',') 440 { 441 format++; 442 HANDLE_QUOTE; 443 } 444 } 445 446 /* Parse fraction. */ 447 if (!quoting && *format == '.') 448 { 449 format++; 450 HANDLE_QUOTE; 451 while (!quoting && *format == '0') 452 { 453 format++; 454 HANDLE_QUOTE; 455 } 456 while (!quoting && *format == '#') 457 { 458 format++; 459 HANDLE_QUOTE; 460 } 461 } 462 463 /* Parse exponent. */ 464 if (!quoting && *format == 'E') 465 { 466 const char *format_save = format; 467 format++; 468 HANDLE_QUOTE; 469 if (!quoting && *format == '0') 470 { 471 do 472 { 473 format++; 474 HANDLE_QUOTE; 475 } 476 while (!quoting && *format == '0'); 477 } 478 else 479 { 480 /* Back up. */ 481 format = format_save; 482 quoting = false; 483 } 484 } 485 486 /* Parse suffix. */ 487 while (*format != '\0' 488 && (seen_semicolon || !(!quoting && *format == ';'))) 489 { 490 if (format[0] == '\\') 491 { 492 if (format[1] == 'u' 493 && c_isxdigit (format[2]) 494 && c_isxdigit (format[3]) 495 && c_isxdigit (format[4]) 496 && c_isxdigit (format[5])) 497 format += 6; 498 else 499 format += 2; 500 } 501 else 502 format += 1; 503 HANDLE_QUOTE; 504 } 505 506 if (seen_semicolon || !(!quoting && *format == ';')) 507 break; 508 } 509 510 return (*format == '\0'); 511} 512 513/* Return true if a format is a valid choiceFormatPattern. 514 Extracts argument type information into spec. */ 515static bool 516choice_format_parse (const char *format, struct spec *spec, 517 char **invalid_reason) 518{ 519 /* Pattern syntax: 520 pattern := | choice | choice '|' pattern 521 choice := number separator messageformat 522 separator := '<' | '#' | '\u2264' 523 Single-quote starts a quoted section, to be terminated at the next 524 single-quote or string end. Double single-quote gives a single 525 single-quote. 526 */ 527 bool quoting = false; 528 529 HANDLE_QUOTE; 530 if (*format == '\0') 531 return true; 532 for (;;) 533 { 534 /* Don't bother looking too precisely into the syntax of the number. 535 It can contain various Unicode characters. */ 536 bool number_nonempty; 537 char *msgformat; 538 char *mp; 539 bool msgformat_valid; 540 541 /* Parse number. */ 542 number_nonempty = false; 543 while (*format != '\0' 544 && !(!quoting && (*format == '<' || *format == '#' 545 || strncmp (format, "\\u2264", 6) == 0 546 || *format == '|'))) 547 { 548 if (format[0] == '\\') 549 { 550 if (format[1] == 'u' 551 && c_isxdigit (format[2]) 552 && c_isxdigit (format[3]) 553 && c_isxdigit (format[4]) 554 && c_isxdigit (format[5])) 555 format += 6; 556 else 557 format += 2; 558 } 559 else 560 format += 1; 561 number_nonempty = true; 562 HANDLE_QUOTE; 563 } 564 565 /* Short clause at end of pattern is valid and is ignored! */ 566 if (*format == '\0') 567 break; 568 569 if (!number_nonempty) 570 { 571 *invalid_reason = 572 xasprintf (_("In the directive number %u, a choice contains no number."), spec->directives); 573 return false; 574 } 575 576 if (*format == '<' || *format == '#') 577 format += 1; 578 else if (strncmp (format, "\\u2264", 6) == 0) 579 format += 6; 580 else 581 { 582 *invalid_reason = 583 xasprintf (_("In the directive number %u, a choice contains a number that is not followed by '<', '#' or '%s'."), spec->directives, "\\u2264"); 584 return false; 585 } 586 HANDLE_QUOTE; 587 588 msgformat = (char *) xmalloca (strlen (format) + 1); 589 mp = msgformat; 590 591 while (*format != '\0' && !(!quoting && *format == '|')) 592 { 593 *mp++ = *format++; 594 HANDLE_QUOTE; 595 } 596 *mp = '\0'; 597 598 msgformat_valid = 599 message_format_parse (msgformat, NULL, spec, invalid_reason); 600 601 freea (msgformat); 602 603 if (!msgformat_valid) 604 return false; 605 606 if (*format == '\0') 607 break; 608 609 format++; 610 HANDLE_QUOTE; 611 } 612 613 return true; 614} 615 616static int 617numbered_arg_compare (const void *p1, const void *p2) 618{ 619 unsigned int n1 = ((const struct numbered_arg *) p1)->number; 620 unsigned int n2 = ((const struct numbered_arg *) p2)->number; 621 622 return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); 623} 624 625static void * 626format_parse (const char *format, bool translated, char *fdi, 627 char **invalid_reason) 628{ 629 struct spec spec; 630 struct spec *result; 631 632 spec.directives = 0; 633 spec.numbered_arg_count = 0; 634 spec.allocated = 0; 635 spec.numbered = NULL; 636 637 if (!message_format_parse (format, fdi, &spec, invalid_reason)) 638 goto bad_format; 639 640 /* Sort the numbered argument array, and eliminate duplicates. */ 641 if (spec.numbered_arg_count > 1) 642 { 643 unsigned int i, j; 644 bool err; 645 646 qsort (spec.numbered, spec.numbered_arg_count, 647 sizeof (struct numbered_arg), numbered_arg_compare); 648 649 /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ 650 err = false; 651 for (i = j = 0; i < spec.numbered_arg_count; i++) 652 if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) 653 { 654 enum format_arg_type type1 = spec.numbered[i].type; 655 enum format_arg_type type2 = spec.numbered[j-1].type; 656 enum format_arg_type type_both; 657 658 if (type1 == type2 || type2 == FAT_OBJECT) 659 type_both = type1; 660 else if (type1 == FAT_OBJECT) 661 type_both = type2; 662 else 663 { 664 /* Incompatible types. */ 665 type_both = FAT_NONE; 666 if (!err) 667 *invalid_reason = 668 INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); 669 err = true; 670 } 671 672 spec.numbered[j-1].type = type_both; 673 } 674 else 675 { 676 if (j < i) 677 { 678 spec.numbered[j].number = spec.numbered[i].number; 679 spec.numbered[j].type = spec.numbered[i].type; 680 } 681 j++; 682 } 683 spec.numbered_arg_count = j; 684 if (err) 685 /* *invalid_reason has already been set above. */ 686 goto bad_format; 687 } 688 689 result = XMALLOC (struct spec); 690 *result = spec; 691 return result; 692 693 bad_format: 694 if (spec.numbered != NULL) 695 free (spec.numbered); 696 return NULL; 697} 698 699static void 700format_free (void *descr) 701{ 702 struct spec *spec = (struct spec *) descr; 703 704 if (spec->numbered != NULL) 705 free (spec->numbered); 706 free (spec); 707} 708 709static int 710format_get_number_of_directives (void *descr) 711{ 712 struct spec *spec = (struct spec *) descr; 713 714 return spec->directives; 715} 716 717static bool 718format_check (void *msgid_descr, void *msgstr_descr, bool equality, 719 formatstring_error_logger_t error_logger, 720 const char *pretty_msgstr) 721{ 722 struct spec *spec1 = (struct spec *) msgid_descr; 723 struct spec *spec2 = (struct spec *) msgstr_descr; 724 bool err = false; 725 726 if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) 727 { 728 unsigned int i, j; 729 unsigned int n1 = spec1->numbered_arg_count; 730 unsigned int n2 = spec2->numbered_arg_count; 731 732 /* Check the argument names are the same. 733 Both arrays are sorted. We search for the first difference. */ 734 for (i = 0, j = 0; i < n1 || j < n2; ) 735 { 736 int cmp = (i >= n1 ? 1 : 737 j >= n2 ? -1 : 738 spec1->numbered[i].number > spec2->numbered[j].number ? 1 : 739 spec1->numbered[i].number < spec2->numbered[j].number ? -1 : 740 0); 741 742 if (cmp > 0) 743 { 744 if (error_logger) 745 error_logger (_("a format specification for argument {%u}, as in '%s', doesn't exist in 'msgid'"), 746 spec2->numbered[j].number, pretty_msgstr); 747 err = true; 748 break; 749 } 750 else if (cmp < 0) 751 { 752 if (equality) 753 { 754 if (error_logger) 755 error_logger (_("a format specification for argument {%u} doesn't exist in '%s'"), 756 spec1->numbered[i].number, pretty_msgstr); 757 err = true; 758 break; 759 } 760 else 761 i++; 762 } 763 else 764 j++, i++; 765 } 766 /* Check the argument types are the same. */ 767 if (!err) 768 for (i = 0, j = 0; j < n2; ) 769 { 770 if (spec1->numbered[i].number == spec2->numbered[j].number) 771 { 772 if (spec1->numbered[i].type != spec2->numbered[j].type) 773 { 774 if (error_logger) 775 error_logger (_("format specifications in 'msgid' and '%s' for argument {%u} are not the same"), 776 pretty_msgstr, spec2->numbered[j].number); 777 err = true; 778 break; 779 } 780 j++, i++; 781 } 782 else 783 i++; 784 } 785 } 786 787 return err; 788} 789 790 791struct formatstring_parser formatstring_java = 792{ 793 format_parse, 794 format_free, 795 format_get_number_of_directives, 796 NULL, 797 format_check 798}; 799 800 801#ifdef TEST 802 803/* Test program: Print the argument list specification returned by 804 format_parse for strings read from standard input. */ 805 806#include <stdio.h> 807 808static void 809format_print (void *descr) 810{ 811 struct spec *spec = (struct spec *) descr; 812 unsigned int last; 813 unsigned int i; 814 815 if (spec == NULL) 816 { 817 printf ("INVALID"); 818 return; 819 } 820 821 printf ("("); 822 last = 0; 823 for (i = 0; i < spec->numbered_arg_count; i++) 824 { 825 unsigned int number = spec->numbered[i].number; 826 827 if (i > 0) 828 printf (" "); 829 if (number < last) 830 abort (); 831 for (; last < number; last++) 832 printf ("_ "); 833 switch (spec->numbered[i].type) 834 { 835 case FAT_OBJECT: 836 printf ("*"); 837 break; 838 case FAT_NUMBER: 839 printf ("Number"); 840 break; 841 case FAT_DATE: 842 printf ("Date"); 843 break; 844 default: 845 abort (); 846 } 847 last = number + 1; 848 } 849 printf (")"); 850} 851 852int 853main () 854{ 855 for (;;) 856 { 857 char *line = NULL; 858 size_t line_size = 0; 859 int line_len; 860 char *invalid_reason; 861 void *descr; 862 863 line_len = getline (&line, &line_size, stdin); 864 if (line_len < 0) 865 break; 866 if (line_len > 0 && line[line_len - 1] == '\n') 867 line[--line_len] = '\0'; 868 869 invalid_reason = NULL; 870 descr = format_parse (line, false, NULL, &invalid_reason); 871 872 format_print (descr); 873 printf ("\n"); 874 if (descr == NULL) 875 printf ("%s\n", invalid_reason); 876 877 free (invalid_reason); 878 free (line); 879 } 880 881 return 0; 882} 883 884/* 885 * For Emacs M-x compile 886 * Local Variables: 887 * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../intl -DHAVE_CONFIG_H -DTEST format-java.c ../gnulib-lib/libgettextlib.la" 888 * End: 889 */ 890 891#endif /* TEST */ 892