From 482840e61f86ca321838a91e902c41d40c098bbb Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Wed, 24 Dec 2014 07:38:37 +0000 Subject: Imported from /home/lorry/working-area/delta_gettext-tarball/gettext-0.19.4.tar.xz. --- gettext-tools/src/ChangeLog | 7833 +++++++++++++++++++++++ gettext-tools/src/ChangeLog.0 | 5247 +++++++++++++++ gettext-tools/src/FILES | 363 ++ gettext-tools/src/Makefile.am | 621 ++ gettext-tools/src/Makefile.in | 3511 ++++++++++ gettext-tools/src/color.c | 445 ++ gettext-tools/src/color.h | 57 + gettext-tools/src/dir-list.c | 85 + gettext-tools/src/dir-list.h | 51 + gettext-tools/src/file-list.c | 92 + gettext-tools/src/file-list.h | 40 + gettext-tools/src/filter-quote.c | 224 + gettext-tools/src/filter-sr-latin.c | 400 ++ gettext-tools/src/filters.h | 51 + gettext-tools/src/format-awk.c | 663 ++ gettext-tools/src/format-boost.c | 772 +++ gettext-tools/src/format-c-parse.h | 854 +++ gettext-tools/src/format-c.c | 376 ++ gettext-tools/src/format-csharp.c | 293 + gettext-tools/src/format-elisp.c | 502 ++ gettext-tools/src/format-gcc-internal.c | 852 +++ gettext-tools/src/format-gfc-internal.c | 504 ++ gettext-tools/src/format-invalid.h | 40 + gettext-tools/src/format-java.c | 893 +++ gettext-tools/src/format-javascript.c | 333 + gettext-tools/src/format-kde.c | 349 + gettext-tools/src/format-librep.c | 463 ++ gettext-tools/src/format-lisp.c | 3658 +++++++++++ gettext-tools/src/format-lua.c | 348 + gettext-tools/src/format-pascal.c | 548 ++ gettext-tools/src/format-perl-brace.c | 294 + gettext-tools/src/format-perl.c | 768 +++ gettext-tools/src/format-php.c | 501 ++ gettext-tools/src/format-python-brace.c | 542 ++ gettext-tools/src/format-python.c | 695 ++ gettext-tools/src/format-qt-plural.c | 194 + gettext-tools/src/format-qt.c | 266 + gettext-tools/src/format-scheme.c | 3581 +++++++++++ gettext-tools/src/format-sh.c | 403 ++ gettext-tools/src/format-tcl.c | 550 ++ gettext-tools/src/format-ycp.c | 250 + gettext-tools/src/format.c | 197 + gettext-tools/src/format.h | 175 + gettext-tools/src/gnu/gettext/DumpResource.java | 236 + gettext-tools/src/gnu/gettext/GetURL.java | 81 + gettext-tools/src/hostname.c | 389 ++ gettext-tools/src/lang-table.c | 315 + gettext-tools/src/lang-table.h | 35 + gettext-tools/src/libexpat-compat.c | 326 + gettext-tools/src/libexpat-compat.h | 94 + gettext-tools/src/message.c | 914 +++ gettext-tools/src/message.h | 370 ++ gettext-tools/src/msgattrib.c | 683 ++ gettext-tools/src/msgcat.c | 485 ++ gettext-tools/src/msgcmp.c | 555 ++ gettext-tools/src/msgcomm.c | 471 ++ gettext-tools/src/msgconv.c | 401 ++ gettext-tools/src/msgen.c | 397 ++ gettext-tools/src/msgexec.c | 468 ++ gettext-tools/src/msgfilter.c | 753 +++ gettext-tools/src/msgfmt.c | 1473 +++++ gettext-tools/src/msgfmt.cs | 119 + gettext-tools/src/msgfmt.h | 27 + gettext-tools/src/msggrep.c | 860 +++ gettext-tools/src/msginit.c | 1776 +++++ gettext-tools/src/msgl-ascii.c | 112 + gettext-tools/src/msgl-ascii.h | 48 + gettext-tools/src/msgl-cat.c | 799 +++ gettext-tools/src/msgl-cat.h | 60 + gettext-tools/src/msgl-charset.c | 133 + gettext-tools/src/msgl-charset.h | 38 + gettext-tools/src/msgl-check.c | 914 +++ gettext-tools/src/msgl-check.h | 68 + gettext-tools/src/msgl-english.c | 70 + gettext-tools/src/msgl-english.h | 38 + gettext-tools/src/msgl-equal.c | 250 + gettext-tools/src/msgl-equal.h | 56 + gettext-tools/src/msgl-fsearch.c | 668 ++ gettext-tools/src/msgl-fsearch.h | 69 + gettext-tools/src/msgl-header.c | 170 + gettext-tools/src/msgl-header.h | 43 + gettext-tools/src/msgl-iconv.c | 597 ++ gettext-tools/src/msgl-iconv.h | 87 + gettext-tools/src/msgmerge.c | 2061 ++++++ gettext-tools/src/msgunfmt.c | 555 ++ gettext-tools/src/msgunfmt.cs | 241 + gettext-tools/src/msgunfmt.h | 24 + gettext-tools/src/msgunfmt.tcl | 82 + gettext-tools/src/msguniq.c | 434 ++ gettext-tools/src/open-catalog.c | 128 + gettext-tools/src/open-catalog.h | 43 + gettext-tools/src/plural-count.c | 38 + gettext-tools/src/plural-count.h | 29 + gettext-tools/src/plural-distrib.h | 57 + gettext-tools/src/plural-eval.c | 93 + gettext-tools/src/plural-eval.h | 67 + gettext-tools/src/plural-exp.c | 21 + gettext-tools/src/plural-table.c | 67 + gettext-tools/src/plural-table.h | 33 + gettext-tools/src/po-charset.c | 662 ++ gettext-tools/src/po-charset.h | 93 + gettext-tools/src/po-error.c | 42 + gettext-tools/src/po-error.h | 76 + gettext-tools/src/po-gram-gen.c | 1922 ++++++ gettext-tools/src/po-gram-gen.h | 106 + gettext-tools/src/po-gram-gen.y | 450 ++ gettext-tools/src/po-gram-gen2.h | 106 + gettext-tools/src/po-gram.h | 32 + gettext-tools/src/po-lex.c | 1151 ++++ gettext-tools/src/po-lex.h | 103 + gettext-tools/src/po-time.c | 75 + gettext-tools/src/po-time.h | 38 + gettext-tools/src/po-xerror.c | 199 + gettext-tools/src/po-xerror.h | 82 + gettext-tools/src/pos.h | 32 + gettext-tools/src/project-id | 86 + gettext-tools/src/read-catalog-abstract.c | 748 +++ gettext-tools/src/read-catalog-abstract.h | 195 + gettext-tools/src/read-catalog.c | 494 ++ gettext-tools/src/read-catalog.h | 192 + gettext-tools/src/read-csharp.c | 168 + gettext-tools/src/read-csharp.h | 30 + gettext-tools/src/read-desktop.c | 645 ++ gettext-tools/src/read-desktop.h | 121 + gettext-tools/src/read-java.c | 138 + gettext-tools/src/read-java.h | 29 + gettext-tools/src/read-mo.c | 467 ++ gettext-tools/src/read-mo.h | 26 + gettext-tools/src/read-po.c | 48 + gettext-tools/src/read-po.h | 26 + gettext-tools/src/read-properties.c | 560 ++ gettext-tools/src/read-properties.h | 26 + gettext-tools/src/read-resources.c | 138 + gettext-tools/src/read-resources.h | 27 + gettext-tools/src/read-stringtable.c | 963 +++ gettext-tools/src/read-stringtable.h | 26 + gettext-tools/src/read-tcl.c | 157 + gettext-tools/src/read-tcl.h | 28 + gettext-tools/src/recode-sr-latin.c | 395 ++ gettext-tools/src/str-list.c | 236 + gettext-tools/src/str-list.h | 88 + gettext-tools/src/urlget.c | 448 ++ gettext-tools/src/user-email.sh.in | 435 ++ gettext-tools/src/write-catalog.c | 469 ++ gettext-tools/src/write-catalog.h | 91 + gettext-tools/src/write-csharp.c | 783 +++ gettext-tools/src/write-csharp.h | 35 + gettext-tools/src/write-desktop.c | 225 + gettext-tools/src/write-desktop.h | 51 + gettext-tools/src/write-java.c | 1234 ++++ gettext-tools/src/write-java.h | 39 + gettext-tools/src/write-mo.c | 817 +++ gettext-tools/src/write-mo.h | 43 + gettext-tools/src/write-po.c | 1620 +++++ gettext-tools/src/write-po.h | 87 + gettext-tools/src/write-properties.c | 305 + gettext-tools/src/write-properties.h | 26 + gettext-tools/src/write-qt.c | 754 +++ gettext-tools/src/write-qt.h | 30 + gettext-tools/src/write-resources.c | 194 + gettext-tools/src/write-resources.h | 31 + gettext-tools/src/write-stringtable.c | 335 + gettext-tools/src/write-stringtable.h | 26 + gettext-tools/src/write-tcl.c | 229 + gettext-tools/src/write-tcl.h | 32 + gettext-tools/src/x-awk.c | 887 +++ gettext-tools/src/x-awk.h | 51 + gettext-tools/src/x-c.c | 2174 +++++++ gettext-tools/src/x-c.h | 92 + gettext-tools/src/x-csharp.c | 2147 +++++++ gettext-tools/src/x-csharp.h | 50 + gettext-tools/src/x-desktop.c | 193 + gettext-tools/src/x-desktop.h | 47 + gettext-tools/src/x-elisp.c | 1255 ++++ gettext-tools/src/x-elisp.h | 54 + gettext-tools/src/x-glade.c | 612 ++ gettext-tools/src/x-glade.h | 53 + gettext-tools/src/x-gsettings.c | 386 ++ gettext-tools/src/x-gsettings.h | 45 + gettext-tools/src/x-java.c | 1497 +++++ gettext-tools/src/x-java.h | 50 + gettext-tools/src/x-javascript.c | 1673 +++++ gettext-tools/src/x-javascript.h | 52 + gettext-tools/src/x-librep.c | 1133 ++++ gettext-tools/src/x-librep.h | 54 + gettext-tools/src/x-lisp.c | 1425 +++++ gettext-tools/src/x-lisp.h | 54 + gettext-tools/src/x-lua.c | 1215 ++++ gettext-tools/src/x-lua.h | 48 + gettext-tools/src/x-perl.c | 3592 +++++++++++ gettext-tools/src/x-perl.h | 55 + gettext-tools/src/x-php.c | 1605 +++++ gettext-tools/src/x-php.h | 53 + gettext-tools/src/x-po.c | 240 + gettext-tools/src/x-po.h | 46 + gettext-tools/src/x-properties.h | 45 + gettext-tools/src/x-python.c | 1779 +++++ gettext-tools/src/x-python.h | 51 + gettext-tools/src/x-rst.c | 236 + gettext-tools/src/x-rst.h | 46 + gettext-tools/src/x-scheme.c | 1339 ++++ gettext-tools/src/x-scheme.h | 54 + gettext-tools/src/x-sh.c | 1360 ++++ gettext-tools/src/x-sh.h | 52 + gettext-tools/src/x-smalltalk.c | 598 ++ gettext-tools/src/x-smalltalk.h | 46 + gettext-tools/src/x-stringtable.h | 45 + gettext-tools/src/x-tcl.c | 999 +++ gettext-tools/src/x-tcl.h | 54 + gettext-tools/src/x-vala.c | 1250 ++++ gettext-tools/src/x-vala.h | 50 + gettext-tools/src/x-ycp.c | 788 +++ gettext-tools/src/x-ycp.h | 48 + gettext-tools/src/xgettext.c | 3701 +++++++++++ gettext-tools/src/xgettext.h | 416 ++ 215 files changed, 111665 insertions(+) create mode 100644 gettext-tools/src/ChangeLog create mode 100644 gettext-tools/src/ChangeLog.0 create mode 100644 gettext-tools/src/FILES create mode 100644 gettext-tools/src/Makefile.am create mode 100644 gettext-tools/src/Makefile.in create mode 100644 gettext-tools/src/color.c create mode 100644 gettext-tools/src/color.h create mode 100644 gettext-tools/src/dir-list.c create mode 100644 gettext-tools/src/dir-list.h create mode 100644 gettext-tools/src/file-list.c create mode 100644 gettext-tools/src/file-list.h create mode 100644 gettext-tools/src/filter-quote.c create mode 100644 gettext-tools/src/filter-sr-latin.c create mode 100644 gettext-tools/src/filters.h create mode 100644 gettext-tools/src/format-awk.c create mode 100644 gettext-tools/src/format-boost.c create mode 100644 gettext-tools/src/format-c-parse.h create mode 100644 gettext-tools/src/format-c.c create mode 100644 gettext-tools/src/format-csharp.c create mode 100644 gettext-tools/src/format-elisp.c create mode 100644 gettext-tools/src/format-gcc-internal.c create mode 100644 gettext-tools/src/format-gfc-internal.c create mode 100644 gettext-tools/src/format-invalid.h create mode 100644 gettext-tools/src/format-java.c create mode 100644 gettext-tools/src/format-javascript.c create mode 100644 gettext-tools/src/format-kde.c create mode 100644 gettext-tools/src/format-librep.c create mode 100644 gettext-tools/src/format-lisp.c create mode 100644 gettext-tools/src/format-lua.c create mode 100644 gettext-tools/src/format-pascal.c create mode 100644 gettext-tools/src/format-perl-brace.c create mode 100644 gettext-tools/src/format-perl.c create mode 100644 gettext-tools/src/format-php.c create mode 100644 gettext-tools/src/format-python-brace.c create mode 100644 gettext-tools/src/format-python.c create mode 100644 gettext-tools/src/format-qt-plural.c create mode 100644 gettext-tools/src/format-qt.c create mode 100644 gettext-tools/src/format-scheme.c create mode 100644 gettext-tools/src/format-sh.c create mode 100644 gettext-tools/src/format-tcl.c create mode 100644 gettext-tools/src/format-ycp.c create mode 100644 gettext-tools/src/format.c create mode 100644 gettext-tools/src/format.h create mode 100644 gettext-tools/src/gnu/gettext/DumpResource.java create mode 100644 gettext-tools/src/gnu/gettext/GetURL.java create mode 100644 gettext-tools/src/hostname.c create mode 100644 gettext-tools/src/lang-table.c create mode 100644 gettext-tools/src/lang-table.h create mode 100644 gettext-tools/src/libexpat-compat.c create mode 100644 gettext-tools/src/libexpat-compat.h create mode 100644 gettext-tools/src/message.c create mode 100644 gettext-tools/src/message.h create mode 100644 gettext-tools/src/msgattrib.c create mode 100644 gettext-tools/src/msgcat.c create mode 100644 gettext-tools/src/msgcmp.c create mode 100644 gettext-tools/src/msgcomm.c create mode 100644 gettext-tools/src/msgconv.c create mode 100644 gettext-tools/src/msgen.c create mode 100644 gettext-tools/src/msgexec.c create mode 100644 gettext-tools/src/msgfilter.c create mode 100644 gettext-tools/src/msgfmt.c create mode 100644 gettext-tools/src/msgfmt.cs create mode 100644 gettext-tools/src/msgfmt.h create mode 100644 gettext-tools/src/msggrep.c create mode 100644 gettext-tools/src/msginit.c create mode 100644 gettext-tools/src/msgl-ascii.c create mode 100644 gettext-tools/src/msgl-ascii.h create mode 100644 gettext-tools/src/msgl-cat.c create mode 100644 gettext-tools/src/msgl-cat.h create mode 100644 gettext-tools/src/msgl-charset.c create mode 100644 gettext-tools/src/msgl-charset.h create mode 100644 gettext-tools/src/msgl-check.c create mode 100644 gettext-tools/src/msgl-check.h create mode 100644 gettext-tools/src/msgl-english.c create mode 100644 gettext-tools/src/msgl-english.h create mode 100644 gettext-tools/src/msgl-equal.c create mode 100644 gettext-tools/src/msgl-equal.h create mode 100644 gettext-tools/src/msgl-fsearch.c create mode 100644 gettext-tools/src/msgl-fsearch.h create mode 100644 gettext-tools/src/msgl-header.c create mode 100644 gettext-tools/src/msgl-header.h create mode 100644 gettext-tools/src/msgl-iconv.c create mode 100644 gettext-tools/src/msgl-iconv.h create mode 100644 gettext-tools/src/msgmerge.c create mode 100644 gettext-tools/src/msgunfmt.c create mode 100644 gettext-tools/src/msgunfmt.cs create mode 100644 gettext-tools/src/msgunfmt.h create mode 100644 gettext-tools/src/msgunfmt.tcl create mode 100644 gettext-tools/src/msguniq.c create mode 100644 gettext-tools/src/open-catalog.c create mode 100644 gettext-tools/src/open-catalog.h create mode 100644 gettext-tools/src/plural-count.c create mode 100644 gettext-tools/src/plural-count.h create mode 100644 gettext-tools/src/plural-distrib.h create mode 100644 gettext-tools/src/plural-eval.c create mode 100644 gettext-tools/src/plural-eval.h create mode 100644 gettext-tools/src/plural-exp.c create mode 100644 gettext-tools/src/plural-table.c create mode 100644 gettext-tools/src/plural-table.h create mode 100644 gettext-tools/src/po-charset.c create mode 100644 gettext-tools/src/po-charset.h create mode 100644 gettext-tools/src/po-error.c create mode 100644 gettext-tools/src/po-error.h create mode 100644 gettext-tools/src/po-gram-gen.c create mode 100644 gettext-tools/src/po-gram-gen.h create mode 100644 gettext-tools/src/po-gram-gen.y create mode 100644 gettext-tools/src/po-gram-gen2.h create mode 100644 gettext-tools/src/po-gram.h create mode 100644 gettext-tools/src/po-lex.c create mode 100644 gettext-tools/src/po-lex.h create mode 100644 gettext-tools/src/po-time.c create mode 100644 gettext-tools/src/po-time.h create mode 100644 gettext-tools/src/po-xerror.c create mode 100644 gettext-tools/src/po-xerror.h create mode 100644 gettext-tools/src/pos.h create mode 100755 gettext-tools/src/project-id create mode 100644 gettext-tools/src/read-catalog-abstract.c create mode 100644 gettext-tools/src/read-catalog-abstract.h create mode 100644 gettext-tools/src/read-catalog.c create mode 100644 gettext-tools/src/read-catalog.h create mode 100644 gettext-tools/src/read-csharp.c create mode 100644 gettext-tools/src/read-csharp.h create mode 100644 gettext-tools/src/read-desktop.c create mode 100644 gettext-tools/src/read-desktop.h create mode 100644 gettext-tools/src/read-java.c create mode 100644 gettext-tools/src/read-java.h create mode 100644 gettext-tools/src/read-mo.c create mode 100644 gettext-tools/src/read-mo.h create mode 100644 gettext-tools/src/read-po.c create mode 100644 gettext-tools/src/read-po.h create mode 100644 gettext-tools/src/read-properties.c create mode 100644 gettext-tools/src/read-properties.h create mode 100644 gettext-tools/src/read-resources.c create mode 100644 gettext-tools/src/read-resources.h create mode 100644 gettext-tools/src/read-stringtable.c create mode 100644 gettext-tools/src/read-stringtable.h create mode 100644 gettext-tools/src/read-tcl.c create mode 100644 gettext-tools/src/read-tcl.h create mode 100644 gettext-tools/src/recode-sr-latin.c create mode 100644 gettext-tools/src/str-list.c create mode 100644 gettext-tools/src/str-list.h create mode 100644 gettext-tools/src/urlget.c create mode 100644 gettext-tools/src/user-email.sh.in create mode 100644 gettext-tools/src/write-catalog.c create mode 100644 gettext-tools/src/write-catalog.h create mode 100644 gettext-tools/src/write-csharp.c create mode 100644 gettext-tools/src/write-csharp.h create mode 100644 gettext-tools/src/write-desktop.c create mode 100644 gettext-tools/src/write-desktop.h create mode 100644 gettext-tools/src/write-java.c create mode 100644 gettext-tools/src/write-java.h create mode 100644 gettext-tools/src/write-mo.c create mode 100644 gettext-tools/src/write-mo.h create mode 100644 gettext-tools/src/write-po.c create mode 100644 gettext-tools/src/write-po.h create mode 100644 gettext-tools/src/write-properties.c create mode 100644 gettext-tools/src/write-properties.h create mode 100644 gettext-tools/src/write-qt.c create mode 100644 gettext-tools/src/write-qt.h create mode 100644 gettext-tools/src/write-resources.c create mode 100644 gettext-tools/src/write-resources.h create mode 100644 gettext-tools/src/write-stringtable.c create mode 100644 gettext-tools/src/write-stringtable.h create mode 100644 gettext-tools/src/write-tcl.c create mode 100644 gettext-tools/src/write-tcl.h create mode 100644 gettext-tools/src/x-awk.c create mode 100644 gettext-tools/src/x-awk.h create mode 100644 gettext-tools/src/x-c.c create mode 100644 gettext-tools/src/x-c.h create mode 100644 gettext-tools/src/x-csharp.c create mode 100644 gettext-tools/src/x-csharp.h create mode 100644 gettext-tools/src/x-desktop.c create mode 100644 gettext-tools/src/x-desktop.h create mode 100644 gettext-tools/src/x-elisp.c create mode 100644 gettext-tools/src/x-elisp.h create mode 100644 gettext-tools/src/x-glade.c create mode 100644 gettext-tools/src/x-glade.h create mode 100644 gettext-tools/src/x-gsettings.c create mode 100644 gettext-tools/src/x-gsettings.h create mode 100644 gettext-tools/src/x-java.c create mode 100644 gettext-tools/src/x-java.h create mode 100644 gettext-tools/src/x-javascript.c create mode 100644 gettext-tools/src/x-javascript.h create mode 100644 gettext-tools/src/x-librep.c create mode 100644 gettext-tools/src/x-librep.h create mode 100644 gettext-tools/src/x-lisp.c create mode 100644 gettext-tools/src/x-lisp.h create mode 100644 gettext-tools/src/x-lua.c create mode 100644 gettext-tools/src/x-lua.h create mode 100644 gettext-tools/src/x-perl.c create mode 100644 gettext-tools/src/x-perl.h create mode 100644 gettext-tools/src/x-php.c create mode 100644 gettext-tools/src/x-php.h create mode 100644 gettext-tools/src/x-po.c create mode 100644 gettext-tools/src/x-po.h create mode 100644 gettext-tools/src/x-properties.h create mode 100644 gettext-tools/src/x-python.c create mode 100644 gettext-tools/src/x-python.h create mode 100644 gettext-tools/src/x-rst.c create mode 100644 gettext-tools/src/x-rst.h create mode 100644 gettext-tools/src/x-scheme.c create mode 100644 gettext-tools/src/x-scheme.h create mode 100644 gettext-tools/src/x-sh.c create mode 100644 gettext-tools/src/x-sh.h create mode 100644 gettext-tools/src/x-smalltalk.c create mode 100644 gettext-tools/src/x-smalltalk.h create mode 100644 gettext-tools/src/x-stringtable.h create mode 100644 gettext-tools/src/x-tcl.c create mode 100644 gettext-tools/src/x-tcl.h create mode 100644 gettext-tools/src/x-vala.c create mode 100644 gettext-tools/src/x-vala.h create mode 100644 gettext-tools/src/x-ycp.c create mode 100644 gettext-tools/src/x-ycp.h create mode 100644 gettext-tools/src/xgettext.c create mode 100644 gettext-tools/src/xgettext.h (limited to 'gettext-tools/src') diff --git a/gettext-tools/src/ChangeLog b/gettext-tools/src/ChangeLog new file mode 100644 index 0000000..fbfa43f --- /dev/null +++ b/gettext-tools/src/ChangeLog @@ -0,0 +1,7833 @@ +2014-12-24 Daiki Ueno + + * gettext 0.19.4 released. + +2014-12-18 Daiki Ueno + + * x-sh.c (read_word): Use phase1 instead of phase2 for Bash ANSI-C + escape sequences. Also handle '\"' and '\E'. + +2014-12-17 Daiki Ueno + + * x-sh.c (phase2_getc): Fix typo: debackslahificication -> + debackslashification. + +2014-12-12 Daiki Ueno + + * read-desktop.c (desktop_parse): Check and ignore + token_type_other. + +2014-12-12 Daiki Ueno + + * format-lisp.c (make_intersected_list): Don't dereference + potentially released memory. 'append_repeated_to_initial' may + release the LIST->element. Spotted by clang-analyzer. + * format-scheme.c (make_intersected_list): Likewise. + +2014-12-10 Daiki Ueno + + * msgl-check.c (check_header_entry): Don't declare unused variable + 'nrequiredfields'. + +2014-12-09 Daiki Ueno + + * xgettext.c (arglist_parser_done): Avoid potential + null-dereference. Spotted by clang-analyzer. + +2014-12-09 Daiki Ueno + + * msgfmt.c (msgfmt_desktop_bulk): Don't dereference potentially + uninitialized value. Spotted by clang-analyzer. + +2014-12-09 Daiki Ueno + + * x-vala.c (phase3_get): Factor out the buffer allocation as a + macro. + +2014-12-09 Daiki Ueno + + * read-desktop.c (desktop_lex): Undef the APPEND macro before + defining. + +2014-12-09 Daiki Ueno + + desktop: Simplify the parsing logic + * read-desktop.h (desktop_reader_class_ty): Rename 'handle_text' + to 'handle_blank'. + (desktop_reader_handle_blank): Rename from + 'desktop_reader_handle_text'. + * read-desktop.c (SIZEOF): New macro. + (desktop_reader_handle_blank): Rename from + 'desktop_reader_handle_blank'. + (read_until_newline, read_group_name, read_key_name): Remove. + Merge into... + (desktop_lex): ...here. + (desktop_parse): Call 'desktop_lex' instead of read_*. Don't + normalize whitespaces. + (enum token_type_ty): New enum. + (struct token_ty): New struct. + (free_token): New function. + * write-desktop.c (msgfmt_desktop_handle_blank): Rename from + 'msgfmt_desktop_handle_text'. + * x-desktop.c: Include "c-ctype.h". + (extract_desktop_handle_comment): Normalize whitespaces here. + (extract_desktop_handle_blank): Rename from + 'extract_desktop_handle_text'. + +2014-12-07 Daiki Ueno + + vala: Make regex literal handling robuster + * x-vala.c (token_type_ty): New enumeration values + 'token_type_arithmetic_operator' and 'token_type_question'. + Remove 'token_type_minus'. + (phase3_get): Rewrite regex literal handling right after + arithmetic assignment operators. + +2014-12-05 Daiki Ueno + + msgunfmt: Avoid integer overflow using xsize + * read-mo.c (get_uint32, get_sysdep_string): Use xsum to avoid + integer overflow, when checking length and offset fields. + Reported by Jakub Wilk at: + . + +2014-12-04 Daiki Ueno + + libgettextsrc: Follow plural.c -> pluralx.c file name change + * plural-exp.c: Include "../intl/pluralx.c" instead of + "../intl/plural.c". Adjust to commit d2d04ba9. + +2014-12-03 Daiki Ueno + + intl: Work around LCOV relative base directory resolution + * plural-exp.c: Include "../intl/plural.c" instead of + "../../gettext-runtime/intl/plural.c". + +2014-12-02 Daiki Ueno + + c: Minor cleanup of the previous commit + * x-c.c (phase5_get): Remove redundant check of is_prefix; fit + lines to 80 column. + +2014-12-02 Daiki Ueno + + c: Support C++11 string literals + * x-c.c (phase5_get): Recognize C++ string literals, defined in + ISO/IEC 9899:2011. Reported at: + . + +2014-12-01 Daiki Ueno + + c#: Recognize Unicode surrogate character pair + * x-csharp.c (accumulate_escaped): Change the first argument type + from 'struct string_buffer *' to 'struct mixed_string_buffer *', + for Unicode surrogate character pair handling; all callers + changed. Reported by Petr Kadlec at: + . + +2014-11-28 Daiki Ueno + + * msgfilter.c (prepare_read): Simplify the last commit 06e206f5, + by always adding 1 to the buffer size. + +2014-11-28 Daiki Ueno + + msgfilter: Fix read buffer allocation for empty input + * msgfilter.c (prepare_read): Increase allocated buffer size even + if the original size is < 2. + Reported by Robin McCorkell at: + . + +2014-11-27 Daiki Ueno + + javascript: Simplify Unicode character escape handling + ECMA-262 only supports "\uXXXX" style Unicode character escape and + we don't need extra space for character names. + * x-javascript.c: Don't include "uniname.h". + (phase2_pushback): Decrease to 5. Don't refer to UNINAME_MAX. + +2014-11-18 Daiki Ueno + + * read-mo.c: Include "xsize.h". + (get_string): Use xsum3 to avoid overflow, when checking length + and offset fields. + Reported by Jakub Wilk at: + . + +2014-10-28 Daiki Ueno + + xgettext: Allow plural extraction from a single argument function + The commit 8137d2b4 was a wrong fix since both singular/plural msgids + may point to the same address for Qt4 plural forms. This reverts + the commit and fix the original double-free problem in the right + way. Thanks to Jesper Fehrlund for suggestions. + * xgettext.c (arglist_parser_remember_literal): Don't ignore + plural argument even if ARGNUM1 equals to ARGNUM2. + (arglist_parser_done): Make a copy of best_cp->msgid_plural when + passing it to remember_a_message_plural, if it equals to + best_cp->msgid. Also move code conversion logic earlier taking + into account of the ownership transfer of best_cp->msgid. + +2014-10-28 Daiki Ueno + + xgettext: Fix double-free in singular/plural argument extraction + After commit 6aa7b7ed in 2009, xgettext assumed that ARGNUM1 and + ARGNUM2 of -k are different. That could cause an double-free in + exceptional cases. + Reported by Johan Liljegren in: + . + * xgettext.c (arglist_parser_remember_literal): Don't assume that + ARGNUM1 and ARGNUM2 are different. + +2014-10-15 Daiki Ueno + + * gettext 0.19.3 released. + +2014-10-08 Daiki Ueno + + * write-po.c (wrap): Report error on incomplete multibyte sequence + at the end of input bytes. + Reported by Jakub Wilk at: + . + +2014-09-30 Daiki Ueno + + * x-c.c (literalstring_parse): Fix octal character escape handling. + Reported by Kjartan Maraas at: + . + +2014-09-24 Daiki Ueno + + * x-python.c (x_python_lex): Move 'token3' variable declaration + out of the internal block. + +2014-09-24 Daiki Ueno + + * filter-quote.c (BOLD_START, BOLD_END): Don't use non-portable + character escape "\e". + +2014-08-28 Jonas 'Sortie' Termansen (tiny change) + + * msginit.c: Include . + (get_user_pwd): Cast uid_t value into uintmax_t and print it with + '%ju' format directive. + +2014-08-27 Jonas 'Sortie' Termansen (tiny change) + + * msgfilter.c (process_message): Use proper format directive for + printing size_t. + * msgexec.c (process_message): Likewise. + +2014-08-27 Jonas 'Sortie' Termansen (tiny change) + + * hostname.c: Add guard around #include . + +2014-07-14 Daiki Ueno + + * gettext 0.19.2 released. + +2014-07-14 Daiki Ueno + + vala: Fix empty string literal handling + Similar to the commit 7b2d8d61 on July 11. + * x-vala.c (phase3_get): Add missing memory allocation for empty + string literal. + +2014-07-14 Daiki Ueno + + build: Fix race in post-install removal of *.a + There was an implicit dependency between the prerequisites of + install-exec-local, which may have caused error with make -jN. + Use install-exec-hook to remove the dependency. + Reported by Christian Weisgerber in: + . + * Makefile.am (install-exec-local): Remove. + (install-exec-hook): New rule, depend on install-exec-clean. + +2014-07-11 Daiki Ueno + + c: Fix empty string literal handling + Problem reported by Bernhard Voelker in: + . + * x-c.c (phase5_get): Add missing memory allocation for empty + string literal. + +2014-06-10 Daiki Ueno + + * gettext 0.19.1 released. + +2014-06-09 Daiki Ueno + + msgmerge: Disable --color option in --update mode + Suggested by 林V字龍 at: + . + * msgmerge.c (main): Error out when --color and --update are + specified at the same time. + +2014-06-07 Daiki Ueno + + xgettext: Fix misrecognition of character literals in C and Vala + Problem reported by Paul Eggert at + . + * x-c.c (phase5_get): Make sure to skip contents of character constant. + * x-vala.c (phase3_get): Likewise. + +2014-06-03 Daiki Ueno + + desktop: Use logical filename as msgid location + * read-desktop.c (desktop_parse): Pass logical filename instead of + real filename to desktop_reader_handle_pair. + +2014-06-03 Daiki Ueno + + desktop: Read LINGUAS file even if LINGUAS envvar is set + * msgfmt.c (get_languages): If the LINGUAS envvar is set, use it + to restrict the languages list read from the LINGUAS file, not to + extend the list. + (add_languages): Take an optional DESIRED_LANGUAGES argument. + +2014-06-03 Daiki Ueno + + vala: Don't elide comments while parsing string literal + * x-vala.c (phase3_get): Use phase1_getc to read characters inside + a string literal. + +2014-06-02 Daiki Ueno + + msgl-check: Treat missing header errors as warnings + Problem reported by Richard Hughes at + . + After , "msgfmt -c" reports + error on missing PO headers. However, it turned out to be too strict + for the projects using Launchpad Translations. Let's relax the check + for the moment and wait for one or two release cycles. + * msgl-check.c (check_header_entry): Report missing + required headers as warning, instead of error. + +2014-06-02 Daiki Ueno + + * gettext 0.19 released. + +2014-05-31 Daiki Ueno + + msgexec: Pass previous msgid to the child process + Suggested by Pavel Kharitonov in: + . + * msgexec.c (process_string): Set MSGEXEC_PREV_* envvar. + +2014-05-31 Daiki Ueno + + msgfilter: Pass previous msgid to the child process + Suggested by Pavel Kharitonov in: + . + * msgfilter.c (process_message): Set MSGFILTER_PREV_* envvar. + +2014-05-19 Daiki Ueno + + * Makefile.am (po-gram-gen2.h): Adjust the directory to which + po-gram-gen.h, for VPATH build. + +2014-05-15 Stanislav Brabec (tiny change) + + msgfilter: Implement plural support + * msgfilter.c (process_message): Set or unset + MSGFILTER_MSGID_PLURAL and MSGFILTER_PLURAL_FORM. + +2014-05-15 Stanislav Brabec (tiny change) + + msgexec: Implement plural support + * msgexec.c (process_string): Set or unset MSGEXEC_MSGID_PLURAL. + (process_message): Set or unset MSGEXEC_PLURAL_FORM. + +2014-05-14 Daiki Ueno + + msgfmt: Report error on accelerator mismatch + * msgl-check.c (check_pair): Increment error count on missing + accelerator character or too many accelerators. + +2014-05-13 Daiki Ueno + + msgfmt: Accumulate errors when parsing the PO header + Problem reported by Peter Eisentraut at + . + * msgl-check.c (check_header_entry): Return the number of errors. + (check_message): Check the return value of check_header_entry. + +2014-05-13 Felipe Sateler (tiny change) + + project-id: Add missing quotes around `pwd` for basename + Problem reported at . + * project-id: Quote argument of the basename command. + +2014-05-12 Daiki Ueno + + msgfilter: Fix quote handling of doubled grave charaters + * filter-quote.c (convert_ascii_quote_to_unicode): Fix handling of + doubled grave characters. + +2014-05-12 Daiki Ueno + + * msgfmt.c (add_languages): New function split from get_languages. + (get_languages): Use add_languages instead of manually parsing + LINGUAS envvar with strtok_r. + + * x-c.h (literalstring_c): Remove unnecessary DLL_VARIABLE. + * xgettext.c (arglist_parser_alloc): Use LET_NONE instead of 0. + (arglist_parser_remember): Likewise. + * xgettext.h (enum literalstring_escape_type): New enum value + LET_NONE. + +2014-05-10 Guido Flohr + + msgattrib: Add --empty option to clear msgstr + * msgattrib.c (REMOVE_TRANSLATION): New enum value. + (long_options): Add --empty. + (main): Set REMOVE_TRANSLATION flag when --empty is given. + (usage): Show help of --empty. + (process_message_list): Handle REMOVE_TRANSLATION flag. + +2014-05-09 Daiki Ueno + + vala: Interpret string literals lazily + * x-vala.c (P7_EOF, P7_STRING_END, P7_QUOTES, P7_QUOTE, P7_NEWLINE) + (UNICODE, IS_UNICODE, UNICODE_VALUE): Remove. + (phase7_getc): Remove. + (phase7_ungetc): Remove. + (phase3_get): Use 'phase2_get' directly to extract string + literals; use 'arglist_parser_remember_literal' instead of + 'arglist_parser_remember'. + (literalstring_c): Declare external variable. + (extract_balanced): Remove the + 'xgettext_current_source_encoding' setting to prevent encoding + conversion around 'arglist_parser_done'. + (token_ty): New field 'escape'. + * x-vala.h (SCANNERS_VALA): Register 'literalstring_c' as a + literalstring_parser. + +2014-05-09 Daiki Ueno + + c: Interpret string literals lazily + * x-c.c (P7_EOF, P7_STRING_END, P7_QUOTES, P7_QUOTE, P7_NEWLINE) + (UNICODE, IS_UNICODE, UNICODE_VALUE): Remove. + (phase7_get): Remove. + (phase7_ungetc): Remove. + (phase5_get): Use 'phase3_get' directly to extract string + literals; use 'arglist_parser_remember_literal' instead of + 'arglist_parser_remember'. + (literalstring_parse): New function. + (literalstring_c): New variable. + (extract_parenthesized): Remove the + 'xgettext_current_source_encoding' setting to prevent encoding + conversion around 'arglist_parser_done'. + * x-c.h (SCANNERS_C): Register 'literalstring_c' as a + literalstring_parser. + (literalstring_c): New variable declaration. + +2014-05-09 Daiki Ueno + + xgettext: Provide a way to interpret string literals lazily + * xgettext.c (extract_ty): New field 'literalstring_parser'. + (current_literalstring_parser): New variable. + (extract_from_file): Set 'current_literalstring_parser'. + (savable_comment_convert_encoding): New function. + (arglist_parser_remember_literal): New function. + (arglist_parser_remember): Call 'arglist_parser_remember_literal'. + (arglist_parser_done): Call literalstring_parser on msgctxt, + msgid, and msgid_plural before calling 'remember_a_message'; + convert encoding of msgid_comment. + * x-*.h: Register 'literalstring_parser' through SCANNER_*. + * xgettext.h (enum literalstring_escape_type): New enum. + (struct literalstring_parser): New struct. + (struct partial_call): New fields 'msgctxt_escape', + 'msgid_escape', and 'msgid_plural_escape'. + (arglist_parser_remember_literal): New function declaration. + (savable_comment_convert_encoding): New function declaration. + +2014-05-03 Daiki Ueno + + xgettext: Recognize prefixed comment tag + Reported by Jiang Xin in + . + * xgettext.c (remember_a_message): Discard a string prefixed to + the comment tag from all remaining comment lines. + +2014-05-03 Daiki Ueno + + c: Support C99-style Unicode character escapes + * x-c.c: Include assert.h and po-charset.h. + (P7_QUOTES, P7_QUOTE, P7_NEWLINE): Redefine as a negative integer. + (P7_EOF, P7_STRING_END): New definitions. + (UNICODE): New macro. + (IS_UNICODE): New macro. + (UNICODE_VALUE): New macro. + (phase7_getc): Recognize "\unnnn" and "\Unnnnnnnn". + (phase5_get): Use mixed_string_buffer for parse string literal. + +2014-05-02 Daiki Ueno + + vala: Support C99-style Unicode character escapes + * x-vala.c: Include assert.h and po-charset.h. + (P7_QUOTES, P7_QUOTE, P7_NEWLINE): Redefine as a negative integer. + (P7_EOF, P7_STRING_END): New definitions. + (UNICODE): New macro. + (IS_UNICODE): New macro. + (UNICODE_VALUE): New macro. + (phase7_getc): Recognize "\unnnn" and "\Unnnnnnnn". + (phase3_get): Use mixed_string_buffer for parse string literal. + +2014-05-02 Daiki Ueno + + xgettext: Factor out commonly used mixed_string_buffer + * x-python.c (init_mixed_string_buffer) + (mixed_string_buffer_append_byte) + (mixed_string_buffer_append_unicode_grow) + (mixed_string_buffer_append_unicode) + (mixed_string_buffer_flush_utf16_surr) + (mixed_string_buffer_flush_curr_buffer) + (mixed_string_buffer_append, mixed_string_buffer_result) + (free_mixed_string_buffer): Move to... + * xgettext.c: ...here. + (mixed_string_buffer_alloc): Rename from init_mixed_string_buffer. + (mixed_string_buffer_append_to_curr_buffer): Rename from + mixed_string_buffer_append_byte. + (mixed_string_buffer_append_to_utf8_buffer): Rename from + mixed_string_buffer_append_unicode. + (mixed_string_buffer_grow_utf8_buffer): Rename from + mixed_string_buffer_append_unicode_grow. + (mixed_string_buffer_append_char): Split from + mixed_string_buffer_append. + (mixed_string_buffer_append_unicode): Split from + mixed_string_buffer_append. + (mixed_string_buffer_done): New function merging + mixed_string_buffer_result and free_mixed_string_buffer. + * xgettext.h (mixed_string_buffer): New struct moved from + x-python.c; add logical_file_name and line_number fields. + (mixed_string_buffer_alloc): New function declaration. + (mixed_string_buffer_append_char): New function declaration. + (mixed_string_buffer_append_unicode): New function declaration. + (mixed_string_buffer_done): New function declaration. + * x-javascript.c (init_mixed_string_buffer) + (mixed_string_buffer_append_byte) + (mixed_string_buffer_append_unicode_grow) + (mixed_string_buffer_append_unicode) + (mixed_string_buffer_flush_utf16_surr) + (mixed_string_buffer_flush_curr_buffer) + (mixed_string_buffer_append, mixed_string_buffer_result) + (free_mixed_string_buffer): Remove. + +2014-04-30 Daiki Ueno + + scheme: Recognize GIMP script-fu extension _"abc" + * x-scheme.c (read_object): Recognize _"abc". + +2014-04-30 Daiki Ueno + + format-python-brace: Limit acceptable format specifiers + Problem reported by Kovid Goyal at: + . + * format-python-brace.c (parse_directive): Only recognize a single + nested format directive or the standard format specifiers as + format specifiers. + +2014-04-22 Roumen Petrov (tiny change) + + build: Use Automake 'subdir-objects' option + * Makefile.am (AUTOMAKE_OPTIONS): Add 'subdir-objects'. + +2014-04-21 Daiki Ueno + + msgfilter: Fix single quote handling in the quot filter + * filter-quote.c (convert_ascii_quote_to_unicode): Fix single + quote handling to accept multiple quotations in a string. + +2014-04-17 Daiki Ueno + + xgettext: Strip multiple ".in" suffixes from the file name. + * xgettext.c (main): Strip multiple ".in" suffixes. + +2014-04-16 Daiki Ueno + + * msgfmt.c (get_languages): Allow any whitespace character as a + list separator in LINGUAS. + +2014-04-15 Daiki Ueno + + msgfilter: Add 'quot' and 'boldquot' built-in filters + * filter-quote.c: New file. + * filters.h (ascii_quote_to_unicode, ascii_quote_to_unicode_bold): + New function declaration. + * msgfilter.c (main): Handle 'quot' and 'boldquot' filters. + * Makefile.am (msgfilter_SOURCES): Add filter-quote.c. + +2014-04-04 Daiki Ueno + + * xgettext.c (main): Warn user if invalid encoding name is + specified with the --from-code option. + Reported by jaroslav.fojtik@evolvsys.cz in + . + +2014-04-04 Daiki Ueno + + msgfmt: Add support for Desktop Entry files + * write-desktop.h: New file. + * write-desktop.c: New file. + * msgfmt.c (desktop_mode, desktop_locale_name) + (desktop_template_name, desktop_base_directory, desktop_keywords) + (desktop_default_keywords): New variables. + (long_options): Add --desktop and --template options for Desktop + Entry mode. + (get_languages): New function. + (msgfmt_desktop_bulk): New function which implements bulk + operation mode for Desktop Entry mode. + (main): Handle Desktop Entry mode options; call + 'msgfmt_desktop_bulk' if -d option is seen. + * Makefile.am (noinst_HEADERS): Add write-desktop.h. + (msgfmt_SOURCES): Add write-desktop.c + +2014-04-04 Daiki Ueno + + xgettext: Add support for Desktop Entry files + * read-desktop.h: New file. + * read-desktop.c: New file. + * x-desktop.h: New file. + * x-desktop.h: New file. + * xgettext.c (main): Regiser keywords for Desktop Entry mode. + (usage): Mention Desktop Entry source language. + (language_to_extractor): Add Desktop Entry rule. + (extension_to_language): Add Desktop Entry rule. + * Makefile.am (noinst_HEADERS): Add read-desktop.h and x-desktop.h. + (xgettext_SOURCES): Add x-desktop.c. + +2014-03-26 Aurélien Gâteau (tiny change) + + msgfmt: Add --source option to generate .java file instead of .class + * msgfmt.c (java_output_source): New variable. + (long_options, main, usage): Add --source option. + * write-java.h (msgdomain_write_java): Add OUTPUT_SOURCE argument. + * write-java.c (msgdomain_write_java): Generate .java file instead + of .class if OUTPUT_SOURCE argument is given. + Reported at . + +2014-03-25 Daiki Ueno + + Extend --add-location option to suppress line number output + The --add-location option of msgattrib, msgcat, msgcomm, msgconv, + msgen, msgfilter, msggrep, msgmerge, msguniq, and xgettext + commands now got new semantics. It takes an optional argument + 'never', 'full', or 'file', to control the format of "#: ..." + comments. + The default catalog reader changed to always remember file + positions so the line number part can be suppressed in output + phase rather than input phase. + Feature requested in: + . + * read-catalog.h (line_comment): Abolish. + (DEFAULT_CATALOG_READER_TY): Remove handle_filepos_comments field. + * read-catalog.c (line_comment): Abolish. + (default_destructor, default_copy_comment_state) + (default_reset_comment_state, default_comment_filepos): Always + remember filepos. + (default_parse_brief, read_catalog_stream): Adjust to the change. + * write-po.h (enum filepos_comment_type): New enum. + (message_print_style_filepos): New function declaration. + (handle_filepos_comment_option): New function declaration. + * write-po.c (message_print_style_filepos): New function + (handle_filepos_comment_option): New function. + (message_print_comment_filepos): Uniquify mp->filepos elements + ignoring line number if filepos_comment_type is + filepos_comment_file. + * msgfmt.c: Adjust to the change. + * msgattrib.c (long_options, main): Allow --add-location option to + take an optional format specifier. + * msgcat.c (long_options, main): Likewise. + * msgcomm.c (long_options, main): Likewise. + * msguniq.c (long_options, main): Likewise. + * xgettext.c (long_options, main): Likewise. + * msgconv.c (long_options, main): Likewise; add a new option -n as + an alias of --add-location. + * msgen.c (long_options, main): Likewise. + * msgfilter.c (long_options, main): Likewise. + * msggrep.c (long_options, main): Likewise. + * msgmerge.c (long_options, main): Likewise. + +2014-03-12 Daiki Ueno + + xgettext: Fix infloop on loading Glade files with non-DL expat + * libexpat-compat.c: Keep the references to + XML_GetCurrent{Line,Column}Number symbols before including + libexpat-compat.h, since they are redefined. + +2014-03-10 Daiki Ueno + + php: Recognize single and double quotes around heredoc label + Problem reported by Byrial Jensen in: + . + Based on the patch by Andreas Stricker posted as: + . + * x-php.c (phase4_get): Strip quotes around heredoc label. + +2013-11-20 Daiki Ueno + + xgettext: Add E4X support to JavaScript scanner + Reported by Piotr Drąg at: . + * xgettext.h (enum lexical_context_ty): New enumeration items + lc_xml_open_tag, lc_xml_close_tag, lc_xml_content. + * x-javascript.c (phase5_scan_xml_markup): New function. + (phase5_get): Handle '<', '>', '/', '=', '{', and '}' specially + to support E4X. + (enum token_type_ty): New enumeration item token_type_equal. + (xml_element_depth): New variable. + (inside_embedded_in_xml): New variable. + (extract_javascript): Initialize those variables. + +2013-11-14 Daiki Ueno + + * x-javascript.c (phase3_getc): Make sure to call comment_line_end + after parsing C++ style comment line. + Reported by Illimar Tambek at: . + +2013-11-14 Daiki Ueno + + * x-javascript.c (comment_line_end): Add missing chars_to_remove + argument; all callers changed. + Reported by Illimar Tambek at: . + +2013-11-11 Daiki Ueno + + * x-c.c (phase5_get): Fix misuse of a logical operator. + * x-perl.c (extract_quotelike_pass3): Likewise. + * x-vala.c (phase3_get): Likewise. + Reported by David Binderman at: . + +2013-10-23 Daiki Ueno + + * read-catalog-abstract.c (catalog_reader_parse): Clear + error_message_count before parsing, rather than after. The + variable may be > 0 before calling the PO parser, when xgettext + handles mutiple files. + Problem reported by Emil Wojak in + . + +2013-10-15 Peter Eisentraut (tiny change) + + * msgl-check.c (check_header_entry): Adjust the default value of + PO-Revision-Date to xgettext output. Reported at + . + +2013-08-29 Daiki Ueno + + * po-gram-gen.y (message): Free memory allocated for + msgid_pluralform. + (string_list): Free memory allocated for STRING. + (prev_string_list): Free memory allocated for PREV_STRING. + Reported by Alexander Potashev in + . + +2013-08-13 Miguel Angel Arruga Vivas + + * x-glade.c (start_element_glade1): Use extract_all variable. + (start_element_glade2): Ignore --extract-all option. + (start_element_gtkbuilder): Likewise. + (start_element_handler): Initialize p->extract_string to false. + +2013-08-12 Daiki Ueno + + * xgettext.c (remember_a_message): Handle multi-line extracted + comments. + Reported by Gabor Kelemen in + . + +2013-08-09 Daiki Ueno + + * x-gsettings.c (extract_gsettings): Add guard when expat is not + available at compile time. + +2013-08-08 Miguel Angel Arruga Vivas (tiny change) + + Fix copyright year in xgettext version string. + * xgettext.c (main): Update copyright year. + +2013-08-06 Daiki Ueno + + xgettext: add support for GSettings schema file + * x-gsettings.h: New file. + * x-gsettings.c: New file. + * xgettext.c: Include x-gsettings.h. + (flag_table_vala): New variable. + (usage): Mention GSettings source language. + (language_to_extractor): Add GSettings rule. + (extension_to_language): Add GSettings rule. + * Makefile.am (noinst_HEADERS): Add x-gsettings.h. + (xgettext_SOURCES): Add x-gsettings.c. + * FILES: Update. + +2013-08-05 Daiki Ueno + + * xgettext.c (main): Allow exntension with multiple + dots. e.g. .gschema.xml. + +2013-03-02 Miguel Angel Arruga Vivas + + Extract libexpat compatibility layer. + * libexpat-compat.h, libexpat-compat.c: Extracted from x-glade.c + * x-glade.c: Use libexpat-compat.h + +2013-07-11 Daiki Ueno + + Fix crash when parsing '..' with non-string argument. + Reported by Koen Dergent at . + * x-lua.c (extract_balanced): Skip stray '..'. + +2013-06-27 Daiki Ueno + + * x-python.c (phase5_pushback): Increment size to 2. + +2013-06-25 Daiki Ueno + + Fix handling of \u escape sequences in Tcl. + * x-tcl.c (do_getc_escaped): Fix handling of \u. + Reported by Guido Berhoerster in + . + +2013-06-17 Daiki Ueno + + * x-python.c (init_flag_table_python): Enable python-brace-format + by default. + +2013-06-17 Daiki Ueno + + * x-glade.c (start_element_null): New function. + (end_element_null): New function. + (element_parser_null): New variable. + (start_element_handler): Use element_parser_null if the top-level + element does not indicate a valid Glade file. + (do_extract_glade): Initialize element_parser to + element_parser_null rather than NULL. + +2013-06-13 Miguel Angel Arruga Vivas + Daiki Ueno + + Support for GtkBuilder file format in the Glade scanner. + * x-glade.h (EXTENSIONS_GLADE): Recognize .ui. + * x-glade.c (element_parser): New struct. + (start_element_glade1): New function split from start_element_handler. + (end_element_glade1): New function split from end_element_handler. + (start_element_glade2): New function split from start_element_handler. + (end_element_glade2): New function split from end_element_handler. + (start_element_gtkbuilder): New function. + (end_element_gtkbuilder): New function. + (element_parser_glade1): New variable. + (element_parser_glade2): New variable. + (element_parser_gtkbuilder): New variable. + (start_element_handler): Delegate the actual parsing logic to + specific element_parser. + (end_element_handler): Likewise. + Thanks to Miguel Ángel Arruga Vivas for the initial implementation + and the discussion in + + footnote 2. + +2013-06-10 Daiki Ueno + + * Makefile.am: Use $(MKDIR_P) instead of $(mkdir_p). + Suggested by Stefano Lattarini in + . + +2013-06-04 Daiki Ueno + + Support for Vala. + * x-vala.h: New file. + * x-vala.c: New file. + * xgettext.c: Include x-vala.h. + (flag_table_vala): New variable. + (main): Invoke init_flag_table_vala, x_vala_extract_all, + x_vala_keyword. + (usage): Mention Vala source language. + (language_to_extractor): Add Vala rule. + (extension_to_language): Add Vala rule. + * Makefile.am (noinst_HEADERS): Add x-vala.h. + (xgettext_SOURCES): Add x-vala.c. + * FILES: Update. + +2013-05-21 Daiki Ueno + + * x-javascript.c (phase7_getuc): Treat non-legitimate character + escape sequences more strictly. + +2013-05-20 Pavel Kharitonov (tiny change) + + Add --previous option to msgattrib. + * msgattrib.c (long_options, main, process_message_list): + (usage): Add --previous option. + +2013-05-12 Daiki Ueno + + Fix end-of-string handling in JavaScript scanner. + * x-javascript.c (phase7_getuc): Remove unused BACKSLASH_COUNTER + argument; all callers changed. + Reported by Andreas Stricker in + . + +2013-05-03 Daiki Ueno + + * user-email.sh.in (lowercase_sed): Don't use non-portable + character escape in sed 's' command. + Reported by Ben Fox-Moore in + + and thanks to Ineiev for the suggestion. + +2013-04-26 Daiki Ueno + + Support for Python brace format. + * message.h (format_type): New enum value 'format_python_brace. + (NFORMATS): Increment. + * message.c (format_language): Add format_python_brace entry. + (format_language_pretty): Likewise. + * format.h (formatstring_python_brace): New declaration. + * format-python-brace.c: New file. + * format.c (formatstring_parsers): Add formatstring_python_brace. + * x-python.h (SCANNERS_PYTHON): Refar to formatstring_python_brace. + * xgettext.c (xgettext_record_flag): Handle format_python_brace. + * FILES: Update. + +2013-04-22 Daiki Ueno + + Make msgfmt --check-header more reliable. + * msgl-check.c (check_header_entry): Don't use c_strstr to parse + message header. + +2013-04-22 Daiki Ueno + + Support CR/LF line terminators in Python sources even on Unix. + * x-python.c (phase0_getc, phase0_ungetc): New functions. + (phase1_getc): Use them instead of calling getc/ungetc directly. + +2013-04-22 Daiki Ueno + + Support explicit string concatenation in Python. + * x-python.c (enum token_type_ty): New enumeration item + token_type_plus. + (free_token): New function. + (phase5_get): Recognize token_type_plus. + (x_python_lex): Handle string concatenation with '+'. + (extract_balanced): Handle token_type_plus. + +2013-04-18 Daiki Ueno + + * xgettext.c (usage): Wrap long lines in --help output. + +2013-04-17 Andreas Stricker + + Support for JavaScript. + * message.h (format_type): New enum value 'format_javascript'. + (NFORMATS): Increment. + * message.c (format_language): Add format_javascript entry. + (format_language_pretty): Likewise. + * format.h (formatstring_javascript): New declaration. + * format-javascript.c: New file. + * format.c (formatstring_parsers): Add formatstring_javascript. + * x-javascript.h: New file. + * x-javascript.c: New file. + * xgettext.c: Include x-javascript.h. + (flag_table_javascript): New variable. + (main): Invoke init_flag_table_javascript, x_javascript_extract_all, + x_javascript_keyword. + (usage): Mention JavaScript source language. + (xgettext_record_flag): Handle format_javascript. + (language_to_extractor): Add JavaScript rule. + (extension_to_language): Add JavaScript rule. + * Makefile.am (noinst_HEADERS): Add x-javascript.h. + (FORMAT_SOURCE): Add format-javascript.c. + (xgettext_SOURCES): Add x-javascript.c. + * FILES: Update. + +2013-04-16 Ľubomír Remák + + Support for escape sequences added in Lua 5.2. + * x-lua.c (phase3_get): Add \x and \z escape sequences. + +2013-04-11 Ľubomír Remák + + Support for Lua. + * message.h (format_type): New enum value 'format_lua'. + (NFORMATS): Increment. + * message.c (format_language): Add format_lua entry. + (format_language_pretty): Likewise. + * format.h (formatstring_lua): New declaration. + * format-lua.c: New file. + * format.c (formatstring_parsers): Add formatstring_lua. + * x-lua.h: New file. + * x-lua.c: New file. + * xgettext.c: Include x-lua.h. + (flag_table_lua): New variable. + (main): Invoke init_flag_table_lua, x_lua_extract_all, + x_lua_keyword. + (usage): Mention Lua source language. + (xgettext_record_flag): Handle format_lua. + (language_to_extractor): Add Lua rule. + (extension_to_language): Add Lua rule. + * Makefile.am (noinst_HEADERS): Add x-lua.h. + (FORMAT_SOURCE): Add format-lua.c. + (xgettext_SOURCES): Add x-lua.c. + * FILES: Update. + +2013-03-15 Miguel Ángel Arruga Vivas + Daiki Ueno + + Extract msgctxt from Glade input files. + Reported at + * x-glade.c (struct element_state): Add field 'extract_context'. + (start_element_handler): Check "context" attribute if the string + contains msgctxt. + (end_element_handler): Extract msgctxt if extract_context is set. + +2013-02-25 Daiki Ueno + + * Makefile.am (libgettextsrc_la_CPPFLAGS): Define to specify Woe32 + DLL export flags. + +2013-01-09 Andreas Stricker (tiny change) + + * po-xerror.c: Include error.h for error_message_count. + * read-catalog-abstract.c: Likewise. + +2013-01-06 Daiki Ueno + + * msgl-fsearch.c (message_fuzzy_index_ty): Don't use float-derived + integer SHORT_MSG_MAX to define array. + (message_fuzzy_index_alloc): Allocate memory for 'short_messages' + field dynamically. + (message_fuzzy_index_free): Free it. + +2013-01-03 Daiki Ueno + + * xgettext.c (construct_header): Fix memory leak. + +2012-12-25 Daiki Ueno + + * gettext-0.18.2 released. + +2012-06-03 Jim Meyering + + * msginit.c: Spelling fixes. + * write-catalog.c: Likewise. + +2012-05-03 Bruno Haible + + Document msgfmt option --endianness. + * msgfmt.c (usage): Document the option --endianness. + Reported by Paul Martin via + Santiago Vila . + +2012-01-26 Bruno Haible + + Modernize quoting. + * hostname.c (usage): Quote 'like this', not `like this', as per the + recent change to the GNU coding standards. + * msgattrib.c (usage): Likewise. + * msgcat.c (usage): Likewise. + * msgcmp.c (usage): Likewise. + * msgcomm.c (usage): Likewise. + * msgconv.c (usage): Likewise. + * msgen.c (usage): Likewise. + * msgexec.c (usage): Likewise. + * msgfilter.c (usage): Likewise. + * msgfmt.c (usage, msgfmt_set_domain, msgfmt_frob_new_message): + Likewise. + * msggrep.c (usage): Likewise. + * msginit.c (usage): Likewise. + * msgl-cat.c (catenate_msgdomain_list): Likewise. + * msgl-check.c (check_pair, check_header_entry): Likewise. + * msgmerge.c (usage): Likewise. + * msgunfmt.c (usage): Likewise. + * msguniq.c (usage): Likewise. + * recode-sr-latin.c (usage): Likewise. + * urlget.c (usage): Likewise. + * write-po.c (wrap): Likewise. + * xgettext.c (main, usage, remember_a_message, construct_header, + language_to_extractor): Likewise. + * po-gram-gen.y: Likewise. + * po-error.h: Likewise. + * po-lex.h: Likewise. + * read-catalog-abstract.h: Likewise. + * x-c.c: Likewise. + +2011-10-04 Bruno Haible + + xgettext for Scheme: Understand guile 2.0 comment syntax, part 2. + * x-scheme.c (read_object): Extract and don't ignore block comments. + Understand the #|...|# syntax of SRFI-30. (Code copied from x-lisp.c.) + +2011-10-04 Bruno Haible + + xgettext for Scheme: Understand guile 2.0 comment syntax, part 1. + * x-scheme.c (read_object): Understand !# as a block comment terminator + even when not surrounded by newlines. + Reported by David Pirotte + via Santiago Vila . + +2011-07-29 Bruno Haible + + Fix xgettext crash when extracting a message with plural that is + excluded. + * xgettext.h (remember_a_message): Document the return value. + * xgettext.c (arglist_parser_done): Handle the case where + remember_a_message returned NULL. + * x-smalltalk.c (extract_smalltalk): Likewise. + * x-ycp.c (extract_parenthesized): Likewise. + Reported by Jean-Luc Coulon via + Santiago Vila . + +2011-06-13 Bruno Haible + + Avoid compilation error on Solaris 7 with cc. + * msgl-fsearch.c (SHORT_MSG_MAX): Define more directly with SunPRO C. + +2011-06-04 Bruno Haible + + Avoid link error when linking statically on AIX 7. + * Makefile.am (xgettext_LDADD): Add LTLIBICONV. + +2011-04-13 Bruno Haible + + * msgcat.c (usage): Fix description of --use-first. + * msgcomm.c (usage): Fix typo. + Reported by Matthijs Kooijman. + +2011-06-04 Bruno Haible + + Update after __attibute__ is no longer defined by gnulib. + * msgl-check.c (formatstring_error_logger): Use __attribute__ only with + compiler versions that support it. + +2011-06-02 Bruno Haible + + Use u8_mbtoucr instead of u8_mbtouc in some places. + * po-lex.c (mbfile_getc): Call u8_mbtoucr instead of u8_mbtouc. + * x-csharp.c (phase2_getc): Likewise. + * x-python.c (phase2_getc): Likewise. + Needed after libunistring changed on 2010-11-13. + +2011-06-02 Bruno Haible + + Update after gnulib changed. + * msgexec.c: Include spawn-pipe.h instead of pipe.h. + * msginit.c: Likewise. + * read-csharp.c: Likewise. + * read-java.c: Likewise. + * read-resources.c: Likewise. + * read-tcl.c: Likewise. + * write-resources.c: Likewise. + +2010-11-20 Bruno Haible + + Port to uClibc. + * write-po.c (wrap): Treat uClibc like a non-glibc platform. + * format-c-parse.h (HANDLE_I_FLAG): Likewise. + * po-charset.c (po_lex_charset_set): Likewise. + * recode-sr-latin.c (process): Likewise. + * x-python.c (set_current_file_source_encoding): Likewise. + * xgettext.c (main): Likewise. + +2010-11-13 Ihar Hrachyshka (tiny change) + + * plural-table.c (plural_table): Added Belarusian, copied from Russian + and Ukrainian. + +2010-11-07 Bruno Haible + + format-c.c: Share code with libintlext. + * format-c-parse.h: New file, extracted from format-c.c. + * format-c.c: Include it. + (format_parse_entrails): Remove function. + * Makefile.am (FORMAT_SOURCE): Add format-c-parse.h. + * FILES: Update. + +2010-11-07 Bruno Haible + + format-c.c: Prepare for sharing code with libintlext. + * format-c.c: Move INVALID_* macros. + (IF_OOM): New macro. + (SYSDEP_SEGMENTS_PROCESSED): New macro. + (FAT_BASIC_MASK): New enumeration value. + (HANDLE_I_FLAG): New macro. + (format_parse_entrails): New function, extracted from format_parse. + Use the IF_OOM, SYSDEP_SEGMENTS_PROCESSED, HANDLE_I_FLAG macros. Handle + I64 as a size specifier on native Win32. + (format_parse): Rewritten to call format_parse_entrails. + +2010-11-07 Bruno Haible + + format-c.c: Prepare for sharing code with libintlext. + * format-c.c (INVALID_ANGLE_BRACKET, INVALID_IGNORED_ARGUMENT): New + macros. + (format_parse): Use them. + +2011-06-07 Bruno Haible + + Rely more on libtool. + * Makefile.am (libgettextsrc_la_LDFLAGS): Use -no-undefined always. + Don't use @LTNOUNDEF@. + +2010-06-06 Bruno Haible + + Bug fixes in libgettextpo. + * msgl-check.h (check_message_list): Add ignore_untranslated_messages, + ignore_fuzzy_messages arguments. + * msgl-check.c (check_plural, check_message_list): Likewise. + * msgfmt.c (main): Update. + +2010-06-04 Bruno Haible + + * gettext-0.18.1 released. + +2010-06-03 Bruno Haible + + xgettext: Recognize language of files ending in .perl. + * x-perl.h (EXTENSIONS_PERL): Recognize .perl. + Suggested by Ævar Arnfjörð Bjarmason . + +2010-06-03 Bruno Haible + + urlget: Don't attempt to run the Java program if we don't install it. + * Makefile.am (USEJAVA): New variable. + (DEFS): Define USEJAVA. + * urlget.c (fetch): Don't try to use Java is USEJAVA is 0. + +2010-05-19 Bruno Haible + + Link with libunistring, if it exists. + * Makefile.am (LDADD, libgettextsrc_la_LDFLAGS): Add LTLIBUNISTRING. + +2010-05-18 Bruno Haible + + Correct program dependencies. + * Makefile.am (OTHERPROGDEPENDENCIES): New variable. + (recode_sr_latin_LDADD): Remove redundant variable. + (recode_sr_latin_DEPENDENCIES): Use OTHERPROGDEPENDENCIES. + (hostname_DEPENDENCIES, urlget_DEPENDENCIES): New variables. + +2010-05-09 Bruno Haible + + * gettext-0.18 released. + +2010-05-09 Bruno Haible + + * msgattrib.c (main): Update year in --version output. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + +2010-04-25 Bruno Haible + + Update support of object-pascal-format strings. + * format-pascal.c: Update description of format strings. + (enum format_arg_type): Remove FAT_INTEGER64. + (format_parse): Accept an empty digit sequence before ':'. Treat 'd', + 'u', 'x' the same way. + (main): Update. + +2010-04-05 Bruno Haible + + Interoperability with mono versions >= 2009-02-27. + * write-csharp.c (write_csharp_code): Emit a TableInitialized field. + Change ReadResources so that it may be called multiple times, even + concurrently. + Reported by Guido Flohr + +2010-03-31 Guido Flohr + + Improve how xgettext handles Perl syntax ambiguities. + * x-perl.c(enum token_type_ty): New enumeration items + token_type_number, token_type_object. + (struct token_ty): New field 'last_type'. + (token2string): Handle token_type_number, token_type_object. + (free_token): Likewise. + (prefer_division_over_regexp): Remove variable. + (extract_variable): Recognize token of type token_type_object. + (prefer_regexp_over_division): New function. + (last_token_type): Renamed from last_token. + (x_perl_prelex): Assign the token's last_type. Recognize token of type + token_type_number. Don't special-case "grep" and "split". Invoke + prefer_regexp_over_division for disambiguation. + (token_stack_dump): Handle token_type_number, token_type_object. + (x_perl_lex): Assign the token's last_type. Update last_token_type + intelligently. + (collect_message): Invoke prefer_regexp_over_division for + disambiguation. + (extract_balanced): Don't set last_token_type here. Handle + token_type_number, token_type_object. + (extract_perl): Initialize last_token_type here. + Reported by Guillem Jover via Santiago Vila. + + * x-perl.c (x_perl_prelex): Clarify interpolate_keywords arguments. + + * x-perl.c (eaten_here): Renamed from here_eaten. + (phase1_getc, get_here_document, skip_pod, extract_perl): Update. + +2010-03-13 Bruno Haible + + New options --color, --style for many programs. + * msgattrib.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msgcomm.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msgconv.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msgen.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msgfilter.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msggrep.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msginit.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msgmerge.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msgunfmt.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * msguniq.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + * xgettext.c: Include color.h. + (long_options): Add options --color, --style. + (main): Implement them. + (usage): Document them. + Reported by Kalle Olavi Niemitalo + via Santiago Vila . + +2010-02-20 Bruno Haible + + * plural-table.c (plural_table): Add Bulgarian. + Reported by Roumen Petrov . + +2010-02-20 Bruno Haible + + * plural-table.c (plural_table): Sort in the same order as + gettext.texi. + +2010-02-17 Bruno Haible + + Fix error in conversion of large PO file (> 4000 msgs) to Java class. + * write-java.c: Include minmax.h. + (write_java1_init_statements, write_java2_init_statements): New + functions, extracted from write_java_code. + (write_java_code): If there are many messages, split the initialization + code into several static methods. + Reported by sasha . + +2009-12-21 Bruno Haible + + * lang-table.c (language_table): Add Mapudungun, Lower Sorbian, + Upper Sorbian, Mohawk, Yakut, Southern Sami, Lule Sami, Inari Sami, + Skolt Sami. + * msginit.c (catalogname_for_locale): Add Mapudungun, Lower Sorbian, + Upper Sorbian, Yakut, Inari Sami, Skolt Sami. + +2009-12-12 Bruno Haible + + * *.h, *.c, *.y: Untabify. + +2009-11-28 Bruno Haible + + * x-perl.c (extract_variable): Fix tp->type when returning at EOF. + Reported by Guido Flohr . + +2009-11-15 Bruno Haible + + * x-python.c: Update comments regarding PEP 0263. + +2009-09-05 Bruno Haible + + * write-po.c (wrap): Remove unused initialization. + * x-glade.c (comment_handler): Likewise. + * xgettext.c (flag_context_list_table_insert): Add comment. + Found by clang's static analyzer. + +2009-08-30 Bruno Haible + + Accommodate the Solaris iconv_open function, which cannot convert + directly between many pairs of encodings. + * msgl-iconv.h (convert_string_directly): Renamed from convert_string. + * msgl-iconv.c: Include xstriconveh.h. + (convert_string_directly): Renamed from convert_string. + (convert_string): New function. + (convert_string_list, convert_prev_msgid, convert_msgid, + iconvable_string_list, iconvable_prev_msgid, iconvable_msgid): Change + argument type to 'const iconveh_t *'. + (convert_msgstr, iconvable_string, iconvable_msgstr): Likewise. Invoke + xmem_cd_iconveh instead of xmem_cd_iconv. + (iconv_message_list_internal, is_message_list_iconvable): Invoke + iconveh_open, iconveh_close instead of iconv_open, iconv_close. + * xgettext.c (from_current_source_encoding): Update. + +2009-08-16 Bruno Haible + + Reduce dependency on project-id heuristic. + * msginit.c (project_id): Add header argument. Return first part of + Project-Id-Version field if present. + (fill_header): Update. + +2009-08-13 Bruno Haible + + * msgexec.c: Include msgl-charset.h. + (main): Invoke compare_po_locale_charsets. + (process_string): Add comment. + * msgfilter.c (process_message): Likewise. + +2009-08-10 Bruno Haible + + Avoid a gcc warning. + * msgfilter.c (sub_argv): Change type to 'const char **'. + (main): Update. + +2009-08-10 Bruno Haible + + Use type 'ucs4_t' more often. Avoids gcc warnings on Cygwin. + * po-lex.c (struct mbchar): Change type of 'uc'. + (mb_width): Update. + * read-properties.c (read_escaped_string): Change type of local + variable 'uc'. + * read-stringtable.c (phase2_getc): Likewise. + * write-properties.c (conv_to_java, write_escaped_string): Change type + of local variables 'uc', 'uc1', 'uc2'. + * write-java.c (string_hashcode, write_java_string): Likewise. + * write-csharp.c (construct_class_name, write_csharp_string): Change + type of local variable 'uc'. + * write-tcl.c (write_tcl_string): Likewise. + * write-qt.c (conv_to_iso_8859_1, conv_to_utf16): Likewise. + * x-python.c (phase2_getc, mixed_string_buffer_append): Likewise. + (mixed_string_buffer_append_unicode): Change argument type to 'ucs4_t'. + * x-java.c (string_buffer_append): Change type of local variable 'uc'. + (string_buffer_append_unicode): Change argument type to 'ucs4_t'. + * x-csharp.c (phase2_getc): Change type of local variable 'uc'. + +2009-08-04 Bruno Haible + + Port msgfilter to Woe32 platforms. + * msgfilter.c: Don't include errno.h, fcntl.h, io.h, sys/select.h, + pipe.h, wait-process.h. Include pipe-filter.h instead. + (HAVE_SELECT): Remove undefine. + (nonintr_close, nonintr_read, nonintr_write, nonintr_select): Remove + functions. + (O_NONBLOCK): Remove fallback. + (IS_EAGAIN): Remove macro. + (struct locals): New type. + (prepare_write, done_write, prepare_read, done_read): New functions. + (generic_filter): Implement using pipe_filter_ii_execute. + +2009-07-27 Часлав Илић (Chusslove Illich) + + Extend recode-sr-latin to also transform letters with accents. + * filter-sr-latin.c (table): Add entries for letters with accents. + (IS_UPPERCASE_CYRILLIC): Recognize also U+04E2, U+04EE. + (serbian_to_latin): Update. + +2009-06-06 Bruno Haible + + * x-perl.c (x_perl_prelex): Recognize the perl 5.10 operator '//'. + Reported by Kevin Ryde . + +2009-05-29 Bruno Haible + + Improve msgfmt error message about format directive mismatches + in msgstr[i]. + * format.h (struct formatstring_parser): Add pretty_msgid argument to + 'check' member. + * format-awk.c (format_check): Add pretty_msgid argument. + * format-boost.c (format_check): Likewise. + * format-c.c (format_check): Likewise. + * format-csharp.c (format_check): Likewise. + * format-elisp.c (format_check): Likewise. + * format-gcc-internal.c (format_check): Likewise. + * format-gfc-internal.c (format_check): Likewise. + * format-java.c (format_check): Likewise. + * format-kde.c (format_check): Likewise. + * format-librep.c (format_check): Likewise. + * format-lisp.c (format_check): Likewise. + * format-pascal.c (format_check): Likewise. + * format-perl-brace.c (format_check): Likewise. + * format-perl.c (format_check): Likewise. + * format-php.c (format_check): Likewise. + * format-python.c (format_check): Likewise. + * format-qt.c (format_check): Likewise. + * format-qt-plural.c (format_check): Likewise. + * format-scheme.c (format_check): Likewise. + * format-sh.c (format_check): Likewise. + * format-tcl.c (format_check): Likewise. + * format-ycp.c (format_check): Likewise. + * format.c (check_msgid_msgstr_format_i): Pass either "msgid" or + "msgid_plural" as pretty_msgid argument. + +2009-05-23 Bruno Haible + + * Makefile.am (libgettextsrc_la_LINK, msgattrib_LINK, msgcat_LINK, + msgcomm_LINK, msgconv_LINK, msgen_LINK, msgfilter_LINK, msggrep_LINK, + msgmerge_LINK, msguniq_LINK, xgettext_LINK): Add $(AM_V_lt) flag. + +2009-05-16 Bruno Haible + + Extract comments meant for translators from Glade input files. + * xgettext.h (remember_a_message): Add 'extracted_comment' argument. + * xgettext.c (remember_a_message): Likewise. + (arglist_parser_done): Update. + * x-glade.c (struct element_state): Add field 'extracted_comment'. + (start_element_handler): Fill it. + (end_element_handler): Pass it to 'remember_a_message', free it. + * x-awk.c (extract_parenthesized): Update. + * x-c.c (extract_parenthesized): Update. + * x-csharp.c (extract_parenthesized): Update. + * x-elisp.c (read_object): Update. + * x-java.c (extract_parenthesized): Update. + * x-librep.c (read_object): Update. + * x-lisp.c (read_object): Update. + * x-perl.c (extract_variable, interpolate_keywords, extract_balanced): + Update. + * x-php.c (extract_balanced): Update. + * x-python.c (extract_balanced): Update. + * x-rst.c (extract_rst): Update. + * x-scheme.c (read_object): Update. + * x-sh.c (read_word, read_command): Update. + * x-smalltalk.c (extract_smalltalk): Update. + * x-tcl.c (read_command): Update. + * x-ycp.c (extract_parenthesized): Update. + Reported by + at . + +2009-03-29 Bruno Haible + + * message.h (format_type): New enum value 'format_gfc_internal'. + (NFORMATS): Increment. + * message.c (format_language): Add format_gfc_internal entry. + (format_language_pretty): Likewise. + * format.h (formatstring_gfc_internal): New declaration. + * format-gfc-internal.c: New file. + * format.c (formatstring_parsers): Add formatstring_gfc_internal. + * x-c.h (SCANNERS_C): In language GCC-source, also keep track of + gfc-internal-format format strings. + * x-c.c (init_flag_table_gcc_internal): Also register flags for + gfc-internal-format. + * xgettext.c (xgettext_record_flag): Store gfc-internal-format flags in + flag_table_gcc_internal. + * Makefile.am (FORMAT_SOURCE): Add format-gfc-internal.c. + * FILES: Update. + Reported by Göran Uddeborg . + +2009-01-27 Bruno Haible + + * message.h (format_type): New enum value 'format_qt_plural'. + (NFORMATS): Increment. + * message.c (format_language): Add format_qt_plural entry. + (format_language_pretty): Likewise. + * format.h (formatstring_qt_plural): New declaration. + * format-qt-plural.c: New file. + * format.c (formatstring_parsers): Add formatstring_qt_plural. + * xgettext.h (struct flag_context_ty): Add fields is_format3, + pass_format3. + * xgettext.c (struct extractor_ty): Add field formatstring_parser3. + (inherited_context, flag_context_list_table_insert): Handle the new + flag_context_ty fields. + (xgettext_record_flag): Handle format_qt_plural. + (current_formatstring_parser3): New variable. + (extract_from_file): Initialize it. + (set_format_flags_from_context): Handle the new flag_context_ty fields. + (remember_a_message, remember_a_message_plural): Handle + current_formatstring_parser3. Avoid adding a c-format flag to a message + already flagged as qt-plural-format. + (arglist_parser_remember): Allow argnum1 and argnum2 in the call shape + to be the same. + (arglist_parser_done): Add special recognition of qt-plural-format + strings. + (language_to_extractor): Set the formatstring_parser3 in the result. + * Makefile.am (FORMAT_SOURCE): Add format-qt-plural.c. + * FILES: Update. + +2009-01-27 Bruno Haible + + * plural-table.c (plural_table): Put Turkish under nplurals=2. + Reported by Sertaç Ö. Yıldız . + +2009-01-26 Bruno Haible + + * msgattrib.c (main): Update year in --version output. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + * urlget.c (main): Likewise. + +2009-01-25 Bruno Haible + + Fix bug introduced on 2005-10-01. + * msgl-charset.c (compare_po_locale_charsets): Fix recognition of + header entry. + +2009-01-25 Bruno Haible + + Fix bug introduced on 2008-10-04. + * read-catalog-abstract.c (po_parse_comment_special): Initialize + *rangep. + Reported by Ralf Wildenhues . + +2009-01-18 Bruno Haible + + * msgfmt.c: Include str-list.h. + (main): When --statistics and --verbose are both specified, output the + input file name in front of the statistics line. + Suggested by Vincent Lefevre . + +2009-01-18 Bruno Haible + + Allow multiple levels of verbosity in 'msgfmt'. + * msgfmt.h (verbose): Change from 'bool' to 'int'. + * msgfmt.c (verbose): Likewise. + (main): Increment 'verbose' for each --verbose option. + * write-java.c (msgdomain_write_java): Update. + * write-csharp.c (msgdomain_write_csharp): Update. + * write-resources.c (msgdomain_write_csharp_resources): Update. + +2009-01-18 Bruno Haible + + * str-list.h (string_list_join): Change the separator argument, + allowing an entire separator string. + * str-list.c (string_list_join): Likewise. + +2009-01-18 Bruno Haible + + Fix a '(null)' in an error message. Bug present since gettext-0.16. + * read-catalog.h (DEFAULT_CATALOG_READER_TY): Add a file_name field. + * read-catalog.c (read_catalog_stream): Initialize it. + * x-po.c (extract): Likewise. + * msgfmt.c (read_catalog_file_msgfmt): Likewise. + (msgfmt_parse_debrief): Use the file_name field from the catalog reader + instead of gram_pos.file_name. + +2009-01-17 Bruno Haible + + Update after gnulib changed. + * Makefile.am (RELOCATABLE_STRIP): New variable. + +2008-12-07 Bruno Haible + + * write-po.c (message_print_comment_filepos): Use a 'const' pointer + where possible. + * write-stringtable.c (write_message): Likewise. + +2008-12-07 Bruno Haible + + Avoid gcc -Wredundant-decls warnings. + * x-awk.c: Don't include the specification header file twice. + * x-c.c: Likewise. + * x-csharp.c: Likewise. + * x-elisp.c: Likewise. + * x-glade.c: Likewise. + * x-java.c: Likewise. + * x-librep.c: Likewise. + * x-lisp.c: Likewise. + * x-perl.c: Likewise. + * x-php.c: Likewise. + * x-po.c: Likewise. + * x-python.c: Likewise. + * x-rst.c: Likewise. + * x-scheme.c: Likewise. + * x-sh.c: Likewise. + * x-smalltalk.c: Likewise. + * x-tcl.c: Likewise. + * x-ycp.c: Likewise. + +2008-11-14 Bruno Haible + + * msgfilter.c: Include xvasprintf.h, xsetenv.h. + (process_message): Set the environment variables MSGFILTER_MSGCTXT, + MSGFILTER_MSGID, MSGFILTER_LOCATION. + +2008-10-28 Bruno Haible + + * msgmerge.c (match_domain): Remove space between '#' and 'pragma' for + OpenMP. + Reported by Lamarque Eric . + +2008-10-04 Bruno Haible + + * plural-distrib.h (struct plural_distribution): New field 'histogram'. + * format.h (check_msgid_msgstr_format_i, check_msgid_msgstr_format): + Add range argument. + * format.c (check_msgid_msgstr_format_i): Add range argument. Set + strict_checking to false if, due to the range, the plural forms applies + to only one value of n. + (check_msgid_msgstr_format): Add range argument. + * msgl-check.c (plural_expression_histogram): New function. + (check_plural_eval, check_plural): Update. + (check_pair): Pass the range to check_msgid_msgstr_format. + (check_message_list): Update. + * msgmerge.c (message_merge): Pass the range to + check_msgid_msgstr_format_i. + Reported by Anatoly Techtonik + via . + +2008-10-04 Bruno Haible + + * message.h (struct argument_range): New type. + (has_range_p): New macro. + (struct message_ty): Add field 'range'. + * message.c (message_alloc): Initialize the 'range' field. + (message_copy): Copy the 'range' field. + * read-catalog-abstract.h (po_parse_comment_special): Add 'rangep' + argument. + * read-catalog-abstract.c: Include . + (po_parse_comment_special): Add 'rangep' argument. Parse the range + description syntax. + * read-catalog.h (DEFAULT_CATALOG_READER_TY): Add 'range' field. + * read-catalog.c (default_constructor): Initialize the 'range' field. + (default_copy_comment_state): Copy the 'range' field into the new + message. + (default_reset_comment_state): Clear the 'range' field. + (default_comment_special): Update. + * write-po.h (make_range_description_string): New declaration. + * write-po.c (make_range_description_string): New function. + (message_print_comment_flags): Also print the range. + * write-stringtable.c (write_message): Likewise. + * msgl-cat.c: Include . + (catenate_msgdomain_list): Fill in the range of the resulting messages. + * msgl-equal.c (message_equal): Compare also the ranges. + * msgmerge.c (message_merge): Fill in the range of the resulting + message. Set it fuzzy if a range was introduced or extended. + * xgettext.c (remember_a_message): Set the range of the new message. + +2008-10-03 Bruno Haible + + * plural-distrib.h: New file. + * format.h: Include plural-distrib.h. + (check_msgid_msgstr_format_i, check_msgid_msgstr_format): Replace + plural_distribution, plural_distribution_length arguments with a single + distribution argument. + * format.c (check_msgid_msgstr_format_i, check_msgid_msgstr_format): + Likewise. + * msgl-check.h: Include plural-distrib.h. + (check_plural_eval, check_message): Replace plural_distribution, + plural_distribution_length arguments with a single distribution + argument. + * msgl-check.c (check_plural_eval): Likewise. Free array in case of + error. + (check_plural): Replace plural_distribution, plural_distribution_length + arguments with a single distribution argument. Don't store a + distribution result when there are errors. + (check_pair, check_message): Replace plural_distribution, + plural_distribution_length arguments with a single distribution + argument. + (check_message_list): Update. + * msgmerge.c (message_merge: Replace plural_distribution, + plural_distribution_length arguments with a single distribution + argument. + (match_domain): Update. + * Makefile.am (noinst_HEADERS): Add plural-distrib.h. + * FILES: Mention plural-distrib.h. + +2008-09-28 Bruno Haible + + * write-catalog.c (msgdomain_list_print): Open the output stream in + binary mode, for consistency with the term_styled_ostream or fd_stream + based case. + +2008-09-28 Bruno Haible + + * Makefile.am (msg*_DEPENDENCIES, xgettext_DEPENDENCIES, + recode_sr_latin_DEPENDENCIES): Add $(WOE32_LDADD). + +2008-09-28 Bruno Haible + + * msgexec.c (process_string): Don't die from SIGPIPE if the subprocess + does not want our input. + Reported by Rainer Tammer . + +2008-09-28 Bruno Haible + + * Makefile.am (msgcmp_LDADD): Add MSGMERGE_LIBM. + Reported by Rainer Tammer . + +2008-09-27 Bruno Haible + + * lang-table.c (language_table): Add Asturian, Crimean Tatar, Friulian, + Papiamento. + * msginit.c (catalogname_for_locale): Add Asturian, Friulian, + Papiamento. + +2008-09-26 Bruno Haible + + * write-catalog.c (cmp_by_msgid, cmp_by_filepos): Compare the msgctxt + fields if the msgid fields are the same. + Reported by Rainer Tammer . + +2008-09-15 Bruno Haible + + * Makefile.am (msg*_DEPENDENCIES, xgettext_DEPENDENCIES, + recode_sr_latin_DEPENDENCIES): New variables. + +2008-09-15 Bruno Haible + + * msgl-fsearch.h: Include stdbool.h. + (message_fuzzy_index_search): Add 'heuristic' argument. + * msgl-fsearch.c (message_fuzzy_index_search): Likewise. If !heuristic, + consider all messages. + * msgmerge.c (struct definitions_ty): Add fields 'curr_findex', + 'curr_findex_init_lock'. + (definitions_init): Initialize them. + (definitions_set_current_list): Clear the previous curr_findex. + (definitions_init_curr_findex): New function. + (definitions_search_fuzzy): Do fuzzy matching by calling + message_fuzzy_index_search on a hashed index, rather than through + message_list_search_fuzzy. + (definitions_destroy): Update. + (merge): Determine the definitions' canonical encoding. + * msgcmp.c: Include xmalloca.h, po-charset.h, msgl-fsearch.h. + (match_domain): Add defmlp_findex, def_canon_charset arguments. Do + fuzzy matching by calling message_fuzzy_index_search on a hashed + index, rather than through message_list_search_fuzzy. + (compare): Determine the definitions' canonical encoding. Prepare room + for a lazily allocated hashed index. + * Makefile.am (msgcmp_SOURCES): Add msgl-fsearch.c. + +2008-09-15 Bruno Haible + + * msgcmp.c (use_fuzzy_matching): New variable. + (long_options): Add option -N/--no-fuzzy-matching. + (main, match_domain): Implement it. + (usage): Document it. + +2008-09-14 Bruno Haible + + * msgl-fsearch.h (message_fuzzy_index_search): Add 'lower_bound' + argument. + * msgl-fsearch.c (message_fuzzy_index_search): Likewise. + * msgmerge.c (definitions_search_fuzzy): Use the result of the fuzzy + search in the current list as a lower bound for the fuzzy search in the + compendiums. + +2008-09-14 Bruno Haible + + * message.h (message_list_search_fuzzy, FUZZY_THRESHOLD): Clarify + documentation. + + * msgmerge.c (struct definitions_ty): Rename field 'findex' to + 'comp_findex', and 'findex_init_lock' to 'comp_findex_init_lock'. + (definitions_init): Update. + (definitions_init_comp_findex): Renamed from definitions_init_findex. + Update. + (definitions_search_fuzzy, definitions_destroy): Update. + + * msgmerge.c (definitions_current_list, definitions_set_current_list): + Move functions. + +2008-09-14 Ralf Wildenhues + Bruno Haible + + * message.h (fuzzy_search_goal_function): Add 'lower_bound' argument. + * message.c (fuzzy_search_goal_function): Likewise. Use fstrcmp_bounded + instead of fstrcmp. + (message_list_search_fuzzy_inner): Pass fuzzy_search_goal_function the + best weight known so far, to shortcut computations. + * msgl-fsearch.c (message_fuzzy_index_search): Likewise. + * msgmerge.c (definitions_search_fuzzy): Update + fuzzy_search_goal_function calls. + +2008-09-14 Ralf Wildenhues + Bruno Haible + + * xgettext.c: Include concat-filename.h. + (main, xgettext_open): Use xconcatenated_filename instead of + concatenated_filename. + +2008-09-01 Bruno Haible + + * color.c: Include concat-filename.h. + (style_file_lookup, style_file_prepare): Use xconcatenated_filename + instead of concatenated_filename. + * msginit.c: Include concat-filename.h instead of filename.h. + (project_id, project_id_version): Use xconcatenated_filename instead of + concatenated_filename. + * open-catalog.c: Include concat-filename.h. + (try_open_catalog_file): Use xconcatenated_filename instead of + concatenated_filename. + * read-csharp.c: Include concat-filename.h instead of filename.h. + (msgdomain_read_csharp): Use xconcatenated_filename instead of + concatenated_filename. + * read-resources.c: Include concat-filename.h instead of filename.h. + (read_resources_file): Use xconcatenated_filename instead of + concatenated_filename. + * read-tcl.c: Include concat-filename.h instead of filename.h. + (msgdomain_read_tcl): Use xconcatenated_filename instead of + concatenated_filename. + * write-resources.c: Include concat-filename.h instead of filename.h. + (msgdomain_write_csharp_resources): Use xconcatenated_filename instead of + concatenated_filename. + * write-csharp.c: Include concat-filename.h instead of filename.h. + (msgdomain_write_csharp): Use xconcatenated_filename instead of + concatenated_filename. + * write-java.c: Include concat-filename.h instead of filename.h. + (msgdomain_write_java): Use xconcatenated_filename instead of + concatenated_filename. + * write-tcl.c: Include concat-filename.h instead of filename.h. + (msgdomain_write_tcl: Use xconcatenated_filename instead of + concatenated_filename. + +2008-08-31 Bruno Haible + + * msgexec.c: Include . + (main): Block SIGPIPE for the duration of the processing. + +2008-08-23 Bruno Haible + + Fix behaviour of "msgmerge --update" when sorting is requested and + obsolete messages are present. + * write-catalog.h (struct catalog_output_format): New field + 'sorts_obsoletes_to_end'. + * write-po.c (output_format_po): Initialize it to true. + * write-properties.c (output_format_properties): Initialize it to false. + * write-stringtable.c (output_format_stringtable): Likewise. + * msgmerge.c (msgdomain_list_stablesort_by_obsolete): New function. + (main): Before testing whether the result is the same as the old + contents, sort the result using msgdomain_list_stablesort_by_obsolete. + Reported by Vincent Danjean + via . + +2008-08-16 Bruno Haible + + * x-python.c (enum token_type_ty): New values token_type_lbracket, + token_type_rbracket. + (phase5_get): Recognize also token_type_lbracket, token_type_rbracket. + (extract_balanced): Renamed from extract_parenthesized. Add 'delim' + argument. Handle token_type_lbracket and token_type_rbracket. + (extract_python): Update. + Reported by Claude Paroz + via . + +2008-08-16 Bruno Haible + + * x-php.c (extract_balanced): Fix small bug in 2007-03-17 commit. + +2008-08-15 Bruno Haible + + * format-python.c (format_parse): For %.0s and %.0r, set the type to + FORMAT_ANY. + (format_check): When strict equality is not desired, compare FORMAT_ANY + as matching any type. + Reported by Alexander Dupuy in + . + +2008-08-14 Bruno Haible + + * format-python.c (format_parse): For '%', set the type to FAT_NONE. + +2008-08-14 Bruno Haible + + * format-python.c (format_check): Require the same number of unnamed + arguments also when !equality. + Reported by Alexander Dupuy in + . + +2008-08-14 Bruno Haible + + * msgmerge.c: Include "glthread/lock.h" instead of "lock.h". + +2008-08-03 Bruno Haible + + * x-python.c (mixed_string_buffer_append): Replace a lone high + surrogate with U+FFFD. + Reported by Yann + via Santiago Vila . + +2008-07-19 Bruno Haible + + * gnu/gettext/GetURL.java: Don't output anything to standard error. + Instead, set exit code to indicate failure reason. + * urlget.c (verbose): New variable. + (long_options): Add --quiet, --silent option. + (main): Implement --quiet, --silent option. + (usage): Document --quiet, --silent option. + (java_exitcode): New variable. + (execute_it): Set it. Return false also when the exit code is 2. + (fetch): Implement verbosity to standard error here. + Reported by 宋浩 . + +2008-06-10 Bruno Haible + + * msgexec.c (process_string): Update for changed signature of + wait_subprocess(). + * msgfilter.c (generic_filter): Likewise. + * msginit.c (project_id, project_id_version, get_user_email, + language_team_address): Likewise. + * read-csharp.c (execute_and_read_po_output): Likewise. + * read-java.c (execute_and_read_po_output): Likewise. + * read-resources.c (execute_and_read_po_output): Likewise. + * read-tcl.c (msgdomain_read_tcl): Likewise. + * write-resources.c (execute_writing_input): Likewise. + * urlget.c (execute_it, fetch): Update for changed signature of + execute(). + +2008-05-26 Bruno Haible + + * msginit.c (get_field, put_field): Recognize a field also if there is + no space after the ':'. + Reported by Nacho . + +2008-05-16 Bruno Haible + + * msgl-iconv.c (iconvable_prev_msgid): Fix typo. + Reported by Karl Eichwalder + via Philipp Thomas + at . + +2008-05-10 Bruno Haible + + * write-po.c: Include unilbrk.h instead of linebreak.h. + (wrap): Update. + * po-lex.c: Don't include linebreak.h. + +2008-04-25 Bruno Haible + + * lang-table.c (language_table): Add Beja. Remove Adangme, Banda, + Batak. Update Scottish Gaelic, Central Khmer, Romansh. + * msginit.c (catalogname_for_locale: Add Beja. Remove Banda, Batak. + Update Scottish Gaelic, Central Khmer, Romansh. + +2008-04-20 Bruno Haible + + Improve error message. + * xgettext.h (lexical_context_ty): New type. + (non_ascii_error_message): New declaration. + (from_current_source_encoding): Add lcontext argument. + * xgettext.c (non_ascii_error_message): New function. + (from_current_source_encoding): Add lcontext argument. Use + non_ascii_error_message. + (CONVERT_STRING): Add lcontext argument. + (remember_a_message, remember_a_message_plural): Update. + * x-csharp.c (lexical_context): New variable. + (phase2_getc): Use non_ascii_error_message. + (comment_start, comment_line_end, phase6_get): Set lexical_context. + (extract_csharp): Initialize lexical_context. + * x-java.c (struct string_buffer): Add lcontext field. + (init_string_buffer): Add lcontext argument. + (string_buffer_flush_curr_buffer): Update from_current_source_encoding + call. + (comment_start): Set lcontext. + (phase5_get): Pass lcontext argument. + * x-perl.c (get_here_document, phase2_getc, + extract_quotelike_pass1_utf8): Pass lcontext argument. + * x-python.c (lexical_context): New variable. + (phase2_getc): Use non_ascii_error_message. + (comment_start, comment_line_end, phase6_get): Set lexical_context. + (struct mixed_string_buffer): Add lcontext field. + (init_mixed_string_buffer): Add lcontext argument. + (mixed_string_buffer_flush_curr_buffer): Update + from_current_source_encoding call. + (phase5_get): Set lexical_context. Pass lcontext argument. + (extract_python): Initialize lexical_context. + Reported by Mark Weyer. + +2008-04-20 Bruno Haible + + * xgettext.c (usage): Clarify single-letter options that take an + optional argument: -c, -k, -m, -M. + +2008-04-16 Bruno Haible + + * msginit.c (catalogname_for_locale): Add entries for Maori, Uighur. + +2008-02-20 Jakub Jelinek + Bruno Haible + + * format-gcc-internal.c: Update for GCC 4.3. + (FAT_TREE_STATEMENT): New enum item. + (format_parse): Recognize %K. + (format_print): Update. + +2008-02-04 Bruno Haible + + * msgmerge.c (match_domain): Remove the prev_msgid fields also from + the untranslated messages. + Reported by Yukiko Bando via + Chusslove Illich (Часлав Илић) . + +2008-02-04 Bruno Haible + + * msgmerge.c (message_merge): Store the prev_msgid field also if the + resulting message is not fuzzy: it might be marked as fuzzy later. + (match_domain): Remove the prev_msgid fields from the non-fuzzy + messages. + +2008-01-29 Bruno Haible + + * write-po.c (wrap): Change the severity of the "should not contain + escape sequence" message from PO_SEVERITY_ERROR to PO_SEVERITY_WARNING. + Reported by Kerb . + +2008-01-13 Bruno Haible + + * Makefile.am (noinst_headers): Add msgl-header.h. + +2007-12-24 Bruno Haible + + Introduce 'Language' header field. + * xgettext.c (construct_header): Add an empty 'Language' field. + * msginit.c (language_value): New function. + (fields): Add 'Language'. + * msgmerge.c: Include lang-table.h. + (catalogname): New variable. + (long_options): Add --lang option. + (main): Handle --lang option. + (usage): Document --lang option. + (message_merge): Set the 'Language' field if --lang was specified or + if it is missing and can be inferred from the 'Language-Team' field. + * msgl-header.h: New file. + * msgl-header.c: New file. + * msgcat.c: Include msgl-header.h. + (long_options): Add --lang option. + (main): Handle --lang option. + (usage): Document --lang option. + * msgen.c: Include msgl-header.h. + (long_options): Add --lang option. + (main): Handle --lang option. + (usage): Document --lang option. + * msgl-check.c (plural_help): Look at the 'Language' field before + looking at the 'Language-Team' field. + (check_header_entry): Fix comparison with default value. When the + 'Language' field is missing, signal a warning. + * Makefile.am (libgettextsrc_la_SOURCES): Add msgl-header.c. + (msgmerge_SOURCES): Add lang-table.c. + * FILES: Update. + +2007-12-22 Bruno Haible + + * write-po.c (wrap): Avoid breaking line in the middle of a format + directive. + Reported by Dwayne Bailey . + +2007-11-07 Jim Meyering + Bruno Haible + + * write-catalog.c (msgdomain_list_print): Fix open() call. + +2007-11-07 Bruno Haible + + * gettext-0.17 released. + +2007-10-28 Bruno Haible + + * color.c (style_file_lookup): New function. + (style_file_prepare): Use it. + +2007-10-21 Bruno Haible + + Normalize the leading space of every comment line during input, not + during output. + * read-catalog-abstract.c (po_callback_comment_dispatcher): Before + calling po_callback_comment or po_callback_comment_dot, drop the + leading space. + * write-po.c (message_print_comment, message_print_comment_dot): Don't + remove the leading space of every comment line. + * write-stringtable.c (write_message): Likewise. + Suggested by Dwayne Bailey . + +2007-10-20 Bruno Haible + + * msgmerge.c (message_merge): Set the fuzzy flag if the msgid_plural + changed. + Suggested by Chusslove Illich (Часлав Илић) . + +2007-10-20 Bruno Haible + + Make msgmerge's introduction of fuzzy markers more consistent with + msgfmt's --check-format. + * format.h (check_msgid_msgstr_format_i): New declaration. + * format.c (check_msgid_msgstr_format_i): New function, extracted from + check_msgid_msgstr_format. + (check_msgid_msgstr_format): Use it. + * msgl-check.h: Include plural-eval.h. + (check_plural_eval): New declaration. + * msgl-check.c (check_plural_eval): Add const to first parameter. Make + non-static. + (check_plural): Update. + * msgmerge.c: Include plural-exp.h, msgl-check.h, po-xerror.h. + (msgfmt_check_pair_fails): Remove function. + (silent_error_logger, silent_xerror): New functions. + (message_merge): Add plural_distribution, plural_distribution_length + arguments. Call check_msgid_msgstr_format_i instead of + msgfmt_check_pair_fails. + (match_domain): Extract not only the plural count, but also the + plural expression from the header entry. Determine the plural + distribution from it. Pass it to message_merge. + Reported by Chusslove Illich (Часлав Илић) . + +2007-10-20 Bruno Haible + + * msgl-check.c (check_plural): If there is no header entry, or if the + header entry does not contain plural= and nplurals=, return a plural + distribution corresponding to the Germanic plural. + +2007-10-20 Bruno Haible + + * format.h (check_msgid_msgstr_format): Add plural_distribution_length + argument. + * format.c (check_msgid_msgstr_format): Likewise. + * msgl-check.h (check_message): Likewise. + * msgl-check.c (check_plural_eval, check_plural, check_pair, + check_message): Likewise. + (check_message_list): Update. + +2007-10-18 Bruno Haible + + * plural-count.c (get_plural_count): Adapt to changed prototype of + extract_plural_expression. + * write-csharp.c (write_csharp_expression): Add const to parameter. + (write_csharp_code): Adapt to changed prototype of + extract_plural_expression. + * write-java.c (write_java_expression): Add const to parameter. + (write_java_code): Adapt to changed prototype of + extract_plural_expression. + +2007-10-07 Bruno Haible + + * hostname.c (main): In the --version output, say GPLv3+. + * msgattrib.c (main): Likewise. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * recode-sr-latin.c (main): Likewise. + * urlget.c (main): Likewise. + * xgettext.c (main): Likewise. + +2007-09-30 Bruno Haible + + * xgettext.c (package_name, package_version): New variables. + (long_options): Add --package-name, --package-version. + (main): Handle options --package-name, --package-version. + (usage): Document them. + (construct_header): Use package_name if present. + * msginit.c (project_id_version): Take a header argument. Return the + old value if already present. + (fields): Update. + +2007-09-30 Bruno Haible + + * message.h (format_type): New enum value 'format_kde'. + (NFORMATS): Increment. + * message.c (format_language): Add format_kde entry. + (format_language_pretty): Likewise. + * format.h (formatstring_kde): New declaration. + * format-kde.c: New file. + * format.c (formatstring_parsers): Add formatstring_kde. + * x-c.c (init_flag_table_c): Also register flags for qt-format and + kde-format. + * xgettext.c (flag_table_cxx_kde): New variable. + (recognize_format_kde): New variable. + (main): Handle --kde option. + (usage): Document --kde option. + (xgettext_record_flag): Also fill flag_table_cxx_kde. + (remember_a_message, remember_a_message_plural): In the heuristics, + don't mark a string as c-format that is already known to be a + kde-format. + (language_to_extractor): Use a flag_table_cxx_kde that is different + from flag_table_c. + * Makefile.am (FORMAT_SOURCE): Add format-kde.c. + * FILES: Update. + +2007-09-09 Bruno Haible + + Add support for Qt 4 format strings. + * format-qt.c (struct spec): Increase args_used field size to 100. + Add 'simple' field. + (format_parse): Update for Qt 4 syntax. Remove error message when the + same format argument is used more than once. + (format_check): Add check: If the msgid is simple, the msgstr must be + simple as well. + Reported by Chusslove Illich . + +2007-09-02 Bruno Haible + + Correct handling of different libexpat ABIs. + * x-glade.c: Include . + (p_XML_GetCurrentLineNumber, p_XML_GetCurrentColumnNumber): New + variables. + (is_XML_LARGE_SIZE_ABI, GetCurrentLineNumber, GetCurrentColumnNumber): + New functions. + (XML_GetCurrentLineNumber, XML_GetCurrentColumnNumber): Redefine. + (XML_Expat_Version, XML_FeatureEnum, XML_Feature): New types. + (p_XML_ExpatVersionInfo, p_XML_GetFeatureList): New variables. + (p_XML_GetCurrentLineNumber, p_XML_GetCurrentColumnNumber): Change + type to 'void *'. + (XML_ExpatVersionInfo, XML_GetFeatureList): New macros. + (XML_Size_ABI): New type. + (get_XML_Size_ABI): New function. + (XML_GetCurrentLineNumber, XML_GetCurrentColumnNumber): New functions. + (load_libexpat): Try both version 2 and version 1. Initialize + p_XML_ExpatVersionInfo, p_XML_GetFeatureList. + +2007-09-02 Bruno Haible + + Implement msgctxt for C# ResourceManagers. + * x-csharp.c (init_keywords): Also register GetParticularString and + GetParticularPluralString. + (init_flag_table_csharp): Update accordingly. + * write-csharp.c: Include xmalloca.h. + (write_csharp_msgid): New function. + (write_csharp_code): Use it instead of write_csharp_string. + (msgdomain_write_csharp): Remove error message if mlp has entries with + context. + * msgunfmt.cs (DumpResource.DumpMessage): Emit an msgctxt line + if the message contain the context separator. + +2007-09-01 Bruno Haible + + Implement msgctxt for Java ResourceBundles. + * x-java.c (init_keywords): Also register pgettext and npgettext. + (init_flag_table_java): Update accordingly. + * write-java.c (msgid_hashcode): New function. + (compute_hashsize, compute_table_items): Use it instead of + string_hashcode. + (write_java_msgid): New function. + (write_java_code): Use it instead of write_java_string. + (msgdomain_write_java): Remove error message if mlp has entries with + context. + * gnu/gettext/DumpResource.java (dumpMessage): Emit an msgctxt line + if the message contain the context separator. + Suggested by Felix Berger. + +2007-09-01 Bruno Haible + + * po-lex.c: Include uniwidth.h. + +2007-06-19 Bruno Haible + + * x-c.c (phase8_get): Call free_token, so that the reference to + tmp.comment gets dropped. + +2007-08-27 Bruno Haible + + * x-python.c (phase7_getuc): Interpret octal and hexadecimal escapes + as Unicode code points inside Unicode strings. + Reported and patch by Jakub Wilk . + +2007-08-23 Bruno Haible + + * file-list.c: Don't include getline.h. + * format-awk.c: Likewise. + * format-boost.c: Likewise. + * format-c.c: Likewise. + * format-csharp.c: Likewise. + * format-elisp.c: Likewise. + * format-gcc-internal.c: Likewise. + * format-java.c: Likewise. + * format-librep.c: Likewise. + * format-lisp.c: Likewise. + * format-pascal.c: Likewise. + * format-perl-brace.c: Likewise. + * format-perl.c: Likewise. + * format-php.c: Likewise. + * format-python.c: Likewise. + * format-qt.c: Likewise. + * format-scheme.c: Likewise. + * format-sh.c: Likewise. + * format-tcl.c: Likewise. + * format-ycp.c: Likewise. + * msginit.c: Likewise. + * x-perl.c: Likewise. + +2007-08-18 Bruno Haible + + * write-csharp.c (msgdomain_write_csharp): Don't recommend to use + --verbose if it is already in use. + * write-java.c (msgdomain_write_java): Likewise. + Reported by Karsten Kousgaard . + +2007-07-07 Bruno Haible + + * Makefile.am (AM_CPPFLAGS): Remove reference to libuniname directory. + (LIBUNINAME): Remove variable. + (xgettext_LDADD): Update. + +2007-07-04 Bruno Haible + + Recognize the PHP string concatenation operator. + * x-php.c (enum token_type_ty): New elements token_type_dot, + token_type_operator1, token_type_operator2. + (struct token_ty): Add comment field. + (free_token): Drop reference to comment field. + (phase4_pushback, phase4_pushback_length): New variables. + (phase4_get): Renamed from x_php_lex. Return last pushed-back token if + available. Recognize tokens '.', '+', '-', '*', '/', '%', '++', '--', + '!', '~', '@'. Fill in tp->comment. + (phase4_unget): New function. + (phase5_last): New variable. + (x_php_lex): New function. + (extract_balanced): Handle the new token types. Pass token's comment + to remember_a_message. + (extract_php): Initialize phase5_last. + Reported by Jan Engelhardt . + +2007-06-30 Bruno Haible + + * hostname.c (main): Use the standard --version output, see + . + * msgattrib.c (main): Likewise. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * recode-sr-latin.c (main): Likewise. + * urlget.c (main): Likewise. + * xgettext.c (main): Likewise. + +2007-06-30 Bruno Haible + + * lang-table.c (language_table): Fix entry for Javanese. + +2007-06-28 Bruno Haible + + * format-gcc-internal.c (format_parse): Tweak an error message. + Reported by Karl Eichwalder. + +2007-06-28 Bruno Haible + + * lang-table.h (language_variant_table, language_variant_table_size): + New declarations. + * lang-table.c (language_variant_table, language_variant_table_size): + New variables. + * msginit.c (language_team_englishname): New function. + (language_team): Use it instead of englishname_of_language. + Reported by Li Daobing . + +2007-06-25 Bruno Haible + + Change boundary of extracted comments. + * x-perl.c (struct token_ty): Add comment field. + (free_token): Release tp->comment. + (extract_quotelike): Fill in tp->comment. + (x_perl_prelex): Set or release tp->comment when constructing tokens. + (x_perl_lex): Likewise. + (extract_balanced): Pass token's comment to remember_a_message. + +2007-06-18 Bruno Haible + + Change boundary of extracted comments. + * x-ycp.c (struct token_ty): Add comment field. + (free_token): New function. + (phase5_get): Fill in tp->comment. + (phase8_get): Use free_token. + (extract_parenthesized): Pass token's comment to remember_a_message. + Use free_token. + Reported by Karl Eichwalder . + +2007-06-09 Bruno Haible + + * format-java.c: Include xmalloca.h instead of xallocsa.h. + (message_format_parse): Use xmalloca/freea instead of xallocsa/freesa. + * message.c: Include xmalloca.h instead of xallocsa.h. + (message_list_hash_insert_entry, message_list_search): Use + xmalloca/freea instead of xallocsa/freesa. + * msggrep.c: Include xmalloca.h instead of xallocsa.h. + (is_message_selected_no_invert): Use xmalloca/freea instead of + xallocsa/freesa. + * msginit.c: Include xmalloca.h instead of xallocsa.h. + (subst_string): Use xmalloca/freea instead of xallocsa/freesa. + * msgl-cat.c: Include xmalloca.h instead of xallocsa.h. + (catenate_msgdomain_list): Use xmalloca/freea instead of + xallocsa/freesa. + * msgl-charset.c: Include xmalloca.h instead of xallocsa.h. + (compare_po_locale_charsets): Use xmalloca/freea instead of + xallocsa/freesa. + * msgl-iconv.c: Include xmalloca.h instead of xallocsa.h. + (iconv_message_list_internal, is_message_list_iconvable): Use + xmalloca/freea instead of xallocsa/freesa. + * msgmerge.c: Include xmalloca.h instead of xallocsa.h. + (merge): Use xmalloca/freea instead of xallocsa/freesa. + * po-charset.c: Include xmalloca.h instead of xallocsa.h. + (po_lex_charset_set): Use xmalloca/freea instead of xallocsa/freesa. + * read-tcl.c: Include xmalloca.h instead of xallocsa.h. + (msgdomain_read_tcl): Use xmalloca/freea instead of xallocsa/freesa. + * write-java.c: Include xmalloca.h instead of xallocsa.h. + (compute_hashsize, msgdomain_write_java): Use xmalloca/freea instead of + xallocsa/freesa. + * write-mo.c: Include xmalloca.h instead of xallocsa.h. + (write_table): Use xmalloca/freea instead of xallocsa/freesa. + * write-po.c: Include xmalloca.h instead of xallocsa.h. + (msgdomain_list_print_po): Use xmalloca/freea instead of + xallocsa/freesa. + * write-tcl.c: Include xmalloca.h instead of xallocsa.h. + (msgdomain_write_tcl): Use xmalloca/freea instead of xallocsa/freesa. + * xgettext.c: Include xmalloca.h instead of xallocsa.h. + (flag_context_list_table_insert): Use xmalloca/freea instead of + xallocsa/freesa. + +2007-06-08 Bruno Haible + + * msgattrib.c (main): Update year in --version output. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + * recode-sr-latin.c (main): Likewise. + * hostname.c (main): Likewise. + * urlget.c (main): Likewise. + +2007-06-08 Bruno Haible + + * msginit.c: Include localename.h. + (_nl_locale_name): Remove declaration. + (main): Use gl_locale_name instead of _nl_locale_name. + * Makefile.am (msginit_LDADD): Remove localename object file. + +2007-05-28 Bruno Haible + + * Makefile.am (WOE32_LDADD): New variable. + (LDADD, msgcmp_LDADD, msgfmt_LDADD, msgmerge_LDADD, msgunfmt_LDADD, + xgettext_LDADD, msgattrib_LDADD, msgcat_LDADD, msgcomm_LDADD, + msgconv_LDADD, msgen_LDADD, msgexec_LDADD, msgfilter_LDADD, + msggrep_LDADD, msginit_LDADD, msguniq_LDADD, recode_sr_latin_LDADD): + Include it. + (gettext.res): New rule. + (MOSTLYCLEANFILES): Add gettext.res. + +2007-05-27 Bruno Haible + + Avoid link error; u16_mbtouc_aux is no longer a public API. + * x-java.c (string_buffer_append): Use u16_mbtouc instead of + u16_mbtouc_aux. + * x-python.c (mixed_string_buffer_append): Likewise. + +2007-05-13 Bruno Haible + + * write-java.c (mkdir): Remove definition. Now done by gnulib. + * write-csharp.c (mkdir): Remove definition. Now done by gnulib. + +2007-05-13 Bruno Haible + + * msgl-check.c: Include stdio.h instead of vasprintf.h. + * po-lex.c: Don't include vasprintf.h. + +2007-03-27 Bruno Haible + + * hostname.c (usage): Ask translators to specify a translation bug + report address. + * msgattrib.c (usage): Likewise. + * msgcat.c (usage): Likewise. + * msgcmp.c (usage): Likewise. + * msgcomm.c (usage): Likewise. + * msgconv.c (usage): Likewise. + * msgen.c (usage): Likewise. + * msgexec.c (usage): Likewise. + * msgfilter.c (usage): Likewise. + * msgfmt.c (usage): Likewise. + * msggrep.c (usage): Likewise. + * msginit.c (usage): Likewise. + * msgmerge.c (usage): Likewise. + * msgunfmt.c (usage): Likewise. + * msguniq.c (usage): Likewise. + * recode-sr-latin.c (usage): Likewise. + * urlget.c (usage): Likewise. + * xgettext.c (usage): Likewise. + +2007-03-24 Bruno Haible + + * po-lex.c: Include unistr.h instead of utf8-ucs4.h. + * read-properties.c: Include unistr.h instead of utf16-ucs4.h, + ucs4-utf8.h. + * x-java.c: Likewise. + * read-stringtable.c: Include unistr.h instead of utf8-ucs4.h, + ucs4-utf8.h. + * x-csharp.c: Likewise. + * write-csharp.c: Include unistr.h instead of utf8-ucs4.h. + * write-java.c: Likewise. + * write-properties.c: Likewise. + * write-qt.c: Likewise. + * write-tcl.c: Likewise. + * x-perl.c: Likewise. + * x-python.c: Include unistr.h instead of utf8-ucs4.h, utf16-ucs4.h, + ucs4-utf8.h. + * x-tcl.c: Include unistr.h instead of ucs4-utf8.h. + +2007-03-17 Bruno Haible + + * x-php.c (enum token_type_ty): New values token_type_lbracket, + token_type_rbracket. + (x_php_lex): Recognize also token_type_lbracket, token_type_rbracket. + (extract_balanced): Renamed from extract_parenthesized. Add 'delim' + argument. Handle token_type_lbracket and token_type_rbracket. + (extract_php): Update. + Reported by Robert Vock . + +2007-03-10 Bruno Haible + + * msginit.c (get_user_fullname): Reduce scope of local variables. + +2007-03-04 Bruno Haible + + Moved --enable-relocatable infrastructure to gnulib. + * Makefile.am: Remove SET_RELOCATABLE invocation. + +2007-02-25 Bruno Haible + + * color.c: Include filename.h instead of pathname.h. + (style_file_prepare): Update. + * msginit.c: Include filename.h instead of pathname.h. + (project_id, project_id_version): Update. + * open-catalog.c: Include filename.h instead of pathname.h. + (try_open_catalog_file): Update. + * read-csharp.c: Include filename.h instead of pathname.h. + (msgdomain_read_csharp): Update. + * read-resources.c: Include filename.h instead of pathname.h. + (read_resources_file): Update. + * read-tcl.c: Include filename.h instead of pathname.h. + (msgdomain_read_tcl): Update. + * write-csharp.c: Include filename.h instead of pathname.h. + (msgdomain_write_csharp): Update. + * write-java.c Include filename.h instead of pathname.h. + (msgdomain_write_java): Update. + * write-resources.c: Include filename.h instead of pathname.h. + (msgdomain_write_csharp_resources): Update. + * write-tcl.c: Include filename.h instead of pathname.h. + (msgdomain_write_tcl): Update. + * xgettext.c: Include filename.h instead of pathname.h. + (main, xgettext_open): Update. + +2007-02-19 Bruno Haible + + * file-list.c: Don't include exit.h. + * hostname.c: Likewise. + * msgattrib.c: Likewise. + * msgcat.c: Likewise. + * msgcmp.c: Likewise. + * msgcomm.c: Likewise. + * msgconv.c: Likewise. + * msgen.c: Likewise. + * msgexec.c: Likewise. + * msgfilter.c: Likewise. + * msgfmt.c: Likewise. + * msggrep.c: Likewise. + * msginit.c: Likewise. + * msgl-cat.c: Include instead of exit.h. + * msgl-charset.c: Likewise. + * msgl-check.c: Don't include exit.h. + * msgmerge.c: Likewise. + * msgunfmt.c: Likewise. + * msguniq.c: Likewise. + * po-lex.c: Likewise. + * po-xerror.c: Likewise. + * read-csharp.c: Likewise. + * read-java.c: Likewise. + * read-mo.c: Likewise. + * read-resources.c: Likewise. + * read-tcl.c: Likewise. + * recode-sr-latin.c: Likewise. + * urlget.c: Likewise. + * write-mo.c: Likewise. + * write-qt.c: Likewise. + * write-resources.c: Likewise. + * write-tcl.c: Include instead of exit.h. + * x-awk.c: Don't include exit.h. + * x-c.c: Likewise. + * x-csharp.c: Likewise. + * x-elisp.c: Likewise. + * x-glade.c: Likewise. + * x-java.c: Likewise. + * x-librep.c: Likewise. + * x-lisp.c: Likewise. + * x-perl.c: Likewise. + * x-php.c: Likewise. + * x-python.c: Likewise. + * x-rst.c: Include instead of exit.h. + * x-scheme.c: Don't include exit.h. + * x-sh.c: Likewise. + * x-smalltalk.c: Likewise. + * x-tcl.c: Likewise. + * x-ycp.c: Likewise. + * xgettext.c: Likewise. + +2007-01-28 Bruno Haible + + * x-java.c (string_buffer_append_lone_surrogate): New function. + (string_buffer_flush_utf16_surr, string_buffer_append): Use it. + +2007-01-28 Bruno Haible + + * x-java.c (string_buffer_flush_utf16_surr): Give a warning when + converting a surrogate code point to U+FFFD. + (string_buffer_append): Convert a lone high surrogate code point to + U+FFFD, and give a warning. + +2007-01-26 Bruno Haible + + * msgfilter.c: Include unconditionally. + +2007-01-26 Bruno Haible + + * msginit.c: Don't include strpbrk.h, use instead. + +2007-01-26 Bruno Haible + + * msgmerge.c: Don't include stpncpy.h, use instead. + +2007-01-26 Bruno Haible + + * msgfmt.c: Don't include stpcpy.h, use instead. + * msgmerge.c: Likewise. + +2007-01-21 Bruno Haible + + * msgl-iconv.c (convert_string, convert_msgstr, iconvable_string, + iconvable_msgstr): Update for changed calling convention of + xmem_cd_iconv. + * recode-sr-latin.c (process): Likewise. + +2007-01-07 Bruno Haible + + * msgmerge.c (message_merge): Copy the obsolete bit from the ref + message. + Reported by Leonardo Fontenelle . + +2006-12-24 Bruno Haible + + * Makefile.am (AM_CXXFLAGS): New variable. + +2006-12-23 Bruno Haible + + * color.c (print_color_test): Print labels before the hue/saturation + rectangle. Show also a few lines that mix colors and attributes. + +2006-12-03 Bruno Haible + + * msgcat.c: Include color.h. + (long_options): Add --color, --style options. + (main): Handle them. Invoke print_color_test when --color=test was + given. + (usage): Document --color, --style options. + +2006-12-01 Bruno Haible + + Add styling support to the PO output routines. + * color.h: New file. + * color.c: New file. + * write-catalog.h (struct catalog_output_format): Add field + 'supports_color'. + * write-catalog.c: Include fcntl.h, unistd.h, styled-ostream.h, + term-styled-ostream.h, html-styled-ostream.h, fd-ostream.h, color.h, + po-charset.h, msgl-iconv.h. + (STDOUT_FILENO): New macro. + (ENABLE_COLOR): New macro. + (msgdomain_list_print): Use a styled_ostream_t that uses the + style_file_name. + * write-po.c: Include format.h, styled-ostream.h. + (is_stylable, begin_css_class, end_css_class): New functions or macros. + (class_header, class_translated, class_untranslated, class_fuzzy, + class_obsolete, class_comment, class_translator_comment, + class_extracted_comment, class_reference_comment, class_reference, + class_flag_comment, class_flag, class_fuzzy_flag, + class_previous_comment, class_previous, class_msgid, class_msgstr, + class_keyword, class_string, class_text, class_escape_sequence, + class_format_directive, class_invalid_format_directive): New variables. + (ATTR_ESCAPE_SEQUENCE, ATTR_FORMAT_DIRECTIVE, + ATTR_INVALID_FORMAT_DIRECTIVE): New enum items. + (message_print_comment, message_print_comment_dot, + message_print_comment_filepos, message_print_comment_flags): Call + begin/end_css_class. + (memset_small): New function. + (wrap): Add css_class argument. Determine the extent of format + string directives. Call begin/end_css_class. + (print_blank_line): Call begin/end_css_class. + (message_print, message_print_obsolete): Likewise. Pass a css_class + argument to 'wrap'. + (msgdomain_list_print_po): Call begin/end_css_class. + (output_format_po): Update. + * write-properties.c (output_format_properties): Update. + * write-stringtable.c (output_format_stringtable): Update. + * Makefile.am (noinst_HEADERS): Add color.h. + (libgettextsrc_la_SOURCES): Add color.c. + +2006-11-26 Bruno Haible + + * write-po.c (make_format_description_string, + make_c_width_description_string): Strip off the leading space. + (message_print_comment_flags): Print the space here. + * write-stringtable.c (write_message): Print the space here. + +2006-11-26 Bruno Haible + + * write-po.c: Include write-catalog.h instead of write-properties.h, + write-stringtable.h. + +2006-11-26 Bruno Haible + + * format.h (FMTDIR_START, FMTDIR_END, FMTDIR_ERROR): New enum values. + (FDI_SET): New macro. + (struct formatstring_parser): Add fdi argument to 'parse' method. + * format.c (check_msgid_msgstr_format): Update. + * format-awk.c (format_parse): Add fdi argument. Invoke FDI_SET. + * format-boost.c (format_parse): Likewise. + * format-c.c (format_parse): Add fdi argument. Invoke FDI_SET. + (format_c_parse, format_objc_parse): Add fdi argument. + (get_sysdep_c_format_directives): Update. + * format-csharp.c (format_parse): Add fdi argument. Invoke FDI_SET. + * format-elisp.c (format_parse): Likewise. + * format-gcc-internal.c (format_parse): Likewise. + * format-java.c (message_format_parse): Add fdi argument. Invoke + FDI_SET. + (choice_format_parse): Update. + (format_parse): Add fdi argument. + * format-librep.c (format_parse): Add fdi argument. Invoke FDI_SET. + * format-lisp.c (parse_upto): Add fdi argument. Invoke FDI_SET. + (format_parse): Add fdi argument. + * format-pascal.c (format_parse): Add fdi argument. Invoke FDI_SET. + * format-perl.c (format_parse): Likewise. + * format-perl-brace.c (format_parse): Likewise. + * format-php.c (format_parse): Likewise. + * format-python.c (format_parse): Add fdi argument. Invoke FDI_SET. + (get_python_format_unnamed_arg_count): Update. + * format-qt.c (format_parse): Add fdi argument. Invoke FDI_SET. + * format-scheme.c (parse_upto): Add fdi argument. Invoke FDI_SET. + (format_parse): Add fdi argument. + * format-sh.c (format_parse): Add fdi argument. Invoke FDI_SET. + * format-tcl.c (format_parse): Likewise. + * format-ycp.c (format_parse): Likewise. + * msgmerge.c (msgfmt_check_pair_fails): Update. + * read-mo.c (read_mo_file): Update. + * xgettext.c (set_format_flags_from_context, remember_a_message, + remember_a_message_plural): Update. + +2006-11-25 Bruno Haible + + * message.h (message_list_copy, msgdomain_list_copy): New declarations. + * message.c (message_list_copy, msgdomain_list_copy): New functions. + +2006-11-25 Bruno Haible + + * msgl-iconv.h (iconv_msgdomain_list): Add update_header argument. + * msgl-iconv.c (iconv_message_list_internal): Renamed from + iconv_msgdomain_list. Add update_header argument. + (iconv_message_list): New function. + (iconv_msgdomain_list): Add update_header argument. + * msgcmp.c (compare): Update iconv_msgdomain_list call. + * msgconv.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgmerge.c (merge): Likewise. + * xgettext.c (main): Likewise. + +2006-11-23 Bruno Haible + + * write-catalog.h: Include ostream.h. Don't include . + (struct catalog_output_format): Use ostream_t instead of 'FILE *'. + * write-catalog.c: Include ostream.h, file-ostream.h. + (msgdomain_list_print): Open a temporary file_ostream_t. + * write-po.h: Include ostream.h. Don't include . + (message_print_comment, message_print_comment_dot, + message_print_comment_filepos, message_print_comment_flags): Replace + 'FILE *' argument with an 'ostream_t' argument. + * write-po.c: Include ostream.h. + (message_print_comment, message_print_comment_dot, + message_print_comment_filepos, message_print_comment_flags, wrap, + print_blank_line, message_print, message_print_obsolete, + msgdomain_list_print_po): Replace 'FILE *' argument with an 'ostream_t' + argument. + * write-properties.c: Include ostream.h. + (write_escaped_string, write_message, write_properties, + msgdomain_list_print_properties): Replace 'FILE *' argument with an + 'ostream_t' argument. + * write-stringtable.c: Include ostream.h, xvasprintf.h. Don't include + . + (write_escaped_string, write_message, write_stringtable, + msgdomain_list_print_stringtable): Replace 'FILE *' argument with an + 'ostream_t' argument. + +2006-11-12 Bruno Haible + + * write-po.c (wrap): Indent by use of spaces, not tabs. + +2006-11-03 Bruno Haible + + Simplify xmalloc expressions. Add overflow check in xmalloc arguments. + * filter-sr-latin.c (serbian_to_latin): Use XNMALLOC instead of xmalloc. + * format-awk.c (format_parse): Use XMALLOC instead of xmalloc. + * format-boost.c (format_parse): Likewise. + * format-c.c (format_parse): Use XMALLOC, XNMALLOC instead of xmalloc. + (get_sysdep_c_format_directives): Use XNMALLOC instead of xmalloc. + * format-csharp.c (format_parse): Use XMALLOC instead of xmalloc. + * format-elisp.c (format_parse): Likewise. + * format-gcc-internal.c (format_parse): Likewise. + * format-java.c (format_parse): Likewise. + * format-librep.c (format_parse): Likewise. + * format-lisp.c (copy_list): Use XMALLOC, XNMALLOC instead of xmalloc. + (make_unconstrained_list): Likewise. + (make_empty_list): Use XMALLOC instead of xmalloc. + (rotate_loop): Use XNMALLOC instead of xmalloc. + (make_intersected_list): Use XMALLOC instead of xmalloc. + (make_union_list): Use XMALLOC, XNMALLOC instead of xmalloc. + (make_repeated_list_of_lists): Likewise. + (make_repeated_list): Likewise. + (format_parse): Use XMALLOC instead of xmalloc. + * format-pascal.c (format_parse): Likewise. + * format-perl.c (format_parse): Likewise. + * format-perl-brace.c (format_parse): Use XMALLOC, XNMALLOC instead of + xmalloc. + * format-php.c (format_parse): Use XMALLOC instead of xmalloc. + * format-python.c (format_parse): Use XMALLOC, XNMALLOC instead of + xmalloc. + * format-qt.c (format_parse): Use XMALLOC instead of xmalloc. + * format-scheme.c (copy_list): Use XMALLOC, XNMALLOC instead of xmalloc. + (make_unconstrained_list): Likewise. + (make_empty_list): Use XMALLOC instead of xmalloc. + (rotate_loop): Use XNMALLOC instead of xmalloc. + (make_intersected_list): Use XMALLOC instead of xmalloc. + (make_union_list): Use XMALLOC, XNMALLOC instead of xmalloc. + (make_repeated_list_of_lists): Likewise. + (make_repeated_list): Likewise. + (format_parse): Use XMALLOC instead of xmalloc. + * format-sh.c (format_parse): Use XMALLOC, XNMALLOC instead of xmalloc. + * format-tcl.c (format_parse): Use XMALLOC instead of xmalloc. + * format-ycp.c (format_parse): Likewise. + * message.c (message_alloc, message_list_list_alloc, msgdomain_alloc): + Use XMALLOC instead of xmalloc. + (msgdomain_list_alloc): Use XMALLOC, XNMALLOC instead of xmalloc. + * msgexec.c (main): Use XNMALLOC instead of xmalloc. + * msgfilter.c (main, generic_filter, process_message): Likewise. + * msgfmt.c (add_mo_suffix): Likewise. + (new_domain): Use XMALLOC instead of xmalloc. + * msginit.c (catalogname_for_locale, language_of_locale, + get_user_fullname, get_field, put_field, subst_string, + update_msgstr_plurals): Use XNMALLOC instead of xmalloc. + * msgl-cat.c (catenate_msgdomain_list): Likewise. + * msgl-check.c (check_plural_eval): Use XCALLOC instead of xcalloc. + * msgl-english.c (msgdomain_list_english): Use XNMALLOC instead of + xmalloc. + * msgl-fsearch.c (new_index): Likewise. + (message_fuzzy_index_alloc): Use XMALLOC instead of xmalloc. + (mult_index_list_accumulate): Use XNMALLOC instead of xmalloc. + * msgl-iconv.c (iconv_message_list): Likewise. + * msgmerge.c (message_merge, match_domain): Likewise. + * po-gram-gen.y (plural_form_list): Likewise. + * read-catalog-abstract.c (po_parse_comment_filepos, + po_parse_comment_solaris_filepos): Likewise. + * read-mo.c (get_sysdep_string): Likewise. + * read-properties.c (conv_from_iso_8859_1, read_escaped_string): + Likewise. + * read-stringtable.c (conv_from_ucs4): Likewise. + * str-list.c (string_list_alloc): Use XMALLOC instead of xmalloc. + (string_list_concat, string_list_join): Use XNMALLOC instead of xmalloc. + * write-csharp.c (construct_class_name, msgdomain_write_csharp): + Likewise. + * write-java.c (compute_hashsize, compute_table_items): Likewise. + * write-mo.c: Include xsize.h. + (write_table): Use XNMALLOC instead of xmalloc. Use xsum, xtimes in + xmalloc argument. + * write-po.c (wrap): Use XNMALLOC instead of xmalloc. + * write-properties.c (conv_to_java): Likewise. + * write-qt.c (conv_to_iso_8859_1, conv_to_utf16): Likewise. + * x-elisp.c (init_token, string_of_object): Likewise. + (read_object): Use XMALLOC instead of xmalloc. + * x-librep.c (init_token, string_of_object): Use XNMALLOC instead of + xmalloc. + (read_object): Use XMALLOC instead of xmalloc. + * x-lisp.c (x_lisp_keyword, init_token, string_of_object): Use XNMALLOC + instead of xmalloc. + (read_object): Use XMALLOC instead of xmalloc. + * x-perl.c (get_here_document, extract_quotelike_pass1, + extract_quotelike_pass3): Use XNMALLOC instead of xmalloc. + (x_perl_lex): Use XMALLOC instead of xmalloc. + * x-po.c (strextract_add_message, extract): Use XNMALLOC instead of + xmalloc. + * x-python.c (try_to_extract_coding): Use XNMALLOC instead of xmalloc. + * x-scheme.c (init_token, string_of_object): Use XNMALLOC + instead of xmalloc. + (read_object): Use XMALLOC instead of xmalloc. + * x-sh.c (init_token, string_of_token, string_of_word): Use XNMALLOC + instead of xmalloc. + (read_word): Use XMALLOC instead of xmalloc. + * x-smalltalk.c (phase2_get): Use XNMALLOC instead of xmalloc. + * x-tcl.c (init_token, string_of_word): Use XNMALLOC instead of xmalloc. + (read_word): Use XMALLOC instead of xmalloc. + * xgettext.c: Include xsize.h. + (split_keywordspec): Use XNMALLOC instead of xmalloc. + (insert_keyword_callshape): Use XMALLOC instead of xmalloc. Use xsum, + xtimes to avoid overflow. + (flag_context_list_table_insert): Use XMALLOC instead of xmalloc. + (savable_comment_add): Likewise. + (remember_a_message_plural): Use XNMALLOC instead of xmalloc. + (arglist_parser_alloc, arglist_parser_clone): Use xsum, xtimes. + (arglist_parser_done): Use XNMALLOC instead of xmalloc. + (finalize_header): Likewise. + +2006-10-30 Bruno Haible + + * plural-eval.h [C++]: Define functions without name mangling. + +2006-10-29 Bruno Haible + + Clean up libgettextpo exports in C++ mode. + * po-gram.h: Wrap declarations in extern "C". + +2006-10-29 Bruno Haible + + Make it compile in C++ mode. + * dir-list.c (dir_list_restore): Add cast. + * format-c.c (format_arg_type_t): New type. Use it instead of + 'enum format_arg_type'. + * format-gcc-internal.c (format_arg_type_t): New type. Use it instead of + 'enum format_arg_type'. + * format-perl.c (format_arg_type_t): New type. Use it instead of + 'enum format_arg_type'. + * format-lisp.c (struct segment): Move out of the scope of struct + format_arg_list. + * format-scheme.c (struct segment): Likewise. + * message.h (struct altstr): Move out of the scope of struct message_ty. + * msgl-equal.c (msgstr_equal_ignoring_potcdate): Cast memchr results. + * po-lex.c (mb_copy): Rename arguments to new_mbc, old_mbc. + * read-mo.c (enum mo_endianness): Move out of the scope of struct + binary_mo_file. + * read-po.c (this): New macro. + * read-properties.c (this): New macro. + * write-mo.c (write_table): Cast alloca result. + * x-glade.c (load_libexpat): Cast dlsym results. + * x-perl.c (get_here_document): Use xmalloc instead of + xrealloc (NULL...). Cast its result. + * x-po.c (extract_add_message): Limit the scope of variable + 'charsetstr'. + * x-smalltalk.c (phase2_get): Cast xmalloc results. + +2006-10-29 Bruno Haible + + * x-awk.h: Make includable without prerequisites. + * x-c.h: Likewise. + * x-csharp.h: Likewise. + * x-elisp.h: Likewise. + * x-glade.h: Likewise. + * x-java.h: Likewise. + * x-librep.h: Likewise. + * x-lisp.h: Likewise. + * x-perl.h: Likewise. + * x-php.h: Likewise. + * x-po.h: Likewise. + * x-properties.h: Likewise. + * x-python.h: Likewise. + * x-rst.h: Likewise. + * x-scheme.h: Likewise. + * x-sh.h: Likewise. + * x-smalltalk.h: Likewise. + * x-stringtable.h: Likewise. + * x-tcl.h: Likewise. + * x-ycp.h: Likewise. + * x-awk.c: Include x-awk.h. + * x-c.c: Include x-c.h. + * x-csharp.c: Include x-csharp.h. + * x-elisp.c: Include x-elisp.h. + * x-glade.c: Include x-glade.h. + * x-java.c: Include x-java.h. + * x-librep.c: Include x-librep.h. + * x-lisp.c: Include x-lisp.h. + * x-perl.c: Include x-perl.h. + * x-php.c: Include x-php.h. + * x-po.c: Include x-po.h, x-properties.h, x-stringtable.h. + * x-python.c: Include x-python.h. + * x-rst.c: Include x-rst.h. + * x-scheme.c: Include x-scheme.h. + * x-sh.c: Include x-sh.h. + * x-smalltalk.c: Include x-smalltalk.h. + * x-tcl.c: Include x-tcl.h. + * x-ycp.c: Include x-ycp.h. + * xgettext.c: Remove extern "C" around x-*.h includes. + +2006-10-29 Bruno Haible + + * format-awk.c (format_parse): Fix bug with unnumbered argument in + precision field. + +2006-11-27 Bruno Haible + + * gettext-0.16.1 released. + +2006-10-26 Bruno Haible + + * gettext-0.16 released. + +2006-10-24 Bruno Haible + + * Makefile.am (AM_CPPFLAGS): Add also $(top_srcdir). Needed so that + woe32dll/export.h is found while compiling gettextsrc-exports.c in a + VPATH build. + Reported by Charles Wilson . + +2006-10-24 Bruno Haible + + * msgmerge.c (struct definitions_ty): Remove semicolon after + gl_lock_define invocation. + +2006-10-21 Bruno Haible + + * po-error.h (po_error, po_error_at_line): Use format attribute only + with gcc >= 3.1. + +2006-10-21 Bruno Haible + + * msgmerge.c (struct definitions_ty): Use gl_lock_define to declare + the findex_init_lock field. + (definitions_init): Use gl_lock_init to initialize it, instead of + memcpy hack. + +2006-10-16 Bruno Haible + + Work around automake-1.10 problem described in + http://lists.gnu.org/archive/html/bug-automake/2006-08/msg00025.html. + * Makefile.am (CXXLINK): Remove variable. + (msgmerge_CXXFLAGS): New variable. + (libgettextsrc_la_LINK, msgattrib_LINK, msgcat_LINK, msgcomm_LINK, + msgconv_LINK, msgen_LINK, msgfilter_LINK, msggrep_LINK, msgmerge_LINK, + msguniq_LINK, xgettext_LINK): New variables. + +2006-10-18 Bruno Haible + + * read-po.h (input_format_po): Mark as DLL_VARIABLE. + * read-properties.h (input_format_properties): Likewise. + * read-stringtable.h (input_format_stringtable): Likewise. + * write-po.h (output_format_po): Likewise. + * write-properties.h (output_format_properties): Likewise. + * write-stringtable.h (output_format_stringtable): Likewise. + +2006-10-16 Bruno Haible + + * gettext-po.h.in: Move to ../libgettextpo/. + * gettext-po.c: Move to ../libgettextpo/. + * Makefile.am (lib_LTLIBRARIES): Remove libgettextpo.la. + (nodist_include_HEADERS): Remove variable. + (libgettextpo_la_SOURCES, LTV_CURRENT, LTV_REVISION, LTV_AGE, + libgettextpo_la_LDFLAGS, libgettextpo_la_DEPENDENCIES, + libgettextpo_la_LDFLAGS): Remove variables. + (EXTRA_DIST, BUILT_SOURCES, CLEANFILES): Remove gettext-po.h support. + (gettext-po.h): Remove rule. + +2006-10-18 Bruno Haible + + * po-error.h (__attribute__, __format__, __printf__): New macros. + (po_error, po_error_at_line): Declare format string argument. + * po-lex.h (__attribute__, __format__, __printf__): New macros. + (po_gram_error, po_gram_error_at_line): Declare format string argument. + + * msgl-cat.c (catenate_msgdomain_list): Avoid "gcc -Wshadow" warning. + * msgl-check.c (formatstring_error_logger): Avoid "gcc + -Wmissing-format-attribute" warning. + (check_pair): Avoid "gcc -Wshadow" warning. + + * read-stringtable.c (stringtable_parse): Pass a mutable string as + msgstr to po_callback_message. + * write-po.c (msgdomain_list_print_po): Avoid "gcc -Wwrite-strings" + warning. + * xgettext.c (remember_a_message_plural): Free a temporary string + after use. + +2006-10-17 Bruno Haible + + * lang-table.c (language_table): Add the most important languages that + have an ISO 639-2 code. + * msginit.c (catalogname_for_locale): Likewise. + +2006-10-15 Bruno Haible + + * read-catalog-abstract.h (input_syntax_ty): Remove type. + (catalog_input_format): New structure type. + (catalog_input_format_ty): New type. + (catalog_reader_parse): Change type of input_syntax argument. + * read-catalog.h (input_syntax): Remove variable. + (read_catalog_stream, read_catalog_file): Add input_syntax argument. + * msgl-cat.h: Include read-catalog-abstract.h. + (catenate_msgdomain_list): Add input_syntax argument. + * read-po.h: New file. + * read-po.c: New file, extracted from read-catalog-abstract.c. + * msgattrib.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update read_catalog_file calls. + * msgcat.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update catenate_msgdomain_list call. + * msgcmp.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update compare call. + (compare): Add input_syntax argument. + * msgcomm.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update catenate_msgdomain_list call. + * msgconv.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update read_catalog_file call. + * msgen.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update read_catalog_file call. + * msgexec.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update read_catalog_file call. + * msgfilter.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update read_catalog_file call. + * msgfmt.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update read_catalog_file_msgfmt call. + (read_catalog_file_msgfmt): Add input_syntax argument. + * msggrep.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update read_catalog_file call. + * msginit.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update read_catalog_file call. + * msgl-cat.c (catenate_msgdomain_list): Add input_syntax argument. + * msgmerge.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update merge call. + (compendium): Read it in PO syntax. + (merge): Add input_syntax argument. + * msguniq.c: Include read-po.h, read-properties.h, read-stringtable.h. + (main): Change handling of --properties-input, --stringtable-input + options. Update catenate_msgdomain_list call. + * read-catalog-abstract.c: Don't include po-gram.h, read-properties.h, + read-stringtable.h. + (catalog_reader_parse): Change type of input_syntax argument. Invoke + the parser indirectly. + * read-catalog.c (input_syntax): Remove variable. + (read_catalog_stream, read_catalog_file): Add input_syntax argument. + * read-csharp.c: Include read-po.h. + (execute_and_read_po_output): Update read_catalog_stream call. + * read-java.c: Include read-po.h. + (execute_and_read_po_output): Update read_catalog_stream call. + * read-properties.c (properties_parse): Make static. + (input_format_properties): New variable. + * read-properties.h (properties_parse): Remove declaration. + (input_format_properties): New declaration. + * read-resources.c: Include read-po.h. + (execute_and_read_po_output): Update read_catalog_stream call. + * read-stringtable.c (stringtable_parse): Make static. + (input_format_stringtable): New variable. + * read-stringtable.h (stringtable_parse): Remove declaration. + (input_format_stringtable): New declaration. + * read-tcl.c: Include read-po.h. + (msgdomain_read_tcl): Update read_catalog_stream call. + * x-po.c: Include read-po.h, read-properties.h, read-stringtable.h. + (extract): Change type of input_syntax argument. + (extract_po, extract_properties, extract_stringtable): Update. + * xgettext.c: Include read-po.h. + (read_exclusion_file): Read it in PO syntax. + * gettext-po.c: Include read-po.h. + (po_file_read_v3, po_file_read_v2, po_file_read): Update + read_catalog_stream call. + * Makefile.am (noinst_HEADERS): Add read-po.h. + (COMMON_SOURCE): Add read-po.c. + +2006-10-15 Bruno Haible + + * read-catalog-abstract.h (abstract_catalog_reader_ty): Renamed from + abstract_po_reader_ty. + (abstract_catalog_reader_class_ty): Renamed from + abstract_po_reader_class_ty. Update. + (ABSTRACT_CATALOG_READER_TY): Renamed from ABSTRACT_PO_READER_TY. + Update. + (abstract_catalog_reader_ty): Renamed from abstract_po_reader_ty. + Update. + (catalog_reader_alloc): Renamed from po_reader_alloc. + (catalog_reader_parse): Renamed from po_scan. + (catalog_reader_free): Renamed from po_reader_free. + * read-catalog.h: Update. + (default_catalog_reader_class_ty): Renamed from + default_po_reader_class_ty. + (DEFAULT_CATALOG_READER_TY): Renamed from DEFAULT_PO_READER_TY. Update. + (default_catalog_reader_ty): Renamed from default_po_reader_ty. Update. + (default_constructor, default_destructor, default_parse_brief, + default_parse_debrief, default_directive_domain, + default_directive_message, default_comment, default_comment_dot, + default_comment_filepos, default_comment_special, default_set_domain, + default_add_message): Update. + (default_catalog_reader_alloc): Renamed from default_po_reader_alloc. + Update. + (read_catalog_stream): Renamed from read_po. + (read_catalog_file): Renamed from read_po_file. + * open-catalog.h (open_catalog_file): Renamed from open_po_file. + * msgattrib.c: Update. + (main): Update. + * msgcat.c: Update. + * msgcmp.c: Update. + (compare): Update. + * msgcomm.c: Update. + * msgconv.c: Update. + (main): Update. + * msgen.c: Update. + (main): Update. + * msgexec.c: Update. + (main): Update. + * msgfilter.c: Update. + (main): Update. + * msgfmt.c: Update. + (read_catalog_file_msgfmt): Renamed from read_po_file_msgfmt. Update. + (main): Update. + (msgfmt_catalog_reader_ty): Renamed from msgfmt_po_reader_ty. Update. + (msgfmt_constructor, msgfmt_parse_debrief, msgfmt_set_domain, + msgfmt_add_message, msgfmt_frob_new_message, msgfmt_comment_special, + msgfmt_methods): Update. + (read_catalog_file_msgfmt): Renamed from read_po_file_msgfmt. Update. + * msggrep.c: Update. + (main): Update. + * msginit.c: Update. + (main): Update. + * msgl-cat.c: Update. + (catenate_msgdomain_list): Update. + * msgmerge.c: Update. + (compendium, merge): Update. + * msguniq.c: Update. + * open-catalog.c: Update. + (try_open_catalog_file): Renamed from try_open_po_file. + (open_catalog_file): Renamed from open_po_file. Update. + * po-gram-gen.y: Update. + * read-catalog-abstract.c: Update. + (callback_arg): Update. + (catalog_reader_alloc): Renamed from po_reader_alloc. + (catalog_reader_free): Renamed from po_reader_free. + (call_parse_brief, call_parse_debrief, call_directive_domain, + call_directive_message, call_comment, call_comment_dot, + call_comment_filepos, call_comment_special): Update. + (parse_start): Renamed from po_scan_start. + (parse_end): Renamed from po_scan_end. + (catalog_reader_parse): Renamed from po_scan. + * read-catalog.c: Update. + (call_set_domain, call_add_message, call_frob_new_message): Update. + (default_constructor, default_destructor, default_parse_brief, + default_parse_debrief, default_copy_comment_state, + default_reset_comment_state, default_directive_domain, + default_directive_message, default_comment, default_comment_dot, + default_comment_filepos, default_comment_special, default_set_domain, + default_add_message, default_methods): Update. + (default_catalog_reader_alloc): Renamed from default_po_reader_alloc. + (read_catalog_stream): Renamed from read_po. Update. + (read_catalog_file): Renamed from read_po_file. Update. + * read-csharp.c: Update. + (execute_and_read_po_output): Update. + * read-java.c: Update. + (execute_and_read_po_output): Update. + * read-properties.c: Update. + (properties_parse): Update. + * read-properties.h: Update. + (properties_parse): Update. + * read-resources.c: Update. + (execute_and_read_po_output): Update. + * read-stringtable.c: Update. + (stringtable_parse): Update. + * read-stringtable.h: Update. + (stringtable_parse): Update. + * read-tcl.c: Update. + (msgdomain_read_tcl): Update. + * x-po.c: Update. + (extract_add_message, extract_methods, extract): Update. + * xgettext.c: Update. + (exclude_directive_domain, exclude_directive_message, exclude_methods, + read_exclusion_file): Update. + * xgettext.h: Update. + * gettext-po.c: Update. + (po_file_read_v3, po_file_read_v2, po_file_read): Update. + + * read-catalog-abstract.h: Renamed from read-po-abstract.h. + * read-catalog-abstract.c: Renamed from read-po-abstract.c. + * read-catalog.h: Renamed from read-po.h. + * read-catalog.c: Renamed from read-po.c. + * open-catalog.h: Renamed from open-po.h. + * open-catalog.c: Renamed from open-po.c. + * Makefile.am (noinst_HEADERS, COMMON_SOURCE, libgettextsrc_la_SOURCES): + Update. + +2006-10-15 Bruno Haible + + * write-catalog.h: New file. + * write-catalog.c: New file, mostly extracted from write-po.c. + * write-po.h (message_page_width_set): Move declaration to + write-catalog.h. + (message_print_syntax_properties, message_print_syntax_stringtable): + Remove declarations. + (msgdomain_list_print): Move out to write-catalog.h, adding a + catalog_output_format_ty argument. + (msgdomain_list_sort_by_msgid, msgdomain_list_sort_by_filepos): Move + out to write-catalog.h. + (output_format_po): New declaration. + * write-po.c: Don't include fwriteerror.h, error-progname.h. + (page_width, message_page_width_set): Move out to write-catalog.c. + (use_syntax_properties): Remove variable. + (message_print_syntax_properties): Remove function. + (use_syntax_stringtable): Remove variable. + (message_print_syntax_stringtable): Remove function. + (wrap, message_print, message_print_obsolete, msgdomain_list_print_po): + Add page_width argument. + (msgdomain_list_print): Move out to write-catalog.c, adding a + catalog_output_format_ty argument. + (cmp_by_msgid, msgdomain_list_sort_by_msgid, + cmp_filepos, msgdomain_list_sort_filepos, + cmp_by_filepos, msgdomain_list_sort_by_filepos): Move out to + write-catalog.c. + (output_format_po): New variable. + * write-properties.h (msgdomain_list_print_properties): Remove + declaration. + (output_format_properties): New declaration. + * write-properties.c (msgdomain_list_print_properties): Make static. + (output_format_properties): New variable. + * write-stringtable.h (msgdomain_list_print_stringtable): Remove + declaration. + (output_format_stringtable): New declaration. + * write-stringtable.c (msgdomain_list_print_stringtable): Make static. + (output_format_stringtable): New variable. + * msgattrib.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msgcat.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msgcomm.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msgconv.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msgen.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msgfilter.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msggrep.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msginit.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msgmerge.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msgunfmt.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * msguniq.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (main): Change handling of --properties-output, --stringtable-output + options. Pass the output_syntax to msgdomain_list_print. + * xgettext.c: Include write-catalog.h, write-properties.h, + write-stringtable.h. + (output_syntax): Change type to catalog_output_format_ty. + (main): Update. Pass the output_syntax to msgdomain_list_print. + (finalize_header): Update. + * gettext-po.c: Include write-catalog.h. + (po_file_write): Specify PO syntax. + * Makefile.am (noinst_HEADERS): Add write-catalog.h. + (libgettextsrc_la_SOURCES): Add write.catalog.c. + +2006-10-05 Bruno Haible + + * lang-table.c (language_table): Add Adangme, Kashubian. Fix the + names of Western Frisian, Marshallese, Sinhala. + * msginit.c (catalogname_for_locale): Add entries for Kashubian, + Western Frisian, South Ndebele. Change entry for Serbian from sr_CS to + sr_RS. + +2006-10-06 Bruno Haible + + * write-java.c (msgdomain_write_java): Use fopen_temp, fwriteerror_temp + instead of fopen, fwriteerror. + * write-csharp.c (msgdomain_write_csharp): Likewise. + +2006-10-06 Bruno Haible + + * Makefile.am (LTV_CURRENT, LTV_REVISION, LTV_AGE): Bump to 3:0:3. + +2006-10-03 Bruno Haible + + * message.h (struct message_ty): New fields prev_msgctxt, prev_msgid, + prev_msgid_plural. + * message.c (message_alloc): Initialize the prev_msg* fields. + (message_free): Free the prev_msg* fields. + (message_copy): Copy the prev_msg* fields. + * msgl-ascii.c (is_ascii_message): Consider also the prev_msg* fields. + * msgl-cat.c (catenate_msgdomain_list): Copy the prev_msg* fields if + a message is copied, not merged. + * msgl-equal.c (message_equal): Consider also the prev_msg* fields. + * msgl-iconv.c (convert_prev_msgid): New function. + (iconv_message_list): Call it. + iconvable_prev_msgid): New function. + (is_message_list_iconvable): Call it. + + * po-lex.c (po_lex_previous): New variable. + (lex_start, lex_end): Reset it. + (keyword_p): Test it. New return values PREV_MSGID, PREV_MSGID_PLURAL, + PREV_MSGCTXT. + (po_gram_lex): Recognize #| and #~| syntax. New return value + PREV_STRING. + * po-gram-gen.y (do_callback_message): Add prev_msgctxt, prev_msgid, + prev_msgid_plural arguments. + (free_message_intro): New macro. + (PREV_MSGCTXT, PREV_MSGID, PREV_MSGID_PLURAL, PREV_STRING): New tokens. + (prev, message_intro): New structures. + (po_file): Renamed from msgfmt. + (message): Pass prev_* fields around. Call free_message_intro. + (message_intro, prev): New rules. + (msg_intro): Renamed from message_intro. + (prev_msg_intro, prev_msgid_pluralform, prev_string_list): New rules. + * read-po-abstract.h (struct abstract_po_reader_class_ty): Add + prev_msgctxt, prev_msgid, prev_msgid_plural arguments to the + 'directive_message' method. + (po_callback_message): Add prev_msgctxt, prev_msgid, + prev_msgid_plural arguments. + * read-po-abstract.c (call_directive_message, po_callback_message): Add + prev_msgctxt, prev_msgid, prev_msgid_plural arguments. + * read-po.h (struct default_po_reader_class_ty): Add prev_msgctxt, + prev_msgid, prev_msgid_plural arguments to the 'add_message' method. + (default_directive_message, default_add_message): Add prev_msgctxt, + prev_msgid, prev_msgid_plural arguments. + * read-po.c (call_add_message, default_directive_message, + default_add_message): Add prev_msgctxt, prev_msgid, prev_msgid_plural + arguments. + * read-properties.c (properties_parse): Update. + * read-stringtable.c (stringtable_parse): Update. + * xgettext.c (exclude_directive_message): Add prev_msgctxt, prev_msgid, + prev_msgid_plural arguments. + * x-po.c (extract_add_message): Add prev_msgctxt, prev_msgid, + prev_msgid_plural arguments. + + * write-po.c (wrap): Add extra_indent argument. + (message_print, message_print_obsolete): Print the prev_msgctxt, + prev_msgid, prev_msgid_plural fields. Compute an extra_indent. + + * msgmerge.c (keep_previous): New variable. + (long_options): Add --previous option. + (main): Handle it. + (usage): Document --previous option. + (message_merge): Add force_fuzzy argument. Set the new message's + prev_msg* fields. + (match_domain): Update. + * msgattrib.c (REMOVE_PREV): New enum item. + (long_options): Add --clear-previous option. + (main): Handle it. + (usage): Document --clear-previous option. + (process_message_list): Handle REMOVE_PREV. + * msgfmt.c (msgfmt_add_message): Add prev_msgctxt, prev_msgid, + prev_msgid_plural arguments. + * gettext-po.c (po_message_prev_msgctxt, po_message_set_prev_msgctxt, + po_message_prev_msgid, po_message_set_prev_msgid, + po_message_prev_msgid_plural, po_message_set_prev_msgid_plural): New + functions. + +2006-10-03 Bruno Haible + + * msgmerge.c (definitions_init): Fix initialization of fresh_lock. + +2006-10-03 Bruno Haible + + * msgcmp.c (include_fuzzies, include_untranslated): New variables. + (long_options): Add options --use-fuzzy, --use-untranslated. + (main): Handle them. + (usage): Document them. + (match_domain): Consider include_fuzzies and include_untranslated. + +2006-10-02 Bruno Haible + + * gettext-po.h.in (LIBGETTEXTPO_VERSION): Bump version number. + * Makefile.am (LTV_CURRENT, LTV_REVISION, LTV_AGE): Bump to 2:1:2. + +2006-10-01 Bruno Haible + + * x-perl.c (extract_balanced): Remove state argument. + + * x-perl.c (extract_balanced): Remove unused variable prev_last_token. + + Fix Perl parsing bug introduced between 0.14.6 and 0.15. + * x-perl.c (extract_balanced): Add eat_delim and comma_delim arguments. + Use recursion to handle the case of a keyword not followed by an + opening parenthesis. When a keyword is seen, set next_argparser + instead of modifying the argparser variable of the current recursion. + When seeing an opening parenthesis, distinguish the case of function + arguments and the case of a subexpression. Don't call collect_message + for unused argument positions. + Reported by Nicolas François . + + * x-perl.c (extract_balanced): Remove unused variable. + +2006-09-06 Bruno Haible + + * msgl-iconv.c: Include xstriconv.h instead of iconvstring.h. + (convert_string, convert_msgstr, iconvable_string, iconvable_msgstr): + Use xmem_cd_iconv instead of iconv_string. + * recode-sr-latin.c: Include xstriconv.h instead of iconvstring.h. + (process): Use xmem_cd_iconv instead of iconv_string. + +2006-09-04 Bruno Haible + + * Makefile.am (msginit_SOURCES): Add ../../gettext-runtime/intl/lock.c. + Reported by Aaron Williams . + +2006-08-28 Bruno Haible + + * x-scheme.c (read_object): Don't call arglist_parser_remember if + argparser is NULL. + * x-elisp.c (read_object): Likewise. + * x-librep.c (read_object): Likewise. + * x-lisp.c (read_object): Likewise. + Reported by Kevin Ryde . + +2006-08-16 Bruno Haible + + * plural-table.c (plural_table): Put Hungarian under nplurals=2. + Reported by Arpad Biro and + Gabor Kelemen . + +2006-08-16 Bruno Haible + + * read-mo.c: Include stdbool.h, stdlib.h, format.h. + (read_mo_file): Mark messages with system-dependent segments as + "#, c-format" or "#, objc-format", as appropriate. + Reported by Egmont Koblinger . + +2006-08-07 Bruno Haible + + * xgettext.c (remember_a_message): Use the position passed as argument + instead of a dummypos. + Reported by Paul Eggert . + +2006-08-01 Bruno Haible + + Fix behaviour of msgmerge when the PO file and the compendium are + in different encodings. + * msgl-iconv.h (is_message_list_iconvable): New declaration. + * msgl-iconv.c (iconvable_string, iconvable_string_list, + iconvable_msgid, iconvable_msgstr, is_message_list_iconvable): New + functions. + * msgmerge.c: Include xallocsa.h. + (merge): In the case that the .pot file is ASCII, not UTF-8, convert + the definitions and the compendia contents to a common encoding. + Reported by Stanislav Brabec . + +2006-07-30 Bruno Haible + + * Makefile.am (AM_CPPFLAGS, LDADD, libgettextsrc_la_LDFLAGS, + libgettextpo_la_LDFLAGS, recode_sr_latin_LDADD, RELOCATABLE_SRC_DIR, + RELOCATABLE_BUILD_DIR): Replace ../lib with ../gnulib-lib. + (JAVACOMP, CSHARPCOMP): Remove lib/ from pathname. + +2006-07-30 Bruno Haible + + * msgl-cat.c: Include c-strstr.h instead of strstr.h. + (catenate_msgdomain_list): Use c_strstr instead of strstr. + * msgl-charset.c: Include c-strstr.h instead of strstr.h. + (compare_po_locale_charsets): Use c_strstr instead of strstr. + * msgl-check.c: Include c-strstr.h instead of strstr.h. + (plural_help, check_plural, check_header_entry): Use c_strstr instead + of strstr. + * msgl-iconv.c: Include c-strstr.h instead of strstr.h. + (iconv_message_list): Use c_strstr instead of strstr. + * po-charset.c: Include c-strstr.h instead of strstr.h. + (po_lex_charset_set): Use c_strstr instead of strstr. + * write-po.c: Include c-strstr.h instead of strstr.h. + (msgdomain_list_print_po): Use c_strstr instead of strstr. + * write-stringtable.c: Include c-strstr.h instead of strstr.h. + (write_message): Use c_strstr instead of strstr. + * msgcmp.c: Include c-strstr.h instead of strstr.h. + (compare): Use c_strstr instead of strstr. + * msginit.c: Include c-strstr.h instead of strstr.h. + (content_type): Use c_strstr instead of strstr. + * msgmerge.c: Include c-strstr.h instead of strstr.h. + (message_merge, merge): Use c_strstr instead of strstr. + * x-python.c: Include c-strstr.h instead of strstr.h. + (try_to_extract_coding): Use c_strstr instead of strstr. + * xgettext.c: Include c-strstr.h instead of strstr.h. + (remember_a_message, finalize_header): Use c_strstr instead of strstr. + +2006-07-23 Bruno Haible + + Exploit CPUs with multiple execution units. + * msgmerge.c: Include lock.h. + (struct definitions_ty): Add 'findex_init_lock' field. + (definitions_init): Initialize it. + (definitions_init_findex): Ensure findex is initialized by the + first thread who attempts so. + (match_domain): Split the main loop into two. Parallelize the first + loop using OpenMP pragmas. + * Makefile.am (msgmerge_CFLAGS): New variable. + (msgmerge_LDADD): Add OPENMP_CFLAGS. + +2006-07-25 Bruno Haible + + * Makefile.msvc: Remove file. + * Makefile.am (EXTRA_DIST): Remove Makefile.msvc. + +2006-07-25 Bruno Haible + + * Makefile.vms: Remove file. + * Makefile.am (EXTRA_DIST): Remove Makefile.vms. + +2006-07-24 Bruno Haible + + * Makefile.am (include_HEADERS): Remove variable. + (nodist_include_HEADERS): New variable. + Suggested by Ralf Corsepius . + +2006-07-21 Bruno Haible + + * gettext-0.15 released. + +2006-07-20 Bruno Haible + + * gettext-po.h.in (LIBGETTEXTPO_VERSION): Bump version number. + +2006-07-20 Bruno Haible + + * Makefile.am (LTV_CURRENT, LTV_REVISION, LTV_AGE): Bump to 2:0:2. + +2006-07-14 Bruno Haible + + * Makefile.am (MOSTLYCLEANFILES): New variable. + +2006-07-02 Bruno Haible + + * write-csharp.c (msgdomain_write_csharp): Update. + * write-java.c (msgdomain_write_java): Update. + +2006-06-29 Bruno Haible + + * write-csharp.c (msgdomain_write_csharp): Update. + * write-java.c (msgdomain_write_java): Likewise. + +2006-06-28 Bruno Haible + + Assume on all Unix platforms. Assume closedir works. + * msginit.c: Don't include , , . + (find_pot): Use closedir directly. + +2006-06-27 Bruno Haible + + Assume correct S_ISDIR macro. + * write-csharp.c: Remove test of STAT_MACROS_BROKEN. + * write-java.c: Likewise. + +2006-04-14 Bruno Haible + + Assume autoconf >= 2.60. + * Makefile.am (localedir): Remove variable. + +2006-06-21 Bruno Haible + + * x-sh.c (read_word): Recognize the Bash process substitution syntax. + +2006-06-21 Bruno Haible + + * x-sh.c (read_word): Recognize $(...) and $((...)) also inside + double-quoted strings. + Reported by Michelle Konzack . + +2006-06-04 Bruno Haible + + * msgl-check.c (check_plural_eval): Declare 'distribution' as volatile, + so that it is unaffected by longjmp. + +2006-06-04 Bruno Haible + + * xgettext.c (arglist_parser_done): Cast length argument for format + string. + +2006-05-31 Bruno Haible + + * x-ycp.c (phase5_pushback, phase5_pushback_length): New variables. + (phase5_get): Renamed from x_ycp_lex. + (phase5_unget): New function. + (phase8_get): New function. + (extract_parenthesized): Inside i18n construct, use phase8_get + instead of phase5_get. + Reported by Karl Eichwalder . + +2006-05-22 Bruno Haible + + * msgfilter.c: Include also on Minix. + Reported by Leonard den Ottolander . + +2006-05-17 Bruno Haible + + Cygwin portability. + * hostname.c (WIN32_NATIVE): Renamed from WIN32. + +2006-05-16 Bruno Haible + + * Makefile.am (CLEANFILES): Add gettext-po.h. + +2006-05-16 Bruno Haible + + * xgettext.c (main): Use a size_t index variable for looping over the + files. + (arglist_parser_done): Fix a format string. + +2006-05-16 Bruno Haible + + * msgfilter.c (main): Change type of i. + +2006-05-15 Bruno Haible + + * Makefile.am: Test flag WOE32DLL instead of CYGWINDLL. + +2006-05-14 Bruno Haible + + * x-php.c (x_php_lex): Fix handling of here documents. + Reported by Gaëtan Frenoy . + +2006-05-12 Bruno Haible + + * write-csharp.c [MINGW]: Include . + (mkdir): Define using _mkdir. + * write-java.c [MINGW]: Include . + (mkdir): Define using _mkdir. + +2006-05-11 Bruno Haible + + * gettext-po.h.in: Renamed from gettext-po.h. + * Makefile.am: Treat condition WOE32DLL like MINGW. Update file names. + (libgettextsrc_la_SOURCES, libgettextsrc_la_LDFLAGS): Modify for + Cygwin. + libgettextpo_la_SOURCES, libgettextpo_la_LDFLAGS): Likewise. + (BUILT_SOURCES): Add gettext-po.h. + (EXTRA_DIST): Add gettext-po.h.in. + (gettext-po.h): New rule. + +2006-05-11 Bruno Haible + + * Makefile.am (format_CFLAGS): Remove variable. + (format.lo): Remove variable. + (FORMAT_SOURCE) [MINGW]: Use ../mingw/c++format.cc instead of format.c. + +2006-05-09 Bruno Haible + + * plural-eval.h (USE_SIGINFO): Don't define on Cygwin. + +2006-05-02 Charles Wilson + + * write-mo.h: Remove DLL_VARIABLE declarations, not needed since + write-mo.c is not compiled into a shared library. + * lang-table.h: Likewise. + +2006-05-02 Charles Wilson + + * filters.h: Add C++ guards. + +2006-05-07 Bruno Haible + + * x-perl.c (extract_variable): Fix syntax error introduced on + 2005-10-03. + +2006-05-07 Bruno Haible + + * format-awk.c: Include xvasprintf.h instead of xerror.h. + * format-elisp.c: Likewise. + * format-librep.c: Likewise. + * format-pascal.c: Likewise. + * format-php.c: Likewise. + * format-tcl.c: Likewise. + +2006-04-30 Bruno Haible + + * write-java.c (msgdomain_write_java): Specify source-version 1.3 and + require target-version 1.1. + +2006-04-30 Bruno Haible + + * msginit.c (main): Simplify by use of xasprintf(). + * write-java.c: Include xvasprintf.h. + (msgdomain_write_java): Simplify by use of xasprintf(). + * x-c.c: Include xvasprintf.h. + (phase8a_get): Simplify by use of xasprintf(). + * xgettext.c: Don't include stpcpy.h. + (remember_a_message, remember_a_message_plural): Simplify by use of + xasprintf(). + +2006-04-30 Bruno Haible + + * msgfmt.c: Include xvasprintf.h. + * msginit.c: Likewise. + * msgl-cat.c: Likewise. + * msgl-charset.c: Likewise. + * po-xerror.c: Likewise. + * x-csharp.c: Likewise. + * x-glade.c: Likewise. + * x-python.c: Likewise. + * xgettext.c: Likewise. + * format-boost.c: Include xvasprintf.h instead of xerror.h. + * format-c.c: Likewise. + * format-csharp.c: Likewise. + * format-gcc-internal.c: Likewise. + * format-java.c: Likewise. + * format-lisp.c: Likewise. + * format-perl.c: Likewise. + * format-python.c: Likewise. + * format-qt.c: Likewise. + * format-scheme.c: Likewise. + * format-ycp.c: Likewise. + * msgexec.c: Likewise. + * msgl-check.c: Likewise. + * msgl-iconv.c: Likewise. + * open-po.c: Likewise. + * po-charset.c: Likewise. + * po-lex.c: Likewise. + * po-time.c: Likewise. + * read-po-abstract.c: Likewise. + * read-properties.c: Likewise. + * read-stringtable.c: Likewise. + * write-po.c: Likewise. + +2006-04-30 Bruno Haible + + * msginit.c (project_id, project_id_version, get_user_email): Close + the FILE in case of I/O error. + +2006-04-30 Bruno Haible + + * write-java.c (msgdomain_write_java): Don't fclose the FILE after + fwriteerror signalled a failure. + * write-csharp.c (msgdomain_write_csharp): Likewise. + +2006-04-23 Bruno Haible + + * msgexec.c: Include unconditionally. + * msgfilter.c: Likewise. + * msggrep.c: Likewise. + * msginit.c: Likewise. + * hostname.c: Likewise. + * urlget.c: Likewise. + +2006-04-17 Bruno Haible + + * Makefile.am: Use $(mkdir_p) instead of $(mkinstalldirs). + +2006-04-09 Bruno Haible + + * write-csharp.c: Include clean-temp.h. Don't include unistd.h, + mkdtemp.h, tmpdir.h, pathmax.h, fatal-signal.h, xallocsa.h. + (cleanup_list): Remove variable. + (cleanup): Remove function. + (msgdomain_write_csharp): Simplify handling of temporary directory. + * write-java.c: Include clean-temp.h. Don't include unistd.h, + mkdtemp.h, tmpdir.h, pathmax.h, fatal-signal.h. + (cleanup_list): Remove variable. + (cleanup): Remove function. + (msgdomain_write_java): Simplify handling of temporary directory. + +2006-04-03 Bruno Haible + + * x-glade.c (p_XML_GetCurrentLineNumber, p_XML_GetCurrentColumnNumber): + Declare differently for expat >= 2.0.0. + (load_libexpat): Search for a differently library name for expat >= + 2.0.0. + (do_extract_glade): Update. + Reported by Mike Frysinger . + +2006-04-02 Bruno Haible + + * hostname.c: Include propername.h. + (main): Call proper_name. + * msgattrib.c: Include propername.h. + (main): Call proper_name. + * msgcat.c: Include propername.h. + (main): Call proper_name. + * msgcmp.c: Include propername.h. + (main): Call proper_name. + * msgcomm.c: Include propername.h. + (main): Call proper_name. + * msgconv.c: Include propername.h. + (main): Call proper_name. + * msgen.c: Include propername.h. + (main): Call proper_name. + * msgexec.c: Include propername.h. + (main): Call proper_name. + * msgfilter.c: Include propername.h. + (main): Call proper_name. + * msgfmt.c: Include propername.h. + (main): Call proper_name. + * msggrep.c: Include propername.h. + (main): Call proper_name. + * msginit.c: Include propername.h. + (main): Call proper_name. + * msgmerge.c: Include propername.h. + (main): Call proper_name. + * msgunfmt.c: Include propername.h. + (main): Call proper_name. + * msguniq.c: Include propername.h. + (main): Call proper_name. + * recode-sr-latin.c: Include propername.h. + (main): Call proper_name and proper_name_utf8. + * urlget.c: Include propername.h. + (main): Call proper_name. + * xgettext.c: Include propername.h. + (main): Call proper_name. + +2006-04-01 Bruno Haible + + * xgettext.c (main): Treat --keyword= like --keyword. + +2006-03-28 Bruno Haible + + * filters.h: New file. + * filter-sr-latin.c: New file, based on code from Danilo Šegan. + * recode-sr-latin.c: New file. + * msgfilter.c: Include filters.h, msgl-iconv.h, po-charset.h. + (filter): New variable. + (main): Recognize recode-sr-latin as built-in. + (generic_filter): New function, extracted from process_string. + (process_string): Call the filter. + * Makefile.am (bin_PROGRAMS): Add recode-sr-latin. + (noinst_HEADERS): Add filters.h. + (msgfilter_SOURCES): Add filter-sr-latin.c. + (recode_sr_latin_SOURCES, recode_sr_latin_LDADD, + recode_sr_latin_CPPFLAGS, recode_sr_latin_LDFLAGS): New variables. + * Makefile.msvc (PROGRAMS): Add recode-sr-latin.exe. + (msgfilter_OBJECTS): Add filter-sr-latin.obj. + (recode_sr_latin_OBJECTS): New variable. + (filter-sr-latin.obj, recode-sr-latin.obj): New rules. + (recode-sr-latin.exe): New rule. + (install): Also install recode-sr-latin.exe. + (uninstall): Also uninstall recode-sr-latin.exe. + * Makefile.vms (PROGRAMS): Add recode-sr-latin.exe. + (msgfilter_OBJECTS): Add filter-sr-latin.obj. + (recode_sr_latin_OBJECTS): New variable. + (filter-sr-latin.obj, recode-sr-latin.obj): New rules. + (recode-sr-latin.exe): New rule. + (install): Also install recode-sr-latin.exe. + (uninstall): Also uninstall recode-sr-latin.exe. + +2006-03-28 Bruno Haible + + * msgl-iconv.c: Include iconvstring.h. Don't include . + (iconv_string): Remove function, moved to ../lib/. + +2006-03-19 Bruno Haible + + * xgettext.h (struct callshape): New field 'xcomments'. + (struct partial_call): New field 'xcomments'. + * xgettext.c (split_keywordspec): Accept the ID:ARGNUM...,"XCOMMENT" + syntax. + (insert_keyword_callshape): Store the xcomments from the new shape. + (arglist_parser_alloc, arglist_parser_clone): Update. + (arglist_parser_done): After calling remember_a_message, attach the + specified extracted comments. + * x-perl.c (extract_variable): Update. + Suggested by Behdad Esfahbod . + +2006-03-19 Bruno Haible + + * xgettext.c (arglist_parser_done): Use error_at_line instead of error. + +2006-03-16 Bruno Haible + + * msggrep.c (grep_task): Increment size. + (long_options): Add --extracted-comment option. + (main): Handle --extracted-comment option. + (no_pass, usage): Update. + (is_message_selected_no_invert): Implement matching on extracted + comments. + Suggested by Behdad Esfahbod . + +2006-03-16 Bruno Haible + + Handle GNOME glib context syntax. + * xgettext.h (struct callshape): Add fields argnum1_glib_context, + argnum2_glib_context. + (struct partial_call): Add fields argnum1_glib_context, + argnum2_glib_context. + * xgettext.c (split_keywordspec): Recognize suffix 'g' and set the + argnum1_glib_context, argnum2_glib_context fields. + (insert_keyword_callshape, arglist_parser_alloc, arglist_parser_clone): + Update. + (arglist_parser_done): When suffix 'g' was specified, split off the + context from the msgid and/or msgid_plural. + * x-perl.c (extract_variable): Update. + Suggested by Danilo Šegan . + +2006-03-16 Bruno Haible + + * xgettext.c (set_format_flags_from_context): Break long line. + +2006-03-11 Bruno Haible + + * message.c (fuzzy_search_goal_function): Use 'volatile double'. + +2006-03-11 Bruno Haible + + Speed up msgmerge with large compendia. + * message.h (message_list_free): Add keep_messages argument. + (message_list_list_free): Add keep_level argument. + (fuzzy_search_goal_function): New declaration. + (FUZZY_THRESHOLD): New macro. + * message.c (message_list_free): Add keep_messages argument. + (fuzzy_search_goal_function): New function, extracted from + message_list_search_fuzzy_inner. + (message_list_search_fuzzy_inner): Use it. + (message_list_search_fuzzy): Use symbolic value FUZZY_THRESHOLD. + (message_list_list_free): Comment in. Add keep_level argument. + (message_list_list_search_fuzzy): Comment out. Use symbolic value + FUZZY_THRESHOLD. + (msgdomain_free): Update. + (msgdomain_list_search_fuzzy): Use symbolic value FUZZY_THRESHOLD. + * po-charset.h: Include stddef.h. + (character_iterator_t): New type. + (po_charset_character_iterator): New declaration. + * po-charset.c (char_iterator, euc_character_iterator, + euc_jp_character_iterator, euc_tw_character_iterator, + big5_character_iterator, big5hkscs_character_iterator, + gbk_character_iterator, gb18030_character_iterator, + shift_jis_character_iterator, johab_character_iterator, + utf8_character_iterator, po_charset_character_iterator): New functions. + * msgl-fsearch.h: New file. + * msgl-fsearch.c: New file. + * msgmerge.c: Include po-charset.h, msgl-fsearch.h. + (compendium_filenames): New variable. + (compendium): Also put the filename into compendium_filenames. + (definitions_ty): New structure type. + (definitions_init, definitions_init_findex, definitions_current_list, + definitions_set_current_list, definitions_search, + definitions_search_fuzzy, definitions_destroy): New functions. + (match_domain): Change type of 'definitions' argument. + (merge): Also convert the compendium to UTF-8. Use definitions_init, + definitions_set_current_list, definitions_destroy. + * msgfmt.c (main): Update. + * Makefile.am (noinst_HEADERS): Add msgl-fsearch.h. + (msgmerge_SOURCES): Add msgl-fsearch.c. + (msgmerge_LDADD): Link with MSGMERGE_LIBM. + * Makefile.msvc (msgmerge_OBJECTS): Add msgl-fsearch.obj. + (msgl-fsearch.obj): New rule. + * Makefile.vms (msgmerge_OBJECTS): Add msgl-fsearch.obj. + (msgl-fsearch.obj): New rule. + Reported by Clytie Siddall . + +2006-03-09 Bruno Haible + + * Makefile.am (CXXLINK) [!mingw]: Overwrite automake's value. Fixes + unintended dependency on libstdc++ introduced on 2005-07-05. + +2005-10-09 Bruno Haible + + * lang-table.h: New file. + * lang-table.c: New file, extracted from msginit.c. + * msginit.c: Include lang-table.h. + (englishname_of_language): Use language_table. + * Makefile.am (noinst_HEADERS): Add lang-table.h. + (msginit_SOURCES): Add lang-table.c. + * Makefile.msvc (MSGINIT_OBJECTS): Add lang-table.obj. + (lang-table.obj): New rule. + * Makefile.vms (MSGINIT_OBJECTS): Add lang-table.obj. + (lang-table.obj): New rule. + +2006-02-12 Bruno Haible + + * message.h (format_type): New enum value 'format_boost'. + (NFORMATS): Increment. + * message.c (format_language): Add format_boost entry. + (format_language_pretty): Likewise. + * format.h (formatstring_boost): New declaration. + * format-boost.c: New file, based on format-c.c. + * format.c (formatstring_parsers): Add formatstring_boost. + * x-c.c (init_flag_table_c): Also register flags for boost-format. + * xgettext.c (flag_table_cxx_qt, flag_table_cxx_boost): New variables. + (recognize_format_boost): New variable. + (main): Handle --boost option. + (usage): Document --boost option. + (xgettext_record_flag): Also fill flag_table_cxx_qt, + flag_table_cxx_boost. + (remember_a_message, remember_a_message_plural): In the heuristics, + don't mark a string as c-format that is already known to be a qt-format + or boost-format. + (language_to_extractor): Use a flag_table_cxx_qt or + flag_table_cxx_boost that is different from flag_table_c. + * Makefile.am (FORMAT_SOURCE): Add format-boost.c. + * Makefile.msvc (OBJECTS): Add format-boost.obj. + (format-boost.obj): New rule. + * Makefile.vms (OBJECTS): Add format-boost.obj. + (format-boost.obj): New rule. + * FILES: Update. + +2006-02-12 Bruno Haible + + * xgettext.c (xgettext_record_flag): When asked to set "qt-format", + really set "qt-format", not "c-format". Fixes bug introduced on + 2003-10-19. + +2006-02-12 Bruno Haible + + Reduce the number of false positives produced by the c-format guessing + heuristics. + * format.h (struct formatstring_parser): Add 'is_unlikely_intentional' + field. + * format-awk.c (formatstring_awk): Update. + * format-c.c (struct spec): Add 'unlikely_unintentional' field. + (format_parse): Set unlikely_unintentional to true when encountering + a "%...%" directive other than "%%". + (format_is_unlikely_intentional): New function. + (formatstring_c, formatstring_objc): Use it. + * format-csharp.c (formatstring_csharp): Update. + * format-elisp.c (formatstring_elisp): Update. + * format-gcc-internal.c (formatstring_gcc_internal): Update. + * format-java.c (formatstring_java): Update. + * format-librep.c (formatstring_librep): Update. + * format-lisp.c (formatstring_lisp): Update. + * format-pascal.c (formatstring_pascal): Update. + * format-perl.c (formatstring_perl): Update. + * format-perl-brace.c (formatstring_perl_brace): Update. + * format-php.c (format_php): Update. + * format-python.c (formatstring_python): Update. + * format-qt.c (formatstring_qt): Update. + * format-scheme.c (formatstring_scheme): Update + * format-sh.c (formatstring_sh): Update. + * format-tcl.c (formatstring_tcl): Update. + * format-ycp.c (formatstring_ycp, formatstring_smalltalk): Update. + * xgettext.c (remember_a_message, remember_a_message_plural): Don't + mark the message as c-format if the validity as c-format looks + unintentional. + +2006-02-12 Bruno Haible + + * msgattrib.c (main): Update year in --version output. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + +2006-01-25 Bruno Haible + + * x-php.c (x_php_lex): Treat byte values >= 127 like alphabetic. + Reported by Hagen Fritsch . + +2005-12-25 Bruno Haible + + Tell Python developers to use named arguments in format strings. + * format.h (get_python_format_unnamed_arg_count): New declaration. + * format-python.c (get_python_format_unnamed_arg_count): New function. + * xgettext.c (warn_format_string): New function. + (remember_a_message, remember_a_message_plural): Call it. + Suggested by Martin von Löwis. + +2005-12-07 Bruno Haible + + * msgl-iconv.c (iconv_message_list): Fix syntax error. + Reported by Dave Patton . + +2005-12-04 Bruno Haible + + * Makefile.am (CSHARPCOMPFLAGS): Use value set by csharpcomp.m4. + +2005-11-29 Colin Watson + + * msggrep.c (invert_match): New variable. + (long_options): Add --invert-match option. + (main): Handle --invert-match option. + (usage): Document --invert-match option. + (is_message_selected_no_invert): New function, extracted from + is_message_selected. + (is_message_selected): Call it. Handle match inversion. + +2005-11-01 Bruno Haible + + * write-csharp.c (write_csharp_code): Add culture_name argument. + Emit an AssemblyCulture note for it. + (msgdomain_write_csharp): Update. + Suggested by Pepa . + +2005-10-09 Bruno Haible + + * plural-eval.h: New file. + * plural-eval.c: Include plural-eval.h. + (sigfpe_exit, sigfpe_code, sigfpe_handler, install_sigfpe_handler, + uninstall_sigfpe_handler): New definitions, moved here from + msgl-check.c. + * msgl-check.c: Include plural.eval.h. + (sigjmp_buf, sigsetjmp, siglongjmp, USE_SIGINFO): Move to plural-eval.h. + (sigfpe_exit, sigfpe_code, sigfpe_handler, install_sigfpe_handler, + uninstall_sigfpe_handler): Move to plural-eval.c. + * Makefile.am (noinst_HEADERS): Add plural-eval.h. + +2005-10-09 Bruno Haible + + * msgl-check.c: Include c-ctype.h instead of ctype.h. + (check_plural): Use c_isspace instead of isspace. + * x-librep.c: Include c-ctype.h instead of ctype.h. + (read_token): Use c_isxdigit instead of isxdigit. + * x-rst.c: Include c-ctype.h instead of ctype.h. + (extract_rst): Use c_isdigit instead of isdigit. + * x-ycp.c: Don't include ctype.h. + +2005-10-09 Bruno Haible + + * plural-exp.c: Renamed from plural.c. + * Makefile.am (libgettextsrc_la_SOURCES): Update. + +2005-10-18 Bruno Haible + + Disambiguate overloaded function calls according to argument count. + * xgettext.h (struct callshape): Add field 'argtotal'. + (struct partial_call): Likewise. + (arglist_parser_done): Add argnum argument. + * xgettext.c (split_keywordspec): Recognize NNt syntax and fill + argtotal. + (insert_keyword_callshape, arglist_parser_alloc, arglist_parser_clone): + Update. + (arglist_parser_decidedp): Compare also the argtotal if given. + (arglist_parser_done): Add argnum argument. + * x-awk.c (extract_parenthesized): Pass arg count to + arglist_parser_done. + * x-c.c (extract_parenthesized): Likewise. + * x-csharp.c (extract_parenthesized): Likewise. + * x-elisp.c (read_object): Likewise. + * x-java.c (extract_parenthesized): Likewise. + * x-librep.c (read_object): Likewise. + * x-lisp.c (read_object): Likewise. + * x-perl.c (extract_variable): Update. + (extract_balanced): Pass arg count to arglist_parser_done. + * x-php.c (extract_parenthesized): Likewise. + * x-python.c (extract_parenthesized): Likewise. + * x-scheme.c (read_object): Likewise. + * x-sh.c (read_command): Likewise. + * x-tcl.c (read_command): Likewise. + Stimulated by a patch from + Chusslove Illich (Часлав Илић) . + +2005-10-09 Bruno Haible + + * msginit.c (catalogname_for_locale, language_of_locale): Drop support + of CEN locale name syntax. + +2005-10-05 Bruno Haible + + * format.h (check_msgid_msgstr_format): Add plural_distribution + argument. + * format.c (check_msgid_msgstr_format): Add plural_distribution + argument. Use it for index dependent strictness. + * msgl-check.h (check_message): Add plural_distribution argument. + * msgl-check.c: Include xalloc.h. + (check_plural_eval): Create a plural_distribution array as additional + output parameter. + (check_plural): Return plural_distribution array as additional output + parameter. + (check_pair, check_message): Add plural_distribution argument. + (check_message_list): Pass the plural_distribution from check_plural + to check_message. + * gettext-po.c (po_message_check_format): Update. + +2005-10-04 Bruno Haible + + Combine all msgfmt checks in a single place. + * format.h (check_msgid_msgstr_format): Change return type to int. + * format.c (check_msgid_msgstr_format): Return the number of errors. + * msgl-check.h (check_plural): Remove declaration. + * msgl-check.c (check_plural): Make static. Return the number of + errors. + (check_pair): Return the number of errors. + (check_message_list): Likewise. + * msgfmt.c (main): Perform the message checks here, after all files + were read. Call check_message_list instead of check_plural and + check_message. Emit statistics of fatal errors. + (msgfmt_frob_new_message): Don't call check_message here. + +2005-10-04 Bruno Haible + + * msgl-check.h (check_message_list): New declaration. + * msgl-check.c (check_message_list): New function. + * gettext-po.c (po_file_check_all, po_message_check_all): Call + check_message_list instead of check_message and check_plural. + +2005-10-04 Bruno Haible + + Fix handling of obsolete messages. + * msgl-check.c (check_plural): Ignore obsolete messages. + * gettext-po.c (po_file_check_all, po_message_check_all, + po_message_check_format): Likewise. + +2005-10-04 Bruno Haible + + * msgl-check.h (check_message): Remove msgstr_pos argument. + * msgl-check.c (check_pair, check_message): Remove msgstr_pos argument. + * msgfmt.c (msgfmt_frob_new_message): Update. + * gettext-po.c (po_file_check_all, po_message_check_all, + po_message_check_format): Update. + +2005-10-03 Bruno Haible + + Add support for contexts in xgettext. + * xgettext.h: Include stdbool.h. + (struct callshape): New structure type. + (split_keywordspec): Return a struct callshape, not two integers, + by reference. + (struct callshapes): New structure type. + (insert_keyword_callshape): New declaration. + (remember_a_message): Add msgctxt argument. + (struct partial_call): New structure type. + (struct arglist_parser): New structure type. + (arglist_parser_alloc, arglist_parser_clone, arglist_parser_remember, + arglist_parser_decidedp, arglist_parser_done): New declarations. + * xgettext.c (split_keywordspec): Return a struct callshape, not two + integers, by reference. Handle the notation Nc for a context argument. + (insert_keyword_callshape): New function. + (remember_a_message): Add msgctxt argument. Free both strings when + the message is excluded. + (arglist_parser_alloc, arglist_parser_clone, arglist_parser_remember, + arglist_parser_decidedp, arglist_parser_done): New functions. + * x-awk.c (x_awk_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (extract_parenthesized): Replace commas_to_skip, plural_commas + arguments with a single argparser argument. Use arglist_parser_* API. + (extract_awk): Update. + * x-c.c (add_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (init_keywords): Register the new functions [d[c]][n]pgettext. + (init_flag_table_c, init_flag_table_objc): Likewise. + (struct xgettext_token_ty): Replace argnum1, argnum2 with a callshapes + pointer. + (x_c_lex): Update. + (extract_parenthesized): Replace commas_to_skip, plural_commas + arguments with a single argparser argument. Use arglist_parser_* API. + (extract_whole_file): Update. + * x-csharp.c (x_csharp_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (extract_parenthesized): Replace commas_to_skip, plural_commas + arguments with a single argparser argument. Use arglist_parser_* API. + (extract_csharp): Update. + * x-elisp.c (x_elisp_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (read_object): Use arglist_parser_* API. + * x-glade.c (start_element_handler, end_element_handler): Update. + * x-java.c (x_java_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (extract_parenthesized): Replace commas_to_skip, plural_commas + arguments with a single argparser argument. Use arglist_parser_* API. + (extract_java): Update. + * x-librep.c (x_librep_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (read_object): Use arglist_parser_* API. + * x-lisp.c (x_lisp_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (read_object): Use arglist_parser_* API. + * x-perl.c (x_perl_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (extract_balanced): Replace arg_sg, arg_pl arguments with arg, + argparser arguments. Use arglist_parser_* API. + (extract_variable, interpolate_keywords, extract_perl): Update. + * x-php.c (x_php_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (extract_parenthesized): Replace commas_to_skip, plural_commas + arguments with a single argparser argument. Use arglist_parser_* API. + (extract_php): Update. + * x-python.c (x_python_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (extract_parenthesized): Replace commas_to_skip, plural_commas + arguments with a single argparser argument. Use arglist_parser_* API. + (extract_python): Update. + * x-rst.c (extract_rst): Update. + * x-scheme.c (x_scheme_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (read_object): Use arglist_parser_* API. + * x-sh.c (x_sh_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (read_word): Update. + (read_command): Use arglist_parser_* API. + * x-smalltalk.c (extract_smalltalk): Update. + * x-tcl.c (x_tcl_keyword): Use callshape API: split_keywordspec, + insert_keyword_callshape. + (read_command): Use arglist_parser_* API. + * x-ycp.c (extract_parenthesized): Update. + +2005-10-03 Bruno Haible + + * xgettext.h (savable_comment_to_xgettext_comment): Remove + declaration. + (remember_a_message, remember_a_message_plural): Add a comment + argument. + * xgettext.c (savable_comment_to_xgettext_comment): Make static. + (remember_a_message): Add comment argument. Call + savable_comment_to_xgettext_comment and savable_comment_reset at the + end. + (remember_a_message_plural): Add comment argument. Call + savable_comment_to_xgettext_comment and xgettext_comment_reset, + savable_comment_reset at the end. + * x-awk.c (extract_parenthesized): Pass comment to remember_a_message. + * x-c.c (extract_parenthesized): Likewise. + * x-csharp.c (extract_parenthesized): Likewise. + * x-elisp.c (read_object): Likewise. + * x-glade.c (start_element_handler, end_element_handler): Likewise. + * x-java.c (extract_parenthesized): Likewise. + * x-librep.c (read_object): Likewise. + * x-lisp.c (read_object): Likewise. + * x-perl.c (extract_variable, interpolate_keywords, extract_balanced): + Likewise. + * x-php.c (extract_parenthesized): Likewise. + * x-python.c (extract_parenthesized): Likewise. + * x-scheme.c (read_object): Likewise. + * x-sh.c (read_word, read_command): Likewise. + * x-smalltalk.c (extract_smalltalk): Likewise. + * x-tcl.c (read_command): Likewise. + * x-ycp.c (extract_parenthesized): Likewise. + * x-rst.c (extract_rst): Update. + +2005-10-03 Bruno Haible + + * xgettext.h (xgettext_comment_add, xgettext_comment, + xgettext_comment_reset): Remove declarations. + * xgettext.c (xgettext_comment_add, xgettext_comment, + xgettext_comment_reset): Make static. + +2005-10-03 Bruno Haible + + Use savable_comment_* API instead of xgettext_comment_* API. + * x-awk.c (phase2_getc): Call savable_comment_add instead of + xgettext_comment_add. + (x_awk_lex): Call savable_comment_reset instead of + xgettext_comment_reset. + (extract_parenthesized): Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-elisp.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (read_object): Call savable_comment_reset instead of + xgettext_comment_reset. Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-glade.c (start_element_handler, end_element_handler): Call + savable_comment_to_xgettext_comment and savable_comment_reset around + remember_a_message. Call savable_comment_reset instead of + xgettext_comment_reset. + (comment_handler): Call savable_comment_add instead of + xgettext_comment_add. + * x-librep.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (read_object): Call savable_comment_reset instead of + xgettext_comment_reset. Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-lisp.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (read_object): Call savable_comment_reset instead of + xgettext_comment_reset. Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-perl.c (phase2_getc): Call savable_comment_add instead of + xgettext_comment_add. + (extract_variable, interpolate_keywords): Call + savable_comment_to_xgettext_comment and savable_comment_reset around + remember_a_message. + (x_perl_prelex): Call savable_comment_reset instead of + xgettext_comment_reset. + (extract_balanced): Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-php.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (x_php_lex): Call savable_comment_reset instead of + xgettext_comment_reset. + (extract_parenthesized): Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-scheme.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (read_object): Call savable_comment_reset instead of + xgettext_comment_reset. Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-sh.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (read_word): Call savable_comment_reset instead of + xgettext_comment_reset. Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + (read_command): Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-smalltalk.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (phase2_get): Call savable_comment_reset instead of + xgettext_comment_reset. + (extract_smalltalk): Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-tcl.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (read_word): Call savable_comment_reset instead of + xgettext_comment_reset. + (read_command): Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + * x-ycp.c (phase2_getc): Call savable_comment_add instead of + xgettext_comment_add. + (x_ycp_lex): Call savable_comment_reset instead of + xgettext_comment_reset. + (extract_parenthesized): Call savable_comment_to_xgettext_comment and + savable_comment_reset around remember_a_message. + +2005-10-03 Bruno Haible + + * message.c (message_list_hash_insert_entry): Update. + +2005-10-03 Bruno Haible + + * message.c (message_list_alloc, message_list_free, + message_list_hash_insert_entry, message_list_remove_if_not, + message_list_msgids_changed, message_list_search): Update. + * write-qt.c (write_qm): Update. + * xgettext.c (flag_context_list_table_lookup, + flag_context_list_table_insert): Update. + * x-awk.c (x_awk_keyword, extract_parenthesized): Update. + * x-c.c (add_keyword, x_c_lex): Update. + * x-csharp.c (x_csharp_keyword, extract_parenthesized: Update. + * x-elisp.c (x_elisp_keyword, read_object): Update. + * x-glade.c (x_glade_keyword, start_element_handler): Update. + * x-java.c (x_java_keyword, extract_parenthesized): Update. + * x-librep.c (x_librep_keyword, read_object): Update. + * x-lisp.c (x_lisp_keyword, read_object): Update. + * x-perl.c (x_perl_keyword, extract_variable, interpolate_keywords, + extract_balanced): Update. + * x-php.c (x_php_keyword, extract_parenthesized): Update. + * x-python.c (x_python_keyword, extract_parenthesized): Update. + * x-scheme.c (x_scheme_keyword, read_object): Update. + * x-sh.c (x_sh_keyword, read_command): Update. + * x-tcl.c (x_tcl_keyword, read_command): Update. + +2005-10-01 Bruno Haible + + Support for context dependent translations in PO files. + * message.h (MSGCTXT_SEPARATOR): New macro. + (struct message_ty): Add 'msgctxt' field. + (message_alloc): Add msgctxt argument. + (is_header): New macro. + (message_list_search, message_list_search_fuzzy, + message_list_list_search, message_list_list_search_fuzzy, + msgdomain_list_search, msgdomain_list_search_fuzzy): Add msgctxt + argument. + * message.c: Include xallocsa.h. + (message_alloc): Add msgctxt argument. + (message_copy): Update. + (message_list_hash_insert_entry): New function. + (message_list_append, message_list_prepend, message_list_insert_at, + message_list_msgids_changed): Use it. + (message_list_search): Add msgctxt argument. + (message_list_search_fuzzy_inner): Likewise. + (message_list_search_fuzzy): Likewise. + (message_list_list_search): Likewise. + (message_list_list_search_fuzzy): Likewise. + * msgl-ascii.c (is_ascii_message): Also test the msgctxt. + * write-po.c (message_print): Warn if some msgctxt has non-ASCII + characters. Write out the msgctxt. + (message_print_obsolete): Likewise. + (msgdomain_list_print_po): Use is_header macro. + (msgdomain_list_print): Likewise. Bail out if contexts are present and + cannot be stored in the given output format. + * write-properties.c (write_message): Use is_header macro. + * po-lex.c: Include message.h. + (keyword_p): Also recognize 'msgctxt'. + (po_gram_lex): Bail out if a string contains the EOT character. + * read-po-abstract.h (struct abstract_po_reader_class_ty): Add msgctxt + argument to directive_message function pointer. + (po_callback_message): Add msgctxt argument. + * read-po-abstract.c (call_directive_message, po_callback_message): Add + msgctxt argument. + * po-gram-gen.y (do_callback_message): Add msgctxt argument. Use + is_header macro. + (MSGCTXT): New token type. + (message_intro, MSGCTXT): Declare return types. + (message): Use message_intro instead of just MSGID. + (message_intro): New nonterminal reduction rules. + * read-properties.c (properties_parse): Update. + * read-stringtable.c (stringtable_parse): Update. + * read-po.h (struct default_po_reader_class_ty): Add msgctxt argument + to add_message field. + (default_directive_message, default_add_message): Likewise. + * read-po.c (call_add_message, default_directive_message, + default_add_message): Add msgctxt argument. + * msgl-iconv.c (convert_msgid): Also convert the msgctxt. + (iconv_message_list): Use is_header macro. Test also the msgctxt for + non-ASCII-ness. + * msgl-cat.c (is_message_selected, is_message_needed): Use is_header + macro. + (catenate_msgdomain_list): Likewise. Update. + * msgl-equal.c (message_equal): Likewise. Also compare the msgctxt. + * msgcmp.c (is_message_selected): Use is_header macro. + (match_domain): Update. + (compare): Use is_header macro. + * msgmerge.c (message_merge, match_domain): Use is_header macro. + Update. + (merge): Update. + * msgattrib.c (is_message_selected): Use is_header macro. + (process_message_list): Likewise. Update. + * msgl-charset.c (compare_po_locale_charsets): Use is_header macro. + * msgexec.c (process_string): Also set or unset MSGEXEC_MSGCTXT + variable. + * msgfilter.c (process_message): Use is_header macro. + * msggrep.c (grep_task): Increase size from 3 to 4. + (long_options): Add --msgctxt option. + (main): Accept -J/--msgctxt option. Update grep_pass numbers. + (no_pass): Update. + (usage): Mention -J option. + (is_message_selected): Use is_header macro. Perform a new grep pass on + the msgctxt. + * msginit.c (fill_header): Use is_header macro. Update. + (update_msgstr_plurals): Update. + * read-mo.c (read_mo_file): Split msgid into msgctxt and msgid. Update. + * read-tcl.c (msgdomain_read_tcl): Use is_header macro. + * msgl-check.c (check_plural): Update. + (check_message): Use is_header macro. + * write-mo.c (write_table): Write msgid with msgctxt, instead of just + the msgid. + * write-java.c: Include xerror.h. + (write_java_code): Update. + (msgdomain_write_java): Give error if some entries have a context. + * write-csharp.c: Include xerror.h. + (write_csharp_code): Update. + (msgdomain_write_csharp): Give error if some entries have a context. + * write-resources.c (msgdomain_write_csharp_resources): Bail out if + contexts are present and cannot be stored in the given output format. + * write-tcl.c (write_msg): Use is_header macro. + (msgdomain_write_tcl): Bail out if contexts are present and cannot be + stored in the given output format. + * write-qt.c: Include hash.h. + (write_qm): Use is_header macro. Write out msgctxt if present. Write + a contexts section if appropriate. + * msgfmt.c (msgfmt_add_message): Add msgctxt argument. + (msgfmt_frob_new_message): Use is_header macro. + * xgettext.c (exclude_directive_message): Add msgctxt argument. + (remember_a_message, construct_header, finalize_header): Update. + * x-po.c (extract_add_message): Add msgctxt argument. Update. + (extract): Update. + * gettext-po.h (po_message_msgctxt, po_message_set_msgctxt): New + declarations. + * gettext-po.c (po_file_domain_header): Use is_header macro. + (po_message_create): Update. + (po_message_msgctxt, po_message_set_msgctxt): New functions. + (po_message_check_all): Use is_header macro. + +2005-10-01 Bruno Haible + + Avoid a crash when msgcat or msgconv is asked to convert a non-ASCII + msgid. + * msgl-iconv.h (iconv_message_list): Change return type to boolean. + * msgl-iconv.c (iconv_message_list): Likewise. Return true if some + msgids change. + * msgl-cat.c (catenate_msgdomain_list): Exit with an error message if + the msgids change through charset conversion. + +2005-10-04 Bruno Haible + + * format.c: Include stdbool.h. + (check_msgid_msgstr_format): Enable strict checking also when + msgid_plural is present but nplurals = 1. + Reported by Farzaneh Sarafraz . + +2005-10-01 Bruno Haible + + Change msgattrib to no longer remove extracted comments and file + positions when marking messages obsolete. + * msgmerge.c (merge): When marking a message obsolete, remove the + extracted comments and file positions. + * write-po.c (message_print_obsolete): Also print the extracted + comments and file positions. + +2005-09-25 Bruno Haible + + Support for Python source encodings (PEP 0263). + * x-python.c: Include progname.h, basename.h, xerror.h, strstr.h, + c-ctype.h, utf8-ucs4.h. + (phase1_pushback): Reduce size. + (UEOF): New macro. + (phase2_pushback, phase2_pushback_length): New variables. + (phase2_getc, phase2_ungetc): New functions. + (struct unicode_string_buffer): New structure type. + (init_unicode_string_buffer, unicode_string_buffer_append_unicode_grow, + unicode_string_buffer_append_unicode, unicode_string_buffer_result, + free_unicode_string_buffer): New functions. + (comment_buffer): New variable. + (buffer, bufmax, buflen): Remove variables. + (comment_start, comment_add, comment_line_end): Rewritten. + (comment_at_start): New function. + (xgettext_current_file_source_encoding): New variable. + (xgettext_current_file_source_iconv): New variable. + (set_current_file_source_encoding, try_to_extract_coding): New + functions. + (continuation_or_nonblank_line): New variable. + (phase3_getc): Renamed from phase2_getc. Use phase2_getc instead of + phase1_getc. Return a Unicode character. Call try_to_extract_coding + when seeing a comment among the first two lines. + (phase3_ungetc): Renamed from phase2_ungetc. Use phase2_ungetc instead + of phase1_ungetc. + (UNICODE, IS_UNICODE, UNICODE_VALUE): New macros. + (struct mixed_string_buffer): New structure type. + (init_mixed_string_buffer, mixed_string_buffer_append_byte, + mixed_string_buffer_append_unicode_grow, + mixed_string_buffer_append_unicode, + mixed_string_buffer_flush_utf16_surr, + mixed_string_buffer_flush_curr_buffer, mixed_string_buffer_append, + mixed_string_buffer_result, free_mixed_string_buffer): New functions. + (phase7_getuc): Use phase2_getc instead of phase1_getc. Return a + Unicode character except for \ooo and \xnn. + (phase5_get): Operate on the level of Unicode characters instead of + at the level of bytes. Use a mixed_string_buffer to accumulate a + string literal. + (extract_parenthesized): Set xgettext_current_source_encoding to UTF-8 + while passing UTF-8 strings to the xgettext main code. + (extract_python): Initialize xgettext_current_file_source_encoding and + xgettext_current_source_encoding. + +2005-09-25 Bruno Haible + + * x-csharp.c (phase2_getc): Fix mis-use of iconv() when the source + encoding is neither ASCII nor UTF-8. + +2005-09-20 Bruno Haible + + * msgattrib.c (main): Invoke bindtextdomain for bison-runtime. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + * Makefile.am (DEFS): Also define BISON_LOCALEDIR. + +2005-09-17 Bruno Haible + + * msgl-check.h: New file. + * msgl-check.c: New file, mostly extracted from msgfmt.c. + * Makefile.am (noinst_HEADERS): Add msgl-check.h. + (libgettextsrc_la_SOURCES): Add msgl-check.c, plural-eval.c. + (msgfmt_SOURCES): Remove plural-eval.c. + * format.c (check_msgid_msgstr_format): Add const to argument type. + * format.h (check_msgid_msgstr_format): Add const to argument type. + * gettext-po.c: Include msgl-check.h. + (po_file_check_all, po_message_check_all): New functions. + (po_xerror_logger): Remove function. + (po_message_check_format): Use new check_message function. + * gettext-po.h (po_file_check_all, po_message_check_all): New + declarations. + * msgfmt.c: Include msgl-check.h instead of setjmp.h, signal.h, + stdarg.h, po-xerror.h, format.h, plural-exp.h, plural-table.h, + strstr.h. + (SIZEOF, sigjmp_buf, sigsetjmp, siglongjmp, USE_SIGINFO, + sigfpe_exit, sigfpe_code, sigfpe_handler, install_sigfpe_handler, + uninstall_sigfpe_handler, check_plural_eval, plural_help, check_plural, + curr_mp, curr_msgid_pos, formatstring_error_logger, check_pair, + check_header_entry): Move definitions to msgl-check.c. + (main): Update. + (msgfmt_frob_new_message): Call check_message instead of + check_header_entry and check_pair. + +2005-09-17 Bruno Haible + + Use new error handlers in libgettextpo. + * gettext-po.h (PO_SEVERITY_WARNING, PO_SEVERITY_ERROR, + PO_SEVERITY_FATAL_ERROR): New macros. + (po_xerror_handler): New structure type. + (po_xerror_handler_t): New type. + (po_file_read): Change signature; version 3. + (po_file_write): Change signature; version 2. + (po_message_check_format): Change signature; version 2. + * gettext-po.c: Include po-xerror.h. + (po_file_read): New implementation. Renamed old implementation to + po_file_read_v2. + (po_file_write): New implementation. Keep old implementation. + (po_xerror_logger): New function. + (po_message_check_format): New implementation. Keep old implementation. + +2005-09-17 Bruno Haible + + * msgfmt.c (check_plural): Fix broken determination of max_nplurals. + +2005-09-17 Bruno Haible + + New, higher-level error message output primitives. + * po-xerror.h: New file. + * po-xerror.c: New file. + * open-po.c: Include xerror.h, po-xerror.h, not error.h. + (open_po_file): Use po_xerror instead of error. + * po-charset.c: Include po-xerror.h, not po-error.h. + (po_lex_charset_set): Use po_xerror instead of po_multiline_warning. + * po-lex.c: Include xerror.h, po-error.h, po-xerror.h. + (po_gram_error, po_gram_error_at_line): Use po_xerror instead of + po_error or po_error_at_line. Don't decrement error_message_count; + let the caller do this instead. + (mbfile_getc, lex_getc): Use po_xerror instead of po_error. + * po-lex.h: Don't include po-error.h. + (po_gram_error, po_gram_error_at_line): Remove optimized macros. + * read-po-abstract.c: Include xerror.h, po-xerror.h. + (po_scan): Use po_xerror instead of po_error. + * read-po.c: Include po-xerror.h. + (default_add_message): Use po_xerror2 instead of po_gram_error_at_line + pair. + * read-properties.c: Include xerror.h, po-xerror.h, not exit.h. + (phase1_getc, phase4_getuc): Use po_xerror instead of error. + * read-stringtable.c: Include xerror.h, po-xerror.h, not exit.h. + (phase1_getc, read_string, stringtable_parse): Use po_xerror instead of + error. + * write-po.c: Include po-xerror.h, not exit.h, po-error.h. + (wrap): Pass the entire message as argument. Use po_xerror instead of + po_error. + (message_print, message_print_obsolete): Update. Use po_xerror instead + of po_multiline_warning. + (msgdomain_list_print): Use po_xerror instead of po_error or + po_error_at_line. + * msgl-iconv.h (struct conversion_context): Add 'message' field. + * msgl-iconv.c: Include xerror.h, po-xerror.h, not error.h, exit.h. + (conversion_error): Use po_xerror instead of error. + (iconv_message_list): Likewise. Set the entire message in the + conversion context before calling convert_*. + (iconv_msgdomain_list): Use po_xerror instead of error. + * xgettext.c (from_current_source_encoding): Set context.message. + * msgfmt.c: Include po-xerror.h. + (check_plural_eval): Pass the entire header as argument. Use po_xerror + instead of error_at_line. + (plural_help): New function, extracted from check_plural. + (check_plural): Change type of local variables has_plural, min_pos, + max_pos. Use po_xerror2 instead of error_at_line pairs. Use po_xerror + instead of error_at_line. + (curr_mp): New variable. + (formatstring_error_logger): Use po_xerror instead of vfprintf. + (check_pair): Take the entire message as argument. Use po_xerror + instead of error_at_line. Set curr_mp before calling + check_msgid_msgstr_format. + (check_header_entry): Take the entire header message as argument. Use + po_xerror instead of multiline_error. + (msgfmt_frob_new_message): Update. + * msgcmp.c (match_domain): Decrement error_message_count between two + error messages that belong together. + * msgmerge.c (match_domain): Decrement error_message_count between two + error messages that belong together. + * Makefile.am (noinst_HEADERS): Add po-xerror.h. + (COMMON_SOURCE): Add po-xerror.c. + +2005-09-16 Bruno Haible + + * gettext-po.c (po_file_read): Set gram_max_allowed_errors to a large + integer, so that po_gram_error doesn't cause the program to exit(). + +2005-09-14 Bruno Haible + + * x-csharp.c: Include xerror.h. + +2005-09-11 Bruno Haible + + * user-email.sh.in: Add support for Mozilla Thunderbird and GNOME + Evolution 2. Improve mutt support. + +2005-08-26 Bruno Haible + + * plural-table.c (plural_table): Add an entry about Romanian. + Explanations by Eddy Petrişor . + +2005-08-23 Bruno Haible + + * write-mo.h (byteswap): New variable. + * write-mo.c: Include byteswap.h. + (byteswap): New variable. + (BSWAP32): New macro. + (write_table): If requested, byteswap all 32-bit values before writing + them out. + * msgfmt.c (long_options): Add --endianness. + (main): Accept --endianness argument and set byteswap. + +2005-08-08 Bruno Haible + + * msginit.c (catalogname_for_locale): Add support for Old English, + Hyam, Jju, Tyap, Maithili, Nahuatl, Low Saxon, Northern Sotho, Páez, + Tetum, Zapotec. + (englishname_of_language): Add support for Old English, Hyam, Jju, + Tyap, Kagoma, Maithili, Mayan, Nahuatl, Low Saxon, Northern Sotho, + Páez, Tetum, Zapotec. + +2005-05-21 Bruno Haible + + New undocumented option, used by glocale PO files. + * msgfmt.c (include_fuzzies): Renamed from include_all. + (include_untranslated): New variable. + (long_options): Add --use-untranslated. + (main): Handle --use-untranslated. + (msgfmt_frob_new_message): Test include_untranslated. + (msgfmt_comment_special): Update. + +2005-07-26 Bruno Haible + + * user-email.sh.in (datarootdir): New variable. + +2005-07-26 Bruno Haible + + Fix compilation error on systems without iconv(). + * xgettext.c (convert_string): Remove fallback macro definition. + (from_current_source_encoding) [!HAVE_ICONV]: Just abort here. + +2005-07-17 Bruno Haible + + * po-lex.c (mb_width): Remove gcc warnings on platform with unsigned + 'char' type (e.g. Linux/PowerPC). + Reported by Jeff Rizzo . + +2005-07-05 Bruno Haible + + * Makefile.am (msgmerge_SOURCES, xgettext_SOURCES, msgattrib_SOURCES, + msgcat_SOURCES, msgcomm_SOURCES, msgconv_SOURCES, msgen_SOURCES, + msgfilter_SOURCES, msggrep_SOURCES, msguniq_SOURCES): On mingw, use + C++ source code. + (libgettextsrc_la_LDFLAGS, libgettextpo_la_LDFLAGS): Use LTNOUNDEF. + (format_CFLAGS): New variable. + (format.lo): New rule. + (msg*_CFLAGS, xgettext_CFLAGS): Remove variables. + (msg*_CPPFLAGS, xgettext_CPPFLAGS): New variables. + +2005-05-27 Bruno Haible + + * write-po.c (wrap): Output \a and \v as an escape sequence, like + \b, \f, \r. + Suggested by Asgeir Frimannsson . + +2005-05-05 Bruno Haible + + * Makefile.am (msgfmt_SOURCES): Add hash-string.c. + * Makefile.msvc (msgfmt_OBJECTS): Add hash-string.obj. + (hash-string.obj): New rule. + * Makefile.vms (msgfmt_OBJECTS): Add hash-string.obj. + (hash-string.obj): New rule. + +2005-05-01 Bruno Haible + + * x-sh.c (open_singlequote_terminator): New variable. + (saw_opening_singlequote): Set it. + (phase2_getc): Handle the case of open_singlequote_terminator == '"'. + (read_word): Likewise. Treat i18n quotes like single quotes with + terminator '"'. + Reported by Jakub Bogusz . + +2005-05-01 Bruno Haible + + Improved error message. + * msgl-iconv.h (struct conversion_context): New type. + (convert_string): Add context argument. + * msgl-iconv.c (conversion_error): New function. + (convert_string, convert_string_list, convert_msgid, convert_msgstr): + Add context argument. + (iconv_message_list): Construct context for them. + * xgettext.c (convert_string): Add context argument. + (from_current_source_encoding): Construct context for convert_string. + Reported by Hans Ulrich Niedermann . + +2005-04-18 Bruno Haible + + * po-lex.h (po_gram_error, po_gram_error_at_line): Test for + __APPLE_CC__ > 1. Needed because gcc-4.0 defines __APPLE_CC__ and + implements __VA_ARGS__ correctly. + * po-lex.c (po_gram_error, po_gram_error_at_line): Likewise. + * read-stringtable.c (phase2_getc): Optimize UTF-8 code. Avoids + gcc-4.0 warnings. + +2006-06-21 Bruno Haible + + * gettext-0.14.6 released. + +2006-06-21 Bruno Haible + + * Makefile.am (CLEANFILES): Add msgfmt.net.exe.mdb, + msgunfmt.net.exe.mdb. + +2006-06-21 Bruno Haible + + * gettext-po.h (LIBGETTEXTPO_VERSION): Bump version number. + +2006-06-20 Bruno Haible + + * format-gcc-internal.c (struct unnumbered_arg): Remove type. + (struct numbered_arg): New type. + (struct spec): Use it. + (isdigit): New macro. + (numbered_arg_compare): New function. + (format_parse, format_free, format_check, format_print): Update to + GCC 4.1 internal format strings. + Reported by Göran Uddeborg . + +2005-05-23 Bruno Haible + + * gettext-0.14.5 released. + +2005-05-23 Bruno Haible + + * gettext-po.h (LIBGETTEXTPO_VERSION): Bump version number. + +2005-05-21 Bruno Haible + + * format-gcc-internal.c: Update for GCC 4.0. + (FAT_POINTER, FAT_SIZE_LONGLONG, FAT_SIZE_WIDE, FAT_SIZE_MASK): New + enum items. + (FAT_*): Update. + (struct spec): Add uses_err_no field. + (format_parse): Initialize uses_err_no field. Handle %q flag. Handle + 'll', 'w' size specifiers. Handle %<, %>, %', %m, %p, %J directives. + (format_check): Also check that the use of err_no is the same. + Based on a patch by Jakub Jelinek . + +2005-04-11 Bruno Haible + + * gettext-0.14.4 released. + +2005-04-11 Bruno Haible + + * gettext-po.h (LIBGETTEXTPO_VERSION): Bump version number. + +2005-03-14 Bruno Haible + + * gettext-0.14.3 released. + +2005-03-08 Bruno Haible + + * gettext-po.h (LIBGETTEXTPO_VERSION): Bump version number. + +2005-02-26 Bruno Haible + + * format-scheme.c (format_arg_type): Add FAT_COMPLEX. Remove + FAT_FUNCTION. + (make_intersected_element): Simplify also the intersection with + FAT_COMPLEX. + (make_union_element): Simplify also the union with FAT_COMPLEX. + (IIC): New array. + (nocheck_params): Remove function. + (parse_upto): Remove handling of ~W, ~<, ~>. + Implement non-CL handling of ~C, ~I, ~_, ~/, ~T. + Implement handling of ~Y, ~!, ~Q, ~K. + +2005-02-26 Bruno Haible + + * format-scheme.c (parse_upto): Remove support for ~!. + Reported by Kevin Ryde . + +2005-02-24 Bruno Haible + + * gettext-0.14.2 released. + +2005-02-12 Bruno Haible + + * Makefile.msvc (gettextpo.lib): Link with intl.lib as well. + +2005-02-12 Bruno Haible + + * Makefile.msvc (msginit_OBJECTS): Add plural-count.obj. + * Makefile.vms (msginit_OBJECTS): Add plural-count.obj. + +2005-02-10 Bruno Haible + + * gettext-po.h (po_header_set_field): New declaration. + * gettext-po.c (po_header_set_field): New function. + Suggested by Ross Golder . + +2005-02-10 Bruno Haible + + * gettext-po.h (LIBGETTEXTPO_VERSION): New macro. + (libgettextpo_version): New declaration. + * gettext-po.c (libgettextpo_version): New variable. + +2005-02-10 Bruno Haible + + * gettext-po.h (po_message_remove_filepos, po_message_add_filepos): New + declarations. + * gettext-po.c (po_message_remove_filepos, po_message_add_filepos): New + functions. + Suggested by Asgeir Frimannsson . + +2005-02-10 Bruno Haible + + * gettext-po.h (po_message_set_extracted_comments): New declaration. + * gettext-po.c (po_message_set_extracted_comments): New function. + Suggested by Asgeir Frimannsson . + +2005-02-08 Bruno Haible + + * msginit.c (get_user_email): Cast _() to 'char *', to avoid compiler + warning. + +2005-02-07 Bruno Haible + + Make the tools work in the tr_TR locale. + * po-charset.c: Include c-strcase.h instead of strcase.h. + (po_charset_canonicalize): Use c_strcasecmp. + * msgcmp.c: Include c-strcase.h instead of strcase.h. + (compare): Use c_strncasecmp. + * msginit.c: Include c-strcase.h instead of strcase.h. + (content_type): Use c_strcasecmp. + * msgmerge.c: Include c-strcase.h instead of strcase.h. + (message_merge, merge): Use c_strncasecmp. + * xgettext.c: Include c-strcase.h instead of strcase.h. + (language_to_extractor): Use c_strcasecmp. + Reported by Recai Oktaş . + +2005-02-07 Bruno Haible + + * project-id: Use LC_ALL=C to protect range expression against + Estonian locale. + +2005-02-06 Bruno Haible + + Leave #: lines with unknown syntax alone instead of turning them into + plain comments. + * po-hash.h: Remove file. + * po-hash-gen.y: Remove file. + * read-po-abstract.c: Don't include po-hash.h. + (po_parse_comment_filepos, po_parse_comment_solaris_filepos): New + functions. + (po_callback_comment_dispatcher): Call them instead of the old + po_parse_comment_filepos. + * read-stringtable.c: Don't include po-hash.h. + * Makefile.am (noinst_HEADERS): Remove po-hash.h, po-hash-gen.h. + (COMMON_SOURCE): Remove po-hash-gen.y. + (BUILT_SOURCES): Remove po-hash-gen.c, po-hash-gen.h. + * Makefile.msvc (OBJECTS): Remove po-hash-gen.obj. + (po-hash-gen.obj): Remove rule. + * Makefile.vms (OBJECTS): Remove po-hash-gen.obj. + (po-hash-gen.obj): Remove rule. + * FILES: Update. + Reported by David Fraser . + +2005-02-04 Bruno Haible + + * plural-table.c (plural_table): Add an entry about Vietnamese. + Explanations by Clytie Siddall . + +2005-01-29 Bruno Haible + + * Makefile.am (msgcmp_LDADD, msgfmt_LDADD, msgmerge_LDADD, + msgunfmt_LDADD, xgettext_LDADD, msgattrib_LDADD, msgcat_LDADD, + msgcomm_LDADD, msgconv_LDADD, msgen_LDADD, msgexec_LDADD, + msgfilter_LDADD, msggrep_LDADD, msguniq_LDADD): Add INTL_MACOSX_LIBS. + +2005-01-27 Bruno Haible + + * msgfmt.c (formatstring_error_logger): Cast the fprrintf argument, + to make it work on big-endian 64-bit machines. + +2005-01-16 Bruno Haible + + Support for Scheme. + * message.h (format_type): New enum value 'format_scheme'. + (NFORMATS): Increment. + * message.c (format_language): Add format_scheme entry. + (format_language_pretty): Likewise. + * format.h (formatstring_scheme): New declaration. + * format-scheme.c: New file, based on format-lisp.c. + * format.c (formatstring_parsers): Add formatstring_scheme. + * x-scheme.h: New file. + * x-scheme.c: New file. + * xgettext.c: Include x-scheme.h. + (flag_table_scheme): New variable. + (main): Invoke init_flag_table_scheme, x_scheme_extract_all, + x_scheme_keyword. + (usage): Mention Scheme source language. + (xgettext_record_flag): Handle format_scheme. + (language_to_extractor): Add Scheme rule. + (extension_to_language): Add Scheme rule. + * Makefile.am (noinst_HEADERS): Add x-scheme.h. + (FORMAT_SOURCE): Add format-scheme.c. + (xgettext_SOURCES): Add x-scheme.c. + * Makefile.msvc (OBJECTS): Add format-scheme.obj. + (xgettext_OBJECTS): Add x-scheme.obj. + (format-scheme.obj, x-scheme.obj): New rules. + * Makefile.vms (OBJECTS): Add format-scheme.obj. + (xgettext_OBJECTS): Add x-scheme.obj. + (format-scheme.obj, x-scheme.obj): New rules. + * FILES: Update. + +2005-01-08 Bruno Haible + + * msggrep.c (grep_args, grep_path, grep_argv): Remove variables. + (struct grep_task): New type. + (grep_task): New variable. + (main): Initialize grep_task instead of grep_args. Change processing + of options 'E', 'F' to set a matcher, of 'e', 'f' to append to the + pattern string, and of 'i' to set a boolean. Compile the patterns. + (is_string_selected): Execute a compiled pattern instead of calling + the grep program. + (is_message_selected): Update. + * Makefile.am (AM_CPPFLAGS): Add -I option for libgrep. + (LIBGREP): New variable. + (msggrep_LDADD): Add $(LIBGREP). + * Makefile.msvc (INCLUDES): Add libgrep directory. + (msggrep.exe): Link with libgrep. + * Makefile.vms (INCLUDES): Add libgrep directory. + (msggrep.exe): Link with libgrep. + +2005-01-09 Bruno Haible + + * msgl-ascii.h (is_ascii_msgdomain_list): New declaration. + * msgl-ascii.c (is_ascii_msgdomain_list): New function. + * x-po.c (header_charset): New variable. + (extract_add_message): When seeing the header entry, store the charset + in header_charset. + (extract): Store the value of header_charset in the new constructed + header entry. + * xgettext.c (main): Convert the existing PO file to UTF-8 if it was + not entirely ASCII. + Reported by Jörg Schilling . + +2005-01-08 Bruno Haible + + * msggrep.c (usage): Explain COMMENT-PATTERN syntax as well. + +2005-01-06 Bruno Haible + + * x-tcl.c (read_word): Ignore non-space whitespace at the beginning. + Reported by William J Poser . + +2005-01-06 Bruno Haible + + * write-csharp.c (msgdomain_write_csharp): Don't call fclose after + fwriteerror. + * write-java.c (msgdomain_write_java): Likewise. + * write-mo.c (msgdomain_write_mo): Likewise. + * write-po.c (msgdomain_list_print): Likewise. + * write-qt.c (msgdomain_write_qt): Likewise. + * write-resources.c (execute_writing_input): Likewise. + * write-tcl.c (msgdomain_write_tcl): Likewise. + +2005-01-05 Bruno Haible + + * msgattrib.c (main): Update year in --version output. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + +2005-01-07 Bruno Haible + + * gettext-po.c (po_file_read): Interpret "-" and "/dev/stdin" as + denoting stdin. + Suggested by Asgeir Frimannsson . + +2004-11-29 Bruno Haible + + * Makefile.am (libgettextpo_la_LDFLAGS): Add @LTLIBINTL@ @LTLIBICONV@. + Reported by Sam Steingold . + +2004-10-01 Guido Flohr + + * x-perl.c (x_perl_prelex): Recognize function names starting with '-'. + Reported by Ryan Anderson . + +2004-09-16 Bruno Haible + + * format.h (formatstring_error_logger_t): Modify decl for GCC <= 3.0. + Reported by Jens A. Tkotz . + +2004-09-11 Bruno Haible + + * Makefile.am (msginit_SOURCES): Add plural-count.c. + * msginit.c (update_msgstr_plurals): New function. + (main): Call it. + * msgmerge.c (match_domain): Provide the appropriate number of plural + forms for messages that don't occur in the Def.po file. + Reported by Jens A. Tkotz . + +2004-09-06 Bruno Haible + + * format.h (formatstring_error_logger_t): New type. + (struct formatstring_parser): Change calling convention of 'check' + method. + (check_msgid_msgstr_format): New declaration. + * format-awk.c (format_check): Use error_logger argument instead of + noisy and error_at_line. + * format-c.c (format_check): Likewise. + * format-csharp.c (format_check): Likewise. + * format-elisp.c (format_check): Likewise. + * format-gcc-internal.c (format_check): Likewise. + * format-java.c (format_check): Likewise. + * format-librep.c (format_check): Likewise. + * format-lisp.c (format_check): Likewise. + * format-pascal.c (format_check): Likewise. + * format-perl.c (format_check): Likewise. + * format-perl-brace.c (format_check): Likewise. + * format-php.c (format_check): Likewise. + * format-python.c (format_check): Likewise. + * format-qt.c (format_check): Likewise. + * format-sh.c (format_check): Likewise. + * format-tcl.c (format_check): Likewise. + * format-ycp.c (format_check): Likewise. + * format.c (check_msgid_msgstr_format): New function, extracted from + msgfmt.c. + * msgfmt.c (curr_msgid_pos): New variable. + (formatstring_error_logger): New function. + (check_pair): Use check_msgid_msgstr_format, formatstring_error_logger. + * msgmerge.c (msgfmt_check_pair_fails): Update. + * gettext-po.h (po_message_check_format): New declaration. + * gettext-po.c (po_error_logger, po_message_check_format): New + functions. + +2004-09-05 Bruno Haible + + * po-error.h: New file. + * po-error.c: New file. + * po-charset.c: Include po-error.h. + (po_lex_charset_set): Use po_multiline_warning instead of + multiline_warning. + * po-lex.h: Include po-error.h. + (po_gram_error): Use po_error instead of error. + (po_gram_error_at_line): Use po_error_at_line instead of error_at_line. + * po-lex.c (po_gram_error): Use po_error instead of error. + (po_gram_error_at_line): Use po_error_at_line instead of error_at_line. + (mbfile_getc, lex_getc): Use po_error instead of error. + * read-po-abstract.c (po_scan): Likewise. + * write-po.c: Include po-error.h instead of error.h. + (wrap): Use po_error instead of error. + (message_print, message_print_obsolete): Use po_multiline_warning + instead of multiline_warning. + (msgdomain_list_print): Use po_error instead of error, po_error_at_line + instead of error_at_line. + * str-list.h (string_list_join): New declaration. + * str-list.c (string_list_join): New function. + * message.h (message_list_insert_at): New declaration. + * message.c (message_list_insert_at): New function. + * gettext-po.h (po_filepos_t): New type. + (struct po_error_handler, po_error_handler_t): New types. + (po_file_create): New declaration. + (po_file_read): Add handler argument. + (po_file_write, po_message_insert, po_message_create, + po_message_set_msgid, po_message_set_msgid_plural, + po_message_set_msgstr, po_message_set_msgstr_plural, + po_message_comments, po_message_set_comments, + po_message_extracted_comments, po_message_filepos, + po_message_set_obsolete, po_message_set_fuzzy, po_message_set_format, + po_filepos_file, po_filepos_start_line): New declarations. + * gettext-po.c (po_file_create): New function. + (po_file_read): Add handler argument. Keep an old version for backward + compatibility. + (po_file_write): New function. + (po_message_iterator): Store more information, to make + po_message_insert possible. + (po_message_iterator_free): Update. + (po_next_message): Don't crash if iterator->mlp is NULL. + (po_message_insert): New function. + (po_message_create): New function. + (po_message_set_msgid, po_message_set_msgid_plural, + po_message_set_msgstr, po_message_set_msgstr_plural, + po_message_comments, po_message_set_comments, + po_message_extracted_comments, po_message_filepos, + po_message_set_obsolete, po_message_set_fuzzy, po_message_set_format, + po_filepos_file, po_filepos_start_line): New functions. + * Makefile.am (noinst_HEADERS): Add po-error.h. + (COMMON_SOURCE): Add po-error.c. + * Makefile.msvc (OBJECTS): Add po-error.obj. + (po-error.obj): New rule. + * Makefile.vms (OBJECTS): Add po-error.obj. + (po-error.obj): New rule. + * FILES: Update. + +2004-09-03 Bruno Haible + + * Makefile.am (msginit_LDADD): Use the INTL_MACOSX_LDFLAGS. + +2004-08-30 Bruno Haible + + * plural-table.c (plural_table): Czech is now like Slovak. + Reported by Stepan Kasal . + +2004-06-23 Bruno Haible + + * x-c.c (phase1_getc): Fix phase0_getc invocation. + Reported by Matt Dreezer . + +2004-05-14 Bruno Haible + + * format-java.c (message_format_parse): Fix argument of freesa() calls. + +2004-03-19 Bruno Haible + + * Makefile.am (install-exec-clean): Don't remove libgettextsrc.a on + AIX. + Reported by Kouichi Hashikawa . + +2004-03-14 Bruno Haible + + * format-lisp.c (parse_upto): Add integer restriction for the dispatch + argument in ~[...~]. + +2004-03-02 Bruno Haible + + * read-csharp.c (msgdomain_read_csharp): Extend the lang -> frobbedlang + mapping so as to support Serbian and Uzbek locales. + * write-csharp.c (msgdomain_write_csharp): Likewise. + Reported by Jorn Baayen . + +2004-02-27 Bruno Haible + + * plural-table.c (plural_table): Add an entry about Serbian. + Reported by Danilo Segan . + +2004-01-29 Bruno Haible + + * gettext-0.14.1 released. + +2004-01-28 Bruno Haible + + * gettext-0.14 released. + +2004-01-18 Bruno Haible + + * msginit.c (catalogname_for_locale): Add entries for Akan, Avaric, + Bambara, Cree, Divehi (Maldivian), Éwé, Igbo, Kongo, Kanuri, Ganda, + Luba-Katanga, Ojibwa, Venda. + (englishname_of_language): Likewise, and also for Fulah (Fulfulde). + +2004-01-09 Bruno Haible + + * format.h (struct formatstring_parser): Add 'translated' argument to + parse function. + (get_sysdep_c_format_directives): Renamed from + get_c99_format_directives. Add 'translated' argument. Change meaning + of endpos in returned array. + * format-c.c (struct spec): Rename field c99_directives to + sysdep_directives, and c99_directives_count to sysdep_directives_count. + (format_parse): Add 'translated' argument. Handle 'I' flag. Store the + pointer past the '>' character instead of the pointer to the '>'. + (format_c_parse, format_objc_parse): Add 'translated' argument. + (get_sysdep_c_format_directives): Renamed from + get_c99_format_directives. Add 'translated' argument. + * format-awk.c (format_parse): Add 'translated' argument. + * format-csharp.c (format_parse): Likewise. + * format-elisp.c (format_parse): Likewise. + * format-gcc-internal.c (format_parse): Likewise. + * format-java.c (format_parse): Likewise. + * format-librep.c (format_parse): Likewise. + * format-lisp.c (format_parse): Likewise. + * format-pascal.c (format_parse): Likewise. + * format-perl-brace.c (format_parse): Likewise. + * format-perl.c (format_parse): Likewise. + * format-php.c (format_parse): Likewise. + * format-python.c (format_parse): Likewise. + * format-qt.c (format_parse): Likewise. + * format-sh.c (format_parse): Likewise. + * format-tcl.c (format_parse): Likewise. + * format-ycp.c (format_parse): Likewise. + * msgfmt.c (check_pair): Update. + * msgmerge.c (msgfmt_check_pair_fails): Likewise. + * read-mo.c (get_sysdep_string): Don't add '<'...'>' around segments + of length 1. + (read_mo_file): Treat major revision 1 like major revision 0. + * write-mo.c (write_table): Update. Use major revision 1 if "I" occurs + among the sysdep segments. + * xgettext.c (set_format_flags_from_context, remember_a_message, + remember_a_message_plural): Update. + +2004-01-14 Bruno Haible + + * msginit.c (fill_header): Avoid a NULL pointer access when the header + has no comment attached to it. + Reported by Josep Puigdemont . + +2004-01-09 Bruno Haible + + * msgattrib.c (main): Update year in --version output. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + +2003-12-29 Bruno Haible + + * xgettext.h: Include stdlib.h, str-list.h. + (struct refcounted_string_list_ty): New type. + (add_reference, drop_reference): New functions. + (savable_comment, savable_comment_add, savable_comment_reset, + savable_comment_to_xgettext_comment): New declarations. + * xgettext.c (savable_comment): New variable. + (savable_comment_add, savable_comment_reset, + savable_comment_to_xgettext_comment): New functions. + * x-java.c (struct refcounted_string_list_ty, comment, add_reference, + drop_reference, x_java_comment_add, x_java_comment_reset, + x_java_comment_to_xgettext_comment): Remove. Use replacement from + xgettext.{h,c} instead. + * x-csharp.c (struct refcounted_string_list_ty, comment, add_reference, + drop_reference, x_csharp_comment_add, x_csharp_comment_reset, + x_csharp_comment_to_xgettext_comment): Remove. Use replacement from + xgettext.{h,c} instead. + * x-c.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (struct token_ty): Add 'comment' field. + (free_token): Free it. + (phase5_get): Initialize token's 'comment' field. + (phase6_get): Call savable_comment_reset instead of + xgettext_comment_reset. + (phase8a_get): Initialize token's 'comment' field. + (phase8b_get): Call savable_comment_reset instead of + xgettext_comment_reset. + (phase8c_get): In @"...", use the comment of the first token, not of + the second. + (struct xgettext_token_ty): Add 'comment' field. + (x_c_lex): Deal with the token's comment. + (extract_parenthesized): Call savable_comment_to_xgettext_comment and + savable_comment_reset. Free the token's comment field. + * x-python.c (comment_line_end): Call savable_comment_add instead of + xgettext_comment_add. + (struct token_ty): Add 'comment' field. + (phase5_get): Call savable_comment_reset instead of + xgettext_comment_reset. Initialize token's 'comment' field. + (extract_parenthesized): Call savable_comment_to_xgettext_comment and + savable_comment_reset. Free the token's comment field. + +2003-12-28 Bruno Haible + + * read-mo.h (read_mo_file): Change 'fn' into 'filename'. + * read-mo.c (read_mo_file): Likewise. + +2003-12-28 Bruno Haible + + * write-resources.h: New file. + * write-resources.c: New file. + * msgfmt.cs: New file. + * msgfmt.c: Include write-resources.h. + (csharp_resources_mode): New variable. + (long_options): Add --csharp-resources. + (main): Handle --csharp-resources. More generic code for detection of + contradicting modes. Invoke msgdomain_write_csharp_resources. + (usage): Document --csharp-resources option. + (msgfmt_set_domain): Update. + * read-resources.h: New file. + * read-resources.c: New file. + * msgunfmt.cs (DumpResource): Add a constructor that dumps a .resources + file. + (Main): Invoke it when only one argument is given. + * msgunfmt.c: Include read-resources.h. + (csharp_resources_mode): New variable. + (long_options): Add --csharp-resources. + (main): Handle --csharp-resources. More generic code for detection of + contradicting modes. Invoke read_one_file instead of read_mo_file. + (usage): Document --csharp-resources option. + (read_one_file): New function. + * Makefile.am (noinst_HEADERS): Add read-resources.h and + write-resources.h. + (msgfmt_SOURCES): Add write-resources.c. + (msgunfmt_SOURCES): Add read-resources.c. + (EXTRA_DIST): Add msgfmt.cs. + (CLEANFILES): Add msgfmt.net.exe. + (msgfmt.net.exe): New rule. + (all-csharp-yes): Depend on it. + (install-exec-csharp-yes): Also install msgfmt.net.exe. + (uninstall-csharp-yes): Also uninstall msgfmt.net.exe. + * Makefile.msvc (msgfmt_OBJECTS): Add write-resources.obj. + (msgunfmt_OBJECTS): Add read-resources.obj. + (write-resources.obj, read-resources.obj): New rules. + * Makefile.vms (msgfmt_OBJECTS): Add write-resources.obj. + (msgunfmt_OBJECTS): Add read-resources.obj. + (write-resources.obj, read-resources.obj): New rules. + * FILES: Update. + +2003-12-26 Bruno Haible + + Support for C#. + * write-csharp.h: New file. + * write-csharp.c: New file. + * msgfmt.c: Include write-csharp.h. + (csharp_mode, csharp_resource_name, csharp_locale_name, + csharp_base_directory): New variables. + (long_options): Add option --csharp. + (main): Handle option --csharp. Initialize csharp_resource_name, + csharp_locale_name, csharp_base_directory. Perform checks for C# mode. + Invoke msgdomain_write_csharp. + (usage): Document --csharp option and C# mode. + (msgfmt_set_domain): Ignore in C# mode. + * read-csharp.h: New file. + * read-csharp.c: New file. + * msgunfmt.cs: New file. + * msgunfmt.c: Include read-csharp.h. + (csharp_mode, csharp_resource_name, csharp_locale_name, + csharp_base_directory): New variables. + (long_options): Add option --csharp. + (main): Handle option --csharp. Initialize csharp_resource_name, + csharp_locale_name, csharp_base_directory. Perform checks for C# mode. + Invoke msgdomain_read_csharp. + (usage): Document --csharp option and C# mode. + * Makefile.am (noinst_HEADERS): Add read-csharp.h, write-csharp.h. + (CSHARPCOMP, CSHARPCOMPFLAGS): New variables. + (msgfmt_SOURCES): Add write-csharp.c. + (msgunfmt_SOURCES): Add read-csharp.c. + (EXTRA_DIST): Add msgunfmt.cs. + (CLEANFILES): Add msgunfmt.net.exe. + (all-csharp-yes, all-csharp-no): New rules. + (all-local): Depend on them. + (msgunfmt.net.exe): New rule. + (install-exec-csharp-yes, install-exec-csharp-no): New rules. + (install-exec-local): Depend on them. + (installdirs-csharp): New rule. + (installdirs-local): Depend on it. + (uninstall-csharp-yes, uninstall-csharp-no): New rules. + (uninstall-local): Depend on it. + * Makefile.msvc (msgfmt_OBJECTS): Add write-csharp.obj. + (msgunfmt_OBJECTS): Add read-csharp.obj. + (write-csharp.obj, read-csharp.obj): New rules. + * Makefile.vms (msgfmt_OBJECTS): Add write-csharp.obj. + (msgunfmt_OBJECTS): Add read-csharp.obj. + (write-csharp.obj, read-csharp.obj): New rules. + * FILES: Update. + +2003-12-26 Bruno Haible + + * read-java.c (msgdomain_read_java): Relocate also the GETTEXTJAR + value. + * urlget.c (fetch): Likewise. + +2003-12-26 Bruno Haible + + * write-java.c (write_java_code): Emit a static method + 'get_msgid_plural_table' instead of a static field 'plural'. + * gnu/gettext/DumpResource.java (DumpResource.dump): Exploit a + 'get_msgid_plural_table' method if it exists. + +2003-12-14 Bruno Haible + + * message.h (format_type): New enum value 'format_csharp'. + (NFORMATS): Increment. + * message.c (format_language): Add format_csharp entry. + (format_language_pretty): Likewise. + * format.h (formatstring_csharp): New declaration. + * format-csharp.c: New file. + * format.c (formatstring_parsers): Add formatstring_csharp. + * x-csharp.h: New file. + * x-csharp.c: New file. + * xgettext.c: Include x-csharp.h. + (flag_table_csharp): New variable. + (main): Invoke init_flag_table_csharp, x_csharp_extract_all, + x_csharp_keyword. + (usage): Mention C# source language. + (xgettext_record_flag): Handle format_csharp. + (language_to_extractor): Add C# rule. + (extension_to_language): Add C# rule. + * Makefile.am (noinst_HEADERS): Add x-csharp.h. + (FORMAT_SOURCE): Add format-csharp.c. + (xgettext_SOURCES): Add x-csharp.c. + * Makefile.msvc (OBJECTS): Add format-csharp.obj. + (xgettext_OBJECTS): Add x-csharp.obj. + (format-csharp.obj, x-csharp.obj): New rules. + * Makefile.vms (OBJECTS): Add format-csharp.obj. + (xgettext_OBJECTS): Add x-csharp.obj. + (format-csharp.obj, x-csharp.obj): New rules. + * FILES: Update. + +2003-12-28 Bruno Haible + + * msgexec.c (process_string): Pass ignore_sigpipe = false. + * msgfilter.c (process_string): Likewise. + * msggrep.c (is_string_selected): Likewise. + * msginit.c (project_id, project_id_version, get_user_email, + language_team_address): Likewise. + * read-java.c (execute_and_read_po_output): Likewise. + * read-tcl.c (msgdomain_read_tcl): Likewise. + * urlget.c (execute_it): Pass ignore_sigpipe = true. + (fetch): Pass ignore_sigpipe = true when fetching the file, = false + otherwise. + +2003-12-14 Bruno Haible + + * x-c.c (SIZEOF): New macro. + (phase1_ungetc): Verify that the pushback buffer's size is sufficient. + (phase2_ungetc): Likewise. + (phase3_ungetc): Likewise. + (phase5_unget): Likewise. + (phase6_unget): Likewise. + * x-java.c (SIZEOF): New macro. + (phase1_ungetc): Verify that the pushback buffer's size is sufficient. + (phase2_ungetc): Likewise. + (phase3_ungetc): Likewise. + (phase5_unget): Likewise. + (phase6_unget): Likewise. + * x-php.c (SIZEOF): New macro. + (phase1_ungetc): Verify that the pushback buffer's size is sufficient. + (phase2_ungetc): Likewise. + (phase3_ungetc): Likewise. + * x-python.c (SIZEOF): New macro. + (phase1_ungetc): Verify that the pushback buffer's size is sufficient. + (phase5_unget): Likewise. + * x-sh.c (SIZEOF): New macro. + (phase1_ungetc): Verify that the pushback buffer's size is sufficient. + (phase2_ungetc): Likewise. + * x-smalltalk.c (SIZEOF): New macro. + (phase2_unget): Verify that the pushback buffer's size is sufficient. + * x-tcl.c (SIZEOF): New macro. + (phase1_ungetc): Verify that the pushback buffer's size is sufficient. + (phase2_ungetc): Likewise. + * x-ycp.c (SIZEOF): New macro. + (phase2_unget): Verify that the pushback buffer's size is sufficient. + +2003-12-14 Bruno Haible + + * x-c.c (phase1_pushback): Reduce size to 2. + (phase2_pushback): Reduce size to 1. + (phase3_pushback): Reduce size to 2. + (phase5_pushback): Reduce size to 1. + (phase6_pushback): Reduce size to 2. + * x-java.c (phase2_pushback): Reduce size to 1. + (phase3_pushback): Reduce size to 2. + (phase5_pushback): Reduce size to 3. + (phase6_pushback): Reduce size to 2. + * x-php.c (phase1_pushback): Reduce size to 2. + * x-python.c (max): New macro. + (phase1_pushback): Reduce size to UNINAME_MAX + 3. + (phase5_pushback): Reduce size to 1. + * x-sh.c (phase1_pushback): Reduce size to 1. + (phase2_pushback): Reduce size to 2. + * x-smalltalk.c (phase2_pushback): Reduce size to 1. + * x-tcl.c (phase1_pushback): Reduce size to 1. + (phase2_pushback): Reduce size to 2. + +2003-12-12 Bruno Haible + + Assume automake-1.8. + * Makefile.am (install-exec-local): Renamed from install-exec-am. + +2003-11-30 Bruno Haible + + * format-java.c: Include xallocsa.h. + (message_format_parse): Use xallocsa instead of alloca. + (choice_format_parse): Likewise. + * msgl-cat.c: Include xallocsa.h. + (catenate_msgdomain_list): Use xallocsa instead of alloca. + * msgl-charset.c: Include xallocsa.h. + (compare_po_locale_charsets): Use xallocsa instead of alloca. + * msgl-iconv.c: Include xallocsa.h. + (iconv_message_list): Use xallocsa instead of alloca. + * po-charset.c: Include xallocsa.h. + (po_lex_charset_set): Use xallocsa instead of alloca. + * read-tcl.c: Include xallocsa.h. + (msgdomain_read_tcl): Use xallocsa instead of alloca. + * write-java.c: Include xallocsa.h. + (compute_hashsize, msgdomain_write_java): Use xallocsa instead of + alloca. + * write-mo.c: Include xallocsa.h. + (write_table): Use xallocsa instead of alloca. + * write-po.c: Include xallocsa.h. + (msgdomain_list_print_po): Use xallocsa instead of alloca. + * write-tcl.c: Include xallocsa.h. + (msgdomain_write_tcl): Use xallocsa instead of alloca. + * msggrep.c: Include xallocsa.h. + (is_message_selected): Use xallocsa instead of alloca. + * msginit.c: Include xallocsa.h. + (subst_string): Use xallocsa instead of alloca. + * msgmerge.c: Include obstack.h. + (obstack_chunk_alloc, obstack_chunk_free): New macros. + (message_merge): Use obstack_alloc instead of alloca. + * xgettext.c: Include xallocsa.h. + (flag_context_list_table_insert): Use xallocsa instead of alloca. + +2003-12-17 Bruno Haible + + * gettext-0.13.1 released. + +2003-12-14 Bruno Haible + + * x-sh.c (phase2_getc): Call phase1_ungetc instead of phase2_ungetc: + The next phase2_getc invocation must return QUOTED('"'), not '"'. + +2003-12-14 Bruno Haible + + * x-c.c (phase7_getc): Remove dead code. + * x-java.c (accumulate_escaped): More precise error message. + +2003-12-02 Bruno Haible + + * msginit.c (catalogname_for_locale): Treat sr_CS like sr_YU. + +2003-11-30 Bruno Haible + + * gettext-0.13 released. + +2003-11-28 Bruno Haible + + * xgettext.c (set_format_flags_from_context): Add casts, to make it + compile in C++ mode. + +2003-11-22 Bruno Haible + + * x-c.c (phase4_getc): Skip leading whitespace in C++ style comments + as well. + * x-sh.c (read_word): Skip leading whitespace in comments. + * x-lisp.c (read_object): Skip leading whitespace in single-line + comments as well. + * x-elisp.c (read_object): Skip leading whitespace in comments. + * x-librep.c (read_object): Skip leading whitespace in single-line + comments as well. + * x-java.c (phase4_getc): Skip leading whitespace in C++ style comments + as well. + * x-awk.c (phase2_getc): Skip leading whitespace in comments. + * x-ycp.c (phase2_getc): Skip leading whitespace in single-line + comments as well. + * x-tcl.c (read_command): Skip leading whitespace in comments. + +2003-11-24 Bruno Haible + + * format-lisp.c (check_params): Use ngettext for one of the messages. + Reported by Rafał Maszkowski . + +2003-11-15 Bruno Haible + + * Makefile.am (AM_CPPFLAGS): Renamed from INCLUDES. + +2003-11-09 Bruno Haible + + * msginit.c (main): Drop the blank line in --no-translator mode. + +2003-11-07 Bruno Haible + + * xgettext.c (remember_a_message): Omit the programmer comments of a + duplicated msgid only if they are redundant. + Reported by Christian Neumair . + +2003-11-05 Bruno Haible + + * user-email.sh.in: Renamed from user-email.in. Internationalize, use + localedir and gettext. + * FILES: Update. + +2003-10-23 Bruno Haible + + * read-stringtable.c (read_string, stringtable_parse): Cast + gram_pos.line_number to 'unsigned long'. + * x-sh.c (read_word): Case line_number to 'unsigned long'. + +2003-10-21 Bruno Haible + + * message.h (message_list_msgids_changed): New declaration. + * message.c (message_list_msgids_changed): New function. + * msgl-iconv.c (iconv_message_list): Update the message list's hash + table if it happens to contain non-ASCII msgids. + +2003-10-19 Bruno Haible + + * message.h (format_type): New enum value 'format_qt'. + (NFORMATS): Increment. + * message.c (format_language): Add format_qt entry. + (format_language_pretty): Likewise. + * format.h (formatstring_qt): New declaration. + * format-qt.c: New file. + * format.c (formatstring_parsers): Add formatstring_qt. + * xgettext.c (recognize_format_qt): New variable. + (long_options): Add option "--qt". + (main): Handle --qt option. Determine the extractor only after option + processing is complete. + (usage): Document --qt option. + (xgettext_record_flag): Handle format_qt. + (language_to_extractor): For C++, return a different extractor when + --qt was specified. + * write-qt.h: New file. + * write-qt.c: New file. + * msgfmt.c: Include write-qt.h. + (qt_mode): New variable. + (long_options): Add option "--qt". + (main): Handle --qt option. More checks for contradicting options. + Call msgdomain_write_qt. + (usage): Mention Qt mode. + (format_directive_domain): Ignore domain directive if in Qt mode. + * Makefile.am (noinst_HEADERS): Add write-qt.h. + (FORMAT_SOURCE): Add format-qt.c. + (msgfmt_SOURCES): Add write-qt.c. + * Makefile.msvc (OBJECTS): Add format-qt.obj. + (msgfmt_OBJECTS): Add write-qt.obj. + (format-qt.obj, write-qt.obj): New rules. + * Makefile.vms (OBJECTS): Add format-qt.obj. + (msgfmt_OBJECTS): Add write-qt.obj. + (format-qt.obj, write-qt.obj): New rules. + * FILES: Update. + +2003-10-14 Bruno Haible + + Fix compilation errors in C++ mode. + * msgfilter.c (process_string): Cast the xrealloc expression. + * xgettext.c (language_to_extractor, extension_to_language): Move the + typedef after the struct definition. + +2003-10-13 Bruno Haible + + Support CR/LF line terminators in C sources even on Unix. + * x-c.c (phase0_getc, phase0_ungetc): New functions. + (phase1_getc): Use them instead of calling getc/ungetc directly. + Reported by Christoph Thielecke . + +2003-10-13 Bruno Haible + + Support and recognize Objective C specific format strings. + * message.h (enum format_type): New item format_objc. + (NFORMATS): Increment. + * message.c (format_language): Add an entry for format_objc. + (format_language_pretty): Likewise. + * format.h (formatstring_objc): New declaration. + * format-c.c (enum format_arg_type): New item FAT_OBJC_OBJECT. + (format_parse): Add objc_extensions argument. Handle %@ in ObjC mode. + (format_c_parse, format_objc_parse): New functions. + (formatstring_c): Use format_c_parse instead of format_parse. + (formatstring_objc): New variable. + (get_c99_format_directives): Update. + * format.c (formatstring_parsers): Add an entry for format_objc. + * write-mo.c (write_table): Look for system dependent strings also in + ObjectiveC format strings. + * x-c.h (SCANNERS_C): Use separate flag_table for ObjectiveC. + (x_objc_keyword, init_flag_table_objc): New declarations. + (x_c_any_keywords): Remove declaration. + * x-c.c (c_keywords): Renamed from keywords. + (objc_keywords): New variable. + (add_keyword): Renamed from x_c_keyword. Add keywords table argument. + (x_c_keyword, x_objc_keyword): New functions. + (x_c_any_keywords): Remove function. + (init_keywords): Also initialize ObjectiveC keyword table. + (init_flag_table_objc): New function. + (enum token_type_ty): New item token_type_colon. + (phase5_get): Recognize colon. + (enum xgettext_token_type_ty): New item xgettext_token_type_colon. + (x_c_lex): Use keywords table depending on objc_extensions. Handle + colon. + (extract_parenthesized): Change the context_iter and inner_context + after a keyword/symbol followed by a colon was seen. + * xgettext.c (flag_table_objc): New variable. + (main): Invoke init_flag_table_objc, x_objc_keyword. Watch out for + keywords arguments, instead of calling x_c_any_keywords(). + (flag_context_list_table_insert): New function, extracted from + xgettext_record_flag. + (xgettext_record_flag): Call it. For format_c, insert the flags also + in the flag_table_objc. Handle format_objc. + (remember_a_message): Don't add a heuristic c-format flag to an entry + that already carries objc-format. + (remember_a_message_plural): Likewise. + +2003-10-18 Bruno Haible + + Support for GNUstep .strings format. + * read-stringtable.h: New file. + * read-stringtable.c: New file. + * read-po-abstract.h (enum input_syntax_ty): New item + syntax_stringtable. + (po_callback_comment_dispatcher): Renamed from po_callback_comment. + (po_callback_comment, po_callback_comment_special): New declarations. + * read-po-abstract.c: Include read-stringtable.h. + (po_scan): Add support for syntax_stringtable. + (po_callback_comment_dispatcher): Renamed from po_callback_comment. + (po_callback_comment, po_callback_comment_dot): New functions. + (po_callback_comment_special): New function. + * read-po.c (read_po): Set mdlp->encoding for syntax_stringtable too. + * po-gram-gen.y: Update po_callback_comment_dispatcher call. + * read-properties.c (properties_parse): Likewise. + * write-stringtable.h: New file. + * write-stringtable.c: New file. + * write-po.h (make_format_description_string, significant_format_p): + New declarations. + (message_print_syntax_stringtable): New declaration. + * write-po.c: Include write-stringtable.h. + (make_format_description_string, significant_format_p): Make + non-static. + (use_syntax_stringtable): New variable. + (message_print_syntax_stringtable): New function. + (msgdomain_list_print): Add check for plural forms for + syntax_stringtable too. Add support for writing the .strings format. + * msgattrib.c (long_options): Add --stringtable-input/output. + (main): Handle them. + (usage): Document options --stringtable-input/output. + * msgcat.c (long_options): Add --stringtable-input/output. + (main): Handle them. If option --stringtable-output is used, convert to + UTF-8 and ignore the to_code. + (usage): Document options --stringtable-input/output. + * msgcmp.c (long_options): Add --stringtable-input. + (main): Handle it. + (usage): Document option --stringtable-input. + * msgcomm.c (long_options): Add --stringtable-input/output. + (main): Handle them. + (usage): Document options --stringtable-input/output. + * msgconv.c (long_options): Add --stringtable-input/output. + (main): Handle them. If option --stringtable-output is used, ignore the + to_code. + (usage): Document options --stringtable-input/output. + * msgen.c (long_options): Add --stringtable-input/output. + (main): Handle them. + (usage): Document options --stringtable-input/output. + * msgexec.c: Include limits.h. + (long_options): Add --stringtable-input. + (main): Handle it. + (usage): Document option --stringtable-input. + * msgfilter.c (long_options): Add --stringtable-input/output. + (main): Handle them. + (usage): Document options --stringtable-input/output. + * msgfmt.c (long_options): Add --stringtable-input. + (main): Handle it. + (usage): Document option --stringtable-input. + * msggrep.c (long_options): Add --stringtable-input/output. + (main): Handle them. + (usage): Document options --stringtable-input/output. + * msginit.c (long_options): Add --stringtable-input/output. + (main): Handle them. + (usage): Document options --stringtable-input/output. + * msgmerge.c (long_options): Add --stringtable-input/output. + (main): Handle them. In update mode, --stringtable-input implies + --stringtable-output. + (usage): Document options --stringtable-input/output. + * msgunfmt.c (long_options): Add --stringtable-output. + (main): Handle it. + (usage): Document option --stringtable-output. + * msguniq.c (long_options): Add --stringtable-input/output. + (main): Handle them. + (usage): Document options --stringtable-input/output. + * x-stringtable.h: New file. + * x-po.c: Include x-stringtable.h. + (extract_stringtable): New function. + * xgettext.c: Include x-stringtable.h. + (long_options): Add --stringtable-output. + (main): Handle it. + (usage): Document options -L NXStringTable and --stringtable-output. + (finalize_header): If --stringtable-output was given, set the charset. + (language_to_extractor): Add support for .strings format. + (extension_to_language): Likewise. + * Makefile.am (noinst_HEADERS): Add read-stringtable.h, + write-stringtable.h, x-stringtable.h. + (COMMON_SOURCE): Add read-stringtable.c. + (libgettextsrc_la_SOURCES): Add write-stringtable.c. + * Makefile.msvc (OBJECTS): Add read-stringtable.obj, + write-stringtable.obj. + (read-stringtable.obj, write-stringtable.obj): New rules. + * Makefile.vms (OBJECTS): Add read-stringtable.obj, + write-stringtable.obj. + (read-stringtable.obj, write-stringtable.obj): New rules. + * FILES: Update. + +2003-10-21 Bruno Haible + + * hostname.c (xgethostname): Add support for native Woe32 API. + +2003-10-21 Bruno Haible + + * message.c (message_list_search): Avoid casting a pointer to a local + variable; it violated strict aliasing. + +2003-10-21 Bruno Haible + + * write-java.c (mkdir): Redefine on mingw. + +2003-10-12 Bruno Haible + + Improved ObjectiveC support. + * x-c.h (extract_objc): New declaration. + (SCANNERS_C): For ObjectiveC, use extract_objc. + * x-c.c (objc_extensions): New variable. + (enum token_type_ty): New item token_type_objc_special. + (phase5_get): Recognize '@' for ObjectiveC. + (phase8b_get, phase8b_unget): New functions. Handle + token_type_white_space and token_type_eoln here instead of in x_c_lex. + (phase8c_get, phase8c_unget): New functions. + (phase8_get): Rely on phase8c, not phase8a. No more need to care about + token_type_white_space and token_type_eoln. + (x_c_lex): Drop handling of token_type_white_space and token_type_eoln. + (extract_whole_file): Renamed from extract_c. + (extract_c, extract_objc): New functions. + +2003-10-20 Bruno Haible + + * x-java.c (phase3_getc): Fix typo: Use phase2_ungetc, not phase2_getc. + +2003-10-20 Bruno Haible + + Portability to AIX with cc. + * xgettext.h (struct flag_context_ty): Define the bitfields of type + 'unsigned int'. + +2003-10-20 Bruno Haible + + Portability to Solaris with cc. + * x-java.c (string_buffer_append): Avoid aggregate initializer + containing non-constant expressions. + * xgettext.c (flag_context_list_iterator): Likewise. + (language_to_extractor): Likewise. + +2003-10-11 Bruno Haible + + * xgettext.c (main): Drop suffix ".in" from input file name, if + present, before looking at the file extension. + +2003-10-09 Bruno Haible + + * format-awk.c: Include xalloc.h instead of xmalloc.h. + * format-c.c: Likewise. + * format-elisp.c: Likewise. + * format-gcc-internal.c: Likewise. + * format-java.c: Likewise. + * format-librep.c: Likewise. + * format-lisp.c: Likewise. + * format-pascal.c: Likewise. + * format-perl.c: Likewise. + * format-perl-brace.c: Likewise. + * format-php.c: Likewise. + * format-python.c: Likewise. + * format-sh.c: Likewise. + * format-tcl.c: Likewise. + * format-ycp.c: Likewise. + * gettext-po.c: Likewise. + * hostname.c: Likewise. + * message.c: Likewise. + * msgexec.c: Likewise. + * msgfilter.c: Likewise. + * msgfmt.c: Likewise. + * msggrep.c: Likewise. + * msginit.c: Likewise. + * msgl-cat.c: Likewise. + * msgl-english.c: Likewise. + * msgl-iconv.c: Likewise. + * msgmerge.c: Likewise. + * open-po.c: Likewise. + * po-gram-gen.y: Likewise. + * po-hash-gen.y: Likewise. + * po-lex.c: Likewise. + * read-mo.c: Likewise. + * read-po-abstract.c: Likewise. + * read-po.c: Likewise. + * read-properties.c: Likewise. + * str-list.c: Likewise. + * write-java.c: Likewise. + * write-mo.c: Likewise. + * write-po.c: Likewise. + * write-properties.c: Likewise. + * write-tcl.c: Likewise. + * x-awk.c: Likewise. + * x-c.c: Likewise. + * x-elisp.c: Likewise. + * x-glade.c: Likewise. + * x-java.c: Likewise. + * x-librep.c: Likewise. + * x-lisp.c: Likewise. + * x-perl.c: Likewise. + * x-php.c: Likewise. + * x-po.c: Likewise. + * x-python.c: Likewise. + * x-rst.c: Likewise. + * x-sh.c: Likewise. + * x-smalltalk.c: Likewise. + * x-tcl.c: Likewise. + * x-ycp.c: Likewise. + * xgettext.c: Likewise. + +2003-10-07 Bruno Haible + + More reliable subprocess cleanup. + * msgexec.c (process_string): Pass slave_process = true to + create_pipe_out() and wait_subprocess(). + * msgfilter.c (process_string): Pass slave_process = true to + create_pipe_bidi() and wait_subprocess(). + * msggrep.c (is_string_selected): Pass slave_process = true to + create_pipe_out() and wait_subprocess(). + * msginit.c (project_id, project_id_version, get_user_email, + language_team_address): Pass slave_process = true to create_pipe_in() + and wait_subprocess(). + * read-java.c (execute_and_read_po_output): Likewise. + * read-tcl.c (msgdomain_read_tcl): Likewise. + * urlget.c (execute_it, fetch): Pass slave_process = true to execute(). + +2003-10-05 Bruno Haible + + * write-java.c: Include fatal-signal.h, not signal.h. + (uninstall_handlers): Remove function. + (cleanup): Remove signal argument. Don't execute the signal's default + action; leave that to the caller. + (install_handlers, init_signal_set, block, unblock): Remove functions. + (msgdomain_write_java): Invoke at_fatal_signal instead of + install_handlers/uninstall_handlers. Invoke [un]block_fatal_signals + instead of [un]block. + +2003-10-05 Bruno Haible + + * xgettext.c (substring_match): Remove variable. + (long_options): Remove --keyword-substring option. + (main): Remove handling of --keyword-substring option. + +2003-10-05 Bruno Haible + + * message.h (enum is_format): New item yes_according_to_context. + * message.c (possible_format_p): Handle also yes_according_to_context. + * write-po.c (make_format_description_string): Likewise. + + * xgettext.h (struct flag_context_ty): New type. + (null_context, passthrough_context): New declarations. + (inherited_context): New declaration. + (struct flag_context_list_ty): New type. + (struct flag_context_list_iterator_ty): New type. + (null_context_list_iterator, passthrough_context_list_iterator): New + declarations. + (flag_context_list_iterator): New declaration. + (flag_context_list_iterator_advance): New declaration. + (flag_context_list_table_ty): New type. + (flag_context_list_table_lookup): New declaration. + (xgettext_record_flag): New declaration. + (remember_a_message, remember_a_message_plural): Add context argument. + * xgettext.c: Include alloca.h. + (flag_table_c, flag_table_gcc_internal, flag_table_sh, + flag_table_python, flag_table_lisp, flag_table_elisp, + flag_table_librep, flag_table_java, flag_table_awk, flag_table_ycp, + flag_table_tcl, flag_table_perl, flag_table_php): New variables. + (long_options): Add option --flag. + (extractor_func): Add argument flag_table. + (struct extractor_ty): New type. + (main): Use type 'extractor_ty' instead of 'extractor_func'. + Invoke init_flag_table_c(), init_flag_table_gcc_internal(), + init_flag_table_sh(), init_flag_table_python(), init_flag_table_lisp(), + init_flag_table_elisp(), init_flag_table_librep(), + init_flag_table_java(), init_flag_table_awk(), init_flag_table_ycp(), + init_flag_table_tcl(), init_flag_table_perl(), init_flag_table_php(). + Implement option --flag. + (usage): Rename a section to "Language specific options". Document + the languages to which --extract-all, --keyword, --trigraphs are + applicable. Document option --flag. + (null_context): New variable. + (passthrough_context): New variable. + (inherited_context): New function. + (null_context_list_iterator): New variable. + (passthrough_context_circular_list, passthrough_context_list_iterator): + New variables. + (flag_context_list_iterator): New function. + (flag_context_list_iterator_advance): New function. + (flag_context_list_table_lookup): New function. + (xgettext_record_flag): New function. + (extract_from_file): Change argument type to 'extractor_ty' instead of + 'extractor_func'. Set current_formatstring_parser{1,2} before invoking + the extractor. + (set_format_flags_from_context): New function. + (remember_a_message): Add context argument. Set some *-format flag if + the context specifies it. + (remember_a_message_plural): Likewise. + (language_to_extractor): Change return type to 'extractor_ty' instead + of 'extractor_func'. + + * x-awk.h (SCANNERS_AWK): Refer to flag_table_awk. + (extract_awk): Add argument flag_table. + (init_flag_table_awk): New declaration. + * x-awk.c (init_flag_table_awk): New function. + (enum token_type_ty): New enum item token_type_semicolon. + (x_awk_lex): Recognize semicolon. + (flag_context_list_table): New variable. + (extract_parenthesized): Add arguments outer_context, context_iter. + Implement context handling depending on symbol before '('. Also + recognize argument lists that start without '(', but only up to the + next semicolon. + (extract_awk): Add argument flag_table. Initialize + flag_context_list_table. + + * x-c.h (SCANNERS_C): Refer to flag_table_c, flag_table_gcc_internal. + (extract_c): Add argument flag_table. + (init_flag_table_c): New declaration. + (init_flag_table_gcc_internal): New declaration. + * x-c.c (init_flag_table_c): New function. + (init_flag_table_gcc_internal): New function. + (enum xgettext_token_type_ty): New item xgettext_token_type_other. + (x_c_lex): For token_type_name, put the string into the resulting + token instead of freeing it. Return token type + xgettext_token_type_other instead of xgettext_token_type_symbol in + some cases. + (flag_context_list_table): New variable. + (extract_parenthesized): Add arguments outer_context, context_iter. + Implement context handling depending on symbol before '('. + (extract_c): Add argument flag_table. Initialize + flag_context_list_table. + + * x-elisp.h (SCANNERS_ELISP): Refer to flag_table_elisp. + (extract_elisp): Add argument flag_table. + (init_flag_table_elisp): New declaration. + * x-elisp.c (init_flag_table_elisp): New function. + (flag_context_list_table): New variable. + (read_object): Add argument outer_context. Implement context handling + depending on first symbol after '('. + (extract_elisp): Add argument flag_table. Initialize + flag_context_list_table. + + * x-glade.h (SCANNERS_GLADE): Update. + (extract_glade): Add argument flag_table. + * x-glade.c (start_element_handler, end_element_handler): Pass null + context to remember_a_message. + (extract_glade): Add argument flag_table. + + * x-java.h (SCANNERS_JAVA): Refer to flag_table_java. + (extract_java): Add argument flag_table. + (init_flag_table_java): New declaration. + * x-java.c (init_flag_table_java): New function. + (flag_context_list_table): New variable. + (extract_parenthesized): Add arguments outer_context, context_iter. + Implement context handling depending on symbol before '('. + (extract_java): Add argument flag_table. Initialize + flag_context_list_table. + + * x-librep.h (SCANNERS_ELISP): Refer to flag_table_librep. + (extract_librep): Add argument flag_table. + (init_flag_table_librep): New declaration. + * x-librep.c (init_flag_table_librep): New function. + (flag_context_list_table): New variable. + (read_object): Add argument outer_context. Implement context handling + depending on first symbol after '('. + (extract_librep): Add argument flag_table. Initialize + flag_context_list_table. + + * x-lisp.h (SCANNERS_LISP): Refer to flag_table_lisp. + (extract_lisp): Add argument flag_table. + (init_flag_table_lisp): New declaration. + * x-lisp.c (init_flag_table_lisp): New function. + (flag_context_list_table): New variable. + (read_object): Add argument outer_context. Implement context handling + depending on first symbol after '('. + (extract_lisp): Add argument flag_table. Initialize + flag_context_list_table. + + * x-perl.h (SCANNERS_PERL): Refer to flag_table_perl. + (extract_perl): Add argument flag_table. + (init_flag_table_perl): New declaration. + * x-perl.c (init_flag_table_java): New function. + (flag_context_list_table): New variable. + (extract_variable): Update. Implement context handling depending on + symbol before '{'...'}'. + (interpolate_keywords): Implement context handling depending on symbol + before '->' or '{'...'}'. + (extract_balanced): Add arguments outer_context, context_iter. + Implement context handling depending on symbol before '('. Also + recognize argument lists that start without '('. + (extract_perl): Add argument flag_table. Initialize + flag_context_list_table. + + * x-php.h (SCANNERS_PHP): Refer to flag_table_php. + (extract_php): Add argument flag_table. + (init_flag_table_php): New declaration. + * x-php.c (init_flag_table_php): New function. + (flag_context_list_table): New variable. + (extract_parenthesized): Add arguments outer_context, context_iter. + Implement context handling depending on symbol before '('. + (extract_php): Add argument flag_table. Initialize + flag_context_list_table. + + * x-po.h (SCANNERS_PO): Update. + (extract_po): Add argument flag_table. + * x-properties.h (SCANNERS_PROPERTIES): Update. + (extract_properties): Add argument flag_table. + * x-po.c (extract_po): Add argument flag_table. + (extract_properties): Add argument flag_table. + + * x-python.h (SCANNERS_PYTHON): Refer to flag_table_python. + (extract_python): Add argument flag_table. + (init_flag_table_python): New declaration. + * x-python.c (init_flag_table_python): New function. + (flag_context_list_table): New variable. + (extract_parenthesized): Add arguments outer_context, context_iter. + Implement context handling depending on symbol before '('. + (extract_python): Add argument flag_table. Initialize + flag_context_list_table. + + * x-rst.h (SCANNERS_RST): Update. + (extract_rst): Add argument flag_table. + * x-rst.c (extract_rst): Add argument flag_table. + + * x-sh.h (SCANNERS_SH): Refer to flag_table_sh. + (extract_sh): Add argument flag_table. + (init_flag_table_sh): New declaration. + * x-sh.c (init_flag_table_sh): New function. + (flag_context_list_table): New variable. + (read_word): Add context argument. + (read_command): Add outer_context argument. Implement context handling + depending on first symbol of command. + (read_command_list): Add outer_context argument. + (extract_sh): Add argument flag_table. Initialize + flag_context_list_table. + + * x-smalltalk.h (SCANNERS_SMALLTALK): Update. + (extract_smalltalk): Add argument flag_table. + * x-smalltalk.c (extract_smalltalk): Add argument flag_table. + + * x-tcl.h (SCANNERS_TCL): Refer to flag_table_tcl. + (extract_tcl): Add argument flag_table. + (init_flag_table_tcl): New declaration. + * x-tcl.c (init_flag_table_tcl): New function. + (flag_context_list_table): New variable. + (accumulate_word): Add context argument. + (read_word): Add context argument. + (read_command): Add outer_context argument. Implement context handling + depending on first symbol of command. + (read_command_list): Add outer_context argument. + (extract_tcl): Add argument flag_table. Initialize + flag_context_list_table. + + * x-ycp.h (SCANNERS_YCP): Refer to flag_table_ycp. + (extract_ycp): Add argument flag_table. + (init_flag_table_ycp): New declaration. + * x-ycp.c (init_flag_table_ycp): New function. + (flag_context_list_table): New variable. + (extract_parenthesized): New function, split off from extract_ycp. + Implement context handling depending on symbol before '('. + (extract_ycp): Add argument flag_table. Initialize + flag_context_list_table. Call extract_parenthesized to do the work. + + * po-lex.c (mbfile_getc, control_sequence): Remove explicit marking of + strings as c-format, now done by xgettext. + +2003-09-23 Bruno Haible + + * x-awk.c (extract_parenthesized): Remove optimization of the + extract_all case. + * x-c.c (extract_parenthesized): Likewise. + * x-elisp.c (read_object): Likewise. + * x-librep.c (read_object): Likewise. + * x-lisp.c (read_object): Likewise. + * x-perl.c (x_perl_prelex, extract_balanced): Likewise. + * x-php.c (extract_parenthesized): Likewise. + * x-python.c (extract_parenthesized): Likewise. + * x-sh.c (read_command): Likewise. + +2003-10-08 Bruno Haible + + * msgmerge.c (main): Make option -N work. + Reported by Liu Garfield . + +2003-10-04 Bruno Haible + + New Java backend. + * x-java.c: New file. + * x-java.l: Remove file. + * Makefile.am (xgettext_SOURCES): Add x-java.c, remove x-java.l. + (x-java.c): Remove rule. + * FILES: Update. + +2003-09-22 Bruno Haible + + * x-glade.c (start_element_handler): Implement extract_all behaviour. + +2003-10-04 Bruno Haible + + * x-glade.c (do_extract_glade): Initialize stack_depth. + * x-perl.c (extract_perl): Initialize linesize and linepos. + + * x-python.c (phase2_getc): Set last_comment_line. + (phase5_get): Set last_non_comment_line. + +2003-10-04 Bruno Haible + + * x-glade.c: Don't include msgl-ascii.h and msgl-iconv.h. + * x-python.c: Likewise. + * x-tcl.c: Likewise. + +2003-10-06 Guido Flohr + Bruno Haible + + * x-perl.c (enum symbol_type_ty): New enum. + (struct token_ty): Rename field string_type to sub_type and change its + type to 'int'. + (extract_quotelike_pass3, interpolate_keywords): Update. + (x_perl_prelex): Set sub_type also for symbol tokens. + (x_perl_lex): Special handling of prototype argument lists. + Reported by Crispin Flowerday . + +2003-09-22 Bruno Haible + + * x-sh.c (read_word): Warn about $"...". + +2003-09-18 Bruno Haible + + * gettext-po.h (po_file_domain_header, po_header_field, + po_message_is_obsolete, po_message_is_fuzzy, po_message_is_format): New + declarations. + * gettext-po.c (po_file_domain_header, po_header_field, + po_message_is_obsolete, po_message_is_fuzzy, po_message_is_format): New + functions. + * Makefile.am (LTV_CURRENT, LTV_REVISION, LTV_AGE): Bump to 1:0:1. + +2003-09-14 Bruno Haible + + * plural-count.c: Include plural-count.h. + * x-po.c: Include x-properties.h. + * x-java.l (append_strings): Change argument type to 'const char *'. + +2003-09-14 Bruno Haible + + * msgfmt.c (check_plural_eval): Mark some strings as c-format. + (msgfmt_add_message): Make static. + * po-lex.c (mbfile_getc, control_sequence): Mark some strings as + c-format. + +2003-09-18 Bruno Haible + + * FILES: Update. + +2003-09-14 Bruno Haible + + More reliable checking for read errors. + * po-lex.c (mbfile_getc): Diagnose read errors also in the middle of + multibyte characters. + (lex_getc): Diagnose read errors also right after backslash. + * x-rst.c (extract_rst): Diagnose read errors also inside ConstName + and immediately after #. + +2003-09-14 Bruno Haible + + * write-mo.c: Include fwriterror.h. + (msgdomain_write_mo): Use fwriteerror, to get right errno value in + error message about write error. + * write-po.c: Include fwriterror.h. + (msgdomain_list_print): Use fwriteerror, to get right errno value in + error message about write error. + * write-java.c: Include fwriterror.h. + (msgdomain_write_java): Use fwriteerror, to get right errno value in + error message about write error. + * write-tcl.c: Include fwriterror.h. + (msgdomain_write_tcl): Use fwriteerror, to get right errno value in + error message about write error. + +2003-09-18 Bruno Haible + + * xgettext.c (from_current_source_encoding): Use multiline_error, + for a prettier error message. + + * x-perl.c (extract_quotelike_pass1): Store counter_delim instead of + EOF as delimiter, to avoid error during from_current_source_encoding. + Reported by Crispin Flowerday . + +2003-09-13 Bruno Haible + + * format-sh.c (INVALID_SHELL_SYNTAX, + INVALID_CONTEXT_DEPENDENT_VARIABLE): New macros. + (format_parse): Reject constructs like ${variable-default} and $?. + +2003-09-13 Bruno Haible + + Fix behaviour of " --help > /dev/full". + * hostname.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgattrib.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgcat.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgcmp.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgcomm.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgconv.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgen.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgexec.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgfilter.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgfmt.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msggrep.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msginit.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgmerge.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msgunfmt.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * msguniq.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * urlget.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * xgettext.c: Include closeout.h. + (main): Register close_stdout for execution at program exit. + * write-po.c (msgdomain_list_print): Don't fclose (stdout) here. + +2003-09-11 Bruno Haible + + * po-lex.c (mbfile_getc): Handle unexpected return value of + u8_mbtouc(). Reported by Jochen Hein . + +2003-09-09 Bruno Haible + + * read-properties.c (phase4_getuc): Cast line_number to 'unsigned long' + before outputting it. + +2003-09-09 Bruno Haible + + * po-lex.c: Test HAVE_DECL_GETC_UNLOCKED instead of HAVE_GETC_UNLOCKED. + * write-po.c: Test HAVE_DECL_PUTC_UNLOCKED instead of + HAVE_PUTC_UNLOCKED. + +2003-09-09 Guido Flohr + + * x-perl.c (extract_quotelike_pass3): Change \l handling to no longer + eat up non-uppercase characters; likewise for \u. Also make \l and \u + work on characters written using hex or octal escape sequence. Fix \Q + handling. With option --extract-all, avoid "invalid variable + interpolation" warnings. + (x_perl_prelex): With option --extract-all, keep variables inside + double-quoted strings untouched. + +2003-09-03 Bruno Haible + + * x-sh.h: New file. + * x-sh.c: New file. + * xgettext.c: Include x-sh.h. + (main): Call x_sh_extract_all, x_sh_keyword. + (usage): Mention Shell language. + (language_to_extractor): Add Shell rule. + (extension_to_language): Add Shell rule. + * Makefile.am (noinst_HEADERS): Add x-sh.h. + (xgettext_SOURCES): Add x-sh.c. + * Makefile.msvc (xgettext_OBJECTS): Add x-sh.obj. + (x-sh.obj): New rule. + * Makefile.vms (xgettext_OBJECTS): Add x-sh.obj. + (x-sh.obj): New rule. + +2003-08-31 Bruno Haible + + * format-sh.c: New file. + * message.h (format_sh): New enum value. + (NFORMATS): Increment. + * format.h (formatstring_sh): New declaration. + * format.c (formatstring_parsers): Add entry for sh. + * message.c (format_language, format_language_pretty): Likewise. + * Makefile.am (FORMAT_SOURCE): Add format-sh.c. + * Makefile.msvc (OBJECTS): Add format-sh.obj. + (format-sh.obj): New rule. + * Makefile.vms (OBJECTS): Add format-sh.obj. + (format-sh.obj): New rule. + +2003-08-31 Bruno Haible + + * format-gcc-internal.c (isdigit): Remove unused macro. + * format-perl-brace.c (isdigit): Likewise. + +2003-09-01 Guido Flohr + + * x-perl.c (extract_balanced): Access the global last_token. + Initialize last_token and prefer_division_over_regexp here. + (extract_perl): Not here. + +2003-08-29 Bruno Haible + + * x-perl.c: Include getline.h. + +2003-08-24 Bruno Haible + + * Makefile.msvc (format.obj, msgmerge.obj, xgettext.obj, msgattrib.obj, + msgcat.obj, msgcomm.obj, msgconv.obj, msgen.obj, msgfilter.obj, + msggrep.obj, msguniq.obj): Compile these in C++ mode. This works around + the "non-constant initializer" error that we get for addresses of + variables in DLLs in C mode. + +2003-08-24 Bruno Haible + + * msgfilter.c (process_message): Cast xrealloc() result. + +2003-08-24 Bruno Haible + + * msgfilter.c: On Windows, include instead of . + * msggrep.c: Likewise. + +2003-08-24 Bruno Haible + + * plural-count.h: New file. + * plural-count.c: New file, extracted from msgmerge.c. + * msgmerge.c: Include plural-count.h instead of plural-exp.h. + (match_domain): Use function get_plural_count(). + * Makefile.am (noinst_HEADERS): Add plural-count.h. + (msgmerge_SOURCES): Add plural-count.c. + * Makefile.msvc (msgmerge_OBJECTS): Add plural-count.obj. + (plural-count.obj): New rule. + * Makefile.vms (msgmerge_OBJECTS): Add plural-count.obj. + (plural-count.obj): New rule. + +2003-08-24 Bruno Haible + + Support for building DLLs on Windows. + * format.h (formatstring_c, formatstring_python, formatstring_lisp, + formatstring_elisp, formatstring_librep, formatstring_smalltalk, + formatstring_java, formatstring_awk, formatstring_pascal, + formatstring_ycp, formatstring_tcl, formatstring_perl, + formatstring_perl_brace, formatstring_php, formatstring_gcc_internal, + formatstring_parsers): Add DLL_VARIABLE attribute. + * message.h (format_language, format_language_pretty): Likewise. + * msgl-cat.h (more_than, less_than, use_first, msgcomm_mode, + omit_header): Likewise. + * plural-table.h (plural_table, plural_table_size): Likewise. + * po-charset.h (po_charset_ascii, po_charset_utf8, po_lex_charset, + po_lex_iconv, po_lex_weird_cjk): Likewise. + * po-lex.h (gram_pos, gram_pos_column, gram_max_allowed_errors, + pass_obsolete_entries): Likewise. + * read-po.h (line_comment, allow_duplicates, input_syntax): Likewise. + * write-mo.h (alignment, no_hash_table): Likewise. + * Makefile.am (po-gram-gen2.h): Likewise. + +2003-08-24 Bruno Haible + + * Makefile.msvc (gettext-po.obj, gettextpo.lib, gettextpo.res): New + rules. + (all): Depend on gettextpo.lib. + (install, installdirs, uninstall): Install/uninstall gettext-po.h, + gettextpo.dll and gettextpo.lib. + * Makefile.vms (gettext-po.obj, gettextpo.olb): New rules. + (all): Depend on gettextpo.olb. + (install, installdirs, uninstall): Install/uninstall gettext-po.h, + gettextpo.olb. + +2003-08-24 Bruno Haible + + * dir-list.h: Make this file includable in C++ mode: add extern "C". + * file-list.h: Likewise. + * format.h: Likewise. + * message.h: Likewise. + * msgl-ascii.h: Likewise. + * msgl-cat.h: Likewise. + * msgl-charset.h: Likewise. + * msgl-english.h: Likewise. + * msgl-equal.h: Likewise. + * msgl-iconv.h: Likewise. + * open-po.h: Likewise. + * po-charset.h: Likewise. + * po-lex.h: Likewise. + * po-time.h: Likewise. + * read-po-abstract.h: Likewise. + * str-list.h: Likewise. + * write-po.h: Likewise. + * xgettext.h: Likewise. + * read-po.h: Likewise. + (this): Redefine to a different symbol. + * xgettext.c: Add extern "C" around all "x-*.h" includes. + +2003-08-22 Bruno Haible + + * format-awk.c: Include error-progname.h instead of progname.h. + * format-c.c: Likewise. + * format-elisp.c: Likewise. + * format-gcc-internal.c: Likewise. + * format-java.c: Likewise. + * format-librep.c: Likewise. + * format-lisp.c: Likewise. + * format-pascal.c: Likewise. + * format-perl.c: Likewise. + * format-perl-brace.c: Likewise. + * format-php.c: Likewise. + * format-python.c: Likewise. + * format-tcl.c: Likewise. + * format-ycp.c: Likewise. + * po-lex.h: Likewise. + * read-properties.c: Likewise. + * write-po.c: Likewise. + * x-awk.c: Likewise. + * x-c.c: Likewise. + * x-perl.c: Likewise. + * x-python.c: Likewise. + * x-rst.c: Likewise. + * hostname.c: Include error-progname.h. + * msgattrib.c: Likewise. + * msgcat.c: Likewise. + * msgcmp.c: Likewise. + * msgcomm.c: Likewise. + * msgconv.c: Likewise. + * msgen.c: Likewise. + * msgexec.c: Likewise. + * msgfilter.c: Likewise. + * msgfmt.c: Likewise. + * msggrep.c: Likewise. + * msginit.c: Likewise. + * msgmerge.c: Likewise. + * msgunfmt.c: Likewise. + * msguniq.c: Likewise. + * urlget.c: Likewise. + * xgettext.c: Likewise. + * po-lex.c: Include error-progname.h and pos.h. + +2003-08-15 Bruno Haible + + * msginit.c (catalogname_for_locale): Add entries for Aragonese, + Haitian, Sichuan Yi, Limburgish. + (englishname_of_language): Likewise. + +2003-08-08 Bruno Haible + + * format-gcc-internal.c: New file. + * message.h (format_gcc_internal): New enum value. + (NFORMATS): Increment. + * format.h (formatstring_gcc_internal): New declaration. + * format.c (formatstring_parsers): Add entry for gcc_internal. + * message.c (format_language, format_language_pretty): Likewise. + * x-c.h (SCANNERS_C): Add an entry for GCC-source. + * xgettext.c (usage): Mention GCC-source language. + * Makefile.am (FORMAT_SOURCE): Add format-gcc-internal.c. + * Makefile.msvc (OBJECTS): Add format-gcc-internal.obj. + (format-gcc-internal.obj): New rule. + * Makefile.vms (OBJECTS): Add format-gcc-internal.obj. + (format-gcc-internal.obj): New rule. + +2003-08-04 Bruno Haible + + * x-perl.c (extract_quotelike_pass3): Fix \x handling. + Reported by Guido Flohr. + +2003-07-05 Bruno Haible + + * format-perl-brace.c: Renamed from format-perl-bracket.c. Recognize + braces instead of brackets. + * format.h (formatstring_perl_brace): Renamed from + formatstring_perl_bracket. + * format.c (formatstring_parsers): Add formatstring_perl_brace, remove + formatstring_perl_bracket. + * message.h (enum format_type): Add format_perl_brace, remove + format_perl_bracket. + * message.c (format_language, format_language_pretty): Update. + * x-perl.h (SCANNERS_PERL): Update. + * Makefile.am (FORMAT_SOURCE): Add format-perl-brace.c, remove + format-perl-bracket.c. + * Makefile.msvc (OBJECTS): Add format-perl-brace.obj, remove + format-perl-bracket.obj. + (format-perl-brace.obj): Renamed from format-perl-bracket.obj. + * Makefile.vms (OBJECTS): Add format-perl-brace.obj, remove + format-perl-bracket.obj. + (format-perl-brace.obj): Renamed from format-perl-bracket.obj. + +2003-07-03 Bruno Haible + + Support for PHP >= 4.2.0. + * x-php.c (init_keywords): Add ngettext, dngettext, dcngettext to the + built-in keywords. + Reported by A. Sopicki . + +2003-06-27 Bruno Haible + + * xgettext.h (from_current_source_encoding): New declaraction. + * xgettext.c (from_current_source_encoding): New function. + (CONVERT_STRING): Use it. + * x-perl.c: Include po-charset.h. + (get_here_document): Convert each line to UTF-8. + (phase2_getc): Convert each comment to UTF-8. Tell xgettext_comment_add + to not convert it. + (extract_quotelike_pass1_utf8): New function. + (extract_quotelike): Use extract_quotelike_pass1_utf8. + (extract_triple_quotelike): Likewise. + (extract_variable): Tell remember_a_message to not convert the string. + (interpolate_keywords): Likewise. + (extract_balanced): Tell remember_a_message, remember_a_message_plural + to not convert the string. + +2003-06-23 Guido Flohr + + * x-perl.c (extract_quotelike_pass3): Fix handling of doubled + backslashes in single-quoted strings. + +2003-06-23 Bruno Haible + + * format-perl-bracket.c (format_check): Allow additional bracketed + items in the msgstr. + +2003-06-22 Bruno Haible + + * write-properties.c: Don't include exit.h and gettext.h. + +2003-06-22 Bruno Haible + + * x-php.c (phase3_ungetc): Comment out unused function. + +2003-06-21 Bruno Haible + + * format-perl.c: Complete rewrite. + * format-perl-bracket.c: New file. + * message.h (format_perl_bracket): New enum value. + (NFORMATS): Increment. + * format.h (formatstring_perl_bracket): New declaration. + * format.c (formatstring_parsers): Add entry for perl_bracket. + * message.c (format_language, format_language_pretty): Likewise. + * x-perl.h (SCANNERS_PERL): Use formatstring_perl and + formatstring_perl_bracket. + * Makefile.am (FORMAT_SOURCE): Add format-perl-bracket.c. + * Makefile.msvc (OBJECTS): Add format-perl.obj,format-perl-bracket.obj. + (xgettext_OBJECTS): Add x-perl.obj. + (format-perl.obj, format-perl-bracket.obj, x-perl.obj): New rules. + * Makefile.vms (OBJECTS): Add format-perl.obj, format-perl-bracket.obj. + (xgettext_OBJECTS): Add x-perl.obj. + (format-perl.obj, format-perl-bracket.obj, x-perl.obj): New rules. + + * x-perl.c (extract_quotelike_pass3): Don't give an invalid + interpolation error for a backslashed dollar sign. + +2003-06-21 Bruno Haible + + * x-perl.c (extract_quotelike_pass3): Fix handling of double backslash. + +2003-06-19 Bruno Haible + + * x-perl.c (enum string_type_ty): Remove trailing comma. + (interpolate_keywords::enum parser_state): Likewise. + (extract_quotelike_pass3): Cast first argument of u8_uctomb(). + +2003-06-15 Bruno Haible + + * x-awk.c: Reorder definitions. + * x-c.c: Reorder definitions. + * x-perl.c: Reorder definitions. + * x-php.c: Reorder definitions. + * x-python.c: Reorder definitions. + * x-smalltalk.c: Reorder definitions. + * x-ycp.c: Reorder definitions. + +2003-06-15 Bruno Haible + + * x-perl.c (struct stack_entry): Remove type. + (struct stack): Remove type. + (struct token_stack_ty): New type. + (token_stack): Change type to 'struct token_stack_ty'. + (token_stack_dump): Renamed from stack_dump. Update. + (token_stack_push): Renamed from stack_unshift. Rewritten. + (token_stack_pop): Renamed from stack_shift. Rewritten. + (token_stack_peek): Renamed from stack_head. Rewritten. + (token_stack_free): Renamed from stack_free. Rewritten. + (x_perl_lex, x_perl_unlex, extract_perl): Update. + +2003-06-15 Bruno Haible + + * x-perl.c (DEBUG_MEMORY): Remove macro. + (remember_a_message_debug, remember_a_message_plural_debug, + xmalloc_debug, xrealloc_debug, xrealloc_static_debug, xstrdup_debug, + free_debug): Remove functions. + (xrealloc_static): Remove macro. Use xrealloc instead. + +2003-06-15 Bruno Haible + + * x-awk.c (phase2_getc, x_awk_lex): Increase bufmax proportionally. + * x-c.c (comment_add, comment_line_end, phase5_get, phase6_get): + Likewise. + * x-elisp.c (comment_add, comment_line_end): Likewise. + * x-librep.c (comment_add, comment_line_end): Likewise. + * x-lisp.c (comment_add, comment_line_end): Likewise. + * x-php.c (comment_add, comment_line_end, x_php_lex): Likewise. + * x-python.c (comment_add, comment_line_end, phase5_get): Likewise. + * x-rst.c (extract_rst): Likewise. + * x-smalltalk.c (comment_add, comment_line_end, phase2_get): Likewise. + * x-tcl.c (comment_add, comment_line_end): Likewise. + * x-ycp.c (phase2_getc, x_ycp_lex): Likewise. + +2003-06-15 Bruno Haible + + * xgettext.c (current_formatstring_parser1, + current_formatstring_parser2): New variables, replacing + current_formatstring_parser. + (remember_a_message, remember_a_message_plural): Try both + formatstring parsers. + (language_to_extractor): Allow two formatstring parsers per language, + not just one. + * x-perl.h (SCANNERS_PERL): Add formatstring_c as secondary format + string parser. + * x-awk.h (SCANNERS_AWK): Update. + * x-c.h (SCANNERS_C): Update. + * x-elisp.h (SCANNERS_ELISP): Update. + * x-glade.h (SCANNERS_GLADE): Update. + * x-java.h (SCANNERS_JAVA): Update. + * x-librep.h (SCANNERS_LIBREP): Update. + * x-lisp.h (SCANNERS_LISP): Update. + * x-php.h (SCANNERS_PHP): Update. + * x-po.h (SCANNERS_PO): Update. + * x-properties.h (SCANNERS_PROPERTIES): Update. + * x-python.h (SCANNERS_PYTHON): Update. + * x-rst.h (SCANNERS_RST): Update. + * x-smalltalk.h (SCANNERS_SMALLTALK): Update. + * x-tcl.h (SCANNERS_TCL): Update. + * x-ycp.h (SCANNERS_YCP): Update. + +2003-06-15 Bruno Haible + + * x-perl.c (case_whitespace): New macro. + (is_whitespace): New function. + (interpolate_keywords): Add lineno argument. Track newlines to update + lineno while processing the string. Use is_whitespace. + (extract_triple_quotelike): Update. Use is_whitespace. + (extract_variable): Simplify: add final NUL byte only when done. Use + is_whitespace. + (x_perl_prelex): Use is_whitespace. After reading a here document, use + line_number+1, not line_number. Fix buffer overrun in < + + * x-perl.c (enum token_type_ty): Reorder, so as to simplify the + function free_token(). + (struct token_ty): Fix comments. + (token2string): Add const. + (token_stack): Make static. + (*_debug, stack_dump): Remove all fflushs. + (linebuf, linesize, linepos, linebuf_size, last_token, here_eaten, + end_of_file): Make static. + (last_string, last_string_finished): Remove unused variables. + (phase1_getc, get_here_document, skip_pod): Test getline() result + for being < 0, not == EOF. + (phase1_ungetc): Signal internal error through abort(), not exit(). + (get_here_document): Tweak. Increase bufmax proportionally. + (phase2_getc): Increase bufmax proportionally. + (prefer_division_over_regexp): Fix comment. + (extract_hex, extract_oct): Add const. Simplify. + (extract_quotelike): Add a safety check. + (extract_quotelike_pass1): Increase bufmax proportionally. Fix + insufficient memory allocation at "bufpos + len >= bufmax". + (extract_quotelike_pass3): Increase bufmax proportionally. Make crs + const. After \x{ABC} position crs after the closing brace. Simplify \c + code. Reduce memory allocation when \N{ABC} is seen. Fix bug in \l and + \u. Fix error message when $ or @ is seen. + (extract_variable): Increase bufmax proportionally. + (interpolate_keywords): Likewise. Convert c's value to unsigned char. + (x_perl_prelex): Increase bufmax proportionally. Compute 4th argument + to extract_triple_quotelike completely. + (extract_perl): Initialize token_stack correctly. + +2003-06-13 Bruno Haible + + * msgexec.c (process_string): Pass null_stderr=false. + * msgfilter.c (process_string): Likewise. + * msggrep.c (is_string_selected): Likewise. + * read-java.c (execute_and_read_po_output): Likewise. + * read-tcl.c (msgdomain_read_tcl): Likewise. + * msginit.c (project_id, project_id_version, get_user_email, + language_team_address): Return a default value if the subprocess + fails, instead of exiting. + +2003-06-12 Bruno Haible + + * Makefile.am (noinst_HEADERS): Add x-perl.h. + (FORMAT_SOURCE): Add format-perl.c. + (xgettext_SOURCES): Add x-perl.c. + * message.h (enum format_type): Mention perl between tcl and php. + * message.c (format_language, format_language_pretty): Likewise. + * format.h (formatstring_perl): Likewise. + * format.c (formatstring_parsers): Likewise. + * format-perl.c: Use GNU coding style. Prefer xmalloc over xcalloc. + * x-perl.h: Assume ANSI C. + * x-perl.c: Assume ANSI C. Use GNU coding style. Prefer xmalloc over + xcalloc. + (token_buf): Remove unused variable. + (xcalloc_debug, stack_push, stack_pop): Remove unused functions. + * xgettext.c: Mention perl between tcl and php. + +2003-06-11 Guido Flohr + + * message.h (enum format_type): New enum value 'format_perl'. + (NFORMATS): Increment. + * message.c (format_language, format_language_pretty): Add entry + for perl. + * format.h (formatstring_perl): New declaration. + * format-perl.c: New file. + * format.c (formatstring_parsers): Add entry for perl. + * x-perl.h: New file. + * x-perl.c: New file. + * xgettext.c: Include x-perl.h. + (main): Call x_perl_extract_all, x_perl_keyword. + (usage): Mention Perl language. + (language_to_extractor): Add Perl rule. + (extension_to_language): Add Perl rule. + +2003-06-08 Bruno Haible + + * Makefile.vms (LDADD): Take options from .opt files. + (libiconv.opt): New rule. + (msgcmp.exe, msgfmt.exe, msgmerge.exe, msgunfmt.exe, xgettext.exe, + msgattrib.exe, msgcat.exe, msgcomm.exe, msgconv.exe, msgen.exe, + msgexec.exe, msgfilter.exe, msggrep.exe, msginit.exe, msguniq.exe): + Depend on it. + * plural-eval.c: Include "eval-plural.h" without a path, rely on the + Makefile's -I options instead. + * read-properties.c (conv_from_java): Change type of q to + 'unsigned char *'. + Reported by Jouk Jansen . + +2003-05-27 Bruno Haible + + * plural-table.c (plural_table): Correct entry for Slovak. + Reported by Marcel Telka . + +2003-05-24 Bruno Haible + + * x-glade.h (EXTENSIONS_GLADE): Also recognize the .glade2 extension. + * x-glade.c (start_element_handler): Also extract strings according to + Glade 2 conventions. + +2003-05-22 Bruno Haible + + * gettext-0.12.1 released. + +2003-05-21 Bruno Haible + + * msggrep.c (main) [Solaris]: Fix syntax error. + Reported by Valery Beaud . + +2003-05-21 Bruno Haible + + * xgettext.c (output_syntax): New variable. + (main): Set it. + (finalize_header): If --properties-output was given, set the charset. + +2003-05-18 Bruno Haible + + * Makefile.msvc (DEBUGFLAGS): New variable. + (gettextsrc.lib): Use it. + (msgcmp.exe, msgfmt.exe, msgmerge.exe, msgunfmt.exe, xgettext.exe, + msgattrib.exe, msgcat.exe, msgcomm.exe, msgconv.exe, msgen.exe, + msgexec.exe, msgfilter.exe, msggrep.exe, msginit.exe, msguniq.exe): + Use it, and MFLAGS too. + +2003-05-17 Bruno Haible + + * gettext-0.12 released. + +2003-05-17 Bruno Haible + + * Makefile.msvc (msgcmp.exe, msgfmt.exe, msgmerge.exe, msgunfmt.exe, + msgattrib.exe, msgcat.exe, msgcomm.exe, msgconv.exe, msgen.exe, + msgexec.exe, msgfilter.exe, msggrep.exe, msginit.exe, msguniq.exe): + Link with iconv.lib, needed by intl.lib. + +2003-05-12 Bruno Haible + + * msginit.c (F_OK): Provide a fallback value. + Reported by Perry Rapp. + +2003-05-10 Bruno Haible + + * msgl-iconv.c (iconv_string): Don't return -1 just because the string + is longer than 4 KB. + Reported by Denis Barbier . + +2003-05-04 Bruno Haible + + * po-lex.h (po_gram_error): Don't use ISO C 99 feature on DEC C. + * po-lex.c (po_gram_error): LIkewise. + +2003-05-03 Bruno Haible + + * msgfilter.c: Include also on EMX. + Reported by Andreas Buening . + +2003-04-26 Bruno Haible + + * message.h (msgdomain_list_ty): Add field 'encoding'. + * message.c (msgdomain_list_alloc): Initialize it. + * read-properties.h: New file. + * read-properties.c: New file. + * read-po-abstract.h (abstract_po_reader_class_ty): In + directive_message field, add force_fuzzy argument. + (po_scan_start, po_scan_end): Remove declarations. + (input_syntax_ty): New type. + (po_callback_message): Add force_fuzzy argument. + * read-po-abstract.c (call_directive_message, po_callback_message): + Add force_fuzzy argument. + (po_scan_start, po_scan_end): Make static. + (po_scan): Add support for syntax_properties. Move + error_message_count handling to here. + (po_callback_message): Move po_lex_charset_set invocation away. + * read-po.h (default_po_reader_class_ty): In add_message field, add + force_fuzzy argument. + (default_directive_message, default_add_message): Add force_fuzzy + argument. + (inout_syntax): New declaration. + * read-po.c (call_add_message): Add force_fuzzy argument. + (default_directive_message, default_add_message): Likewise. + (input_syntax): New variable. + (read_po): Pass input_syntax to po_scan(). Set mdlp->encoding if + possible. + * po-gram-gen.y: Pass force_fuzzy = false. + (do_callback_message): New function. Move po_lex_charset_set + invocation to here. + * po-lex.c (lex_end): Move error_message_count handling away. + * write-properties.h: New file. + * write-properties.c: New file. + * write-po.h (message_print_syntax_properties): New declaration. + * write-po.c (use_syntax_properties): New variable. + (message_print_syntax_properties): New function. + (msgdomain_list_print_po): New function, extracted from + msgdomain_list_print. + (msgdomain_list_print): Add check for plural forms. Add support for + writing the .properties format. + (extract_po): Renamed from extract. + * write-java.h (msgdomain_write_java): Add canon_encoding argument. + * write-java.c (msgdomain_write_java): Likewise. + * write-tcl.h (msgdomain_write_tcl): Likewise. + * write-tcl.c (msgdomain_write_tcl): Likewise. + * msgl-cat.c (catenate_msgdomain_list): Use mdlp->encoding if there + is no header entry. Set total_mdlp->encoding if possible. + * msgl-iconv.c (iconv_msgdomain_list): Use mdlp->encoding if there + is no header entry. Set mdlp->encoding. + * msgattrib.c (long_options): Add --properties-input/output. + (main): Handle them. + (usage): Document options -P and -p. + * msgcat.c (long_options): Add --properties-input/output. + (main): Handle them. If option -p is used, convert to UTF-8 and + ignore the to_code. + (usage): Document options -P and -p. + * msgcomm.c (long_options): Add --properties-input/output. + (main): Handle them. + (usage): Document options -P and -p. + * msgconv.c (long_options): Add --properties-input/output. + (main): Handle them. If option -p is used, ignore the to_code. + (usage): Document options -P and -p. + * msgen.c (long_options): Add --properties-input/output. + (main): Handle them. + (usage): Document options -P and -p. + * msgfilter.c (long_options): Add --properties-input/output. + (main): Handle them. + (usage): Document options -P and -p. + * msggrep.c (long_options): Add --properties-input/output. + (main): Handle them. + (usage): Document options -P and -p. + * msginit.c (long_options): Add --properties-input/output. + (main): Handle them. + (usage): Document options -P and -p. + * msgmerge.c (long_options): Add --properties-input/output. + (main): Handle them. In update mode, --properties-input implies + --properties-output. + (usage): Document options -P and -p. + (merge): Set result->encoding if possible. + * msguniq.c (long_options): Add --properties-input/output. + (main): Handle them. + (usage): Document options -P and -p. + * msgcmp.c (long_options): Add --properties-input. + (main): Handle it. + (usage): Document option -P. + * msgexec.c (long_options): Add --properties-input. + (main): Handle it. + (usage): Document option -P. + * msgfmt.c (long_options): Add --properties-input. + (main): Handle it. Pass the known canon_encoding to + msgdomain_write_java and msgdomain_write_tcl. + (usage): Document option -P. + (msgfmt_add_message): Add force_fuzzy argument. + (read_po_file_msgfmt): Pass input_syntax to po_scan(). + * msgunfmt.c (long_options): Add --properties-output. + (main): Handle it. + (usage): Document option -p. + * x-properties.h: New file. + * x-po.c (extract_add_message): Add force_fuzzy argument. + (extract): Renamed from extract_po. Pass input_syntax to po_scan(). + (extract_po): New function. + (extract_properties): New function. + * xgettext.h: Include read-po.h. + * xgettext.c (long_options): Add --properties-output. + (main): Handle it. + (usage): Document options -L JavaProperties and --properties-output. + (exclude_directive_message): Add force_fuzzy argument. + (read_exclusion_file): Pass input_syntax to po_scan(). + (table): Add support for .properties format. + * Makefile.am (noinst_HEADERS): Add read-properties.h, + write-properties.h, x-properties.h. + (COMMON_SOURCE): Add read-properties.c. + (libgettextsrc_la_SOURCES): Add write-properties.c. + * Makefile.msvc (OBJECTS): Add read-properties.obj, + write-properties.obj. + (read-properties.obj, write-properties.obj): New rules. + * Makefile.vms (OBJECTS): Add read-properties.obj, + write-properties.obj. + (read-properties.obj, write-properties.obj): New rules. + * FILES: Update. + +2003-04-21 Bruno Haible + + * read-po-abstract.h (po_scan_file): Remove declaration. + * read-po-abstract.c (po_scan_file): Remove function. + * read-po.c (read_po_file): Call read_po. + * msgfmt.c (read_po_file_msgfmt): Call open_po_file. Invoke po_scan + instead of po_scan_file. + * xgettext.c (read_exclusion_file): Call open_po_file. Invoke po_scan + instead of po_scan_file. + +2003-04-20 Bruno Haible + + * open-po.h (open_po_file): Add argument 'exit_on_error'. + * open-po.c (try_open_po_file): Renamed from open_po_file. + (open_po_file): New function. + * po-lex.h (lex_end): Change return type to void. + (lex_open, lex_close): Remove declarations. + * po-lex.c (lex_end): Change return type to void. + (lex_open, lex_close): Remove functions. + * read-po-abstract.c (po_scan_file): Inline lex_open and lex_close. + Reuse po_scan code. + +2003-04-14 Bruno Haible + + * hostname.c (usage): Split strings: use one string per option + description. + * msgattrib.c (usage): Likewise. + * msgcat.c (usage): Likewise. + * msgcmp.c (usage): Likewise. + * msgcomm.c (usage): Likewise. + * msgconv.c (usage): Likewise. + * msgen.c (usage): Likewise. + * msgexec.c (usage): Likewise. + * msgfilter.c (usage): Likewise. + * msgfmt.c (usage): Likewise. + * msggrep.c (usage): Likewise. + * msginit.c (usage): Likewise. + * msgmerge.c (usage): Likewise. + * msgunfmt.c (usage): Likewise. + * msguniq.c (usage): Likewise. + * urlget.c (usage): Likewise. + * xgettext.c (usage): Likewise. + +2003-04-13 Bruno Haible + + Unify three PO file parsers. + * read-po-abstract.h: Renamed from po.h. + (abstract_po_reader_class_ty): Renamed from po_method_ty. + (abstract_po_reader_ty): Renamed from po_ty. + (ABSTRACT_PO_READER_TY): Renamed from PO_BASE_TY. + (po_reader_alloc): Renamed from po_alloc. + (po_reader_free): Renamed from po_free. + * read-po-abstract.c: Renamed from po.c. + (po_reader_alloc): Renamed from po_alloc. + (po_reader_free): Renamed from po_free. + (call_parse_brief): Renamed from po_parse_brief. + (call_parse_debrief): Renamed from po_parse_debrief. + (call_directive_domain): Renamed from po_directive_domain. + (call_directive_message): Renamed from po_directive_message. + (call_comment): Renamed from po_comment. + (call_comment_dot): Renamed from po_comment_dot. + (call_comment_filepos): Renamed from po_comment_filepos. + (call_comment_special): Renamed from po_comment_special. + * read-po.h: Include read-po-abstract.h. + (default_po_reader_class_ty, default_po_reader_ty): New types. + (ABSTRACT_PO_READER_TY): New macro. + (default_constructor, default_destructor, default_parse_brief, + default_parse_debrief, default_directive_domain, + default_directive_message, default_comment, default_comment_dot, + default_comment_filepos, default_comment_special, default_set_domain, + default_add_message): New declarations. + (default_po_reader_alloc): New declaration. + * read-po.c (default_po_reader_ty): Renamed from readall_class_ty. + Add fields handle_comments, handle_filepos_comments, + allow_domain_directives, allow_duplicates, + allow_duplicates_if_same_msgstr. + (call_set_domain, call_add_message, call_frob_new_message): New + functions. + (default_constructor): Renamed from readall_constructor. Don't set + this->mdlp and this->mlp. + (default_destructor): Renamed from readall_destructor. + (default_parse_brief): Renamed from readall_parse_brief. + (default_parse_debrief): New function. + (default_copy_comment_state, default_reset_comment_state): New + functions. + (default_directive_domain): Renamed from readall_directive_domain. + Call set_domain method. + (default_directive_message): Renamed from readall_directive_message. + Call add_message method. + (default_comment): Renamed from readall_comment. + (default_comment_dot): Renamed from readall_comment_dot. + (default_comment_filepos): Renamed from readall_comment_filepos. + (default_comment_special): Renamed from readall_comment_special. + (default_set_domain, default_add_message): New functions. + (default_methods): Renamed from readall_methods. + (default_po_reader_alloc): New function. + (read_po, read_po_file): Update. + * msgfmt.c: Include read-po.h instead of po.h. + (msgfmt_po_reader_ty): Renamed from msgfmt_class_ty. Inherit + from default_po_reader_ty. + (main): Drop po_lex_pass_comments call, done by default_parse_brief. + (msgfmt_constructor): Renamed from format_constructor. Call superclass + function. + (msgfmt_parse_debrief): Renamed from format_debrief. Call superclass + function. + (format_directive_domain): Remove function. + (msgfmt_set_domain): New function. + (format_directive_message): Remove function. + (msgfmt_add_message, msgfmt_frob_new_message): New functions. + (msgfmt_comment_special): Renamed from format_comment_special. + (msgfmt_methods): Renamed from format_methods. + (read_po_file_msgfmt): Renamed from read_po_file. + * x-po.c: Include read-po.h instead of po.h. + (extract_class_ty, extract_constructor, extract_directive_domain, + extract_directive_message, extract_parse_brief, extract_comment, + extract_comment_dot, extract_comment_filepos, extract_comment_special): + Remove functions. + (extract_add_message): New function. + (extract_methods): Update. + (extract_po): Update. + * xgettext.c: Include read-po-abstract.h instead of po.h. + (exclude_directive_domain, exclude_directive_message, exclude_methods, + read_exclusion_file): Update. + * msgcmp.c: Don't include po.h. + * msgmerge.c: Likewise. + * po-gram-gen.y: Include read-po-abstract.h instead of po.h. + * po-hash-gen.y: Likewise. + (po_parse_comment_filepos): Renamed from po_hash. + * po-hash.h (po_parse_comment_filepos): Renamed from po_hash. + * Makefile.am (noinst_HEADERS): Remove po.h, add read-po-abstract.h. + (COMMON_SOURCE): Remove po.c, add read-po-abstract.c. + * Makefile.msvc (OBJECTS): Remove po.obj, add read-po-abstract.obj. + (read-po-abstract.obj): Renamed from po.obj. + * Makefile.vms (OBJECTS): Remove po.obj, add read-po-abstract.obj. + (read-po-abstract.obj): Renamed from po.obj. + +2003-04-13 Bruno Haible + + * read-po.c (readall_directive_message): Separate accumulation and + reset. + * x-po.c (extract_directive_message): Likewise. + +2003-04-13 Bruno Haible + + * write-po.h (message_print_comment, message_print_comment_dot, + message_print_comment_filepos, message_print_comment_flags): New + declarations. + * write-po.c: Reorder functions. + (message_print_comment, message_print_comment_dot, + message_print_comment_filepos, message_print_comment_flags): New + functions, extracted from message_print. + (message_print): Use them. + (message_print_obsolete): Use message_print_comment. + +2003-04-22 Bruno Haible + + * plural-table.c (plural_table): Add an entry about Faroese. + Reported by Jacob Sparre Andersen . + +2003-04-13 Bruno Haible + + * po.h (po_scan_start, po_scan_end): New declarations. + * po.c: Reorder functions. + (po_scan_start, po_scan_end): New functions. + (po_scan, po_scan_file): Use them. + +2003-04-12 Bruno Haible + + * Makefile.vms: New variables ABIFLAGS, DEFS. Avoid rules with no + lines. Don't use the force target. Correct wildcard syntax. + Suggested by Jouk Jansen . + +2003-04-13 Bruno Haible + + * xgettext.c (construct_header): Remove spurious comma. + +2003-03-30 Bruno Haible + + * Makefile.vms: New file. + * Makefile.am (EXTRA_DIST): Add Makefile.vms. + (x-java.c): Prepend a few VMS specific lines. + * po-lex.c (mbfile_getc): Change element type of scatchbuf to + 'unsigned char'. + * write-java.c (string_hashcode, write_java_string): Cast argument of + u8_mbtouc. + * write-tcl.c (write_tcl_string): Likewise. + * x-python.c (phase7_getuc): Cast argument of unicode_name_character. + * msgfilter.c (write): #undef before redefining it. Needed for VMS. + (select): Likewise. + +2003-03-30 Bruno Haible + + * Makefile.am (noinst_HEADERS): Add po-gram-gen2.h. + (BUILT_SOURCES): Add po-gram-gen2.h. + (DISTCLEANFILES): Remove po-gram-gen2.h. + (po-gram-gen2.h): Simplify rule. + +2003-03-17 Bruno Haible + + Native Woe32/MSVC support. + * Makefile.msvc: New file. + * Makefile.am (EXTRA_DIST): Add it. + * msgfilter.c: Include only if it exists. + (process_string): Mark as NYI on Woe32. + * msgfmt.c (sigjmp_buf, sigsetjmp, siglongjmp): Provide fallback + definitions for Woe32. + * msginit.c: Include , for F_OK. Include only if it + exists. + (get_user_pwd, get_user_fullname): Adapt for when is missing. + (project_id, project_id_version, get_user_email, + language_team_address): Use DEV_NULL instead of "/dev/null". + * msggrep.c (is_string_selected): Likewise. + * read-java.c (execute_and_read_po_output): Likewise. + * read-tcl.c (msgdomain_read_tcl): Likewise. + * write-mo.c: Include only if it exists. + +2003-03-28 Bruno Haible + + * msgmerge.c (main): Use copy_file_preserving instead of copy_file. + +2003-03-16 Bruno Haible + + * str-list.h (string_list_join): Comment out. + * str-list.c (string_list_join): Comment out. + +2003-03-10 Bruno Haible + + * msggrep.c (no_pass): Mention option -C. + (usage): Document option -C completely. + Reported by Martin Quinson . + +2003-02-28 Bruno Haible + + Support for relocatable installation. + * msgattrib.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgcat.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgcmp.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgcomm.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgconv.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgen.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgexec.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgfilter.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgfmt.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msggrep.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msginit.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + (project_id, project_id_version, get_user_email): Relocate LIBDIR + value. + (language_team_address): Relocate LIBDIR and PROJECTSDIR values. + * msgmerge.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msgunfmt.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * msguniq.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * xgettext.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. + * read-java.c: Include relocatable.h. + (msgdomain_read_java): Relocate GETTEXTJEXEDIR value. + * read-tcl.c: Include relocatable.h. + (msgdomain_read_tcl): Relocate GETTEXTDATADIR value. + * hostname.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. Update copyright year. + * urlget.c: Include relocatable.h. + (main): Relocate LOCALEDIR value. Update copyright year. + (fetch): Relocate GETTEXTJEXEDIR value. + * user-email.in: Relocate $libdir. + * Makefile.am (aliaspath): New variable. + (DEFS): Define LOCALE_ALIAS_PATH. + (msginit_SOURCES): Add .../localealias.c. + (msginit_LDADD): Remove .../localealias.lo. + (msgcmp_CFLAGS, msgfmt_CFLAGS, msgmerge_CFLAGS, msgunfmt_CFLAGS, + xgettext_CFLAGS, msgattrib_CFLAGS, msgcat_CFLAGS, msgcomm_CFLAGS, + msgconv_CFLAGS, msgen_CFLAGS, msgexec_CFLAGS, msgfilter_CFLAGS, + msggrep_CFLAGS, msginit_CFLAGS, msguniq_CFLAGS, hostname_CFLAGS, + urlget_CFLAGS): New variables. + (msgcmp_LDFLAGS, msgfmt_LDFLAGS, msgmerge_LDFLAGS, msgunfmt_LDFLAGS, + xgettext_LDFLAGS, msgattrib_LDFLAGS, msgcat_LDFLAGS, msgcomm_LDFLAGS, + msgconv_LDFLAGS, msgen_LDFLAGS, msgexec_LDFLAGS, msgfilter_LDFLAGS, + msggrep_LDFLAGS, msginit_LDFLAGS, msguniq_LDFLAGS, hostname_LDFLAGS, + urlget_LDFLAGS): New variables. + (install-exec-java-yes): Don't ignore INSTALL_PROGRAM_ENV. + (RELOCATABLE_LIBRARY_PATH, RELOCATABLE_SRC_DIR, RELOCATABLE_BUILD_DIR, + RELOCATABLE_CONFIG_H_DIR): New variables. + +2003-03-13 Bruno Haible + + * msggrep.c (main): On Solaris, prefer /usr/xpg4/bin/grep to + /usr/bin/grep. Needed for Solaris 2.8 and 2.9. + Reported by Sven Utcke . + +2003-03-03 Bruno Haible + + * msgen.c (usage): Don't say that the entries are marked fuzzy. + Reported by Karl Eichwalder . + +2003-02-23 Bruno Haible + + Improve error messages for invalid format strings. + * format-invalid.h: New file. + * format.h (struct formatstring_parser): Add invalid_reason argument + to 'parse' field. + * format-awk.c: Include c-ctype.h, xerror.h, format-invalid.h. + (format_parse): Add invalid_reason argument. + * format-c.c: Include c-ctype.h, xerror.h, format-invalid.h. + (INVALID_C99_MACRO): New macro. + (format_parse): Add invalid_reason argument. + (get_c99_format_directives): Update. + * format-elisp.c: Include c-ctype.h, xerror.h, format-invalid.h. + (format_parse): Add invalid_reason argument. + * format-java.c: Include xerror.h, format-invalid.h. + (message_format_parse, choice_format_parse, format_parse): Add + invalid_reason argument. + (choice_format_parse): Return false if a choice contains an empty + number part. + * format-librep.c: Include c-ctype.h, xerror.h, format-invalid.h. + (format_parse): Add invalid_reason argument. + * format-lisp.c: Include xerror.h, format-invalid.h. + (check_params, nocheck_params): Add directives, invalid_reason + arguments. + (parse_upto, format_parse): Add invalid_reason argument. + * format-pascal.c: Include xerror.h, format-invalid.h. + (format_parse): Add invalid_reason argument. + * format-php.c: Include c-ctype.h, xerror.h, format-invalid.h. + (format_parse): Add invalid_reason argument. + * format-python.c: Include c-ctype.h, xerror.h, format-invalid.h. + (INVALID_MIXES_NAMED_UNNAMED): New macro. + (format_parse): Add invalid_reason argument. + * format-tcl.c: Include c-ctype.h, xerror.h, format-invalid.h. + (format_parse): Add invalid_reason argument. + * format-ycp.c: Include c-ctype.h, xerror.h, format-invalid.h. + (format_parse): Add invalid_reason argument. + * msgfmt.c (check_pair): Output invalid_reason returned for msgstr. + * msgmerge.c (msgfmt_check_pair_fails): Update. + * xgettext.c (remember_a_message, remember_a_message_plural): Update. + * Makefile.am (FORMAT_SOURCE): Add format-invalid.h. + +2003-02-22 Bruno Haible + + * x-python.c (init_keywords): Add u*gettext variants and plural + handling functions added in Python 2.3. + +2003-02-22 Bruno Haible + + * Makefile.am (installdirs): Remove dependency, redundant with + automake >= 1.6. + +2003-02-20 Bruno Haible + + * Makefile.am (libgettextpo_la_DEPENDENCIES): New variable. + Reported by Jim Meyering . + +2003-02-19 Bruno Haible + + * Makefile.am (xgettext_LDADD): Mention $(LIBUNINAME) before + libgettextsrc.la, not after it. Needed avoid link error on OSF/1 4.0: + libtool produces a link command line that contains "-lc" where a + shared library was used, and on OSF/1 4.0 "-lc" must not be used + before objects that use integer division. + +2003-02-18 Bruno Haible + + * message.c (msgdomain_list_free): Uncomment this function. + * Makefile.am (libgettextpo_la_LDFLAGS): Add ../lib/libgettextlib.la. + Needed for Solaris 2.7. + +2003-02-16 Bruno Haible + + * msgmerge.c (message_merge): Add support for Report-Msgid-Bugs-To + field. + * xgettext.c (msgid_bugs_address): New variable. + (long_options): Add option --msgid-bugs-address. + (main): Handle option --msgid-bugs-address. + (usage): Document option --msgid-bugs-address. + (construct_header): Add 'Report-Msgid-Bugs-To:' field. Warn if + msgid_bugs_address is empty. + +2003-02-15 Bruno Haible + + * user-email.in: Add support for GNOME evolution, OpenOffice and + StarOffice with nonstandard installation directory. + +2003-02-15 Bruno Haible + + * msgattrib.c (main): Update year in --version output. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + +2003-02-12 Bruno Haible + + Big restructuring. + * gettext.c: Remove file. + * ngettext.c: Remove file. + * Makefile.am (bin_PROGRAMS): Remove gettext, ngettext. + (EXTRA_DIST): Add ChangeLog.0. + (pkgdatadir): Define using 'gettext' instead of @PACKAGE@. + (pkglibdir): Likewise. + (INCLUDES): Refer to intl in gettext-runtime directory. + (gettext_SOURCES, ngettext_SOURCES, gettext_LDADD, ngettext_LDADD): + Remove variables. + (install-exec-local, installdirs-local, uninstall-local, + install-exec-java-yes, installdirs-java-yes, uninstall-java-yes): Use + $(pkglibdir) instead of $(libdir)/$(PACKAGE). + * plural.c: Update for changed directory structure. + * user-email.in: Use 'gettext' instead of @PACKAGE@. + + +See ChangeLog.0 for earlier changes. diff --git a/gettext-tools/src/ChangeLog.0 b/gettext-tools/src/ChangeLog.0 new file mode 100644 index 0000000..a049391 --- /dev/null +++ b/gettext-tools/src/ChangeLog.0 @@ -0,0 +1,5247 @@ +2003-01-31 Bruno Haible + + * gettext-po.h: New file. + * gettext-po.c: New file. + * Makefile.am (lib_LTLIBRARIES): Add libgettextpo.la. + (include_HEADERS): New variable. + (libgettextpo_la_SOURCES, libgettextpo_la_LDFLAGS): New variables. + (LTV_CURRENT, LTV_REVISION, LTV_AGE): New variables. + +2003-01-24 Bruno Haible + + * msgattrib.c (long_options): Add --only-file and --ignore-file. + (main): Handle options --only-file and --ignore-file. + (usage): Document options --only-file and --ignore-file. + (process_message_list): Add arguments only_mdlp, ignore_mdlp. + (process_msgdomain_list): Likewise. + +2003-01-23 Bruno Haible + + * po-gram.h: Don't include . + +2003-01-23 Bruno Haible + + * msgmerge.c (use_fuzzy_matching): New variable. + (long_options): Add option -N/--no-fuzzy-matching. + (main, match_domain): Implement it. + (usage): Document it. + +2003-01-23 Bruno Haible + + * write-mo.c (write_table): Use xmalloc/free instead of alloca/freea + for most allocations. Needed for platforms with a small stack, such + as Woe32 with a default stack size of 1.2 MB. + Reported by Xiong Jiang . + +2003-01-23 Bruno Haible + + * format-c.c (get_c99_format_directives): Free the allocated + descriptor after use. + +2003-01-18 Bruno Haible + + * FILES: Update. + +2003-01-12 Bruno Haible + + * msgconv.c: Include localcharset.h. + (locale_charset): Remove declaration. + * msginit.c: Include localcharset.h. + (locale_charset): Remove declaration. + * msgl-charset.c: Include localcharset.h. + (locale_charset): Remove declaration. + +2003-01-12 Bruno Haible + + * format-java.c: Include alloca.h instead of liballoca.h. + * msggrep.c: Likewise. + * msginit.c: Likewise. + * msgl-cat.c: Likewise. + * msgl-charset.c: Likewise. + * msgl-iconv.c: Likewise. + * msgmerge.c: Likewise. + * po-charset.c: Likewise. + * read-tcl.c: Likewise. + * write-java.c: Likewise. + * write-mo.c: Likewise. + * write-po.c: Likewise. + * write-tcl.c: Likewise. + +2003-01-12 Bruno Haible + + * Makefile.am: Make use of += for variables. + +2003-01-12 Bruno Haible + + * write-po.c (wrap): Add an assertion, to protect against Solaris 2.9 + iconv() bug. + +2003-01-10 Bruno Haible + + * po-charset.c (po_lex_charset_set): Work around Solaris 2.9 iconv() + bug. + * write-po.c (wrap): Likewise. + +2003-01-08 Bruno Haible + + * project-id: Make it work with configure files that use + AM_INIT_AUTOMAKE from automake-1.7.2. + +2002-12-04 Bruno Haible + + Fix handling of references to filenames starting with a digit. + * po-hash-gen.y (last_was_colon): New variable. + (yylex): Update it for each token. Recognize numbers only immediately + after a colon. + (po_hash): Move function. Initialize last_was_colon. + Reported by Yanko Kaneti and + Jordi Mallach . + +2002-11-22 Bruno Haible + + * msgexec.c (process_string): Change test of full_write return value. + * msggrep.c (is_string_selected): Likewise. + * urlget.c (cat_file): Likewise. + +2002-11-14 Bruno Haible + + * x-php.c (phase3_getc): Initialize variable last_was_qmark. + +2002-11-13 Bruno Haible + + Assume ANSI C. + * dir-list.h (dir_list_append, dir_list_nth, dir_list_save_reset, + dir_list_restore): Use ANSI C function declarations. + * dir-list.c (dir_list_append, dir_list_nth, dir_list_restore): + Likewise. + * file-list.h (read_names_from_file): Likewise. + * file-list.c (read_names_from_file): Likewise. + * format.h (struct formatstring_parser, get_c99_format_directives): + Likewise. + * format-awk.c (numbered_arg_compare, format_parse, format_free, + format_get_number_of_directives, format_check, format_print): Likewise. + * format-c.c (numbered_arg_compare, format_parse, format_free, + format_get_number_of_directives, format_check, + get_c99_format_directives, format_print): Likewise. + * format-elisp.c (numbered_arg_compare, format_parse, format_free, + format_get_number_of_directives, format_check, format_print): Likewise. + * format-java.c (message_format_parse, date_format_parse, + number_format_parse, choice_format_parse, numbered_arg_compare, + format_parse, format_free, format_get_number_of_directives, + format_check, format_print): Likewise. + * format-librep.c (numbered_arg_compare, format_parse, format_free, + format_get_number_of_directives, format_check, format_print): Likewise. + * format-lisp.c (verify_element, verify_list, free_element, free_list, + copy_element, copy_list, equal_element, equal_list, + ensure_initial_alloc, grow_initial_alloc, ensure_repeated_alloc, + grow_repeated_alloc, normalize_outermost_list, normalize_list, + is_empty_list, unfold_loop, rotate_loop, initial_splitelement, + initial_unshare, shift_list, make_intersected_element, + append_repeated_to_initial, backtrack_in_initial, + make_intersected_list, make_intersection_with_empty_list, + intersection, make_union_element, make_union_list, + make_union_with_empty_list, union, is_required, + add_required_constraint, add_end_constraint, add_type_constraint, + add_listtype_constraint, add_req_type_constraint, + add_req_listtype_constraint, make_repeated_list_of_lists, + make_repeated_list, check_params, nocheck_params, parse_upto, + format_parse, format_free, format_get_number_of_directives, + format_check, print_element, print_list, format_print): Likewise. + * format-pascal.c (numbered_arg_compare, format_parse, format_free, + format_get_number_of_directives, format_check, format_print): Likewise. + * format-php.c (numbered_arg_compare, format_parse, format_free, + format_get_number_of_directives, format_check, format_print): Likewise. + * format-python.c (named_arg_compare, format_parse, format_free, + format_get_number_of_directives, format_check, format_print): Likewise. + * format-tcl.c (numbered_arg_compare, format_parse, format_free, + format_get_number_of_directives, format_check, format_print): Likewise. + * format-ycp.c (format_parse, format_free, + format_get_number_of_directives, format_check, format_print): Likewise. + * gettext.c (main, usage, expand_escape): Likewise. + * hostname.c (main, usage): Likewise. + * message.h (possible_format_p, message_alloc, message_free, + message_comment_append, message_comment_dot_append, + message_comment_filepos, message_copy, message_list_alloc, + message_list_free, message_list_append, message_list_prepend, + message_list_delete_nth, message_predicate_ty, + message_list_remove_if_not, message_list_search, + message_list_search_fuzzy, message_list_list_alloc, + message_list_list_free, message_list_list_append, + message_list_list_append_list, message_list_list_search, + message_list_list_search_fuzzy, msgdomain_alloc, msgdomain_free, + msgdomain_list_alloc, msgdomain_list_free, msgdomain_list_append, + msgdomain_list_append_list, msgdomain_list_sublist, + msgdomain_list_search, msgdomain_list_search_fuzzy): Likewise. + * message.c (possible_format_p, message_alloc, message_free, + message_comment_append, message_comment_dot_append, + message_comment_filepos, message_copy, message_list_alloc, + message_list_free, message_list_append, message_list_prepend, + message_list_delete_nth, message_list_remove_if_not, + message_list_search, message_list_search_fuzzy_inner, + message_list_search_fuzzy, message_list_list_free, + message_list_list_append, message_list_list_append_list, + message_list_list_search, message_list_list_search_fuzzy, + msgdomain_alloc, msgdomain_free, msgdomain_list_alloc, + msgdomain_list_free, msgdomain_list_append, + msgdomain_list_append_list, msgdomain_list_sublist, + msgdomain_list_search, msgdomain_list_search_fuzzy): Likewise. + * msgattrib.c (main, usage, is_message_selected, process_message_list, + process_msgdomain_list): Likewise. + * msgcat.c (main, usage): Likewise. + * msgcmp.c (main, usage, is_message_selected, remove_obsoletes, + match_domain, compare): Likewise. + * msgcomm.c (main, usage): Likewise. + * msgconv.c (locale_charset, main, usage): Likewise. + * msgen.c (main, usage): Likewise. + * msgexec.c (main, usage, nonintr_close, process_string, + process_message, process_message_list, process_msgdomain_list): + Likewise. + * msgfilter.c (main, usage, nonintr_close, nonintr_read, nonintr_write, + nonintr_select, process_string, process_message, process_message_list, + process_msgdomain_list): Likewise. + * msgfmt.c (main, usage, add_mo_suffix, new_domain, is_nonobsolete, + sigfpe_handler, check_plural_eval, check_plural, check_pair, + check_header_entry, format_constructor, format_debrief, + format_directive_domain, format_directive_message, + format_comment_special, read_po_file): Likewise. + * msggrep.c (main, no_pass, usage, filename_list_match, nonintr_close, + is_string_selected, is_message_selected, process_message_list, + process_msgdomain_list): Likewise. + * msginit.c (locale_charset, _nl_locale_name, _nl_expand_alias, main, + usage, catalogname_for_locale, language_of_locale, po_revision_date, + content_type, fields, get_field, put_field, subst_string, + subst_string_list, fill_header): Likewise. + * msgl-ascii.h (is_ascii_string, is_ascii_string_list, + is_ascii_message, is_ascii_message_list): Likewise. + * msgl-ascii.c (is_ascii_string, is_ascii_string_list, + is_ascii_message, is_ascii_message_list): Likewise. + * msgl-cat.h (catenate_msgdomain_list): Likewise. + * msgl-cat.c (is_message_selected, is_message_needed, + is_message_first_needed, catenate_msgdomain_list): Likewise. + * msgl-charset.h (compare_po_locale_charsets): Likewise. + * msgl-charset.c (locale_charset, compare_po_locale_charsets): + Likewise. + * msgl-english.h (msgdomain_list_english): Likewise. + * msgl-english.c (msgdomain_list_english): Likewise. + * msgl-equal.h (string_list_equal, message_equal, message_list_equal, + msgdomain_list_equal): Likewise. + * msgl-equal.c (msgstr_equal, msgstr_equal_ignoring_potcdate, + pos_equal, string_list_equal, message_equal, message_list_equal, + msgdomain_equal, msgdomain_list_equal): Likewise. + * msgl-iconv.h (convert_string, iconv_message_list, + iconv_msgdomain_list): Likewise. + * msgl-iconv.c (iconv_string, convert_string, convert_string_list, + convert_msgid, convert_msgstr, iconv_message_list, + iconv_msgdomain_list): Likewise. + * msgmerge.c (main, usage, compendium, msgfmt_check_pair_fails, + message_merge, match_domain, merge): Likewise. + * msgunfmt.c (main, usage): Likewise. + * msguniq.c (main, usage): Likewise. + * ngettext.c (main, usage, expand_escape): Likewise. + * open-po.h (open_po_file): Likewise. + * open-po.c (open_po_file): Likewise. + * po-charset.h (po_charset_canonicalize, po_charset_ascii_compatible, + po_is_charset_weird, po_is_charset_weird_cjk, po_lex_charset_init, + po_lex_charset_set, po_lex_charset_close): Likewise. + * po-charset.c (po_charset_canonicalize, po_charset_ascii_compatible, + po_is_charset_weird, po_is_charset_weird_cjk, po_lex_charset_set): + Likewise. + * po-gram.h (po_gram_parse): Likewise. + * po-hash-gen.y (yyerror, yylex, po_hash): Likewise. + * po-hash.h (po_hash): Likewise. + * po-lex.h (lex_start, lex_end, lex_open, lex_close, po_gram_lex, + po_lex_pass_comments, po_lex_pass_obsolete_entries, po_gram_error, + po_gram_error_at_line): Likewise. + * po-lex.c: Include instead of libstdarg.h. + (ALERT_CHAR): Remove macro. + (po_gram_error, po_gram_error_at_line): Use ANSI C function + declarations. Use va_start instead of VA_START. + (memcpy_small, mb_iseof, mb_ptr, mb_len, mb_iseq, mb_isnul, mb_cmp, + mb_equal, mb_isascii, mb_width, mb_putc, mb_setascii, mb_copy, + mbfile_init, mbfile_getc, mbfile_ungetc, lex_start, lex_open, + lex_getc, lex_ungetc, keyword_p, po_lex_pass_comments, + po_lex_pass_obsolete_entries): Use ANSI C function declarations. + * po-time.h (po_strftime): Likewise. + * po-time.c (difftm, po_strftime): Likewise. + * po.h (struct po_method_ty, po_alloc, po_scan, po_scan_file, po_free, + po_callback_domain, po_callback_message, po_callback_comment, + po_callback_comment_dot, po_callback_comment_filepos, + po_parse_comment_special): Likewise. + * po.c (po_alloc, po_free, po_parse_brief, po_parse_debrief, po_scan, + po_scan_file, po_directive_domain, po_callback_domain, + po_directive_message, po_callback_message, po_comment_special, + po_comment, po_comment_dot, po_callback_comment, po_comment_filepos, + po_callback_comment_filepos, po_parse_comment_special): Likewise. + * read-java.h (msgdomain_read_java): Likewise. + * read-java.c (execute_and_read_po_output, msgdomain_read_java): + Likewise. + * read-mo.h (read_mo_file): Likewise. + * read-mo.c (read_binary_mo_file, get_uint32, get_string, + get_sysdep_string, read_mo_file): Likewise. + * read-po.h (read_po, read_po_file): Likewise. + * read-po.c (readall_constructor, readall_destructor, + readall_directive_domain, readall_directive_message, + readall_parse_brief, readall_comment, readall_comment_dot, + readall_comment_special, readall_comment_filepos, read_po, + read_po_file): Likewise. + * read-tcl.h (msgdomain_read_tcl): Likewise. + * read-tcl.c (msgdomain_read_tcl): Likewise. + * str-list.h (string_list_init, string_list_alloc, string_list_append, + string_list_append_unique, string_list_destroy, string_list_free, + string_list_concat, string_list_concat_destroy, string_list_join, + string_list_member): Likewise. + * str-list.c (string_list_init, string_list_append, + string_list_append_unique, string_list_destroy, string_list_free, + string_list_concat, string_list_concat_destroy, string_list_join, + string_list_member): Likewise. + * urlget.c (main, usage, cat_file, execute_it, fetch): Likewise. + * write-java.h (msgdomain_write_java): Likewise. + * write-java.c (check_resource_name, string_hashcode, + compute_hashsize, compare_index, compute_table_items, + write_java_string, write_java_msgstr, write_lookup_code, + is_expression_boolean, write_java_expression, write_java_code, + cleanup, msgdomain_write_java): Likewise. + * write-mo.h (msgdomain_write_mo): Likewise. + * write-mo.c (compare_id, write_table, msgdomain_write_mo): Likewise. + * write-po.h (message_page_width_set, message_page_width_ignore, + message_print_style_indent, message_print_style_uniforum, + message_print_style_escape, msgdomain_list_print, + msgdomain_list_sort_by_msgid, msgdomain_list_sort_by_filepos): + Likewise. + * write-po.c (message_page_width_set, message_print_style_escape, + make_format_description_string, significant_format_p, + has_significant_format_p, make_c_width_description_string, + memcpy_small, wrap, print_blank_line, message_print, + message_print_obsolete, msgdomain_list_print, cmp_by_msgid, + msgdomain_list_sort_by_msgid, cmp_filepos, + msgdomain_list_sort_filepos, cmp_by_filepos, + msgdomain_list_sort_by_filepos): Likewise. + * write-tcl.h (msgdomain_write_tcl): Likewise. + * write-tcl.c (write_tcl_string, write_msg, msgdomain_write_tcl): + Likewise. + * x-awk.h (extract_awk, x_awk_keyword, x_awk_extract_all): Likewise. + * x-awk.c (ALERT_CHAR): Remove macro. + (x_awk_keyword, phase1_ungetc, phase2_ungetc, free_token, x_awk_lex, + extract_parenthesized, extract_awk): Use ANSI C function declarations. + * x-c.h (extract_c, x_c_extract_all, x_c_keyword, x_c_any_keywords, + x_c_trigraphs): Likewise. + * x-c.c (ALERT_CHAR): Remove macro. + (x_c_keyword, phase1_ungetc, phase2_ungetc, phase3_ungetc, + comment_add, comment_line_end, phase4_ungetc, phase7_ungetc, + free_token, phase5_get, phaseX_get, phase6_get, phase6_unget, + is_inttypes_macro, phase8a_get, phase8a_unget, phase8_get, x_c_lex, + extract_parenthesized, extract_c): Use ANSI C function declarations. + * x-elisp.h (extract_elisp, x_elisp_extract_all, x_elisp_keyword): + Likewise. + * x-elisp.c (ALERT_CHAR): Remove macro. + (x_elisp_keyword, do_ungetc, init_token, free_token, grow_token, + is_integer, is_float, read_token, comment_add, comment_line_end, + free_object, string_of_object, do_getc_escaped, read_object, + extract_elisp): Use ANSI C function declarations. + * x-glade.h (extract_glade, x_glade_extract_all, x_glade_keyword): + Likewise. + * x-glade.c (x_glade_keyword, XML_StartElementHandler, + XML_EndElementHandler, XML_CharacterDataHandler, XML_CommentHandler, + p_XML_ParserCreate, p_XML_SetElementHandler, + p_XML_SetCharacterDataHandler, p_XML_SetCommentHandler, p_XML_Parse, + p_XML_GetErrorCode, p_XML_GetCurrentLineNumber, + p_XML_GetCurrentColumnNumber, p_XML_ParserFree, p_XML_ErrorString, + ensure_stack_size, start_element_handler, end_element_handler, + character_data_handler, comment_handler, do_extract_glade, + extract_glade): Likewise. + * x-java.h (extract_java, x_java_keyword, x_java_extract_all): + Likewise. + * x-java.l (append_char_buf, get_string, destroy_charbuf, + update_line_no, strip_ending_spaces, append_strings, isplus, isdot, + translate_esc, object_list_destroy, get_num_objects, get_object, + add_object, alloc_keyword, tailcmp, do_compare, is_keyword, + x_java_keyword, extract_java): Likewise. + * x-librep.h (extract_librep, x_librep_extract_all, x_librep_keyword): + Likewise. + * x-librep.c (ALERT_CHAR): Remove macro. + (x_librep_keyword, do_ungetc, init_token, free_token, grow_token, + read_token, comment_add, comment_line_end, free_object, + string_of_object, do_getc_escaped, read_object, extract_librep): Use + ANSI C function declarations. + * x-lisp.h (extract_lisp, x_lisp_extract_all, x_lisp_keyword): + Likewise. + * x-lisp.c (x_lisp_keyword, do_ungetc, syntax_code_of, + read_char_syntax, attribute_of, init_token, free_token, grow_token, + read_token, has_a_dot, all_a_number, a_letter_to_digit, has_a_digit, + has_adjacent_letters, is_potential_number, is_number, upcase_token, + downcase_token, case_convert_token, comment_add, comment_line_end, + free_object, string_of_object, read_object, extract_lisp): Likewise. + * x-php.h (extract_php, x_php_keyword, x_php_extract_all): Likewise. + * x-php.c (x_php_keyword, phase1_ungetc, phase2_ungetc, comment_add, + comment_line_end, phase3_ungetc, free_token, x_php_lex, + extract_parenthesized, extract_php): Likewise. + * x-po.h (extract_po): Likewise. + * x-po.c (extract_constructor, extract_directive_domain, + extract_directive_message, extract_parse_brief, extract_comment, + extract_comment_dot, extract_comment_filepos, extract_comment_special, + extract_po): Likewise. + * x-python.h (extract_python, x_python_keyword, x_python_extract_all): + Likewise. + * x-python.c (ALERT_CHAR): Remove macro. + (x_python_keyword, phase1_ungetc, comment_add, phase2_ungetc, + phase7_getuc, phase5_get, phase5_unget, x_python_lex, + extract_parenthesized, extract_python): Use ANSI C function + declarations. + * x-rst.h (extract_rst): Likewise. + * x-rst.c (extract_rst): Likewise. + * x-smalltalk.h (extract_smalltalk): Likewise. + * x-smalltalk.c (phase1_ungetc, comment_add, phase2_get, phase2_unget, + x_smalltalk_lex, extract_smalltalk): Likewise. + * x-tcl.h (extract_tcl, x_tcl_extract_all, x_tcl_keyword): Likewise. + * x-tcl.c (ALERT_CHAR): Remove macro. + (x_tcl_keyword, do_ungetc, phase1_ungetc, phase2_pop, phase2_ungetc, + init_token, free_token, grow_token, comment_add, free_word, + string_of_word, accumulate_word, read_word, read_command, + read_command_list, extract_tcl): Use ANSI C function declarations. + * x-ycp.h (extract_ycp): Likewise. + * x-ycp.c (phase1_ungetc, phase2_ungetc, x_ycp_lex, extract_ycp): + Likewise. + * xgettext.h (split_keywordspec, xgettext_comment_add, + xgettext_comment, xgettext_comment_reset, remember_a_message, + remember_a_message_plural): Likewise. + * xgettext.c (extractor_func, main, usage, exclude_directive_domain, + exclude_directive_message, read_exclusion_file, split_keywordspec, + xgettext_comment_add, xgettext_comment, xgettext_open, + extract_from_file, remember_a_message, remember_a_message_plural, + finalize_header, language_to_extractor, extension_to_language): + Likewise. + +2002-11-05 Bruno Haible + + Allow non-ASCII msgids in POT files. + * po-charset.h (po_charset_utf8): New declaration. + * po-charset.c (utf8, po_charset_utf8): New variables. + (po_charset_canonicalize): Use po_charset_utf8. + * msgl-iconv.h: Include iconv.h. + (convert_string): New declaration. + * msgl-iconv.c (convert_string): Export function. + (convert_msgid): New function. + (iconv_message_list): Call it. + * xgettext.h: Include iconv.h. + (xgettext_global_source_encoding, xgettext_global_source_iconv, + xgettext_current_source_encoding, xgettext_current_source_iconv): New + declarations. + * xgettext.c (xgettext_global_source_encoding, + xgettext_global_source_iconv, xgettext_current_source_encoding, + xgettext_current_source_iconv): New variables. + (long_options): New option --from-code. + (main): Initialize xgettext_global_source_encoding. Handle option + --from-code. Initialize and destroy xgettext_global_source_iconv. + (usage): Document option --from-code. + (extract_from_file): Set xgettext_current_source_encoding and + xgettext_current_source_iconv. + (CONVERT_STRING): New macro. + (remember_a_message, remember_a_message_plural): Call CONVERT_STRING. + (finalize_header): Set the charset in the header here. + * x-glade.c (do_extract_glade): Set xgettext_current_source_encoding. + Don't set the result's header charset; this is now done in xgettext.c. + * x-python.c (extract_python): Likewise. + * x-tcl.c (extract_tcl): Likewise. + * write-po.c (message_print, message_print_obsolete): Don't warn about + non-ASCII msgids if the file's encoding is UTF-8. + * msginit.c (content_type): Add header argument. Use charset UTF-8 + if that was already the POT file's encoding. + (fields): Update. + * msgmerge.c (merge): If the POT file was in UTF-8, convert the + definitions to UTF-8. + * msgcmp.c (compare): Likewise. + +2002-11-01 Bruno Haible + + * msgcmp.c: Include read-po.h. + (is_message_selected, remove_obsoletes): New functions. + (grammar, compare_constructor, compare_destructor, + compare_directive_domain, compare_directive_message): Remove functions. + (compare_class_ty): Remove type. + (compare_methods): Remove variable. + (compare): Use read_po_file instead. + +2002-10-14 Bruno Haible + + * project-id: When testing for "GNU $package", ignore libtool, because + libtool.m4 calls a package "GNU $package" even if it is not GNU. + Reported by Andrew Suffield . + +2002-10-03 Bruno Haible + + * msgl-equal.h (message_equal, message_list_equal, + msgdomain_list_equal): Add 'ignore_potcdate' argument. + * msgl-equal-c (msgstr_equal, msgstr_equal_ignoring_potcdate): New + functions. + (message_equal, message_list_equal, msgdomain_equal, + msgdomain_list_equal): Add 'ignore_potcdate' argument. + * msgmerge.c (main): Call msgdomain_list_equal with ignore_potcdate = + true. + +2002-09-09 Bruno Haible + + * x-smalltalk.h: New file. + * x-smalltalk.c: New file. + * xgettext.c: Include x-smalltalk.h. + (usage): Mention Smalltalk language. + (language_to_extractor): Add Smalltalk support. + (extension_to_language): Likewise. + * Makefile.am (noinst_HEADERS): Add x-smalltalk.h. + (xgettext_SOURCES): Add x-smalltalk.c. + +2002-08-18 Bruno Haible + + * message.h (enum format_type): New enum value 'format_php'. + (NFORMATS): Increment. + * message.c (format_language, format_language_pretty): Add entry + for php. + * format.h (formatstring_php): New declaration. + * format-php.c: New file. + * format.c (formatstring_parsers): Add entry for php. + * x-php.h: New file. + * x-php.c: New file. + * xgettext.c: Include x-php.c. + (main): Call x_php_extract_all, x_php_keyword. + (language_to_scanner): Add PHP rule. + (extension_to_language): Add PHP rule. + * Makefile.am (noinst_HEADERS): Add x-php.h. + (FORMAT_SOURCE): Add format-php.c. + (xgettext_SOURCES): Add x-php.c. + +2002-08-17 Bruno Haible + + * urlget.c (fetch): Also try invoking the 'curl' program. + +2002-08-06 Bruno Haible + + * gettext-0.11.5 released. + +2002-08-02 Bruno Haible + + * read-mo.c (get_sysdep_string): Make the error message easier to + translate. + +2002-07-25 Bruno Haible + + * gettext-0.11.4 released. + +2002-07-21 Bruno Haible + + * format.h (struct interval): New type. + (get_c99_format_directives): New declaration. + * format-c.c (FAT_SIZE_8_T, FAT_SIZE_16_T, FAT_SIZE_32_T, + FAT_SIZE_64_T, FAT_SIZE_LEAST8_T, FAT_SIZE_LEAST16_T, + FAT_SIZE_LEAST32_T, FAT_SIZE_LEAST64_T, FAT_SIZE_FAST8_T, + FAT_SIZE_FAST16_T, FAT_SIZE_FAST32_T, FAT_SIZE_FAST64_T, + FAT_SIZE_INTPTR_T): New enum values. + (FAT_SIZE_MASK): Update. + (struct spec): New fields c99_directives_count, c99_directives. + (format_parse): Recognize C 99 macros. Set the new + fields c99_directives_count, c99_directives in the result. + (get_c99_format_directives): New function. + (format_print): Update. + * x-c.c (is_inttypes_macro, phase8a_get, phase8a_unget): New functions. + (phase8_get): Use phase8a_get instead of phase6_get. + * write-mo.c: Include format.h, xmalloc.h. + (freea): New macro. + (struct pre_string): New type. + (struct pre_message): Renamed from struct id_str_pair. + (compare_id): Update. + (struct pre_sysdep_segment): New type. + (struct pre_segment_pair): New type. + (struct pre_sysdep_string): New type. + (struct pre_sysdep_message): New type. + (write_table): Rewritten. + * read-mo.c (endian): Remove variable. + (read32, seek32, string32): Remove functions. + (struct binary_mo_file): New type. + (read_binary_mo_file, get_uint32, get_string, get_sysdep_string): New + functions. + (read_mo_file): Reworked to work in-memory, without fseek/lseek calls. + * msgfmt.c (format_directive_message): Copy the is_format array. + Needed by write-mo.c. + +2002-07-20 Andreas Schwab + + * plural-eval.c: Include if !INTDIV0_RAISES_SIGFPE. + +2002-07-17 Bruno Haible + + * gettext-0.11.3 released. + +2002-07-17 Bruno Haible + + * dir-list.c: Include stdlib.h. + +2002-07-16 Bruno Haible + + * read-java.c (msgdomain_read_java): Pass $GETTEXTJEXEDIR to + execute_java_class. + * urlget.c (fetch): Likewise. + * Makefile.am (DEFS): Define USEJEXE and GETTEXTJEXEDIR. + (GCJ, GCJFLAGS, USEJEXE): New variables. + (all-java*): Renamed to take into account BUILDJAVAEXE. + (install-data-java*): Likewise. + (installdirs-java*): Likewise. + (uninstall-java*): Likewise. + (install-exec-*): New rules. + (gnu.gettext.DumpResource, gnu.gettext.GetURL): New rules. + (CLEANFILES): Add them. + +2002-07-16 Bruno Haible + + * project-id: Make it work when the configure file was generated by + autoconf-2.52 with AC_INIT and AM_INIT_AUTOMAKE(...). + +2002-06-14 Bruno Haible + + * po-lex.c (mbfile_getc): Handle the case where iconv converts + some character and returns -1 at once; this can happen immediately + after an invalid multibyte sequence. + Reported by Andrew V. Samoilov . + +2002-06-08 Bruno Haible + + * user-email.in: Look at /etc/sysconfig/mail, found on SuSE Linux. + Suggested by Karl Eichwalder . + +2002-06-07 Bruno Haible + + * user-email.in: Look at /etc/mailname, found on Debian systems. + Suggested by Mark W. Eichin . + +2002-06-07 Bruno Haible + + * msgfmt.c (install_sigfpe_handler): Assume the signal is always + SIGFPE, regardless of the platform. + (uninstall_sigfpe_handler): Likewise. + +2002-05-29 Bruno Haible + + * dir-list.h (dir_list_save_reset, dir_list_restore): New declarations. + * dir-list.c (dir_list_save_reset, dir_list_restore): New functions. + * xgettext.c (main): Temporarily reset the dir_list while opening + reading the contents of the output file. + Reported by Mark Eichin . + +2002-05-22 Bruno Haible + + * msgl-iconv.h (iconv_message_list): Add from_filename argument. + (iconv_msgdomain_list): Likewise. + * msgl-iconv.c (iconv_message_list): Add from_filename argument. + Don't give an error for POT files containing charset=CHARSET. + (iconv_msgdomain_list): Add from_filename argument. + * msgl-cat.c (catenate_msgdomain_list): Don't give an error for + POT files containing charset=CHARSET. Pass input file name to + iconv_message_list. + * msgconv.c (main): Pass input file name to iconv_msgdomain_list. + * write-java.c (msgdomain_write_java): Update. + * write-tcl.c (msgdomain_write_tcl): Update. + * x-glade.c (do_extract_glade): Update. + * x-python.c (extract_python): Update. + * x-tcl.c (extract_tcl): Update. + +2002-05-18 Bruno Haible + + * Makefile.am (RM): New variable. + (install-exec-am): Depend on install-exec-clean. + (install-exec-clean): New rule. + +2002-05-14 Bruno Haible + + * write-po.c (wrap): Canonicalize the charset. Handle the case when + po_charset_canonicalize returns NULL. + Reported by Jan-Marek Glogowski . + +2002-05-12 Bruno Haible + + * po-charset.c (po_charset_canonicalize): Add ISO-8859-14, KOI8-T, + GEORGIAN-PS to the list of allowed encodings. + +2002-04-30 Bruno Haible + + * hostname.c (ipv6_ntop): Provide a fallback definition for systems + that define s6_addr but no s6_addr16 element of 'struct in6_addr'. + Reported by Perry Rapp . + +2002-04-28 Bruno Haible + + * project-id: Recognize package name and version when given by a + two-argument AC_INIT invocation (autoconf 2.52 and newer). + +2002-04-24 Bruno Haible + + * gettext-0.11.2 released. + +2002-04-23 Bruno Haible + + * write-po.h (message_page_width_ignore): New declaration. + * write-po.c (wrap_strings): New variable. + (message_page_width_ignore): New function. + (wrap): Ignore the do_wrap argument if wrap_strings is false. + * msgattrib.c (long_options): Add --no-wrap option. + (main): Handle option --no-wrap. + (usage): Document option --no-wrap. + * msgcat.c: Likewise. + * msgcomm.c: Likewise. + * msgconv.c: Likewise. + * msgen.c: Likewise. + * msgfilter.c: Likewise. + * msggrep.c: Likewise. + * msginit.c: Likewise. + * msgmerge.c: Likewise. + * msgunfmt.c: Likewise. + * msguniq.c: Likewise. + * xgettext.c: Likewise. + +2002-04-19 Bruno Haible + + * msginit.c (catalogname_for_locale): Add wa_BE. Change Javanese from + jw to jv. + +2002-04-15 Bruno Haible + + * po-lex.h (po_gram_error): Treat Apple's version of GCC like non-GCC. + * po-lex.c (po_gram_error): Likewise. + Reported by Richard S. Blake . + +2002-04-04 Bruno Haible + + * po-lex.h (po_gram_error): Use __VA_ARGS__ + 0 instead of __VA_ARGS__. + (po_gram_error_at_line): LIkewise. + Fixes syntax error when __VA_ARGS__ is empty. + Reported by Trond Eivind Glomsrød . + +2002-04-04 Bruno Haible + + * msginit.c (find_pot): Add dummy return, to avoid gcc warning. + +2002-03-12 Bruno Haible + + * gettext-0.11.1 released. + +2002-03-10 Bruno Haible + + * x-python.c (phase7_getuc): Change type of buf to 'unsigned char[]'. + +2002-03-07 Bruno Haible + + * Makefile.am (msginit_LDADD): Use @INTL_LIBTOOL_SUFFIX_PREFIX@o + instead of $(OBJEXT). + +2002-03-05 Bruno Haible + + * hostname.c (s6_addr16): Define if not defined by the system. + (ipv6_ntop): Use s6_addr16 instead of in6_u.u6_addr16. + +2002-03-05 Bruno Haible + + * xgettext.c (usage): Mention languages Python, awk, Tcl, RST, Glade. + +2002-03-03 Bruno Haible + + * message.h (format_type): New enum value 'format_tcl'. + (NFORMATS): Increment. + * message.c (format_language): Add format_tcl entry. + (format_language_pretty): Likewise. + * format.h (formatstring_tcl): New declaration. + * format-tcl.c: New file. + * format.c (formatstring_parsers): Add formatstring_tcl. + * x-tcl.h: New file. + * x-tcl.c: New file. + * xgettext.c: Include x-tcl.h. + (main): Call x_tcl_extract_all, x_tcl_keyword. + (language_to_scanner): Add Tcl rule. + (extension_to_language): Add Tcl rule. + * write-tcl.h: New file. + * write-tcl.c: New file. + * msgfmt.c: Include write-tcl.h. + (tcl_mode, tcl_locale_name, tcl_base_directory): New variables. + (long_options): Add option "--tcl". + (main): Handle --tcl option. Set tcl_mode, tcl_locale_name, + tcl_base_directory. More checks for contradicting options. Call + msgdomain_write_tcl. + (usage): Mention Tcl mode. + (format_directive_domain): Ignore domain directive if in Tcl mode. + * read-tcl.h: New file. + * read-tcl.c: New file. + * msgunfmt.c: Include read-tcl.h. + (tcl_mode, tcl_locale_name, tcl_base_directory): New variables. + (long_options): Add option "--tcl". + (main): Handle --tcl and -d options. Set tcl_mode, tcl_locale_name, + tcl_base_directory. More checks for contradicting options. Call + msgdomain_read_tcl. + (usage): Mention Tcl mode. + * msgunfmt.tcl: New file. + * Makefile.am (noinst_HEADERS): Add read-tcl.h, write-tcl.h, x-tcl.h. + (EXTRA_DIST): Add msgunfmt.tcl. + (DEFS): Add -DGETTEXTDATADIR. + (FORMAT_SOURCE): Add format-tcl.c. + (msgfmt_SOURCES): Add write-tcl.c. + (msgunfmt_SOURCES): Add read-tcl.c. + (xgettext_SOURCES): Add x-tcl.c. + (install-tcl, installdirs-tcl, uninstall-tcl): New targets. + +2002-03-02 Bruno Haible + + * msgfmt.c (check_pair): Don't count "&&" as an accelerator designator, + for consistency with Qt and KDE. + +2002-02-24 Bruno Haible + + * po-lex.c: Include libstdarg.h. + (VA_START, va_alist, va_dcl): Remove macros. + (po_gram_error): Modernize varargs handling. Fix memory leak. + (po_gram_error_at_line): Likewise. + +2002-02-21 Bruno Haible + + * msggrep.c: Include liballoca.h. + (grep_args): Add a third one. + (grep_argv): Likewise. + (long_options): Add --comment option. + (main): Handle option --comment/-C. + (usage): Document option -C. + (is_message_selected): If option -C is given, concatenate the + translator comments and run a third grep pass on the result. + +2002-02-09 Bruno Haible + + * message.h (possible_format_p): Change return type to bool. + * message.c (possible_format_p): Likewise. + +2002-01-14 Bruno Haible + + * x-glade.h: New file. + * x-glade.c: New file. + * xgettext.c: Include x-glade.h. + (main): Call x_glade_extract_all, x_glade_keyword. + (language_to_scanner): Add glade rule. + (extension_to_language): Add glade rule. + * Makefile.am (noinst_HEADERS): Add x-glade.h. + (xgettext_SOURCES): Add x-glade.c. + (xgettext_LDADD): Add @LTLIBEXPAT@. + +2002-01-27 Bruno Haible + + * message.h (format_type): New enum value 'format_awk'. + (NFORMATS): Increment. + * message.c (format_language): Add format_awk entry. + (format_language_pretty): Likewise. + * format.h (formatstring_awk): New declaration. + * format-awk.c: New file. + * format.c (formatstring_parsers): Add formatstring_awk. + * x-awk.h: New file. + * x-awk.c: New file. + * xgettext.c: Include x-awk.h. + (main): Call x_awk_extract_all, x_awk_keyword. + (language_to_scanner): Add awk rule. + (extension_to_language): Add awk rule. + * Makefile.am (noinst_HEADERS): Add x-awk.h. + (FORMAT_SOURCE): Add format-awk.c. + (xgettext_SOURCES): Add x-awk.c. + +2002-02-02 Bruno Haible + + * x-python.h: New file. + * x-python.c: New file. + * xgettext.c: Include x-python.h. + (main): Call x_python_extract_all, x_python_keyword. + (language_to_scanner): Add Python rule. + (extension_to_language): Add Python rule. + * Makefile.am (noinst_HEADERS): Add x-python.h. + (INCLUDES): Add -I$(top_srcdir)/libuniname. + (LIBUNINAME): New variable. + (xgettext_SOURCES): Add x-python.c. + (xgettext_LDADD): Add $(LIBUNINAME). + +2002-02-03 Bruno Haible + + * msgfmt.c (check_header_entry): Terminate the error strings with + newlines. + +2002-02-03 Bruno Haible + + * plural-table.h: New file. + * plural-table.c: New file. + * msginit.c: Include plural-table.h. + (plural_forms): Use plural_table. + * msgfmt.c: Include plural-table.h. + (check_plural): Print a recommendation about the right plural formula. + * Makefile.am (noinst_HEADERS): Add plural-table.h. + (libgettextsrc_la_SOURCES): Add plural-table.c. + +2002-02-03 Bruno Haible + + * xgettext.c (finalize_header): New function. + (main): Call it. + +2002-02-03 Bruno Haible + + * msginit.c (fill_header): Also replace "PACKAGE" in the comment. + +2002-02-02 Bruno Haible + + * Makefile.am (INCLUDES): Add -I../lib. Needed for builds with + builddir != srcdir on platforms that don't have fnmatch and stdbool.h. + +2002-01-31 Bruno Haible + + * gettext-0.11 released. + +2002-01-31 Bruno Haible + + * plural-eval.h: Include eval-plural.h instead of plural-eval.c. + +2002-01-30 Bruno Haible + + Make the gettext and ngettext programs independent of libgettextlib. + * Makefile.am (gettext_LDADD, ngettext_LDADD): New variables. + +2002-01-27 Bruno Haible + + * x-ycp.c (enum token_type_ty): New enum value token_type_symbol. + (x_ycp_lex): Assign a value to tp->string if and only if the type is + token_type_string_literal or token_type_symbol. + (extract_ycp): Simplify cleanup. + +2002-01-27 Bruno Haible + + * x-java.l (extract_java): Don't create a plural message if the + function can take two string argument but actually has only one + string argument. + +2002-01-27 Bruno Haible + + * x-lisp.c (extract_lisp): Free memory allocated in toplevel_object. + * x-elisp.c (string_of_object): Use memcpy. + (extract_elisp): Free memory allocated in toplevel_object. + * x-librep.c (string_of_object): Use memcpy. + (extract_librep): Free memory allocated in toplevel_object. + +2002-01-27 Bruno Haible + + * x-c.c (ALERT_CHAR): New macro. + (phase7_getc): Accept \a and \v as well. + + * x-c.c (free_token): New inline function. + (phase6_get): Use it. + +2002-01-27 Bruno Haible + + * format-elisp.c (format_check): Don't use Java syntax for format + argument number in error message. + * format-librep.c (format_check): Likewise. + * format-pascal.c (format_check): Likewise. + +2002-01-24 Bruno Haible + + * Makefile.am (LDADD): Use @LTLIBINTL@ instead of @INTLLIBS@. + (libgettextsrc_la_LDFLAGS): Likewise. Use @LTLIBICONV@ instead of + @LIBICONV@. + +2002-01-21 Bruno Haible + + * msggrep.c (main): On SunOS4, don't pass option "-q" to grep. + +2002-01-19 Bruno Haible + + * msgfmt.c (check_header_entry): Emit error messages including the + file name. + +2002-01-19 Bruno Haible + + * open-po.c (open_po_file): Use 'size_t' instead of 'int'. + * x-java.l (translate_esc): Likewise. + * read-mo.c (read_mo_file): Use 'unsigned int' instead of 'int'. + +2002-01-13 Bruno Haible + + * msgl-iconv.c (iconv_message_list): Don't complain by a nonportable + encoding name if it has been overridden by an argument canon_from_code. + +2002-01-13 Bruno Haible + + * msgl-ascii.c (is_ascii_message): Don't consider a message as being + ASCII if its msgid or msgid_plural is not ASCII. + +2002-01-12 Bruno Haible + + * gettext.c (main): Update year in --version output. + * ngettext.c (main): Likewise. + * msgattrib.c (main): Likewise. + * msgcat.c (main): Likewise. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msgfilter.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msggrep.c (main): Likewise. + * msginit.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + +2002-01-11 Bruno Haible + + * write-po.c: Include po-charset.h. + +2002-01-11 Bruno Haible + + * msgfmt.c (install_sigfpe_handler, uninstall_sigfpe_handler): On AIX, + treat SIGTRAP like SIGFPE. + +2002-01-09 Bruno Haible + + * msgfmt.c (USE_SIGINFO): New macro. + (sigfpe_code, sigfpe_handler, install_sigfpe_handler, + uninstall_sigfpe_handler, check_plural_eval): Use USE_SIGINFO instead + of HAVE_SIGINFO. + (install_sigfpe_handler, uninstall_sigfpe_handler): On Irix, treat + SIGTRAP like SIGFPE. + +2002-01-09 Bruno Haible + + * x-java.l (strip_ending_spaces): Fix logic. + +2002-01-09 Tommy Johansson + + * x-java.l: Avoid endless loop when encountering unterminated string. + +2002-01-08 Bruno Haible + + * message.h (format_type): New enum value 'format_elisp'. + (NFORMATS): Increment. + * message.c (format_language): Add format_elisp entry. + (format_language_pretty): Likewise. + * format.h (formatstring_elisp): New declaration. + * format-elisp.c: New file. + * format.c (formatstring_parsers): Add formatstring_elisp. + * x-elisp.h: New file. + * x-elisp.c: New file. + * xgettext.c: Include x-elisp.h. + (main): Call x_elisp_extract_all, x_elisp_keyword. + (usage): Mention the languages Lisp, EmacsLisp, librep. + (language_to_scanner): Add elisp rule. + (extension_to_language): Add elisp rule. + * Makefile.am (noinst_HEADERS): Add x-elisp.h. + (FORMAT_SOURCE): Add format-elisp.c. + (xgettext_SOURCES): Add x-elisp.c. + +2002-01-06 Bruno Haible + + * x-librep.c (read_object): Fix handling of #[ and #(. + +2002-01-06 Bruno Haible + + * x-lisp.c (read_object): Treat ',.' like ',@'. + +2002-01-05 Bruno Haible + + Make "msgmerge --update" work better on CJK files even if iconv() is + not available. + * po-charset.h (po_is_charset_weird): New declaration. + (po_is_charset_weird_cjk): Likewise.. + (po_lex_weird_cjk): New variable declaration. + * po-charset.c (po_is_charset_weird): New function, extracted from + po_lex_charset_set. + (po_is_charset_weird_cjk): New function. + (po_lex_weird_cjk): New variable. + (po_lex_charset_init): Initialize po_lex_weird_cjk. + (po_lex_charset_set): Call po_is_charset_weird and + po_is_charset_weird_cjk. Set po_lex_weird_cjk. + (po_lex_charset_close): Reset po_lex_weird_cjk. + * po-lex.c (mbfile_getc): If po_lex_weird_cjk is set, possibly return + a double byte instead of single byte. + * write-po.c (wrap): Call po_is_charset_weird_cjk. If it returns true, + group double bytes where possible. + +2002-01-05 Bruno Haible + + * gettext.c: TESTS version is now separate. + +2002-01-05 Bruno Haible + + * x-java.l (extract_java): Avoid "uninitialized variable" warnings. + Free a temporary string. + +2002-01-05 Bruno Haible + + * Makefile.am (libgettextsrc_la_LDFLAGS): Add -lc. Needed on AIX. + +2002-01-05 Bruno Haible + + * x-c.c (buflen): Make static. + * x-librep.c (buflen): Likewise. + * x-lisp.c (buflen): Likewise. + +2001-12-23 Bruno Haible + + * msgexec.c (process_string): Use xsetenv instead of setenv. + * msginit.c (canonical_locale_charset): Likewise. + (get_title): Likewise. + +2001-12-22 Bruno Haible + + * dir-list.c: Include instead of . + * xgettext.h: Likewise. + * file-list.c: Include exit.h instead of system.h. + * gettext.c: Likewise. + * hostname.c: Likewise. + * msgattrib.c: Likewise. + * msgcat.c: Likewise. + * msgcomm.c: Likewise. + * msgconv.c: Likewise. + * msgexec.c: Likewise. + * msgunfmt.c: Likewise. + * msguniq.c: Likewise. + * ngettext.c: Likewise. + * read-java.c: Likewise. + * x-ycp.c: Likewise. + * msgcmp.c: Include exit.h instead of system.h. Don't include + str-list.h. + * msgfilter.c: Likewise. + * msgen.c: Include exit.h instead of system.h. Don't include xmalloc.h. + * msgfmt.c: Include exit.h, stpcpy.h instead of system.h. Don't + include , getline.h. + * msggrep.c: Include exit.h instead of system.h. Don't include + . + * msginit.c: Include liballoca.h, , exit.h, pathname.h + instead of system.h. + * msgl-cat.c: Include liballoca.h, exit.h instead of system.h. + Include instead of . + * msgl-charset.c: Likewise. + * msgl-iconv.c: Include liballoca.h, exit.h instead of system.h. + * write-po.c: Likewise. + * msgmerge.c: Include liballoca.h, exit.h, strcase.h, stpcpy.h, + stpncpy.h instead of system.h. + * format-java.c: Include liballoca.h instead of system.h. + * format-lisp.c: Don't include system.h. + * open-po.c: Include pathname.h, xmalloc.h instead of system.h. + Don't include , error.h. + * po-charset.c: Include liballoca.h, string.h, strcase.h instead of + system.h. Don't include error.h. Include progname.h. + (program_name): Remove declaration. + * po-gram-gen.y: Include . + * po-hash-gen.y: Include . + * po-lex.c: Include exit.h instead of system.h. Don't include + . + * read-mo.c: Include binary-io.h, exit.h instead of system.h. + Include instead of . + * urlget.c: Include , binary-io.h, exit.h instead of + system.h. + (O_BINARY): Remove fallback definition. + * write-java.c: Include liballoca.h, pathname.h instead of system.h. + * write-mo.c: Include liballoca.h, binary-io.h, exit.h instead of + system.h. Don't include msgfmt.h. + * x-c.c: Include exit.h instead of system.h. Don't include . + * x-librep.c: Include exit.h instead of system.h. Include hash.h. + * x-lisp.c: Likewise. + * x-rst.c: Include exit.h instead of system.h. Include + instead of . + * xgettext.c: Include , exit.h, pathname.h, strcase.h, + stpcpy.h instead of system.h. Include str-list.h. Don't include + , , , , getline.h. + (getpwuid): Remove declaration. + +2001-12-21 Bruno Haible + + * format-lisp.c: Include minmax.h. + +2001-12-21 Bruno Haible + + * file-list.c: Include gettext.h instead of libgettext.h. + * format-c.c: Likewise. + * format-java.c: Likewise. + * format-librep.c: Likewise. + * format-lisp.c: Likewise. + * format-pascal.c: Likewise. + * format-python.c: Likewise. + * format-ycp.c: Likewise. + * gettext.c: Likewise. + * hostname.c: Likewise. + * msgattrib.c: Likewise. + * msgcat.c: Likewise. + * msgcmp.c: Likewise. + * msgcomm.c: Likewise. + * msgconv.c: Likewise. + * msgen.c: Likewise. + * msgexec.c: Likewise. + * msgfilter.c: Likewise. + * msgfmt.c: Likewise. + * msggrep.c: Likewise. + * msginit.c: Likewise. + * msgl-cat.c: Likewise. + * msgl-charset.c: Likewise. + * msgl-iconv.c: Likewise. + * msgmerge.c: Likewise. + * msgunfmt.c: Likewise. + * msguniq.c: Likewise. + * ngettext.c: Likewise. + * open-po.c: Likewise. + * po-charset.c: Likewise. + * po-gram-gen.y: Likewise. + * po-lex.c: Likewise. + * read-java.c: Likewise. + * read-mo.c: Likewise. + * read-po.c: Likewise. + * urlget.c: Likewise. + * write-java.c: Likewise. + * write-mo.c: Likewise. + * write-po.c: Likewise. + * x-c.c: Likewise. + * x-librep.c: Likewise. + * x-lisp.c: Likewise. + * x-po.c: Likewise. + * x-rst.c: Likewise. + * x-ycp.c: Likewise. + * xgettext.c: Likewise. + +2001-12-21 Bruno Haible + + * message.h (message_merge): Remove declaration. + * message.c (msgfmt_check_pair_fails, message_merge): Move to + msgmerge.c. + * msgmerge.c (msgfmt_check_pair_fails, message_merge): Moved to here + from message.c. + +2001-12-20 Bruno Haible + + * format-java.c: Include system.h. + * format-lisp.c (parse_upto): Fix prototype. char promotes to int. + * po-lex.c (mb_iseq, mb_setascii): Likewise. + * po-hash-gen.y: alloca fix for AIX 3. + * x-lisp.c (syntax_code_of, read_char_syntax, attribute_of, is_number): + Move prototypes, to avoid compilation errors on AIX 3. + (syntax_code_of, attribute_of): Fix prototype. unsigned char promotes + to int. + * x-java.l (TOKEN_TYPE): Remove trailing comma in enum. + (PARSER_STATE): Likewise. + * msgfilter.c: Include on AIX. + +2001-12-12 Bruno Haible + + * xgettext.c: Include . + (copyright_holder): New variable. + (foreign_user): Remove variable. + (long_options): Add --copyright-holder option. + (main): Implement --copyright-holder option. + (usage): Document --copyright-holder option. + (construct_header): Use the copyright_holder variable. If it is + empty, add a "public domain" comment. + +2001-12-17 Bruno Haible + + * msggrep.c: Include . + (filename_list_match): New function. + (is_message_selected): Use it instead of string_list_member. + +2001-12-16 Bruno Haible + + * message.h (format_type): New enum value 'format_librep'. + (NFORMATS): Increment. + * message.c (format_language): Add format_librep entry. + (format_language_pretty): Likewise. + * format.h (formatstring_librep): New declaration. + * format-librep.c: New file. + * format.c (formatstring_parsers): Add formatstring_librep. + * x-librep.h: New file. + * x-librep.c: New file. + * xgettext.c: Include x-librep.h. + (main): Call x_librep_extract_all, x_librep_keyword. + (language_to_scanner): Add librep rule. + (extension_to_language): Add librep rule. + * Makefile.am (noinst_HEADERS): Add x-librep.h. + (FORMAT_SOURCE): Add format-librep.c. + (xgettext_SOURCES): Add x-librep.c. + +2001-12-15 Bruno Haible + + * msgfmt.c (check_plural): Use ngettext for two messages. + Reported by Primoz Peterlin . + +2001-12-15 Bruno Haible + + * xgettext.c (remember_a_message_plural): Mark the message as xx-format + if the English plural form has xx format syntax and the singular form + may or may not have xx format syntax. + +2001-12-15 Bruno Haible + + * format.h (struct formatstring_parser): Add 'equality' and + 'pretty_msgstr' arguments to 'check' field. + * format-c.c (format_check): Add 'equality' and 'pretty_msgstr' + arguments. + * format-java.c (format_check): Likewise. + * format-lisp.c (format_check): Likewise. + * format-pascal.c (format_check): Likewise. + * format-python.c (format_check): Likewise. + * format-ycp.c (format_check): Likewise. + * msgfmt.c (check_pair): Check messages with plural forms as well. + * message.c (msgfmt_check_pair_fails): Add msgid_plural, msgstr_len + arguments. Check messages with plural forms as well. + (message_merge): Check messages with plural forms as well. + +2001-12-11 Bruno Haible + + * x-java.l (strip_ending_spaces): Fix isspace call. + + * xgettext.h (xgettext_omit_header): Renamed from omit_header. + * xgettext.c (xgettext_omit_header): Likewise. + (long_options, main, remember_a_message): Update. + * x-po.c (extract_directive_message): Update. + +2001-12-09 Bruno Haible + + * x-lisp.h: New file. + * x-lisp.c: New file. + * xgettext.c: Include x-lisp.h. + (main): Call x_lisp_extract_all, x_lisp_keyword. + (language_to_scanner): Add Lisp rule. Remove preliminary Lisp rule. + (extension_to_language): Add Lisp rule. + * Makefile.am (noinst_HEADERS): Add x-lisp.h. + (xgettext_SOURCES): Add x-lisp.c. + +2001-12-09 Bruno Haible + + * xgettext.h (split_keywordspec): New declaration. + * xgettext.c (split_keywordspec): New function. + * x-c.c (x_c_keyword): Use split_keywordspec. + * x-java.l (extract_keyword, extract_msgid_arg, + extract_msgid_plural_arg): Remove functions. + (x_java_keyword): Use split_keywordspec. + +2001-12-09 Bruno Haible + + * x-c.c: Reorder. + +2001-12-08 Bruno Haible + + * msgattrib.c (process_message_list): Never make the header entry + obsolete. + +2001-12-08 Bruno Haible + + * msgfilter.c (main): Fix getopt_long string: -i takes an argument. + +2001-12-08 Bruno Haible + + * xgettext.c (remember_a_message): Don't print the line number if it + is = (size_t)(-1). + +2001-12-08 Bruno Haible + + * msgfilter.c: Renamed from msgexec.c. + * msgexec.c: New file. + * Makefile.am (bin_PROGRAMS): Add msgfilter. + (msgfilter_SOURCES): New variable. + (msgfilter_LDADD): New variable. + +2001-12-07 Bruno Haible + + * msgexec.c (keep_header): New variable. + (long_options): New option --keep-header. + (usage): Document option --keep-header. + (process_message): Implement --keep-header. + +2001-12-06 Bruno Haible + + * format.h (struct formatstring_parser): Add 'noisy' argument to + 'check' field. + * format-c.c (format_check): Add 'noisy' argument. + * format-java.c (format_check): Likewise. + * format-lisp.c (format_check): Likewise. + * format-pascal.c (format_check): Likewise. + * format-python.c (format_check): Likewise. + * format-ycp.c (format_check): Likewise. + * msgfmt.c (check_pair): Pass noisy=true. + * message.c: Include format.h. + (msgfmt_check_pair_fails): New function. + (message_merge): Set fuzzy flag when adding a -format specifier that + needs the translator's attention. + +2001-12-10 Bruno Haible + + * x-java.l (tailcmp): If s1 ends with s2, it must be either equal to + s2 or have a dot right before s2. + +2001-12-01 Bruno Haible + + * msginit.c (project_id): Make it possible to specify the location + of the project-id script through an environment variable. + (project_id_version): Likewise. + +2001-12-03 Tommy Johansson + + * x-java.l (tailcmp): New function. + (do_compare): Use it instead of strcmp. + (extract_java): After seeing an invocation, don't set the state to + STATE_NONE. + +2001-11-30 Bruno Haible + + * message.c (message_merge): Determine whether postprocessing is + needed. + * msgmerge.c (match_domain): Postprocess some problematic cases. + * Makefile.am (libgettextsrc_la_SOURCES): Add plural.c. + (msgfmt_SOURCES): Remove plural.c. + +2001-11-29 Bruno Haible + + * plural-eval.c: Include config.h and plural-exp.h. + +2001-11-27 Bruno Haible + + * msgfmt.c (struct msgfmt_class_ty): New field + 'has_nonfuzzy_header_entry'. + (format_constructor): Initialize it. + (format_debrief): Give a warning if the header entry exists but is + fuzzy. + (format_directive_message): Update has_nonfuzzy_header_entry. + +2001-11-25 Bruno Haible + + * x-c.c (comment_start, comment_add, comment_line_end): New inline + functions. + (phase4_getc): Use them. + +2001-11-25 Bruno Haible + + * msginit.c (no_translator): New variable. + (long_options): Add option --no-translator. + (main): Handle option --no-translator. + (usage): Document option --no-translator. + (po_revision_date): Add header argument. Return POT's creation date + if no_translator. + (last_translator): Return a dummy string if no_translator. + (language_team): Likewise. + (fields): Allow getter with 0 or 1 argument. + (get_field): New function. + (fill_header): Call getter with 0 or 1 argument. + +2001-11-25 Bruno Haible + + * msginit.c (canonical_locale_charset): Return the charset of 'locale' + if available. + (get_title): Return a correct value even if 'locale' is a nonexistent + locale. + +2001-11-24 Bruno Haible + + * urlget.c (fetch): Call execute_java_class with quiet=true. If Java + program failed, try wget or lynx. + + * read-java.c (msgdomain_read_java): Update for changed + execute_java_class parameter list. + +2001-11-24 Bruno Haible + + * msgexec.c (process_message): Really process each plural form once. + +2001-11-17 Bruno Haible + + * msgfmt.c (is_nonobsolete): New function. + (main): Remove obsolete messages after reading all input files. + (check_header_entry): New function, extracted from + format_directive_message. + (format_directive_message): Perform duplicate checking also on + obsolete, untranslated and fuzzy messages, like msgmerge and the + other tools do. Put also obsolete, untranslated and fuzzy messages + on the result list, but mark them obsolete. + (read_po_file): Call po_lex_pass_obsolete_entries. + +2001-11-17 Bruno Haible + + * po.c (po_callback_message): Ignore obsolete header entries, for + consistency with msgdomain_list_print(), compare_po_locale_charsets(), + iconv_message_list(), catenate_msgdomain_list(). + +2001-11-17 Bruno Haible + + * msgfmt.c (struct msg_domain): Remove symbol_tab field. + (main, new_domain): Update. + (format_directive_message): Use message_list_search instead of + explicit hash table calls. + +2001-11-17 Bruno Haible + + * message.h: Include hash.h. + (message_list_ty): Add use_hashtable, htable fields. + (message_list_alloc): Add 'use_hashtable' argument. + (msgdomain_alloc): Likewise. + (msgdomain_list_ty): Add use_hashtable field. + (msgdomain_list_alloc): Add 'use_hashtable' argument. + * message.c: Include hash.h. + (message_list_alloc): Add 'use_hashtable' argument. + (message_list_free): Delete the hash table if present. + (message_list_append): If a hash table is in use, verify no duplicates. + (message_list_prepend): Likewise. + (message_list_remove_if_not): Remove hash table after removal. + (message_list_search): Use hash table if present. + (msgdomain_alloc): Add 'use_hashtable' argument. + (msgdomain_list_alloc): Add 'use_hashtable' argument. + (msgdomain_list_sublist): Use 'use_hashtable'. + * msgcmp.c (compare): Update. + (compare_constructor): Allocate a message list with hash table. + * msgfmt.c (new_domain): Allocate a message list with hash table. + Remove impossible error message. + * msgl-cat.c (catenate_msgdomain_list): Allocate message lists with + hash table. + * msgmerge.c (merge): Update. + * msgunfmt.c (main): Update. + * read-po.c (readall_constructor): Allocate a message list with hash + table if and only if duplicates will lead to an error message. + * xgettext.c (main): Allocate a message list with hash table, to + speed up insertion. + (exclude_directive_message): Allocate a message list with hash table, + to speed up exclusion test. + +2001-11-17 Bruno Haible + + * message.h (msgdomain_list_sublist): Change last argument to bool. + * message.c (msgdomain_list_sublist): Likewise. + * msgcmp.c (compare): Update msgdomain_list_sublist calls. + (compare_constructor, compare_directive_message): Likewise. + * msgl-cat.c (catenate_msgdomain_list): Likewise. + * msgmerge.c (merge): Likewise. + * read-po.c (readall_constructor, readall_directive_message): Likewise. + +2001-11-17 Bruno Haible + + * msgfmt.c (format_directive_message): Finish 2001-08-30 patch. + +2001-11-17 Bruno Haible + + * msgfmt.c (accelerator_char): New variable. + (long_options): --check-accelerators has optional argument. + (main): Store --check-accelerators value in accelerator_char. + (usage): --check-accelerators has optional argument. + (check_pair): Use accelerator_char instead of hardwired '&'. + +2001-11-17 Bruno Haible + + * msginit.c (main): Print a blank line. + (get_title): Always use the English title first. + (fill_header): Update accordingly. + +2001-11-17 Bruno Haible + + * message.h (NFORMATS): Fix misapplied patch. + +2001-11-11 Bruno Haible + + * msgfmt.c (check_accelerators): New variable. + (long_options): Add --check-accelerators. + (main): Handle --check-accelerators. + (usage): Document --check-accelerators option. + (check_pair): Implement check_accelerators handling. + +2001-11-11 Bruno Haible + + * msgl-cat.c (catenate_msgdomain_list): Fix a typo in 2001-11-01 patch. + Reported by Karl Eichwalder. + +2001-11-11 Bruno Haible + + * msginit.c (verbose): Remove variable. + (long_options): Remove --verbose. + (main): Remove --verbose option handling. + (usage): Remove --verbose documentation. + (language_team_address): Don't pass a verbose option to the subprocess. + +2001-11-08 Bruno Haible + + * message.h: New enum value format_smalltalk. + * message.c (format_language): Add value for format_smalltalk. + (format_language_pretty): Likewise. + * format.h (formatstring_smalltalk): New declaration. + * format-ycp.c (formatstring_smalltalk): New variable. + * format.c (formatstring_parsers): Add value for format_smalltalk. + +2001-11-05 Bruno Haible + + * format-lisp.c (parse_upto): Remove 'base' argument. It is always 0. + (format_parse): Update. + + * msgl-equal.c: Include string.h. + * str-list.c: Likewise. + +2001-11-03 Bruno Haible + + * xgettext.c (line_comment): Remove variable, so default value is + always 1. + +2001-11-03 Bruno Haible + + * x-c.c (extract_parenthesized): New function, extracted from + extract_c. + (extract_c): Call extract_parenthesized. + +2001-11-03 Bruno Haible + + Add YCP plural form support. + * x-ycp.c (enum token_type_ty): New value token_type_comma. + (x_ycp_lex): Recognize comma. + (extract_ycp): Also recognize the _("singular","plural" pattern. + +2001-11-01 Bruno Haible + + * msgfmt.c (check_plural_eval): Fix #if mistake. + +2001-11-01 Bruno Haible + + * msgl-iconv.h (iconv_message_list): Add argument 'canon_from_code'. + * msgl-iconv.c (iconv_message_list): Add argument 'canon_from_code'. + (iconv_msgdomain_list): Update. + * write-java.c (msgdomain_write_java): Update. + * msgl-cat.c (catenate_msgdomain_list): Pass the canon_charsets[n][k] + to iconv_message_list; the header entry that contained the charset + specification may already have been removed from the message list. + +2001-11-01 Bruno Haible + + * msgl-equal.h (string_list_equal): New declaration. + * msgl-equal.c (string_list_equal): Export function. + * message.h (struct altstr): New fields 'comment', 'comment_dot'. + * msgl-cat.c: Include msgl-equal.h. + (catenate_msgdomain_list): If all alternative comments are equal, + use one copy of them, instead of concatenating them. Likewise for dot + comments. + * Makefile.am (libgettextsrc_la_SOURCES): Add msgl-equal.c. + (msgmerge_SOURCES): Remove msgl-equal.c. + +2001-10-31 Bruno Haible + + * Makefile.am (noinst_HEADERS): Add read-java.h, write-java.h, + format.h, msgunfmt.h. + (EXTRA_DIST): Add project-id, gnu/gettext/*.java. + * message.h (NFORMATS): Define via #define, not via an enum value. + * format.h (struct formatstring_parser): Use PARAMS. + * format-c.c (FAT_SIZE_MASK): Remove extraneous backslashes. + * po-charset.c (po_lex_charset_set): Fix syntax error. + * write-java.c (write_java_string): Drop hexdigit[] size. + * x-java.l (add_object): Remove C++ style comment. + +2001-10-28 Bruno Haible + + * hostname.c: Include "progname.h". + (program_name): Remove variable. + (main): Call set_program_name instead of setting program_name. + +2001-10-28 Bruno Haible + + Create a libgettextsrc shared library. + * Makefile.am (lib_LTLIBRARIES): New variable. + (libgettextsrc_la_SOURCES): New variable. + (msgcmp_SOURCES, msgfmt_SOURCES, msgmerge_SOURCES, msgunfmt_SOURCES, + xgettext_SOURCES, msgattrib_SOURCES, msgcat_SOURCES, msgcomm_SOURCES, + msgconv_SOURCES, msgen_SOURCES, msgexec_SOURCES, msggrep_SOURCES, + msginit_SOURCES, msguniq_SOURCES): Remove source files listed in + libgettextsrc_la_SOURCES. + (libgettextsrc_la_LDFLAGS): New variable. + (msgcmp_LDADD, msgfmt_LDADD, msgmerge_LDADD, msgunfmt_LDADD, + xgettext_LDADD, msgattrib_LDADD, msgcat_LDADD, msgcomm_LDADD, + msgconv_LDADD, msgen_LDADD, msgexec_LDADD, msggrep_LDADD, + msginit_LDADD, msguniq_LDADD): Add libgettextsrc.la. Remove all + dependencies already listed in libgettextsrc_la_LDFLAGS. + (po-lex.lo): Depend on po-gram-gen2.h. + +2001-10-21 Bruno Haible + + * Makefile.am (LDADD): Replace libnlsut.a with libgettextlib.la. + (*_LDADD): Likewise. + +2001-10-26 Bruno Haible + + * x-c.h (EXTENSIONS_C): Add ".hxx". + +2001-10-21 Bruno Haible + + * msgmerge.c (match_domain): Change type of 'processed' argument to + 'unsigned int *'. + (merge): Change type of 'processed' to 'unsigned int'. + +2001-10-21 Bruno Haible + + Avoid warnings on platforms where size_t is larger than int. + * msgfmt.c (check_pair): Use an 'unsigned int' variable instead of + 'size_t', to avoid warnings. + * po-lex.h (po_gram_error): Cast line number to 'unsigned long'. + * po-lex.c (po_gram_error): Likewise. + * x-c.c (extract_c): Likewise. + +2001-10-21 Bruno Haible + + * msgexec.c [BeOS]: Fix #ifdef conditional. + +2001-10-21 Bruno Haible + + * read-java.c (execute_and_read_po_output): Return false, not 0. + +2001-10-11 Bruno Haible + + * msgattrib.c (usage): Fix: --sort-output doesn't deal with duplicates. + * msgcat.c (usage): Likewise. + * msgcomm.c (usage): Likewise. + * msgconv.c (usage): Likewise. + * msgen.c (usage): Likewise. + * msgexec.c (usage): Likewise. + * msggrep.c (usage): Likewise. + * msgmerge.c (usage): Likewise. + * msgunfmt.c (usage): Likewise. + * msguniq.c (usage): Likewise. + * xgettext.c (usage): Likewise. + +2001-10-09 Bruno Haible + + * write-po.c (memcpy_small): New function. + (wrap): Use it instead of memcpy. Optimize handling of escape + characters. + +2001-10-09 Bruno Haible + + * write-po.c (putc): Define as putc_unlocked if available. + +2001-10-09 Bruno Haible + + * po-lex.c (getc): Define as getc_unlocked if available. + +2001-10-21 Bruno Haible + + * msginit.c: New file. + * project-id: New file. + * Makefile.am (bin_PROGRAMS): Add msginit. + (projectsdir): New variable. + (DEFS): Add -DLIBDIR and -DPROJECTSDIR. + (msginit_SOURCES): New variable. + (msginit_LDADD): New variable. + (install-exec-local): Also install project-id. + (uninstall-local): Also uninstall project-id. + +2001-10-21 Bruno Haible + + * gnu/gettext/GetURL.java: New file. + * urlget.c: New file. + * Makefile.am (noinst_PROGRAMS): Add urlget. + (urlget_SOURCES): New variable. + (install-exec-local): Also install urlget. + (uninstall-local): Also uninstall urlget. + (gnu/gettext/GetURL.class): New rule. + (gettext.jar): Also include gnu/gettext/GetURL*.class. + +2001-10-08 Bruno Haible + + * msgl-english.h: New file. + * msgl-english.c: New file, extracted from msgen.c. + * msgen.c: Include msgl-english.h. + (english): Move to msgl-english.c. + (main): Update accordingly. + * Makefile.am (noinst_HEADERS): Add msgl-english.h. + (msgen_SOURCES): Add msgl-english.c. + +2001-10-08 Bruno Haible + + * po-time.h: New file. + * po-time.c: New file, extracted from xgettext.c. + * xgettext.c: Include po-time.h. + (difftm): Move to po-time.c. + (construct_header): Move some code to po-time.c. Call po_strftime. + * Makefile.am (noinst_HEADERS): Add po-time.h. + (xgettext_SOURCES): Add po-time.c. + +2001-09-29 Bruno Haible + + * user-email.in: New file. + * Makefile.am (install-exec-local): Install user-email. + (uninstall-local): Uninstall user-email. + (DISTCLEANFILES): Add user-email. + +2001-09-29 Bruno Haible + + * hostname.c: New file. + * Makefile.am (noinst_PROGRAMS, hostname_SOURCES): New variables. + (install-exec-local, installdirs-local, uninstall-local): New rules. + +2001-10-10 Bruno Haible + + * msgexec.c (process_string): Update for changed create_pipe_bidi(), + wait_subprocess(). + * msggrep.c (is_string_selected): Update for changed create_pipe_out(), + wait_subprocess(). + * read-java.c (execute_and_read_po_output): Update for changed + create_pipe_in(), wait_subprocess(). + +2001-10-08 Bruno Haible + + * xgettext.c (remember_a_message): When the comment tag is seen, + remember all remaining comment lines, not just one. + +2001-09-25 Bruno Haible + + Upgrade to automake-1.5. + * Makefile.am (AUTOMAKE_OPTIONS): Add 'no-dependencies'. + (COMMON_SOURCE): Renamed from COMMON_SOURCES. + (FORMAT_SOURCE): Renamed from FORMAT_SOURCES. + * ylwrap: Remove file. + +2001-09-22 Bruno Haible + + * xgettext.c (extractor_func): New typedef. + (scanner_fp): Remove typedef. + (main): Use extractor_func and call it via extract_from_file. + (extract_from_file): New function. + (scan_c_file, scan_po_file, scan_java_file, scan_ycp_file, + scan_rst_file): Remove functions. + (language_to_extractor): Renamed from language_to_scanner. + * x-c.h (SCANNERS_C): Use extract_c instead of scan_c_file. + * x-java.h (SCANNERS_JAVA): Use extract_java instead of scan_java_file. + * x-po.h (SCANNERS_PO): Use extract_po instead of scan_po_file. + * x-rst.h (SCANNERS_RST): Use extract_rst instead of scan_rst_file. + * x-ycp.h (SCANNERS_YCP): Use extract_ycp instead of scan_ycp_file. + +2001-09-23 Bruno Haible + + * plural-eval.c: New file. + * msgfmt.c: Include plural-exp.h. + (sigfpe_exit, sigfpe_code): New variables. + (sigfpe_handler, install_sigfpe_handler, uninstall_sigfpe_handler, + check_plural_eval, check_plural): New functions. + (main): Call it if --check-header is enabled. + * Makefile.am (msgfmt_SOURCES): Add plural-eval.c. + +2001-10-04 Tommy Johansson + + * x-java.l (object_list): New type. + (INITIAL_OBJECT_LIST_SIZE, OBJECT_LIST_GROWTH): New macros. + (java_keyword): New type. + (strip_ending_spaces): New function. + (object_list_alloc, object_list_destroy, get_num_objects, get_object, + add_object, alloc_keyword): New functions. + (java_keywords): Change type. + (extract_keyword, extract_msgid_arg, extract_msgid_plural_arg): New + functions. + (is_keyword): Change return type. + (x_java_keyword): Update. + (extract_java): Count arguments, and support plural keywords like + ngettext. + +2001-09-19 Bruno Haible + + * xgettext.h (omit_header): New declaration. + * xgettext.c (omit_header): Make global. + * x-po.c (extract_directive_message): Don't throw the header entry + away if omit_header is true. + +2001-09-19 Bruno Haible + + * write-po.c (msgdomain_list_print): Treat charset=CHARSET as if it + were absent, i.e. like ASCII, not like an unknown 8-bit encoding. + +2001-09-18 Bruno Haible + + * msgl-equal.h: New file. + * msgl-equal.c: New file. + * msgmerge.c: Include msgl-equal.h, backupfile.h, copy-file.h. + (update_mode, version_control_string, backup_suffix_string): New + variables. + (long_options): Add --backup, --suffix, --update. + (main): Accept options --backup, --suffix, -U/--update. If in update + mode, compare old and new message lists, backup the def.po file, and + overwrite the def.po file. + (usage): Document --backup, --suffix, -U/--update. + (DOT_FREQUENCY): Renamed from DOT_FREQUENCE. + (merge): Also return def.po message list. Don't share messages between + def and result; copy instead. + * Makefile.am (noinst_HEADERS): Add msgl-equal.h. + (msgmerge_SOURCES): Add msgl-equal.c. + +2001-09-19 Bruno Haible + + * po-hash-gen.y (number): Change type from 'int' to 'size_t'. + (filepos): Accept strings without line number. + (yylex): Change n from 'int' to 'size_t'. + * po.h (po_method_ty): In field comment_filepos, change line's type + from 'int' to 'size_t'. + (po_callback_comment_filepos): Change line's type from 'int' to + 'size_t'. + * read-po.c (readall_comment_filepos): Likewise. + * x-po.c (extract_comment_filepos): Likewise. + * po.c (po_comment_filepos): Likewise. + (po_callback_comment_filepos): Likewise. + (po_callback_comment): Reduce the possibility that po_hash succeeds + if the comment is not a Sun file position comment. + +2001-09-16 Bruno Haible + + * x-rst.h: New file. + * x-rst.c: New file. + * xgettext.c: Include x-rst.h. + (scan_rst_file): New function. + (language_to_scanner): Add RST rule. + (extension_to_language): Add RST rule. + * write-po.c (message_print): Don't print the line number if it is + = (size_t)(-1). + * Makefile.am (noinst_HEADERS): Add x-rst.h. + (xgettext_SOURCES): Add x-rst.c. + +2001-09-20 Bruno Haible + + * format-pascal.c: New file. + * format.h (formatstring_pascal): New declaration. + * message.h (enum format_type): Add format_pascal. + * message.c (format_language): Add format_pascal entry. + (format_language_pretty): Likewise. + * format.c (formatstring_parsers): Add formatstring_pascal. + * Makefile.am (FORMAT_SOURCES): Add format-pascal.c. + +2001-09-16 Bruno Haible + + * x-ycp.h: New file. + * x-ycp.c: New file. + * xgettext.c: Include x-ycp.h. + (usage): Mention YCP. + (scan_ycp_file): New function. + (language_to_scanner): Add YCP rule. Remove preliminary YCP rule. + (extension_to_language): Add YCP rule. + * Makefile.am (noinst_HEADERS): Add x-ycp.h. + (xgettext_SOURCES): Add x-ycp.c. + +2001-09-17 Bruno Haible + + * gettext.c: Include basename.h. + * ngettext.c: Likewise. + * msgattrib.c: Likewise. + * msgcat.c: Likewise. + * msgcmp.c: Likewise. + * msgcomm.c: Likewise. + * msgconv.c: Likewise. + * msgen.c: Likewise. + * msgexec.c: Likewise. + * msgfmt.c: Likewise. + * msggrep.c: Likewise. + * msgl-cat.c: Likewise. + * msgl-charset.c: Likewise. + * msgl-iconv.c: Likewise. + * msgmerge.c: Likewise. + * msgunfmt.c: Likewise. + * msguniq.c: Likewise. + * xgettext.c: Likewise. + * po-charset.c: Include and basename.h. + +2001-09-16 Bruno Haible + + * x-c.c (phase4_getc): Rename local variable 'state' to + 'last_was_star'. + +2001-09-11 Bruno Haible + + * Makefile.am (COMMON_SOURCES, FORMAT_SOURCES): New variables. + (msgcmp_SOURCES, msgfmt_SOURCES, msgmerge_SOURCES, msgunfmt_SOURCES, + xgettext_SOURCES, msgattrib_SOURCES, msgcat_SOURCES, msgcomm_SOURCES, + msgconv_SOURCES, msgen_SOURCES, msgexec_SOURCES, msggrep_SOURCES, + msguniq_SOURCES): Simplify. + +2001-09-09 Bruno Haible + + * file-list.c: Include system.h. + * format-c.c: Include xmalloc.h instead of system.h. + * format-ycp.c: Likewise. + * read-po.c: Likewise. + * x-po.c: Likewise. + * format-java.c: Include xmalloc.h. + * format-lisp.c: Likewise. + * format-python.c: Likewise. + * gettext.c: Likewise. + * msgen.c: Likewise. + * msgexec.c: Likewise. + * msggrep.c: Likewise. + * ngettext.c: Likewise. + * po-lex.c: Likewise. + * read-mo.c: Likewise. + * x-c.c: Likewise. + * message.c: Include xmalloc.h and strstr.h. + * msgfmt.c: Likewise. + * write-po.c: Likewise. + * msgl-cat.c: Include string.h, xmalloc.h and strstr.h. + * msgl-iconv.c: Likewise. + * msgl-charset.c: Include strstr.h. + * po-charset.c: Likewise. + * po-gram-gen.y: Include stdlib.h, xmalloc.h instead of system.h. + * po-hash-gen.y: Likewise. + * str-list.c: Likewise. + * po.c: Include string.h, xmalloc.h instead of system.h. + * write-java.c: Include string.h, xmalloc.h. + * x-java.l: Include stdlib.h, xmalloc.h, strstr.h instead of system.h. + * xgettext.c: Include xmalloc.h, strstr.h, xerror.h. + (main, construct_header): Use xasprintf instead of asprintf. + +2001-09-24 Bruno Haible + + * po-lex.c (mb_iseq): Compare the byte sequence, not the Unicode + equivalent. This is necessary for non-ASCII-compatible encodings like + SHIFT_JIS. + +2001-09-08 Bruno Haible + + * msgunfmt.h: New file. + * msgunfmt.c (verbose): New variable. + (java_mode, java_resource_name, java_locale_name): New variables. + (long_options): Add --java, --locale, --resource, --verbose. + (main): Recognize new options -j, -l, -r, -v. Handle java mode + differently. + (usage): Document --java, --resource, --locale, --verbose. + * read-java.h: New file. + * read-java.c: New file. + * read-po.h (read_po): New declaration. + * read-po.c (read_po): New function. + * gnu/gettext/DumpResource.java: New file. + * Makefile.am (jardir): New variable. + (DEFS): Define GETTEXTJAR, for read-java.c. + (JAR): New variable. + (JAVACOMP): New variable. + (msgunfmt_SOURCES): Add open-po.c, po-gram-gen.y, po-hash-gen.y, + po-charset.c, po-lex.c, po.c, read-po.c, dir-list.c, read-java.c. + (all-local, all-java-no, all-java-yes, gnu/gettext/DumpResource.class, + gettext.jar, install-data-local, install-java-no, install-java-yes, + installdirs-local, installdirs-java-no, installdirs-java-yes, + uninstall-local, uninstall-java-no, uninstall-java-yes): New rules. + (CLEANFILES): New variable. + +2001-09-08 Bruno Haible + + * msgexec.c (nonintr_select): Add function prototype. + + * xgettext.c (scan_java_file): Add function prototype. + +2001-09-23 Bruno Haible + + * xgettext.c (remember_a_message): Warn about empty msgid if it + conflicts with construct_header(). + +2001-09-08 Bruno Haible + + * msgcat.c (main): Move the option checking after the handling of + --version and --help, but before opening of any file. + * msgcomm.c (main): Move the option checking after the handling of + --version and --help. + * msguniq.c (main): Likewise. + * xgettext.c (main): Likewise. + * msgconv.c (main): Additional option checking for --sort-by-file. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msggrep.c (main): Likewise. + * msgmerge.c (main): Likewise. + +2001-09-08 Bruno Haible + + * dir-list.c: Include specification header before all other headers. + * file-list.c: Likewise. + * message.c: Likewise. + * msgl-cat.c: Likewise. + * msgl-charset.c: Likewise. + * msgl-iconv.c: Likewise. + * open-po.c: Likewise. + * po.c: Likewise. + * po-gram-gen.y: Likewise. + * po-hash-gen.y: Likewise. + * po-lex.c: Likewise. + * read-po.c: Likewise. + * str-list.c: Likewise. + * write-mo.c: Likewise. + * write-po.c: Likewise. + * msgfmt.h: Don't include message.h. + * open-po.h: Include , for FILE. + * write-mo.h: Include message.h and . + +2001-09-21 Tommy Johansson + + * x-java.l (create_char_buf): Fix problem with empty messages. + (//): Remove leading "//" from result. + (x_java_keyword): Handle NULL argument. + +2001-09-19 Tommy Johansson + + * x-java.l (is_keyword): Swap arguments of do_compare. + +2001-09-02 Bruno Haible + + * write-java.h: New file. + * write-java.c: New file. + * plural.c: New file. + * msgfmt.c: Include write-java.h. + (java_mode, assume_java2, java_resource_name, java_locale_name, + java_class_directory): New variables. + (long_options): Add --java, --java2, --locale, --resource. + (main): Add command line option -d, -j, -l, -r. Handle them and + --java2. Forbid some options depending on whether --java was given + or not. In java_mode, call msgdomain_write_java. + (usage): Update. + (format_directive_domain): In java_mode, ignore domain directives. + * Makefile.am (msgfmt_SOURCES): Add write-java.c, msgl-ascii.c, + msgl-iconv.c, plural.c. + +2001-09-16 Bruno Haible + + * Makefile.am (noinst_HEADERS): Add x-java.h. + +2001-09-14 Tommy Johansson + + * x-java.h (x_java_extract_all): New declaration. + * x-java.l (extract_all_strings): New variable. + (x_java_extract_all): New function. + (extract_java): Test extract_all_strings. + * xgettext.c (main): Call x_java_extract_all. + +2001-09-03 Bruno Haible + + * x-c.h (EXTENSIONS_C, SCANNERS_C): Remove extraneous commas. + * x-po.h (EXTENSIONS_PO, SCANNERS_PO): Likewise. + +2001-09-11 Bruno Haible + + * xgettext.c: Set msgstr_prefix to "", not NULL. + Reported by Martin Quinson . + +2001-09-10 Bruno Haible + + * msgattrib.c (main): Call set_program_name instead of assigning + program_name directly. + * msgcat.c (main): Likewise. + * msgconv.c (main): Likewise. + * msgen.c (main): Likewise. + * msgexec.c (main): Likewise. + * msggrep.c (main): Likewise. + * msguniq.c (main): Likewise. + +2001-09-08 Bruno Haible + + * msgattrib.c: New file. + * Makefile.am (bin_PROGRAMS): Add msgattrib. + (msgattrib_SOURCES, msgattrib_LDADD): New variables. + +2001-09-02 Bruno Haible + + * format-lisp.c (gcd): Remove function. Include "gcd.h" instead. + +2001-09-03 Bruno Haible + + * xgettext.c (usage): Mention ObjectiveC and Java. + * xgettext.h (substring_match): New declaration. + * Makefile.am (x-java.c): New rule. Automake's .l.c rules doesn't + work if $(LEX) is defined to ":". + +2001-09-03 Tommy Johansson + + * x-java.h: New file. + * x-java.l: New file. + * xgettext.c: Include x-java.h. + (substring_match): New variable. + (long_options): Add --keyword-substring. + (main): For -k, also call x_java_keyword. Treat --keyword-substring. + (scan_java_file): New function. + (language_to_scanner): Add Java rule. Remove preliminary Java rule. + (extension_to_language): Add Java rule. + * Makefile.am (xgettext_SOURCES): Add x-java.l. + +2001-09-02 Bruno Haible + + * read-mo.h: New file. + * read-mo.c: New file, extracted from msgunfmt.c. + (read_mo_file): Require non-null message list argument. Return void. + * msgunfmt.c (endian, read32, seek32, string32, read_mo_file): Move to + read-mo.c. + (main): Pass a fresh list, not NULL, to read_mo_file. + * Makefile.am (noinst_HEADERS): Add read-mo.h. + (msgunfmt_SOURCES): Add read-mo.c. + +2001-09-02 Bruno Haible + + * msgfmt.h (struct hashtable_entry): Remove type. + (verbose): New declaration. + * msgfmt.c (msg_domain): Add a message list field. + (verbose): Make non-static. + (main): Pass the message list, not the hash table, to + msgdomain_write_mo. Free the message list when done. + (new_domain): Initialize the message list to empty. + (format_directive_message): Change type of entry to 'message_ty *'. + Store msgid in it. + * write-mo.h: Don't include hash.h. + (msgdomain_write_mo): Change argument type from hash table to message + list. + * write-mo.c (id_str_pair): Add const. + (write_table): Change argument type from hash table to message list. + Use 'message_ty' instead of 'struct hashtable_entry'. + (msgdomain_write_mo): Change argument type from hash table to message + list. + +2001-09-01 Bruno Haible + + * msgfmt.h: New file, extracted from msgfmt.c. + * write-mo.h: New file, extracted from msgfmt.c. + * write-mo.c: New file, extracted from msgfmt.c. + (write_table): Don't call delete_hash here. + (msgdomain_write_mo): Add check against writing on full disk. + * msgfmt.c (struct id_str_pair): Move to write-mo.c. + (struct hashtable_entry): Move to msgfmt.h. + (alignment): Move to write-mo.h and write-mo.c. + (no_hash_table): Likewise. Change type to boolean. + (long_options): Don't take &no_hash_table. + (roundup): Move to write-mo.c. + (main): Set no_hash_table here. Call msgdomain_write_mo and + delete_hash. + (compare_id): Move to write-mo.c. + (write_table): Likewise. + (read_po_file): Renamed from grammar. + * Makefile.am (noinst_HEADERS): Add msgfmt.h, write-mo.h. + (msgfmt_SOURCES): Add write-mo.c. + +2001-09-01 Bruno Haible + + * write-po.c (msgdomain_list_print): Before fclose, check for ferror. + +2001-09-01 Bruno Haible + + * message.c (message_list_list_search): If the first match has an + empty translation but there is later in the list a non-empty + translation, return the latter one. This fixes the msgmerge-8 test. + +2001-08-30 Bruno Haible + + Reorganize msgfmt checking options. + * msgfmt.c: Include limits.h. + (verbose): Renamed from verbose_level. + (check_format_strings): Renamed from do_check. + (check_header, check_domain, check_compatibility): New variables. + (long_options): Add --check-domain, --check-format, --check-header, + --check-compatibility. + (main): Recognize options -C, --check-compatibility, --check-domain, + --check-format, --check-header. Option -c implies the last three. + (usage): Update. + (format_debrief): Test check_header, not verbose_level. + (format_directive_domain): Test check_domain, not verbose_level. + (format_directive_message): Test check_compatibility and check_header, + not verbose_level. Always perform the check for duplicates. + (format_comment_special): Test check_compatibility, not verbose_level. + (check_pair): Error if check_compatibility is true an msgid_plural is + used. + +2001-08-26 Bruno Haible + + * format.h: New file. + * format-c.c: New file. + * format-java.c: New file. + * format-lisp.c: New file. + * format-python.c: New file. + * format-ycp.c: New file. + * format.c: New file. + * Makefile.am (msgfmt_SOURCES): Add format.c, format-c.c, + format-java.c, format-lisp.c, format-python.c, format-ycp.c. + (xgettext_SOURCES): Likewise. + * message.h (enum format_type): New type. + (format_language, format_language_pretty): New declarations. + (parse_c_format_description_string): Remove declaration. + (possible_format_p): Renamed from possible_c_format_p. + (struct message_ty): Change field 'is_c_format' to an array and + rename it to 'is_format'. + * message.c (parse_c_format_description_string): Remove function. + (format_language): New array. + (format_language_pretty): New array. + (possible_format_p): Renamed from possible_c_format_p. + (parse_c_width_description_string): Remove function. + (message_alloc): Update for is_format array. + (message_copy): Likewise. + (message_merge): Likewise. + * po.h (po_parse_comment_special): New declaration. + * po.c (po_parse_comment_special): New function. + * msgl-cat.c (catenate_msgdomain_list): Update for is_format array. + * read-po.c (struct readall_class_ty): Change field 'is_c_format' to + an array and rename it to 'is_format'. + (readall_constructor): Update for is_format array. + (readall_directive_message): Likewise. + (readall_comment_special): Call po_parse_comment_special instead of + parse_c_format_description_string and parse_c_width_description_string. + * write-po.c (make_format_description_string): Renamed from + make_c_format_description_string. Add a language argument. Remove the + impossible and undecided cases. + (significant_format_p): Renamed from significant_c_format_p. + (has_significant_format_p): New function. + (message_print): Update for is_format array. + * msgfmt.c: Include format.h instead of printf.h. + (struct msgfmt_class_ty): Change field 'is_c_format' to an array and + rename it to 'is_format'. + (format_constructor): Update for is_format array. + (format_directive_message): Likewise. + (format_comment_special): Call po_parse_comment_special instead of + parse_c_format_description_string and parse_c_width_description_string. + (check_pair): Change is_format argument to an array. Call language + dependent format string checking routines. + * x-po.c (struct extract_class_ty): Change field 'is_c_format' to + an array and rename it to 'is_format'. + (extract_constructor): Update for is_format array. + (extract_directive_message): Likewise. + (extract_comment_special): Call po_parse_comment_special instead of + parse_c_format_description_string and parse_c_width_description_string. + * x-c.h (SCANNERS_C): Refer to formatstring_c. + * x-po.h (SCANNERS_PO): Add NULL formatstring reference. + * xgettext.c: Include format.h instead of printf-parse.h. + (test_whether_c_format): Remove function. + (current_formatstring_parser): New variable. + (remember_a_message): Update for is_format array. Call + po_parse_comment_special instead of parse_c_format_description_string + and parse_c_width_description_string. Decide whether format string + depending on current_formatstring_parser. + (language_to_scanner): Also set current_formatstring_parser. Add + dummy table entries for Python, Lisp, Java, YCP. + +2001-08-12 Bruno Haible + + * pos.h: Include . + +2001-08-12 Bruno Haible + + * x-c.c (real_file_name): Renamed from file_name. + (last_comment_line, last_non_comment_line, newline_count): Don't + initialize statically. + (phase1_getc): Update. + (x_c_lex): Don't reset last_comment_line, last_non_comment_line, + newline_count upon EOF. + (extract_c): Reset them here. Update. + + * x-c.c (x_c_lex): Move lazy keyword initialization... + (init_keywords): ... to here. New function. + (extract_c): Call init_keywords. + + * x-c.c (x_c_lex): Look up token without NUL in hash table. + (x_c_keyword): Store keywords without NUL in hash table. + +2001-07-28 Bruno Haible + + * gettext.c (usage): If IN_HELP2MAN is set, output template for + localedir, not its value. + * ngettext.c (usage): Likewise. + +2001-07-28 Bruno Haible + + * po-lex.c: Include c-ctype.h instead of . + (control_sequence): Use C locale character classifications. + * xgettext.c (main): Cast isspace argument to 'unsigned char'. + +2001-07-28 Bruno Haible + + * po-lex.h (lex_start, lex_end): New declarations. + * po-lex.c (lex_start): New function. + (lex_end): New function. + (lex_open): Call lex_start. + (lex_close): Call lex_end. + * open-po.h (open_po_file): Nop. + * open-po.c (open_po_file): Small optimization. + * po.h (po_scan): New declaration. + (po_scan_file): Renamed from po_scan. + * po.c (po_scan): New function. + (po_scan_file): Renamed from po_scan. + * msgcmp.c (grammar): Update po_scan_file call. + * msgfmt.c (grammar): Likewise. + * read-po.c (read_po_file): Likewise. + * x-po.h: New file. + * x-po.c: New file. + (extract_class_ty, extract_constructor, extract_directive_domain, + extract_directive_message, extract_parse_brief, extract_comment, + extract_comment_dot, extract_comment_filepos, extract_comment_special, + extract_methods): Moved here from xgettext.c. + (extract_po): New function, extracted from read_po_file. + * xgettext.h (line_comment, exclude): New declarations. + * xgettext.c: Include x-po.h. + (line_comment): Make non-static. + (exclude): Likewise. + (main): Update scan_po_file call. + (read_exclusion_file): Update po_scan_file call. + (extract_class_ty, extract_constructor, extract_directive_domain, + extract_directive_message, extract_parse_brief, extract_comment, + extract_comment_dot, extract_comment_filepos, extract_comment_special, + extract_methods): Move to x-po.c. + (scan_po_file): Renamed from read_po_file. Call xgettext_open and + extract_po. + (language_to_scanner): Use SCANNERS_PO. + (extension_to_language): Use EXTENSIONS_PO. + * Makefile.am (noinst_HEADERS): Add x-po.h. + (xgettext_SOURCES): Add x-po.c. + +2001-07-27 Bruno Haible + + * x-c.h: New file. + * x-c.c: New file. + (xgettext_token_type_ty, xgettext_token_ty): Moved here from + xget-lex.h. In xgettext_token_ty, unify line_number and file_name into + lex_pos_ty. + (token_type_ty, token_ty): Moved here from xget-lex.c. + (extract_all): Moved here from xgettext.c. + (keywords, default_keywords, trigraphs, file_name, logical_file_name, + line_number, fp, last_comment_line, last_non_comment_line, + newline_count): Moved here from xget-lex.c. + (phase1_pushback, phase1_pushback_length, phase1_getc, phase1_ungetc, + phase2_pushback, phase2_pushback_length, phase2_getc, phase2_ungetc, + phase3_pushback, phase3_pushback_length, phase3_getc, phase3_ungetc, + phase4_getc, phase4_ungetc, phase7_getc, phase7_ungetc, + phase5_pushback, phase5_pushback_length, phase5_get, phase5_unget, + phaseX_get, phase6_pushback, phase6_pushback_length, phase6_get, + phase6_unget, phase8_get): Moved here from xget-lex.c. Use + xgettext_comment_add instead of accessing 'comment'. Don't free + logical_file_name; it is still used as file_name of messages in mdlp. + (x_c_lex): Moved here from xget-lex.c, renamed from xgettext_lex. + (extract_c): Moved here from xgettext.c, renamed from scan_c_file. + Change arguments from filename to FILE * and two filenames. Don't call + xgettext_lex_open and xgettext_lex_close. + (x_c_extract_all): New function. + (x_c_keyword): Moved here from xget-lex.c, renamed from + xgettext_lex_keyword. + (x_c_any_keywords): Moved here from xget-lex.c, renamed from + xgettext_any_keywords. + (x_c_trigraphs): Moved here from xget-lex.c, renamed from + xgettext_lex_trigraphs. + * xgettext.h: New file. + * xgettext.c (extract_all): Move to x-c.c. + (comment): Moved here from xget-lex.c. + (xgettext_comment_add): New function. + (xgettext_comment): Moved here from xget-lex.c, renamed from + xgettext_lex_comment. + (xgettext_comment_reset): Moved here from xget-lex.c, renamed from + xgettext_lex_comment_reset. + (xgettext_open): New function, mostly taken from xgettext_lex_open + in xget-lex.c. + (remember_a_message): Make non-static. Replace xgettext_token_ty arg + with lex_pos_ty argument. + (remember_a_message_plural): Likewise. + (scan_c_file): Moved the body to x-c.c:extract_c. + (language_to_scanner): Use SCANNERS_C. + (extension_to_language): Use EXTENSIONS_C. + * xget-lex.h: Remove file. + * xget-lex.c: Remove file. + * Makefile.am (noinst_HEADERS): Remove xget-lex.h. Add xgettext.h and + x-c.h. + (xgettext_SOURCES): Remove xget-lex.c. Add x-c.c. + +2001-07-26 Bruno Haible + + * file-list.h: New file. + * file-list.c: New file, extracted from msgcomm.c. + * msgcat.c: Include file-list.h. + (main): Call read_names_from_file instead of read_name_from_file. + (read_name_from_file): Remove function. + * msgcomm.c: Include file-list.h. + (main): Call read_names_from_file instead of read_name_from_file. + (read_name_from_file): Remove function. + * xgettext.c: Include file-list.h. + (main): Call read_names_from_file instead of read_name_from_file. + (read_name_from_file): Remove function. + * Makefile.am (noinst_HEADERS): Add file-list.h. + (xgettext_SOURCES): Add file-list.c. + (msgcat_SOURCES): Likewise. + (msgcomm_SOURCES): Likewise. + +2001-07-23 Bruno Haible + + * msgcomm.c: Assume exists. + * msgexec.c: Likewise. + * msggrep.c: Likewise. + * write-po.c: Likewise. + +2001-08-02 Bruno Haible + + * gettext.c (usage): Change bug report address to + . + * ngettext.c (usage): Likewise. + * msgcat.c (usage): Likewise. + * msgcmp.c (usage): Likewise. + * msgcomm.c (usage): Likewise. + * msgconv.c (usage): Likewise. + * msgen.c (usage): Likewise. + * msgexec.c (usage): Likewise. + * msgfmt.c (usage): Likewise. + * msggrep.c (usage): Likewise. + * msgmerge.c (usage): Likewise. + * msgunfmt.c (usage): Likewise. + * msguniq.c (usage): Likewise. + * xgettext.c (usage): Likewise. + +2001-07-22 Bruno Haible + + * xgettext.c (language_to_scanner): Add language "ObjectiveC". + (extension_to_language): Map extension "m" to "ObjectiveC". + +2001-07-22 Bruno Haible + + Make msgcomm work with multidomain input and different encodings. + * msgl-cat.h (msgcomm_mode, omit_header): New declarations. + * msgl-cat.c (msgcomm_mode, omit_header): New variables. + (is_message_selected): Different behaviour if omit_header is true. + (is_message_needed): Different behaviour if msgcomm_mode is true. + (catenate_msgdomain_list): Likewise. + * msgcomm.c: Don't include , , po.h. Include + read-po.h, msgl-cat.h instead. + (line_comment, omit_header, more_than, less_than): Remove variables. + (to_code): New variable. + (long_options): New option --to-code. + (main): New option -t/--to-code. Don't take the address of omit_header. + Call catenate_msgdomain_list instead of read_po_file and + is_message_selected. + (is_message_selected, extract_constructor, extract_directive_domain, + extract_directive_message, extract_parse_brief, extract_comment, + extract_comment_dot, extract_comment_filepos, extract_comment_special, + read_po_file): Remove functions. + * Makefile.am (msgcomm_SOURCES): Add read-po.c, msgl-iconv.c, + msgl-cat.c. + +2001-07-22 Bruno Haible + + * msgl-ascii.h: New file. + * msgl-ascii.c: New file. + * po-charset.h (po_charset_ascii, po_charset_ascii_compatible): New + declarations. + * po-charset.c (po_charset_ascii): New variable. + (po_charset_ascii_compatible): New function. + * write-po.c (has_nonascii): Remove function. + (message_print, message_print_obsolete): Use is_ascii_string instead. + * msgl-iconv.c (iconv_message_list): Don't complain about missing + header entry with charset if all messages are ASCII. + * msgl-cat.c (catenate_msgdomain_list): Don't complain about missing + header entry with charset if all messages are ASCII. Better choice of + canon_to_code: when combining ASCII and an ASCII compatible encoding, + choose the latter, not UTF-8. Avoid performing trivial conversions. + * Makefile.am (noinst_HEADERS): Add msgl-ascii.h. + (msgmerge_SOURCES): Add msgl-ascii.c. + (msgunfmt_SOURCES): Likewise. + (msgcat_SOURCES): Likewise. + (msgcomm_SOURCES): Likewise. + (msgconv_SOURCES): Likewise. + (msgen_SOURCES): Likewise. + (msgexec_SOURCES): Likewise. + (msggrep_SOURCES): Likewise. + (msguniq_SOURCES): Likewise. + +2001-07-22 Bruno Haible + + * msgl-cat.h: Include . + * po.h: Likewise. + * read-po.h: Likewise. + * write-po.h: Likewise. + * xget-lex.h: Likewise. + +2001-07-21 Bruno Haible + + * msgl-cat.h: New file. + * msgl-cat.c: New file, extracted from msgcat.c. + * msgcat.c (more_than, less_than): Move to msgl-cat.c. + (use_first): Likewise. Change type to bool. + (long_options): Don't take the address of use_first. + (main): Initialize more_than, less_than, use_first explicitly. + Add --use-first handling. + (is_message_selected, is_message_needed, is_message_first_needed, + catenate_msgdomain_list): Move to msgl-cat.c. + * read-po.h (allow_duplicates): New declaration. + * read-po.c (allow_duplicates): New variable. + (readall_directive_message): If allow_duplicates is true, don't search + for the message ID, just append the message. + * msguniq.c: New file. + * Makefile.am (bin_PROGRAMS): Add msguniq. + (noinst_HEADERS): Add msgl-cat.h. + (msgcat_SOURCES): Add msgl-cat.c. + (msguniq_SOURCES, msguniq_LDADD): New variables. + +2001-07-21 Bruno Haible + + * msgcat.c (usage): The default value for more-than is 0 here. + (is_message_selected): Always keep the header entry. Needed when + option --unique is used. + +2001-07-21 Bruno Haible + + * message.h (struct message_ty): Add fields 'alternative_count', + 'alternative'. + * msgcat.c (catenate_msgdomain_list): is_c_format defaults to + undecided, not to no. If all alternative translations of a message + are the same, keep the first translation instead of mentioning them + all, and don't make the message fuzzy. + +2001-07-21 Bruno Haible + + * msgcomm.c (extract_directive_message): Don't make the message fuzzy + if the first occurrence wasn't fuzzy. + +2001-07-21 Bruno Haible + + * message.c (message_comment_filepos): Don't keep the filepos[] array + sorted. + * write-po.c (cmp_by_msgid): Renamed from msgid_cmp. + (cmp_filepos, msgdomain_list_sort_filepos): New functions. + (cmp_by_filepos): Renamed from filepos_cmp. + (msgdomain_list_sort_by_filepos): Call msgdomain_list_sort_filepos. + +2001-07-12 Bruno Haible + + * msgexec.c: New file. + * msgsed.c: Remove file. + * Makefile.am (bin_PROGRAMS): Replace msgsed with msgexec. + (msgexec_SOURCES, msgexec_LDADD): New variables. + (msgsed_SOURCES, msgsed_LDADD): Remove variables. + +2001-07-11 Bruno Haible + + * xgettext.c (test_whether_c_format): When encountering an + unterminated % format specifier, return 'impossible'. + Reported by Gaute B Strokkenes . + +2001-07-09 Bruno Haible + + * write-po.c (has_nonascii): New function. + (message_print): Warn if the msgid is not fully ASCII. + (message_print_obsolete): Likewise. + +2001-07-05 Bruno Haible + + * msgcomm.c (is_message_selected): Keep the header entry. + +2001-07-01 Bruno Haible + + * po-charset.h (po_lex_charset): New declaration. + * po-charset.c (po_lex_charset): Export variable. + * po-lex.h: Include xerror.h. + (gram_pos_column): New declaration. + (po_gram_error): Also output current column number. + (po_gram_error_at_line): Work around ## problem in gcc. + * po-lex.c: Include linebreak.h and utf8-ucs4.h. + (gram_pos_column): New variable. + (po_gram_error): Also output current column number. + (MBCHAR_BUF_SIZE): New macro. + (struct mbchar, mbchar_t): New types. + (memcpy_small, mb_iseof, mb_ptr, mb_len, mb_iseq, mb_isnul, mb_cmp, + mb_equal, mb_isascii): New functions. + (MB_UNPRINTABLE_WIDTH): New macro. + (mb_width, mb_putc, mb_setascii, mb_copy): New functions. + (NPUSHBACK): New macro. + (struct mbfile, mbfile_t): New types. + (signal_eilseq): New variable. + (mbfile_init, mbfile_getc, mbfile_ungetc): New functions. + (mbf): New variable. + (fp): Remove variable. + (lex_open): Initialize mbf, gram_pos_column, signal_eilseq. + (lex_close): Reset mbf, gram_pos_column, signal_eilseq. + (lex_getc): Return a multibyte character. Update gram_pos_column. + (lex_ungetc): Take a multibyte character. Update gram_pos_column. + (keyword_p): Use po_gram_error_at_line instead of po_gram_error. + No column number needed here. + (control_sequence): Read multibyte characters instead of bytes. + (po_gram_lex): Likewise. + * xgettext.c (exclude_directive_domain): Use po_gram_error_at_line + instead of po_gram_error. No column number needed here. + (extract_directive_domain): Likewise. + * msgcomm.c (extract_directive_domain): Likewise. + +2001-06-30 Bruno Haible + + * message.h: Include stdbool.h. + (message_ty): Change type of fields 'is_fuzzy', 'obsolete' to bool. + (message_predicate_ty): Change return type to bool. + * message.c (message_alloc): Update. + * msgcat.c (is_message_selected): Change return type to bool. + (is_message_needed): Likewise. + (is_message_first_needed): Likewise. + (main): Use bool. + (catenate_msgdomain_list): Update. Use yes/no for is_c_format and + do_wrap. + * msgcmp.c (multi_domain_mode): Change type to bool. + (compare_directive_message): Change 'obsolete' argument type to bool. + (main): Use bool. + * msgcomm.c (is_message_selected): Change return type to bool. + (extract_directive_message): Change 'obsolete' argument type to bool. + (main): Use bool. + (struct extract_class_ty): Change type of field 'is_fuzzy' to bool. + Change type of field 'filepos_count' to size_t. + (extract_constructor): Update. + (extract_parse_brief): Update. + (extract_comment_special): Update. + (read_po_file): Update. + * msgconv.c (main): Use bool. + * msgen.c (main): Use bool. + * msgfmt.c (struct msgfmt_class_ty): Change type of fields 'is_fuzzy', + 'has_header_entry' to bool. + (include_all): Change type to bool. + (do_check): Likewise. + (long_options): Update. + (format_directive_message): Change 'obsolete' argument type to bool. + (main): Use bool. + (format_constructor): Update. + (format_debrief): Update. + (format_comment_special): Update. + (check_pair): Update. + * msggrep.c (is_string_selected): Change return type to bool. + (is_message_selected): Likewise. + (main): Use bool. + * msgl-charset.c (compare_po_locale_charsets): Use bool. + * msgl-iconv.c (iconv_message_list): Update. + * msgmerge.c (quiet): Change type to bool. + (multi_domain_mode): Likewise. + (main): Use bool. + (match_domain): Update. + (merge): Update. + * msgsed.c (main): Use bool. + * msgunfmt.c (main): Use bool. + * po-gram-gen.y (string, stringlist, number, pos, rhs): Change type of + 'obsolete' field to bool. + * po-lex.h: Include stdbool.h. + (pass_obsolete_entries): Change type to bool. + (po_lex_pass_comments): Change argument type to bool. + (po_lex_pass_obsolete_entries): Likewise. + (po_gram_error): Update. + (po_gram_error_at_line): Update. + * po-lex.c (po_lex_obsolete): Change type to bool. + (pass_comments): Likewise. + (pass_obsolete_entries): Likewise. + (lex_open): Update. + (lex_close): Update. + (po_gram_error): Update. + (po_gram_error_at_line): Update. + (po_gram_lex): Update. + (po_lex_pass_comments): Change argument type to bool. + (po_lex_pass_obsolete_entries): Likewise. + * po.h (struct po_method_ty): Change 'obsolete' argument type of + directive_message to bool. + (po_callback_message): Change 'obsolete' argument type to bool. + * po.c (po_directive_message): Change 'obsolete' argument type to bool. + (po_callback_message): Likewise. + * read-po.c (struct readall_class_ty): Change type of field 'is_fuzzy' + to bool. + (readall_directive_message): Change 'obsolete' argument type to bool. + (readall_constructor): Update. + (readall_parse_brief): Update. + (readall_comment_special): Update. + (read_po_file): Update. + * str-list.h: Include stdbool.h. + (string_list_member): Change return type to bool. + * str-list.c (string_list_member): Change return type to bool. + * write-po.h (message_print_style_escape, msgdomain_list_print): Change + argument type to bool. + * write-po.c (make_c_format_description_string) Change argument type + to bool. + (message_print): Likewise. + (message_print_obsolete): Likewise. + (indent): Change type to bool. + (uniforum): Likewise. + (escape): Likewise. + (message_print_style_indent): Update. + (message_print_style_uniforum): Update. + (message_print_style_escape): Change argument type to bool. + (wrap): Update. + (msgdomain_list_print): Change argument type to bool. + * xget-lex.h (xgettext_any_keywords): Change return type to bool. + * xget-lex.c (trigraphs): Change type to bool. + (default_keywords): Likewise. + (phase5_get): Use NULL not 0. Update. + (phaseX_get): Use bool. + (xgettext_lex): Update. + (xgettext_lex_keyword): Update. + (xgettext_any_keywords): Change return type to bool. + (xgettext_lex_trigraphs): Update. + * xgettext.c (add_all_comments): Change type to bool. + (extract_all): Likewise. + (long_options): Update. + (exclude_directive_message): Change 'obsolete' argument type to bool. + (extract_directive_message): Likewise. + (main): Use bool. + (scan_c_file): Update. + (struct extract_class_ty): Change type of field 'is_fuzzy' to bool. + Change type of field 'filepos_count' to size_t. + (extract_constructor): Update. + (extract_parse_brief): Update. + (extract_comment_special): Update. + (construct_header): Update. + +2001-06-25 Bruno Haible + + * msggrep.c (usage): Explain the pattern syntax. + +2001-06-18 Karl Eichwalder + + * msgcomm.c (long_options): Add '--unique' option as documented + within --help output. + +2001-06-12 Bruno Haible + + * xgettext.c (extension_to_language): Recognize "*.hh" as being C++. + Reported by Felix Natter. + +2001-06-10 Bruno Haible + + * msgcmp.c (main): Set gram_max_allowed_errors to UINT_MAX, as in + msgmerge.c. + +2001-06-10 Bruno Haible + + * message.c (message_alloc): Cast xmalloc return value. + (message_list_alloc): Likewise. + (message_list_list_alloc): Likewise. + (msgdomain_alloc): Likewise. + (msgdomain_list_alloc): Likewise. + * msgunfmt.c (string32): Likewise. + * po.c (po_alloc): Likewise. + * str-list.c (string_list_concat): Likewise. + (string_list_join): Likewise. + +2001-06-10 Bruno Haible + + * message.h (message_ty): New field 'tmp'. + (message_predicate_ty): New type. + (message_list_remove_if_not): New declaration. + * message.c (message_list_delete_nth): Comment out. + (message_list_remove_if_not): New function. + * msgl-charset.h: New file. + * msgl-charset.c: New file. + * msgl-iconv.h: New file. + * msgl-iconv.c: New file. + * msgcat.c: New file. + * msgconv.c: New file. + * msgen.c: New file. + * msggrep.c: New file. + * msgsed.c: New file. + * msgcomm.c (default_domain, output_dir): Remove variables. + (long_options): Remove --default-domain and --output-dir. Add + --output-file. + (main): Don't accept -a, -C, -d, -k, -l, -L, -m, -M, -p, -T, -x. + Use output_file instead of complexly computed file_name. + Use O(n) loop instead of O(n^2) loop for removing messages. + (usage): Don't document --default-domain and --output-dir. + (is_message_selected): New function. + * Makefile.am (bin_PROGRAMS): Add msgcat, msgconv, msgen, msggrep, + msgsed. + (noinst_HEADERS): Add msgl-charset.h, msgl-iconv.h. + (msgcat_SOURCES, msgconv_SOURCES, msgen_SOURCES, msggrep_SOURCES, + msgsed_SOURCES): New variables. + (msgcat_LDADD, msgconv_LDADD, msgen_LDADD, msggrep_LDADD, + msgsed_LDADD): New variables. + +2001-06-10 Bruno Haible + + * message.h (struct message_variant_ty): Remove type. + (struct message_ty): Remove variant_count, variant fields. Add + msgstr, msgstr_len, pos fields. + (message_alloc): Add msgstr, msgstr_len, pp arguments. + (message_variant_search, message_variant_append): Remove declarations. + (msgdomain_ty): New type. + (msgdomain_alloc, msgdomain_free): New declarations. + (msgdomain_list_ty): New type. + (msgdomain_list_alloc, msgdomain_list_free, msgdomain_list_append, + msgdomain_list_append_list, msgdomain_list_sublist, + msgdomain_list_search, msgdomain_list_search_fuzzy): New declarations. + * message.c (message_alloc): Add msgstr, msgstr_len, pp arguments. + (message_free): Update. + (message_variant_search): Remove function. + (message_variant_append): Remove function. + (message_copy): Update. + (message_merge): Update. + (message_list_alloc): Use NULL, not 0. + (message_list_search): Likewise. + (message_list_search_fuzzy_inner): Update. + (message_list_list_alloc): Use NULL, not 0. + (message_list_list_search): Likewise. + (message_list_list_free): Comment out. + (msgdomain_alloc, msgdomain_free, msgdomain_list_alloc, + msgdomain_list_append, msgdomain_list_sublist): New functions. + * read-po.h (read_po_file): Change return type to + 'msgdomain_list_ty *'. + * read-po.c (readall_class_ty): Remove field domain_list, add field + mdlp. + (readall_constructor): Update. + (readall_destructor): Likewise. + (readall_directive_message): Likewise. + (readall_parse_debrief): Remove function. + (readall_methods): Use NULL instead of readall_parse_debrief. + (read_po_file): Change return type to 'msgdomain_list_ty *'. + * write-po.h (message_list_print, message_list_sort_by_msgid, + message_list_sort_by_filepos): Remove declarations. + (msgdomain_list_print, msgdomain_list_sort_by_msgid, + msgdomain_list_sort_by_filepos): New declarations. + * write-po.c (message_print): Remove domain argument. Update. Assume + 'first' is true. + (message_print_obsolete): Likewise. + (msgdomain_list_print): Renamed from message_list_print. Change type + of first argument. + (msgdomain_list_sort_by_msgid): Renamed from + message_list_sort_by_msgid. Change type of argument. + (msgdomain_list_sort_by_filepos): Renamed from + message_list_sort_by_filepos. Change type of argument. + * msgcmp.c (compare_class_ty): Remove field domain_list, add field + mdlp. + (multi_domain_mode): New variable. + (long_options): Add --multi-domain. + (main): Accept -m/--multi-domain. + (usage): Explain --multi-domain. + (match_domain): New function, extracted from 'compare'. + (compare): Update. Handle multi_domain_mode. Change argument types to + 'const char *'. + (compare_constructor): Update. + (compare_destructor): Likewise. + (compare_directive_message): Likewise. + (compare_parse_debrief): Remove function. + (compare_methods): Use NULL instead of compare_parse_debrief. + (grammar): Change argument type to 'const char *' and return type to + 'msgdomain_list_ty *'. + * msgcomm.c (main): Update. + (extract_directive_message): Update. + * msgmerge.c (multi_domain_mode): New variable. + (long_options): Add --multi-domain. + (struct statistics): New type. + (main): Accept -m/--multi-domain. Update. + (usage): Explain --multi-domain. + (compendium): Update. + (match_domain): New function, extracted from 'merge'. + (merge): Update. Handle multi_domain_mode. + * msgunfmt.c (main): Update. + (read_mo_file): Update. + * xgettext.c (main): Update. + (exclude_directive_message): Update. + (remember_a_message): Likewise. + (remember_a_message_plural): Likewise. + (scan_c_file): Change argument type to 'msgdomain_list_ty *'. + (extract_directive_message): Update. + (read_po_file): Change argument type to 'msgdomain_list_ty *'. + (construct_header): Update. + + * dir-list.h: Don't use reserved identifiers in prototypes. + * gettext.c: Likewise. + * message.c: Likewise. + * msgcmp.c: Likewise. + * msgcomm.c: Likewise. + * msgfmt.c: Likewise. + * msgunfmt.c: Likewise. + * ngettext.c: Likewise. + * open-po.h: Likewise. + * po-hash.h: Likewise. + * po-lex.h: Likewise. + * po-lex.c: Likewise. + * po.h: Likewise. + * po.c: Likewise. + * str-list.h: Likewise. + * write-po.c: Likewise. + * xget-lex.h: Likewise. + * xget-lex.c: Likewise. + * xgettext.c: Likewise. + +2001-06-10 Bruno Haible + + * message.h (message_list_prepend): New declaration. + * message.c (message_list_prepend): New function. + * msgmerge.c (merge): Add a header entry to the ref list if it has + none. + +2001-06-10 Bruno Haible + + * msgfmt.c: Change the --strict option to not apply to domain + directives, only to the output filename argument. + (struct msg_domain): Add file_name field. + (domain_list): Renamed from domain. + (new_domain): Add file_name argument. + (main): Pass file name to new_domain(). Use file_name passed to + new_domain() in fopen call; don't free it. + (new_domain): New argument 'file_name'. + (format_directive_domain): Always ensure ".mo" suffix, independently + of --strict. + (format_directive_message): Likewise. + (add_mo_suffix): Don't call xstrdup. + +2001-06-01 Bruno Haible + + * Makefile.am (datadir): Remove definition. + Reported by Albert Chin-A-Young . + +2001-05-15 Bruno Haible + + * msgcmp.c (main): Call set_program_name instead of assigning + program_name directly. + * msgcomm.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * xgettext.c (main): Likewise. + * gettext.c (main): Remove "lt-" prefix from program_name. + * ngettext.c (main): Likewise. + +2001-05-04 Bruno Haible + + * gettext.c (usage): Add description of what the program does. + Separate blocks with blank lines. + * ngettext.c (usage): Likewise. + + * msgcmp.c (usage): Add a blank line after the first usage line. + Hardwire the blank line output. + * msgcomm.c (usage): Likewise. + * msgfmt.c (usage): Likewise. + * msgmerge.c (usage): Likewise. + * msgunfmt.c (usage): Likewise. + * xgettext.c (usage): Likewise. + +2001-05-05 Karl Eichwalder + + * xgettext.c (read_name_from_file): Remove trailing space from line. + * msgcomm.c (read_name_from_file): Likewise. + +2001-05-02 Bruno Haible + + * po-charset.c: Don't include stdio.h, stdlib.h, mbswidth.h. Include + xerror.h instead. + (multiline_warning): Move to xerror.c. + (po_lex_charset_set): Use xasprintf instead of asprintf. + * msgfmt.c: Include xerror.h. + (format_debrief): Use multiline_error for the error message. + +2001-05-01 Bruno Haible + + Prefix most error messages with the program name, except those + starting with "filename:lineno:". + * po-lex.h: Include progname.h. + (po_gram_error): Set error_with_progname = 0 during error_at_line() + call. + (po_gram_error_at_line): Likewise. + * po-lex.c (po_gram_error): Likewise. + (po_gram_error_at_line): Likewise. + * msgcmp.c: Include progname.h. + (program_name): Remove variable. + (error_print): Remove function. + (main): Initialize error_print_progname with maybe_print_progname. + * msgcomm.c: Include progname.h. + (program_name): Remove variable. + (error_print): Remove function. + (main): Initialize error_print_progname with maybe_print_progname. + * msgfmt.c: Include progname.h. + (program_name): Remove variable. + (error_print): Remove function. + (main): Initialize error_print_progname with maybe_print_progname. + (format_directive_message): Set error_with_progname = 0 during + error_at_line() call. + (check_pair): Likewise. + * msgmerge.c: Include progname.h. + (program_name): Remove variable. + (error_print): Remove function. + (main): Initialize error_print_progname with maybe_print_progname. + * msgunfmt.c: Include progname.h. + (program_name): Remove variable. + (error_print): Remove function. + (main): Initialize error_print_progname with maybe_print_progname. + * xget-lex.c: Include progname.h. + (phase5_get): Set error_with_progname = 0 during error() call. + * xgettext.c: Include progname.h. + (program_name): Remove variable. + (error_print): Remove function. + (main): Initialize error_print_progname with maybe_print_progname. + (scan_c_file): Set error_with_progname = 0 during error() call. + +2001-04-30 Bruno Haible + + Save the messages' obsoleteness status in the 'message_ty'. + * po.h (struct po_method_ty): Add 'obsolete' argument to + directive_message method. + (po_callback_message): Add 'obsolete' argument. + * po.c (po_directive_message): Add 'obsolete' argument. Pass it to + method->directive_message. + (po_callback_message): Add 'obsolete' argument. + * po-gram-gen.y (message): Pass 'obsolete' argument to + po_callback_message. + * msgcmp.c (compare_directive_message): Add 'obsolete' argument. + * msgcomm.c (extract_directive_message): Likewise. + * msgfmt.c (format_directive_message): Likewise. + * xgettext.c (exclude_directive_message, extract_directive_message): + Likewise. + * read-po.c (readall_directive_message): Likewise. Set mp->obsolete. + +2001-05-01 Bruno Haible + + Reduce running time for very long msgstrs from O(n^2) to O(n) where + n is the number of string pieces. + * str-list.h (string_list_init, string_list_destroy, + string_list_concat, string_list_concat_destroy): New declarations. + * str-list.c (string_list_init, string_list_destroy, + string_list_concat, string_list_concat_destroy): New functions. + * po-gram-gen.y Include str-list.h. + (%union): Add new alternative of type string_list_ty. + (string_list): Change type to stringlist. + (message): Use string_list_concat_destroy to convert a stringlist to + a single string. Use string_list_destroy instead of free. + (msgid_pluralform): Likewise. + (pluralform): Likewise. + (string_list): Return a stringlist. Don't concatenate the strings one + by one. + * po-lex.c: Include str-list.h. + +2001-04-30 Bruno Haible + + * message.h (message_alloc): Add const to prototype. + * message.c (message_alloc): Add const to argument type. + * msgcmp.c (compare_class_ty): Add const to 'domain' field. + * msgcomm.c (default_domain): Add const. + (main): Add const to 'file_name' variable. + * xgettext.c (default_domain): Add const. + (msgstr_prefix): Likewise. + (msgstr_suffix): Likewise. + (main): Add const to 'file_name' variable. + +2001-04-29 Bruno Haible + + * read-po.h: New file. + * read-po.c: New file, extracted from msgmerge.c. + * msgmerge.c: Don't include string.h. Include read-po.h instead. + (merge_class_ty, line_comment, merge_constructor, merge_destructor, + merge_directive_domain, merge_directive_message, merge_parse_brief, + merge_parse_debrief, merge_comment, merge_comment_dot, + merge_comment_special, merge_comment_filepos, grammar): Move out to + read-po.c. + (compendium, merge): Call read_po_file() instead of grammar(). + * Makefile.am (noinst_HEADERS): Add read-po.h. + (msgmerge_SOURCES): Add read-po.c. + * FILES: Update. + +2001-04-28 Bruno Haible + + * msgcmp.c (usage): Restructure usage message. Talk about ref.pot, not + ref.po. + * msgcomm.c (long_options): Remove --add-comments, --join-existing + options. + (main): Rename local variables sort_output, sort_by_file to + sort_by_msgid, sort_by_filepos. Remove -c and -j options. + (usage): Restructure usage message. INPUTFILE is not mandatory. Remove + --trigraphs option. + * msgfmt.c (usage): Restructure usage message. + * msgmerge.c (main): Signal error if both --sort-output and + --sort-by-file were given. + (usage): Restructure usage message. Talk about ref.pot, not ref.po. + Document --sort-output, --sort-by-file, --quiet. + * msgunfmt.c (long_options): Add --sort-output. + (main): Recognize -s/--sort-output option, and sort the message list + when it is given. + (usage): Restructure usage message. + * xgettext.c (main): Rename local variables sort_output, sort_by_file + to sort_by_msgid, sort_by_filepos. + (usage): Restructure usage message. + +2001-04-28 Bruno Haible + + * po-charset.h: New file. + * po-charset.c: New file, extracted from po.c. Don't warn about wrong + or missing charset if it's a POT file. + * po-lex.h (iconv.h, po_lex_iconv): Move to po-charset.h. + (po_lex_charset): Remove declaration. + * po-lex.c: Include po-charset.h. + (po_lex_charset, po_lex_iconv): Move to po-charset.c. + (lex_open): Call po_lex_charset_init. + (lex_close): Call po_lex_charset_close. + * po.c: Don't include ctype.h, stdio.h, mbswidth.h, libgettext.h. + Include po-charset.h instead. + (program_name, _, SIZEOF, multiline_warning): Move to po-charset.c. + (po_callback_message): Move charset handling to po-charset.c. + * Makefile.am (noinst_HEADERS): Add po-charset.h. + (msgcmp_SOURCES, msgfmt_SOURCES, msgmerge_SOURCES, xgettext_SOURCES, + msgcomm_SOURCES): Add po-charset.c. + * FILES: Update. + + * msgfmt.c: Don't include ctype.h. + * msgunfmt.c: Likewise. + +2001-04-28 Bruno Haible + + * po.h (PO_BASE_TY): Remove next_is_fuzzy field. + * po.c (po_alloc): Don't reset next_is_fuzzy. + (po_callback_message): Ignore fuzziness of the header entry. Don't + reset next_is_fuzzy. + (po_callback_comment): Don't set next_is_fuzzy. + * msgfmt.c (format_directive_message): Ignore fuzziness of the header + entry. Count fuzzy untranslated entries as untranslated. + +2001-09-13 Bruno Haible + + * gettext-0.10.40 released. + +2001-07-24 Bruno Haible + + * gettext-0.10.39 released. + +2001-05-23 Bruno Haible + + * gettext-0.10.38 released. + +2001-05-23 Bruno Haible + + * write-po.c (wrap): Avoid broken EUC-KR conversion in glibc-2.1. + +2001-05-21 Bruno Haible + + * xgettext.c (construct_header): Replace 8-bit with 8bit. + +2001-04-19 Bruno Haible + + * gettext-0.10.37 released. + +2001-04-18 Bruno Haible + + * po.c (po_callback_message): Don't call iconv_open when the + OLD_PO_FILE_INPUT environment variable is set. + * write-po.c (wrap): Don't call iconv_open when the OLD_PO_FILE_OUTPUT + environment variable is set. + +2001-04-09 Bruno Haible + + * po.c: Include mbswidth.h. + (program_name): New declaration. + (multiline_warning): New function. + (po_callback_message): Change messages again. Call multiline_warning + instead of error. + +2001-04-04 Bruno Haible + + * gettext.c (program_name): Change type to 'const char *'. + * ngettext.c (program_name): Likewise. + +2001-04-04 Bruno Haible + + * write-po.c (wrap): Prohibit line breaks inside backslash escape + sequences. + +2001-03-29 Bruno Haible + + * gettext-0.10.36 released. + +2001-03-27 Bruno Haible + + * po.c (po_callback_message): Change message again. + +2001-03-26 Bruno Haible + + * xgettext.c (test_whether_c_format): Recognize '%%' as valid. + * msgfmt.c (check_pair): In error message, count format + specifications starting from 1, not 0. + +2001-03-25 Bruno Haible + + * xgettext.c (warn_id_len): Remove unused variable. + (main): Don't set it. 'l' option becomes a nop. + (usage): Remove mention of --string-limits/-l. + +2001-03-23 Bruno Haible + + * msgcomm.c (main): Don't give an error message if there are "too few" + messages in the input files. Do sanity checking before reading the + input files. Make --add-location the default, even if --omit-header + is specified. + +2001-03-22 Bruno Haible + + * xgettext.c (construct_header): Replace ENCODING with 8-bit. + +2001-03-20 Bruno Haible + + * po.c (po_callback_message): Change message, refer to GNU libiconv. + +2001-03-18 Bruno Haible + + * gettext.c [TESTS]: When testing, set HAVE_SETLOCALE. + (main): Expand escape sequences also if no domain is given. Don't need + to call bindtextdomain if there are no string arguments. + * ngettext.c: New file. + * Makefile.am (bin_PROGRAMS): Add ngettext. + (ngettext_SOURCES): New variable. + +2001-03-19 Bruno Haible + + * xgettext.c (extension_to_language): Recognize "*.cpp" and "*.hpp" as + being C++. + Patch by Dennis Bjorklund . + +2001-03-11 Bruno Haible + + * gettext.c: Force inclusion of libgnuintl.h. On Solaris, + has already included libintl.h. + +2001-03-11 Bruno Haible + Karl Eichwalder + + * po.c (po_callback_message): Add comments to support translators. + +2001-03-11 Bruno Haible + + * po-lex.c (lex_close): Use ngettext and plural-form message. + * msgcmp.c (compare): Likewise. + * msgfmt.c (main): Likewise. + +2001-03-10 Bruno Haible + + * po.c (po_callback_message): Cast %*s argument from size_t to int. + * msgfmt.c (format_debrief): Likewise. + (check_pair): Cast format string argument from size_t to long. + * msgmerge.c (merge): Likewise. + + * gettext.c (main): Update copyright year. + * msgcmp.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgfmt.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgunfmt.c (main): Likewise. + * xgettext.c (main): Likewise. + + * po.c (po_callback_message): Also warn if no iconv is present and + the charset is a CJK encoding requiring iconv. Also avoid broken + EUC-KR conversion in glibc-2.1. + +2001-03-09 Bruno Haible + + Fix output of strings in CJK encodings. + * write-po.c: Include iconv.h. + (wrap): While preparing a line, use iconv to avoid treating the second + byte of a multi-byte character as an ASCII character. + +2001-03-04 Bruno Haible + + * msgcomm.c (main): Use IS_ABSOLUTE_PATH and concatenated_pathname. + Fixes an incorrectly computed memory allocation. + * xgettext.c (main): Likewise. + * open-po.c (open_po_file): Use IS_ABSOLUTE_PATH and + concatenated_pathname. + * xget-lex.c (xgettext_lex_open): Likewise. + * msgfmt.c (main): Use SET_BINARY instead of setmode. + * msgunfmt.c (read_mo_file): Likewise. + +2001-03-04 Bruno Haible + + Check syntax of obsolete entries of PO files, not only in msgmerge. + * po-lex.h (pass_obsolete_entries): New declaration. + (msgstr_def): Remove pos field. + * po-lex.c (po_lex_obsolete): New variable. + (pass_obsolete_entries): Make non-static. + (lex_open): Initialize po_lex_obsolete. + (lex_close): Reset po_lex_obsolete. + (po_gram_lex): Don't look at pass_obsolete_entries. Instead, set + po_lex_obsolete to 1 when "#~" is seen. Reset po_lex_obsolete when + a newline is seen. Before returning, fill the 'pos' and 'obsolete' + fields of any token. + * po-gram.gen.y (check_obsolete): New macro. + (union): Add a 'pos' and 'obsolete' field for any symbol type. + (NAME): Assign to type . + (DOMAIN, MSGID, MSGID_PLURAL, MSGSTR, '[', ']'): Assign to type . + (msgid, msgstr): Remove. + (message, msgid_pluralform, pluralform_list, pluralform, string_list): + Signal an error if the 'obsolete' field is not the same across the + entire token sequence of the rule. + (message): Deal with pass_obsolete_entries here. + (msgid_pluralform, pluralform_list, pluralform, string_list): Fill the + 'pos' and 'obsolete' fields of $$. + +2001-03-03 Bruno Haible + + Fix parsing of strings in CJK encodings. + * po.h (PO_BASE_TY): New field next_is_fuzzy. + * po-lex.h: Include iconv.h. + (po_lex_charset, po_lex_iconv): New declarations. + * po.c (SIZEOF): New macro. + (po_alloc): Initialize next_is_fuzzy. + (po_callback_message): Add check of charset in header entry. + Set po_lex_charset and po_lex_iconv. + After calling po_directive_message, reset next_is_fuzzy. + (po_callback_comment): Set next_is_fuzzy. + * msgfmt.c (format_directive_message): Remove check of charset in + header entry, now done in po.c. + * po-lex.c (po_lex_charset, po_lex_iconv): New variables. + (lex_open): Initialize them. + (lex_close): Reset them. + (po_gram_lex): While parsing a string, use 'po_lex_iconv' to avoid + treating the second byte of a multi-byte character as an ASCII + character. + * Makefile.am (msgcmp_LDADD, msgfmt_LDADD): New variables. + +2001-03-03 Bruno Haible + + * write-po.h: New file, pieces of message.h. + * message.h (message_page_width_set, message_print_style_indent, + message_print_style_uniforum, message_print_style_escape, + message_list_print, message_list_sort_by_msgid, + message_list_sort_by_filepos): Move to write-po.h. + * write-po.c: New file, pieces of message.c. + (wrap): Add charset argument. Rewritten to use mbs_width_linebreaks(). + Use c_isprint() instead of isprint(). + (message_print): Add charset argument. Insert a space after '#' even + if the comment line's first character is a tab. + (message_print_obsolete): Likewise. + (message_list_print): Don't change locales. Extract the charset name + from the header entry. + (msgid_cmp, filepos_cmp): Always use strcmp, never use strcoll. + (message_list_sort_by_msgid, message_list_sort_by_filepos): Don't + change locales. + * message.c (indent, uniforum, escape, page_width, + make_c_format_description_string, significant_c_format_p, + make_c_width_description_string, message_print_style_indent, + message_print_style_uniforum, message_print_style_escape, wrap, + print_blank_line, message_print, message_print_obsolete, + message_list_print, msgid_cmp, message_list_sort_by_msgid, + filepos_cmp, message_list_sort_by_filepos, message_page_width_set): + Move to write-po.c. + * msgmerge.c: Include write-po.h. + * msgunfmt.c: Likewise. + * xgettext.c: Likewise. + * msgcomm.c: Likewise. + * Makefile.am (noinst_HEADERS): Add write-po.h. + (msgmerge_SOURCES): Add write-po.c. + (msgunfmt_SOURCES): Likewise. + (xgettext_SOURCES): Likewise. + (msgcomm_SOURCES): Likewise. + (msgmerge_LDADD, msgunfmt_LDADD, xgettext_LDADD, msgcomm_LDADD): New + variables. + +2001-03-03 Bruno Haible + + * msgcmp.c (compare): Internationalize error message. + +2001-02-10 Bruno Haible + + * msgunfmt.c (main): Accept -e and -E options. + +2001-02-04 Bruno Haible + + * msgfmt.c (main): Open the output file in binary mode. + * msgunfmt.c (read_mo_file): Open the input file in binary mode. + +2001-01-21 Bruno Haible + + Use libtool. + * Makefile.am (LDADD): Remove ../intl/libintl.$la, not needed any more. + (l): Remove macro. + +2001-01-20 Bruno Haible + + * msgcomm.c (main): Options '-<' and '->' want an argument. + +2001-01-07 Bruno Haible + + * gettext.c: Renamed from gettextp.c. Use included libintl if TESTS + is defined. + * setlocale.c: Move to ../tests. + * Makefile.am (EXTRA_PROGRAMS, all-local, CLEANFILES): Remove. + (tstgettext_SOURCES): Remove. + (gettext_SOURCES): Update. + +2001-01-07 Bruno Haible + + * msgcmp.c: Include libgettext.h instead of libintl.h, so that the + configure argument --disable-nls gets respected. + * msgfmt.c: Likewise. + * msgmerge.c: Likewise. + * msgunfmt.c: Likewise. + * open-po.c: Likewise. + * po-gram-gen.y: Likewise. + * po-lex.c: Likewise. + * xgettext.c: Likewise. Don't include gettext.h. + +2001-01-07 Bruno Haible + + Assume , , , exist. + * dir-list.c: Likewise. + * gettextp.c: Likewise. + * message.c: Likewise. + * msgcmp.c: Likewise. + * msgcomm.c: Likewise. + * msgfmt.c: Likewise. + * msgmerge.c: Likewise. + * msgunfmt.c: Likewise. + * open-po.c: Likewise. + * po.c: Likewise. + * setlocale.c: Likewise. + * str-list.h: Likewise. + * xget-lex.c: Likewise. + * xgettext.c: Likewise. + +2001-01-06 Bruno Haible + + * Makefile.am (datadir): Assume DATADIRNAME = share. + +2001-01-06 Bruno Haible + + * setlocale.c: New file. + * Makefile.am (EXTRA_PROGRAMS): New variable. + (all-local): New target. + (CLEANFILES): New variable. + (tstgettext_SOURCES): New variable. + +2001-01-05 Bruno Haible + + * Makefile.am (po-gram-gen.h): Don't use $^, not supported by the + SUSV2 "make" specification. + (LDADD): Add @INTLLIBS@. Needed on Solaris without + --with-included-gettext. + * po-lex.c (gram_max_allowed_errors): Change type to + 'unsigned int'. + +2001-01-01 Bruno Haible + + Implement plural form handling. + * message.h (struct message_variant_ty): Add msgstr_len field. + (struct message_ty): Add msgid_plural field. + (message_alloc): Take additional msgid_plural argument. + (message_variant_append): Take additional msgstr_len argument. + * message.c (message_alloc): Take additional msgid_plural argument. + (message_free): Free msgid_plural field. + (message_variant_append): Take additional msgstr_len argument. + (message_copy): Copy msgid_plural as well. Pass msgstr_len. + (message_merge): Likewise. + (message_print): Print plural entries using a different format. + (message_print_obsolete): Likewise. + * msgunfmt.c (string32): Return the string's size as well. Verify + the string is NUL terminated. + (read_mo_file): Split the original string into msgid and msgid_plural. + Pass msgstr_len. + * po-lex.h (msgstr_def): New definition, taken from msgfmt.c. + * po-lex.c (keyword_p): Recognize the msgid_plural keyword. + (po_gram_lex): Accept brackets as single-character tokens. + * po.h (struct po_method_ty): Method 'directive_message' takes + additional arguments 'msgid_plural', 'msgstr_len'. + (po_callback_message): Additional arguments 'msgid_plural', + 'msgstr_len'. + * po-hash-gen.y (yyerror): Effectively rename to po_hash_error. + * po-gram-gen.y (yyerror): Effectively rename to po_gram_error, + thus enabling reporting of syntax errors. + (plural_counter): New variable. + (%token): Add MSGID_PLURAL, '[', ']' as new tokens. + (%union): Add new alternative of type 'struct msgstr_def'. + (msgid_pluralform, pluralform, pluralform_list): New productions. + (message): Add plural rules. + * po.c (po_directive_message): Additional arguments 'msgid_plural', + 'msgstr_len'. + (po_callback_message): Likewise. + * msgfmt.c (SIZEOF): New macro. + (struct id_str_pair): Add id_len, id_plural, id_plural_len, str_len + fields. + (struct hashtable_entry): Renamed from struct msgstr_def. Add + 'msgid_plural', 'msgstr_len' fields. + (format_directive_message): Additional arguments 'msgid_plural', + 'msgstr_len'. Verify the validity of the charset field in the header. + Compare msgstr using memcmp, not strcmp. + (check_pair): Additional arguments 'msgid_plural', 'msgstr_len'. + Apply the tests to msgid_plural and each msgstr[i] string. + (format_debrief): Change error message. + (write_table): Store msgid_plural and msgstr_len in msg_arr[], then + output the strings including embedded NULs. + * msgcmp.c (compare_directive_message): Additional arguments + 'msgid_plural', 'msgstr_len'. + * msgcomm.c (extract_directive_message): Additional arguments + 'msgid_plural', 'msgstr_len'. + * msgmerge.c (merge_directive_message): Additional arguments + 'msgid_plural', 'msgstr_len'. + * xget-lex.h (struct xgettext_token_ty): Replace argnum field with + argnum1, argnum2. + * xget-lex.c (xgettext_lex): Add to default keywords: "ngettext:1,2", + "dngettext:2,3", "dcngettext:2,3". + (xgettext_lex_keyword): Accept new syntax "id:argnum1,argnum2". + * xgettext.c (exclude_directive_message): Additional arguments + 'msgid_plural', 'msgstr_len'. + (remember_a_message): Return the new message. + (remember_a_message_plural): New function. + (scan_c_file): Extend state machine to allow remembering msgid1 and + msgid2 later. + (extract_directive_message): Additional arguments 'msgid_plural', + 'msgstr_len'. Compare msgstr using memcmp, not strcmp. + (construct_header): Update. + +2000-12-31 Bruno Haible + + * msgfmt.c (format_directive_message): Pass to insert_entry and + find_entry the length including the terminating NUL. + * xget-lex.c (xgettext_lex, xgettext_lex_keyword): Likewise. + +2000-12-31 Bruno Haible + + * msgunfmt.c (read_mo_file): Recognize "/dev/stdin", not "/dev/stdout". + +2000-12-31 Bruno Haible + + * str-list.h: Fix typo. Add comments. + * str-list.c: Add comments. + + * dir-list.h: Add comments. + * dir-list.c: Likewise. + + * domain.h: Remove file. + * msgfmt.c: Incorporate it here. Don't include "domain.h". + * msgunfmt.c: Don't include "domain.h". + * xgettext.c: Likewise. + * Makefile.am (noinst_HEADERS): Remove domain.h. + + * open-po.h: New file. + * open-po.c: Include it. + * po-lex.c: Likewise. + * Makefile.am (noinst_HEADERS): Add open-po.h. + + * open-po.c (open_po_file): Merge duplicated code. + + * po-lex.h: Add comments. + (gram_max_allowed_errors): Change type to 'unsigned int'. + (po_gram_error_at_line): Renamed from gram_error_at_line. + (po_gram_error, po_gram_error_at_line): Use ISO C 99 macro vararg + syntax if possible. + * po-lex.c: Add comments. Don't include "po-gram.h". + (po_gram_error_at_line): Renamed from gram_error_at_line. + * msgmerge.c (main): Initialize gram_max_allowed_errors to UINT_MAX. + + * message.h: Add comments. + (enum is_wrap): New type. + (parse_c_width_description_string): Change return type. + (message_ty): Change type of do_wrap field. + * message.c (wrap): Change type of do_wrap argument. + * msgfmt.c (msgfmt_class_ty): Change type of do_wrap field. + * msgmerge.c (merge_class_ty): Change type of do_wrap field. + * msgcomm.c (extract_class_ty): Change types of is_c_format, do_wrap + fields. + * xgettext.c (extract_class_ty): Likewise. + (remember_a_message): Change type of do_wrap. + + * pos.h: New file. + * po-lex.h: Include it. + (struct lex_pos_ty): Remove type, now in pos.h. + * message,h: Include pos.h instead of po-lex.h. + * Makefile.am (noinst_HEADERS): Add pos.h. + + * po.h: Add comments. + + * gettextp.c (main): Use bindtextdomain instead of bindtextdomain__, + and dgettext instead of dgettext__. There is no need to use the + included libintl when GNU gettext or an X/Open gettext or a catgets + has been found in the system's C library. + + * ylwrap: Update from automake-1.4. + + * FILES: New file. + * Makefile.am (EXTRA_DIST): New variable. + +2000-12-30 Bruno Haible + + * xgettext.c (scanner_c, scanner_cxx): Remove declarations. + +2000-12-30 Bruno Haible + + * msgfmt.c (write_table): Pass additional argument &id_len to + iterate_table. + +2000-09-13 Bruno Haible + + Look into #define bodies. + * xget-lex.c (phaseX_get): Simplify. + (phase6_get): Stop the loop when recognizing a #define directive. + Based on a patch by Martin v. Löwis. + + Accept ISO C 99 comment syntax. + * xget-lex.c (cplusplus_comments: Remove variable. + (phase4_getc): Always recognize // comments. + (xgettext_lex_cplusplus): Remove function. + * xget-lex.h (xgettext_lex_cplusplus): Remove declaration. + * xgettext.c (scan_c_file): Remove is_cpp_file argument. + (scanner_c, scanner_cxx): Remove functions. + (language_to_scanner): Call scan_c_file directly. + +2000-08-23 Bruno Haible + + * po-lex.c (ALERT_CHAR): New constant macro. + (control_sequence): Accept \a. Don't accept \X. + +2000-08-23 Ulrich Drepper + + * po-lex.c (control_sequence): Unget character which ended \x.. + sequence. + +2000-07-28 Bruno Haible + + * xget-lex.h (enum xgettext_token_type_ty): Rename + xgettext_token_type_lp/rp to xgettext_token_type_lparen/rparen. + * xget-lex.c (enum token_type_ty): Rename token_type_lp/rp to + token_type_lparen/rparen. + (xgettext_any_keywords): `keywords' is now a hash table. + * xgettext.c (construct_header): Change two printf directives %02d + to %02ld. + +1998-07-17 Paul Eggert + + Add support for user-specified argument numbers for keywords. + Extract all strings from a keyword arg, not just the first one. + Handle parenthesized commas inside keyword args correctly. + Warn about nested keywords. + + * xgettext.c (scan_c_file): + Warn about nested keywords, e.g. _(_("xxx")). + Warn also about not-yet-implemented but allowed nesting, e.g. + dcgettext(..._("xxx")..., "yyy"). + Get all strings in a keyword arg, not just the first one. + Handle parenthesized commas inside keyword args correctly. + + * xget-lex.h (enum xgettext_token_type_ty): + Replace xgettext_token_type_keyword1 and + xgettext_token_type_keyword2 with just plain + xgettext_token_type_keyword; it now has argnum value. + Add xgettext_token_type_rp. + (struct xgettext_token_ty): Add argnum member. + line_number and file_name are now also set for + xgettext_token_type_keyword. + (xgettext_lex_keyword): Arg is const char *. + + * xget-lex.c: Include "hash.h". + (enum token_type_ty): Add token_type_rp. + (keywords): Now a hash table. + (phase5_get): Return token_type_rp for ')'. + (xgettext_lex, xgettext_lex_keyword): Add support for keyword argnums. + (xgettext_lex): Return xgettext_token_type_rp for ')'. + Report keyword argnum, line number, and file name back to caller. + +2000-05-06 Ulrich Drepper + + * Makefile.am (EXTRA_DIST): Replace po-gram.gen.h and po-hash.gen.h + with po-gram-gen.h and po-hash-gen.h. + (YACC): Define as @YACC@ -d. + (YFLAGS): Removed. + (msgcmp_SOURCES): Replace dependencies on po-gram.gen.c and + po-hash.gen.c with po-gram-gen.y and po-hash-gen.y. + (msgmerge_SOURCES): Likewise. + (xgettext_SOURCES): Likewise. + (msgcomm_SOURCES): Likewise. + Add rule to built po-gram-gen2.h and make po-lex.o depend on it. + Remove rules to generate po-gram.gen.c and po-hash.gen.c. + Patches by Jim Meyering. + + * po-lex.c: Include "po-gram-gen2.h" instead of "po-gram.gen.h". + + * message.c (message_list_search_fuzzy_inner): Define static. Take + extra parameter with best weight so far. + (message_list_search_fuzzy): New function. + (message_list_list_alloc): New function. + (message_list_list_append): New function. + (message_list_list_append_list): New function. + (message_list_list_search): New function. + (message_list_list_search_fuzzy): New function. + (message_list_list_free): New function. + * message.h (message_list_list_ty): Define type. + (message_list_list_alloc): Add prototype. + (message_list_list_free): Likewise. + (message_list_list_append): Likewise. + (message_list_list_append_list): Likewise. + (message_list_list_search): Likewise. + (message_list_list_search_fuzzy): Likewise. + * msgmerge.c: Implement --compendium/-C option. + * xget-lex.c (xgettext_any_keywords): New function. + * xget-lex.h (xgettext_any_keywords): Add prototype. + * xgettext.c: Use xgettext_any_keywords to see whether keywords are + defined. + Patches by Peter Miller. + +1998-05-15 Ulrich Drepper + + * po-lex.c (control_sequence): Replace illegal with invalid. + +1998-05-01 08:47 Ulrich Drepper + + * gettext-0.10.35 released. + +1998-04-30 Ulrich Drepper + + * dir-list.c: Update Peter Miller's mail address. + * dir-list.h: Likewise. + * message.c: Likewise. + * message.h: Likewise. + * msgcmp.c: Likewise. + * msgcomm.c: Likewise. + * msgmerge.c: Likewise. + * po-gram.h: Likewise. + * po-gram.y: Likewise. + * po-hash.h: Likewise. + * po-hash.y: Likewise. + * po-lex.c: Likewise. + * po-lex.h: Likewise. + * po.c: Likewise. + * po.h: Likewise. + * str-list.c: Likewise. + * str-list.h: Likewise. + * xget-lex.c: Likewise. + * xget-lex.h: Likewise. + +1998-04-30 Ulrich Drepper + + * msgfmt.c: Fix typo in --help text. + Reported by Jan.Djarv@mbox200.swipnet.se. + +1998-04-03 01:18 1998 Philippe De Muyter + + * str-list.h (stddef.h): Include that file only if STDC_HEADERS. + Otherwise include sys/types.h and stdio.h. + * msgmerge.c (string.h): Include that file if HAVE_STRING_H, not + if STDC_HEADERS. + +1998-04-29 Ulrich Drepper + + * message.c: Use unsigned char for various local variables. + * xgettext.c (comment_tag): Define as unsigned char *. + For losing Solaris systems. Patches by Jim Meyering. + + * msgfmt.c: Use extra braces in if to shut up gcc. + * po-lex.h: Don't declare function po_gram_error and + gram_error_at_line if macros with the same names are defined. + * Makefile.am (MAINTAINERCLEANFILES): New variable. + Patches by Jim Meyering. + +1998-04-27 Ulrich Drepper + + * xgettext.c: Update year and bug report address. Add little + explanation in --help messages. + * msgfmt.c: Likewise. + + * msgmerge.c: Update year and bug report address. + * msgcomm.c: Likewise. + * msgcmp.c: Likewise. + * msgunfmt.c: Likewise. + +1997-08-31 22:20 Ulrich Drepper + + * msgfmt.c (format_directive_message): Count fuzzy messages as + fuzzy, even if they are not written to the output file. + +1997-08-18 13:47 Philippe De Muyter + + * msgcomm.c (sys/types.h): File included. + +1997-08-15 12:38 Ulrich Drepper + + * xgettext.c: Include to define MIN/MAX for HP/UX. + Patch by Kaveh R. Ghazi . + + * msgfmt.c: Change DEFAULT_ALIGNMENT to DEFAULT_OUTPUT_ALIGNMENT + to avoid clash with macro with same name in obstack.c. + Reported by Akim Demaille . + +1997-08-01 15:48 Ulrich Drepper + + * Makefile.am (AUTOMAKE_OPTIONS): Require version 1.2. + +1997-05-07 04:21 Ulrich Drepper + + * msgcomm.c (main): Print author in --version message. + * msgunfmt.c: Likewise. + * msgcmp.c: Likewise. + * msgmerge.c: Likewise. + * msgfmt.c: Likewise. + * xgettext.c: Likewise. + * gettextp.c: Likewise. + +1997-05-01 02:33 Ulrich Drepper + + * msgcmp.c (main): Update copyright. + * msgunfmt.c (main): Likewise. + * msgcomm.c (main): Likewise. + * msgmerge.c (main): Likewise. + * msgfmt.c (main): Likewise. + * gettextp.c (main): Likewise. + + * msgcomm.c: Fix comment about default output (Jan Djarv). Split + help message. + +1997-03-31 16:09 Ulrich Drepper + + * msgcomm.c (usage): Use program_name as argument for print to + print message, not stdout. + Patch by Jan Djarv . + +Mon Mar 10 06:18:58 1997 Ulrich Drepper + + * xgettext.c: Implement generic language scanner handling. + + * xget-lex.c (phase7_getc): Better comments. + (phase5_get): Print warnings about unterminated strings and + character constants. + + * po-lex.c (po_gram_error): Don't count continuation lines in + messages as errors. + (gram_error_at_line): Likewise. + * po-lex.h: Likewise for macro versions. + + * po-hash.y: Correct typo. + + * msgunfmt.c: Implement --force-po option. + + * msgmerge.c: Implement --force-po, --no-location, and + --add-location options. + + * msgfmt.c (format_directive_message): If messages are duplicated + and translations are different this is a fatal error. + + * msgcmp.c (compare_directive_message): Use correct format for + continuation line in message. + + * message.h: Add prototype for message_list_delete_nth. + + * message.c: Add message_list_delete_nth function. + + * Makefile.am (bin_PROGRAMS): Add msgcomm. (l): New variable. + Set to `l' is using libtool. (LDADD): Change for the needs of + libtool. (msgcomm_SOURCES): New variable. + + * msgcomm.c: New file. + +Wed Dec 4 01:58:10 1996 Ulrich Drepper + + * Makefile.am (LDADD): Change for use of libtool. + +Tue Dec 3 18:08:46 1996 Ulrich Drepper + + * xget-lex.c (phase7_getc): Return \n when newline is seen, not + P7_NEWLINE. + + * xgettext.c (main): Implement --foreign-user flag. + + * msgcmp.c (main): Change --version output to what is required by + GNU standards. (usage): Correct bug report address. + * msgfmt.c: Likewise. + * msgunfmt.c: Likewise. + * msgmerge.c: Likewise. + * xgettext.c: Likewise. + * gettextp.c: Likewise. + +Sat Sep 14 04:28:09 1996 Ulrich Drepper + + * msgunfmt.c (usage): Put bug report address in separate string. + * msgmerge.c (usage): Likewise. + * msgcmp.c (usage): Likewise. + * xgettext.c (usage): Likewise. + * msgfmt.c (usage): Likewise. + * gettextp.c (usage): Likewise. + +Thu Sep 12 21:40:48 1996 Andreas Schwab + + * msgfmt.c (check_pair): Fix error messages. + +Sat Aug 31 14:05:29 1996 Ulrich Drepper + + * msgunfmt.c (usage): Add hint about where to report bugs to. + * msgmerge.c (usage): Likewise. + * msgcmp.c (usage): Likewise. + * xgettext.c (usage): Likewise. + * msgfmt.c (usage): Likewise. + * gettextp.c (usage): Likewise. + +Sat Aug 31 04:49:38 1996 Ulrich Drepper + + * gettextp.c: Don't include since this can generate + conflicts. + +Mon Jul 15 02:21:25 1996 Ulrich Drepper + + * msgfmt.c: Major change: msgfmt now does not write fuzzy messages + out by default. The option -f/--use-fuzzy must be used to + explicitely tell to do it. + +Sat Jul 13 20:23:34 1996 Ulrich Drepper + + * xget-lex.c (phase6_get): Reset selected comments on every + preprocessor directive. + +Fri Jul 12 12:38:49 1996 Ulrich Drepper + + * xgettext.c (main): Remove `v' from short option list. + +Sat Jul 6 11:22:47 1996 Kaveh R. Ghazi + + * message.c (message_merge): Add some casts to (char *) in alloca + calls. Make KNOWN_FIELDS array static. + +Sat Jul 6 11:15:43 1996 Jim Meyering + + * msgfmt.c (check_pair): Correct English in Message. + +Fri Jul 5 17:27:11 1996 Ulrich Drepper + + * message.c (message_merge): Terminate string for UNKNOWN fields + in header entry. + + * message.c (message_merge): Don't print POT-Revision-Date twice. + + * msgfmt.c: Implement --statistics. + Suggested by Santiago Vila Doncel. + + * msgfmt.c: and change message checking so that tests for leading + and trailing \n are always performed. + Suggested by François Pinard. + +Wed Jun 19 02:42:52 1996 Ulrich Drepper + + * message.c (message_merge): Implement sorting of header entry + lines. + +Sat Jun 15 19:46:50 1996 Ulrich Drepper + + * msgmerge.c (usage): Correct -w option in help string (was -W). + +Tue Jun 11 15:28:44 1996 Ulrich Drepper + + * xget-lex.c, xget-lex.h (xgettext_lex_comment): Change parameter + type to size_t. + + * po-lex.h, po-lex.c (gram_max_allowed_errors): Change type to + size_t. + + * message.c, message.h (message_page_width_set): Change parameter + type to size_t. + + * Makefile.am (AUTOMAKE_OPTIONS): Add variable. Must be defined + in all subdirs. + +Mon Jun 10 02:53:52 1996 Marcus Daniels + + * dir-list.c: Include system.h in order to get size_t and NULL. + +Thu Jun 6 01:59:31 1996 Ulrich Drepper + + * str-list.h: Include to get size_t defined. + Reported by Philippe Defert. + + * Makefile.am (LDADD): Remove `@INTLLIBS@ ../lib/libnlsut.a + @LIBS@'. This is not necessary anymore. + +Wed Jun 5 00:00:08 1996 Ulrich Drepper + + * xgettext.c, msgunfmt.c (usage): Correct --page-width to --width + in usage string. + + * open-po.c (open_po_file): Implement search path for PO files. + + * xget-lex.c (xgettext_lex_open): Implement search path for PO + files. Patch by Peter Miller. + + * message.h (struct message_ty): Add do_wrap member. + Add prototypes for parse_c_width_description_string and + message_page_width_set. + + * xgettext.c: Implement --width option to specify width for which + line wrapping is done. Change --directory option implement search + path instead of single directory. Patch by Peter Miller. + + * message.c (wrap): Implement no-wrap special comment. If given + no wrapping is performed. + + * gettextp.c, msgcmp.c, msgfmt.c, msgmerge.c, msgunfmt.c, + xgettext.c: Add `no-wrap' comments at usage message strings. + + * msgcmp.c: Implement --directory to specify search path for .po + files. Patch by Peter Miller. + +Tue Jun 4 23:57:59 1996 Ulrich Drepper + + * msgfmt.c: Implement --width option to specify width for which + line wrapping is done. Patch by Peter Miller. + + * msgmerge.c: Implement --width option to specify width for which + line wrapping is done. Implement --directory to specify search + path for .po files. Patch by Peter Miller. + + * msgunfmt.c: Implement --width option to specify width for which + line wrapping is done. Patch by Peter Miller. + +Tue Jun 4 00:12:25 1996 Ulrich Drepper + + * po-hash.y: Add new clause where in GNU style format the name is + `file'. + + * Makefile.am (noinst_HEADERS): Add dir-list.h. + (msgcmp_SOURCES, msgfmt_SOURCES, msgmerge_SOURCES, xgettext_SOURCES): + Add dir-list.c + +Mon Jun 3 00:43:07 1996 Ulrich Drepper + + * message.h: Add new parameter for message_list_print. + + * msgmerge.c, msgunfmt.c (main): Add new parameter to + message_list_print. Set to 0 because we don't need to know about + the reasoning. + + * xgettext.c: Implement new option --debug which determines + whether a difference is made between c-format and + possible-c-format. Default is to print c-format for both cases. + + * message.c (make_c_format_description_string): Take additional + parameter DEBUG. If nonzero, distinguish between c-format and + possible-c-format. + + * message.c (message_print_obsolete): Copy precious translator + comment to output file. Reported by Santiago Vila Doncel. + + * dir-list.c: Include file now is called dir-list.h, not + dir_list.h. + + * message.c: Include . + + * Makefile.am (EXTRA_DIST): Add variable to distribute po-gram.y + and po-hash.y. + + * Makefile.am (msgfmt_SOURCES): Fix typo: msgfmt.o -> msgfmt.c. + +Sat Jun 1 04:33:48 1996 Ulrich Drepper + + * Makefile.in: Remove support for tupdate. msgmerge is stable + now. + +Wed Apr 10 01:20:49 1996 Ulrich Drepper + + * message.c (message_print_obsolete): Don't print + c-format/no-c-format flags for obsolete entries. + + * xgettext.c (construct_header): Change DIST to ZONE in header + entry template. + + * message.c (message_merge): Insert POT-Creation-Date field before + PO-Revision-Date. + +Tue Apr 9 17:13:34 1996 Ulrich Drepper + + * xgettext.c (construct_header): Move POT-Creation-Date line + before PO-Revision-Date line. + +Fri Apr 5 12:07:19 1996 Ulrich Drepper + + * msgmerge.c: Implement --quiet option to prevent dots printed as + progress report. + (merge): Don't print dots if `quiet'. + + * msgmerge.c (merge): Nicer statistics message. + + * message.c (message_merge): Update POT-Creation-Date field in + header entry from contents in reference file. + + * msgfmt.c (format_directive_message): Better test for unchanged + fields in header entry. + + * xgettext.c (difftm): Is back. + (construct_header): Print distance to GMT in POT file time stamp. + + * xgettext.c (construct_header): Print leading comment and fuzzy + flag. + + * message.c (message_print): Allow translator comment to fill more + than one line. + + * xgettext.c (construct_header): Introduce POT-Creation-Date + field. + +Fri Apr 5 03:05:07 1996 Ulrich Drepper + + * msgmerge.c (merge): Rename empty in missing. There might be + more empty messages which are not missed. + + * msgmerge.c (merge): Terminate `.' line if no verbose output is + selected. + + * msgmerge.c (main): Implement -v option to increase verbosity + level. + (merge): Print `.' to signal ongoing work. + Unless verbosity level > 1 don't print information about fuzzy and + missing matches. Instead print statistics at the end. + Suggested by François Pinard and Santiago Vila Doncel. + +Thu Apr 4 11:59:20 1996 Ulrich Drepper + + * xgettext.c (difftm): Remove unused function. + (construct_header): Don't fill Last-Translator field with information + about curent user but instead constant text mentioning xgettext. + + * message.c (message_list_search_fuzzy): Initialize mp before + using it. + + * message.c (message_print): Normalize printed messages even more. + Don't print fuzzy flag is msgstr is empty. + + * message.c (message_list_search_fuzzy): Don't try to match + against msgid if none if the msgstr of this message in non-empty. + +Thu Apr 4 01:57:37 1996 Ulrich Drepper + + * msgfmt.c (format_directive_message): When testing header entry + also check whether they still contain the initial values. + Suggested by François Pinard. + +Tue Apr 2 16:19:42 1996 Ulrich Drepper + + * xgettext.c (main): Add "warning" to message about unknown input + file type. + + * Makefile.in (all-gettext): New goal. Same as all. + + * xgettext.c (usage): Rearange help strings. One of them was too + long for some dumb catgets programs. Reported by Marcus Daniels. + + * msgfmt.c (format_directive_message): Check for standard header + entry fields. + + * xgettext.c (construct_header): MIME-VERSION should be written + MIME-Version. + + * msgmerge.c (main): Don't recognize -f option anymore. This is + the default now. + + * msgfmt.c (format_debrief): New function. Warn if no header + entry is found. + (format_directive_message): Remember if header entry is found. + +Tue Apr 2 11:12:15 1996 Ulrich Drepper + + * msgfmt.c (format_constructor): New function. Initialize + is_c_format field. + (format_directive_message): Clear is_c_format field for next message + at the end. + + * xgettext.c, po-lex.h, po-lex.c, po-gram.y, msgmerge.c, msgfmt.c, + msgcmp.c: Use gram_error_at_line instead of gram_error_with_loc + and error_at_line instead of error_with_loc. Roland does not like + my English. + +Tue Apr 2 03:27:34 1996 Ulrich Drepper + + * msgunfmt.c (main): Removed -S option. People should think twice + before using this and so are forced to use the long version. + +Tue Apr 2 03:25:56 1996 François Pinard + + * msgunfmt.c (usage): Remove Tab character from message. + +Tue Apr 2 03:15:16 1996 Marcus Daniels + + * message.c (significant_c_format_p): If is_c_format is `no' this + is significant. + +Tue Apr 2 03:12:24 1996 Kaveh R. Ghazi + + * po.c (po_comment_filepos): Make definition static as declaration + was before. + + * msgunfmt.c (usage): Add missing \n to help string. + +Mon Apr 1 02:37:45 1996 Ulrich Drepper + + * xgettext.c (main): When recognizing file type, default to C + instead of regarding it as an error. Suggested by Marcus Daniels. + + * po.c (po_callback_comment): For now recognize #! also as special + comment. + + * msgmerge.c (merge): Remove --force option. We now always write + the result. It makes no sense to reject the output because some + messages are not matching. + + * po-lex.c (po-gram_error, gram_error_with_loc): Use + error_message_count instead of gram_nerrors. + (gram_nerrors): Remove definition. + + * po-lex.h (po-gram_error, gram_error_with_loc): Use + error_message_count instead of gram_nerrors. + + * xgettext.c: Remove verbose option and variable. + (test_whether_c_format): Don't return `possible' is string + contains no format specifier. + +Sun Mar 31 23:23:40 1996 Ulrich Drepper + + * xget-lex.c (xgettext_lex): Fix typo. Reported by François + Pinard. + +Thu Mar 28 19:01:22 1996 Ulrich Drepper + + * po.c (po_callback_comment): Correct handling of special + comments. Give whole comment to callback function instead of + tokenized form. + +Thu Mar 28 18:37:49 1996 Ulrich Drepper + + * xgettext.c (remember_a_message): Always look through comments + because we have to look for c-format comments. + + * message.h, msgmerge.c, xgettext.c, message.c, msgfmt.c: + Implement more detailed C format string handling. Basically coded + by Marcus Daniels. + +Thu Mar 28 16:52:56 1996 Marcus Daniels + + * Makefile.in (MSGFMT_OBJ): Add message.o. + +Wed Mar 27 03:16:01 1996 Ulrich Drepper + + * xget-lex.c (xgettext_lex): Correct implementation of comments + grouped with messages. + +Tue Mar 26 21:23:54 1996 Ulrich Drepper + + * po.c (po_callback_comment): Remove unused variable string. + Include because isspace is used. + + * message.h (message_list_print): Undo change of Mon Mar 25 + 03:34:44 1996. Don't print trailing comment. Remove additional + argument. + + * message.c (message_list_print): Undo change of Mon Mar 25 + 03:34:44 1996. Don't print trailing comment. We now have a + correct implementation of obsolete entry handling. + * msgmerge.c: Ditto. + + * po-lex.h: Add prototype for po_lex_pass_obsolete_entries. + + * po-lex.c (po_gram_lex): Implement handling of comments for + obsolete entries (#~). + + * msgunfmt.c, xgettext.c (main): Undo change of Mon Mar 25 + 03:34:44 1996. Remove added argument to message_list_print again. + + * po-lex.c (po_gram_lex): Small optimization in string collection. + + * message.c (message_print_obsolete): Obsolete messages are now + preceded by #~ on each line. + + * xgettext.c (remember_a_message): Fix bug with dereferencing + dangling pointer. This caused xgettext test 1 to fail. + + * xgettext.c (construct_header): Update format for file header. + + * xgettext.c (extract_directive_message): Don't report error when + message variant exists but the value is the same. Reported by + Roland McGrath. + + * message.c (wrap): Only write characters in escape notation if + explicitely wanted or if it is one of the well known escapes like + \n. + + * msgunfmt.c (main): Adopt interface to GNU coding standard. All + given files on command line are input files. Output by default is + written to standard output and can be redirected using -o. + +Mon Mar 25 04:25:42 1996 Ulrich Drepper + + * message.c, message.h (message_list_print): Parameter filename is + const. + + * message.c (wrap): Change line break behaviour a bit. While it + is reasonable to break long line containing #: comments + immediately when reaching the the limit, this could lead to unnice + results for the strings in msgid and msgstr. The programmer + usually knows why the lines are that long. + + So we break for now after reaching 2 * PAGE_WIDTH instead of + PAGE_WIDTH. + + * message.c (message_list_print): Print blank line before trailing + comments. + + * message.c (message_print_obsolete): Don't print anything for + obsolete entries with empty msgstr. + +Mon Mar 25 03:34:44 1996 Ulrich Drepper + + * msgunfmt.c (main): Make program by default read from stdin and + by default write to stdout in the appropriate argument is not + given. Suggested by François Pinard. + + * msgfmt.c (format_directive_domain): Only check for correct + format string elements is special comment contains c-format. + + * msgfmt.c: Use sizeof instead of strlen to determine length of + constant string. + + * xgettext.c: Implement generation of c-format special comments. + + * msgunfmt.c (main): Call message_list_print with additional + argument set to NULL. + + * xgettext.c (main): Call message_list_print with additional + argument set to NULL; + (extract_class_ty): We don't have field comment_special anymore, but + instead flags fuzzy and c_format. + (extract_constructor): Reset fields fuzzy and c_format. + (extract_directive_message): Set flags according to special comments, + not string list. + (extract_comment_special): Set flags according to special comment. + + * message.h (message_ty): We don't have field comment_special + anymore. Instead flags fuzzy and c_format. + (message_comment_special_append): Remove prototype. + (message_list_print): Add new argument to prototype. + + * message.c (message_alloc): We don't have the comment_special, + but fuzzy and c_format. + (message_comment_special_append): Remove function. We now have flags. + (message_copy, message_merge): Copy fuzzy and c_format flag + appropriately. + (message_print, message_print_obsolete): Print special comment using + flags, not string list. + (message_list_print): Additional argument with trailing comments. + Printed at the end if not NULL. + + * msgmerge.c (merge_class_ty): New fields fuzzy and c_format for + flags; + (trailing_comments): New global variable for list of trailing + comments in definition file. + (grammar): Takes an additional argument which if NULL gets the list of + trailing comments assigned to. + (main): Call message_list_print with additional argument of trailing + comments. + (merge_comment_special): Recognize fuzzy and c-format special comment. + +Sun Mar 24 17:35:26 1996 Ulrich Drepper + + * xgettext.c (usage): Option --output-suffix does not exist + anymore. + + * msgmerge.c (usage): --strict does not have short form -S. + + * message.h (message_ty): New field `obsolete'. + + * msgmerge.c (merge): Change behaviour not not matched entries. + Instead of giving a message write them out at the end of the + regular output and precede each line with `# '. + + * message.c (message_list_print): Handle obsolete entries + separately by printing them at the end and preceded by `# '. + (message_print_obsolete): New function to print obsolete entries. + + * Makefile.in ($(PROGRAMS)): Add generated libraries as + dependencies for programs. + + * Makefile.in (PROGRAMS): Remove msgjoin. + +Sun Mar 24 01:03:32 1996 Ulrich Drepper + + * xgettext.c (extract_comment_filepos): Only add line comment if + requested by -n option. + + * po.c (po_callback_comment): Short by one bug in special comment + entry copying. + + * po.c (po_callback_comment): Handle special comments. Separate + them into a list of comma separate entries. + + * message.c (message_print): Format of lines containing fuzzy + comments et.al. is now `#, xxx'. + + * Makefile.in: msgjoin program is not anymore generated. + + * xgettext.c: First step to implementing general input file + handling. The program now recognizes the file type by the file + name extension and uses the appropriate function. For now two + file types are recognized: C/C++ and PO. Especially handling PO + files make the msgjoin program obsolete. + +Sat Mar 23 01:50:00 1996 Ulrich Drepper + + * msgfmt.c (verbose): Rename to verbose_level. + (main): Increment verbose_level each time -v option is given. + (format_directive_domain): Print some of the diagnostic messages only + if verbosity level is > 1. Suggested by François Pinard for a + better interface to PO mode. + + * xgettext.c (scan_c_file): Extract all string if `extract-all' + option is given. Reported by Roland McGrath. + +Thu Mar 14 14:55:20 1996 Ulrich Drepper + + * msgfmt.c (format_comment_special): Be prepared that special + comment contains more than one entry, separated by commas. + + * message.c (message_print): Special comments are now written in a + line, separated by commas. + + * msgmerge.c (merge): Replace `INEXACT' with `fuzzy'. + +Thu Mar 14 11:50:48 1996 Marcus Daniels + + * po-hash.h (po_hash): Change __P to PARAMS in prototype. + +Fri Mar 1 13:35:01 1996 Ulrich Drepper + + * msgfmt.c (main): Set error_one_per_line to 1 to prevent more + than one error message per line. Suggested by François Pinard. + + * po-lex.h (po_gram_error): Don't write source file name in fatal + message. + (po_gram_error_with_loc): Ditto. + + * po-lex.c (lex_close): Don't write source file name in fatal + message. + (po_gram_error): Ditto. + (po_gram_error_with_loc): Ditto. + +Fri Mar 1 00:30:49 1996 Ulrich Drepper + + * po-lex.h: Use PARAMS instead of __P in header declarations. + + * msgcmp.c (compare_methods): Set new field comment_special to + NULL. + + * msgjoin.c (join_methods): Set new field comment_special to NULL. + + * xgettext.c (exclude_methods): Set new field comment_special to + NULL. + + * po.h (struct po_method_ty): New field comment_special. + (po_directive_domain, po_directive_message, po_parse_brief, + po_parse_debrief, po_comment, po_comment_dot, po_comment_filepos): + Remove prototypes. Now are local functions. + + * po.c (po_parse_brief, po_parse_debrief, po_directive_domain, + po_directive_message, po_comment, po_comment_dot, + po_comment_filepos): Declare functions as local and add + prototypes. + (po_comment_special): New function. + (po_callback_comment): For special comments call po_comment_special. + + * msgmerge.c (usage): Add --force in help message. + (merge_methods): Set special comment callback to NULL. + + * po-lex.c (lex_open): Don't set pass_comments to 0. This has to + be done in upper layer functions. + (po_gram_lex): Also pass #! comments up. + + * msgfmt.c (main): Make lexer pass comments up. + (format_comment_special): New function. Warns about `#! INEXACT' + comments. + (format_methods): Add callback for special comment. + +Wed Feb 14 01:56:14 1996 Ulrich Drepper + + * xgettext.c (main): Remove option --output-suffix. When default + domain name is "-" write to stdout. + +Mon Feb 12 02:20:09 1996 Ulrich Drepper + + * xgettext.c (main): Implement --output-suffix parameter to + determine alternative form of suffix for output file. + + * xgettext.c, msgjoin.c, msgmerge.c, msgunfmt.c (main): Add + additional argument to message_list_print call: control output in + case of empty PO file. + * message.c (message_list_print): Implement FORCE parameter. + * message.h: Change prototype. + +Sun Jan 21 17:24:56 1996 Ulrich Drepper + + * Makefile.in (install-exec): Include empty else case for case + when Perl is not available. Reported by John David Anglin. + +Sat Dec 23 12:41:43 1995 Jun Young + + * gettextp.c (usage): Short option for version info is -V. + +Tue Dec 19 22:10:12 1995 Ulrich Drepper + + * Makefile.in (Makefile, tupdate): Explicitly use $(SHELL) for + running shell scripts. + +Sat Dec 9 17:06:11 1995 Ulrich Drepper + + * xgettext.c, xget-lex.h, xget-lex.c, str-list.h, po.h, po.c, + po-lex.c, po-hash.y, po-gram.h, open-po.c, msgunfmt.c, msgmerge.c, + msgjoin.c, msgfmt.c, msgcmp.c, message.h, message.c, gettextp.c: + Use PARAMS instead of __P. Suggested by Roland McGrath. + +Fri Dec 8 01:38:40 1995 Ulrich Drepper + + * Makefile.in (PROGRAMS): Add definitions for new msgjoin program. + + * msgjoin.c: Initial revision. + +Wed Dec 6 18:43:14 1995 Ulrich Drepper + + * open-po.c (open_po_file): Recognize /dev/stdin as name for + stdin. Recognize .pot as valid extension. + +Wed Dec 6 18:05:11 1995 Ulrich Drepper + + * msgmerge.c (main): Sort options in getopt loop. + +Mon Dec 4 15:37:22 1995 Ulrich Drepper + + * msgmerge.c: (main): Remove unused variable `exit_status'. + +Sun Dec 3 02:51:31 1995 Ulrich Drepper + + * xget-lex.h: [xgettext_token_type_ty]: We now have two keyword + types and also the comma is important. + + * xgettext.c (remember_a_message): + Correct handling of -c option. This is not a + string to prepend to output. Instead it selects single strings to + include (instead of all). Reported by Marcus Daniels. + (scan_c_file): Extend state machine. We have to retrieve the second + argument for the keywords `dgettext' and `dcgetetxt`. + + * xget-lex.c (phase5_get): Recognize ','. + (xgettext_lex): Pass ',' on caller. + Return different value for keywords `dgetetxt' and `dcgettext`. + + * xget-lex.c (xgettext_lex): `gettext_noop' is an default keyword. + + * msgunfmt.c (usage): + Message should not contain TABs. Reported by François Pinard. + + * msgunfmt.c (usage): + Correct typo: Uniforun -> Uniforum. Reported by François Pinard. + +Mon Nov 20 21:12:52 1995 Ulrich Drepper + + * po-lex.c, message.c: Some more pretty printing. + + * message.c (wrap): Don't support '\a' and '\v'. + + * xget-lex.c (phase7_getc): Don't support '\v'. + + * po-lex.c (control_sequence): Don't support '\v'. + + * gettextp.c (expand_escape): Don't support \a and \v. + + * msgcmp.c (compare): Really define static. + +Thu Nov 16 22:42:33 1995 Ulrich Drepper + + * msgmerge.c (merge): Remove additional parameter in in prototype. + + * xgettext.c: Reomved unused type definition. Patch by Peter Miller. + + * xget-lex.c: Correct some comments and better implementation of + -k option. Patch by Peter Miller. + + * po.h: Fix typos. By Peter Miller. + + * po-lex.c (po_gram_lex): Prevent accumulation of #! comments. + + * po-gram.y (comments): Remove unused rule. + + * msgmerge.c: Implement new options sort-by-file and sort-output. + Patches by Peter Miller. + + * msgcmp.c (domain): Remove unused global variable. + (domain_directive): Remove unused function. + + * message.h: Fix comment for MESSAGE_DOMAIN_DEFAULT definition. + + * message.c (message_print): Correct typo. + Clarify comments about ANSI escape sequences. + Patches by Peter Miller. + + * Makefile.in (DISTFILES): Remove $(COMSRCS). + (MSGFMT_OBJ): Correct indentation. Patches by Peter Miller. + +Sun Nov 12 12:52:29 1995 Ulrich Drepper + + * xgettext.c (line, string, comment): Remove unused global variables. + (read_name_from_file): Remove unused variable `cp'. + + * msgmerge.c (merge): Add missing return statement. + + * msgfmt.c (check_pair): Correctly pair comparisons. + + * msgcmp.c (domain_list, mlp): Remove unused global variables. + +Sat Nov 11 21:39:17 1995 Ulrich Drepper + + * message.c (message_list_print): + Prevent output if we have no (real) entry. + + * xgettext.c (remember_a_message): Implement exclude file handling. + +Sat Nov 11 17:38:05 1995 Ulrich Drepper + + * msgunfmt.c: Fix message. + + * xgettext.c: Use string handling from str-list and .po file + handling use xget-lex et.al. + + * xget-lex.h, xget-lex.c, str-list.h, str-list.c, po.h: + Initial revision. + + * po-lex.h: Allow variable upper limit of errors. + New prototypes. + + * po-lex.c: Allow variable upper limit of errors. + Make comment's text available to the caller. + + * po.c, po-hash.y, po-hash.h: Initial revision. + + * po-gram.y: Add handling of comments. + + * po-gram.h: Remove all but one declaration. + + * msgunfmt.c, msgmerge.c: Initial revision. + + * msgfmt.c: Adopt for new interface to parser. + + * msgcmp.c: Move lot's of general code to other files. + + * message.h, message.c: + Extended functionality for Peter Miller's pseudo-OO programming. + + * Makefile.in: Rewrite after adding rules for new programs. + +Fri Nov 10 10:01:37 1995 Ulrich Drepper + + * Makefile.in (PROGRAMS): Add new programs msgmerge and msgunfmt. + +Thu Nov 9 01:29:46 1995 Ulrich Drepper + + * gettextp.c (usage): Split message in two parts. + + * xgettext.c (usage): Split message in three parts. + + * xgettext.c (main): + Print warning if --files-from option and file names on command + line are given. + + * xgettext.c (long_options): + Mixed up `default-domain' and `directory' values. + +Wed Nov 8 23:31:34 1995 Ulrich Drepper + + * xgettext.c: Implement -D and -f option. + +Tue Nov 7 13:44:44 1995 Ulrich Drepper + + * Makefile.in (LIBS): One @LIBS@ must be @INTLLIBS@. + + * Makefile.in (LIBS): Correct definition. We must be prepared to + use two different libintl.a libraries. + (po-gram.gen.c): Don't use $< in non-implicit rule. + + * Makefile.in (install-exec): Use `test -n' instead of + `test XXX != ""'. Proposed by François Pinard. + +Sun Nov 5 23:59:03 1995 Ulrich Drepper + + * Makefile.in (INSTALL_PROGRAM): Do not specify mode. + +Sun Nov 5 21:13:57 1995 Ulrich Drepper + + * xgettext.c, msgfmt.c: + Comments describing what has to be done should start with FIXME. + +Sun Nov 5 19:39:56 1995 Ulrich Drepper + + * Makefile.in (dist-gettext): Make synonym for dist. + +Sun Nov 5 18:11:15 1995 Ulrich Drepper + + * po-lex.h (gram_error, gram_error_with_loc): + Don't define macros when !__STDC__ + even when using gcc. + + * po-lex.c (gram_error, gram_error_with_loc): + Compile if !__STDC__ even if using gcc. + + * Makefile.in (po-gram.gen.c po-gram.gen.h): + Remove file prior of generation. + +Sun Nov 5 11:39:21 1995 Ulrich Drepper + + * Makefile.in (dist): Suppress error message when ln failed. + Get files from $(srcdir) explicitly. + + * xgettext.c (process_c_source): + Make gettext_noop the forth builtin marker. + +Fri Nov 3 00:57:52 1995 Ulrich Drepper + + * msgfmt.c (main): Don't free fname when no suffix was added. + +Thu Nov 2 22:55:44 1995 Ulrich Drepper + + * Makefile.in (dist): Also remove msgcmp. + +Tue Oct 31 22:27:52 1995 Ulrich Drepper + + * msgfmt.c: New option --strict: Only if this is given the .mo + file ending is forced. + + * msgfmt.c (message_directive): + Call error_with_loc with correct parameters. + + * msgfmt.c (message_directive): + Ignores empty message does not count as fatal error. + + * Makefile.in (po-gram.gen.c): + Add g suffix to make multiple substitution in one + line possible. + +Mon Oct 30 22:35:41 1995 Ulrich Drepper + + * po-gram.h: + Don't give defines for translation of yy* symbols. This is not enough + to be able to have more than one parser. See src/Makefile for the way + we chose. + + * Makefile.in (po-gram.gen.c): + Rewrite generated source while copying. This is the + only portable way to get more than one parser in the same program. + Patch by Peter Miller. + +Sun Oct 29 10:49:59 1995 Ulrich Drepper + + * Makefile.in (INSTALL_SCRIPT): New variable. + (install-exec): Install tupdate using INSTALL_SCRIPT to prevent error + when using strip flag. + +Sat Oct 28 14:39:33 1995 Ulrich Drepper + + * po-gram.h: Include . + + * xgettext.c (main): + Honour -n option even if --omit-header is given. By Peter Miller. + + * msgcmp.c (check_domain_coverage): + No double space in message. By Peter Miller. + + * msgcmp.c (grammar): + Close input file after coverage check. Patch by Peter Miller. + +Wed Sep 27 20:27:26 1995 Ulrich Drepper + + * msgcmp.c: + Don't try to include . This is done in "system.h". + + * po-lex.c (gram_error, gram_error_with_loc): + Add argument definition for K&R style. + (gram_error, gram_error_with_loc): We increase gram_nerrors, + not nerrors. Reported by Francesco Potorti`. + +Tue Sep 26 10:03:29 1995 Ulrich Drepper + + * po-gram.h (yyparse): Add redefinition to gram_parse. + + * Makefile.in (YFLAGS): + Don't use -p option. Stupid old yaccs do not know it. + + * po-lex.c: Include po-gram.h to get redefinitions of yy* symbols. + + * po-gram.h (yylex, yylval, yyerror): + Redefine these symbols to gram_* because the + yacc is now called without -p option. + + * Makefile.in (LIBS): + Undid last change. On some systems libintl.a is not + completely self-contained. alloca() is miisong e.g. on HP-UX. + +Mon Sep 25 22:35:55 1995 Ulrich Drepper + + * msgfmt.c (add_mo_suffix): Fix typo. + + * po-lex.h: Include . + (lex_open): Argument is now `const char *'. + + * msgfmt.c (add_mo_suffix): + Allow .gmo suffix. Great idea by Marcus Daniels. + +Sat Sep 23 08:20:54 1995 Ulrich Drepper + + * po-gram.y, po-lex.c: Include error.h. + + * open-po.c (open_po_file): + Remove unused variables `path_dir' and `open_po_file'. + +Thu Sep 21 15:30:36 1995 Ulrich Drepper + + * Makefile.in (LIBS): + using libnlsut.a twice is not necessary anymore. libintl.a is + selfcontained. + + * gettextp.c (main): + Use dcgettext__ and bindtextdomain__ instead of __dcgettext + and __bindtextdomain. + + * msgfmt.c, xgettext.c (exit_status): + New variables. Contains exit status for the case the program + ends normally. Changed when non-fatal error messages are given. + +Wed Sep 20 09:16:57 1995 Ulrich Drepper + + * Makefile.in (xgettext): No also link po-gram.gen.o and po-lex.o. + + * po-lex.c: Pretty print comments. + Do some CSE in computation of hex value. + + * xgettext.c (read_po_file): Now use the generated scanner. + (domain_directive, message_directive): New functions needed for + scanner. + (add_id_str): Correct test for exclude files. The messages in the + exclude table are in raw format, not C format. + (write_out_domain): Check for zero messages and don't write anything in + this case. + + * po-lex.c (lex_open): Argument NAME is now const. + (gram_error): Move VARARGS1 comment to right place. + (control_sequence): Pretty print some comments. + +Mon Sep 18 21:23:55 1995 Ulrich Drepper + + * msgfmt.c (add_mo_suffix): Really check for not .mo suffix. + + * xgettext.c (write_out_domain): + Write file names in #: lines to file, not stdout. + + * po-gram.y (grammar): + Remove function. This allows sharing this file in different + programs. + + * domain.h (msg_domain): Member DOMAIN_NAME is now const. + + * Makefile.in (PROGRAMS): Add msgcmp. + (HEADERS): Add message.h. + (SOURCES): Add message.c and msgcmp.c. + (OBJECTS): Add message.o and msgcmp.o. + (msgcmp): Rule to construct program. + + * message.h, message.c: Initial revision + + * msgfmt.c (grammar): Close comment so that function is seen. + (message_directive): MSGID and MSGSTR are not const. + + * msgcmp.c: Initial revision + + * po-gram.h (message_directive): + MSGID and MSGSTR argument are not const. + + * po-gram.h (grammar): Remove prototype. Is now locally defined. + + * po-lex.h (gram_error, gram_error_with_loc): + Protect the instructions by do while (0). + + * msgfmt.c (grammar): + Define function here. This allows sharing the grammar file + with the msgcmp program. + + * msgfmt.c (domain_directive): Free memory of NAME if not needed. + (new_domain): Do not duplicate filename, use it as it is. + + * msgfmt.c (message_directive): + Free parameter string memory here if necessary. + Was done in po-gram.y before. + + * po-gram.h: Remove comment after closing #endif. + + * po-gram.h (grammar): Name parameter in prototype. + +Sun Sep 17 23:29:30 1995 Ulrich Drepper + + * xgettext.c (read_po_file): We don't have a search path anymore, + so third argument to open_po_file is not needed anymore. Reduce + argument list by this parameter, too. + + * po-lex.c (lex_open): + We don't have a search path anymore, so third argument to + ope_po_file is not needed anymore. + + * open-po.c (open_po_file): Remove unused `use_path' parameter. + + * Makefile.in (HEADERS): Add po-gram.h and po-lex.h. + + * po-gram.h, po-lex.h: Initial revision + + * Makefile.in (YACC, YFLAGS): New program used for .po file grammar. + (SOURCES): Add po-gram.y and po-lex.c. + (GENHEADERS, GENSOURCES): New variables for generated headers + and sources. + (OBJECTS): Add po-gram.gen.o and po-lex.o. + Add rules for new files and add to dependency list for msgfmt. + + * po-lex.c, po-gram.y: Initial revision + + * xgettext.c: Remove input path handling. + Adapt for new hashing functions return values. + + * msgfmt.c: + Rewrite .po file handling. Use Peter Millers .po file Yacc grammar. + + * open-po.c: Remove handling of input path. + + * xgettext.c (write_out_domain): + Split #: lines each 80 columns. Based on a patch by + Peter Miller. + + * Makefile.in: hash.[ch] moved from src/ to lib/ subdirectory. + +Wed Aug 23 21:13:11 1995 Ulrich Drepper + + * tupdate.in: Don't print comment in front of obsolete entries. + +Tue Aug 22 22:16:31 1995 Ulrich Drepper + + * Makefile.in (AR, RANLIB): Remove definition. Not needed here. + Reported by François Pinard. + +Sat Aug 19 17:38:22 1995 Ulrich Drepper + + * Makefile.in (install-src): + Make behave like install. I.e. really install the catalogs. + +Sat Aug 19 00:57:07 1995 Ulrich Drepper + + * Makefile.in (SCRIPTS): + New variable. Contains names of scipts to be generated and + installed. For now it is tupdate. + (PROGRAMS): Remove tupdate. + (all): Also depend on $(SCRIPTS). + +Fri Aug 18 13:02:04 1995 Ulrich Drepper + + * Makefile.in (PROGRAMS): Add tupdate. + (tupdate): New rule. Rebuild tupdate if tupdate.in or + ../config.status changed. + + * tupdate.in: Correct case where message is new: no really print msgid. + Better help message by François Pinard. + Recognize #\t as comment. + Print comment for now obsolete entries. + Handle real comments (translator comments and tupdate generate obsolete + entries). + + * gettextp.c (usage): Better help message. + (usage): Add -s description to help screen. + +Mon Aug 14 23:50:48 1995 Ulrich Drepper + + * Makefile.in (install-src): New no-op goal. + +Thu Aug 10 11:26:45 1995 Ulrich Drepper + + * tupdate.in: Don't print two " in front of commented out msgstrs. + +Wed Aug 9 09:10:30 1995 Ulrich Drepper + + * hash.c: + Better implementation. Rehashing is now much faster because the + hashing value stored in the `used' field is reused. + (insert_entry): Split into two function. `insert_entry_2' now does the + the work while in `insert_entry' the checks are done. + (lookup_2): New function. Expects the search key to be NUL + terminated. This is the case when the key is already in the + hash table when rehashing. + + * msgfmt.c (write_table): + Third argument to `iterate_table' is now a `const' pointer. + + * hash.h (iterate_table): Third arguemtn is `const' pointer. + + * xgettext.c (struct id_str): Make fields `const' pointers. + +Fri Aug 4 22:45:39 1995 Ulrich Drepper + + * msgfmt.c (main): Fix typo: me -> we. + + * msgfmt.c (output_file_open): Remove this unused variable. + Reported by Jim Meyering. + + * Makefile.in (dist): Remove `copying instead' message. + + * gettextp.c: Start to implement non-Uniforum behaviour. + Implemented new mode where gettext behaves like `echo', + while translating the messages available in the specified test. + New option: -d, -e, -E, also available in normal mode: + -E: ignored. + -e: enable expansion of some escape sequences. + -d: specify text domain to use. + New option: -s: enable `echo' mode. + +Thu Aug 3 18:25:37 1995 Ulrich Drepper + + * msgfmt.c (usage): + Fix typo: anormalies -> anomalies. + Reported by Karl Anders O/ygard. + +Wed Aug 2 18:51:08 1995 Ulrich Drepper + + * Makefile.in (ID, TAGS): Do not use $^. + + * xgettext.c (write-header): Add `Content-Type' field. + +Tue Aug 1 20:07:58 1995 Ulrich Drepper + + * Makefile.in (distclean): Remove ID file. + + * Makefile.in (TAGS, ID): Use $^ as command argument. + (TAGS): Give etags -o option t write to current directory, + not $(srcdir). + (ID): Use $(srcdir) instead os $(top_srcdir)/src. + +Mon Jul 31 20:57:48 1995 Ulrich Drepper + + * msgfmt.c (process_po_file): + Quote msgstr in message "empty `msgstr' entry ignored". + Report by Karl Anders O/ygard. + +Sun Jul 30 12:14:29 1995 Ulrich Drepper + + * Makefile.in (LIBS): Always use ../intl/libintl.a. + (all): Always depend on ../intl/libintl.a. + +Tue Jul 25 00:15:01 1995 Ulrich Drepper + + * msgfmt.c (process_po_file): Correct problem with empty lines. + +Sun Jul 23 22:47:56 1995 Ulrich Drepper + + * msgfmt.c (process_po_file): + Give a message when a sole msgid is found at the end of file. + +Wed Jul 19 01:52:13 1995 Ulrich Drepper + + * Makefile.in (PROGRAMS): Always compile all three programs. + + * gettextp.c: Include libgettext.h explicitly, in addition to + libintl.h. On system having libintl.h provided by the C library + this assures to have the prototypes for the function defined in + GNU gettext library. + Use __bindtextdomain and __dgettext instead of bindtextdomain and + dgettext resp. + Swap arguments in bindtextdomain call. + +Tue Jul 18 23:57:16 1995 Ulrich Drepper + + * xgettext.c (add_id_str): + `free(msgstr)' can fail on system not allowing free(0). + Reported by Francesco Potorti`. + +Tue Jul 18 19:43:41 1995 Ulrich Drepper + + * xgettext.c (getpwuid): + Define prototype if !defined _POSIX_VERSION. + + * hash.c: Don't include malloc.h and string.h because it will be + done in system.h. + + * msgfmt.c: Don't include malloc.h because it will be done in + system.h. + +Sat Jul 15 00:45:31 1995 Ulrich Drepper + + * xgettext.c (main): + Disable line_comment option when omit_header is selected. + (write_out_domain): Don't write empty line if !line_comment. + + * Makefile.in (DISTFILES): + Due shorted file names now distribute tupdate.in. + (install, clean): Handle tupdate, not tupdate.perl. + + * tupdate.in: Rename tupdate.perl.in to tupdate.in to fit in 14 + character file systems. + +Thu Jul 13 22:21:22 1995 Ulrich Drepper + + * Makefile.in (install): Test whether Perl was found before + installing. + + * tupdate.perl.in: Make die message more GNU-like. + + * gettextp.c (usage): + Protect prototypes with __P and use K&R form for parameters. + + * xgettext.c (main): + Don't use 100u; poor K&R compilers need (unsigned) 100. + + * open-po.c (xstrdup): Protect prototype with __P. + + * msgfmt.c (usage, new_domain, process_po_file, compare_id, + write_table, check_pair): Protect prototypes with __P. + + * hash.c (xmalloc): Protect prototype with __P. + +Thu Jul 13 01:39:47 1995 Ulrich Drepper + + * Makefile.in (check): New no-op goal. + +Wed Jul 12 10:40:54 1995 Ulrich Drepper + + * tupdate.perl.in: Implement --help and --version options + + * xgettext.c: Add --string-limit option to specify limit on string + length. + Only warned when verbose mode is selected. + Add --verbose option. + Help message now correctly says --version == -V. + +Tue Jul 11 22:57:54 1995 Ulrich Drepper + + * xgettext.c (usage): Split help string because it breaks 1024 + byte limit. + + * Makefile.in (install-exec): Install tupdate. + + * xgettext.c (stdlib.h): Protect inclusion by STDC_HEADERS. + (assert): No assertions anymore. + (HAVE_STRTOUL): strtoul is now substituted when not available. + Add warning about too long strings (some systems have limits + for strings in their compiler and/or tools). E.g. Sinix's + gencat program. + + * msgfmt.c (printf.h): Include always + (assert): No assertions anymore (all bugs are gone :-). + (HAVE_STRTOUL): Not needed anymore because we substitute + strtoul if not present. + + * Makefile.in (DISTFILES,distclean): tupdate.perl is now found here. + +Tue Jul 11 01:31:03 1995 Ulrich Drepper + + * Makefile.in: msgfmt.o depends on ../lib/printf.h. + + * msgfmt.c: protect inclusion of malloc.h and stdlib.h. + Don't use GCC's `case b ... e:' feature (NeXT's gcc is + gcc-2.xx but does not understand this. Grrr!). + + * open-po.c: Protect inclusion of string.h and stdlib.h. + + * hash.c: Pretty print #define. + (init_hash, insert_entry): Cast result of calloc. + (insert_entry): Remove non-ANSI `(type *) var = ...' by + `*(type **) &var = ...'. + (compute_hashval): Cast constant to unsigned long (default: int). + Has effects on 64-bit machines. + +Tue Jul 4 00:39:58 1995 Ulrich Drepper + + * xgettext.c: Don't write "Version:" field for msgid "". + +Mon Jul 3 23:02:04 1995 Ulrich Drepper + + * xgettext.c, msgfmt.c: Better comment. + + * Makefile.in: Pretty print with François. + Fix typo in dependencies. + Even more dependency corrections. + Correct dependencies of open-po.c. + Remove unneeded $(srcdir) from Makefile.in dependency. + (LIBS): Correct for building in different directory. + (INCLUDES): Correct for building in different directory. + (DEFS): Rename DEF_MSG_DOM_DIR to LOCALEDIR. + (INCLUDE): Don't use -I paths when not needed. + + * hash.c: Include malloc.c and protect string.h inclusion. + + * gettextp.c: Protect include of stdlib.h declare prototype for + getenv if not __STDC__. + Include system.h for EXIT_FAILURE. + (main, usage): Replace DEF_MSG_DOM_DIR by LOCALEDIR. + + * open-po.c: Include system.h for EXIT_FAILURE. + + * msgfmt.c: Fix typo in !__STDC__ path. + (process_po_file): Change for new .po file format. + + * xgettext.c (main): Rename DEF_MSG_DOM_DIR to LOCALEDIR. + Update to new .po file format because Solaris' msgfmt can only + handle ANSI C style multi-line strings. + +Sun Jul 2 21:31:00 1995 Ulrich Drepper + + * gettextp.c: gettextp.c (usage): Fix typo in help message. + Reported by François Pinard. + +Sun Jul 2 02:12:41 1995 Ulrich Drepper + + * First official release. This directory contains the + source code for the programs specified in the Uniforum proposal + for internationalization. diff --git a/gettext-tools/src/FILES b/gettext-tools/src/FILES new file mode 100644 index 0000000..5fb474f --- /dev/null +++ b/gettext-tools/src/FILES @@ -0,0 +1,363 @@ + Short description of the source files + ===================================== + +The msg* and xgettext programs. + +Bottom-up structure: + +str-list.h +str-list.c + A list-of-immutable-strings type. + +dir-list.h +dir-list.c + Management of the list of directories where PO files are + searched. + +file-list.h +file-list.c + Reading a file list from a file. Used by those programs which + accept multiple file arguments and have a --files-from option. + +lang-table.h +lang-table.c + Language names according to ISO 639. + +pos.h + Source file positions. + +message.h +message.c + The message type, with many utility routines. + A list-of-messages type. + A list-of-lists-of-messages type. + +msgl-ascii.h +msgl-ascii.c + Message list test for ASCII character set. + +po-error.h +po-error.c +po-xerror.h +po-xerror.c + Error handling during writing and reading of PO files. + ++-------------- Writing PO files +| color.h +| color.c +| Color and style handling. +| write-catalog.h +| write-catalog.c +| Output of a list-of-messages. +| write-po.h +| write-po.c +| Output of a list-of-messages to a PO file. +| write-properties.h +| write-properties.c +| Output of a list-of-messages to a Java .properties file. +| write-stringtable.h +| write-stringtable.c +| Output of a list-of-messages to a NeXTstep/GNUstep .strings +| file. +| write-desktop.h +| write-desktop.c +| Output of a list-of-messages to a .desktop file. ++-------------- Writing PO files + ++-------------- Reading PO files +| open-catalog.h +| open-catalog.c +| Opening PO files for reading. +| +| po-charset.h +| po-charset.c +| Charset handling while reading PO files. +| +| po-lex.h +| po-lex.c +| Lexical analysis of PO files. +| +| read-catalog-abstract.h +| po-gram.h +| po-gram-gen.y +| read-po.h +| read-po.c +| read-properties.h +| read-properties.c +| read-stringtable.h +| read-stringtable.c +| read-catalog-abstract.c +| Parsing of PO files and Java .properties and NeXTstep/GNUstep +| .strings files. +| read-catalog-abstract.h +| General parser structure. +| po-gram.h +| po-gram-gen.y +| Parsing of PO files, based on po-lex.{h,c}. +| read-po.h +| read-po.c +| Parsing of PO files. +| read-properties.h +| read-properties.c +| Parsing of Java .properties files. +| read-stringtable.h +| read-stringtable.c +| Parsing of NeXTstep/GNUstep .strings files. +| read-catalog-abstract.c +| Top-level parser functions and callbacks. +| +| read-catalog.h +| read-catalog.c +| Reading of a PO file, returning a list-of-messages. +| +| read-desktop.h +| read-desktop.c +| Reading of a .desktop file, returning a list-of-messages. ++-------------- Reading PO files + +msgl-iconv.h +msgl-iconv.c + Convert a list-of-messages to another character encoding. + +msgl-cat.h +msgl-cat.c + Concatenate message lists from several files, with handling + of duplicate msgids. + +msgcmp.c Main source for the 'msgcmp' program. + ++-------------- The 'msgmerge' program +| msgl-equal.h +| msgl-equal.c +| Comparing two lists-of-messages. +| plural-count.h +| plural-count.c +| Extracting the plural count of a header of a lists-of-messages. +| msgmerge.c +| Main source for the 'msgmerge' program. +| ++-------------- The 'msgmerge' program + +msgl-header.h +msgl-header.c + Message list header manipulation. + +msgcomm.c Main source for the 'msgcomm' program. +msgattrib.c Main source for the 'msgattrib' program. +msgcat.c Main source for the 'msgcat' program. +msgconv.c Main source for the 'msgconv' program. +msguniq.c Main source for the 'msguniq' program. + +msgl-charset.h +msgl-charset.c + Compare the encoding of a list-of-messages with the locale + encoding. + +msgexec.c Main source for the 'msgexec' program. +msgfilter.c Main source for the 'msgfilter' program. +msggrep.c Main source for the 'msggrep' program. + ++-------------- The 'msgen' program +| msgl-english.h +| msgl-english.c +| English message initialization. +| msgen.c +| Main source for the 'msgen' program. +| ++-------------- The 'msgen' program + +po-time.h +po-time.c + Create time stamps for use in PO/POT files. + +plural-table.h +plural-table.c + Table of plural form formulas. + ++-------------- The 'msginit' program +| hostname.c +| The 'hostname' program. +| user-email.sh.in +| Determine the user's email address. +| urlget.c +| The 'urlget' program. +| project-id +| Determine the package's name. +| msginit.c +| Main source for the 'msginit' program. +| ++-------------- The 'msginit' program + ++-------------- The 'msgunfmt' program +| msgunfmt.h +| Declarations. +| read-mo.h +| read-mo.c +| Reading GNU .mo files. +| read-java.h +| read-java.c +| Reading Java ResourceBundle files. +| read-csharp.h +| read-csharp.c +| msgunfmt.cs +| Reading C# satellite assemblies. +| read-resources.h +| read-resources.c +| msgunfmt.cs +| Reading C# .resources files. +| read-tcl.h +| read-tcl.c +| msgunfmt.tcl +| Reading Tcl .msg files. +| msgunfmt.c +| Main source for the 'msgunfmt' program. +| ++-------------- The 'msgunfmt' program + +format.h Declarations of the language dependent format string handlers. +format-invalid.h Declarations of some error messages for invalid strings. +format-c.c Format string handling for C. +format-c-parse.h Format string handling for C, parsing routine. +format-sh.c Format string handling for Shell. +format-python.c Format string handling for Python. +format-python-brace.c Format string handling for Python, braced syntax. +format-lisp.c Format string handling for Common Lisp. +format-elisp.c Format string handling for Emacs Lisp. +format-librep.c Format string handling for librep. +format-scheme.c Format string handling for Scheme. +format-java.c Format string handling for Java. +format-csharp.c Format string handling for C#. +format-awk.c Format string handling for awk. +format-pascal.c Format string handling for Object Pascal. +format-ycp.c Format string handling for YCP. +format-tcl.c Format string handling for Tcl. +format-perl.c Format string handling for Perl. +format-perl-brace.c Format string handling for Perl, braced syntax. +format-php.c Format string handling for PHP. +format-gcc-internal.c Format string handling GCC internal. +format-gfc-internal.c Format string handling GFC internal. +format-qt.c Format string handling for Qt. +format-qt-plural.c Format string handling for Qt plural forms. +format-kde.c Format string handling for KDE. +format-boost.c Format string handling for Boost. +format-lua.c Format string handling for Lua. +format-javascript.c Format string handling for JavaScript. +format.c Table of the language dependent format string handlers. + +plural-exp.c + Parsing plural expressions. +plural-eval.h +plural-eval.c + Evaluating plural expressions. +plural-distrib.h + Value distribution of plural expressions. +msgl-check.h +msgl-check.c + Checking of messages. + ++-------------- The 'msgfmt' program +| msgfmt.h +| Declarations. +| write-mo.h +| write-mo.c +| Generating GNU .mo files. +| write-java.h +| write-java.c +| Generating Java ResourceBundle files. +| write-csharp.h +| write-csharp.c +| Generating C# satellite assemblies. +| write-resources.h +| write-resources.c +| msgfmt.cs +| Generating C# .resources files. +| write-tcl.h +| write-tcl.c +| Generating Tcl .msg files. +| write-qt.h +| write-qt.c +| Generating Qt .qm files. +| msgfmt.c +| Main source for the 'msgfmt' program. +| ++-------------- The 'msgfmt' program + ++-------------- The 'xgettext' program +| xgettext.h +| Declarations used by the backends. +| x-c.h +| x-c.c +| String extractor for C. +| x-po.h +| x-properties.h +| x-stringtable.h +| x-po.c +| String extractor from PO files and Java .properties and +| NeXTstep/GNUstep .strings files. +| x-sh.h +| x-sh.c +| String extractor for Shell. +| x-python.h +| x-python.c +| String extractor for Python. +| x-lisp.h +| x-lisp.c +| String extractor for Common Lisp. +| x-elisp.h +| x-elisp.c +| String extractor for Emacs Lisp. +| x-librep.h +| x-librep.c +| String extractor for librep. +| x-scheme.h +| x-scheme.c +| String extractor for Scheme. +| x-smalltalk.h +| x-smalltalk.c +| String extractor for Smalltalk. +| x-java.h +| x-java.c +| String extractor for Java. +| x-csharp.h +| x-csharp.c +| String extractor for C#. +| x-awk.h +| x-awk.c +| String extractor for awk. +| x-ycp.h +| x-ycp.c +| String extractor for YCP. +| x-tcl.h +| x-tcl.c +| String extractor for Tcl. +| x-perl.h +| x-perl.c +| String extractor for Perl. +| x-php.h +| x-php.c +| String extractor for PHP. +| x-rst.h +| x-rst.c +| String extractor from .rst files, for Object Pascal. +| x-glade.h +| x-glade.c +| String extractor from .glade files, GNOME GUI descriptions. +| x-lua.h +| x-lua.c +| String extractor for Lua. +| x-javascript.h +| x-javascript.c +| String extractor for JavaScript. +| x-vala.h +| x-vala.c +| String extractor for Vala. +| x-gsettings.h +| x-gsettings.c +| String extractor for GSettings schema file. +| x-desktop.h +| x-desktop.c +| String extractor from Desktop Entry file. +| xgettext.c +| Main source for the 'xgettext' program. +| ++-------------- The 'xgettext' program diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am new file mode 100644 index 0000000..9f2325f --- /dev/null +++ b/gettext-tools/src/Makefile.am @@ -0,0 +1,621 @@ +## Makefile for the gettext-tools/src subdirectory of GNU gettext +## Copyright (C) 1995-1998, 2000-2011 Free Software Foundation, Inc. +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . + +## Process this file with automake to produce Makefile.in. + +AUTOMAKE_OPTIONS = 1.5 gnits no-dependencies subdir-objects +EXTRA_DIST = +MOSTLYCLEANFILES = core *.stackdump +CLEANFILES = +DISTCLEANFILES = + +RM = rm -f + +bin_PROGRAMS = \ +msgcmp msgfmt msgmerge msgunfmt xgettext \ +msgattrib msgcat msgcomm msgconv msgen msgexec msgfilter msggrep msginit msguniq \ +recode-sr-latin + +noinst_PROGRAMS = hostname urlget + +lib_LTLIBRARIES = libgettextsrc.la + +noinst_HEADERS = pos.h message.h po-error.h po-xerror.h po-gram.h po-charset.h \ +po-lex.h open-catalog.h read-catalog-abstract.h read-catalog.h \ +read-po.h read-properties.h read-stringtable.h \ +str-list.h \ +color.h write-catalog.h write-po.h write-properties.h write-stringtable.h \ +dir-list.h file-list.h po-gram-gen.h po-gram-gen2.h \ +msgl-charset.h msgl-equal.h msgl-iconv.h msgl-ascii.h msgl-cat.h msgl-header.h \ +msgl-english.h msgl-check.h msgl-fsearch.h msgfmt.h msgunfmt.h \ +plural-count.h plural-eval.h plural-distrib.h \ +read-mo.h write-mo.h \ +read-java.h write-java.h \ +read-csharp.h write-csharp.h \ +read-resources.h write-resources.h \ +read-tcl.h write-tcl.h \ +write-qt.h \ +read-desktop.h write-desktop.h \ +po-time.h plural-table.h lang-table.h format.h filters.h \ +xgettext.h x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \ +x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \ +x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-lua.h \ +x-javascript.h x-vala.h x-gsettings.h x-desktop.h libexpat-compat.h + +EXTRA_DIST += FILES project-id ChangeLog.0 + +aliaspath = $(localedir) +jardir = $(datadir)/gettext +pkgdatadir = $(datadir)/gettext +projectsdir = $(pkgdatadir)/projects +pkglibdir = $(libdir)/gettext + +AM_CPPFLAGS = \ + -I. -I$(srcdir) \ + -I.. -I$(top_srcdir) \ + -I$(top_srcdir)/libgrep \ + -I../gnulib-lib -I$(top_srcdir)/gnulib-lib \ + -I../intl -I$(top_srcdir)/../gettext-runtime/intl +DEFS = \ + -DLOCALEDIR=\"$(localedir)\" -DBISON_LOCALEDIR=\"$(BISON_LOCALEDIR)\" \ + -DLOCALE_ALIAS_PATH=\"$(aliaspath)\" \ + -DUSEJAVA=$(USEJAVA) \ + -DUSEJEXE=$(USEJEXE) \ + -DGETTEXTJEXEDIR=\"$(pkglibdir)\" \ + -DGETTEXTJAR=\"$(jardir)/gettext.jar\" \ + -DLIBDIR=\"$(libdir)\" \ + -DGETTEXTDATADIR=\"$(pkgdatadir)\" \ + -DPROJECTSDIR=\"$(projectsdir)\" @DEFS@ +# Ensure that defines SIZE_MAX in C++ mode, like it does in C mode. +AM_CXXFLAGS = -D__STDC_LIMIT_MACROS + +LDADD = ../gnulib-lib/libgettextlib.la $(LTLIBUNISTRING) @LTLIBINTL@ $(WOE32_LDADD) +OTHERPROGDEPENDENCIES = ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) + +SED = sed +YACC = @YACC@ -d +GCJ = @GCJ@ +GCJFLAGS = @GCJFLAGS@ +JAR = @JAR@ +JAVACOMP = $(SHELL) ../javacomp.sh +CSHARPCOMP = $(SHELL) ../csharpcomp.sh +CSHARPCOMPFLAGS = @CSHARPCOMPFLAGS@ + + +# All programs deal with message lists. +# All programs must read PO files. (msgunfmt also, for read-java.c, +# read-csharp.c and read-resources.c.) +# message.c -> str-list.c. +# (read-catalog-abstract.c <--> po-gram-gen.y <--> po-lex.c) -> str-list.c. +# (read-catalog-abstract.c <--> po-gram-gen.y <--> po-lex.c) -> open-catalog.c -> dir-list.c -> str-list.c. +# (read-catalog-abstract.c <--> po-gram-gen.y <--> po-lex.c) -> po-charset.c. +# (read-catalog-abstract.c <--> po-gram-gen.y <--> po-lex.c) -> message.c -> str-list.c. +COMMON_SOURCE = message.c po-error.c po-xerror.c \ +read-catalog-abstract.c po-lex.c po-gram-gen.y po-charset.c \ +read-po.c read-properties.c read-stringtable.c open-catalog.c \ +dir-list.c str-list.c + +# xgettext and msgfmt deal with format strings. +if !WOE32DLL +FORMAT_SOURCE = format.c +else +FORMAT_SOURCE = ../woe32dll/c++format.cc +endif +FORMAT_SOURCE += \ + format-invalid.h \ + format-c.c format-c-parse.h \ + format-sh.c \ + format-python.c \ + format-python-brace.c \ + format-lisp.c \ + format-elisp.c \ + format-librep.c \ + format-scheme.c \ + format-java.c \ + format-csharp.c \ + format-awk.c \ + format-pascal.c \ + format-ycp.c \ + format-tcl.c \ + format-perl.c \ + format-perl-brace.c \ + format-php.c \ + format-gcc-internal.c \ + format-gfc-internal.c \ + format-qt.c \ + format-qt-plural.c \ + format-kde.c \ + format-boost.c \ + format-lua.c \ + format-javascript.c + +# libgettextsrc contains all code that is needed by at least two programs. +libgettextsrc_la_SOURCES = \ +$(COMMON_SOURCE) read-catalog.c \ +color.c write-catalog.c write-properties.c write-stringtable.c write-po.c \ +msgl-ascii.c msgl-iconv.c msgl-equal.c msgl-cat.c msgl-header.c msgl-english.c \ +msgl-check.c file-list.c msgl-charset.c po-time.c plural-exp.c plural-eval.c \ +plural-table.c \ +$(FORMAT_SOURCE) \ +read-desktop.c + +# msggrep needs pattern matching. +LIBGREP = ../libgrep/libgrep.a + +# Source dependencies. +msgcmp_SOURCES = msgcmp.c +msgcmp_SOURCES += msgl-fsearch.c +msgfmt_SOURCES = msgfmt.c +msgfmt_SOURCES += \ + write-mo.c write-java.c write-csharp.c write-resources.c write-tcl.c \ + write-qt.c write-desktop.c ../../gettext-runtime/intl/hash-string.c +if !WOE32DLL +msgmerge_SOURCES = msgmerge.c +else +msgmerge_SOURCES = ../woe32dll/c++msgmerge.cc +endif +msgmerge_SOURCES += msgl-fsearch.c lang-table.c plural-count.c +msgunfmt_SOURCES = msgunfmt.c +msgunfmt_SOURCES += \ + read-mo.c read-java.c read-csharp.c read-resources.c read-tcl.c +if !WOE32DLL +xgettext_SOURCES = xgettext.c +else +xgettext_SOURCES = ../woe32dll/c++xgettext.cc +endif +xgettext_SOURCES += \ + x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c x-scheme.c \ + x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ + x-rst.c x-glade.c x-lua.c x-javascript.c x-vala.c x-gsettings.c \ + libexpat-compat.c \ + x-desktop.c +if !WOE32DLL +msgattrib_SOURCES = msgattrib.c +else +msgattrib_SOURCES = ../woe32dll/c++msgattrib.cc +endif +if !WOE32DLL +msgcat_SOURCES = msgcat.c +else +msgcat_SOURCES = ../woe32dll/c++msgcat.cc +endif +if !WOE32DLL +msgcomm_SOURCES = msgcomm.c +else +msgcomm_SOURCES = ../woe32dll/c++msgcomm.cc +endif +if !WOE32DLL +msgconv_SOURCES = msgconv.c +else +msgconv_SOURCES = ../woe32dll/c++msgconv.cc +endif +if !WOE32DLL +msgen_SOURCES = msgen.c +else +msgen_SOURCES = ../woe32dll/c++msgen.cc +endif +msgexec_SOURCES = msgexec.c +if !WOE32DLL +msgfilter_SOURCES = msgfilter.c +else +msgfilter_SOURCES = ../woe32dll/c++msgfilter.cc +endif +msgfilter_SOURCES += filter-sr-latin.c +msgfilter_SOURCES += filter-quote.c +if !WOE32DLL +msggrep_SOURCES = msggrep.c +else +msggrep_SOURCES = ../woe32dll/c++msggrep.cc +endif +msginit_SOURCES = msginit.c +msginit_SOURCES += lang-table.c plural-count.c +msginit_SOURCES += ../../gettext-runtime/intl/localealias.c +# This is needed because on Solaris, localealias.c requires the symbol +# libintl_thread_in_use which is defined in lock.c. The copy of lock.c inside +# libintl.so is not sufficient, because libintl.so doesn't export the symbol +# libintl_thread_in_use. +msginit_SOURCES += ../../gettext-runtime/intl/lock.c +if !WOE32DLL +msguniq_SOURCES = msguniq.c +else +msguniq_SOURCES = ../woe32dll/c++msguniq.cc +endif +recode_sr_latin_SOURCES = recode-sr-latin.c filter-sr-latin.c +hostname_SOURCES = hostname.c +urlget_SOURCES = urlget.c + +# How to build libgettextsrc.la. +# Need ../gnulib-lib/libgettextlib.la. +# Need $(LTLIBUNISTRING) because ulc_width_linebreaks, uc_width, etc. may be +# taken from libunistring, when the configure option --with-libunistring-prefix +# was given. +# Need @LTLIBINTL@ because many source files use gettext(). +# Need @LTLIBICONV@ because po-charset.c, po-lex.c, msgl-iconv.c, write-po.c +# use iconv(). +libgettextsrc_la_LDFLAGS = \ + -release @VERSION@ \ + ../gnulib-lib/libgettextlib.la $(LTLIBUNISTRING) @LTLIBINTL@ @LTLIBICONV@ -lc -no-undefined + +libgettextsrc_la_CPPFLAGS = $(AM_CPPFLAGS) + +# Tell the mingw or Cygwin linker which symbols to export. +if WOE32DLL +libgettextsrc_la_SOURCES += ../woe32dll/gettextsrc-exports.c +libgettextsrc_la_LDFLAGS += -Wl,--export-all-symbols +libgettextsrc_la_CPPFLAGS += $(GETTEXTLIB_EXPORTS_FLAGS) +endif + +# No need to install libgettextsrc.a, except on AIX. +install-exec-hook: install-exec-clean +install-exec-clean: + case "@host_os@" in \ + aix*) ;; \ + *) $(RM) $(DESTDIR)$(libdir)/libgettextsrc.a ;; \ + esac + +# Compile-time flags for particular source files. +msgmerge_CFLAGS = $(AM_CFLAGS) $(OPENMP_CFLAGS) +msgmerge_CXXFLAGS = $(AM_CXXFLAGS) $(OPENMP_CFLAGS) + +# Link dependencies. +# INTL_MACOSX_LIBS is needed because the programs depend on libintl.la +# but libtool doesn't put -Wl,-framework options into .la files. +# For msginit, it is also needed because of localename.c. +msgcmp_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ @MSGMERGE_LIBM@ $(WOE32_LDADD) +msgfmt_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgmerge_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ @MSGMERGE_LIBM@ $(WOE32_LDADD) $(OPENMP_CFLAGS) +msgunfmt_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +xgettext_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ @LTLIBEXPAT@ @LTLIBICONV@ $(WOE32_LDADD) +msgattrib_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgcat_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgcomm_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgconv_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgen_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgexec_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgfilter_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msggrep_LDADD = $(LIBGREP) libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msginit_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msguniq_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) + +# Specify when to relink the programs. +msgcmp_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgfmt_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgmerge_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgunfmt_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +xgettext_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgattrib_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgcat_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgcomm_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgconv_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgen_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgexec_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgfilter_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msggrep_DEPENDENCIES = $(LIBGREP) libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msginit_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msguniq_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +recode_sr_latin_DEPENDENCIES = $(OTHERPROGDEPENDENCIES) +hostname_DEPENDENCIES = $(OTHERPROGDEPENDENCIES) +urlget_DEPENDENCIES = $(OTHERPROGDEPENDENCIES) + +# Specify installation directory, for --enable-relocatable. +msgcmp_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgfmt_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgmerge_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgunfmt_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +xgettext_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgattrib_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgcat_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgcomm_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgconv_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgen_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgexec_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgfilter_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msggrep_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msginit_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msguniq_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +recode_sr_latin_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +hostname_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(pkglibdir)\" +urlget_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(pkglibdir)\" +if RELOCATABLE_VIA_LD +msgcmp_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgfmt_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgmerge_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgunfmt_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +xgettext_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgattrib_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgcat_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgcomm_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgconv_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgen_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgexec_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msgfilter_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msggrep_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msginit_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +msguniq_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +recode_sr_latin_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +hostname_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(pkglibdir)` +urlget_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(pkglibdir)` +endif + +# Linking with C++ libraries is needed _only_ on mingw and Cygwin. +if !WOE32DLL +libgettextsrc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(libgettextsrc_la_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgattrib_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgattrib_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgcat_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgcat_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgcomm_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgcomm_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgconv_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgconv_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgen_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgen_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgfilter_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgfilter_LDFLAGS) $(LDFLAGS) \ + -o $@ +msggrep_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msggrep_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgmerge_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(msgmerge_CFLAGS) $(CFLAGS) $(msgmerge_LDFLAGS) $(LDFLAGS) \ + -o $@ +msguniq_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msguniq_LDFLAGS) $(LDFLAGS) \ + -o $@ +xgettext_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(xgettext_LDFLAGS) $(LDFLAGS) \ + -o $@ +else +libgettextsrc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(libgettextsrc_la_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgattrib_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgattrib_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgcat_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgcat_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgcomm_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgcomm_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgconv_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgconv_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgen_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgen_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgfilter_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgfilter_LDFLAGS) $(LDFLAGS) \ + -o $@ +msggrep_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msggrep_LDFLAGS) $(LDFLAGS) \ + -o $@ +msgmerge_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(msgmerge_CXXFLAGS) $(CXXFLAGS) $(msgmerge_LDFLAGS) $(LDFLAGS) \ + -o $@ +msguniq_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msguniq_LDFLAGS) $(LDFLAGS) \ + -o $@ +xgettext_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ + $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(xgettext_LDFLAGS) $(LDFLAGS) \ + -o $@ +endif + +# Special rules for bison and flex generated files. + +BUILT_SOURCES = \ + po-gram-gen.c po-gram-gen.h po-gram-gen2.h + +po-lex.o po-lex.lo: po-gram-gen2.h +po-gram-gen2.h: po-gram-gen.h + srcdir=''; \ + test -f ./po-gram-gen.h || srcdir=$(srcdir)/; \ + $(SED) -e 's/yy/po_gram_/g' -e 's/extern /extern DLL_VARIABLE /' \ + $${srcdir}po-gram-gen.h > $@-tmp && \ + mv $@-tmp $@ + + +# Special rules for installation of auxiliary programs. + +install-exec-local: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) hostname$(EXEEXT) $(DESTDIR)$(pkglibdir)/hostname$(EXEEXT) + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) urlget$(EXEEXT) $(DESTDIR)$(pkglibdir)/urlget$(EXEEXT) + $(INSTALL_SCRIPT) user-email $(DESTDIR)$(pkglibdir)/user-email + $(INSTALL_SCRIPT) $(srcdir)/project-id $(DESTDIR)$(pkglibdir)/project-id + +installdirs-local: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + +uninstall-local: + $(RM) $(DESTDIR)$(pkglibdir)/hostname$(EXEEXT) + $(RM) $(DESTDIR)$(pkglibdir)/urlget$(EXEEXT) + $(RM) $(DESTDIR)$(pkglibdir)/user-email + $(RM) $(DESTDIR)$(pkglibdir)/project-id + +DISTCLEANFILES += user-email + + +# Special rules for Java compilation. + +USEJAVA = $(USEJAVA_@BUILDJAVAEXE@) +USEJAVA_yes = 1 +USEJAVA_no = $(USEJAVA_no_@BUILDJAVA@) +USEJAVA_no_yes = 1 +USEJAVA_no_no = 0 + +USEJEXE = $(USEJEXE_@BUILDJAVAEXE@) +USEJEXE_yes = 1 +USEJEXE_no = 0 + +all-local: all-java-@BUILDJAVAEXE@ +all-java-yes: gnu.gettext.DumpResource$(EXEEXT) gnu.gettext.GetURL$(EXEEXT) +all-java-no: all-java-no-@BUILDJAVA@ +all-java-no-yes: gettext.jar +all-java-no-no: + +gnu.gettext.DumpResource$(EXEEXT): $(srcdir)/gnu/gettext/DumpResource.java + $(GCJ) $(GCJFLAGS) $(srcdir)/gnu/gettext/DumpResource.java --main=gnu.gettext.DumpResource -o $@ + +gnu.gettext.GetURL$(EXEEXT): $(srcdir)/gnu/gettext/GetURL.java + $(GCJ) $(GCJFLAGS) $(srcdir)/gnu/gettext/GetURL.java --main=gnu.gettext.GetURL -o $@ + +gnu/gettext/DumpResource.class: $(srcdir)/gnu/gettext/DumpResource.java + $(JAVACOMP) -d . $(srcdir)/gnu/gettext/DumpResource.java + +gnu/gettext/GetURL.class: $(srcdir)/gnu/gettext/GetURL.java + $(JAVACOMP) -d . $(srcdir)/gnu/gettext/GetURL.java + +gettext.jar: gnu/gettext/DumpResource.class gnu/gettext/GetURL.class + $(JAR) cf $@ gnu/gettext/DumpResource*.class gnu/gettext/GetURL*.class + +EXTRA_DIST += gnu/gettext/DumpResource.java gnu/gettext/GetURL.java + +CLEANFILES += gnu.gettext.DumpResource$(EXEEXT) gnu.gettext.GetURL$(EXEEXT) \ + gettext.jar gnu/gettext/*.class + +install-exec-local: install-exec-java-@BUILDJAVAEXE@ +install-exec-java-yes: all-java-yes + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) gnu.gettext.DumpResource$(EXEEXT) $(DESTDIR)$(pkglibdir)/gnu.gettext.DumpResource$(EXEEXT) + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) gnu.gettext.GetURL$(EXEEXT) $(DESTDIR)$(pkglibdir)/gnu.gettext.GetURL$(EXEEXT) +install-exec-java-no: + +install-data-local: install-data-java-@BUILDJAVAEXE@ +install-data-java-yes: +install-data-java-no: install-data-java-no-@BUILDJAVA@ +install-data-java-no-yes: all-java-no-yes + $(INSTALL_DATA) gettext.jar $(DESTDIR)$(jardir)/gettext.jar +install-data-java-no-no: + +installdirs-local: installdirs-java-@BUILDJAVAEXE@ +installdirs-java-yes: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) +installdirs-java-no: installdirs-java-no-@BUILDJAVA@ +installdirs-java-no-yes: + $(MKDIR_P) $(DESTDIR)$(jardir) +installdirs-java-no-no: + +uninstall-local: uninstall-java-@BUILDJAVAEXE@ +uninstall-java-yes: + $(RM) $(DESTDIR)$(pkglibdir)/gnu.gettext.DumpResource$(EXEEXT) + $(RM) $(DESTDIR)$(pkglibdir)/gnu.gettext.GetURL$(EXEEXT) +uninstall-java-no: uninstall-java-no-@BUILDJAVA@ +uninstall-java-no-yes: + $(RM) $(DESTDIR)$(jardir)/gettext.jar +uninstall-java-no-no: + + +# Special rules for C# auxiliary programs. + +EXTRA_DIST += msgfmt.cs msgunfmt.cs + +CLEANFILES += \ + msgfmt.net.exe msgfmt.net.exe.mdb \ + msgunfmt.net.exe msgunfmt.net.exe.mdb + +all-local: all-csharp-@BUILDCSHARP@ +all-csharp-yes: msgfmt.net.exe msgunfmt.net.exe +all-csharp-no: + +msgfmt.net.exe: msgfmt.cs + $(CSHARPCOMP) $(CSHARPCOMPFLAGS) -o $@ $(srcdir)/msgfmt.cs + +msgunfmt.net.exe: msgunfmt.cs + $(CSHARPCOMP) $(CSHARPCOMPFLAGS) -o $@ -L ../../gettext-runtime/intl-csharp -l GNU.Gettext $(srcdir)/msgunfmt.cs + +install-exec-local: install-exec-csharp-@BUILDCSHARP@ +install-exec-csharp-yes: all-csharp-yes + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + $(INSTALL_DATA) msgfmt.net.exe $(DESTDIR)$(pkglibdir)/msgfmt.net.exe + $(INSTALL_DATA) msgunfmt.net.exe $(DESTDIR)$(pkglibdir)/msgunfmt.net.exe +install-exec-csharp-no: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + +installdirs-local: install-csharp +installdirs-csharp: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + +uninstall-local: uninstall-csharp-@BUILDCSHARP@ +uninstall-csharp-yes: all-csharp-yes + $(RM) $(DESTDIR)$(pkglibdir)/msgfmt.net.exe + $(RM) $(DESTDIR)$(pkglibdir)/msgunfmt.net.exe +uninstall-csharp-no: + + +# Special rules for Tcl auxiliary program. + +EXTRA_DIST += msgunfmt.tcl + +install-data-local: install-tcl +install-tcl: + $(MKDIR_P) $(DESTDIR)$(pkgdatadir) + $(INSTALL_DATA) $(srcdir)/msgunfmt.tcl $(DESTDIR)$(pkgdatadir)/msgunfmt.tcl + +installdirs-local: installdirs-tcl +installdirs-tcl: + $(MKDIR_P) $(DESTDIR)$(pkgdatadir) + +uninstall-local: uninstall-tcl +uninstall-tcl: + $(RM) $(DESTDIR)$(pkgdatadir)/msgunfmt.tcl + + +# Support for relocatability. +RELOCATABLE_LIBRARY_PATH = $(libdir) +RELOCATABLE_SRC_DIR = $(top_srcdir)/gnulib-lib +RELOCATABLE_BUILD_DIR = ../gnulib-lib +RELOCATABLE_CONFIG_H_DIR = .. +RELOCATABLE_STRIP = : + +# Version information according to Woe32 conventions. +if WOE32 +WOE32_LDADD = gettext.res +gettext.res : $(top_srcdir)/../windows/gettext.rc + $(WINDRES) `$(SHELL) $(top_srcdir)/../windows/windres-options --escape $(VERSION)` -i $(top_srcdir)/../windows/gettext.rc -o gettext.res --output-format=coff +MOSTLYCLEANFILES += gettext.res +else +WOE32_LDADD = +endif diff --git a/gettext-tools/src/Makefile.in b/gettext-tools/src/Makefile.in new file mode 100644 index 0000000..4e29138 --- /dev/null +++ b/gettext-tools/src/Makefile.in @@ -0,0 +1,3511 @@ +# Makefile.in generated by automake 1.14.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgincludedir = $(includedir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = msgcmp$(EXEEXT) msgfmt$(EXEEXT) msgmerge$(EXEEXT) \ + msgunfmt$(EXEEXT) xgettext$(EXEEXT) msgattrib$(EXEEXT) \ + msgcat$(EXEEXT) msgcomm$(EXEEXT) msgconv$(EXEEXT) \ + msgen$(EXEEXT) msgexec$(EXEEXT) msgfilter$(EXEEXT) \ + msggrep$(EXEEXT) msginit$(EXEEXT) msguniq$(EXEEXT) \ + recode-sr-latin$(EXEEXT) +noinst_PROGRAMS = hostname$(EXEEXT) urlget$(EXEEXT) + +# Tell the mingw or Cygwin linker which symbols to export. +@WOE32DLL_TRUE@am__append_1 = ../woe32dll/gettextsrc-exports.c +@WOE32DLL_TRUE@am__append_2 = -Wl,--export-all-symbols +@WOE32DLL_TRUE@am__append_3 = $(GETTEXTLIB_EXPORTS_FLAGS) +@WOE32_TRUE@am__append_4 = gettext.res +subdir = src +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(top_srcdir)/../build-aux/mkinstalldirs \ + $(srcdir)/user-email.sh.in po-gram-gen.c \ + $(top_srcdir)/../build-aux/ylwrap $(noinst_HEADERS) ChangeLog +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = \ + $(top_srcdir)/libgettextpo/gnulib-m4/gnulib-comp.m4 \ + $(top_srcdir)/libgrep/gnulib-m4/gnulib-comp.m4 \ + $(top_srcdir)/libgrep/gnulib-m4/langinfo_h.m4 \ + $(top_srcdir)/libgrep/gnulib-m4/localeconv.m4 \ + $(top_srcdir)/libgrep/gnulib-m4/mbrlen.m4 \ + $(top_srcdir)/libgrep/gnulib-m4/nl_langinfo.m4 \ + $(top_srcdir)/libgrep/gnulib-m4/regex.m4 \ + $(top_srcdir)/gnulib-m4/00gnulib.m4 \ + $(top_srcdir)/gnulib-m4/absolute-header.m4 \ + $(top_srcdir)/gnulib-m4/acl.m4 \ + $(top_srcdir)/gnulib-m4/alloca.m4 \ + $(top_srcdir)/gnulib-m4/ansi-c++.m4 \ + $(top_srcdir)/gnulib-m4/asm-underscore.m4 \ + $(top_srcdir)/gnulib-m4/atexit.m4 \ + $(top_srcdir)/gnulib-m4/backupfile.m4 \ + $(top_srcdir)/gnulib-m4/bison-i18n.m4 \ + $(top_srcdir)/gnulib-m4/btowc.m4 \ + $(top_srcdir)/gnulib-m4/byteswap.m4 \ + $(top_srcdir)/gnulib-m4/canonicalize.m4 \ + $(top_srcdir)/gnulib-m4/close.m4 \ + $(top_srcdir)/gnulib-m4/closedir.m4 \ + $(top_srcdir)/gnulib-m4/configmake.m4 \ + $(top_srcdir)/gnulib-m4/copy-file.m4 \ + $(top_srcdir)/gnulib-m4/csharp.m4 \ + $(top_srcdir)/gnulib-m4/csharpcomp.m4 \ + $(top_srcdir)/gnulib-m4/csharpexec.m4 \ + $(top_srcdir)/gnulib-m4/curses.m4 \ + $(top_srcdir)/gnulib-m4/dirent_h.m4 \ + $(top_srcdir)/gnulib-m4/double-slash-root.m4 \ + $(top_srcdir)/gnulib-m4/dup.m4 $(top_srcdir)/gnulib-m4/dup2.m4 \ + $(top_srcdir)/gnulib-m4/eaccess.m4 \ + $(top_srcdir)/gnulib-m4/eealloc.m4 \ + $(top_srcdir)/gnulib-m4/environ.m4 \ + $(top_srcdir)/gnulib-m4/errno_h.m4 \ + $(top_srcdir)/gnulib-m4/error.m4 \ + $(top_srcdir)/gnulib-m4/execute.m4 \ + $(top_srcdir)/gnulib-m4/exponentd.m4 \ + $(top_srcdir)/gnulib-m4/extensions.m4 \ + $(top_srcdir)/gnulib-m4/fatal-signal.m4 \ + $(top_srcdir)/gnulib-m4/fcntl.m4 \ + $(top_srcdir)/gnulib-m4/fcntl_h.m4 \ + $(top_srcdir)/gnulib-m4/fdopen.m4 \ + $(top_srcdir)/gnulib-m4/findprog.m4 \ + $(top_srcdir)/gnulib-m4/float_h.m4 \ + $(top_srcdir)/gnulib-m4/fnmatch.m4 \ + $(top_srcdir)/gnulib-m4/fopen.m4 \ + $(top_srcdir)/gnulib-m4/fpieee.m4 \ + $(top_srcdir)/gnulib-m4/fseeko.m4 \ + $(top_srcdir)/gnulib-m4/fstat.m4 \ + $(top_srcdir)/gnulib-m4/ftell.m4 \ + $(top_srcdir)/gnulib-m4/ftello.m4 \ + $(top_srcdir)/gnulib-m4/gcj.m4 \ + $(top_srcdir)/gnulib-m4/getcwd.m4 \ + $(top_srcdir)/gnulib-m4/getdelim.m4 \ + $(top_srcdir)/gnulib-m4/getdtablesize.m4 \ + $(top_srcdir)/gnulib-m4/getline.m4 \ + $(top_srcdir)/gnulib-m4/getopt.m4 \ + $(top_srcdir)/gnulib-m4/getpagesize.m4 \ + $(top_srcdir)/gnulib-m4/gettimeofday.m4 \ + $(top_srcdir)/gnulib-m4/gnulib-common.m4 \ + $(top_srcdir)/gnulib-m4/gnulib-comp.m4 \ + $(top_srcdir)/gnulib-m4/iconv_h.m4 \ + $(top_srcdir)/gnulib-m4/iconv_open.m4 \ + $(top_srcdir)/gnulib-m4/include_next.m4 \ + $(top_srcdir)/gnulib-m4/inline.m4 \ + $(top_srcdir)/gnulib-m4/intmax_t.m4 \ + $(top_srcdir)/gnulib-m4/inttypes.m4 \ + $(top_srcdir)/gnulib-m4/iswblank.m4 \ + $(top_srcdir)/gnulib-m4/java.m4 \ + $(top_srcdir)/gnulib-m4/javacomp.m4 \ + $(top_srcdir)/gnulib-m4/javaexec.m4 \ + $(top_srcdir)/gnulib-m4/largefile.m4 \ + $(top_srcdir)/gnulib-m4/lib-ld.m4 \ + $(top_srcdir)/gnulib-m4/lib-link.m4 \ + $(top_srcdir)/gnulib-m4/lib-prefix.m4 \ + $(top_srcdir)/gnulib-m4/libcroco.m4 \ + $(top_srcdir)/gnulib-m4/libglib.m4 \ + $(top_srcdir)/gnulib-m4/libunistring-base.m4 \ + $(top_srcdir)/gnulib-m4/libunistring-optional.m4 \ + $(top_srcdir)/gnulib-m4/libunistring.m4 \ + $(top_srcdir)/gnulib-m4/libxml.m4 \ + $(top_srcdir)/gnulib-m4/localcharset.m4 \ + $(top_srcdir)/gnulib-m4/locale-fr.m4 \ + $(top_srcdir)/gnulib-m4/locale-ja.m4 \ + $(top_srcdir)/gnulib-m4/locale-tr.m4 \ + $(top_srcdir)/gnulib-m4/locale-zh.m4 \ + $(top_srcdir)/gnulib-m4/locale_h.m4 \ + $(top_srcdir)/gnulib-m4/localename.m4 \ + $(top_srcdir)/gnulib-m4/lseek.m4 \ + $(top_srcdir)/gnulib-m4/lstat.m4 \ + $(top_srcdir)/gnulib-m4/malloc.m4 \ + $(top_srcdir)/gnulib-m4/malloca.m4 \ + $(top_srcdir)/gnulib-m4/mbchar.m4 \ + $(top_srcdir)/gnulib-m4/mbiter.m4 \ + $(top_srcdir)/gnulib-m4/mbrtowc.m4 \ + $(top_srcdir)/gnulib-m4/mbsinit.m4 \ + $(top_srcdir)/gnulib-m4/mbslen.m4 \ + $(top_srcdir)/gnulib-m4/mbsrtowcs.m4 \ + $(top_srcdir)/gnulib-m4/mbstate_t.m4 \ + $(top_srcdir)/gnulib-m4/mbswidth.m4 \ + $(top_srcdir)/gnulib-m4/mbtowc.m4 \ + $(top_srcdir)/gnulib-m4/memchr.m4 \ + $(top_srcdir)/gnulib-m4/memmove.m4 \ + $(top_srcdir)/gnulib-m4/memset.m4 \ + $(top_srcdir)/gnulib-m4/minmax.m4 \ + $(top_srcdir)/gnulib-m4/mkdtemp.m4 \ + $(top_srcdir)/gnulib-m4/mmap-anon.m4 \ + $(top_srcdir)/gnulib-m4/mode_t.m4 \ + $(top_srcdir)/gnulib-m4/moo.m4 \ + $(top_srcdir)/gnulib-m4/msvc-inval.m4 \ + $(top_srcdir)/gnulib-m4/msvc-nothrow.m4 \ + $(top_srcdir)/gnulib-m4/multiarch.m4 \ + $(top_srcdir)/gnulib-m4/no-c++.m4 \ + $(top_srcdir)/gnulib-m4/nocrash.m4 \ + $(top_srcdir)/gnulib-m4/obstack.m4 \ + $(top_srcdir)/gnulib-m4/off_t.m4 \ + $(top_srcdir)/gnulib-m4/open.m4 \ + $(top_srcdir)/gnulib-m4/opendir.m4 \ + $(top_srcdir)/gnulib-m4/pathmax.m4 \ + $(top_srcdir)/gnulib-m4/pipe2.m4 \ + $(top_srcdir)/gnulib-m4/posix_spawn.m4 \ + $(top_srcdir)/gnulib-m4/printf.m4 \ + $(top_srcdir)/gnulib-m4/putenv.m4 \ + $(top_srcdir)/gnulib-m4/quote.m4 \ + $(top_srcdir)/gnulib-m4/quotearg.m4 \ + $(top_srcdir)/gnulib-m4/raise.m4 \ + $(top_srcdir)/gnulib-m4/rawmemchr.m4 \ + $(top_srcdir)/gnulib-m4/read-file.m4 \ + $(top_srcdir)/gnulib-m4/read.m4 \ + $(top_srcdir)/gnulib-m4/readdir.m4 \ + $(top_srcdir)/gnulib-m4/readlink.m4 \ + $(top_srcdir)/gnulib-m4/realloc.m4 \ + $(top_srcdir)/gnulib-m4/relocatable-lib.m4 \ + $(top_srcdir)/gnulib-m4/relocatable.m4 \ + $(top_srcdir)/gnulib-m4/rmdir.m4 \ + $(top_srcdir)/gnulib-m4/safe-read.m4 \ + $(top_srcdir)/gnulib-m4/safe-write.m4 \ + $(top_srcdir)/gnulib-m4/sched_h.m4 \ + $(top_srcdir)/gnulib-m4/secure_getenv.m4 \ + $(top_srcdir)/gnulib-m4/setenv.m4 \ + $(top_srcdir)/gnulib-m4/setlocale.m4 \ + $(top_srcdir)/gnulib-m4/sig_atomic_t.m4 \ + $(top_srcdir)/gnulib-m4/sigaction.m4 \ + $(top_srcdir)/gnulib-m4/signal_h.m4 \ + $(top_srcdir)/gnulib-m4/signalblocking.m4 \ + $(top_srcdir)/gnulib-m4/sigpipe.m4 \ + $(top_srcdir)/gnulib-m4/sleep.m4 \ + $(top_srcdir)/gnulib-m4/snprintf.m4 \ + $(top_srcdir)/gnulib-m4/spawn-pipe.m4 \ + $(top_srcdir)/gnulib-m4/spawn_h.m4 \ + $(top_srcdir)/gnulib-m4/ssize_t.m4 \ + $(top_srcdir)/gnulib-m4/stat.m4 \ + $(top_srcdir)/gnulib-m4/stdarg.m4 \ + $(top_srcdir)/gnulib-m4/stdbool.m4 \ + $(top_srcdir)/gnulib-m4/stddef_h.m4 \ + $(top_srcdir)/gnulib-m4/stdint.m4 \ + $(top_srcdir)/gnulib-m4/stdio_h.m4 \ + $(top_srcdir)/gnulib-m4/stdlib_h.m4 \ + $(top_srcdir)/gnulib-m4/stpcpy.m4 \ + $(top_srcdir)/gnulib-m4/stpncpy.m4 \ + $(top_srcdir)/gnulib-m4/strchrnul.m4 \ + $(top_srcdir)/gnulib-m4/strcspn.m4 \ + $(top_srcdir)/gnulib-m4/strerror.m4 \ + $(top_srcdir)/gnulib-m4/string_h.m4 \ + $(top_srcdir)/gnulib-m4/strnlen.m4 \ + $(top_srcdir)/gnulib-m4/strpbrk.m4 \ + $(top_srcdir)/gnulib-m4/strstr.m4 \ + $(top_srcdir)/gnulib-m4/strtol.m4 \ + $(top_srcdir)/gnulib-m4/strtoul.m4 \ + $(top_srcdir)/gnulib-m4/symlink.m4 \ + $(top_srcdir)/gnulib-m4/sys_select_h.m4 \ + $(top_srcdir)/gnulib-m4/sys_socket_h.m4 \ + $(top_srcdir)/gnulib-m4/sys_stat_h.m4 \ + $(top_srcdir)/gnulib-m4/sys_time_h.m4 \ + $(top_srcdir)/gnulib-m4/sys_types_h.m4 \ + $(top_srcdir)/gnulib-m4/sys_wait_h.m4 \ + $(top_srcdir)/gnulib-m4/tempname.m4 \ + $(top_srcdir)/gnulib-m4/term-ostream.m4 \ + $(top_srcdir)/gnulib-m4/terminfo.m4 \ + $(top_srcdir)/gnulib-m4/thread.m4 \ + $(top_srcdir)/gnulib-m4/time_h.m4 \ + $(top_srcdir)/gnulib-m4/tls.m4 \ + $(top_srcdir)/gnulib-m4/tmpdir.m4 \ + $(top_srcdir)/gnulib-m4/ungetc.m4 \ + $(top_srcdir)/gnulib-m4/unionwait.m4 \ + $(top_srcdir)/gnulib-m4/unistd-safer.m4 \ + $(top_srcdir)/gnulib-m4/unistd_h.m4 \ + $(top_srcdir)/gnulib-m4/unlocked-io.m4 \ + $(top_srcdir)/gnulib-m4/vasnprintf.m4 \ + $(top_srcdir)/gnulib-m4/vasprintf.m4 \ + $(top_srcdir)/gnulib-m4/vsnprintf.m4 \ + $(top_srcdir)/gnulib-m4/wait-process.m4 \ + $(top_srcdir)/gnulib-m4/waitpid.m4 \ + $(top_srcdir)/gnulib-m4/warn-on-use.m4 \ + $(top_srcdir)/gnulib-m4/wchar_h.m4 \ + $(top_srcdir)/gnulib-m4/wcrtomb.m4 \ + $(top_srcdir)/gnulib-m4/wctob.m4 \ + $(top_srcdir)/gnulib-m4/wctomb.m4 \ + $(top_srcdir)/gnulib-m4/wctype_h.m4 \ + $(top_srcdir)/gnulib-m4/wcwidth.m4 \ + $(top_srcdir)/gnulib-m4/write.m4 \ + $(top_srcdir)/gnulib-m4/xvasprintf.m4 \ + $(top_srcdir)/gnulib-m4/yield.m4 \ + $(top_srcdir)/../m4/fixautomake.m4 \ + $(top_srcdir)/../m4/libtool.m4 \ + $(top_srcdir)/../m4/ltoptions.m4 \ + $(top_srcdir)/../m4/ltsugar.m4 \ + $(top_srcdir)/../m4/ltversion.m4 \ + $(top_srcdir)/../m4/lt~obsolete.m4 \ + $(top_srcdir)/../m4/woe32-dll.m4 \ + $(top_srcdir)/../gettext-runtime/m4/codeset.m4 \ + $(top_srcdir)/../gettext-runtime/m4/extern-inline.m4 \ + $(top_srcdir)/../gettext-runtime/m4/fcntl-o.m4 \ + $(top_srcdir)/../gettext-runtime/m4/gettext.m4 \ + $(top_srcdir)/../gettext-runtime/m4/glibc2.m4 \ + $(top_srcdir)/../gettext-runtime/m4/glibc21.m4 \ + $(top_srcdir)/../gettext-runtime/m4/iconv.m4 \ + $(top_srcdir)/../gettext-runtime/m4/intdiv0.m4 \ + $(top_srcdir)/../gettext-runtime/m4/intl.m4 \ + $(top_srcdir)/../gettext-runtime/m4/intlmacosx.m4 \ + $(top_srcdir)/../gettext-runtime/m4/intmax.m4 \ + $(top_srcdir)/../gettext-runtime/m4/inttypes-pri.m4 \ + $(top_srcdir)/../gettext-runtime/m4/inttypes_h.m4 \ + $(top_srcdir)/../gettext-runtime/m4/lcmessage.m4 \ + $(top_srcdir)/../gettext-runtime/m4/lock.m4 \ + $(top_srcdir)/../gettext-runtime/m4/longlong.m4 \ + $(top_srcdir)/../gettext-runtime/m4/nls.m4 \ + $(top_srcdir)/../gettext-runtime/m4/po.m4 \ + $(top_srcdir)/../gettext-runtime/m4/printf-posix.m4 \ + $(top_srcdir)/../gettext-runtime/m4/progtest.m4 \ + $(top_srcdir)/../gettext-runtime/m4/size_max.m4 \ + $(top_srcdir)/../gettext-runtime/m4/stdint_h.m4 \ + $(top_srcdir)/../gettext-runtime/m4/threadlib.m4 \ + $(top_srcdir)/../gettext-runtime/m4/uintmax_t.m4 \ + $(top_srcdir)/../gettext-runtime/m4/visibility.m4 \ + $(top_srcdir)/../gettext-runtime/m4/wchar_t.m4 \ + $(top_srcdir)/../gettext-runtime/m4/wint_t.m4 \ + $(top_srcdir)/../gettext-runtime/m4/xsize.m4 \ + $(top_srcdir)/m4/exported.m4 $(top_srcdir)/m4/hostname.m4 \ + $(top_srcdir)/m4/setlocale.m4 $(top_srcdir)/m4/siginfo.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/../build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = user-email +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +libgettextsrc_la_LIBADD = +am__libgettextsrc_la_SOURCES_DIST = message.c po-error.c po-xerror.c \ + read-catalog-abstract.c po-lex.c po-gram-gen.y po-charset.c \ + read-po.c read-properties.c read-stringtable.c open-catalog.c \ + dir-list.c str-list.c read-catalog.c color.c write-catalog.c \ + write-properties.c write-stringtable.c write-po.c msgl-ascii.c \ + msgl-iconv.c msgl-equal.c msgl-cat.c msgl-header.c \ + msgl-english.c msgl-check.c file-list.c msgl-charset.c \ + po-time.c plural-exp.c plural-eval.c plural-table.c format.c \ + format-invalid.h format-c.c format-c-parse.h format-sh.c \ + format-python.c format-python-brace.c format-lisp.c \ + format-elisp.c format-librep.c format-scheme.c format-java.c \ + format-csharp.c format-awk.c format-pascal.c format-ycp.c \ + format-tcl.c format-perl.c format-perl-brace.c format-php.c \ + format-gcc-internal.c format-gfc-internal.c format-qt.c \ + format-qt-plural.c format-kde.c format-boost.c format-lua.c \ + format-javascript.c ../woe32dll/c++format.cc read-desktop.c \ + ../woe32dll/gettextsrc-exports.c +am__objects_1 = libgettextsrc_la-message.lo \ + libgettextsrc_la-po-error.lo libgettextsrc_la-po-xerror.lo \ + libgettextsrc_la-read-catalog-abstract.lo \ + libgettextsrc_la-po-lex.lo libgettextsrc_la-po-gram-gen.lo \ + libgettextsrc_la-po-charset.lo libgettextsrc_la-read-po.lo \ + libgettextsrc_la-read-properties.lo \ + libgettextsrc_la-read-stringtable.lo \ + libgettextsrc_la-open-catalog.lo libgettextsrc_la-dir-list.lo \ + libgettextsrc_la-str-list.lo +am__dirstamp = $(am__leading_dot)dirstamp +@WOE32DLL_FALSE@am__objects_2 = libgettextsrc_la-format.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-c.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-sh.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-python.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-python-brace.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-lisp.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-elisp.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-librep.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-scheme.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-java.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-csharp.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-awk.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-pascal.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-ycp.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-tcl.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-perl.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-perl-brace.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-php.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-gcc-internal.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-gfc-internal.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-qt.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-qt-plural.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-kde.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-boost.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-lua.lo \ +@WOE32DLL_FALSE@ libgettextsrc_la-format-javascript.lo +@WOE32DLL_TRUE@am__objects_2 = \ +@WOE32DLL_TRUE@ ../woe32dll/libgettextsrc_la-c++format.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-c.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-sh.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-python.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-python-brace.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-lisp.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-elisp.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-librep.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-scheme.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-java.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-csharp.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-awk.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-pascal.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-ycp.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-tcl.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-perl.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-perl-brace.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-php.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-gcc-internal.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-gfc-internal.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-qt.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-qt-plural.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-kde.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-boost.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-lua.lo \ +@WOE32DLL_TRUE@ libgettextsrc_la-format-javascript.lo +@WOE32DLL_TRUE@am__objects_3 = ../woe32dll/libgettextsrc_la-gettextsrc-exports.lo +am_libgettextsrc_la_OBJECTS = $(am__objects_1) \ + libgettextsrc_la-read-catalog.lo libgettextsrc_la-color.lo \ + libgettextsrc_la-write-catalog.lo \ + libgettextsrc_la-write-properties.lo \ + libgettextsrc_la-write-stringtable.lo \ + libgettextsrc_la-write-po.lo libgettextsrc_la-msgl-ascii.lo \ + libgettextsrc_la-msgl-iconv.lo libgettextsrc_la-msgl-equal.lo \ + libgettextsrc_la-msgl-cat.lo libgettextsrc_la-msgl-header.lo \ + libgettextsrc_la-msgl-english.lo \ + libgettextsrc_la-msgl-check.lo libgettextsrc_la-file-list.lo \ + libgettextsrc_la-msgl-charset.lo libgettextsrc_la-po-time.lo \ + libgettextsrc_la-plural-exp.lo libgettextsrc_la-plural-eval.lo \ + libgettextsrc_la-plural-table.lo $(am__objects_2) \ + libgettextsrc_la-read-desktop.lo $(am__objects_3) +libgettextsrc_la_OBJECTS = $(am_libgettextsrc_la_OBJECTS) +PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) +am_hostname_OBJECTS = hostname-hostname.$(OBJEXT) +hostname_OBJECTS = $(am_hostname_OBJECTS) +hostname_LDADD = $(LDADD) +am__DEPENDENCIES_1 = +@WOE32_TRUE@am__DEPENDENCIES_2 = gettext.res +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +hostname_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(hostname_LDFLAGS) $(LDFLAGS) -o $@ +am__msgattrib_SOURCES_DIST = msgattrib.c ../woe32dll/c++msgattrib.cc +@WOE32DLL_FALSE@am_msgattrib_OBJECTS = msgattrib-msgattrib.$(OBJEXT) +@WOE32DLL_TRUE@am_msgattrib_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msgattrib-c++msgattrib.$(OBJEXT) +msgattrib_OBJECTS = $(am_msgattrib_OBJECTS) +am__msgcat_SOURCES_DIST = msgcat.c ../woe32dll/c++msgcat.cc +@WOE32DLL_FALSE@am_msgcat_OBJECTS = msgcat-msgcat.$(OBJEXT) +@WOE32DLL_TRUE@am_msgcat_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msgcat-c++msgcat.$(OBJEXT) +msgcat_OBJECTS = $(am_msgcat_OBJECTS) +am_msgcmp_OBJECTS = msgcmp-msgcmp.$(OBJEXT) \ + msgcmp-msgl-fsearch.$(OBJEXT) +msgcmp_OBJECTS = $(am_msgcmp_OBJECTS) +msgcmp_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(msgcmp_LDFLAGS) $(LDFLAGS) -o $@ +am__msgcomm_SOURCES_DIST = msgcomm.c ../woe32dll/c++msgcomm.cc +@WOE32DLL_FALSE@am_msgcomm_OBJECTS = msgcomm-msgcomm.$(OBJEXT) +@WOE32DLL_TRUE@am_msgcomm_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msgcomm-c++msgcomm.$(OBJEXT) +msgcomm_OBJECTS = $(am_msgcomm_OBJECTS) +am__msgconv_SOURCES_DIST = msgconv.c ../woe32dll/c++msgconv.cc +@WOE32DLL_FALSE@am_msgconv_OBJECTS = msgconv-msgconv.$(OBJEXT) +@WOE32DLL_TRUE@am_msgconv_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msgconv-c++msgconv.$(OBJEXT) +msgconv_OBJECTS = $(am_msgconv_OBJECTS) +am__msgen_SOURCES_DIST = msgen.c ../woe32dll/c++msgen.cc +@WOE32DLL_FALSE@am_msgen_OBJECTS = msgen-msgen.$(OBJEXT) +@WOE32DLL_TRUE@am_msgen_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msgen-c++msgen.$(OBJEXT) +msgen_OBJECTS = $(am_msgen_OBJECTS) +am_msgexec_OBJECTS = msgexec-msgexec.$(OBJEXT) +msgexec_OBJECTS = $(am_msgexec_OBJECTS) +msgexec_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(msgexec_LDFLAGS) $(LDFLAGS) -o $@ +am__msgfilter_SOURCES_DIST = msgfilter.c filter-sr-latin.c \ + filter-quote.c ../woe32dll/c++msgfilter.cc +@WOE32DLL_FALSE@am_msgfilter_OBJECTS = msgfilter-msgfilter.$(OBJEXT) \ +@WOE32DLL_FALSE@ msgfilter-filter-sr-latin.$(OBJEXT) \ +@WOE32DLL_FALSE@ msgfilter-filter-quote.$(OBJEXT) +@WOE32DLL_TRUE@am_msgfilter_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msgfilter-c++msgfilter.$(OBJEXT) \ +@WOE32DLL_TRUE@ msgfilter-filter-sr-latin.$(OBJEXT) \ +@WOE32DLL_TRUE@ msgfilter-filter-quote.$(OBJEXT) +msgfilter_OBJECTS = $(am_msgfilter_OBJECTS) +am_msgfmt_OBJECTS = msgfmt-msgfmt.$(OBJEXT) msgfmt-write-mo.$(OBJEXT) \ + msgfmt-write-java.$(OBJEXT) msgfmt-write-csharp.$(OBJEXT) \ + msgfmt-write-resources.$(OBJEXT) msgfmt-write-tcl.$(OBJEXT) \ + msgfmt-write-qt.$(OBJEXT) msgfmt-write-desktop.$(OBJEXT) \ + ../../gettext-runtime/intl/msgfmt-hash-string.$(OBJEXT) +msgfmt_OBJECTS = $(am_msgfmt_OBJECTS) +msgfmt_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(msgfmt_LDFLAGS) $(LDFLAGS) -o $@ +am__msggrep_SOURCES_DIST = msggrep.c ../woe32dll/c++msggrep.cc +@WOE32DLL_FALSE@am_msggrep_OBJECTS = msggrep-msggrep.$(OBJEXT) +@WOE32DLL_TRUE@am_msggrep_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msggrep-c++msggrep.$(OBJEXT) +msggrep_OBJECTS = $(am_msggrep_OBJECTS) +am_msginit_OBJECTS = msginit-msginit.$(OBJEXT) \ + msginit-lang-table.$(OBJEXT) msginit-plural-count.$(OBJEXT) \ + ../../gettext-runtime/intl/msginit-localealias.$(OBJEXT) \ + ../../gettext-runtime/intl/msginit-lock.$(OBJEXT) +msginit_OBJECTS = $(am_msginit_OBJECTS) +msginit_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(msginit_LDFLAGS) $(LDFLAGS) -o $@ +am__msgmerge_SOURCES_DIST = msgmerge.c msgl-fsearch.c lang-table.c \ + plural-count.c ../woe32dll/c++msgmerge.cc +@WOE32DLL_FALSE@am_msgmerge_OBJECTS = msgmerge-msgmerge.$(OBJEXT) \ +@WOE32DLL_FALSE@ msgmerge-msgl-fsearch.$(OBJEXT) \ +@WOE32DLL_FALSE@ msgmerge-lang-table.$(OBJEXT) \ +@WOE32DLL_FALSE@ msgmerge-plural-count.$(OBJEXT) +@WOE32DLL_TRUE@am_msgmerge_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msgmerge-c++msgmerge.$(OBJEXT) \ +@WOE32DLL_TRUE@ msgmerge-msgl-fsearch.$(OBJEXT) \ +@WOE32DLL_TRUE@ msgmerge-lang-table.$(OBJEXT) \ +@WOE32DLL_TRUE@ msgmerge-plural-count.$(OBJEXT) +msgmerge_OBJECTS = $(am_msgmerge_OBJECTS) +am_msgunfmt_OBJECTS = msgunfmt-msgunfmt.$(OBJEXT) \ + msgunfmt-read-mo.$(OBJEXT) msgunfmt-read-java.$(OBJEXT) \ + msgunfmt-read-csharp.$(OBJEXT) \ + msgunfmt-read-resources.$(OBJEXT) msgunfmt-read-tcl.$(OBJEXT) +msgunfmt_OBJECTS = $(am_msgunfmt_OBJECTS) +msgunfmt_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(msgunfmt_LDFLAGS) $(LDFLAGS) -o $@ +am__msguniq_SOURCES_DIST = msguniq.c ../woe32dll/c++msguniq.cc +@WOE32DLL_FALSE@am_msguniq_OBJECTS = msguniq-msguniq.$(OBJEXT) +@WOE32DLL_TRUE@am_msguniq_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/msguniq-c++msguniq.$(OBJEXT) +msguniq_OBJECTS = $(am_msguniq_OBJECTS) +am_recode_sr_latin_OBJECTS = \ + recode_sr_latin-recode-sr-latin.$(OBJEXT) \ + recode_sr_latin-filter-sr-latin.$(OBJEXT) +recode_sr_latin_OBJECTS = $(am_recode_sr_latin_OBJECTS) +recode_sr_latin_LDADD = $(LDADD) +recode_sr_latin_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(recode_sr_latin_LDFLAGS) $(LDFLAGS) \ + -o $@ +am_urlget_OBJECTS = urlget-urlget.$(OBJEXT) +urlget_OBJECTS = $(am_urlget_OBJECTS) +urlget_LDADD = $(LDADD) +urlget_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(urlget_LDFLAGS) $(LDFLAGS) -o $@ +am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \ + x-lisp.c x-elisp.c x-librep.c x-scheme.c x-smalltalk.c \ + x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ + x-rst.c x-glade.c x-lua.c x-javascript.c x-vala.c \ + x-gsettings.c libexpat-compat.c x-desktop.c \ + ../woe32dll/c++xgettext.cc +@WOE32DLL_FALSE@am_xgettext_OBJECTS = xgettext-xgettext.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-sh.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-python.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-lisp.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-elisp.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-librep.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-scheme.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-smalltalk.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-java.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-csharp.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-awk.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-ycp.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-tcl.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-perl.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-php.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-rst.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-lua.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-javascript.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-vala.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-gsettings.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-libexpat-compat.$(OBJEXT) \ +@WOE32DLL_FALSE@ xgettext-x-desktop.$(OBJEXT) +@WOE32DLL_TRUE@am_xgettext_OBJECTS = \ +@WOE32DLL_TRUE@ ../woe32dll/xgettext-c++xgettext.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-sh.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-python.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-lisp.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-elisp.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-librep.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-scheme.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-smalltalk.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-java.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-csharp.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-awk.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-ycp.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-tcl.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-perl.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-php.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-rst.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-lua.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-javascript.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-vala.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-gsettings.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-libexpat-compat.$(OBJEXT) \ +@WOE32DLL_TRUE@ xgettext-x-desktop.$(OBJEXT) +xgettext_OBJECTS = $(am_xgettext_OBJECTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = +am__depfiles_maybe = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +am__yacc_c2h = sed -e s/cc$$/hh/ -e s/cpp$$/hpp/ -e s/cxx$$/hxx/ \ + -e s/c++$$/h++/ -e s/c$$/h/ +YACCCOMPILE = $(YACC) $(AM_YFLAGS) $(YFLAGS) +LTYACCCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(YACC) $(AM_YFLAGS) $(YFLAGS) +AM_V_YACC = $(am__v_YACC_@AM_V@) +am__v_YACC_ = $(am__v_YACC_@AM_DEFAULT_V@) +am__v_YACC_0 = @echo " YACC " $@; +am__v_YACC_1 = +YLWRAP = $(top_srcdir)/../build-aux/ylwrap +SOURCES = $(libgettextsrc_la_SOURCES) $(hostname_SOURCES) \ + $(msgattrib_SOURCES) $(msgcat_SOURCES) $(msgcmp_SOURCES) \ + $(msgcomm_SOURCES) $(msgconv_SOURCES) $(msgen_SOURCES) \ + $(msgexec_SOURCES) $(msgfilter_SOURCES) $(msgfmt_SOURCES) \ + $(msggrep_SOURCES) $(msginit_SOURCES) $(msgmerge_SOURCES) \ + $(msgunfmt_SOURCES) $(msguniq_SOURCES) \ + $(recode_sr_latin_SOURCES) $(urlget_SOURCES) \ + $(xgettext_SOURCES) +DIST_SOURCES = $(am__libgettextsrc_la_SOURCES_DIST) \ + $(hostname_SOURCES) $(am__msgattrib_SOURCES_DIST) \ + $(am__msgcat_SOURCES_DIST) $(msgcmp_SOURCES) \ + $(am__msgcomm_SOURCES_DIST) $(am__msgconv_SOURCES_DIST) \ + $(am__msgen_SOURCES_DIST) $(msgexec_SOURCES) \ + $(am__msgfilter_SOURCES_DIST) $(msgfmt_SOURCES) \ + $(am__msggrep_SOURCES_DIST) $(msginit_SOURCES) \ + $(am__msgmerge_SOURCES_DIST) $(msgunfmt_SOURCES) \ + $(am__msguniq_SOURCES_DIST) $(recode_sr_latin_SOURCES) \ + $(urlget_SOURCES) $(am__xgettext_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +pkgdatadir = $(datadir)/gettext +pkglibdir = $(libdir)/gettext +pkglibexecdir = @pkglibexecdir@ +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +ALLOCA_H = @ALLOCA_H@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@ +AR = @AR@ +ARCHIVE_FORMAT = @ARCHIVE_FORMAT@ +ARFLAGS = @ARFLAGS@ +AS = @AS@ +ASM_SYMBOL_PREFIX = @ASM_SYMBOL_PREFIX@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BISON_LOCALEDIR = @BISON_LOCALEDIR@ +BITSIZEOF_PTRDIFF_T = @BITSIZEOF_PTRDIFF_T@ +BITSIZEOF_SIG_ATOMIC_T = @BITSIZEOF_SIG_ATOMIC_T@ +BITSIZEOF_SIZE_T = @BITSIZEOF_SIZE_T@ +BITSIZEOF_WCHAR_T = @BITSIZEOF_WCHAR_T@ +BITSIZEOF_WINT_T = @BITSIZEOF_WINT_T@ +BUILDCSHARP = @BUILDCSHARP@ +BUILDJAVA = @BUILDJAVA@ +BUILDJAVAEXE = @BUILDJAVAEXE@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +BYTESWAP_H = @BYTESWAP_H@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CFLAG_VISIBILITY = @CFLAG_VISIBILITY@ +CLASSPATH = @CLASSPATH@ +CLASSPATH_SEPARATOR = @CLASSPATH_SEPARATOR@ +CLIX_PATH = @CLIX_PATH@ +CLIX_PATH_VAR = @CLIX_PATH_VAR@ +CONF_JAVA = @CONF_JAVA@ +CONF_JAVAC = @CONF_JAVAC@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CROSS_COMPILING = @CROSS_COMPILING@ +CSHARPCOMPFLAGS = @CSHARPCOMPFLAGS@ +CSHARP_CHOICE = @CSHARP_CHOICE@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CXX_CHOICE = @CXX_CHOICE@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DEFS = \ + -DLOCALEDIR=\"$(localedir)\" -DBISON_LOCALEDIR=\"$(BISON_LOCALEDIR)\" \ + -DLOCALE_ALIAS_PATH=\"$(aliaspath)\" \ + -DUSEJAVA=$(USEJAVA) \ + -DUSEJEXE=$(USEJEXE) \ + -DGETTEXTJEXEDIR=\"$(pkglibdir)\" \ + -DGETTEXTJAR=\"$(jardir)/gettext.jar\" \ + -DLIBDIR=\"$(libdir)\" \ + -DGETTEXTDATADIR=\"$(pkgdatadir)\" \ + -DPROJECTSDIR=\"$(projectsdir)\" @DEFS@ + +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +DVIPS = @DVIPS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EMACS = @EMACS@ +EMACSLOADPATH = @EMACSLOADPATH@ +EMULTIHOP_HIDDEN = @EMULTIHOP_HIDDEN@ +EMULTIHOP_VALUE = @EMULTIHOP_VALUE@ +ENOLINK_HIDDEN = @ENOLINK_HIDDEN@ +ENOLINK_VALUE = @ENOLINK_VALUE@ +EOVERFLOW_HIDDEN = @EOVERFLOW_HIDDEN@ +EOVERFLOW_VALUE = @EOVERFLOW_VALUE@ +ERRNO_H = @ERRNO_H@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FLOAT_H = @FLOAT_H@ +FNMATCH_H = @FNMATCH_H@ +GCJ = @GCJ@ +GCJFLAGS = @GCJFLAGS@ +GENCAT = @GENCAT@ +GETOPT_H = @GETOPT_H@ +GETTEXTLIB_EXPORTS_FLAGS = @GETTEXTLIB_EXPORTS_FLAGS@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GLIBC2 = @GLIBC2@ +GLIBC21 = @GLIBC21@ +GLOBAL_SYMBOL_PIPE = @GLOBAL_SYMBOL_PIPE@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNULIB_ALPHASORT = @GNULIB_ALPHASORT@ +GNULIB_ATOLL = @GNULIB_ATOLL@ +GNULIB_BTOWC = @GNULIB_BTOWC@ +GNULIB_CALLOC_POSIX = @GNULIB_CALLOC_POSIX@ +GNULIB_CANONICALIZE_FILE_NAME = @GNULIB_CANONICALIZE_FILE_NAME@ +GNULIB_CHDIR = @GNULIB_CHDIR@ +GNULIB_CHOWN = @GNULIB_CHOWN@ +GNULIB_CLOSE = @GNULIB_CLOSE@ +GNULIB_CLOSEDIR = @GNULIB_CLOSEDIR@ +GNULIB_DIRFD = @GNULIB_DIRFD@ +GNULIB_DPRINTF = @GNULIB_DPRINTF@ +GNULIB_DUP = @GNULIB_DUP@ +GNULIB_DUP2 = @GNULIB_DUP2@ +GNULIB_DUP3 = @GNULIB_DUP3@ +GNULIB_DUPLOCALE = @GNULIB_DUPLOCALE@ +GNULIB_ENVIRON = @GNULIB_ENVIRON@ +GNULIB_EUIDACCESS = @GNULIB_EUIDACCESS@ +GNULIB_FACCESSAT = @GNULIB_FACCESSAT@ +GNULIB_FCHDIR = @GNULIB_FCHDIR@ +GNULIB_FCHMODAT = @GNULIB_FCHMODAT@ +GNULIB_FCHOWNAT = @GNULIB_FCHOWNAT@ +GNULIB_FCLOSE = @GNULIB_FCLOSE@ +GNULIB_FCNTL = @GNULIB_FCNTL@ +GNULIB_FDATASYNC = @GNULIB_FDATASYNC@ +GNULIB_FDOPEN = @GNULIB_FDOPEN@ +GNULIB_FDOPENDIR = @GNULIB_FDOPENDIR@ +GNULIB_FFLUSH = @GNULIB_FFLUSH@ +GNULIB_FFSL = @GNULIB_FFSL@ +GNULIB_FFSLL = @GNULIB_FFSLL@ +GNULIB_FGETC = @GNULIB_FGETC@ +GNULIB_FGETS = @GNULIB_FGETS@ +GNULIB_FOPEN = @GNULIB_FOPEN@ +GNULIB_FPRINTF = @GNULIB_FPRINTF@ +GNULIB_FPRINTF_POSIX = @GNULIB_FPRINTF_POSIX@ +GNULIB_FPURGE = @GNULIB_FPURGE@ +GNULIB_FPUTC = @GNULIB_FPUTC@ +GNULIB_FPUTS = @GNULIB_FPUTS@ +GNULIB_FREAD = @GNULIB_FREAD@ +GNULIB_FREOPEN = @GNULIB_FREOPEN@ +GNULIB_FSCANF = @GNULIB_FSCANF@ +GNULIB_FSEEK = @GNULIB_FSEEK@ +GNULIB_FSEEKO = @GNULIB_FSEEKO@ +GNULIB_FSTAT = @GNULIB_FSTAT@ +GNULIB_FSTATAT = @GNULIB_FSTATAT@ +GNULIB_FSYNC = @GNULIB_FSYNC@ +GNULIB_FTELL = @GNULIB_FTELL@ +GNULIB_FTELLO = @GNULIB_FTELLO@ +GNULIB_FTRUNCATE = @GNULIB_FTRUNCATE@ +GNULIB_FUTIMENS = @GNULIB_FUTIMENS@ +GNULIB_FWRITE = @GNULIB_FWRITE@ +GNULIB_GETC = @GNULIB_GETC@ +GNULIB_GETCHAR = @GNULIB_GETCHAR@ +GNULIB_GETCWD = @GNULIB_GETCWD@ +GNULIB_GETDELIM = @GNULIB_GETDELIM@ +GNULIB_GETDOMAINNAME = @GNULIB_GETDOMAINNAME@ +GNULIB_GETDTABLESIZE = @GNULIB_GETDTABLESIZE@ +GNULIB_GETGROUPS = @GNULIB_GETGROUPS@ +GNULIB_GETHOSTNAME = @GNULIB_GETHOSTNAME@ +GNULIB_GETLINE = @GNULIB_GETLINE@ +GNULIB_GETLOADAVG = @GNULIB_GETLOADAVG@ +GNULIB_GETLOGIN = @GNULIB_GETLOGIN@ +GNULIB_GETLOGIN_R = @GNULIB_GETLOGIN_R@ +GNULIB_GETPAGESIZE = @GNULIB_GETPAGESIZE@ +GNULIB_GETSUBOPT = @GNULIB_GETSUBOPT@ +GNULIB_GETTIMEOFDAY = @GNULIB_GETTIMEOFDAY@ +GNULIB_GETUSERSHELL = @GNULIB_GETUSERSHELL@ +GNULIB_GL_UNISTD_H_GETOPT = @GNULIB_GL_UNISTD_H_GETOPT@ +GNULIB_GRANTPT = @GNULIB_GRANTPT@ +GNULIB_GROUP_MEMBER = @GNULIB_GROUP_MEMBER@ +GNULIB_ICONV = @GNULIB_ICONV@ +GNULIB_IMAXABS = @GNULIB_IMAXABS@ +GNULIB_IMAXDIV = @GNULIB_IMAXDIV@ +GNULIB_ISATTY = @GNULIB_ISATTY@ +GNULIB_ISWBLANK = @GNULIB_ISWBLANK@ +GNULIB_ISWCTYPE = @GNULIB_ISWCTYPE@ +GNULIB_LCHMOD = @GNULIB_LCHMOD@ +GNULIB_LCHOWN = @GNULIB_LCHOWN@ +GNULIB_LINK = @GNULIB_LINK@ +GNULIB_LINKAT = @GNULIB_LINKAT@ +GNULIB_LOCALECONV = @GNULIB_LOCALECONV@ +GNULIB_LSEEK = @GNULIB_LSEEK@ +GNULIB_LSTAT = @GNULIB_LSTAT@ +GNULIB_MALLOC_POSIX = @GNULIB_MALLOC_POSIX@ +GNULIB_MBRLEN = @GNULIB_MBRLEN@ +GNULIB_MBRTOWC = @GNULIB_MBRTOWC@ +GNULIB_MBSCASECMP = @GNULIB_MBSCASECMP@ +GNULIB_MBSCASESTR = @GNULIB_MBSCASESTR@ +GNULIB_MBSCHR = @GNULIB_MBSCHR@ +GNULIB_MBSCSPN = @GNULIB_MBSCSPN@ +GNULIB_MBSINIT = @GNULIB_MBSINIT@ +GNULIB_MBSLEN = @GNULIB_MBSLEN@ +GNULIB_MBSNCASECMP = @GNULIB_MBSNCASECMP@ +GNULIB_MBSNLEN = @GNULIB_MBSNLEN@ +GNULIB_MBSNRTOWCS = @GNULIB_MBSNRTOWCS@ +GNULIB_MBSPBRK = @GNULIB_MBSPBRK@ +GNULIB_MBSPCASECMP = @GNULIB_MBSPCASECMP@ +GNULIB_MBSRCHR = @GNULIB_MBSRCHR@ +GNULIB_MBSRTOWCS = @GNULIB_MBSRTOWCS@ +GNULIB_MBSSEP = @GNULIB_MBSSEP@ +GNULIB_MBSSPN = @GNULIB_MBSSPN@ +GNULIB_MBSSTR = @GNULIB_MBSSTR@ +GNULIB_MBSTOK_R = @GNULIB_MBSTOK_R@ +GNULIB_MBTOWC = @GNULIB_MBTOWC@ +GNULIB_MEMCHR = @GNULIB_MEMCHR@ +GNULIB_MEMMEM = @GNULIB_MEMMEM@ +GNULIB_MEMPCPY = @GNULIB_MEMPCPY@ +GNULIB_MEMRCHR = @GNULIB_MEMRCHR@ +GNULIB_MKDIRAT = @GNULIB_MKDIRAT@ +GNULIB_MKDTEMP = @GNULIB_MKDTEMP@ +GNULIB_MKFIFO = @GNULIB_MKFIFO@ +GNULIB_MKFIFOAT = @GNULIB_MKFIFOAT@ +GNULIB_MKNOD = @GNULIB_MKNOD@ +GNULIB_MKNODAT = @GNULIB_MKNODAT@ +GNULIB_MKOSTEMP = @GNULIB_MKOSTEMP@ +GNULIB_MKOSTEMPS = @GNULIB_MKOSTEMPS@ +GNULIB_MKSTEMP = @GNULIB_MKSTEMP@ +GNULIB_MKSTEMPS = @GNULIB_MKSTEMPS@ +GNULIB_MKTIME = @GNULIB_MKTIME@ +GNULIB_NANOSLEEP = @GNULIB_NANOSLEEP@ +GNULIB_NL_LANGINFO = @GNULIB_NL_LANGINFO@ +GNULIB_NONBLOCKING = @GNULIB_NONBLOCKING@ +GNULIB_OBSTACK_PRINTF = @GNULIB_OBSTACK_PRINTF@ +GNULIB_OBSTACK_PRINTF_POSIX = @GNULIB_OBSTACK_PRINTF_POSIX@ +GNULIB_OPEN = @GNULIB_OPEN@ +GNULIB_OPENAT = @GNULIB_OPENAT@ +GNULIB_OPENDIR = @GNULIB_OPENDIR@ +GNULIB_PCLOSE = @GNULIB_PCLOSE@ +GNULIB_PERROR = @GNULIB_PERROR@ +GNULIB_PIPE = @GNULIB_PIPE@ +GNULIB_PIPE2 = @GNULIB_PIPE2@ +GNULIB_POPEN = @GNULIB_POPEN@ +GNULIB_POSIX_OPENPT = @GNULIB_POSIX_OPENPT@ +GNULIB_POSIX_SPAWN = @GNULIB_POSIX_SPAWN@ +GNULIB_POSIX_SPAWNATTR_DESTROY = @GNULIB_POSIX_SPAWNATTR_DESTROY@ +GNULIB_POSIX_SPAWNATTR_GETFLAGS = @GNULIB_POSIX_SPAWNATTR_GETFLAGS@ +GNULIB_POSIX_SPAWNATTR_GETPGROUP = @GNULIB_POSIX_SPAWNATTR_GETPGROUP@ +GNULIB_POSIX_SPAWNATTR_GETSCHEDPARAM = @GNULIB_POSIX_SPAWNATTR_GETSCHEDPARAM@ +GNULIB_POSIX_SPAWNATTR_GETSCHEDPOLICY = @GNULIB_POSIX_SPAWNATTR_GETSCHEDPOLICY@ +GNULIB_POSIX_SPAWNATTR_GETSIGDEFAULT = @GNULIB_POSIX_SPAWNATTR_GETSIGDEFAULT@ +GNULIB_POSIX_SPAWNATTR_GETSIGMASK = @GNULIB_POSIX_SPAWNATTR_GETSIGMASK@ +GNULIB_POSIX_SPAWNATTR_INIT = @GNULIB_POSIX_SPAWNATTR_INIT@ +GNULIB_POSIX_SPAWNATTR_SETFLAGS = @GNULIB_POSIX_SPAWNATTR_SETFLAGS@ +GNULIB_POSIX_SPAWNATTR_SETPGROUP = @GNULIB_POSIX_SPAWNATTR_SETPGROUP@ +GNULIB_POSIX_SPAWNATTR_SETSCHEDPARAM = @GNULIB_POSIX_SPAWNATTR_SETSCHEDPARAM@ +GNULIB_POSIX_SPAWNATTR_SETSCHEDPOLICY = @GNULIB_POSIX_SPAWNATTR_SETSCHEDPOLICY@ +GNULIB_POSIX_SPAWNATTR_SETSIGDEFAULT = @GNULIB_POSIX_SPAWNATTR_SETSIGDEFAULT@ +GNULIB_POSIX_SPAWNATTR_SETSIGMASK = @GNULIB_POSIX_SPAWNATTR_SETSIGMASK@ +GNULIB_POSIX_SPAWNP = @GNULIB_POSIX_SPAWNP@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSE = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSE@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2 = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDOPEN = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDOPEN@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_DESTROY = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_DESTROY@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_INIT = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_INIT@ +GNULIB_PREAD = @GNULIB_PREAD@ +GNULIB_PRINTF = @GNULIB_PRINTF@ +GNULIB_PRINTF_POSIX = @GNULIB_PRINTF_POSIX@ +GNULIB_PSELECT = @GNULIB_PSELECT@ +GNULIB_PTHREAD_SIGMASK = @GNULIB_PTHREAD_SIGMASK@ +GNULIB_PTSNAME = @GNULIB_PTSNAME@ +GNULIB_PTSNAME_R = @GNULIB_PTSNAME_R@ +GNULIB_PUTC = @GNULIB_PUTC@ +GNULIB_PUTCHAR = @GNULIB_PUTCHAR@ +GNULIB_PUTENV = @GNULIB_PUTENV@ +GNULIB_PUTS = @GNULIB_PUTS@ +GNULIB_PWRITE = @GNULIB_PWRITE@ +GNULIB_QSORT_R = @GNULIB_QSORT_R@ +GNULIB_RAISE = @GNULIB_RAISE@ +GNULIB_RANDOM = @GNULIB_RANDOM@ +GNULIB_RANDOM_R = @GNULIB_RANDOM_R@ +GNULIB_RAWMEMCHR = @GNULIB_RAWMEMCHR@ +GNULIB_READ = @GNULIB_READ@ +GNULIB_READDIR = @GNULIB_READDIR@ +GNULIB_READLINK = @GNULIB_READLINK@ +GNULIB_READLINKAT = @GNULIB_READLINKAT@ +GNULIB_REALLOC_POSIX = @GNULIB_REALLOC_POSIX@ +GNULIB_REALPATH = @GNULIB_REALPATH@ +GNULIB_REMOVE = @GNULIB_REMOVE@ +GNULIB_RENAME = @GNULIB_RENAME@ +GNULIB_RENAMEAT = @GNULIB_RENAMEAT@ +GNULIB_REWINDDIR = @GNULIB_REWINDDIR@ +GNULIB_RMDIR = @GNULIB_RMDIR@ +GNULIB_RPMATCH = @GNULIB_RPMATCH@ +GNULIB_SCANDIR = @GNULIB_SCANDIR@ +GNULIB_SCANF = @GNULIB_SCANF@ +GNULIB_SECURE_GETENV = @GNULIB_SECURE_GETENV@ +GNULIB_SELECT = @GNULIB_SELECT@ +GNULIB_SETENV = @GNULIB_SETENV@ +GNULIB_SETHOSTNAME = @GNULIB_SETHOSTNAME@ +GNULIB_SETLOCALE = @GNULIB_SETLOCALE@ +GNULIB_SIGACTION = @GNULIB_SIGACTION@ +GNULIB_SIGNAL_H_SIGPIPE = @GNULIB_SIGNAL_H_SIGPIPE@ +GNULIB_SIGPROCMASK = @GNULIB_SIGPROCMASK@ +GNULIB_SLEEP = @GNULIB_SLEEP@ +GNULIB_SNPRINTF = @GNULIB_SNPRINTF@ +GNULIB_SPRINTF_POSIX = @GNULIB_SPRINTF_POSIX@ +GNULIB_STAT = @GNULIB_STAT@ +GNULIB_STDIO_H_NONBLOCKING = @GNULIB_STDIO_H_NONBLOCKING@ +GNULIB_STDIO_H_SIGPIPE = @GNULIB_STDIO_H_SIGPIPE@ +GNULIB_STPCPY = @GNULIB_STPCPY@ +GNULIB_STPNCPY = @GNULIB_STPNCPY@ +GNULIB_STRCASESTR = @GNULIB_STRCASESTR@ +GNULIB_STRCHRNUL = @GNULIB_STRCHRNUL@ +GNULIB_STRDUP = @GNULIB_STRDUP@ +GNULIB_STRERROR = @GNULIB_STRERROR@ +GNULIB_STRERROR_R = @GNULIB_STRERROR_R@ +GNULIB_STRNCAT = @GNULIB_STRNCAT@ +GNULIB_STRNDUP = @GNULIB_STRNDUP@ +GNULIB_STRNLEN = @GNULIB_STRNLEN@ +GNULIB_STRPBRK = @GNULIB_STRPBRK@ +GNULIB_STRPTIME = @GNULIB_STRPTIME@ +GNULIB_STRSEP = @GNULIB_STRSEP@ +GNULIB_STRSIGNAL = @GNULIB_STRSIGNAL@ +GNULIB_STRSTR = @GNULIB_STRSTR@ +GNULIB_STRTOD = @GNULIB_STRTOD@ +GNULIB_STRTOIMAX = @GNULIB_STRTOIMAX@ +GNULIB_STRTOK_R = @GNULIB_STRTOK_R@ +GNULIB_STRTOLL = @GNULIB_STRTOLL@ +GNULIB_STRTOULL = @GNULIB_STRTOULL@ +GNULIB_STRTOUMAX = @GNULIB_STRTOUMAX@ +GNULIB_STRVERSCMP = @GNULIB_STRVERSCMP@ +GNULIB_SYMLINK = @GNULIB_SYMLINK@ +GNULIB_SYMLINKAT = @GNULIB_SYMLINKAT@ +GNULIB_SYSTEM_POSIX = @GNULIB_SYSTEM_POSIX@ +GNULIB_TIMEGM = @GNULIB_TIMEGM@ +GNULIB_TIME_R = @GNULIB_TIME_R@ +GNULIB_TMPFILE = @GNULIB_TMPFILE@ +GNULIB_TOWCTRANS = @GNULIB_TOWCTRANS@ +GNULIB_TTYNAME_R = @GNULIB_TTYNAME_R@ +GNULIB_UNISTD_H_NONBLOCKING = @GNULIB_UNISTD_H_NONBLOCKING@ +GNULIB_UNISTD_H_SIGPIPE = @GNULIB_UNISTD_H_SIGPIPE@ +GNULIB_UNLINK = @GNULIB_UNLINK@ +GNULIB_UNLINKAT = @GNULIB_UNLINKAT@ +GNULIB_UNLOCKPT = @GNULIB_UNLOCKPT@ +GNULIB_UNSETENV = @GNULIB_UNSETENV@ +GNULIB_USLEEP = @GNULIB_USLEEP@ +GNULIB_UTIMENSAT = @GNULIB_UTIMENSAT@ +GNULIB_VASPRINTF = @GNULIB_VASPRINTF@ +GNULIB_VDPRINTF = @GNULIB_VDPRINTF@ +GNULIB_VFPRINTF = @GNULIB_VFPRINTF@ +GNULIB_VFPRINTF_POSIX = @GNULIB_VFPRINTF_POSIX@ +GNULIB_VFSCANF = @GNULIB_VFSCANF@ +GNULIB_VPRINTF = @GNULIB_VPRINTF@ +GNULIB_VPRINTF_POSIX = @GNULIB_VPRINTF_POSIX@ +GNULIB_VSCANF = @GNULIB_VSCANF@ +GNULIB_VSNPRINTF = @GNULIB_VSNPRINTF@ +GNULIB_VSPRINTF_POSIX = @GNULIB_VSPRINTF_POSIX@ +GNULIB_WAITPID = @GNULIB_WAITPID@ +GNULIB_WCPCPY = @GNULIB_WCPCPY@ +GNULIB_WCPNCPY = @GNULIB_WCPNCPY@ +GNULIB_WCRTOMB = @GNULIB_WCRTOMB@ +GNULIB_WCSCASECMP = @GNULIB_WCSCASECMP@ +GNULIB_WCSCAT = @GNULIB_WCSCAT@ +GNULIB_WCSCHR = @GNULIB_WCSCHR@ +GNULIB_WCSCMP = @GNULIB_WCSCMP@ +GNULIB_WCSCOLL = @GNULIB_WCSCOLL@ +GNULIB_WCSCPY = @GNULIB_WCSCPY@ +GNULIB_WCSCSPN = @GNULIB_WCSCSPN@ +GNULIB_WCSDUP = @GNULIB_WCSDUP@ +GNULIB_WCSLEN = @GNULIB_WCSLEN@ +GNULIB_WCSNCASECMP = @GNULIB_WCSNCASECMP@ +GNULIB_WCSNCAT = @GNULIB_WCSNCAT@ +GNULIB_WCSNCMP = @GNULIB_WCSNCMP@ +GNULIB_WCSNCPY = @GNULIB_WCSNCPY@ +GNULIB_WCSNLEN = @GNULIB_WCSNLEN@ +GNULIB_WCSNRTOMBS = @GNULIB_WCSNRTOMBS@ +GNULIB_WCSPBRK = @GNULIB_WCSPBRK@ +GNULIB_WCSRCHR = @GNULIB_WCSRCHR@ +GNULIB_WCSRTOMBS = @GNULIB_WCSRTOMBS@ +GNULIB_WCSSPN = @GNULIB_WCSSPN@ +GNULIB_WCSSTR = @GNULIB_WCSSTR@ +GNULIB_WCSTOK = @GNULIB_WCSTOK@ +GNULIB_WCSWIDTH = @GNULIB_WCSWIDTH@ +GNULIB_WCSXFRM = @GNULIB_WCSXFRM@ +GNULIB_WCTOB = @GNULIB_WCTOB@ +GNULIB_WCTOMB = @GNULIB_WCTOMB@ +GNULIB_WCTRANS = @GNULIB_WCTRANS@ +GNULIB_WCTYPE = @GNULIB_WCTYPE@ +GNULIB_WCWIDTH = @GNULIB_WCWIDTH@ +GNULIB_WMEMCHR = @GNULIB_WMEMCHR@ +GNULIB_WMEMCMP = @GNULIB_WMEMCMP@ +GNULIB_WMEMCPY = @GNULIB_WMEMCPY@ +GNULIB_WMEMMOVE = @GNULIB_WMEMMOVE@ +GNULIB_WMEMSET = @GNULIB_WMEMSET@ +GNULIB_WRITE = @GNULIB_WRITE@ +GNULIB__EXIT = @GNULIB__EXIT@ +GREP = @GREP@ +HAVE_ALPHASORT = @HAVE_ALPHASORT@ +HAVE_ASPRINTF = @HAVE_ASPRINTF@ +HAVE_ATOLL = @HAVE_ATOLL@ +HAVE_BTOWC = @HAVE_BTOWC@ +HAVE_CANONICALIZE_FILE_NAME = @HAVE_CANONICALIZE_FILE_NAME@ +HAVE_CHOWN = @HAVE_CHOWN@ +HAVE_CLIX = @HAVE_CLIX@ +HAVE_CLIX_IN_PATH = @HAVE_CLIX_IN_PATH@ +HAVE_CLOSEDIR = @HAVE_CLOSEDIR@ +HAVE_CSC = @HAVE_CSC@ +HAVE_CSCC = @HAVE_CSCC@ +HAVE_CSCC_IN_PATH = @HAVE_CSCC_IN_PATH@ +HAVE_CSC_IN_PATH = @HAVE_CSC_IN_PATH@ +HAVE_DECL_DIRFD = @HAVE_DECL_DIRFD@ +HAVE_DECL_ENVIRON = @HAVE_DECL_ENVIRON@ +HAVE_DECL_FCHDIR = @HAVE_DECL_FCHDIR@ +HAVE_DECL_FDATASYNC = @HAVE_DECL_FDATASYNC@ +HAVE_DECL_FDOPENDIR = @HAVE_DECL_FDOPENDIR@ +HAVE_DECL_FPURGE = @HAVE_DECL_FPURGE@ +HAVE_DECL_FSEEKO = @HAVE_DECL_FSEEKO@ +HAVE_DECL_FTELLO = @HAVE_DECL_FTELLO@ +HAVE_DECL_GETDELIM = @HAVE_DECL_GETDELIM@ +HAVE_DECL_GETDOMAINNAME = @HAVE_DECL_GETDOMAINNAME@ +HAVE_DECL_GETLINE = @HAVE_DECL_GETLINE@ +HAVE_DECL_GETLOADAVG = @HAVE_DECL_GETLOADAVG@ +HAVE_DECL_GETLOGIN_R = @HAVE_DECL_GETLOGIN_R@ +HAVE_DECL_GETPAGESIZE = @HAVE_DECL_GETPAGESIZE@ +HAVE_DECL_GETUSERSHELL = @HAVE_DECL_GETUSERSHELL@ +HAVE_DECL_IMAXABS = @HAVE_DECL_IMAXABS@ +HAVE_DECL_IMAXDIV = @HAVE_DECL_IMAXDIV@ +HAVE_DECL_LOCALTIME_R = @HAVE_DECL_LOCALTIME_R@ +HAVE_DECL_MEMMEM = @HAVE_DECL_MEMMEM@ +HAVE_DECL_MEMRCHR = @HAVE_DECL_MEMRCHR@ +HAVE_DECL_OBSTACK_PRINTF = @HAVE_DECL_OBSTACK_PRINTF@ +HAVE_DECL_SETENV = @HAVE_DECL_SETENV@ +HAVE_DECL_SETHOSTNAME = @HAVE_DECL_SETHOSTNAME@ +HAVE_DECL_SNPRINTF = @HAVE_DECL_SNPRINTF@ +HAVE_DECL_STRDUP = @HAVE_DECL_STRDUP@ +HAVE_DECL_STRERROR_R = @HAVE_DECL_STRERROR_R@ +HAVE_DECL_STRNDUP = @HAVE_DECL_STRNDUP@ +HAVE_DECL_STRNLEN = @HAVE_DECL_STRNLEN@ +HAVE_DECL_STRSIGNAL = @HAVE_DECL_STRSIGNAL@ +HAVE_DECL_STRTOIMAX = @HAVE_DECL_STRTOIMAX@ +HAVE_DECL_STRTOK_R = @HAVE_DECL_STRTOK_R@ +HAVE_DECL_STRTOUMAX = @HAVE_DECL_STRTOUMAX@ +HAVE_DECL_TTYNAME_R = @HAVE_DECL_TTYNAME_R@ +HAVE_DECL_UNSETENV = @HAVE_DECL_UNSETENV@ +HAVE_DECL_VSNPRINTF = @HAVE_DECL_VSNPRINTF@ +HAVE_DECL_WCTOB = @HAVE_DECL_WCTOB@ +HAVE_DECL_WCWIDTH = @HAVE_DECL_WCWIDTH@ +HAVE_DIRENT_H = @HAVE_DIRENT_H@ +HAVE_DPRINTF = @HAVE_DPRINTF@ +HAVE_DUP2 = @HAVE_DUP2@ +HAVE_DUP3 = @HAVE_DUP3@ +HAVE_DUPLOCALE = @HAVE_DUPLOCALE@ +HAVE_EUIDACCESS = @HAVE_EUIDACCESS@ +HAVE_FACCESSAT = @HAVE_FACCESSAT@ +HAVE_FCHDIR = @HAVE_FCHDIR@ +HAVE_FCHMODAT = @HAVE_FCHMODAT@ +HAVE_FCHOWNAT = @HAVE_FCHOWNAT@ +HAVE_FCNTL = @HAVE_FCNTL@ +HAVE_FDATASYNC = @HAVE_FDATASYNC@ +HAVE_FDOPENDIR = @HAVE_FDOPENDIR@ +HAVE_FEATURES_H = @HAVE_FEATURES_H@ +HAVE_FFSL = @HAVE_FFSL@ +HAVE_FFSLL = @HAVE_FFSLL@ +HAVE_FSEEKO = @HAVE_FSEEKO@ +HAVE_FSTATAT = @HAVE_FSTATAT@ +HAVE_FSYNC = @HAVE_FSYNC@ +HAVE_FTELLO = @HAVE_FTELLO@ +HAVE_FTRUNCATE = @HAVE_FTRUNCATE@ +HAVE_FUTIMENS = @HAVE_FUTIMENS@ +HAVE_GCJ = @HAVE_GCJ@ +HAVE_GCJ_C = @HAVE_GCJ_C@ +HAVE_GCJ_IN_PATH = @HAVE_GCJ_IN_PATH@ +HAVE_GETDTABLESIZE = @HAVE_GETDTABLESIZE@ +HAVE_GETGROUPS = @HAVE_GETGROUPS@ +HAVE_GETHOSTNAME = @HAVE_GETHOSTNAME@ +HAVE_GETLOGIN = @HAVE_GETLOGIN@ +HAVE_GETOPT_H = @HAVE_GETOPT_H@ +HAVE_GETPAGESIZE = @HAVE_GETPAGESIZE@ +HAVE_GETSUBOPT = @HAVE_GETSUBOPT@ +HAVE_GETTIMEOFDAY = @HAVE_GETTIMEOFDAY@ +HAVE_GIJ = @HAVE_GIJ@ +HAVE_GIJ_IN_PATH = @HAVE_GIJ_IN_PATH@ +HAVE_GLOBAL_SYMBOL_PIPE = @HAVE_GLOBAL_SYMBOL_PIPE@ +HAVE_GRANTPT = @HAVE_GRANTPT@ +HAVE_GROUP_MEMBER = @HAVE_GROUP_MEMBER@ +HAVE_ILRUN = @HAVE_ILRUN@ +HAVE_ILRUN_IN_PATH = @HAVE_ILRUN_IN_PATH@ +HAVE_INTTYPES_H = @HAVE_INTTYPES_H@ +HAVE_ISWBLANK = @HAVE_ISWBLANK@ +HAVE_ISWCNTRL = @HAVE_ISWCNTRL@ +HAVE_JAVA = @HAVE_JAVA@ +HAVE_JAVAC = @HAVE_JAVAC@ +HAVE_JAVAC_ENVVAR = @HAVE_JAVAC_ENVVAR@ +HAVE_JAVAC_IN_PATH = @HAVE_JAVAC_IN_PATH@ +HAVE_JAVA_ENVVAR = @HAVE_JAVA_ENVVAR@ +HAVE_JAVA_IN_PATH = @HAVE_JAVA_IN_PATH@ +HAVE_JIKES = @HAVE_JIKES@ +HAVE_JIKES_IN_PATH = @HAVE_JIKES_IN_PATH@ +HAVE_JRE = @HAVE_JRE@ +HAVE_JRE_IN_PATH = @HAVE_JRE_IN_PATH@ +HAVE_JVIEW = @HAVE_JVIEW@ +HAVE_JVIEW_IN_PATH = @HAVE_JVIEW_IN_PATH@ +HAVE_LANGINFO_CODESET = @HAVE_LANGINFO_CODESET@ +HAVE_LANGINFO_ERA = @HAVE_LANGINFO_ERA@ +HAVE_LANGINFO_H = @HAVE_LANGINFO_H@ +HAVE_LANGINFO_T_FMT_AMPM = @HAVE_LANGINFO_T_FMT_AMPM@ +HAVE_LANGINFO_YESEXPR = @HAVE_LANGINFO_YESEXPR@ +HAVE_LCHMOD = @HAVE_LCHMOD@ +HAVE_LCHOWN = @HAVE_LCHOWN@ +HAVE_LIBEXPAT = @HAVE_LIBEXPAT@ +HAVE_LIBUNISTRING = @HAVE_LIBUNISTRING@ +HAVE_LINK = @HAVE_LINK@ +HAVE_LINKAT = @HAVE_LINKAT@ +HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@ +HAVE_LSTAT = @HAVE_LSTAT@ +HAVE_MBRLEN = @HAVE_MBRLEN@ +HAVE_MBRTOWC = @HAVE_MBRTOWC@ +HAVE_MBSINIT = @HAVE_MBSINIT@ +HAVE_MBSLEN = @HAVE_MBSLEN@ +HAVE_MBSNRTOWCS = @HAVE_MBSNRTOWCS@ +HAVE_MBSRTOWCS = @HAVE_MBSRTOWCS@ +HAVE_MCS = @HAVE_MCS@ +HAVE_MCS_IN_PATH = @HAVE_MCS_IN_PATH@ +HAVE_MEMCHR = @HAVE_MEMCHR@ +HAVE_MEMPCPY = @HAVE_MEMPCPY@ +HAVE_MKDIRAT = @HAVE_MKDIRAT@ +HAVE_MKDTEMP = @HAVE_MKDTEMP@ +HAVE_MKFIFO = @HAVE_MKFIFO@ +HAVE_MKFIFOAT = @HAVE_MKFIFOAT@ +HAVE_MKNOD = @HAVE_MKNOD@ +HAVE_MKNODAT = @HAVE_MKNODAT@ +HAVE_MKOSTEMP = @HAVE_MKOSTEMP@ +HAVE_MKOSTEMPS = @HAVE_MKOSTEMPS@ +HAVE_MKSTEMP = @HAVE_MKSTEMP@ +HAVE_MKSTEMPS = @HAVE_MKSTEMPS@ +HAVE_MONO = @HAVE_MONO@ +HAVE_MONO_IN_PATH = @HAVE_MONO_IN_PATH@ +HAVE_MSVC_INVALID_PARAMETER_HANDLER = @HAVE_MSVC_INVALID_PARAMETER_HANDLER@ +HAVE_NANOSLEEP = @HAVE_NANOSLEEP@ +HAVE_NEWLOCALE = @HAVE_NEWLOCALE@ +HAVE_NL_LANGINFO = @HAVE_NL_LANGINFO@ +HAVE_OPENAT = @HAVE_OPENAT@ +HAVE_OPENDIR = @HAVE_OPENDIR@ +HAVE_OS_H = @HAVE_OS_H@ +HAVE_PCLOSE = @HAVE_PCLOSE@ +HAVE_PIPE = @HAVE_PIPE@ +HAVE_PIPE2 = @HAVE_PIPE2@ +HAVE_POPEN = @HAVE_POPEN@ +HAVE_POSIX_OPENPT = @HAVE_POSIX_OPENPT@ +HAVE_POSIX_PRINTF = @HAVE_POSIX_PRINTF@ +HAVE_POSIX_SIGNALBLOCKING = @HAVE_POSIX_SIGNALBLOCKING@ +HAVE_POSIX_SPAWN = @HAVE_POSIX_SPAWN@ +HAVE_POSIX_SPAWNATTR_T = @HAVE_POSIX_SPAWNATTR_T@ +HAVE_POSIX_SPAWN_FILE_ACTIONS_T = @HAVE_POSIX_SPAWN_FILE_ACTIONS_T@ +HAVE_PREAD = @HAVE_PREAD@ +HAVE_PSELECT = @HAVE_PSELECT@ +HAVE_PTHREAD_SIGMASK = @HAVE_PTHREAD_SIGMASK@ +HAVE_PTSNAME = @HAVE_PTSNAME@ +HAVE_PTSNAME_R = @HAVE_PTSNAME_R@ +HAVE_PWRITE = @HAVE_PWRITE@ +HAVE_RAISE = @HAVE_RAISE@ +HAVE_RANDOM = @HAVE_RANDOM@ +HAVE_RANDOM_H = @HAVE_RANDOM_H@ +HAVE_RANDOM_R = @HAVE_RANDOM_R@ +HAVE_RAWMEMCHR = @HAVE_RAWMEMCHR@ +HAVE_READDIR = @HAVE_READDIR@ +HAVE_READLINK = @HAVE_READLINK@ +HAVE_READLINKAT = @HAVE_READLINKAT@ +HAVE_REALPATH = @HAVE_REALPATH@ +HAVE_RENAMEAT = @HAVE_RENAMEAT@ +HAVE_REWINDDIR = @HAVE_REWINDDIR@ +HAVE_RPMATCH = @HAVE_RPMATCH@ +HAVE_SCANDIR = @HAVE_SCANDIR@ +HAVE_SCHED_H = @HAVE_SCHED_H@ +HAVE_SECURE_GETENV = @HAVE_SECURE_GETENV@ +HAVE_SETENV = @HAVE_SETENV@ +HAVE_SETHOSTNAME = @HAVE_SETHOSTNAME@ +HAVE_SIGACTION = @HAVE_SIGACTION@ +HAVE_SIGHANDLER_T = @HAVE_SIGHANDLER_T@ +HAVE_SIGINFO_T = @HAVE_SIGINFO_T@ +HAVE_SIGNED_SIG_ATOMIC_T = @HAVE_SIGNED_SIG_ATOMIC_T@ +HAVE_SIGNED_WCHAR_T = @HAVE_SIGNED_WCHAR_T@ +HAVE_SIGNED_WINT_T = @HAVE_SIGNED_WINT_T@ +HAVE_SIGSET_T = @HAVE_SIGSET_T@ +HAVE_SLEEP = @HAVE_SLEEP@ +HAVE_SNPRINTF = @HAVE_SNPRINTF@ +HAVE_SPAWN_H = @HAVE_SPAWN_H@ +HAVE_STDINT_H = @HAVE_STDINT_H@ +HAVE_STPCPY = @HAVE_STPCPY@ +HAVE_STPNCPY = @HAVE_STPNCPY@ +HAVE_STRCASESTR = @HAVE_STRCASESTR@ +HAVE_STRCHRNUL = @HAVE_STRCHRNUL@ +HAVE_STRPBRK = @HAVE_STRPBRK@ +HAVE_STRPTIME = @HAVE_STRPTIME@ +HAVE_STRSEP = @HAVE_STRSEP@ +HAVE_STRTOD = @HAVE_STRTOD@ +HAVE_STRTOLL = @HAVE_STRTOLL@ +HAVE_STRTOULL = @HAVE_STRTOULL@ +HAVE_STRUCT_RANDOM_DATA = @HAVE_STRUCT_RANDOM_DATA@ +HAVE_STRUCT_SCHED_PARAM = @HAVE_STRUCT_SCHED_PARAM@ +HAVE_STRUCT_SIGACTION_SA_SIGACTION = @HAVE_STRUCT_SIGACTION_SA_SIGACTION@ +HAVE_STRUCT_TIMEVAL = @HAVE_STRUCT_TIMEVAL@ +HAVE_STRVERSCMP = @HAVE_STRVERSCMP@ +HAVE_SYMLINK = @HAVE_SYMLINK@ +HAVE_SYMLINKAT = @HAVE_SYMLINKAT@ +HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@ +HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@ +HAVE_SYS_LOADAVG_H = @HAVE_SYS_LOADAVG_H@ +HAVE_SYS_PARAM_H = @HAVE_SYS_PARAM_H@ +HAVE_SYS_SELECT_H = @HAVE_SYS_SELECT_H@ +HAVE_SYS_TIME_H = @HAVE_SYS_TIME_H@ +HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@ +HAVE_TIMEGM = @HAVE_TIMEGM@ +HAVE_TYPE_VOLATILE_SIG_ATOMIC_T = @HAVE_TYPE_VOLATILE_SIG_ATOMIC_T@ +HAVE_UNISTD_H = @HAVE_UNISTD_H@ +HAVE_UNLINKAT = @HAVE_UNLINKAT@ +HAVE_UNLOCKPT = @HAVE_UNLOCKPT@ +HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@ +HAVE_USLEEP = @HAVE_USLEEP@ +HAVE_UTIMENSAT = @HAVE_UTIMENSAT@ +HAVE_VASPRINTF = @HAVE_VASPRINTF@ +HAVE_VDPRINTF = @HAVE_VDPRINTF@ +HAVE_VISIBILITY = @HAVE_VISIBILITY@ +HAVE_WCHAR_H = @HAVE_WCHAR_H@ +HAVE_WCHAR_T = @HAVE_WCHAR_T@ +HAVE_WCPCPY = @HAVE_WCPCPY@ +HAVE_WCPNCPY = @HAVE_WCPNCPY@ +HAVE_WCRTOMB = @HAVE_WCRTOMB@ +HAVE_WCSCASECMP = @HAVE_WCSCASECMP@ +HAVE_WCSCAT = @HAVE_WCSCAT@ +HAVE_WCSCHR = @HAVE_WCSCHR@ +HAVE_WCSCMP = @HAVE_WCSCMP@ +HAVE_WCSCOLL = @HAVE_WCSCOLL@ +HAVE_WCSCPY = @HAVE_WCSCPY@ +HAVE_WCSCSPN = @HAVE_WCSCSPN@ +HAVE_WCSDUP = @HAVE_WCSDUP@ +HAVE_WCSLEN = @HAVE_WCSLEN@ +HAVE_WCSNCASECMP = @HAVE_WCSNCASECMP@ +HAVE_WCSNCAT = @HAVE_WCSNCAT@ +HAVE_WCSNCMP = @HAVE_WCSNCMP@ +HAVE_WCSNCPY = @HAVE_WCSNCPY@ +HAVE_WCSNLEN = @HAVE_WCSNLEN@ +HAVE_WCSNRTOMBS = @HAVE_WCSNRTOMBS@ +HAVE_WCSPBRK = @HAVE_WCSPBRK@ +HAVE_WCSRCHR = @HAVE_WCSRCHR@ +HAVE_WCSRTOMBS = @HAVE_WCSRTOMBS@ +HAVE_WCSSPN = @HAVE_WCSSPN@ +HAVE_WCSSTR = @HAVE_WCSSTR@ +HAVE_WCSTOK = @HAVE_WCSTOK@ +HAVE_WCSWIDTH = @HAVE_WCSWIDTH@ +HAVE_WCSXFRM = @HAVE_WCSXFRM@ +HAVE_WCTRANS_T = @HAVE_WCTRANS_T@ +HAVE_WCTYPE_H = @HAVE_WCTYPE_H@ +HAVE_WCTYPE_T = @HAVE_WCTYPE_T@ +HAVE_WINSOCK2_H = @HAVE_WINSOCK2_H@ +HAVE_WINT_T = @HAVE_WINT_T@ +HAVE_WMEMCHR = @HAVE_WMEMCHR@ +HAVE_WMEMCMP = @HAVE_WMEMCMP@ +HAVE_WMEMCPY = @HAVE_WMEMCPY@ +HAVE_WMEMMOVE = @HAVE_WMEMMOVE@ +HAVE_WMEMSET = @HAVE_WMEMSET@ +HAVE_WPRINTF = @HAVE_WPRINTF@ +HAVE_XLOCALE_H = @HAVE_XLOCALE_H@ +HAVE__BOOL = @HAVE__BOOL@ +HAVE__EXIT = @HAVE__EXIT@ +ICONV_CONST = @ICONV_CONST@ +ICONV_H = @ICONV_H@ +INCCROCO = @INCCROCO@ +INCGLIB = @INCGLIB@ +INCLUDE_NEXT = @INCLUDE_NEXT@ +INCLUDE_NEXT_AS_FIRST_DIRECTIVE = @INCLUDE_NEXT_AS_FIRST_DIRECTIVE@ +INCTERMINFO = @INCTERMINFO@ +INCXML = @INCXML@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_PROGRAM_ENV = @INSTALL_PROGRAM_ENV@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INT32_MAX_LT_INTMAX_MAX = @INT32_MAX_LT_INTMAX_MAX@ +INT64_MAX_EQ_LONG_MAX = @INT64_MAX_EQ_LONG_MAX@ +INTLBISON = @INTLBISON@ +INTLLIBS = @INTLLIBS@ +INTLOBJS = @INTLOBJS@ +INTL_DEFAULT_VERBOSITY = @INTL_DEFAULT_VERBOSITY@ +INTL_LIBTOOL_SUFFIX_PREFIX = @INTL_LIBTOOL_SUFFIX_PREFIX@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +JAR = @JAR@ +JAVA_CHOICE = @JAVA_CHOICE@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBCROCO = @LIBCROCO@ +LIBEXPAT = @LIBEXPAT@ +LIBEXPAT_PREFIX = @LIBEXPAT_PREFIX@ +LIBGLIB = @LIBGLIB@ +LIBGLIB_H = @LIBGLIB_H@ +LIBGREP_LIBDEPS = @LIBGREP_LIBDEPS@ +LIBGREP_LTLIBDEPS = @LIBGREP_LTLIBDEPS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBMULTITHREAD = @LIBMULTITHREAD@ +LIBOBJS = @LIBOBJS@ +LIBPTH = @LIBPTH@ +LIBPTH_PREFIX = @LIBPTH_PREFIX@ +LIBS = @LIBS@ +LIBTERMINFO = @LIBTERMINFO@ +LIBTESTS_LIBDEPS = @LIBTESTS_LIBDEPS@ +LIBTHREAD = @LIBTHREAD@ +LIBTOOL = @LIBTOOL@ +LIBUNISTRING = @LIBUNISTRING@ +LIBUNISTRING_PREFIX = @LIBUNISTRING_PREFIX@ +LIBUNISTRING_UNICONV_H = @LIBUNISTRING_UNICONV_H@ +LIBUNISTRING_UNILBRK_H = @LIBUNISTRING_UNILBRK_H@ +LIBUNISTRING_UNINAME_H = @LIBUNISTRING_UNINAME_H@ +LIBUNISTRING_UNISTR_H = @LIBUNISTRING_UNISTR_H@ +LIBUNISTRING_UNITYPES_H = @LIBUNISTRING_UNITYPES_H@ +LIBUNISTRING_UNIWIDTH_H = @LIBUNISTRING_UNIWIDTH_H@ +LIBXML = @LIBXML@ +LIBXML_H = @LIBXML_H@ +LIB_ACL = @LIB_ACL@ +LIB_POSIX_SPAWN = @LIB_POSIX_SPAWN@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOCALCHARSET_TESTS_ENVIRONMENT = @LOCALCHARSET_TESTS_ENVIRONMENT@ +LOCALE_FR = @LOCALE_FR@ +LOCALE_FR_UTF8 = @LOCALE_FR_UTF8@ +LOCALE_JA = @LOCALE_JA@ +LOCALE_TR_UTF8 = @LOCALE_TR_UTF8@ +LOCALE_ZH_CN = @LOCALE_ZH_CN@ +LTLIBC = @LTLIBC@ +LTLIBCROCO = @LTLIBCROCO@ +LTLIBEXPAT = @LTLIBEXPAT@ +LTLIBGLIB = @LTLIBGLIB@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBMULTITHREAD = @LTLIBMULTITHREAD@ +LTLIBOBJS = @LTLIBOBJS@ +LTLIBPTH = @LTLIBPTH@ +LTLIBTERMINFO = @LTLIBTERMINFO@ +LTLIBTHREAD = @LTLIBTHREAD@ +LTLIBUNISTRING = @LTLIBUNISTRING@ +LTLIBXML = @LTLIBXML@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MONO_PATH = @MONO_PATH@ +MONO_PATH_SEPARATOR = @MONO_PATH_SEPARATOR@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +MSGMERGE_LIBM = @MSGMERGE_LIBM@ +NEXT_AS_FIRST_DIRECTIVE_DIRENT_H = @NEXT_AS_FIRST_DIRECTIVE_DIRENT_H@ +NEXT_AS_FIRST_DIRECTIVE_ERRNO_H = @NEXT_AS_FIRST_DIRECTIVE_ERRNO_H@ +NEXT_AS_FIRST_DIRECTIVE_FCNTL_H = @NEXT_AS_FIRST_DIRECTIVE_FCNTL_H@ +NEXT_AS_FIRST_DIRECTIVE_FLOAT_H = @NEXT_AS_FIRST_DIRECTIVE_FLOAT_H@ +NEXT_AS_FIRST_DIRECTIVE_GETOPT_H = @NEXT_AS_FIRST_DIRECTIVE_GETOPT_H@ +NEXT_AS_FIRST_DIRECTIVE_ICONV_H = @NEXT_AS_FIRST_DIRECTIVE_ICONV_H@ +NEXT_AS_FIRST_DIRECTIVE_INTTYPES_H = @NEXT_AS_FIRST_DIRECTIVE_INTTYPES_H@ +NEXT_AS_FIRST_DIRECTIVE_LANGINFO_H = @NEXT_AS_FIRST_DIRECTIVE_LANGINFO_H@ +NEXT_AS_FIRST_DIRECTIVE_LOCALE_H = @NEXT_AS_FIRST_DIRECTIVE_LOCALE_H@ +NEXT_AS_FIRST_DIRECTIVE_SCHED_H = @NEXT_AS_FIRST_DIRECTIVE_SCHED_H@ +NEXT_AS_FIRST_DIRECTIVE_SIGNAL_H = @NEXT_AS_FIRST_DIRECTIVE_SIGNAL_H@ +NEXT_AS_FIRST_DIRECTIVE_SPAWN_H = @NEXT_AS_FIRST_DIRECTIVE_SPAWN_H@ +NEXT_AS_FIRST_DIRECTIVE_STDARG_H = @NEXT_AS_FIRST_DIRECTIVE_STDARG_H@ +NEXT_AS_FIRST_DIRECTIVE_STDDEF_H = @NEXT_AS_FIRST_DIRECTIVE_STDDEF_H@ +NEXT_AS_FIRST_DIRECTIVE_STDINT_H = @NEXT_AS_FIRST_DIRECTIVE_STDINT_H@ +NEXT_AS_FIRST_DIRECTIVE_STDIO_H = @NEXT_AS_FIRST_DIRECTIVE_STDIO_H@ +NEXT_AS_FIRST_DIRECTIVE_STDLIB_H = @NEXT_AS_FIRST_DIRECTIVE_STDLIB_H@ +NEXT_AS_FIRST_DIRECTIVE_STRING_H = @NEXT_AS_FIRST_DIRECTIVE_STRING_H@ +NEXT_AS_FIRST_DIRECTIVE_SYS_SELECT_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_SELECT_H@ +NEXT_AS_FIRST_DIRECTIVE_SYS_STAT_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_STAT_H@ +NEXT_AS_FIRST_DIRECTIVE_SYS_TIME_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_TIME_H@ +NEXT_AS_FIRST_DIRECTIVE_SYS_TYPES_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_TYPES_H@ +NEXT_AS_FIRST_DIRECTIVE_SYS_WAIT_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_WAIT_H@ +NEXT_AS_FIRST_DIRECTIVE_TIME_H = @NEXT_AS_FIRST_DIRECTIVE_TIME_H@ +NEXT_AS_FIRST_DIRECTIVE_UNISTD_H = @NEXT_AS_FIRST_DIRECTIVE_UNISTD_H@ +NEXT_AS_FIRST_DIRECTIVE_WCHAR_H = @NEXT_AS_FIRST_DIRECTIVE_WCHAR_H@ +NEXT_AS_FIRST_DIRECTIVE_WCTYPE_H = @NEXT_AS_FIRST_DIRECTIVE_WCTYPE_H@ +NEXT_DIRENT_H = @NEXT_DIRENT_H@ +NEXT_ERRNO_H = @NEXT_ERRNO_H@ +NEXT_FCNTL_H = @NEXT_FCNTL_H@ +NEXT_FLOAT_H = @NEXT_FLOAT_H@ +NEXT_GETOPT_H = @NEXT_GETOPT_H@ +NEXT_ICONV_H = @NEXT_ICONV_H@ +NEXT_INTTYPES_H = @NEXT_INTTYPES_H@ +NEXT_LANGINFO_H = @NEXT_LANGINFO_H@ +NEXT_LOCALE_H = @NEXT_LOCALE_H@ +NEXT_SCHED_H = @NEXT_SCHED_H@ +NEXT_SIGNAL_H = @NEXT_SIGNAL_H@ +NEXT_SPAWN_H = @NEXT_SPAWN_H@ +NEXT_STDARG_H = @NEXT_STDARG_H@ +NEXT_STDDEF_H = @NEXT_STDDEF_H@ +NEXT_STDINT_H = @NEXT_STDINT_H@ +NEXT_STDIO_H = @NEXT_STDIO_H@ +NEXT_STDLIB_H = @NEXT_STDLIB_H@ +NEXT_STRING_H = @NEXT_STRING_H@ +NEXT_SYS_SELECT_H = @NEXT_SYS_SELECT_H@ +NEXT_SYS_STAT_H = @NEXT_SYS_STAT_H@ +NEXT_SYS_TIME_H = @NEXT_SYS_TIME_H@ +NEXT_SYS_TYPES_H = @NEXT_SYS_TYPES_H@ +NEXT_SYS_WAIT_H = @NEXT_SYS_WAIT_H@ +NEXT_TIME_H = @NEXT_TIME_H@ +NEXT_UNISTD_H = @NEXT_UNISTD_H@ +NEXT_WCHAR_H = @NEXT_WCHAR_H@ +NEXT_WCTYPE_H = @NEXT_WCTYPE_H@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NO_CXX = @NO_CXX@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENMP_CFLAGS = @OPENMP_CFLAGS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +POSUB = @POSUB@ +PRAGMA_COLUMNS = @PRAGMA_COLUMNS@ +PRAGMA_SYSTEM_HEADER = @PRAGMA_SYSTEM_HEADER@ +PRIPTR_PREFIX = @PRIPTR_PREFIX@ +PRI_MACROS_BROKEN = @PRI_MACROS_BROKEN@ +PTHREAD_H_DEFINES_STRUCT_TIMESPEC = @PTHREAD_H_DEFINES_STRUCT_TIMESPEC@ +PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@ +RANLIB = @RANLIB@ +RC = @RC@ +RELOCATABLE = @RELOCATABLE@ +RELOCATABLE_BUILD_DIR = ../gnulib-lib +RELOCATABLE_CONFIG_H_DIR = .. +RELOCATABLE_LDFLAGS = @RELOCATABLE_LDFLAGS@ + +# Support for relocatability. +RELOCATABLE_LIBRARY_PATH = $(libdir) +RELOCATABLE_SRC_DIR = $(top_srcdir)/gnulib-lib +RELOCATABLE_STRIP = : +REPLACE_BTOWC = @REPLACE_BTOWC@ +REPLACE_CALLOC = @REPLACE_CALLOC@ +REPLACE_CANONICALIZE_FILE_NAME = @REPLACE_CANONICALIZE_FILE_NAME@ +REPLACE_CHOWN = @REPLACE_CHOWN@ +REPLACE_CLOSE = @REPLACE_CLOSE@ +REPLACE_CLOSEDIR = @REPLACE_CLOSEDIR@ +REPLACE_DIRFD = @REPLACE_DIRFD@ +REPLACE_DPRINTF = @REPLACE_DPRINTF@ +REPLACE_DUP = @REPLACE_DUP@ +REPLACE_DUP2 = @REPLACE_DUP2@ +REPLACE_DUPLOCALE = @REPLACE_DUPLOCALE@ +REPLACE_FCHOWNAT = @REPLACE_FCHOWNAT@ +REPLACE_FCLOSE = @REPLACE_FCLOSE@ +REPLACE_FCNTL = @REPLACE_FCNTL@ +REPLACE_FDOPEN = @REPLACE_FDOPEN@ +REPLACE_FDOPENDIR = @REPLACE_FDOPENDIR@ +REPLACE_FFLUSH = @REPLACE_FFLUSH@ +REPLACE_FOPEN = @REPLACE_FOPEN@ +REPLACE_FPRINTF = @REPLACE_FPRINTF@ +REPLACE_FPURGE = @REPLACE_FPURGE@ +REPLACE_FREOPEN = @REPLACE_FREOPEN@ +REPLACE_FSEEK = @REPLACE_FSEEK@ +REPLACE_FSEEKO = @REPLACE_FSEEKO@ +REPLACE_FSTAT = @REPLACE_FSTAT@ +REPLACE_FSTATAT = @REPLACE_FSTATAT@ +REPLACE_FTELL = @REPLACE_FTELL@ +REPLACE_FTELLO = @REPLACE_FTELLO@ +REPLACE_FTRUNCATE = @REPLACE_FTRUNCATE@ +REPLACE_FUTIMENS = @REPLACE_FUTIMENS@ +REPLACE_GETCWD = @REPLACE_GETCWD@ +REPLACE_GETDELIM = @REPLACE_GETDELIM@ +REPLACE_GETDOMAINNAME = @REPLACE_GETDOMAINNAME@ +REPLACE_GETDTABLESIZE = @REPLACE_GETDTABLESIZE@ +REPLACE_GETGROUPS = @REPLACE_GETGROUPS@ +REPLACE_GETLINE = @REPLACE_GETLINE@ +REPLACE_GETLOGIN_R = @REPLACE_GETLOGIN_R@ +REPLACE_GETPAGESIZE = @REPLACE_GETPAGESIZE@ +REPLACE_GETTIMEOFDAY = @REPLACE_GETTIMEOFDAY@ +REPLACE_GMTIME = @REPLACE_GMTIME@ +REPLACE_ICONV = @REPLACE_ICONV@ +REPLACE_ICONV_OPEN = @REPLACE_ICONV_OPEN@ +REPLACE_ICONV_UTF = @REPLACE_ICONV_UTF@ +REPLACE_ISATTY = @REPLACE_ISATTY@ +REPLACE_ISWBLANK = @REPLACE_ISWBLANK@ +REPLACE_ISWCNTRL = @REPLACE_ISWCNTRL@ +REPLACE_ITOLD = @REPLACE_ITOLD@ +REPLACE_LCHOWN = @REPLACE_LCHOWN@ +REPLACE_LINK = @REPLACE_LINK@ +REPLACE_LINKAT = @REPLACE_LINKAT@ +REPLACE_LOCALECONV = @REPLACE_LOCALECONV@ +REPLACE_LOCALTIME = @REPLACE_LOCALTIME@ +REPLACE_LOCALTIME_R = @REPLACE_LOCALTIME_R@ +REPLACE_LSEEK = @REPLACE_LSEEK@ +REPLACE_LSTAT = @REPLACE_LSTAT@ +REPLACE_MALLOC = @REPLACE_MALLOC@ +REPLACE_MBRLEN = @REPLACE_MBRLEN@ +REPLACE_MBRTOWC = @REPLACE_MBRTOWC@ +REPLACE_MBSINIT = @REPLACE_MBSINIT@ +REPLACE_MBSNRTOWCS = @REPLACE_MBSNRTOWCS@ +REPLACE_MBSRTOWCS = @REPLACE_MBSRTOWCS@ +REPLACE_MBSTATE_T = @REPLACE_MBSTATE_T@ +REPLACE_MBTOWC = @REPLACE_MBTOWC@ +REPLACE_MEMCHR = @REPLACE_MEMCHR@ +REPLACE_MEMMEM = @REPLACE_MEMMEM@ +REPLACE_MKDIR = @REPLACE_MKDIR@ +REPLACE_MKFIFO = @REPLACE_MKFIFO@ +REPLACE_MKNOD = @REPLACE_MKNOD@ +REPLACE_MKSTEMP = @REPLACE_MKSTEMP@ +REPLACE_MKTIME = @REPLACE_MKTIME@ +REPLACE_NANOSLEEP = @REPLACE_NANOSLEEP@ +REPLACE_NL_LANGINFO = @REPLACE_NL_LANGINFO@ +REPLACE_NULL = @REPLACE_NULL@ +REPLACE_OBSTACK_PRINTF = @REPLACE_OBSTACK_PRINTF@ +REPLACE_OPEN = @REPLACE_OPEN@ +REPLACE_OPENAT = @REPLACE_OPENAT@ +REPLACE_OPENDIR = @REPLACE_OPENDIR@ +REPLACE_PERROR = @REPLACE_PERROR@ +REPLACE_POPEN = @REPLACE_POPEN@ +REPLACE_POSIX_SPAWN = @REPLACE_POSIX_SPAWN@ +REPLACE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSE = @REPLACE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSE@ +REPLACE_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2 = @REPLACE_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2@ +REPLACE_POSIX_SPAWN_FILE_ACTIONS_ADDOPEN = @REPLACE_POSIX_SPAWN_FILE_ACTIONS_ADDOPEN@ +REPLACE_PREAD = @REPLACE_PREAD@ +REPLACE_PRINTF = @REPLACE_PRINTF@ +REPLACE_PSELECT = @REPLACE_PSELECT@ +REPLACE_PTHREAD_SIGMASK = @REPLACE_PTHREAD_SIGMASK@ +REPLACE_PTSNAME = @REPLACE_PTSNAME@ +REPLACE_PTSNAME_R = @REPLACE_PTSNAME_R@ +REPLACE_PUTENV = @REPLACE_PUTENV@ +REPLACE_PWRITE = @REPLACE_PWRITE@ +REPLACE_QSORT_R = @REPLACE_QSORT_R@ +REPLACE_RAISE = @REPLACE_RAISE@ +REPLACE_RANDOM_R = @REPLACE_RANDOM_R@ +REPLACE_READ = @REPLACE_READ@ +REPLACE_READLINK = @REPLACE_READLINK@ +REPLACE_READLINKAT = @REPLACE_READLINKAT@ +REPLACE_REALLOC = @REPLACE_REALLOC@ +REPLACE_REALPATH = @REPLACE_REALPATH@ +REPLACE_REMOVE = @REPLACE_REMOVE@ +REPLACE_RENAME = @REPLACE_RENAME@ +REPLACE_RENAMEAT = @REPLACE_RENAMEAT@ +REPLACE_RMDIR = @REPLACE_RMDIR@ +REPLACE_SELECT = @REPLACE_SELECT@ +REPLACE_SETENV = @REPLACE_SETENV@ +REPLACE_SETLOCALE = @REPLACE_SETLOCALE@ +REPLACE_SLEEP = @REPLACE_SLEEP@ +REPLACE_SNPRINTF = @REPLACE_SNPRINTF@ +REPLACE_SPRINTF = @REPLACE_SPRINTF@ +REPLACE_STAT = @REPLACE_STAT@ +REPLACE_STDIO_READ_FUNCS = @REPLACE_STDIO_READ_FUNCS@ +REPLACE_STDIO_WRITE_FUNCS = @REPLACE_STDIO_WRITE_FUNCS@ +REPLACE_STPNCPY = @REPLACE_STPNCPY@ +REPLACE_STRCASESTR = @REPLACE_STRCASESTR@ +REPLACE_STRCHRNUL = @REPLACE_STRCHRNUL@ +REPLACE_STRDUP = @REPLACE_STRDUP@ +REPLACE_STRERROR = @REPLACE_STRERROR@ +REPLACE_STRERROR_R = @REPLACE_STRERROR_R@ +REPLACE_STRNCAT = @REPLACE_STRNCAT@ +REPLACE_STRNDUP = @REPLACE_STRNDUP@ +REPLACE_STRNLEN = @REPLACE_STRNLEN@ +REPLACE_STRSIGNAL = @REPLACE_STRSIGNAL@ +REPLACE_STRSTR = @REPLACE_STRSTR@ +REPLACE_STRTOD = @REPLACE_STRTOD@ +REPLACE_STRTOIMAX = @REPLACE_STRTOIMAX@ +REPLACE_STRTOK_R = @REPLACE_STRTOK_R@ +REPLACE_STRTOUMAX = @REPLACE_STRTOUMAX@ +REPLACE_STRUCT_LCONV = @REPLACE_STRUCT_LCONV@ +REPLACE_STRUCT_TIMEVAL = @REPLACE_STRUCT_TIMEVAL@ +REPLACE_SYMLINK = @REPLACE_SYMLINK@ +REPLACE_SYMLINKAT = @REPLACE_SYMLINKAT@ +REPLACE_TIMEGM = @REPLACE_TIMEGM@ +REPLACE_TMPFILE = @REPLACE_TMPFILE@ +REPLACE_TOWLOWER = @REPLACE_TOWLOWER@ +REPLACE_TTYNAME_R = @REPLACE_TTYNAME_R@ +REPLACE_UNLINK = @REPLACE_UNLINK@ +REPLACE_UNLINKAT = @REPLACE_UNLINKAT@ +REPLACE_UNSETENV = @REPLACE_UNSETENV@ +REPLACE_USLEEP = @REPLACE_USLEEP@ +REPLACE_UTIMENSAT = @REPLACE_UTIMENSAT@ +REPLACE_VASPRINTF = @REPLACE_VASPRINTF@ +REPLACE_VDPRINTF = @REPLACE_VDPRINTF@ +REPLACE_VFPRINTF = @REPLACE_VFPRINTF@ +REPLACE_VPRINTF = @REPLACE_VPRINTF@ +REPLACE_VSNPRINTF = @REPLACE_VSNPRINTF@ +REPLACE_VSPRINTF = @REPLACE_VSPRINTF@ +REPLACE_WCRTOMB = @REPLACE_WCRTOMB@ +REPLACE_WCSNRTOMBS = @REPLACE_WCSNRTOMBS@ +REPLACE_WCSRTOMBS = @REPLACE_WCSRTOMBS@ +REPLACE_WCSWIDTH = @REPLACE_WCSWIDTH@ +REPLACE_WCTOB = @REPLACE_WCTOB@ +REPLACE_WCTOMB = @REPLACE_WCTOMB@ +REPLACE_WCWIDTH = @REPLACE_WCWIDTH@ +REPLACE_WRITE = @REPLACE_WRITE@ +SCHED_H = @SCHED_H@ +SED = sed +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@ +SIZE_T_SUFFIX = @SIZE_T_SUFFIX@ +STDARG_H = @STDARG_H@ +STDBOOL_H = @STDBOOL_H@ +STDDEF_H = @STDDEF_H@ +STDINT_H = @STDINT_H@ +STRIP = @STRIP@ +SYS_TIME_H_DEFINES_STRUCT_TIMESPEC = @SYS_TIME_H_DEFINES_STRUCT_TIMESPEC@ +TESTCSHARP = @TESTCSHARP@ +TESTJAVA = @TESTJAVA@ +TESTLIBASPRINTF = @TESTLIBASPRINTF@ +TEXI2PDF = @TEXI2PDF@ +TIME_H_DEFINES_STRUCT_TIMESPEC = @TIME_H_DEFINES_STRUCT_TIMESPEC@ +UINT32_MAX_LT_UINTMAX_MAX = @UINT32_MAX_LT_UINTMAX_MAX@ +UINT64_MAX_EQ_ULONG_MAX = @UINT64_MAX_EQ_ULONG_MAX@ +UNDEFINE_STRTOK_R = @UNDEFINE_STRTOK_R@ +UNISTD_H_HAVE_WINSOCK2_H = @UNISTD_H_HAVE_WINSOCK2_H@ +UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS = @UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS@ +USE_ACL = @USE_ACL@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@ +WINDOWS_64_BIT_OFF_T = @WINDOWS_64_BIT_OFF_T@ +WINDOWS_64_BIT_ST_SIZE = @WINDOWS_64_BIT_ST_SIZE@ +WINDRES = @WINDRES@ +WINT_T_SUFFIX = @WINT_T_SUFFIX@ +WOE32 = @WOE32@ +WOE32DLL = @WOE32DLL@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +YACC = @YACC@ -d +YFLAGS = @YFLAGS@ +YIELD_LIB = @YIELD_LIB@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +aclocaldir = @aclocaldir@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +gl_LIBOBJS = @gl_LIBOBJS@ +gl_LTLIBOBJS = @gl_LTLIBOBJS@ +gltests_LIBOBJS = @gltests_LIBOBJS@ +gltests_LTLIBOBJS = @gltests_LTLIBOBJS@ +gltests_WITNESS = @gltests_WITNESS@ +grgl_LIBOBJS = @grgl_LIBOBJS@ +grgl_LTLIBOBJS = @grgl_LTLIBOBJS@ +grgltests_LIBOBJS = @grgltests_LIBOBJS@ +grgltests_LTLIBOBJS = @grgltests_LTLIBOBJS@ +grgltests_WITNESS = @grgltests_WITNESS@ +gtpo_LIBOBJS = @gtpo_LIBOBJS@ +gtpo_LTLIBOBJS = @gtpo_LTLIBOBJS@ +gtpotests_LIBOBJS = @gtpotests_LIBOBJS@ +gtpotests_LTLIBOBJS = @gtpotests_LTLIBOBJS@ +gtpotests_WITNESS = @gtpotests_WITNESS@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +lispdir = @lispdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = 1.5 gnits no-dependencies subdir-objects + +# Special rules for C# auxiliary programs. + +# Special rules for Tcl auxiliary program. +EXTRA_DIST = FILES project-id ChangeLog.0 \ + gnu/gettext/DumpResource.java gnu/gettext/GetURL.java \ + msgfmt.cs msgunfmt.cs msgunfmt.tcl +MOSTLYCLEANFILES = core *.stackdump $(am__append_4) +CLEANFILES = gnu.gettext.DumpResource$(EXEEXT) \ + gnu.gettext.GetURL$(EXEEXT) gettext.jar gnu/gettext/*.class \ + msgfmt.net.exe msgfmt.net.exe.mdb msgunfmt.net.exe \ + msgunfmt.net.exe.mdb +DISTCLEANFILES = user-email +RM = rm -f +lib_LTLIBRARIES = libgettextsrc.la +noinst_HEADERS = pos.h message.h po-error.h po-xerror.h po-gram.h po-charset.h \ +po-lex.h open-catalog.h read-catalog-abstract.h read-catalog.h \ +read-po.h read-properties.h read-stringtable.h \ +str-list.h \ +color.h write-catalog.h write-po.h write-properties.h write-stringtable.h \ +dir-list.h file-list.h po-gram-gen.h po-gram-gen2.h \ +msgl-charset.h msgl-equal.h msgl-iconv.h msgl-ascii.h msgl-cat.h msgl-header.h \ +msgl-english.h msgl-check.h msgl-fsearch.h msgfmt.h msgunfmt.h \ +plural-count.h plural-eval.h plural-distrib.h \ +read-mo.h write-mo.h \ +read-java.h write-java.h \ +read-csharp.h write-csharp.h \ +read-resources.h write-resources.h \ +read-tcl.h write-tcl.h \ +write-qt.h \ +read-desktop.h write-desktop.h \ +po-time.h plural-table.h lang-table.h format.h filters.h \ +xgettext.h x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \ +x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \ +x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-lua.h \ +x-javascript.h x-vala.h x-gsettings.h x-desktop.h libexpat-compat.h + +aliaspath = $(localedir) +jardir = $(datadir)/gettext +projectsdir = $(pkgdatadir)/projects +AM_CPPFLAGS = \ + -I. -I$(srcdir) \ + -I.. -I$(top_srcdir) \ + -I$(top_srcdir)/libgrep \ + -I../gnulib-lib -I$(top_srcdir)/gnulib-lib \ + -I../intl -I$(top_srcdir)/../gettext-runtime/intl + +# Ensure that defines SIZE_MAX in C++ mode, like it does in C mode. +AM_CXXFLAGS = -D__STDC_LIMIT_MACROS +LDADD = ../gnulib-lib/libgettextlib.la $(LTLIBUNISTRING) @LTLIBINTL@ $(WOE32_LDADD) +OTHERPROGDEPENDENCIES = ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +JAVACOMP = $(SHELL) ../javacomp.sh +CSHARPCOMP = $(SHELL) ../csharpcomp.sh + +# All programs deal with message lists. +# All programs must read PO files. (msgunfmt also, for read-java.c, +# read-csharp.c and read-resources.c.) +# message.c -> str-list.c. +# (read-catalog-abstract.c <--> po-gram-gen.y <--> po-lex.c) -> str-list.c. +# (read-catalog-abstract.c <--> po-gram-gen.y <--> po-lex.c) -> open-catalog.c -> dir-list.c -> str-list.c. +# (read-catalog-abstract.c <--> po-gram-gen.y <--> po-lex.c) -> po-charset.c. +# (read-catalog-abstract.c <--> po-gram-gen.y <--> po-lex.c) -> message.c -> str-list.c. +COMMON_SOURCE = message.c po-error.c po-xerror.c \ +read-catalog-abstract.c po-lex.c po-gram-gen.y po-charset.c \ +read-po.c read-properties.c read-stringtable.c open-catalog.c \ +dir-list.c str-list.c + + +# xgettext and msgfmt deal with format strings. +@WOE32DLL_FALSE@FORMAT_SOURCE = format.c format-invalid.h format-c.c \ +@WOE32DLL_FALSE@ format-c-parse.h format-sh.c format-python.c \ +@WOE32DLL_FALSE@ format-python-brace.c format-lisp.c \ +@WOE32DLL_FALSE@ format-elisp.c format-librep.c format-scheme.c \ +@WOE32DLL_FALSE@ format-java.c format-csharp.c format-awk.c \ +@WOE32DLL_FALSE@ format-pascal.c format-ycp.c format-tcl.c \ +@WOE32DLL_FALSE@ format-perl.c format-perl-brace.c format-php.c \ +@WOE32DLL_FALSE@ format-gcc-internal.c format-gfc-internal.c \ +@WOE32DLL_FALSE@ format-qt.c format-qt-plural.c format-kde.c \ +@WOE32DLL_FALSE@ format-boost.c format-lua.c \ +@WOE32DLL_FALSE@ format-javascript.c +@WOE32DLL_TRUE@FORMAT_SOURCE = ../woe32dll/c++format.cc \ +@WOE32DLL_TRUE@ format-invalid.h format-c.c format-c-parse.h \ +@WOE32DLL_TRUE@ format-sh.c format-python.c \ +@WOE32DLL_TRUE@ format-python-brace.c format-lisp.c \ +@WOE32DLL_TRUE@ format-elisp.c format-librep.c format-scheme.c \ +@WOE32DLL_TRUE@ format-java.c format-csharp.c format-awk.c \ +@WOE32DLL_TRUE@ format-pascal.c format-ycp.c format-tcl.c \ +@WOE32DLL_TRUE@ format-perl.c format-perl-brace.c format-php.c \ +@WOE32DLL_TRUE@ format-gcc-internal.c format-gfc-internal.c \ +@WOE32DLL_TRUE@ format-qt.c format-qt-plural.c format-kde.c \ +@WOE32DLL_TRUE@ format-boost.c format-lua.c format-javascript.c + +# libgettextsrc contains all code that is needed by at least two programs. +libgettextsrc_la_SOURCES = $(COMMON_SOURCE) read-catalog.c color.c \ + write-catalog.c write-properties.c write-stringtable.c \ + write-po.c msgl-ascii.c msgl-iconv.c msgl-equal.c msgl-cat.c \ + msgl-header.c msgl-english.c msgl-check.c file-list.c \ + msgl-charset.c po-time.c plural-exp.c plural-eval.c \ + plural-table.c $(FORMAT_SOURCE) read-desktop.c $(am__append_1) + +# msggrep needs pattern matching. +LIBGREP = ../libgrep/libgrep.a + +# Source dependencies. +msgcmp_SOURCES = msgcmp.c msgl-fsearch.c +msgfmt_SOURCES = msgfmt.c write-mo.c write-java.c write-csharp.c \ + write-resources.c write-tcl.c write-qt.c write-desktop.c \ + ../../gettext-runtime/intl/hash-string.c +@WOE32DLL_FALSE@msgmerge_SOURCES = msgmerge.c msgl-fsearch.c \ +@WOE32DLL_FALSE@ lang-table.c plural-count.c +@WOE32DLL_TRUE@msgmerge_SOURCES = ../woe32dll/c++msgmerge.cc \ +@WOE32DLL_TRUE@ msgl-fsearch.c lang-table.c plural-count.c +msgunfmt_SOURCES = msgunfmt.c read-mo.c read-java.c read-csharp.c \ + read-resources.c read-tcl.c +@WOE32DLL_FALSE@xgettext_SOURCES = xgettext.c x-c.c x-po.c x-sh.c \ +@WOE32DLL_FALSE@ x-python.c x-lisp.c x-elisp.c x-librep.c \ +@WOE32DLL_FALSE@ x-scheme.c x-smalltalk.c x-java.c x-csharp.c \ +@WOE32DLL_FALSE@ x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \ +@WOE32DLL_FALSE@ x-rst.c x-glade.c x-lua.c x-javascript.c \ +@WOE32DLL_FALSE@ x-vala.c x-gsettings.c libexpat-compat.c \ +@WOE32DLL_FALSE@ x-desktop.c +@WOE32DLL_TRUE@xgettext_SOURCES = ../woe32dll/c++xgettext.cc x-c.c \ +@WOE32DLL_TRUE@ x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c \ +@WOE32DLL_TRUE@ x-librep.c x-scheme.c x-smalltalk.c x-java.c \ +@WOE32DLL_TRUE@ x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c \ +@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c x-lua.c \ +@WOE32DLL_TRUE@ x-javascript.c x-vala.c x-gsettings.c \ +@WOE32DLL_TRUE@ libexpat-compat.c x-desktop.c +@WOE32DLL_FALSE@msgattrib_SOURCES = msgattrib.c +@WOE32DLL_TRUE@msgattrib_SOURCES = ../woe32dll/c++msgattrib.cc +@WOE32DLL_FALSE@msgcat_SOURCES = msgcat.c +@WOE32DLL_TRUE@msgcat_SOURCES = ../woe32dll/c++msgcat.cc +@WOE32DLL_FALSE@msgcomm_SOURCES = msgcomm.c +@WOE32DLL_TRUE@msgcomm_SOURCES = ../woe32dll/c++msgcomm.cc +@WOE32DLL_FALSE@msgconv_SOURCES = msgconv.c +@WOE32DLL_TRUE@msgconv_SOURCES = ../woe32dll/c++msgconv.cc +@WOE32DLL_FALSE@msgen_SOURCES = msgen.c +@WOE32DLL_TRUE@msgen_SOURCES = ../woe32dll/c++msgen.cc +msgexec_SOURCES = msgexec.c +@WOE32DLL_FALSE@msgfilter_SOURCES = msgfilter.c filter-sr-latin.c \ +@WOE32DLL_FALSE@ filter-quote.c +@WOE32DLL_TRUE@msgfilter_SOURCES = ../woe32dll/c++msgfilter.cc \ +@WOE32DLL_TRUE@ filter-sr-latin.c filter-quote.c +@WOE32DLL_FALSE@msggrep_SOURCES = msggrep.c +@WOE32DLL_TRUE@msggrep_SOURCES = ../woe32dll/c++msggrep.cc +# This is needed because on Solaris, localealias.c requires the symbol +# libintl_thread_in_use which is defined in lock.c. The copy of lock.c inside +# libintl.so is not sufficient, because libintl.so doesn't export the symbol +# libintl_thread_in_use. +msginit_SOURCES = msginit.c lang-table.c plural-count.c \ + ../../gettext-runtime/intl/localealias.c \ + ../../gettext-runtime/intl/lock.c +@WOE32DLL_FALSE@msguniq_SOURCES = msguniq.c +@WOE32DLL_TRUE@msguniq_SOURCES = ../woe32dll/c++msguniq.cc +recode_sr_latin_SOURCES = recode-sr-latin.c filter-sr-latin.c +hostname_SOURCES = hostname.c +urlget_SOURCES = urlget.c + +# How to build libgettextsrc.la. +# Need ../gnulib-lib/libgettextlib.la. +# Need $(LTLIBUNISTRING) because ulc_width_linebreaks, uc_width, etc. may be +# taken from libunistring, when the configure option --with-libunistring-prefix +# was given. +# Need @LTLIBINTL@ because many source files use gettext(). +# Need @LTLIBICONV@ because po-charset.c, po-lex.c, msgl-iconv.c, write-po.c +# use iconv(). +libgettextsrc_la_LDFLAGS = -release @VERSION@ \ + ../gnulib-lib/libgettextlib.la $(LTLIBUNISTRING) @LTLIBINTL@ \ + @LTLIBICONV@ -lc -no-undefined $(am__append_2) +libgettextsrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(am__append_3) + +# Compile-time flags for particular source files. +msgmerge_CFLAGS = $(AM_CFLAGS) $(OPENMP_CFLAGS) +msgmerge_CXXFLAGS = $(AM_CXXFLAGS) $(OPENMP_CFLAGS) + +# Link dependencies. +# INTL_MACOSX_LIBS is needed because the programs depend on libintl.la +# but libtool doesn't put -Wl,-framework options into .la files. +# For msginit, it is also needed because of localename.c. +msgcmp_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ @MSGMERGE_LIBM@ $(WOE32_LDADD) +msgfmt_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgmerge_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ @MSGMERGE_LIBM@ $(WOE32_LDADD) $(OPENMP_CFLAGS) +msgunfmt_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +xgettext_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ @LTLIBEXPAT@ @LTLIBICONV@ $(WOE32_LDADD) +msgattrib_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgcat_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgcomm_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgconv_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgen_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgexec_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msgfilter_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msggrep_LDADD = $(LIBGREP) libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msginit_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) +msguniq_LDADD = libgettextsrc.la @INTL_MACOSX_LIBS@ $(WOE32_LDADD) + +# Specify when to relink the programs. +msgcmp_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgfmt_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgmerge_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgunfmt_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +xgettext_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgattrib_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgcat_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgcomm_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgconv_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgen_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgexec_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msgfilter_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msggrep_DEPENDENCIES = $(LIBGREP) libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msginit_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +msguniq_DEPENDENCIES = libgettextsrc.la ../gnulib-lib/libgettextlib.la $(WOE32_LDADD) +recode_sr_latin_DEPENDENCIES = $(OTHERPROGDEPENDENCIES) +hostname_DEPENDENCIES = $(OTHERPROGDEPENDENCIES) +urlget_DEPENDENCIES = $(OTHERPROGDEPENDENCIES) + +# Specify installation directory, for --enable-relocatable. +msgcmp_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgfmt_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgmerge_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgunfmt_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +xgettext_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgattrib_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgcat_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgcomm_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgconv_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgen_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgexec_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msgfilter_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msggrep_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msginit_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +msguniq_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +recode_sr_latin_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(bindir)\" +hostname_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(pkglibdir)\" +urlget_CPPFLAGS = $(AM_CPPFLAGS) -DINSTALLDIR=\"$(pkglibdir)\" +@RELOCATABLE_VIA_LD_TRUE@msgcmp_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgfmt_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgmerge_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgunfmt_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@xgettext_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgattrib_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgcat_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgcomm_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgconv_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgen_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgexec_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msgfilter_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msggrep_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msginit_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@msguniq_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@recode_sr_latin_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(bindir)` +@RELOCATABLE_VIA_LD_TRUE@hostname_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(pkglibdir)` +@RELOCATABLE_VIA_LD_TRUE@urlget_LDFLAGS = `$(RELOCATABLE_LDFLAGS) $(pkglibdir)` + +# Linking with C++ libraries is needed _only_ on mingw and Cygwin. +@WOE32DLL_FALSE@libgettextsrc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(libgettextsrc_la_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@libgettextsrc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(libgettextsrc_la_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msgattrib_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgattrib_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msgattrib_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgattrib_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msgcat_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgcat_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msgcat_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgcat_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msgcomm_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgcomm_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msgcomm_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgcomm_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msgconv_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgconv_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msgconv_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgconv_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msgen_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgen_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msgen_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgen_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msgfilter_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msgfilter_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msgfilter_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msgfilter_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msggrep_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msggrep_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msggrep_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msggrep_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msgmerge_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(msgmerge_CFLAGS) $(CFLAGS) $(msgmerge_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msgmerge_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(msgmerge_CXXFLAGS) $(CXXFLAGS) $(msgmerge_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@msguniq_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(msguniq_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@msguniq_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(msguniq_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + +@WOE32DLL_FALSE@xgettext_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ +@WOE32DLL_FALSE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_FALSE@ $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(xgettext_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_FALSE@ -o $@ + +@WOE32DLL_TRUE@xgettext_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ +@WOE32DLL_TRUE@ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link \ +@WOE32DLL_TRUE@ $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(xgettext_LDFLAGS) $(LDFLAGS) \ +@WOE32DLL_TRUE@ -o $@ + + +# Special rules for bison and flex generated files. +BUILT_SOURCES = \ + po-gram-gen.c po-gram-gen.h po-gram-gen2.h + + +# Special rules for Java compilation. +USEJAVA = $(USEJAVA_@BUILDJAVAEXE@) +USEJAVA_yes = 1 +USEJAVA_no = $(USEJAVA_no_@BUILDJAVA@) +USEJAVA_no_yes = 1 +USEJAVA_no_no = 0 +USEJEXE = $(USEJEXE_@BUILDJAVAEXE@) +USEJEXE_yes = 1 +USEJEXE_no = 0 +@WOE32_FALSE@WOE32_LDADD = + +# Version information according to Woe32 conventions. +@WOE32_TRUE@WOE32_LDADD = gettext.res +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .cc .lo .o .obj .y +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnits src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnits src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +user-email: $(top_builddir)/config.status $(srcdir)/user-email.sh.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +../woe32dll/$(am__dirstamp): + @$(MKDIR_P) ../woe32dll + @: > ../woe32dll/$(am__dirstamp) +../woe32dll/libgettextsrc_la-c++format.lo: \ + ../woe32dll/$(am__dirstamp) +../woe32dll/libgettextsrc_la-gettextsrc-exports.lo: \ + ../woe32dll/$(am__dirstamp) + +libgettextsrc.la: $(libgettextsrc_la_OBJECTS) $(libgettextsrc_la_DEPENDENCIES) $(EXTRA_libgettextsrc_la_DEPENDENCIES) + $(AM_V_GEN)$(libgettextsrc_la_LINK) -rpath $(libdir) $(libgettextsrc_la_OBJECTS) $(libgettextsrc_la_LIBADD) $(LIBS) +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +installcheck-binPROGRAMS: $(bin_PROGRAMS) + bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \ + case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \ + *" $$p "* | *" $(srcdir)/$$p "*) continue;; \ + esac; \ + f=`echo "$$p" | \ + sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + for opt in --help --version; do \ + if "$(DESTDIR)$(bindir)/$$f" $$opt >c$${pid}_.out \ + 2>c$${pid}_.err &2; bad=1; fi; \ + done; \ + done; rm -f c$${pid}_.???; exit $$bad + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +hostname$(EXEEXT): $(hostname_OBJECTS) $(hostname_DEPENDENCIES) $(EXTRA_hostname_DEPENDENCIES) + @rm -f hostname$(EXEEXT) + $(AM_V_CCLD)$(hostname_LINK) $(hostname_OBJECTS) $(hostname_LDADD) $(LIBS) +../woe32dll/msgattrib-c++msgattrib.$(OBJEXT): \ + ../woe32dll/$(am__dirstamp) + +msgattrib$(EXEEXT): $(msgattrib_OBJECTS) $(msgattrib_DEPENDENCIES) $(EXTRA_msgattrib_DEPENDENCIES) + @rm -f msgattrib$(EXEEXT) + $(AM_V_GEN)$(msgattrib_LINK) $(msgattrib_OBJECTS) $(msgattrib_LDADD) $(LIBS) +../woe32dll/msgcat-c++msgcat.$(OBJEXT): ../woe32dll/$(am__dirstamp) + +msgcat$(EXEEXT): $(msgcat_OBJECTS) $(msgcat_DEPENDENCIES) $(EXTRA_msgcat_DEPENDENCIES) + @rm -f msgcat$(EXEEXT) + $(AM_V_GEN)$(msgcat_LINK) $(msgcat_OBJECTS) $(msgcat_LDADD) $(LIBS) + +msgcmp$(EXEEXT): $(msgcmp_OBJECTS) $(msgcmp_DEPENDENCIES) $(EXTRA_msgcmp_DEPENDENCIES) + @rm -f msgcmp$(EXEEXT) + $(AM_V_CCLD)$(msgcmp_LINK) $(msgcmp_OBJECTS) $(msgcmp_LDADD) $(LIBS) +../woe32dll/msgcomm-c++msgcomm.$(OBJEXT): ../woe32dll/$(am__dirstamp) + +msgcomm$(EXEEXT): $(msgcomm_OBJECTS) $(msgcomm_DEPENDENCIES) $(EXTRA_msgcomm_DEPENDENCIES) + @rm -f msgcomm$(EXEEXT) + $(AM_V_GEN)$(msgcomm_LINK) $(msgcomm_OBJECTS) $(msgcomm_LDADD) $(LIBS) +../woe32dll/msgconv-c++msgconv.$(OBJEXT): ../woe32dll/$(am__dirstamp) + +msgconv$(EXEEXT): $(msgconv_OBJECTS) $(msgconv_DEPENDENCIES) $(EXTRA_msgconv_DEPENDENCIES) + @rm -f msgconv$(EXEEXT) + $(AM_V_GEN)$(msgconv_LINK) $(msgconv_OBJECTS) $(msgconv_LDADD) $(LIBS) +../woe32dll/msgen-c++msgen.$(OBJEXT): ../woe32dll/$(am__dirstamp) + +msgen$(EXEEXT): $(msgen_OBJECTS) $(msgen_DEPENDENCIES) $(EXTRA_msgen_DEPENDENCIES) + @rm -f msgen$(EXEEXT) + $(AM_V_GEN)$(msgen_LINK) $(msgen_OBJECTS) $(msgen_LDADD) $(LIBS) + +msgexec$(EXEEXT): $(msgexec_OBJECTS) $(msgexec_DEPENDENCIES) $(EXTRA_msgexec_DEPENDENCIES) + @rm -f msgexec$(EXEEXT) + $(AM_V_CCLD)$(msgexec_LINK) $(msgexec_OBJECTS) $(msgexec_LDADD) $(LIBS) +../woe32dll/msgfilter-c++msgfilter.$(OBJEXT): \ + ../woe32dll/$(am__dirstamp) + +msgfilter$(EXEEXT): $(msgfilter_OBJECTS) $(msgfilter_DEPENDENCIES) $(EXTRA_msgfilter_DEPENDENCIES) + @rm -f msgfilter$(EXEEXT) + $(AM_V_GEN)$(msgfilter_LINK) $(msgfilter_OBJECTS) $(msgfilter_LDADD) $(LIBS) +../../gettext-runtime/intl/$(am__dirstamp): + @$(MKDIR_P) ../../gettext-runtime/intl + @: > ../../gettext-runtime/intl/$(am__dirstamp) +../../gettext-runtime/intl/msgfmt-hash-string.$(OBJEXT): \ + ../../gettext-runtime/intl/$(am__dirstamp) + +msgfmt$(EXEEXT): $(msgfmt_OBJECTS) $(msgfmt_DEPENDENCIES) $(EXTRA_msgfmt_DEPENDENCIES) + @rm -f msgfmt$(EXEEXT) + $(AM_V_CCLD)$(msgfmt_LINK) $(msgfmt_OBJECTS) $(msgfmt_LDADD) $(LIBS) +../woe32dll/msggrep-c++msggrep.$(OBJEXT): ../woe32dll/$(am__dirstamp) + +msggrep$(EXEEXT): $(msggrep_OBJECTS) $(msggrep_DEPENDENCIES) $(EXTRA_msggrep_DEPENDENCIES) + @rm -f msggrep$(EXEEXT) + $(AM_V_GEN)$(msggrep_LINK) $(msggrep_OBJECTS) $(msggrep_LDADD) $(LIBS) +../../gettext-runtime/intl/msginit-localealias.$(OBJEXT): \ + ../../gettext-runtime/intl/$(am__dirstamp) +../../gettext-runtime/intl/msginit-lock.$(OBJEXT): \ + ../../gettext-runtime/intl/$(am__dirstamp) + +msginit$(EXEEXT): $(msginit_OBJECTS) $(msginit_DEPENDENCIES) $(EXTRA_msginit_DEPENDENCIES) + @rm -f msginit$(EXEEXT) + $(AM_V_CCLD)$(msginit_LINK) $(msginit_OBJECTS) $(msginit_LDADD) $(LIBS) +../woe32dll/msgmerge-c++msgmerge.$(OBJEXT): \ + ../woe32dll/$(am__dirstamp) + +msgmerge$(EXEEXT): $(msgmerge_OBJECTS) $(msgmerge_DEPENDENCIES) $(EXTRA_msgmerge_DEPENDENCIES) + @rm -f msgmerge$(EXEEXT) + $(AM_V_GEN)$(msgmerge_LINK) $(msgmerge_OBJECTS) $(msgmerge_LDADD) $(LIBS) + +msgunfmt$(EXEEXT): $(msgunfmt_OBJECTS) $(msgunfmt_DEPENDENCIES) $(EXTRA_msgunfmt_DEPENDENCIES) + @rm -f msgunfmt$(EXEEXT) + $(AM_V_CCLD)$(msgunfmt_LINK) $(msgunfmt_OBJECTS) $(msgunfmt_LDADD) $(LIBS) +../woe32dll/msguniq-c++msguniq.$(OBJEXT): ../woe32dll/$(am__dirstamp) + +msguniq$(EXEEXT): $(msguniq_OBJECTS) $(msguniq_DEPENDENCIES) $(EXTRA_msguniq_DEPENDENCIES) + @rm -f msguniq$(EXEEXT) + $(AM_V_GEN)$(msguniq_LINK) $(msguniq_OBJECTS) $(msguniq_LDADD) $(LIBS) + +recode-sr-latin$(EXEEXT): $(recode_sr_latin_OBJECTS) $(recode_sr_latin_DEPENDENCIES) $(EXTRA_recode_sr_latin_DEPENDENCIES) + @rm -f recode-sr-latin$(EXEEXT) + $(AM_V_CCLD)$(recode_sr_latin_LINK) $(recode_sr_latin_OBJECTS) $(recode_sr_latin_LDADD) $(LIBS) + +urlget$(EXEEXT): $(urlget_OBJECTS) $(urlget_DEPENDENCIES) $(EXTRA_urlget_DEPENDENCIES) + @rm -f urlget$(EXEEXT) + $(AM_V_CCLD)$(urlget_LINK) $(urlget_OBJECTS) $(urlget_LDADD) $(LIBS) +../woe32dll/xgettext-c++xgettext.$(OBJEXT): \ + ../woe32dll/$(am__dirstamp) + +xgettext$(EXEEXT): $(xgettext_OBJECTS) $(xgettext_DEPENDENCIES) $(EXTRA_xgettext_DEPENDENCIES) + @rm -f xgettext$(EXEEXT) + $(AM_V_GEN)$(xgettext_LINK) $(xgettext_OBJECTS) $(xgettext_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f ../../gettext-runtime/intl/*.$(OBJEXT) + -rm -f ../woe32dll/*.$(OBJEXT) + -rm -f ../woe32dll/*.lo + +distclean-compile: + -rm -f *.tab.c + +.c.o: + $(AM_V_CC)$(COMPILE) -c -o $@ $< + +.c.obj: + $(AM_V_CC)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: + $(AM_V_CC)$(LTCOMPILE) -c -o $@ $< + +libgettextsrc_la-message.lo: message.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-message.lo `test -f 'message.c' || echo '$(srcdir)/'`message.c + +libgettextsrc_la-po-error.lo: po-error.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-po-error.lo `test -f 'po-error.c' || echo '$(srcdir)/'`po-error.c + +libgettextsrc_la-po-xerror.lo: po-xerror.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-po-xerror.lo `test -f 'po-xerror.c' || echo '$(srcdir)/'`po-xerror.c + +libgettextsrc_la-read-catalog-abstract.lo: read-catalog-abstract.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-read-catalog-abstract.lo `test -f 'read-catalog-abstract.c' || echo '$(srcdir)/'`read-catalog-abstract.c + +libgettextsrc_la-po-lex.lo: po-lex.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-po-lex.lo `test -f 'po-lex.c' || echo '$(srcdir)/'`po-lex.c + +libgettextsrc_la-po-gram-gen.lo: po-gram-gen.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-po-gram-gen.lo `test -f 'po-gram-gen.c' || echo '$(srcdir)/'`po-gram-gen.c + +libgettextsrc_la-po-charset.lo: po-charset.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-po-charset.lo `test -f 'po-charset.c' || echo '$(srcdir)/'`po-charset.c + +libgettextsrc_la-read-po.lo: read-po.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-read-po.lo `test -f 'read-po.c' || echo '$(srcdir)/'`read-po.c + +libgettextsrc_la-read-properties.lo: read-properties.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-read-properties.lo `test -f 'read-properties.c' || echo '$(srcdir)/'`read-properties.c + +libgettextsrc_la-read-stringtable.lo: read-stringtable.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-read-stringtable.lo `test -f 'read-stringtable.c' || echo '$(srcdir)/'`read-stringtable.c + +libgettextsrc_la-open-catalog.lo: open-catalog.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-open-catalog.lo `test -f 'open-catalog.c' || echo '$(srcdir)/'`open-catalog.c + +libgettextsrc_la-dir-list.lo: dir-list.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-dir-list.lo `test -f 'dir-list.c' || echo '$(srcdir)/'`dir-list.c + +libgettextsrc_la-str-list.lo: str-list.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-str-list.lo `test -f 'str-list.c' || echo '$(srcdir)/'`str-list.c + +libgettextsrc_la-read-catalog.lo: read-catalog.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-read-catalog.lo `test -f 'read-catalog.c' || echo '$(srcdir)/'`read-catalog.c + +libgettextsrc_la-color.lo: color.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-color.lo `test -f 'color.c' || echo '$(srcdir)/'`color.c + +libgettextsrc_la-write-catalog.lo: write-catalog.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-write-catalog.lo `test -f 'write-catalog.c' || echo '$(srcdir)/'`write-catalog.c + +libgettextsrc_la-write-properties.lo: write-properties.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-write-properties.lo `test -f 'write-properties.c' || echo '$(srcdir)/'`write-properties.c + +libgettextsrc_la-write-stringtable.lo: write-stringtable.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-write-stringtable.lo `test -f 'write-stringtable.c' || echo '$(srcdir)/'`write-stringtable.c + +libgettextsrc_la-write-po.lo: write-po.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-write-po.lo `test -f 'write-po.c' || echo '$(srcdir)/'`write-po.c + +libgettextsrc_la-msgl-ascii.lo: msgl-ascii.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-msgl-ascii.lo `test -f 'msgl-ascii.c' || echo '$(srcdir)/'`msgl-ascii.c + +libgettextsrc_la-msgl-iconv.lo: msgl-iconv.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-msgl-iconv.lo `test -f 'msgl-iconv.c' || echo '$(srcdir)/'`msgl-iconv.c + +libgettextsrc_la-msgl-equal.lo: msgl-equal.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-msgl-equal.lo `test -f 'msgl-equal.c' || echo '$(srcdir)/'`msgl-equal.c + +libgettextsrc_la-msgl-cat.lo: msgl-cat.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-msgl-cat.lo `test -f 'msgl-cat.c' || echo '$(srcdir)/'`msgl-cat.c + +libgettextsrc_la-msgl-header.lo: msgl-header.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-msgl-header.lo `test -f 'msgl-header.c' || echo '$(srcdir)/'`msgl-header.c + +libgettextsrc_la-msgl-english.lo: msgl-english.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-msgl-english.lo `test -f 'msgl-english.c' || echo '$(srcdir)/'`msgl-english.c + +libgettextsrc_la-msgl-check.lo: msgl-check.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-msgl-check.lo `test -f 'msgl-check.c' || echo '$(srcdir)/'`msgl-check.c + +libgettextsrc_la-file-list.lo: file-list.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-file-list.lo `test -f 'file-list.c' || echo '$(srcdir)/'`file-list.c + +libgettextsrc_la-msgl-charset.lo: msgl-charset.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-msgl-charset.lo `test -f 'msgl-charset.c' || echo '$(srcdir)/'`msgl-charset.c + +libgettextsrc_la-po-time.lo: po-time.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-po-time.lo `test -f 'po-time.c' || echo '$(srcdir)/'`po-time.c + +libgettextsrc_la-plural-exp.lo: plural-exp.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-plural-exp.lo `test -f 'plural-exp.c' || echo '$(srcdir)/'`plural-exp.c + +libgettextsrc_la-plural-eval.lo: plural-eval.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-plural-eval.lo `test -f 'plural-eval.c' || echo '$(srcdir)/'`plural-eval.c + +libgettextsrc_la-plural-table.lo: plural-table.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-plural-table.lo `test -f 'plural-table.c' || echo '$(srcdir)/'`plural-table.c + +libgettextsrc_la-format.lo: format.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format.lo `test -f 'format.c' || echo '$(srcdir)/'`format.c + +libgettextsrc_la-format-c.lo: format-c.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-c.lo `test -f 'format-c.c' || echo '$(srcdir)/'`format-c.c + +libgettextsrc_la-format-sh.lo: format-sh.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-sh.lo `test -f 'format-sh.c' || echo '$(srcdir)/'`format-sh.c + +libgettextsrc_la-format-python.lo: format-python.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-python.lo `test -f 'format-python.c' || echo '$(srcdir)/'`format-python.c + +libgettextsrc_la-format-python-brace.lo: format-python-brace.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-python-brace.lo `test -f 'format-python-brace.c' || echo '$(srcdir)/'`format-python-brace.c + +libgettextsrc_la-format-lisp.lo: format-lisp.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-lisp.lo `test -f 'format-lisp.c' || echo '$(srcdir)/'`format-lisp.c + +libgettextsrc_la-format-elisp.lo: format-elisp.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-elisp.lo `test -f 'format-elisp.c' || echo '$(srcdir)/'`format-elisp.c + +libgettextsrc_la-format-librep.lo: format-librep.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-librep.lo `test -f 'format-librep.c' || echo '$(srcdir)/'`format-librep.c + +libgettextsrc_la-format-scheme.lo: format-scheme.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-scheme.lo `test -f 'format-scheme.c' || echo '$(srcdir)/'`format-scheme.c + +libgettextsrc_la-format-java.lo: format-java.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-java.lo `test -f 'format-java.c' || echo '$(srcdir)/'`format-java.c + +libgettextsrc_la-format-csharp.lo: format-csharp.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-csharp.lo `test -f 'format-csharp.c' || echo '$(srcdir)/'`format-csharp.c + +libgettextsrc_la-format-awk.lo: format-awk.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-awk.lo `test -f 'format-awk.c' || echo '$(srcdir)/'`format-awk.c + +libgettextsrc_la-format-pascal.lo: format-pascal.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-pascal.lo `test -f 'format-pascal.c' || echo '$(srcdir)/'`format-pascal.c + +libgettextsrc_la-format-ycp.lo: format-ycp.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-ycp.lo `test -f 'format-ycp.c' || echo '$(srcdir)/'`format-ycp.c + +libgettextsrc_la-format-tcl.lo: format-tcl.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-tcl.lo `test -f 'format-tcl.c' || echo '$(srcdir)/'`format-tcl.c + +libgettextsrc_la-format-perl.lo: format-perl.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-perl.lo `test -f 'format-perl.c' || echo '$(srcdir)/'`format-perl.c + +libgettextsrc_la-format-perl-brace.lo: format-perl-brace.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-perl-brace.lo `test -f 'format-perl-brace.c' || echo '$(srcdir)/'`format-perl-brace.c + +libgettextsrc_la-format-php.lo: format-php.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-php.lo `test -f 'format-php.c' || echo '$(srcdir)/'`format-php.c + +libgettextsrc_la-format-gcc-internal.lo: format-gcc-internal.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-gcc-internal.lo `test -f 'format-gcc-internal.c' || echo '$(srcdir)/'`format-gcc-internal.c + +libgettextsrc_la-format-gfc-internal.lo: format-gfc-internal.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-gfc-internal.lo `test -f 'format-gfc-internal.c' || echo '$(srcdir)/'`format-gfc-internal.c + +libgettextsrc_la-format-qt.lo: format-qt.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-qt.lo `test -f 'format-qt.c' || echo '$(srcdir)/'`format-qt.c + +libgettextsrc_la-format-qt-plural.lo: format-qt-plural.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-qt-plural.lo `test -f 'format-qt-plural.c' || echo '$(srcdir)/'`format-qt-plural.c + +libgettextsrc_la-format-kde.lo: format-kde.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-kde.lo `test -f 'format-kde.c' || echo '$(srcdir)/'`format-kde.c + +libgettextsrc_la-format-boost.lo: format-boost.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-boost.lo `test -f 'format-boost.c' || echo '$(srcdir)/'`format-boost.c + +libgettextsrc_la-format-lua.lo: format-lua.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-lua.lo `test -f 'format-lua.c' || echo '$(srcdir)/'`format-lua.c + +libgettextsrc_la-format-javascript.lo: format-javascript.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-format-javascript.lo `test -f 'format-javascript.c' || echo '$(srcdir)/'`format-javascript.c + +libgettextsrc_la-read-desktop.lo: read-desktop.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgettextsrc_la-read-desktop.lo `test -f 'read-desktop.c' || echo '$(srcdir)/'`read-desktop.c + +../woe32dll/libgettextsrc_la-gettextsrc-exports.lo: ../woe32dll/gettextsrc-exports.c + $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../woe32dll/libgettextsrc_la-gettextsrc-exports.lo `test -f '../woe32dll/gettextsrc-exports.c' || echo '$(srcdir)/'`../woe32dll/gettextsrc-exports.c + +hostname-hostname.o: hostname.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(hostname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hostname-hostname.o `test -f 'hostname.c' || echo '$(srcdir)/'`hostname.c + +hostname-hostname.obj: hostname.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(hostname_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o hostname-hostname.obj `if test -f 'hostname.c'; then $(CYGPATH_W) 'hostname.c'; else $(CYGPATH_W) '$(srcdir)/hostname.c'; fi` + +msgattrib-msgattrib.o: msgattrib.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgattrib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgattrib-msgattrib.o `test -f 'msgattrib.c' || echo '$(srcdir)/'`msgattrib.c + +msgattrib-msgattrib.obj: msgattrib.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgattrib_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgattrib-msgattrib.obj `if test -f 'msgattrib.c'; then $(CYGPATH_W) 'msgattrib.c'; else $(CYGPATH_W) '$(srcdir)/msgattrib.c'; fi` + +msgcat-msgcat.o: msgcat.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcat_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgcat-msgcat.o `test -f 'msgcat.c' || echo '$(srcdir)/'`msgcat.c + +msgcat-msgcat.obj: msgcat.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcat_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgcat-msgcat.obj `if test -f 'msgcat.c'; then $(CYGPATH_W) 'msgcat.c'; else $(CYGPATH_W) '$(srcdir)/msgcat.c'; fi` + +msgcmp-msgcmp.o: msgcmp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcmp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgcmp-msgcmp.o `test -f 'msgcmp.c' || echo '$(srcdir)/'`msgcmp.c + +msgcmp-msgcmp.obj: msgcmp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcmp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgcmp-msgcmp.obj `if test -f 'msgcmp.c'; then $(CYGPATH_W) 'msgcmp.c'; else $(CYGPATH_W) '$(srcdir)/msgcmp.c'; fi` + +msgcmp-msgl-fsearch.o: msgl-fsearch.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcmp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgcmp-msgl-fsearch.o `test -f 'msgl-fsearch.c' || echo '$(srcdir)/'`msgl-fsearch.c + +msgcmp-msgl-fsearch.obj: msgl-fsearch.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcmp_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgcmp-msgl-fsearch.obj `if test -f 'msgl-fsearch.c'; then $(CYGPATH_W) 'msgl-fsearch.c'; else $(CYGPATH_W) '$(srcdir)/msgl-fsearch.c'; fi` + +msgcomm-msgcomm.o: msgcomm.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcomm_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgcomm-msgcomm.o `test -f 'msgcomm.c' || echo '$(srcdir)/'`msgcomm.c + +msgcomm-msgcomm.obj: msgcomm.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcomm_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgcomm-msgcomm.obj `if test -f 'msgcomm.c'; then $(CYGPATH_W) 'msgcomm.c'; else $(CYGPATH_W) '$(srcdir)/msgcomm.c'; fi` + +msgconv-msgconv.o: msgconv.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgconv_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgconv-msgconv.o `test -f 'msgconv.c' || echo '$(srcdir)/'`msgconv.c + +msgconv-msgconv.obj: msgconv.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgconv_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgconv-msgconv.obj `if test -f 'msgconv.c'; then $(CYGPATH_W) 'msgconv.c'; else $(CYGPATH_W) '$(srcdir)/msgconv.c'; fi` + +msgen-msgen.o: msgen.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgen_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgen-msgen.o `test -f 'msgen.c' || echo '$(srcdir)/'`msgen.c + +msgen-msgen.obj: msgen.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgen_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgen-msgen.obj `if test -f 'msgen.c'; then $(CYGPATH_W) 'msgen.c'; else $(CYGPATH_W) '$(srcdir)/msgen.c'; fi` + +msgexec-msgexec.o: msgexec.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgexec_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgexec-msgexec.o `test -f 'msgexec.c' || echo '$(srcdir)/'`msgexec.c + +msgexec-msgexec.obj: msgexec.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgexec_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgexec-msgexec.obj `if test -f 'msgexec.c'; then $(CYGPATH_W) 'msgexec.c'; else $(CYGPATH_W) '$(srcdir)/msgexec.c'; fi` + +msgfilter-msgfilter.o: msgfilter.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfilter_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfilter-msgfilter.o `test -f 'msgfilter.c' || echo '$(srcdir)/'`msgfilter.c + +msgfilter-msgfilter.obj: msgfilter.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfilter_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfilter-msgfilter.obj `if test -f 'msgfilter.c'; then $(CYGPATH_W) 'msgfilter.c'; else $(CYGPATH_W) '$(srcdir)/msgfilter.c'; fi` + +msgfilter-filter-sr-latin.o: filter-sr-latin.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfilter_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfilter-filter-sr-latin.o `test -f 'filter-sr-latin.c' || echo '$(srcdir)/'`filter-sr-latin.c + +msgfilter-filter-sr-latin.obj: filter-sr-latin.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfilter_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfilter-filter-sr-latin.obj `if test -f 'filter-sr-latin.c'; then $(CYGPATH_W) 'filter-sr-latin.c'; else $(CYGPATH_W) '$(srcdir)/filter-sr-latin.c'; fi` + +msgfilter-filter-quote.o: filter-quote.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfilter_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfilter-filter-quote.o `test -f 'filter-quote.c' || echo '$(srcdir)/'`filter-quote.c + +msgfilter-filter-quote.obj: filter-quote.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfilter_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfilter-filter-quote.obj `if test -f 'filter-quote.c'; then $(CYGPATH_W) 'filter-quote.c'; else $(CYGPATH_W) '$(srcdir)/filter-quote.c'; fi` + +msgfmt-msgfmt.o: msgfmt.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-msgfmt.o `test -f 'msgfmt.c' || echo '$(srcdir)/'`msgfmt.c + +msgfmt-msgfmt.obj: msgfmt.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-msgfmt.obj `if test -f 'msgfmt.c'; then $(CYGPATH_W) 'msgfmt.c'; else $(CYGPATH_W) '$(srcdir)/msgfmt.c'; fi` + +msgfmt-write-mo.o: write-mo.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-mo.o `test -f 'write-mo.c' || echo '$(srcdir)/'`write-mo.c + +msgfmt-write-mo.obj: write-mo.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-mo.obj `if test -f 'write-mo.c'; then $(CYGPATH_W) 'write-mo.c'; else $(CYGPATH_W) '$(srcdir)/write-mo.c'; fi` + +msgfmt-write-java.o: write-java.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-java.o `test -f 'write-java.c' || echo '$(srcdir)/'`write-java.c + +msgfmt-write-java.obj: write-java.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-java.obj `if test -f 'write-java.c'; then $(CYGPATH_W) 'write-java.c'; else $(CYGPATH_W) '$(srcdir)/write-java.c'; fi` + +msgfmt-write-csharp.o: write-csharp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-csharp.o `test -f 'write-csharp.c' || echo '$(srcdir)/'`write-csharp.c + +msgfmt-write-csharp.obj: write-csharp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-csharp.obj `if test -f 'write-csharp.c'; then $(CYGPATH_W) 'write-csharp.c'; else $(CYGPATH_W) '$(srcdir)/write-csharp.c'; fi` + +msgfmt-write-resources.o: write-resources.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-resources.o `test -f 'write-resources.c' || echo '$(srcdir)/'`write-resources.c + +msgfmt-write-resources.obj: write-resources.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-resources.obj `if test -f 'write-resources.c'; then $(CYGPATH_W) 'write-resources.c'; else $(CYGPATH_W) '$(srcdir)/write-resources.c'; fi` + +msgfmt-write-tcl.o: write-tcl.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-tcl.o `test -f 'write-tcl.c' || echo '$(srcdir)/'`write-tcl.c + +msgfmt-write-tcl.obj: write-tcl.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-tcl.obj `if test -f 'write-tcl.c'; then $(CYGPATH_W) 'write-tcl.c'; else $(CYGPATH_W) '$(srcdir)/write-tcl.c'; fi` + +msgfmt-write-qt.o: write-qt.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-qt.o `test -f 'write-qt.c' || echo '$(srcdir)/'`write-qt.c + +msgfmt-write-qt.obj: write-qt.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-qt.obj `if test -f 'write-qt.c'; then $(CYGPATH_W) 'write-qt.c'; else $(CYGPATH_W) '$(srcdir)/write-qt.c'; fi` + +msgfmt-write-desktop.o: write-desktop.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-desktop.o `test -f 'write-desktop.c' || echo '$(srcdir)/'`write-desktop.c + +msgfmt-write-desktop.obj: write-desktop.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgfmt-write-desktop.obj `if test -f 'write-desktop.c'; then $(CYGPATH_W) 'write-desktop.c'; else $(CYGPATH_W) '$(srcdir)/write-desktop.c'; fi` + +../../gettext-runtime/intl/msgfmt-hash-string.o: ../../gettext-runtime/intl/hash-string.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../../gettext-runtime/intl/msgfmt-hash-string.o `test -f '../../gettext-runtime/intl/hash-string.c' || echo '$(srcdir)/'`../../gettext-runtime/intl/hash-string.c + +../../gettext-runtime/intl/msgfmt-hash-string.obj: ../../gettext-runtime/intl/hash-string.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../../gettext-runtime/intl/msgfmt-hash-string.obj `if test -f '../../gettext-runtime/intl/hash-string.c'; then $(CYGPATH_W) '../../gettext-runtime/intl/hash-string.c'; else $(CYGPATH_W) '$(srcdir)/../../gettext-runtime/intl/hash-string.c'; fi` + +msggrep-msggrep.o: msggrep.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msggrep_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msggrep-msggrep.o `test -f 'msggrep.c' || echo '$(srcdir)/'`msggrep.c + +msggrep-msggrep.obj: msggrep.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msggrep_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msggrep-msggrep.obj `if test -f 'msggrep.c'; then $(CYGPATH_W) 'msggrep.c'; else $(CYGPATH_W) '$(srcdir)/msggrep.c'; fi` + +msginit-msginit.o: msginit.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msginit-msginit.o `test -f 'msginit.c' || echo '$(srcdir)/'`msginit.c + +msginit-msginit.obj: msginit.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msginit-msginit.obj `if test -f 'msginit.c'; then $(CYGPATH_W) 'msginit.c'; else $(CYGPATH_W) '$(srcdir)/msginit.c'; fi` + +msginit-lang-table.o: lang-table.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msginit-lang-table.o `test -f 'lang-table.c' || echo '$(srcdir)/'`lang-table.c + +msginit-lang-table.obj: lang-table.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msginit-lang-table.obj `if test -f 'lang-table.c'; then $(CYGPATH_W) 'lang-table.c'; else $(CYGPATH_W) '$(srcdir)/lang-table.c'; fi` + +msginit-plural-count.o: plural-count.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msginit-plural-count.o `test -f 'plural-count.c' || echo '$(srcdir)/'`plural-count.c + +msginit-plural-count.obj: plural-count.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msginit-plural-count.obj `if test -f 'plural-count.c'; then $(CYGPATH_W) 'plural-count.c'; else $(CYGPATH_W) '$(srcdir)/plural-count.c'; fi` + +../../gettext-runtime/intl/msginit-localealias.o: ../../gettext-runtime/intl/localealias.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../../gettext-runtime/intl/msginit-localealias.o `test -f '../../gettext-runtime/intl/localealias.c' || echo '$(srcdir)/'`../../gettext-runtime/intl/localealias.c + +../../gettext-runtime/intl/msginit-localealias.obj: ../../gettext-runtime/intl/localealias.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../../gettext-runtime/intl/msginit-localealias.obj `if test -f '../../gettext-runtime/intl/localealias.c'; then $(CYGPATH_W) '../../gettext-runtime/intl/localealias.c'; else $(CYGPATH_W) '$(srcdir)/../../gettext-runtime/intl/localealias.c'; fi` + +../../gettext-runtime/intl/msginit-lock.o: ../../gettext-runtime/intl/lock.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../../gettext-runtime/intl/msginit-lock.o `test -f '../../gettext-runtime/intl/lock.c' || echo '$(srcdir)/'`../../gettext-runtime/intl/lock.c + +../../gettext-runtime/intl/msginit-lock.obj: ../../gettext-runtime/intl/lock.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msginit_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../../gettext-runtime/intl/msginit-lock.obj `if test -f '../../gettext-runtime/intl/lock.c'; then $(CYGPATH_W) '../../gettext-runtime/intl/lock.c'; else $(CYGPATH_W) '$(srcdir)/../../gettext-runtime/intl/lock.c'; fi` + +msgmerge-msgmerge.o: msgmerge.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CFLAGS) $(CFLAGS) -c -o msgmerge-msgmerge.o `test -f 'msgmerge.c' || echo '$(srcdir)/'`msgmerge.c + +msgmerge-msgmerge.obj: msgmerge.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CFLAGS) $(CFLAGS) -c -o msgmerge-msgmerge.obj `if test -f 'msgmerge.c'; then $(CYGPATH_W) 'msgmerge.c'; else $(CYGPATH_W) '$(srcdir)/msgmerge.c'; fi` + +msgmerge-msgl-fsearch.o: msgl-fsearch.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CFLAGS) $(CFLAGS) -c -o msgmerge-msgl-fsearch.o `test -f 'msgl-fsearch.c' || echo '$(srcdir)/'`msgl-fsearch.c + +msgmerge-msgl-fsearch.obj: msgl-fsearch.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CFLAGS) $(CFLAGS) -c -o msgmerge-msgl-fsearch.obj `if test -f 'msgl-fsearch.c'; then $(CYGPATH_W) 'msgl-fsearch.c'; else $(CYGPATH_W) '$(srcdir)/msgl-fsearch.c'; fi` + +msgmerge-lang-table.o: lang-table.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CFLAGS) $(CFLAGS) -c -o msgmerge-lang-table.o `test -f 'lang-table.c' || echo '$(srcdir)/'`lang-table.c + +msgmerge-lang-table.obj: lang-table.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CFLAGS) $(CFLAGS) -c -o msgmerge-lang-table.obj `if test -f 'lang-table.c'; then $(CYGPATH_W) 'lang-table.c'; else $(CYGPATH_W) '$(srcdir)/lang-table.c'; fi` + +msgmerge-plural-count.o: plural-count.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CFLAGS) $(CFLAGS) -c -o msgmerge-plural-count.o `test -f 'plural-count.c' || echo '$(srcdir)/'`plural-count.c + +msgmerge-plural-count.obj: plural-count.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CFLAGS) $(CFLAGS) -c -o msgmerge-plural-count.obj `if test -f 'plural-count.c'; then $(CYGPATH_W) 'plural-count.c'; else $(CYGPATH_W) '$(srcdir)/plural-count.c'; fi` + +msgunfmt-msgunfmt.o: msgunfmt.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-msgunfmt.o `test -f 'msgunfmt.c' || echo '$(srcdir)/'`msgunfmt.c + +msgunfmt-msgunfmt.obj: msgunfmt.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-msgunfmt.obj `if test -f 'msgunfmt.c'; then $(CYGPATH_W) 'msgunfmt.c'; else $(CYGPATH_W) '$(srcdir)/msgunfmt.c'; fi` + +msgunfmt-read-mo.o: read-mo.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-mo.o `test -f 'read-mo.c' || echo '$(srcdir)/'`read-mo.c + +msgunfmt-read-mo.obj: read-mo.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-mo.obj `if test -f 'read-mo.c'; then $(CYGPATH_W) 'read-mo.c'; else $(CYGPATH_W) '$(srcdir)/read-mo.c'; fi` + +msgunfmt-read-java.o: read-java.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-java.o `test -f 'read-java.c' || echo '$(srcdir)/'`read-java.c + +msgunfmt-read-java.obj: read-java.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-java.obj `if test -f 'read-java.c'; then $(CYGPATH_W) 'read-java.c'; else $(CYGPATH_W) '$(srcdir)/read-java.c'; fi` + +msgunfmt-read-csharp.o: read-csharp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-csharp.o `test -f 'read-csharp.c' || echo '$(srcdir)/'`read-csharp.c + +msgunfmt-read-csharp.obj: read-csharp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-csharp.obj `if test -f 'read-csharp.c'; then $(CYGPATH_W) 'read-csharp.c'; else $(CYGPATH_W) '$(srcdir)/read-csharp.c'; fi` + +msgunfmt-read-resources.o: read-resources.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-resources.o `test -f 'read-resources.c' || echo '$(srcdir)/'`read-resources.c + +msgunfmt-read-resources.obj: read-resources.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-resources.obj `if test -f 'read-resources.c'; then $(CYGPATH_W) 'read-resources.c'; else $(CYGPATH_W) '$(srcdir)/read-resources.c'; fi` + +msgunfmt-read-tcl.o: read-tcl.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-tcl.o `test -f 'read-tcl.c' || echo '$(srcdir)/'`read-tcl.c + +msgunfmt-read-tcl.obj: read-tcl.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgunfmt_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msgunfmt-read-tcl.obj `if test -f 'read-tcl.c'; then $(CYGPATH_W) 'read-tcl.c'; else $(CYGPATH_W) '$(srcdir)/read-tcl.c'; fi` + +msguniq-msguniq.o: msguniq.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msguniq_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msguniq-msguniq.o `test -f 'msguniq.c' || echo '$(srcdir)/'`msguniq.c + +msguniq-msguniq.obj: msguniq.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msguniq_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o msguniq-msguniq.obj `if test -f 'msguniq.c'; then $(CYGPATH_W) 'msguniq.c'; else $(CYGPATH_W) '$(srcdir)/msguniq.c'; fi` + +recode_sr_latin-recode-sr-latin.o: recode-sr-latin.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(recode_sr_latin_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o recode_sr_latin-recode-sr-latin.o `test -f 'recode-sr-latin.c' || echo '$(srcdir)/'`recode-sr-latin.c + +recode_sr_latin-recode-sr-latin.obj: recode-sr-latin.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(recode_sr_latin_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o recode_sr_latin-recode-sr-latin.obj `if test -f 'recode-sr-latin.c'; then $(CYGPATH_W) 'recode-sr-latin.c'; else $(CYGPATH_W) '$(srcdir)/recode-sr-latin.c'; fi` + +recode_sr_latin-filter-sr-latin.o: filter-sr-latin.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(recode_sr_latin_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o recode_sr_latin-filter-sr-latin.o `test -f 'filter-sr-latin.c' || echo '$(srcdir)/'`filter-sr-latin.c + +recode_sr_latin-filter-sr-latin.obj: filter-sr-latin.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(recode_sr_latin_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o recode_sr_latin-filter-sr-latin.obj `if test -f 'filter-sr-latin.c'; then $(CYGPATH_W) 'filter-sr-latin.c'; else $(CYGPATH_W) '$(srcdir)/filter-sr-latin.c'; fi` + +urlget-urlget.o: urlget.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(urlget_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o urlget-urlget.o `test -f 'urlget.c' || echo '$(srcdir)/'`urlget.c + +urlget-urlget.obj: urlget.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(urlget_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o urlget-urlget.obj `if test -f 'urlget.c'; then $(CYGPATH_W) 'urlget.c'; else $(CYGPATH_W) '$(srcdir)/urlget.c'; fi` + +xgettext-xgettext.o: xgettext.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-xgettext.o `test -f 'xgettext.c' || echo '$(srcdir)/'`xgettext.c + +xgettext-xgettext.obj: xgettext.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-xgettext.obj `if test -f 'xgettext.c'; then $(CYGPATH_W) 'xgettext.c'; else $(CYGPATH_W) '$(srcdir)/xgettext.c'; fi` + +xgettext-x-c.o: x-c.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-c.o `test -f 'x-c.c' || echo '$(srcdir)/'`x-c.c + +xgettext-x-c.obj: x-c.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-c.obj `if test -f 'x-c.c'; then $(CYGPATH_W) 'x-c.c'; else $(CYGPATH_W) '$(srcdir)/x-c.c'; fi` + +xgettext-x-po.o: x-po.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-po.o `test -f 'x-po.c' || echo '$(srcdir)/'`x-po.c + +xgettext-x-po.obj: x-po.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-po.obj `if test -f 'x-po.c'; then $(CYGPATH_W) 'x-po.c'; else $(CYGPATH_W) '$(srcdir)/x-po.c'; fi` + +xgettext-x-sh.o: x-sh.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-sh.o `test -f 'x-sh.c' || echo '$(srcdir)/'`x-sh.c + +xgettext-x-sh.obj: x-sh.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-sh.obj `if test -f 'x-sh.c'; then $(CYGPATH_W) 'x-sh.c'; else $(CYGPATH_W) '$(srcdir)/x-sh.c'; fi` + +xgettext-x-python.o: x-python.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-python.o `test -f 'x-python.c' || echo '$(srcdir)/'`x-python.c + +xgettext-x-python.obj: x-python.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-python.obj `if test -f 'x-python.c'; then $(CYGPATH_W) 'x-python.c'; else $(CYGPATH_W) '$(srcdir)/x-python.c'; fi` + +xgettext-x-lisp.o: x-lisp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-lisp.o `test -f 'x-lisp.c' || echo '$(srcdir)/'`x-lisp.c + +xgettext-x-lisp.obj: x-lisp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-lisp.obj `if test -f 'x-lisp.c'; then $(CYGPATH_W) 'x-lisp.c'; else $(CYGPATH_W) '$(srcdir)/x-lisp.c'; fi` + +xgettext-x-elisp.o: x-elisp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-elisp.o `test -f 'x-elisp.c' || echo '$(srcdir)/'`x-elisp.c + +xgettext-x-elisp.obj: x-elisp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-elisp.obj `if test -f 'x-elisp.c'; then $(CYGPATH_W) 'x-elisp.c'; else $(CYGPATH_W) '$(srcdir)/x-elisp.c'; fi` + +xgettext-x-librep.o: x-librep.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-librep.o `test -f 'x-librep.c' || echo '$(srcdir)/'`x-librep.c + +xgettext-x-librep.obj: x-librep.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-librep.obj `if test -f 'x-librep.c'; then $(CYGPATH_W) 'x-librep.c'; else $(CYGPATH_W) '$(srcdir)/x-librep.c'; fi` + +xgettext-x-scheme.o: x-scheme.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-scheme.o `test -f 'x-scheme.c' || echo '$(srcdir)/'`x-scheme.c + +xgettext-x-scheme.obj: x-scheme.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-scheme.obj `if test -f 'x-scheme.c'; then $(CYGPATH_W) 'x-scheme.c'; else $(CYGPATH_W) '$(srcdir)/x-scheme.c'; fi` + +xgettext-x-smalltalk.o: x-smalltalk.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-smalltalk.o `test -f 'x-smalltalk.c' || echo '$(srcdir)/'`x-smalltalk.c + +xgettext-x-smalltalk.obj: x-smalltalk.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-smalltalk.obj `if test -f 'x-smalltalk.c'; then $(CYGPATH_W) 'x-smalltalk.c'; else $(CYGPATH_W) '$(srcdir)/x-smalltalk.c'; fi` + +xgettext-x-java.o: x-java.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-java.o `test -f 'x-java.c' || echo '$(srcdir)/'`x-java.c + +xgettext-x-java.obj: x-java.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-java.obj `if test -f 'x-java.c'; then $(CYGPATH_W) 'x-java.c'; else $(CYGPATH_W) '$(srcdir)/x-java.c'; fi` + +xgettext-x-csharp.o: x-csharp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-csharp.o `test -f 'x-csharp.c' || echo '$(srcdir)/'`x-csharp.c + +xgettext-x-csharp.obj: x-csharp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-csharp.obj `if test -f 'x-csharp.c'; then $(CYGPATH_W) 'x-csharp.c'; else $(CYGPATH_W) '$(srcdir)/x-csharp.c'; fi` + +xgettext-x-awk.o: x-awk.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-awk.o `test -f 'x-awk.c' || echo '$(srcdir)/'`x-awk.c + +xgettext-x-awk.obj: x-awk.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-awk.obj `if test -f 'x-awk.c'; then $(CYGPATH_W) 'x-awk.c'; else $(CYGPATH_W) '$(srcdir)/x-awk.c'; fi` + +xgettext-x-ycp.o: x-ycp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-ycp.o `test -f 'x-ycp.c' || echo '$(srcdir)/'`x-ycp.c + +xgettext-x-ycp.obj: x-ycp.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-ycp.obj `if test -f 'x-ycp.c'; then $(CYGPATH_W) 'x-ycp.c'; else $(CYGPATH_W) '$(srcdir)/x-ycp.c'; fi` + +xgettext-x-tcl.o: x-tcl.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-tcl.o `test -f 'x-tcl.c' || echo '$(srcdir)/'`x-tcl.c + +xgettext-x-tcl.obj: x-tcl.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-tcl.obj `if test -f 'x-tcl.c'; then $(CYGPATH_W) 'x-tcl.c'; else $(CYGPATH_W) '$(srcdir)/x-tcl.c'; fi` + +xgettext-x-perl.o: x-perl.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-perl.o `test -f 'x-perl.c' || echo '$(srcdir)/'`x-perl.c + +xgettext-x-perl.obj: x-perl.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-perl.obj `if test -f 'x-perl.c'; then $(CYGPATH_W) 'x-perl.c'; else $(CYGPATH_W) '$(srcdir)/x-perl.c'; fi` + +xgettext-x-php.o: x-php.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-php.o `test -f 'x-php.c' || echo '$(srcdir)/'`x-php.c + +xgettext-x-php.obj: x-php.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-php.obj `if test -f 'x-php.c'; then $(CYGPATH_W) 'x-php.c'; else $(CYGPATH_W) '$(srcdir)/x-php.c'; fi` + +xgettext-x-rst.o: x-rst.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-rst.o `test -f 'x-rst.c' || echo '$(srcdir)/'`x-rst.c + +xgettext-x-rst.obj: x-rst.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-rst.obj `if test -f 'x-rst.c'; then $(CYGPATH_W) 'x-rst.c'; else $(CYGPATH_W) '$(srcdir)/x-rst.c'; fi` + +xgettext-x-glade.o: x-glade.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-glade.o `test -f 'x-glade.c' || echo '$(srcdir)/'`x-glade.c + +xgettext-x-glade.obj: x-glade.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-glade.obj `if test -f 'x-glade.c'; then $(CYGPATH_W) 'x-glade.c'; else $(CYGPATH_W) '$(srcdir)/x-glade.c'; fi` + +xgettext-x-lua.o: x-lua.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-lua.o `test -f 'x-lua.c' || echo '$(srcdir)/'`x-lua.c + +xgettext-x-lua.obj: x-lua.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-lua.obj `if test -f 'x-lua.c'; then $(CYGPATH_W) 'x-lua.c'; else $(CYGPATH_W) '$(srcdir)/x-lua.c'; fi` + +xgettext-x-javascript.o: x-javascript.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.o `test -f 'x-javascript.c' || echo '$(srcdir)/'`x-javascript.c + +xgettext-x-javascript.obj: x-javascript.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.obj `if test -f 'x-javascript.c'; then $(CYGPATH_W) 'x-javascript.c'; else $(CYGPATH_W) '$(srcdir)/x-javascript.c'; fi` + +xgettext-x-vala.o: x-vala.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-vala.o `test -f 'x-vala.c' || echo '$(srcdir)/'`x-vala.c + +xgettext-x-vala.obj: x-vala.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-vala.obj `if test -f 'x-vala.c'; then $(CYGPATH_W) 'x-vala.c'; else $(CYGPATH_W) '$(srcdir)/x-vala.c'; fi` + +xgettext-x-gsettings.o: x-gsettings.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-gsettings.o `test -f 'x-gsettings.c' || echo '$(srcdir)/'`x-gsettings.c + +xgettext-x-gsettings.obj: x-gsettings.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-gsettings.obj `if test -f 'x-gsettings.c'; then $(CYGPATH_W) 'x-gsettings.c'; else $(CYGPATH_W) '$(srcdir)/x-gsettings.c'; fi` + +xgettext-libexpat-compat.o: libexpat-compat.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-libexpat-compat.o `test -f 'libexpat-compat.c' || echo '$(srcdir)/'`libexpat-compat.c + +xgettext-libexpat-compat.obj: libexpat-compat.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-libexpat-compat.obj `if test -f 'libexpat-compat.c'; then $(CYGPATH_W) 'libexpat-compat.c'; else $(CYGPATH_W) '$(srcdir)/libexpat-compat.c'; fi` + +xgettext-x-desktop.o: x-desktop.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-desktop.o `test -f 'x-desktop.c' || echo '$(srcdir)/'`x-desktop.c + +xgettext-x-desktop.obj: x-desktop.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-desktop.obj `if test -f 'x-desktop.c'; then $(CYGPATH_W) 'x-desktop.c'; else $(CYGPATH_W) '$(srcdir)/x-desktop.c'; fi` + +.cc.o: + $(AM_V_CXX)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: + $(AM_V_CXX)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: + $(AM_V_CXX)$(LTCXXCOMPILE) -c -o $@ $< + +../woe32dll/libgettextsrc_la-c++format.lo: ../woe32dll/c++format.cc + $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgettextsrc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/libgettextsrc_la-c++format.lo `test -f '../woe32dll/c++format.cc' || echo '$(srcdir)/'`../woe32dll/c++format.cc + +../woe32dll/msgattrib-c++msgattrib.o: ../woe32dll/c++msgattrib.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgattrib_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgattrib-c++msgattrib.o `test -f '../woe32dll/c++msgattrib.cc' || echo '$(srcdir)/'`../woe32dll/c++msgattrib.cc + +../woe32dll/msgattrib-c++msgattrib.obj: ../woe32dll/c++msgattrib.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgattrib_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgattrib-c++msgattrib.obj `if test -f '../woe32dll/c++msgattrib.cc'; then $(CYGPATH_W) '../woe32dll/c++msgattrib.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msgattrib.cc'; fi` + +../woe32dll/msgcat-c++msgcat.o: ../woe32dll/c++msgcat.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcat_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgcat-c++msgcat.o `test -f '../woe32dll/c++msgcat.cc' || echo '$(srcdir)/'`../woe32dll/c++msgcat.cc + +../woe32dll/msgcat-c++msgcat.obj: ../woe32dll/c++msgcat.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcat_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgcat-c++msgcat.obj `if test -f '../woe32dll/c++msgcat.cc'; then $(CYGPATH_W) '../woe32dll/c++msgcat.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msgcat.cc'; fi` + +../woe32dll/msgcomm-c++msgcomm.o: ../woe32dll/c++msgcomm.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcomm_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgcomm-c++msgcomm.o `test -f '../woe32dll/c++msgcomm.cc' || echo '$(srcdir)/'`../woe32dll/c++msgcomm.cc + +../woe32dll/msgcomm-c++msgcomm.obj: ../woe32dll/c++msgcomm.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgcomm_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgcomm-c++msgcomm.obj `if test -f '../woe32dll/c++msgcomm.cc'; then $(CYGPATH_W) '../woe32dll/c++msgcomm.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msgcomm.cc'; fi` + +../woe32dll/msgconv-c++msgconv.o: ../woe32dll/c++msgconv.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgconv_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgconv-c++msgconv.o `test -f '../woe32dll/c++msgconv.cc' || echo '$(srcdir)/'`../woe32dll/c++msgconv.cc + +../woe32dll/msgconv-c++msgconv.obj: ../woe32dll/c++msgconv.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgconv_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgconv-c++msgconv.obj `if test -f '../woe32dll/c++msgconv.cc'; then $(CYGPATH_W) '../woe32dll/c++msgconv.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msgconv.cc'; fi` + +../woe32dll/msgen-c++msgen.o: ../woe32dll/c++msgen.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgen_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgen-c++msgen.o `test -f '../woe32dll/c++msgen.cc' || echo '$(srcdir)/'`../woe32dll/c++msgen.cc + +../woe32dll/msgen-c++msgen.obj: ../woe32dll/c++msgen.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgen_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgen-c++msgen.obj `if test -f '../woe32dll/c++msgen.cc'; then $(CYGPATH_W) '../woe32dll/c++msgen.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msgen.cc'; fi` + +../woe32dll/msgfilter-c++msgfilter.o: ../woe32dll/c++msgfilter.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfilter_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgfilter-c++msgfilter.o `test -f '../woe32dll/c++msgfilter.cc' || echo '$(srcdir)/'`../woe32dll/c++msgfilter.cc + +../woe32dll/msgfilter-c++msgfilter.obj: ../woe32dll/c++msgfilter.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgfilter_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgfilter-c++msgfilter.obj `if test -f '../woe32dll/c++msgfilter.cc'; then $(CYGPATH_W) '../woe32dll/c++msgfilter.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msgfilter.cc'; fi` + +../woe32dll/msggrep-c++msggrep.o: ../woe32dll/c++msggrep.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msggrep_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msggrep-c++msggrep.o `test -f '../woe32dll/c++msggrep.cc' || echo '$(srcdir)/'`../woe32dll/c++msggrep.cc + +../woe32dll/msggrep-c++msggrep.obj: ../woe32dll/c++msggrep.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msggrep_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msggrep-c++msggrep.obj `if test -f '../woe32dll/c++msggrep.cc'; then $(CYGPATH_W) '../woe32dll/c++msggrep.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msggrep.cc'; fi` + +../woe32dll/msgmerge-c++msgmerge.o: ../woe32dll/c++msgmerge.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgmerge-c++msgmerge.o `test -f '../woe32dll/c++msgmerge.cc' || echo '$(srcdir)/'`../woe32dll/c++msgmerge.cc + +../woe32dll/msgmerge-c++msgmerge.obj: ../woe32dll/c++msgmerge.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msgmerge_CPPFLAGS) $(CPPFLAGS) $(msgmerge_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msgmerge-c++msgmerge.obj `if test -f '../woe32dll/c++msgmerge.cc'; then $(CYGPATH_W) '../woe32dll/c++msgmerge.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msgmerge.cc'; fi` + +../woe32dll/msguniq-c++msguniq.o: ../woe32dll/c++msguniq.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msguniq_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msguniq-c++msguniq.o `test -f '../woe32dll/c++msguniq.cc' || echo '$(srcdir)/'`../woe32dll/c++msguniq.cc + +../woe32dll/msguniq-c++msguniq.obj: ../woe32dll/c++msguniq.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(msguniq_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/msguniq-c++msguniq.obj `if test -f '../woe32dll/c++msguniq.cc'; then $(CYGPATH_W) '../woe32dll/c++msguniq.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++msguniq.cc'; fi` + +../woe32dll/xgettext-c++xgettext.o: ../woe32dll/c++xgettext.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/xgettext-c++xgettext.o `test -f '../woe32dll/c++xgettext.cc' || echo '$(srcdir)/'`../woe32dll/c++xgettext.cc + +../woe32dll/xgettext-c++xgettext.obj: ../woe32dll/c++xgettext.cc + $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../woe32dll/xgettext-c++xgettext.obj `if test -f '../woe32dll/c++xgettext.cc'; then $(CYGPATH_W) '../woe32dll/c++xgettext.cc'; else $(CYGPATH_W) '$(srcdir)/../woe32dll/c++xgettext.cc'; fi` + +.y.c: + $(AM_V_YACC)$(am__skipyacc) $(SHELL) $(YLWRAP) $< y.tab.c $@ y.tab.h `echo $@ | $(am__yacc_c2h)` y.output $*.output -- $(YACCCOMPILE) + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf ../woe32dll/.libs ../woe32dll/_libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(HEADERS) all-local +install-binPROGRAMS: install-libLTLIBRARIES + +installdirs: installdirs-local + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + -test -z "$(MOSTLYCLEANFILES)" || rm -f $(MOSTLYCLEANFILES) + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f ../../gettext-runtime/intl/$(am__dirstamp) + -rm -f ../woe32dll/$(am__dirstamp) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -rm -f po-gram-gen.c + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \ + clean-libtool clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-data-local + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-exec-local \ + install-libLTLIBRARIES + @$(NORMAL_INSTALL) + $(MAKE) $(AM_MAKEFLAGS) install-exec-hook +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: installcheck-binPROGRAMS + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-libLTLIBRARIES \ + uninstall-local + +.MAKE: all check install install-am install-exec-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am all-local check check-am clean \ + clean-binPROGRAMS clean-generic clean-libLTLIBRARIES \ + clean-libtool clean-noinstPROGRAMS cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-data-local install-dvi \ + install-dvi-am install-exec install-exec-am install-exec-hook \ + install-exec-local install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installcheck-binPROGRAMS \ + installdirs installdirs-local maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \ + uninstall-libLTLIBRARIES uninstall-local + + +# No need to install libgettextsrc.a, except on AIX. +install-exec-hook: install-exec-clean +install-exec-clean: + case "@host_os@" in \ + aix*) ;; \ + *) $(RM) $(DESTDIR)$(libdir)/libgettextsrc.a ;; \ + esac + +po-lex.o po-lex.lo: po-gram-gen2.h +po-gram-gen2.h: po-gram-gen.h + srcdir=''; \ + test -f ./po-gram-gen.h || srcdir=$(srcdir)/; \ + $(SED) -e 's/yy/po_gram_/g' -e 's/extern /extern DLL_VARIABLE /' \ + $${srcdir}po-gram-gen.h > $@-tmp && \ + mv $@-tmp $@ + +# Special rules for installation of auxiliary programs. + +install-exec-local: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) hostname$(EXEEXT) $(DESTDIR)$(pkglibdir)/hostname$(EXEEXT) + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) urlget$(EXEEXT) $(DESTDIR)$(pkglibdir)/urlget$(EXEEXT) + $(INSTALL_SCRIPT) user-email $(DESTDIR)$(pkglibdir)/user-email + $(INSTALL_SCRIPT) $(srcdir)/project-id $(DESTDIR)$(pkglibdir)/project-id + +installdirs-local: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + +uninstall-local: + $(RM) $(DESTDIR)$(pkglibdir)/hostname$(EXEEXT) + $(RM) $(DESTDIR)$(pkglibdir)/urlget$(EXEEXT) + $(RM) $(DESTDIR)$(pkglibdir)/user-email + $(RM) $(DESTDIR)$(pkglibdir)/project-id + +all-local: all-java-@BUILDJAVAEXE@ +all-java-yes: gnu.gettext.DumpResource$(EXEEXT) gnu.gettext.GetURL$(EXEEXT) +all-java-no: all-java-no-@BUILDJAVA@ +all-java-no-yes: gettext.jar +all-java-no-no: + +gnu.gettext.DumpResource$(EXEEXT): $(srcdir)/gnu/gettext/DumpResource.java + $(GCJ) $(GCJFLAGS) $(srcdir)/gnu/gettext/DumpResource.java --main=gnu.gettext.DumpResource -o $@ + +gnu.gettext.GetURL$(EXEEXT): $(srcdir)/gnu/gettext/GetURL.java + $(GCJ) $(GCJFLAGS) $(srcdir)/gnu/gettext/GetURL.java --main=gnu.gettext.GetURL -o $@ + +gnu/gettext/DumpResource.class: $(srcdir)/gnu/gettext/DumpResource.java + $(JAVACOMP) -d . $(srcdir)/gnu/gettext/DumpResource.java + +gnu/gettext/GetURL.class: $(srcdir)/gnu/gettext/GetURL.java + $(JAVACOMP) -d . $(srcdir)/gnu/gettext/GetURL.java + +gettext.jar: gnu/gettext/DumpResource.class gnu/gettext/GetURL.class + $(JAR) cf $@ gnu/gettext/DumpResource*.class gnu/gettext/GetURL*.class + +install-exec-local: install-exec-java-@BUILDJAVAEXE@ +install-exec-java-yes: all-java-yes + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) gnu.gettext.DumpResource$(EXEEXT) $(DESTDIR)$(pkglibdir)/gnu.gettext.DumpResource$(EXEEXT) + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) gnu.gettext.GetURL$(EXEEXT) $(DESTDIR)$(pkglibdir)/gnu.gettext.GetURL$(EXEEXT) +install-exec-java-no: + +install-data-local: install-data-java-@BUILDJAVAEXE@ +install-data-java-yes: +install-data-java-no: install-data-java-no-@BUILDJAVA@ +install-data-java-no-yes: all-java-no-yes + $(INSTALL_DATA) gettext.jar $(DESTDIR)$(jardir)/gettext.jar +install-data-java-no-no: + +installdirs-local: installdirs-java-@BUILDJAVAEXE@ +installdirs-java-yes: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) +installdirs-java-no: installdirs-java-no-@BUILDJAVA@ +installdirs-java-no-yes: + $(MKDIR_P) $(DESTDIR)$(jardir) +installdirs-java-no-no: + +uninstall-local: uninstall-java-@BUILDJAVAEXE@ +uninstall-java-yes: + $(RM) $(DESTDIR)$(pkglibdir)/gnu.gettext.DumpResource$(EXEEXT) + $(RM) $(DESTDIR)$(pkglibdir)/gnu.gettext.GetURL$(EXEEXT) +uninstall-java-no: uninstall-java-no-@BUILDJAVA@ +uninstall-java-no-yes: + $(RM) $(DESTDIR)$(jardir)/gettext.jar +uninstall-java-no-no: + +all-local: all-csharp-@BUILDCSHARP@ +all-csharp-yes: msgfmt.net.exe msgunfmt.net.exe +all-csharp-no: + +msgfmt.net.exe: msgfmt.cs + $(CSHARPCOMP) $(CSHARPCOMPFLAGS) -o $@ $(srcdir)/msgfmt.cs + +msgunfmt.net.exe: msgunfmt.cs + $(CSHARPCOMP) $(CSHARPCOMPFLAGS) -o $@ -L ../../gettext-runtime/intl-csharp -l GNU.Gettext $(srcdir)/msgunfmt.cs + +install-exec-local: install-exec-csharp-@BUILDCSHARP@ +install-exec-csharp-yes: all-csharp-yes + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + $(INSTALL_DATA) msgfmt.net.exe $(DESTDIR)$(pkglibdir)/msgfmt.net.exe + $(INSTALL_DATA) msgunfmt.net.exe $(DESTDIR)$(pkglibdir)/msgunfmt.net.exe +install-exec-csharp-no: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + +installdirs-local: install-csharp +installdirs-csharp: + $(MKDIR_P) $(DESTDIR)$(pkglibdir) + +uninstall-local: uninstall-csharp-@BUILDCSHARP@ +uninstall-csharp-yes: all-csharp-yes + $(RM) $(DESTDIR)$(pkglibdir)/msgfmt.net.exe + $(RM) $(DESTDIR)$(pkglibdir)/msgunfmt.net.exe +uninstall-csharp-no: + +install-data-local: install-tcl +install-tcl: + $(MKDIR_P) $(DESTDIR)$(pkgdatadir) + $(INSTALL_DATA) $(srcdir)/msgunfmt.tcl $(DESTDIR)$(pkgdatadir)/msgunfmt.tcl + +installdirs-local: installdirs-tcl +installdirs-tcl: + $(MKDIR_P) $(DESTDIR)$(pkgdatadir) + +uninstall-local: uninstall-tcl +uninstall-tcl: + $(RM) $(DESTDIR)$(pkgdatadir)/msgunfmt.tcl +@WOE32_TRUE@gettext.res : $(top_srcdir)/../windows/gettext.rc +@WOE32_TRUE@ $(WINDRES) `$(SHELL) $(top_srcdir)/../windows/windres-options --escape $(VERSION)` -i $(top_srcdir)/../windows/gettext.rc -o gettext.res --output-format=coff + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/gettext-tools/src/color.c b/gettext-tools/src/color.c new file mode 100644 index 0000000..47daf00 --- /dev/null +++ b/gettext-tools/src/color.c @@ -0,0 +1,445 @@ +/* Color and styling handling. + Copyright (C) 2006-2008 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "color.h" + +#include +#include +#include +#include +#include + +#include "term-ostream.h" +#include "xalloc.h" +#include "relocatable.h" +#include "filename.h" +#include "concat-filename.h" + + +/* Whether to output a test page. */ +bool color_test_mode; + +/* Color option. */ +enum color_option color_mode = color_tty; + +/* Style to use when coloring. */ +const char *style_file_name; + +/* --color argument handling. Return an error indicator. */ +bool +handle_color_option (const char *option) +{ + if (option != NULL) + { + if (strcmp (option, "never") == 0 || strcmp (option, "no") == 0) + color_mode = color_no; + else if (strcmp (option, "auto") == 0 || strcmp (option, "tty") == 0) + color_mode = color_tty; + else if (strcmp (option, "always") == 0 || strcmp (option, "yes") == 0) + color_mode = color_yes; + else if (strcmp (option, "html") == 0) + color_mode = color_html; + else if (strcmp (option, "test") == 0) + color_test_mode = true; + else + { + fprintf (stderr, "invalid --color argument: %s\n", option); + return true; + } + } + else + /* --color is equivalent to --color=yes. */ + color_mode = color_yes; + return false; +} + +/* --style argument handling. */ +void +handle_style_option (const char *option) +{ + style_file_name = option; +} + +/* Print a color test page. */ +void +print_color_test () +{ + /* Code copied from test-term-ostream.c. */ + static struct { const char *name; term_color_t c; int r; int g; int b; } + colors[] = + { + { "black", -2, 0, 0, 0 }, + { "blue", -2, 0, 0, 255 }, + { "green", -2, 0, 255, 0 }, + { "cyan", -2, 0, 255, 255 }, + { "red", -2, 255, 0, 0 }, + { "magenta", -2, 255, 0, 255 }, + { "yellow", -2, 255, 255, 0 }, + { "white", -2, 255, 255, 255 }, + { "default", COLOR_DEFAULT } + }; + term_ostream_t stream; + int i, row, col; + + stream = term_ostream_create (1, "stdout"); + + for (i = 0; i < 8; i++) + colors[i].c = + term_ostream_rgb_to_color (stream, colors[i].r, colors[i].g, colors[i].b); + + ostream_write_str (stream, "Colors (foreground/background):\n"); + ostream_write_str (stream, " "); + for (col = 0; col <= 8; col++) + { + const char *name = colors[col].name; + ostream_write_str (stream, "|"); + ostream_write_str (stream, name); + ostream_write_mem (stream, " ", 7 - strlen (name)); + } + ostream_write_str (stream, "\n"); + for (row = 0; row <= 8; row++) + { + const char *name = colors[row].name; + ostream_write_str (stream, name); + ostream_write_mem (stream, " ", 7 - strlen (name)); + for (col = 0; col <= 8; col++) + { + term_color_t row_color = colors[row].c; + term_color_t col_color = colors[col].c; + + ostream_write_str (stream, "|"); + term_ostream_set_color (stream, row_color); + term_ostream_set_bgcolor (stream, col_color); + if (!(term_ostream_get_color (stream) == row_color + && term_ostream_get_bgcolor (stream) == col_color)) + abort (); + ostream_write_str (stream, " Words "); + term_ostream_set_color (stream, COLOR_DEFAULT); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + if (!(term_ostream_get_color (stream) == COLOR_DEFAULT + && term_ostream_get_bgcolor (stream) == COLOR_DEFAULT)) + abort (); + } + ostream_write_str (stream, "\n"); + } + ostream_write_str (stream, "\n"); + + ostream_write_str (stream, "Colors (hue/saturation):\n"); + /* Hue from 0 to 1. */ + for (row = 0; row <= 17; row++) + { + ostream_write_str (stream, row == 0 ? "red: " : " "); + for (col = 0; col <= 64; col++) + { + int r = 255; + int b = (int) (255.0f / 64.0f * col + 0.5f); + int g = b + (int) (row / 17.0f * (r - b) + 0.5f); + term_color_t c = term_ostream_rgb_to_color (stream, r, g, b); + term_ostream_set_bgcolor (stream, c); + ostream_write_str (stream, " "); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + } + ostream_write_str (stream, "\n"); + } + /* Hue from 1 to 2. */ + for (row = 17; row >= 0; row--) + { + ostream_write_str (stream, row == 17 ? "yellow: " : " "); + for (col = 0; col <= 64; col++) + { + int g = 255; + int b = (int) (255.0f / 64.0f * col + 0.5f); + int r = b + (int) (row / 17.0f * (g - b) + 0.5f); + term_color_t c = term_ostream_rgb_to_color (stream, r, g, b); + term_ostream_set_bgcolor (stream, c); + ostream_write_str (stream, " "); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + } + ostream_write_str (stream, "\n"); + } + /* Hue from 2 to 3. */ + for (row = 0; row <= 17; row++) + { + ostream_write_str (stream, row == 0 ? "green: " : " "); + for (col = 0; col <= 64; col++) + { + int g = 255; + int r = (int) (255.0f / 64.0f * col + 0.5f); + int b = r + (int) (row / 17.0f * (g - r) + 0.5f); + term_color_t c = term_ostream_rgb_to_color (stream, r, g, b); + term_ostream_set_bgcolor (stream, c); + ostream_write_str (stream, " "); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + } + ostream_write_str (stream, "\n"); + } + /* Hue from 3 to 4. */ + for (row = 17; row >= 0; row--) + { + ostream_write_str (stream, row == 17 ? "cyan: " : " "); + for (col = 0; col <= 64; col++) + { + int b = 255; + int r = (int) (255.0f / 64.0f * col + 0.5f); + int g = r + (int) (row / 17.0f * (b - r) + 0.5f); + term_color_t c = term_ostream_rgb_to_color (stream, r, g, b); + term_ostream_set_bgcolor (stream, c); + ostream_write_str (stream, " "); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + } + ostream_write_str (stream, "\n"); + } + /* Hue from 4 to 5. */ + for (row = 0; row <= 17; row++) + { + ostream_write_str (stream, row == 0 ? "blue: " : " "); + for (col = 0; col <= 64; col++) + { + int b = 255; + int g = (int) (255.0f / 64.0f * col + 0.5f); + int r = g + (int) (row / 17.0f * (b - g) + 0.5f); + term_color_t c = term_ostream_rgb_to_color (stream, r, g, b); + term_ostream_set_bgcolor (stream, c); + ostream_write_str (stream, " "); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + } + ostream_write_str (stream, "\n"); + } + /* Hue from 5 to 6. */ + for (row = 17; row >= 0; row--) + { + ostream_write_str (stream, row == 17 ? "magenta: " : + row == 0 ? "red: " : " "); + for (col = 0; col <= 64; col++) + { + int r = 255; + int g = (int) (255.0f / 64.0f * col + 0.5f); + int b = g + (int) (row / 17.0f * (r - g) + 0.5f); + term_color_t c = term_ostream_rgb_to_color (stream, r, g, b); + term_ostream_set_bgcolor (stream, c); + ostream_write_str (stream, " "); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + } + ostream_write_str (stream, "\n"); + } + ostream_write_str (stream, "\n"); + + ostream_write_str (stream, "Weights:\n"); + term_ostream_set_weight (stream, WEIGHT_NORMAL); + if (term_ostream_get_weight (stream) != WEIGHT_NORMAL) + abort (); + ostream_write_str (stream, "normal, "); + term_ostream_set_weight (stream, WEIGHT_BOLD); + if (term_ostream_get_weight (stream) != WEIGHT_BOLD) + abort (); + ostream_write_str (stream, "bold, "); + term_ostream_set_weight (stream, WEIGHT_DEFAULT); + if (term_ostream_get_weight (stream) != WEIGHT_DEFAULT) + abort (); + ostream_write_str (stream, "default \n"); + ostream_write_str (stream, "\n"); + + ostream_write_str (stream, "Postures:\n"); + term_ostream_set_posture (stream, POSTURE_NORMAL); + if (term_ostream_get_posture (stream) != POSTURE_NORMAL) + abort (); + ostream_write_str (stream, "normal, "); + term_ostream_set_posture (stream, POSTURE_ITALIC); + if (term_ostream_get_posture (stream) != POSTURE_ITALIC) + abort (); + ostream_write_str (stream, "italic, "); + term_ostream_set_posture (stream, POSTURE_DEFAULT); + if (term_ostream_get_posture (stream) != POSTURE_DEFAULT) + abort (); + ostream_write_str (stream, "default \n"); + ostream_write_str (stream, "\n"); + + ostream_write_str (stream, "Text decorations:\n"); + term_ostream_set_underline (stream, UNDERLINE_OFF); + if (term_ostream_get_underline (stream) != UNDERLINE_OFF) + abort (); + ostream_write_str (stream, "normal, "); + term_ostream_set_underline (stream, UNDERLINE_ON); + if (term_ostream_get_underline (stream) != UNDERLINE_ON) + abort (); + ostream_write_str (stream, "underlined, "); + term_ostream_set_underline (stream, UNDERLINE_DEFAULT); + if (term_ostream_get_underline (stream) != UNDERLINE_DEFAULT) + abort (); + ostream_write_str (stream, "default \n"); + ostream_write_str (stream, "\n"); + + ostream_write_str (stream, "Colors (foreground) mixed with attributes:\n"); + for (row = 0; row <= 8; row++) + { + const char *name = colors[row].name; + ostream_write_str (stream, name); + ostream_write_mem (stream, " ", 7 - strlen (name)); + term_ostream_set_color (stream, colors[row].c); + ostream_write_str (stream, "|normal|"); + term_ostream_set_weight (stream, WEIGHT_BOLD); + ostream_write_str (stream, "bold"); + term_ostream_set_weight (stream, WEIGHT_NORMAL); + ostream_write_str (stream, "|normal|"); + term_ostream_set_posture (stream, POSTURE_ITALIC); + ostream_write_str (stream, "italic"); + term_ostream_set_posture (stream, POSTURE_NORMAL); + ostream_write_str (stream, "|normal|"); + term_ostream_set_underline (stream, UNDERLINE_ON); + ostream_write_str (stream, "underlined"); + term_ostream_set_underline (stream, UNDERLINE_OFF); + ostream_write_str (stream, "|normal|"); + term_ostream_set_color (stream, COLOR_DEFAULT); + ostream_write_str (stream, "\n "); + term_ostream_set_color (stream, colors[row].c); + ostream_write_str (stream, "|normal|"); + term_ostream_set_weight (stream, WEIGHT_BOLD); + term_ostream_set_posture (stream, POSTURE_ITALIC); + ostream_write_str (stream, "bold+italic"); + term_ostream_set_weight (stream, WEIGHT_NORMAL); + term_ostream_set_posture (stream, POSTURE_NORMAL); + ostream_write_str (stream, "|normal|"); + term_ostream_set_weight (stream, WEIGHT_BOLD); + term_ostream_set_underline (stream, UNDERLINE_ON); + ostream_write_str (stream, "bold+underl"); + term_ostream_set_weight (stream, WEIGHT_NORMAL); + term_ostream_set_underline (stream, UNDERLINE_OFF); + ostream_write_str (stream, "|normal|"); + term_ostream_set_posture (stream, POSTURE_ITALIC); + term_ostream_set_underline (stream, UNDERLINE_ON); + ostream_write_str (stream, "italic+underl"); + term_ostream_set_posture (stream, POSTURE_NORMAL); + term_ostream_set_underline (stream, UNDERLINE_OFF); + ostream_write_str (stream, "|normal|"); + term_ostream_set_color (stream, COLOR_DEFAULT); + ostream_write_str (stream, "\n"); + } + ostream_write_str (stream, "\n"); + + ostream_write_str (stream, "Colors (background) mixed with attributes:\n"); + for (row = 0; row <= 8; row++) + { + const char *name = colors[row].name; + ostream_write_str (stream, name); + ostream_write_mem (stream, " ", 7 - strlen (name)); + term_ostream_set_bgcolor (stream, colors[row].c); + ostream_write_str (stream, "|normal|"); + term_ostream_set_weight (stream, WEIGHT_BOLD); + ostream_write_str (stream, "bold"); + term_ostream_set_weight (stream, WEIGHT_NORMAL); + ostream_write_str (stream, "|normal|"); + term_ostream_set_posture (stream, POSTURE_ITALIC); + ostream_write_str (stream, "italic"); + term_ostream_set_posture (stream, POSTURE_NORMAL); + ostream_write_str (stream, "|normal|"); + term_ostream_set_underline (stream, UNDERLINE_ON); + ostream_write_str (stream, "underlined"); + term_ostream_set_underline (stream, UNDERLINE_OFF); + ostream_write_str (stream, "|normal|"); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + ostream_write_str (stream, "\n "); + term_ostream_set_bgcolor (stream, colors[row].c); + ostream_write_str (stream, "|normal|"); + term_ostream_set_weight (stream, WEIGHT_BOLD); + term_ostream_set_posture (stream, POSTURE_ITALIC); + ostream_write_str (stream, "bold+italic"); + term_ostream_set_weight (stream, WEIGHT_NORMAL); + term_ostream_set_posture (stream, POSTURE_NORMAL); + ostream_write_str (stream, "|normal|"); + term_ostream_set_weight (stream, WEIGHT_BOLD); + term_ostream_set_underline (stream, UNDERLINE_ON); + ostream_write_str (stream, "bold+underl"); + term_ostream_set_weight (stream, WEIGHT_NORMAL); + term_ostream_set_underline (stream, UNDERLINE_OFF); + ostream_write_str (stream, "|normal|"); + term_ostream_set_posture (stream, POSTURE_ITALIC); + term_ostream_set_underline (stream, UNDERLINE_ON); + ostream_write_str (stream, "italic+underl"); + term_ostream_set_posture (stream, POSTURE_NORMAL); + term_ostream_set_underline (stream, UNDERLINE_OFF); + ostream_write_str (stream, "|normal|"); + term_ostream_set_bgcolor (stream, COLOR_DEFAULT); + ostream_write_str (stream, "\n"); + } + ostream_write_str (stream, "\n"); + + ostream_free (stream); +} + +/* Lookup the location of the style file. */ +static const char * +style_file_lookup (const char *file_name) +{ + if (!IS_PATH_WITH_DIR (file_name)) + { + /* It's a file name without a directory specification. + If it does not exist in the current directory... */ + struct stat statbuf; + + if (stat (file_name, &statbuf) < 0) + { + /* ... but it exists in the styles installation location... */ + const char *gettextstylesdir = relocate (GETTEXTDATADIR "/styles"); + char *possible_file_name = + xconcatenated_filename (gettextstylesdir, file_name, NULL); + + if (stat (possible_file_name, &statbuf) >= 0) + { + /* ... then use the file in the styles installation directory. */ + return possible_file_name; + } + free (possible_file_name); + } + + /* Let the CSS library show a warning. */ + } + return file_name; +} + +/* Assign a default value to style_file_name if necessary. */ +void +style_file_prepare () +{ + if (style_file_name == NULL) + { + const char *user_preference = getenv ("PO_STYLE"); + + if (user_preference != NULL && user_preference[0] != '\0') + style_file_name = style_file_lookup (xstrdup (user_preference)); + else + { + const char *gettextdatadir; + + /* Make it possible to override the po-default.css location. This is + necessary for running the testsuite before "make install". */ + gettextdatadir = getenv ("GETTEXTDATADIR"); + if (gettextdatadir == NULL || gettextdatadir[0] == '\0') + gettextdatadir = relocate (GETTEXTDATADIR); + + style_file_name = + xconcatenated_filename (gettextdatadir, "styles/po-default.css", + NULL); + } + } + else + style_file_name = style_file_lookup (style_file_name); +} diff --git a/gettext-tools/src/color.h b/gettext-tools/src/color.h new file mode 100644 index 0000000..f47f40f --- /dev/null +++ b/gettext-tools/src/color.h @@ -0,0 +1,57 @@ +/* Color and styling handling. + Copyright (C) 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _COLOR_H +#define _COLOR_H + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Whether to output a test page. */ +extern DLL_VARIABLE bool color_test_mode; + +/* Color option. */ +enum color_option { color_no, color_tty, color_yes, color_html }; +extern DLL_VARIABLE enum color_option color_mode; + +/* Style to use when coloring. */ +extern DLL_VARIABLE const char *style_file_name; + +/* --color argument handling. Return an error indicator. */ +extern bool handle_color_option (const char *option); + +/* --style argument handling. */ +extern void handle_style_option (const char *option); + +/* Print a color test page. */ +extern void print_color_test (void); + +/* Assign a default value to style_file_name if necessary. */ +extern void style_file_prepare (void); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _COLOR_H */ diff --git a/gettext-tools/src/dir-list.c b/gettext-tools/src/dir-list.c new file mode 100644 index 0000000..ee46d05 --- /dev/null +++ b/gettext-tools/src/dir-list.c @@ -0,0 +1,85 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1996, 1998, 2000-2002, 2006 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Specification. */ +#include "dir-list.h" + +#include +#include + +#include "str-list.h" + +static string_list_ty *directory /* = NULL */; + + +/* Append a directory to the end of the list of directories. */ +void +dir_list_append (const char *s) +{ + if (directory == NULL) + directory = string_list_alloc (); + string_list_append_unique (directory, s); +} + + +/* Return the nth directory, or NULL of n is out of range. */ +const char * +dir_list_nth (int n) +{ + /* The default value of the list consists of the single directory ".". */ + if (directory == NULL) + dir_list_append ("."); + + if (n < 0 || n >= directory->nitems) + return NULL; + return directory->item[n]; +} + + +/* Return the current list of directories, for later use with dir_list_restore. + Reset the list to empty. */ +void * +dir_list_save_reset () +{ + void *saved_value = directory; + + directory = NULL; + return saved_value; +} + + +/* Restore a previously saved list of directories. */ +void +dir_list_restore (void *saved_value) +{ + /* Don't free the contained strings, because they may have been returned + by dir_list_nth and may still be in use. */ + if (directory != NULL) + { + if (directory->item != NULL) + free (directory->item); + free (directory); + } + + directory = (string_list_ty *) saved_value; +} diff --git a/gettext-tools/src/dir-list.h b/gettext-tools/src/dir-list.h new file mode 100644 index 0000000..6ce32bb --- /dev/null +++ b/gettext-tools/src/dir-list.h @@ -0,0 +1,51 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1996, 1998, 2000-2003 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _DIR_LIST_H +#define _DIR_LIST_H + +/* Management of the list of directories where PO files are searched. + It is an ordered list, without duplicates. The default value of the + list consists of the single directory ".". */ + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Append a directory to the end of the list of directories. */ +extern void dir_list_append (const char *directory); + +/* Return the nth directory, or NULL of n is out of range. */ +extern const char *dir_list_nth (int n); + +/* Return the current list of directories, for later use with dir_list_restore. + Reset the list to empty. */ +extern void *dir_list_save_reset (void); + +/* Restore a previously saved list of directories. */ +extern void dir_list_restore (void *saved_value); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _DIR_LIST_H */ diff --git a/gettext-tools/src/file-list.c b/gettext-tools/src/file-list.c new file mode 100644 index 0000000..2ee983d --- /dev/null +++ b/gettext-tools/src/file-list.c @@ -0,0 +1,92 @@ +/* Reading file lists. + Copyright (C) 1995-1998, 2000-2002, 2007 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "file-list.h" + +#include +#include +#include +#include + +#include "str-list.h" +#include "error.h" +#include "gettext.h" + +/* A convenience macro. I don't like writing gettext() every time. */ +#define _(str) gettext (str) + + +/* Read list of filenames from a file. */ +string_list_ty * +read_names_from_file (const char *file_name) +{ + size_t line_len = 0; + char *line_buf = NULL; + FILE *fp; + string_list_ty *result; + + if (strcmp (file_name, "-") == 0) + fp = stdin; + else + { + fp = fopen (file_name, "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, + _("error while opening \"%s\" for reading"), file_name); + } + + result = string_list_alloc (); + + while (!feof (fp)) + { + /* Read next line from file. */ + int len = getline (&line_buf, &line_len, fp); + + /* In case of an error leave loop. */ + if (len < 0) + break; + + /* Remove trailing '\n' and trailing whitespace. */ + if (len > 0 && line_buf[len - 1] == '\n') + line_buf[--len] = '\0'; + while (len > 0 + && (line_buf[len - 1] == ' ' + || line_buf[len - 1] == '\t' + || line_buf[len - 1] == '\r')) + line_buf[--len] = '\0'; + + /* Test if we have to ignore the line. */ + if (*line_buf == '\0' || *line_buf == '#') + continue; + + string_list_append_unique (result, line_buf); + } + + /* Free buffer allocated through getline. */ + if (line_buf != NULL) + free (line_buf); + + /* Close input stream. */ + if (fp != stdin) + fclose (fp); + + return result; +} diff --git a/gettext-tools/src/file-list.h b/gettext-tools/src/file-list.h new file mode 100644 index 0000000..9665fbe --- /dev/null +++ b/gettext-tools/src/file-list.h @@ -0,0 +1,40 @@ +/* Reading file lists. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _FILE_LIST_H +#define _FILE_LIST_H + +#include "str-list.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Read list of filenames from a file. + One filename per line. Lines starting with # and whitespace lines are + ignored. Trailing whitespace is removed. */ +extern string_list_ty *read_names_from_file (const char *file_name); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _FILE_LIST_H */ diff --git a/gettext-tools/src/filter-quote.c b/gettext-tools/src/filter-quote.c new file mode 100644 index 0000000..2e9b7dc --- /dev/null +++ b/gettext-tools/src/filter-quote.c @@ -0,0 +1,224 @@ +/* Convert ASCII quotations to Unicode quotations. + Copyright (C) 2014 Free Software Foundation, Inc. + Written by Daiki Ueno , 2014. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "filters.h" + +#include +#include +#include +#include "xalloc.h" + +#define BOLD_START "\x1b[1m" +#define BOLD_END "\x1b[0m" + +/* This is a direct translation of po/quot.sed and po/boldquot.sed. */ +static void +convert_ascii_quote_to_unicode (const char *input, size_t input_len, + char **output_p, size_t *output_len_p, + bool bold) +{ + const char *start, *end, *p; + char *output, *r; + bool state; + size_t quote_count; + + start = input; + end = &input[input_len - 1]; + + /* True if we have seen a character which could be an opening + quotation mark. Note that we can't determine if it is really an + opening quotation mark until we see a closing quotation mark. */ + state = false; + + /* Count the number of quotation characters. */ + quote_count = 0; + for (p = start; p <= end; p++) + { + size_t len; + + p = strpbrk (p, "`'\""); + if (!p) + break; + + len = strspn (p, "`'\""); + quote_count += len; + p += len; + } + + /* Large enough. */ + r = output = XNMALLOC (input_len - quote_count + + (bold ? 7 : 3) * quote_count + 1, + char); + +#undef COPY_SEEN +#define COPY_SEEN \ + do \ + { \ + memcpy (r, start, p - start); \ + r += p - start; \ + start = p; \ + } \ + while (0) + + for (p = start; p <= end; p++) + { + switch (*p) + { + case '"': + if (state) + { + if (*start == '"') + { + if (p > start + 1) + { + /* U+201C: LEFT DOUBLE QUOTATION MARK */ + memcpy (r, "\xe2\x80\x9c", 3); + r += 3; + if (bold) + { + memcpy (r, BOLD_START, 4); + r += 4; + } + memcpy (r, start + 1, p - start - 1); + r += p - start - 1; + if (bold) + { + memcpy (r, BOLD_END, 4); + r += 4; + } + /* U+201D: RIGHT DOUBLE QUOTATION MARK */ + memcpy (r, "\xe2\x80\x9d", 3); + r += 3; + } + else + { + /* Consider "" as "". */ + memcpy (r, "\"\"", 2); + r += 2; + } + start = p + 1; + state = false; + } + } + else + { + COPY_SEEN; + state = true; + } + break; + + case '`': + if (state) + { + if (*start == '`') + COPY_SEEN; + } + else + { + COPY_SEEN; + state = true; + } + break; + + case '\'': + if (state) + { + if (/* `...' */ + *start == '`' + /* '...', where: + - The left quote is preceded by a space, and the + right quote is followed by a space. + - The left quote is preceded by a space, and the + right quote is at the end of line. + - The left quote is at the beginning of the line, and + the right quote is followed by a space. + */ + || (*start == '\'' + && (((start > input && *(start - 1) == ' ') + && (p == end || *(p + 1) == '\n' || *(p + 1) == ' ')) + || ((start == input || *(start - 1) == '\n') + && p < end && *(p + 1) == ' ')))) + { + /* U+2018: LEFT SINGLE QUOTATION MARK */ + memcpy (r, "\xe2\x80\x98", 3); + r += 3; + if (bold) + { + memcpy (r, BOLD_START, 4); + r += 4; + } + memcpy (r, start + 1, p - start - 1); + r += p - start - 1; + if (bold) + { + memcpy (r, BOLD_END, 4); + r += 4; + } + /* U+2019: RIGHT SINGLE QUOTATION MARK */ + memcpy (r, "\xe2\x80\x99", 3); + r += 3; + start = p + 1; + } + else + COPY_SEEN; + state = false; + } + else if (p == input || *(p - 1) == '\n' || *(p - 1) == ' ') + { + COPY_SEEN; + state = true; + } + break; + } + } + +#undef COPY_SEEN + + /* Copy the rest to R. */ + if (p > start) + { + memcpy (r, start, p - start); + r += p - start; + } + *r = '\0'; + + *output_p = output; + *output_len_p = r - output; +} + +void +ascii_quote_to_unicode (const char *input, size_t input_len, + char **output_p, size_t *output_len_p) +{ + convert_ascii_quote_to_unicode (input, input_len, + output_p, output_len_p, + false); +} + +void +ascii_quote_to_unicode_bold (const char *input, size_t input_len, + char **output_p, size_t *output_len_p) +{ + convert_ascii_quote_to_unicode (input, input_len, + output_p, output_len_p, + true); +} diff --git a/gettext-tools/src/filter-sr-latin.c b/gettext-tools/src/filter-sr-latin.c new file mode 100644 index 0000000..d6dbd95 --- /dev/null +++ b/gettext-tools/src/filter-sr-latin.c @@ -0,0 +1,400 @@ +/* Recode Serbian text from Cyrillic to Latin script. + Copyright (C) 2006-2007, 2009 Free Software Foundation, Inc. + Written by Danilo Šegan , 2006, + and Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "filters.h" + +#include + +#include "xalloc.h" + + +/* Table for Serbian Cyrillic to Latin transcription. + The table is indexed by the Unicode code point, in the range 0x0400..0x04ef. + The longest table entry is three bytes long. */ +static const char table[240][3 + 1] = +{ + /* U+0400 */ "\xC3\x88", /* "È" */ + /* U+0401 */ "", + /* U+0402 */ "\xC4\x90", /* "Đ" */ + /* U+0403 */ "", + /* U+0404 */ "", + /* U+0405 */ "", + /* U+0406 */ "", + /* U+0407 */ "", + /* U+0408 */ "J", + /* U+0409 */ "Lj", + /* U+040A */ "Nj", + /* U+040B */ "\xC4\x86", /* "Ć" */ + /* U+040C */ "", + /* U+040D */ "\xC3\x8C", /* "Ì" */ + /* U+040E */ "", + /* U+040F */ "D\xC5\xBE", /* "Dž" */ + /* U+0410 */ "A", + /* U+0411 */ "B", + /* U+0412 */ "V", + /* U+0413 */ "G", + /* U+0414 */ "D", + /* U+0415 */ "E", + /* U+0416 */ "\xC5\xBD", /* "Ž" */ + /* U+0417 */ "Z", + /* U+0418 */ "I", + /* U+0419 */ "", + /* U+041A */ "K", + /* U+041B */ "L", + /* U+041C */ "M", + /* U+041D */ "N", + /* U+041E */ "O", + /* U+041F */ "P", + /* U+0420 */ "R", + /* U+0421 */ "S", + /* U+0422 */ "T", + /* U+0423 */ "U", + /* U+0424 */ "F", + /* U+0425 */ "H", + /* U+0426 */ "C", + /* U+0427 */ "\xC4\x8C", /* "Č" */ + /* U+0428 */ "\xC5\xA0", /* "Š" */ + /* U+0429 */ "", + /* U+042A */ "", + /* U+042B */ "", + /* U+042C */ "", + /* U+042D */ "", + /* U+042E */ "", + /* U+042F */ "", + /* U+0430 */ "a", + /* U+0431 */ "b", + /* U+0432 */ "v", + /* U+0433 */ "g", + /* U+0434 */ "d", + /* U+0435 */ "e", + /* U+0436 */ "\xC5\xBE", /* "ž" */ + /* U+0437 */ "z", + /* U+0438 */ "i", + /* U+0439 */ "", + /* U+043A */ "k", + /* U+043B */ "l", + /* U+043C */ "m", + /* U+043D */ "n", + /* U+043E */ "o", + /* U+043F */ "p", + /* U+0440 */ "r", + /* U+0441 */ "s", + /* U+0442 */ "t", + /* U+0443 */ "u", + /* U+0444 */ "f", + /* U+0445 */ "h", + /* U+0446 */ "c", + /* U+0447 */ "\xC4\x8D", /* "č" */ + /* U+0448 */ "\xC5\xA1", /* "š" */ + /* U+0449 */ "", + /* U+044A */ "", + /* U+044B */ "", + /* U+044C */ "", + /* U+044D */ "", + /* U+044E */ "", + /* U+044F */ "", + /* U+0450 */ "\xC3\xA8", /* "è" */ + /* U+0451 */ "", + /* U+0452 */ "\xC4\x91", /* "đ" */ + /* U+0453 */ "", + /* U+0454 */ "", + /* U+0455 */ "", + /* U+0456 */ "", + /* U+0457 */ "", + /* U+0458 */ "j", + /* U+0459 */ "lj", + /* U+045A */ "nj", + /* U+045B */ "\xC4\x87", /* "ć" */ + /* U+045C */ "", + /* U+045D */ "\xC3\xAC", /* "ì" */ + /* U+045E */ "", + /* U+045F */ "d\xC5\xBE", /* "dž" */ + /* U+0460 */ "", + /* U+0461 */ "", + /* U+0462 */ "", + /* U+0463 */ "", + /* U+0464 */ "", + /* U+0465 */ "", + /* U+0466 */ "", + /* U+0467 */ "", + /* U+0468 */ "", + /* U+0469 */ "", + /* U+046A */ "", + /* U+046B */ "", + /* U+046C */ "", + /* U+046D */ "", + /* U+046E */ "", + /* U+046F */ "", + /* U+0470 */ "", + /* U+0471 */ "", + /* U+0472 */ "", + /* U+0473 */ "", + /* U+0474 */ "", + /* U+0475 */ "", + /* U+0476 */ "", + /* U+0477 */ "", + /* U+0478 */ "", + /* U+0479 */ "", + /* U+047A */ "", + /* U+047B */ "", + /* U+047C */ "", + /* U+047D */ "", + /* U+047E */ "", + /* U+047F */ "", + /* U+0480 */ "", + /* U+0481 */ "", + /* U+0482 */ "", + /* U+0483 */ "", + /* U+0484 */ "", + /* U+0485 */ "", + /* U+0486 */ "", + /* U+0487 */ "", + /* U+0488 */ "", + /* U+0489 */ "", + /* U+048A */ "", + /* U+048B */ "", + /* U+048C */ "", + /* U+048D */ "", + /* U+048E */ "", + /* U+048F */ "", + /* U+0490 */ "", + /* U+0491 */ "", + /* U+0492 */ "", + /* U+0493 */ "", + /* U+0494 */ "", + /* U+0495 */ "", + /* U+0496 */ "", + /* U+0497 */ "", + /* U+0498 */ "", + /* U+0499 */ "", + /* U+049A */ "", + /* U+049B */ "", + /* U+049C */ "", + /* U+049D */ "", + /* U+049E */ "", + /* U+049F */ "", + /* U+04A0 */ "", + /* U+04A1 */ "", + /* U+04A2 */ "", + /* U+04A3 */ "", + /* U+04A4 */ "", + /* U+04A5 */ "", + /* U+04A6 */ "", + /* U+04A7 */ "", + /* U+04A8 */ "", + /* U+04A9 */ "", + /* U+04AA */ "", + /* U+04AB */ "", + /* U+04AC */ "", + /* U+04AD */ "", + /* U+04AE */ "", + /* U+04AF */ "", + /* U+04B0 */ "", + /* U+04B1 */ "", + /* U+04B2 */ "", + /* U+04B3 */ "", + /* U+04B4 */ "", + /* U+04B5 */ "", + /* U+04B6 */ "", + /* U+04B7 */ "", + /* U+04B8 */ "", + /* U+04B9 */ "", + /* U+04BA */ "", + /* U+04BB */ "", + /* U+04BC */ "", + /* U+04BD */ "", + /* U+04BE */ "", + /* U+04BF */ "", + /* U+04C0 */ "", + /* U+04C1 */ "", + /* U+04C2 */ "", + /* U+04C3 */ "", + /* U+04C4 */ "", + /* U+04C5 */ "", + /* U+04C6 */ "", + /* U+04C7 */ "", + /* U+04C8 */ "", + /* U+04C9 */ "", + /* U+04CA */ "", + /* U+04CB */ "", + /* U+04CC */ "", + /* U+04CD */ "", + /* U+04CE */ "", + /* U+04CF */ "", + /* U+04D0 */ "", + /* U+04D1 */ "", + /* U+04D2 */ "", + /* U+04D3 */ "", + /* U+04D4 */ "", + /* U+04D5 */ "", + /* U+04D6 */ "", + /* U+04D7 */ "", + /* U+04D8 */ "", + /* U+04D9 */ "", + /* U+04DA */ "", + /* U+04DB */ "", + /* U+04DC */ "", + /* U+04DD */ "", + /* U+04DE */ "", + /* U+04DF */ "", + /* U+04E0 */ "", + /* U+04E1 */ "", + /* U+04E2 */ "\xC4\xAA", /* "Ī" */ + /* U+04E3 */ "\xC4\xAB", /* "ī" */ + /* U+04E4 */ "", + /* U+04E5 */ "", + /* U+04E6 */ "", + /* U+04E7 */ "", + /* U+04E8 */ "", + /* U+04E9 */ "", + /* U+04EA */ "", + /* U+04EB */ "", + /* U+04EC */ "", + /* U+04ED */ "", + /* U+04EE */ "\xC5\xAA", /* "Ū" */ + /* U+04EF */ "\xC5\xAB" /* "ū" */ +}; + +/* Quick test for an uppercase character in the range U+0041..U+005A. + The argument must be a byte in the range 0..UCHAR_MAX. */ +#define IS_UPPERCASE_LATIN(byte) \ + ((unsigned char) ((byte) - 'A') <= 'Z' - 'A') + +/* Quick test for an uppercase character in the range U+0400..U+042F, + or exactly U+04E2 or U+04EE. + The arguments must be bytes in the range 0..UCHAR_MAX. */ +#define IS_UPPERCASE_CYRILLIC(byte1,byte2) \ + (((byte1) == 0xd0 && (unsigned char) ((byte2) - 0x80) < 0x30) \ + || ((byte1) == 0xd3 && ((byte2) == 0xa2 || (byte2) == 0xae))) + +void +serbian_to_latin (const char *input, size_t input_len, + char **output_p, size_t *output_len_p) +{ + /* Loop through the input string, producing a replacement for each character. + Only characters in the range U+0400..U+04EF (\xD0\x80..\xD3\xAF) need to + be handled, and more precisely only those for which a replacement exists + in the table. Other characters are copied without modification. + The characters U+0409, U+040A, U+040F are transliterated to uppercase or + mixed-case replacements ("LJ" / "Lj", "NJ" / "Nj", "DŽ" / "Dž"), depending + on the case of the surrounding characters. + Since we assume UTF-8 encoding, the bytes \xD0..\xD3 can only occur at the + beginning of a character; the second and further bytes of a character are + all in the range \x80..\xBF. */ + + /* Since sequences of 2 bytes are mapped to sequences of at most 3 bytes, + the size of the output will be at most 1.5 * input_len. */ + size_t allocated = input_len + (input_len >> 1); + char *output = XNMALLOC (allocated, char); + + const char *input_end = input + input_len; + const char *ip; + char *op; + + for (ip = input, op = output; ip < input_end; ) + { + unsigned char byte = (unsigned char) *ip; + + /* Test for the first byte of a Cyrillic character. */ + if ((byte >= 0xd0 && byte <= 0xd3) && (ip + 1 < input_end)) + { + unsigned char second_byte = (unsigned char) ip[1]; + + /* Verify the second byte is valid. */ + if (second_byte >= 0x80 && second_byte < 0xc0) + { + unsigned int uc = ((byte & 0x1f) << 6) | (second_byte & 0x3f); + + if (uc >= 0x0400 && uc <= 0x04ef) + { + /* Look up replacement from the table. */ + const char *repl = table[uc - 0x0400]; + + if (repl[0] != '\0') + { + /* Found a replacement. + Now handle the special cases. */ + if (uc == 0x0409 || uc == 0x040a || uc == 0x040f) + if ((ip + 2 < input_end + && IS_UPPERCASE_LATIN ((unsigned char) ip[2])) + || (ip + 3 < input_end + && IS_UPPERCASE_CYRILLIC ((unsigned char) ip[2], + (unsigned char) ip[3])) + || (ip >= input + 1 + && IS_UPPERCASE_LATIN ((unsigned char) ip[-1])) + || (ip >= input + 2 + && IS_UPPERCASE_CYRILLIC ((unsigned char) ip[-2], + (unsigned char) ip[-1]))) + { + /* Use the upper-case replacement instead of + the mixed-case replacement. */ + switch (uc) + { + case 0x0409: + repl = "LJ"; break; + case 0x040a: + repl = "NJ"; break; + case 0x040f: + repl = "D\xC5\xBD"/* "DŽ" */; break; + default: + abort (); + } + } + + /* Use the replacement. */ + *op++ = *repl++; + if (*repl != '\0') + { + *op++ = *repl++; + if (*repl != '\0') + { + *op++ = *repl++; + /* All replacements have at most 3 bytes. */ + if (*repl != '\0') + abort (); + } + } + ip += 2; + continue; + } + } + } + } + *op++ = *ip++; + } + + { + size_t output_len = op - output; + + /* Verify that the allocated size was not exceeded. */ + if (output_len > allocated) + abort (); + /* Shrink the result. */ + if (output_len < allocated) + output = (char *) xrealloc (output, output_len); + + /* Done. */ + *output_p = output; + *output_len_p = output_len; + } +} diff --git a/gettext-tools/src/filters.h b/gettext-tools/src/filters.h new file mode 100644 index 0000000..7c6d90f --- /dev/null +++ b/gettext-tools/src/filters.h @@ -0,0 +1,51 @@ +/* Recoding functions. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Convert a string INPUT of INPUT_LEN bytes containing Serbian input + to Latin script (not Latin language :-)), converting Cyrillic letters to + Latin letters. + Store the freshly allocated result in *OUTPUT_P and its length (in bytes) + in *OUTPUT_LEN_P. + Input and output are in UTF-8 encoding. */ +extern void serbian_to_latin (const char *input, size_t input_len, + char **output_p, size_t *output_len_p); + +/* Convert a string INPUT of INPUT_LEN bytes, converting ASCII + quotations to Unicode quotations. + Store the freshly allocated result in *OUTPUT_P and its length (in bytes) + in *OUTPUT_LEN_P. + Input and output are in UTF-8 encoding. */ +extern void ascii_quote_to_unicode (const char *input, size_t input_len, + char **output_p, size_t *output_len_p); + +/* Convert a string INPUT of INPUT_LEN bytes, converting ASCII + quotations to Unicode quotations, adding bold escape sequence. + Store the freshly allocated result in *OUTPUT_P and its length (in bytes) + in *OUTPUT_LEN_P. + Input and output are in UTF-8 encoding. */ +extern void ascii_quote_to_unicode_bold (const char *input, size_t input_len, + char **output_p, size_t *output_len_p); + +#ifdef __cplusplus +} +#endif + diff --git a/gettext-tools/src/format-awk.c b/gettext-tools/src/format-awk.c new file mode 100644 index 0000000..91251c4 --- /dev/null +++ b/gettext-tools/src/format-awk.c @@ -0,0 +1,663 @@ +/* awk format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* awk format strings are described in the gawk-3.1 documentation and + implemented in gawk-3.1.0/builtin.c: format_tree(). + A directive + - starts with '%' or '%m$' where m is a positive integer, + - is optionally followed by any of the characters '#', '0', '-', ' ', '+', + each of which acts as a flag, + - is optionally followed by a width specification: '*' (reads an argument) + or '*m$' or a nonempty digit sequence, + - is optionally followed by '.' and a precision specification: '*' (reads + an argument) or '*m$' or a nonempty digit sequence, + - is finished by a specifier + - '%', that needs no argument, + - 'c', that need a character argument, + - 's', that need a string argument, + - 'i', 'd', that need a signed integer argument, + - 'o', 'u', 'x', 'X', that need an unsigned integer argument, + - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument. + Numbered ('%m$' or '*m$') and unnumbered argument specifications cannot + be used in the same string. + */ + +enum format_arg_type +{ + FAT_NONE, + FAT_CHARACTER, + FAT_STRING, + FAT_INTEGER, + FAT_UNSIGNED_INTEGER, + FAT_FLOAT +}; + +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + unsigned int unnumbered_arg_count; + struct spec *result; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + unnumbered_arg_count = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + unsigned int number = 0; + enum format_arg_type type; + + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + format = ++f; + } + } + + /* Parse flags. */ + while (*format == ' ' || *format == '+' || *format == '-' + || *format == '#' || *format == '0') + format++; + + /* Parse width. */ + if (*format == '*') + { + unsigned int width_number = 0; + + format++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = + INVALID_WIDTH_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + width_number = m; + format = ++f; + } + } + + if (width_number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = width_number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = FAT_INTEGER; + unnumbered_arg_count++; + } + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (*format == '*') + { + unsigned int precision_number = 0; + + format++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = + INVALID_PRECISION_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + precision_number = m; + format = ++f; + } + } + + if (precision_number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = precision_number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = FAT_INTEGER; + unnumbered_arg_count++; + } + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + } + + switch (*format) + { + case '%': + type = FAT_NONE; + break; + case 'c': + type = FAT_CHARACTER; + break; + case 's': + type = FAT_STRING; + break; + case 'i': case 'd': + type = FAT_INTEGER; + break; + case 'u': case 'o': case 'x': case 'X': + type = FAT_UNSIGNED_INTEGER; + break; + case 'e': case 'E': case 'f': case 'g': case 'G': + type = FAT_FLOAT; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (type != FAT_NONE) + { + if (number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = type; + spec.numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = type; + unnumbered_arg_count++; + } + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Convert the unnumbered argument array to numbered arguments. */ + if (unnumbered_arg_count > 0) + spec.numbered_arg_count = unnumbered_arg_count; + /* Sort the numbered argument array, and eliminate duplicates. */ + else if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + enum format_arg_type type1 = spec.numbered[i].type; + enum format_arg_type type2 = spec.numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_awk = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + switch (spec->numbered[i].type) + { + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_UNSIGNED_INTEGER: + printf ("[unsigned]i"); + break; + case FAT_FLOAT: + printf ("f"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-awk.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-boost.c b/gettext-tools/src/format-boost.c new file mode 100644 index 0000000..688ba97 --- /dev/null +++ b/gettext-tools/src/format-boost.c @@ -0,0 +1,772 @@ +/* Boost format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Boost format strings are described in + boost_1_33_1/libs/format/doc/format.html + and implemented in + boost_1_33_1/boost/format/parsing.hpp. + A directive (other than '%%') + - starts with '%' or '%|'; in the latter case it must end in '|', + - is continued either by + - 'm%' where m is a positive integer, starting with a nonzero digit; + in this case the directive must not have started with '%|'; or + - the following: + - optional: 'm$' where m is a positive integer, starting with a + nonzero digit, + - optional: any of the characters '#', '0', '-', ' ', '+', "'", + '_', '=', 'h', 'l', + - optional: a width specification: '*' (reads an argument) or '*m$' + or a nonempty digit sequence, + - optional: a '.' and a precision specification: '*' (reads an + argument) or '*m$' or a nonempty digit sequence, + - optional: any of the characters 'h', 'l', 'L', + - if the directive started with '%|': + an optional specifier and a final '|', + otherwise + a mandatory specifier. + If no specifier is given, it needs an argument of any type. + The possible specifiers are: + - 'c', 'C', that need a character argument, + - 's', 'S', that need an argument of any type, + - 'i', 'd', 'o', 'u', 'x', 'X', that need an integer argument, + - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument, + - 'p', that needs a 'void *' argument, + - 't', that doesn't need an argument, + - 'TX', where X is any character, that doesn't need an argument, + - 'n', that needs a pointer to integer. + The Boost format string interpreter doesn't actually care about + the argument types, but we do, because it increases the likelihood + of detecting translator mistakes. + Numbered ('%m%' or '%m$' or '*m$') and unnumbered argument specifications + cannot be used in the same string. + */ + +enum format_arg_type +{ + FAT_NONE = 0, + /* Basic types */ + FAT_INTEGER = 1, + FAT_DOUBLE = 2, + FAT_CHAR = 3, + FAT_POINTER = 4, + FAT_ANY = 5 +}; + +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + unsigned int unnumbered_arg_count; + struct spec *result; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + unnumbered_arg_count = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (*format == '%') + format++; + else + { + bool brackets = false; + bool done = false; + unsigned int number = 0; + enum format_arg_type type = FAT_NONE; + + if (*format == '|') + { + format++; + brackets = true; + } + + if (isdigit (*format) && *format != '0') + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if ((!brackets && *f == '%') || *f == '$') + { + if (m == 0) /* can happen if m overflows */ + { + *invalid_reason = INVALID_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + if (*f == '%') + { + type = FAT_ANY; + done = true; + } + format = ++f; + } + } + + if (!done) + { + /* Parse flags. */ + for (;;) + { + if (*format == ' ' || *format == '+' || *format == '-' + || *format == '#' || *format == '0' || *format == '\'' + || *format == '_' || *format == '=' || *format == 'h' + || *format == 'l') + format++; + else + break; + } + + /* Parse width. */ + if (*format == '*') + { + unsigned int width_number = 0; + + format++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = + INVALID_WIDTH_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + width_number = m; + format = ++f; + } + } + + if (width_number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are + exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = + INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = width_number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are + exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = + INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = FAT_INTEGER; + unnumbered_arg_count++; + } + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (*format == '*') + { + unsigned int precision_number = 0; + + format++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = + INVALID_PRECISION_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + precision_number = m; + format = ++f; + } + } + + if (precision_number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are + exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = + INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = precision_number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are + exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = + INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = FAT_INTEGER; + unnumbered_arg_count++; + } + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + } + + /* Parse size. */ + for (;;) + { + if (*format == 'h' || *format == 'l' || *format == 'L') + format++; + else + break; + } + + switch (*format++) + { + case 'c': case 'C': + type = FAT_CHAR; + break; + case 's': case 'S': + type = FAT_ANY; + break; + case 'i': case 'd': case 'o': case 'u': case 'x': case 'X': + type = FAT_INTEGER; + break; + case 'e': case 'E': case 'f': case 'g': case 'G': + type = FAT_DOUBLE; + break; + case 'p': + type = FAT_POINTER; + break; + case 't': + type = FAT_NONE; + break; + case 'T': + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + format++; + type = FAT_NONE; + break; + case 'n': + type = FAT_NONE; + break; + case '|': + if (brackets) + { + --format; + type = FAT_ANY; + break; + } + /*FALLTHROUGH*/ + default: + --format; + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, + *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + if (brackets) + { + if (*format != '|') + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + xasprintf (_("The directive number %u starts with | but does not end with |."), + spec.directives); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + format++; + } + } + + if (type != FAT_NONE) + { + if (number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = type; + spec.numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = type; + unnumbered_arg_count++; + } + } + } + + FDI_SET (format - 1, FMTDIR_END); + } + + /* Convert the unnumbered argument array to numbered arguments. */ + if (unnumbered_arg_count > 0) + spec.numbered_arg_count = unnumbered_arg_count; + /* Sort the numbered argument array, and eliminate duplicates. */ + else if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + enum format_arg_type type1 = spec.numbered[i].type; + enum format_arg_type type2 = spec.numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2 || type2 == FAT_ANY) + type_both = type1; + else if (type1 == FAT_ANY) + type_both = type2; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_boost = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + switch (spec->numbered[i].type) + { + case FAT_INTEGER: + printf ("i"); + break; + case FAT_DOUBLE: + printf ("f"); + break; + case FAT_CHAR: + printf ("c"); + break; + case FAT_POINTER: + printf ("p"); + break; + case FAT_ANY: + printf ("*"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-boost.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ + diff --git a/gettext-tools/src/format-c-parse.h b/gettext-tools/src/format-c-parse.h new file mode 100644 index 0000000..bacfcef --- /dev/null +++ b/gettext-tools/src/format-c-parse.h @@ -0,0 +1,854 @@ +/* Parsing C format strings. + Copyright (C) 2001-2004, 2006-2007, 2009-2010 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +/* C format strings are described in POSIX (IEEE P1003.1 2001), section + XSH 3 fprintf(). See also Linux fprintf(3) manual page. + A directive + - starts with '%' or '%m$' where m is a positive integer, + - is optionally followed by any of the characters '#', '0', '-', ' ', '+', + "'", or - only in msgstr strings - the string "I", each of which acts as + a flag, + - is optionally followed by a width specification: '*' (reads an argument) + or '*m$' or a nonempty digit sequence, + - is optionally followed by '.' and a precision specification: '*' (reads + an argument) or '*m$' or a nonempty digit sequence, + - is either continued like this: + - is optionally followed by a size specifier, one of 'hh' 'h' 'l' 'll' + 'L' 'q' 'j' 'z' 't', + - is finished by a specifier + - '%', that needs no argument, + - 'c', 'C', that need a character argument, + - 's', 'S', that need a string argument, + - 'i', 'd', that need a signed integer argument, + - 'o', 'u', 'x', 'X', that need an unsigned integer argument, + - 'e', 'E', 'f', 'F', 'g', 'G', 'a', 'A', that need a floating-point + argument, + - 'p', that needs a 'void *' argument, + - 'n', that needs a pointer to integer. + or is finished by a specifier '<' inttypes-macro '>' where inttypes-macro + is an ISO C 99 section 7.8.1 format directive. + Numbered ('%m$' or '*m$') and unnumbered argument specifications cannot + be used in the same string. When numbered argument specifications are + used, specifying the Nth argument requires that all the leading arguments, + from the first to the (N-1)th, are specified in the format string. + */ + +enum format_arg_type +{ + FAT_NONE = 0, + /* Basic types */ + FAT_INTEGER = 1, + FAT_DOUBLE = 2, + FAT_CHAR = 3, + FAT_STRING = 4, + FAT_OBJC_OBJECT = 5, + FAT_POINTER = 6, + FAT_COUNT_POINTER = 7, + /* Flags */ + FAT_UNSIGNED = 1 << 3, + FAT_SIZE_SHORT = 1 << 4, + FAT_SIZE_CHAR = 2 << 4, + FAT_SIZE_LONG = 1 << 6, + FAT_SIZE_LONGLONG = 2 << 6, + FAT_SIZE_8_T = 1 << 8, + FAT_SIZE_16_T = 1 << 9, + FAT_SIZE_32_T = 1 << 10, + FAT_SIZE_64_T = 1 << 11, + FAT_SIZE_LEAST8_T = 1 << 12, + FAT_SIZE_LEAST16_T = 1 << 13, + FAT_SIZE_LEAST32_T = 1 << 14, + FAT_SIZE_LEAST64_T = 1 << 15, + FAT_SIZE_FAST8_T = 1 << 16, + FAT_SIZE_FAST16_T = 1 << 17, + FAT_SIZE_FAST32_T = 1 << 18, + FAT_SIZE_FAST64_T = 1 << 19, + FAT_SIZE_INTMAX_T = 1 << 20, + FAT_SIZE_INTPTR_T = 1 << 21, + FAT_SIZE_SIZE_T = 1 << 22, + FAT_SIZE_PTRDIFF_T = 1 << 23, + FAT_WIDE = FAT_SIZE_LONG, + /* Meaningful combinations of basic types and flags: + 'signed char' = FAT_INTEGER | FAT_SIZE_CHAR, + 'unsigned char' = FAT_INTEGER | FAT_SIZE_CHAR | FAT_UNSIGNED, + 'short' = FAT_INTEGER | FAT_SIZE_SHORT, + 'unsigned short' = FAT_INTEGER | FAT_SIZE_SHORT | FAT_UNSIGNED, + 'int' = FAT_INTEGER, + 'unsigned int' = FAT_INTEGER | FAT_UNSIGNED, + 'long int' = FAT_INTEGER | FAT_SIZE_LONG, + 'unsigned long int' = FAT_INTEGER | FAT_SIZE_LONG | FAT_UNSIGNED, + 'long long int' = FAT_INTEGER | FAT_SIZE_LONGLONG, + 'unsigned long long int' = FAT_INTEGER | FAT_SIZE_LONGLONG | FAT_UNSIGNED, + 'double' = FAT_DOUBLE, + 'long double' = FAT_DOUBLE | FAT_SIZE_LONGLONG, + 'char'/'int' = FAT_CHAR, + 'wchar_t'/'wint_t' = FAT_CHAR | FAT_SIZE_LONG, + 'const char *' = FAT_STRING, + 'const wchar_t *' = FAT_STRING | FAT_SIZE_LONG, + 'void *' = FAT_POINTER, + FAT_COUNT_SCHAR_POINTER = FAT_COUNT_POINTER | FAT_SIZE_CHAR, + FAT_COUNT_SHORT_POINTER = FAT_COUNT_POINTER | FAT_SIZE_SHORT, + FAT_COUNT_INT_POINTER = FAT_COUNT_POINTER, + FAT_COUNT_LONGINT_POINTER = FAT_COUNT_POINTER | FAT_SIZE_LONG, + FAT_COUNT_LONGLONGINT_POINTER = FAT_COUNT_POINTER | FAT_SIZE_LONGLONG, + */ + /* Bitmasks */ + FAT_BASIC_MASK = (FAT_INTEGER | FAT_DOUBLE | FAT_CHAR | FAT_STRING + | FAT_OBJC_OBJECT | FAT_POINTER | FAT_COUNT_POINTER), + FAT_SIZE_MASK = (FAT_SIZE_SHORT | FAT_SIZE_CHAR + | FAT_SIZE_LONG | FAT_SIZE_LONGLONG + | FAT_SIZE_8_T | FAT_SIZE_16_T + | FAT_SIZE_32_T | FAT_SIZE_64_T + | FAT_SIZE_LEAST8_T | FAT_SIZE_LEAST16_T + | FAT_SIZE_LEAST32_T | FAT_SIZE_LEAST64_T + | FAT_SIZE_FAST8_T | FAT_SIZE_FAST16_T + | FAT_SIZE_FAST32_T | FAT_SIZE_FAST64_T + | FAT_SIZE_INTMAX_T | FAT_SIZE_INTPTR_T + | FAT_SIZE_SIZE_T | FAT_SIZE_PTRDIFF_T) +}; +#ifdef __cplusplus +typedef int format_arg_type_t; +#else +typedef enum format_arg_type format_arg_type_t; +#endif + +struct numbered_arg +{ + unsigned int number; + format_arg_type_t type; +}; + +struct unnumbered_arg +{ + format_arg_type_t type; +}; + +struct spec +{ + unsigned int directives; + unsigned int unnumbered_arg_count; + unsigned int allocated; + struct unnumbered_arg *unnumbered; + bool unlikely_intentional; + unsigned int sysdep_directives_count; + const char **sysdep_directives; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + +/* Whether to recognize the 'I' flag. */ +#if SYSDEP_SEGMENTS_PROCESSED +/* The 'I' flag can only occur in glibc >= 2.2. On other platforms, gettext() + filters it away even if it is present in the msgstr in the .mo file. */ +# define HANDLE_I_FLAG \ + ((__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2)) \ + && !defined __UCLIBC__) +#else +# define HANDLE_I_FLAG 1 +#endif + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static struct spec * +format_parse_entrails (const char *format, bool translated, + bool objc_extensions, char *fdi, char **invalid_reason, + struct spec *result) +{ + const char *const format_start = format; + struct spec spec; + unsigned int numbered_arg_count; + struct numbered_arg *numbered; + + spec.directives = 0; + numbered_arg_count = 0; + spec.unnumbered_arg_count = 0; + spec.allocated = 0; + numbered = NULL; + spec.unnumbered = NULL; + spec.unlikely_intentional = false; + spec.sysdep_directives_count = 0; + spec.sysdep_directives = NULL; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + unsigned int number = 0; + format_arg_type_t type; + format_arg_type_t size; + + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + format = ++f; + } + } + + /* Parse flags. */ + for (;;) + { + if (*format == ' ' || *format == '+' || *format == '-' + || *format == '#' || *format == '0' || *format == '\'') + format++; +#if HANDLE_I_FLAG + else if (translated && *format == 'I') + { + spec.sysdep_directives = + (const char **) + xrealloc (spec.sysdep_directives, + 2 * (spec.sysdep_directives_count + 1) + * sizeof (const char *)); + IF_OOM (spec.sysdep_directives, goto bad_format;) + spec.sysdep_directives[2 * spec.sysdep_directives_count] = format; + spec.sysdep_directives[2 * spec.sysdep_directives_count + 1] = format + 1; + spec.sysdep_directives_count++; + format++; + } +#endif + else + break; + } + + /* Parse width. */ + if (*format == '*') + { + unsigned int width_number = 0; + + format++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = + INVALID_WIDTH_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + width_number = m; + format = ++f; + } + } + + if (width_number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, spec.allocated * sizeof (struct numbered_arg)); + IF_OOM (numbered, goto bad_format;) + } + numbered[numbered_arg_count].number = width_number; + numbered[numbered_arg_count].type = FAT_INTEGER; + numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.unnumbered = (struct unnumbered_arg *) xrealloc (spec.unnumbered, spec.allocated * sizeof (struct unnumbered_arg)); + IF_OOM (spec.unnumbered, goto bad_format;) + } + spec.unnumbered[spec.unnumbered_arg_count].type = FAT_INTEGER; + spec.unnumbered_arg_count++; + } + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (*format == '*') + { + unsigned int precision_number = 0; + + format++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = + INVALID_PRECISION_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + precision_number = m; + format = ++f; + } + } + + if (precision_number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, spec.allocated * sizeof (struct numbered_arg)); + IF_OOM (numbered, goto bad_format;) + } + numbered[numbered_arg_count].number = precision_number; + numbered[numbered_arg_count].type = FAT_INTEGER; + numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.unnumbered = (struct unnumbered_arg *) xrealloc (spec.unnumbered, spec.allocated * sizeof (struct unnumbered_arg)); + IF_OOM (spec.unnumbered, goto bad_format;) + } + spec.unnumbered[spec.unnumbered_arg_count].type = FAT_INTEGER; + spec.unnumbered_arg_count++; + } + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + } + + if (!SYSDEP_SEGMENTS_PROCESSED && *format == '<') + { + spec.sysdep_directives = + (const char **) + xrealloc (spec.sysdep_directives, + 2 * (spec.sysdep_directives_count + 1) + * sizeof (const char *)); + IF_OOM (spec.sysdep_directives, goto bad_format;) + spec.sysdep_directives[2 * spec.sysdep_directives_count] = format; + + format++; + /* Parse ISO C 99 section 7.8.1 format string directive. + Syntax: + P R I { d | i | o | u | x | X } + { { | LEAST | FAST } { 8 | 16 | 32 | 64 } | MAX | PTR } */ + if (*format != 'P') + { + *invalid_reason = INVALID_C99_MACRO (spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR); + goto bad_format; + } + format++; + if (*format != 'R') + { + *invalid_reason = INVALID_C99_MACRO (spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR); + goto bad_format; + } + format++; + if (*format != 'I') + { + *invalid_reason = INVALID_C99_MACRO (spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR); + goto bad_format; + } + format++; + + switch (*format) + { + case 'i': case 'd': + type = FAT_INTEGER; + break; + case 'u': case 'o': case 'x': case 'X': + type = FAT_INTEGER | FAT_UNSIGNED; + break; + default: + *invalid_reason = INVALID_C99_MACRO (spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR); + goto bad_format; + } + format++; + + if (format[0] == 'M' && format[1] == 'A' && format[2] == 'X') + { + type |= FAT_SIZE_INTMAX_T; + format += 3; + } + else if (format[0] == 'P' && format[1] == 'T' && format[2] == 'R') + { + type |= FAT_SIZE_INTPTR_T; + format += 3; + } + else + { + if (format[0] == 'L' && format[1] == 'E' && format[2] == 'A' + && format[3] == 'S' && format[4] == 'T') + { + format += 5; + if (format[0] == '8') + { + type |= FAT_SIZE_LEAST8_T; + format++; + } + else if (format[0] == '1' && format[1] == '6') + { + type |= FAT_SIZE_LEAST16_T; + format += 2; + } + else if (format[0] == '3' && format[1] == '2') + { + type |= FAT_SIZE_LEAST32_T; + format += 2; + } + else if (format[0] == '6' && format[1] == '4') + { + type |= FAT_SIZE_LEAST64_T; + format += 2; + } + else + { + *invalid_reason = INVALID_C99_MACRO (spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, + FMTDIR_ERROR); + goto bad_format; + } + } + else if (format[0] == 'F' && format[1] == 'A' + && format[2] == 'S' && format[3] == 'T') + { + format += 4; + if (format[0] == '8') + { + type |= FAT_SIZE_FAST8_T; + format++; + } + else if (format[0] == '1' && format[1] == '6') + { + type |= FAT_SIZE_FAST16_T; + format += 2; + } + else if (format[0] == '3' && format[1] == '2') + { + type |= FAT_SIZE_FAST32_T; + format += 2; + } + else if (format[0] == '6' && format[1] == '4') + { + type |= FAT_SIZE_FAST64_T; + format += 2; + } + else + { + *invalid_reason = INVALID_C99_MACRO (spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, + FMTDIR_ERROR); + goto bad_format; + } + } + else + { + if (format[0] == '8') + { + type |= FAT_SIZE_8_T; + format++; + } + else if (format[0] == '1' && format[1] == '6') + { + type |= FAT_SIZE_16_T; + format += 2; + } + else if (format[0] == '3' && format[1] == '2') + { + type |= FAT_SIZE_32_T; + format += 2; + } + else if (format[0] == '6' && format[1] == '4') + { + type |= FAT_SIZE_64_T; + format += 2; + } + else + { + *invalid_reason = INVALID_C99_MACRO (spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, + FMTDIR_ERROR); + goto bad_format; + } + } + } + + if (*format != '>') + { + *invalid_reason = INVALID_ANGLE_BRACKET (spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR); + goto bad_format; + } + + spec.sysdep_directives[2 * spec.sysdep_directives_count + 1] = format + 1; + spec.sysdep_directives_count++; + } + else + { + /* Parse size. */ + size = 0; + for (;; format++) + { + if (*format == 'h') + { + if (size & (FAT_SIZE_SHORT | FAT_SIZE_CHAR)) + size = FAT_SIZE_CHAR; + else + size = FAT_SIZE_SHORT; + } + else if (*format == 'l') + { + if (size & (FAT_SIZE_LONG | FAT_SIZE_LONGLONG)) + size = FAT_SIZE_LONGLONG; + else + size = FAT_SIZE_LONG; + } + else if (*format == 'L') + size = FAT_SIZE_LONGLONG; + else if (*format == 'q') + /* Old BSD 4.4 convention. */ + size = FAT_SIZE_LONGLONG; + else if (*format == 'j') + size = FAT_SIZE_INTMAX_T; + else if (*format == 'z' || *format == 'Z') + /* 'z' is standardized in ISO C 99, but glibc uses 'Z' + because the warning facility in gcc-2.95.2 understands + only 'Z' (see gcc-2.95.2/gcc/c-common.c:1784). */ + size = FAT_SIZE_SIZE_T; + else if (*format == 't') + size = FAT_SIZE_PTRDIFF_T; +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + else if (SYSDEP_SEGMENTS_PROCESSED + && *format == 'I' + && format[1] == '6' + && format[2] == '4') + { + size = FAT_SIZE_64_T; + format += 2; + } +#endif + else + break; + } + + switch (*format) + { + case '%': + /* Programmers writing _("%2%") most often will not want to + use this string as a c-format string, but rather as a + literal or as a different kind of format string. */ + if (format[-1] != '%') + spec.unlikely_intentional = true; + type = FAT_NONE; + break; + case 'm': /* glibc extension */ + type = FAT_NONE; + break; + case 'c': + type = FAT_CHAR; + type |= (size & (FAT_SIZE_LONG | FAT_SIZE_LONGLONG) + ? FAT_WIDE : 0); + break; + case 'C': /* obsolete */ + type = FAT_CHAR | FAT_WIDE; + break; + case 's': + type = FAT_STRING; + type |= (size & (FAT_SIZE_LONG | FAT_SIZE_LONGLONG) + ? FAT_WIDE : 0); + break; + case 'S': /* obsolete */ + type = FAT_STRING | FAT_WIDE; + break; + case 'i': case 'd': + type = FAT_INTEGER; + type |= (size & FAT_SIZE_MASK); + break; + case 'u': case 'o': case 'x': case 'X': + type = FAT_INTEGER | FAT_UNSIGNED; + type |= (size & FAT_SIZE_MASK); + break; + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + case 'a': case 'A': + type = FAT_DOUBLE; + type |= (size & FAT_SIZE_LONGLONG); + break; + case '@': + if (objc_extensions) + { + type = FAT_OBJC_OBJECT; + break; + } + goto other; + case 'p': + type = FAT_POINTER; + break; + case 'n': + type = FAT_COUNT_POINTER; + type |= (size & FAT_SIZE_MASK); + break; + other: + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + } + + if (type != FAT_NONE) + { + if (number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, spec.allocated * sizeof (struct numbered_arg)); + IF_OOM (numbered, goto bad_format;) + } + numbered[numbered_arg_count].number = number; + numbered[numbered_arg_count].type = type; + numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.unnumbered = (struct unnumbered_arg *) xrealloc (spec.unnumbered, spec.allocated * sizeof (struct unnumbered_arg)); + IF_OOM (spec.unnumbered, goto bad_format;) + } + spec.unnumbered[spec.unnumbered_arg_count].type = type; + spec.unnumbered_arg_count++; + } + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (numbered, numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < numbered_arg_count; i++) + if (j > 0 && numbered[i].number == numbered[j-1].number) + { + format_arg_type_t type1 = numbered[i].type; + format_arg_type_t type2 = numbered[j-1].type; + format_arg_type_t type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number); + err = true; + } + + numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + numbered[j].number = numbered[i].number; + numbered[j].type = numbered[i].type; + } + j++; + } + numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + /* Verify that the format strings uses all arguments up to the highest + numbered one. */ + if (numbered_arg_count > 0) + { + unsigned int i; + + for (i = 0; i < numbered_arg_count; i++) + if (numbered[i].number != i + 1) + { + *invalid_reason = INVALID_IGNORED_ARGUMENT (numbered[i].number, i + 1); + goto bad_format; + } + + /* So now the numbered arguments array is equivalent to a sequence + of unnumbered arguments. */ + spec.unnumbered_arg_count = numbered_arg_count; + spec.allocated = spec.unnumbered_arg_count; + spec.unnumbered = XNMALLOC (spec.allocated, struct unnumbered_arg); + IF_OOM (spec.unnumbered, goto bad_format;) + for (i = 0; i < spec.unnumbered_arg_count; i++) + spec.unnumbered[i].type = numbered[i].type; + free (numbered); + numbered_arg_count = 0; + } + + *result = spec; + return result; + + bad_format: + if (numbered != NULL) + free (numbered); + if (spec.unnumbered != NULL) + free (spec.unnumbered); + if (spec.sysdep_directives != NULL) + free (spec.sysdep_directives); + return NULL; +} diff --git a/gettext-tools/src/format-c.c b/gettext-tools/src/format-c.c new file mode 100644 index 0000000..d88f8de --- /dev/null +++ b/gettext-tools/src/format-c.c @@ -0,0 +1,376 @@ +/* C format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#include "format-invalid.h" + +#define INVALID_C99_MACRO(directive_number) \ + xasprintf (_("In the directive number %u, the token after '<' is not the name of a format specifier macro. The valid macro names are listed in ISO C 99 section 7.8.1."), directive_number) + +#define INVALID_ANGLE_BRACKET(directive_number) \ + xasprintf (_("In the directive number %u, the token after '<' is not followed by '>'."), directive_number) + +#define INVALID_IGNORED_ARGUMENT(referenced_arg, ignored_arg) \ + xasprintf (_("The string refers to argument number %u but ignores argument number %u."), referenced_arg, ignored_arg) + +/* Execute statement if memory allocation function returned NULL. */ +#define IF_OOM(allocated_ptr, statement) /* nothing, since we use xalloc.h */ + +/* Specifies whether the system dependent segments in msgid and msgstr have + been processed. This means: + - If false, ISO C 99 directives are denoted with angle + brackets. If true, they have already been expanded, leading in + particular to %I64d directives on native Windows platforms. + - If false, the 'I' flag may be present in msgstr (also on platforms + other than glibc). If true, the 'I' directive may be present in msgstr + only on glibc >= 2.2 platforms. */ +#define SYSDEP_SEGMENTS_PROCESSED false + +/* Include the bulk of the C format string parsing code. */ +#include "format-c-parse.h" + +static void * +format_parse (const char *format, bool translated, bool objc_extensions, + char *fdi, char **invalid_reason) +{ + struct spec result_buf; + struct spec *result; + + result = format_parse_entrails (format, translated, objc_extensions, fdi, invalid_reason, &result_buf); + + if (result != NULL) + { + /* Copy the result to a heap-allocated object. */ + struct spec *safe_result = XMALLOC (struct spec); + *safe_result = *result; + result = safe_result; + } + return result; +} + +static void * +format_c_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + return format_parse (format, translated, false, fdi, invalid_reason); +} + +static void * +format_objc_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + return format_parse (format, translated, true, fdi, invalid_reason); +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->unnumbered != NULL) + free (spec->unnumbered); + if (spec->sysdep_directives != NULL) + free (spec->sysdep_directives); + free (spec); +} + +static bool +format_is_unlikely_intentional (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->unlikely_intentional; +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + unsigned int i; + + /* Check the argument types are the same. */ + if (equality + ? spec1->unnumbered_arg_count != spec2->unnumbered_arg_count + : spec1->unnumbered_arg_count < spec2->unnumbered_arg_count) + { + if (error_logger) + error_logger (_("number of format specifications in '%s' and '%s' does not match"), + pretty_msgid, pretty_msgstr); + err = true; + } + else + for (i = 0; i < spec2->unnumbered_arg_count; i++) + if (spec1->unnumbered[i].type != spec2->unnumbered[i].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, i + 1); + err = true; + } + + return err; +} + + +struct formatstring_parser formatstring_c = +{ + format_c_parse, + format_free, + format_get_number_of_directives, + format_is_unlikely_intentional, + format_check +}; + + +struct formatstring_parser formatstring_objc = +{ + format_objc_parse, + format_free, + format_get_number_of_directives, + format_is_unlikely_intentional, + format_check +}; + + +void +get_sysdep_c_format_directives (const char *string, bool translated, + struct interval **intervalsp, size_t *lengthp) +{ + /* Parse the format string with all possible extensions turned on. (The + caller has already verified that the format string is valid for the + particular language.) */ + char *invalid_reason = NULL; + struct spec *descr = + (struct spec *) + format_parse (string, translated, true, NULL, &invalid_reason); + + if (descr != NULL && descr->sysdep_directives_count > 0) + { + unsigned int n = descr->sysdep_directives_count; + struct interval *intervals = XNMALLOC (n, struct interval); + unsigned int i; + + for (i = 0; i < n; i++) + { + intervals[i].startpos = descr->sysdep_directives[2 * i] - string; + intervals[i].endpos = descr->sysdep_directives[2 * i + 1] - string; + } + *intervalsp = intervals; + *lengthp = n; + } + else + { + *intervalsp = NULL; + *lengthp = 0; + } + + if (descr != NULL) + format_free (descr); + else + free (invalid_reason); +} + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + for (i = 0; i < spec->unnumbered_arg_count; i++) + { + if (i > 0) + printf (" "); + if (spec->unnumbered[i].type & FAT_UNSIGNED) + printf ("[unsigned]"); + switch (spec->unnumbered[i].type & FAT_SIZE_MASK) + { + case 0: + break; + case FAT_SIZE_SHORT: + printf ("[short]"); + break; + case FAT_SIZE_CHAR: + printf ("[char]"); + break; + case FAT_SIZE_LONG: + printf ("[long]"); + break; + case FAT_SIZE_LONGLONG: + printf ("[long long]"); + break; + case FAT_SIZE_8_T: + printf ("[int8_t]"); + break; + case FAT_SIZE_16_T: + printf ("[int16_t]"); + break; + case FAT_SIZE_32_T: + printf ("[int32_t]"); + break; + case FAT_SIZE_64_T: + printf ("[int64_t]"); + break; + case FAT_SIZE_LEAST8_T: + printf ("[int_least8_t]"); + break; + case FAT_SIZE_LEAST16_T: + printf ("[int_least16_t]"); + break; + case FAT_SIZE_LEAST32_T: + printf ("[int_least32_t]"); + break; + case FAT_SIZE_LEAST64_T: + printf ("[int_least64_t]"); + break; + case FAT_SIZE_FAST8_T: + printf ("[int_fast8_t]"); + break; + case FAT_SIZE_FAST16_T: + printf ("[int_fast16_t]"); + break; + case FAT_SIZE_FAST32_T: + printf ("[int_fast32_t]"); + break; + case FAT_SIZE_FAST64_T: + printf ("[int_fast64_t]"); + break; + case FAT_SIZE_INTMAX_T: + printf ("[intmax_t]"); + break; + case FAT_SIZE_INTPTR_T: + printf ("[intptr_t]"); + break; + case FAT_SIZE_SIZE_T: + printf ("[size_t]"); + break; + case FAT_SIZE_PTRDIFF_T: + printf ("[ptrdiff_t]"); + break; + default: + abort (); + } + switch (spec->unnumbered[i].type & ~(FAT_UNSIGNED | FAT_SIZE_MASK)) + { + case FAT_INTEGER: + printf ("i"); + break; + case FAT_DOUBLE: + printf ("f"); + break; + case FAT_CHAR: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_OBJC_OBJECT: + printf ("@"); + break; + case FAT_POINTER: + printf ("p"); + break; + case FAT_COUNT_POINTER: + printf ("n"); + break; + default: + abort (); + } + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_c_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-c.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-csharp.c b/gettext-tools/src/format-csharp.c new file mode 100644 index 0000000..feb3fcf --- /dev/null +++ b/gettext-tools/src/format-csharp.c @@ -0,0 +1,293 @@ +/* C# format strings. + Copyright (C) 2003-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* C# format strings are described in the description of the .NET System.String + class and implemented in + pnetlib-0.5.6/runtime/System/String.cs + and + mcs-0.28/class/corlib/System/String.cs + A format string consists of literal text (that is output verbatim), doubled + braces ('{{' and '}}', that lead to a single brace when output), and + directives. + A directive + - starts with '{', + - is followed by a nonnegative integer m, + - is optionally followed by ',' and an integer denoting a width, + - is optionally followed by ':' and a sequence of format specifiers. + (But the interpretation of the format specifiers is up to the IFormattable + implementation, depending on the argument's runtime value. New classes + implementing IFormattable can be defined by the user.) + - is finished with '}'. + */ + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; +}; + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.numbered_arg_count = 0; + + for (; *format != '\0';) + { + char c = *format++; + + if (c == '{') + { + FDI_SET (format - 1, FMTDIR_START); + if (*format == '{') + format++; + else + { + /* A directive. */ + unsigned int number; + + spec.directives++; + + if (!c_isdigit (*format)) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '{' is not followed by an argument number."), spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR); + return NULL; + } + number = 0; + do + { + number = 10 * number + (*format - '0'); + format++; + } + while (c_isdigit (*format)); + + if (*format == ',') + { + /* Parse width. */ + format++; + if (*format == '-') + format++; + if (!c_isdigit (*format)) + { + *invalid_reason = + xasprintf (_("In the directive number %u, ',' is not followed by a number."), spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, + FMTDIR_ERROR); + return NULL; + } + do + format++; + while (c_isdigit (*format)); + } + + if (*format == ':') + { + /* Parse format specifiers. */ + do + format++; + while (*format != '\0' && *format != '}'); + } + + if (*format == '\0') + { + *invalid_reason = + xstrdup (_("The string ends in the middle of a directive: found '{' without matching '}'.")); + FDI_SET (format - 1, FMTDIR_ERROR); + return NULL; + } + + if (*format != '}') + { + *invalid_reason = + (c_isprint (*format) + ? xasprintf (_("The directive number %u ends with an invalid character '%c' instead of '}'."), spec.directives, *format) + : xasprintf (_("The directive number %u ends with an invalid character instead of '}'."), spec.directives)); + FDI_SET (format, FMTDIR_ERROR); + return NULL; + } + + format++; + + if (spec.numbered_arg_count <= number) + spec.numbered_arg_count = number + 1; + } + FDI_SET (format - 1, FMTDIR_END); + } + else if (c == '}') + { + FDI_SET (format - 1, FMTDIR_START); + if (*format == '}') + format++; + else + { + *invalid_reason = + (spec.directives == 0 + ? xstrdup (_("The string starts in the middle of a directive: found '}' without matching '{'.")) + : xasprintf (_("The string contains a lone '}' after directive number %u."), spec.directives)); + FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR); + return NULL; + } + FDI_SET (format - 1, FMTDIR_END); + } + } + + result = XMALLOC (struct spec); + *result = spec; + return result; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + /* Check that the argument counts are the same. */ + if (equality + ? spec1->numbered_arg_count != spec2->numbered_arg_count + : spec1->numbered_arg_count < spec2->numbered_arg_count) + { + if (error_logger) + error_logger (_("number of format specifications in '%s' and '%s' does not match"), + pretty_msgid, pretty_msgstr); + err = true; + } + + return err; +} + + +struct formatstring_parser formatstring_csharp = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + for (i = 0; i < spec->numbered_arg_count; i++) + { + if (i > 0) + printf (" "); + printf ("*"); + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-csharp.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-elisp.c b/gettext-tools/src/format-elisp.c new file mode 100644 index 0000000..2e43fde --- /dev/null +++ b/gettext-tools/src/format-elisp.c @@ -0,0 +1,502 @@ +/* Emacs Lisp format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Emacs Lisp format strings are implemented in emacs-21.1/src/editfns.c, + xemacs-21.1.14/src/editfns.c and xemacs-21.1.14/src/doprnt.c. + A directive + - starts with '%' or '%m$' where m is a positive integer, + - is optionally followed by any of the characters '#', '0', '-', ' ', '+', + each of which acts as a flag, + - is optionally followed by a width specification: '*' (reads an argument) + or a nonempty digit sequence, + - is optionally followed by '.' and a precision specification: '*' (reads + an argument) or a nonempty digit sequence, + - is finished by a specifier + - '%', that needs no argument, + - 'c', that need a character argument, + - 'd', 'i', 'x', 'X', 'o', that need an integer argument, + - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument, + - 's', that need an argument and prints it using princ, + - 'S', that need an argument and prints it using prin1. + Numbered ('%m$') and unnumbered argument specifications can be used in the + same string. The effect of '%m$' is to set the current argument number to + m. The current argument number is incremented after processing a directive. + */ + +enum format_arg_type +{ + FAT_NONE, + FAT_CHARACTER, + FAT_INTEGER, + FAT_FLOAT, + FAT_OBJECT_PRETTY, + FAT_OBJECT +}; + +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + unsigned int number; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + number = 1; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + enum format_arg_type type; + + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$' && m > 0) + { + number = m; + format = ++f; + } + } + + /* Parse flags. */ + while (*format == ' ' || *format == '+' || *format == '-' + || *format == '#' || *format == '0') + format++; + + /* Parse width. */ + if (*format == '*') + { + format++; + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + + number++; + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (*format == '*') + { + format++; + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + + number++; + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + } + + switch (*format) + { + case '%': + type = FAT_NONE; + break; + case 'c': + type = FAT_CHARACTER; + break; + case 'd': case 'i': case 'x': case 'X': case 'o': + type = FAT_INTEGER; + break; + case 'e': case 'E': case 'f': case 'g': case 'G': + type = FAT_FLOAT; + break; + case 's': + type = FAT_OBJECT_PRETTY; + break; + case 'S': + type = FAT_OBJECT; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (type != FAT_NONE) + { + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = type; + spec.numbered_arg_count++; + + number++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + enum format_arg_type type1 = spec.numbered[i].type; + enum format_arg_type type2 = spec.numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_elisp = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + switch (spec->numbered[i].type) + { + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_FLOAT: + printf ("f"); + break; + case FAT_OBJECT_PRETTY: + printf ("s"); + break; + case FAT_OBJECT: + printf ("*"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-elisp.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-gcc-internal.c b/gettext-tools/src/format-gcc-internal.c new file mode 100644 index 0000000..7057628 --- /dev/null +++ b/gettext-tools/src/format-gcc-internal.c @@ -0,0 +1,852 @@ +/* GCC internal format strings. + Copyright (C) 2003-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* GCC internal format strings consist of language frontend independent + format directives, implemented in gcc-4.3.0/gcc/pretty-print.c (function + pp_base_format), plus some frontend dependent extensions: + - for the C/ObjC frontend + in gcc-4.3.0/gcc/c-objc-common.c (function c_tree_printer) + - for the C++ frontend + in gcc-4.3.0/gcc/cp/error.c (function cp_printer) + Taking these together, GCC internal format strings are specified as follows. + + A directive + - starts with '%', + - either is finished by one of these: + - '%', '<', '>', "'", that need no argument, + - 'm', that needs no argument but looks at an err_no variable, + - or is continued like this: + - optionally 'm$' where m is a positive integer, + - optionally any number of flags: + 'q' (once only), + 'l' (up to twice) or 'w' (once only) (exclusive), + '+' (once only), + '#' (once only), + - finished by a specifier + + - 'c', that needs a character argument, + - 's', that needs a string argument, + - '.NNNs', where NNN is a nonempty digit sequence, that needs a + string argument, + - '.*NNN$s' where NNN is a positive integer and NNN = m - 1, that + needs a signed integer argument at position NNN and a string + argument, + - '.*s', that needs a signed integer argument and a string argument, + - 'i', 'd', that need a signed integer argument of the specified + size, + - 'o', 'u', 'x', that need an unsigned integer argument of the + specified size, + - 'p', that needs a 'void *' argument, + - 'H', that needs a 'location_t *' argument, + - 'J', that needs a general declaration argument, + - 'K', that needs a statement argument, + [see gcc/pretty-print.c] + + - 'D', that needs a general declaration argument, + - 'F', that needs a function declaration argument, + - 'T', that needs a type argument, + - 'E', that needs an expression argument, + [see gcc/c-objc-common.c and gcc/cp/error.c] + + - 'A', that needs a function argument list argument, + - 'C', that needs a tree code argument, + - 'L', that needs a language argument, + - 'O', that needs a binary operator argument, + - 'P', that needs a function parameter argument, + - 'Q', that needs an assignment operator argument, + - 'V', that needs a const/volatile qualifier argument. + [see gcc/cp/error.c] + + Numbered ('%m$' or '*m$') and unnumbered argument specifications cannot + be used in the same string. */ + +enum format_arg_type +{ + FAT_NONE = 0, + /* Basic types */ + FAT_INTEGER = 1, + FAT_CHAR = 2, + FAT_STRING = 3, + FAT_POINTER = 4, + FAT_LOCATION = 5, + FAT_TREE = 6, + FAT_TREE_CODE = 7, + FAT_LANGUAGES = 8, + /* Flags */ + FAT_UNSIGNED = 1 << 4, + FAT_SIZE_LONG = 1 << 5, + FAT_SIZE_LONGLONG = 2 << 5, + FAT_SIZE_WIDE = 3 << 5, + FAT_TREE_DECL = 1 << 7, + FAT_TREE_STATEMENT = 2 << 7, + FAT_TREE_FUNCDECL = 3 << 7, + FAT_TREE_TYPE = 4 << 7, + FAT_TREE_ARGUMENT = 5 << 7, + FAT_TREE_EXPRESSION = 6 << 7, + FAT_TREE_CV = 7 << 7, + FAT_TREE_CODE_BINOP = 1 << 10, + FAT_TREE_CODE_ASSOP = 2 << 10, + FAT_FUNCPARAM = 1 << 12, + /* Bitmasks */ + FAT_SIZE_MASK = (FAT_SIZE_LONG | FAT_SIZE_LONGLONG | FAT_SIZE_WIDE) +}; +#ifdef __cplusplus +typedef int format_arg_type_t; +#else +typedef enum format_arg_type format_arg_type_t; +#endif + +struct numbered_arg +{ + unsigned int number; + format_arg_type_t type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; + bool uses_err_no; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + unsigned int unnumbered_arg_count; + struct spec *result; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + spec.uses_err_no = false; + unnumbered_arg_count = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (*format == '%' || *format == '<' || *format == '>' + || *format == '\'') + ; + else if (*format == 'm') + spec.uses_err_no = true; + else + { + unsigned int number = 0; + unsigned int flag_q = 0; + unsigned int flag_l = 0; + unsigned int flag_w = 0; + unsigned int flag_plus = 0; + unsigned int flag_sharp = 0; + format_arg_type_t size; + format_arg_type_t type; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + format = ++f; + } + } + + /* Parse flags and size. */ + for (;; format++) + { + switch (*format) + { + case 'q': + if (flag_q > 0) + goto invalid_flags; + flag_q = 1; + continue; + case 'l': + if (flag_l > 1 || flag_w) + goto invalid_flags; + flag_l++; + continue; + case 'w': + if (flag_w > 0 || flag_l) + goto invalid_flags; + flag_w = 1; + continue; + case '+': + if (flag_plus > 0) + goto invalid_flags; + flag_plus = 1; + continue; + case '#': + if (flag_sharp > 0) + goto invalid_flags; + flag_sharp = 1; + continue; + invalid_flags: + *invalid_reason = xasprintf (_("In the directive number %u, the flags combination is invalid."), spec.directives); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + default: + break; + } + break; + } + size = (flag_l == 2 ? FAT_SIZE_LONGLONG : + flag_l == 1 ? FAT_SIZE_LONG : + flag_w ? FAT_SIZE_WIDE : + 0); + + if (*format == 'c') + type = FAT_CHAR; + else if (*format == 's') + type = FAT_STRING; + else if (*format == '.') + { + format++; + + if (isdigit (*format)) + { + do + format++; + while (isdigit (*format)); + + if (*format != 's') + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + xasprintf (_("In the directive number %u, a precision is not allowed before '%c'."), spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + type = FAT_STRING; + } + else if (*format == '*') + { + unsigned int precision_number = 0; + + format++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_WIDTH_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + if (unnumbered_arg_count > 0 || number == 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + if (m != number - 1) + { + *invalid_reason = xasprintf (_("In the directive number %u, the argument number for the precision must be equal to %u."), spec.directives, number - 1); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + precision_number = m; + format = ++f; + } + } + + if (precision_number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = precision_number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = FAT_INTEGER; + unnumbered_arg_count++; + } + + if (*format == 's') + type = FAT_STRING; + else + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + xasprintf (_("In the directive number %u, a precision specification is not allowed before '%c'."), spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + } + else + { + *invalid_reason = xasprintf (_("In the directive number %u, the precision specification is invalid."), spec.directives); + FDI_SET (*format == '\0' ? format - 1 : format, + FMTDIR_ERROR); + goto bad_format; + } + } + else if (*format == 'i' || *format == 'd') + type = FAT_INTEGER | size; + else if (*format == 'o' || *format == 'u' || *format == 'x') + type = FAT_INTEGER | FAT_UNSIGNED | size; + else if (*format == 'p') + type = FAT_POINTER; + else if (*format == 'H') + type = FAT_LOCATION; + else if (*format == 'J') + type = FAT_TREE | FAT_TREE_DECL; + else if (*format == 'K') + type = FAT_TREE | FAT_TREE_STATEMENT; + else + { + if (*format == 'D') + type = FAT_TREE | FAT_TREE_DECL; + else if (*format == 'F') + type = FAT_TREE | FAT_TREE_FUNCDECL; + else if (*format == 'T') + type = FAT_TREE | FAT_TREE_TYPE; + else if (*format == 'E') + type = FAT_TREE | FAT_TREE_EXPRESSION; + else if (*format == 'A') + type = FAT_TREE | FAT_TREE_ARGUMENT; + else if (*format == 'C') + type = FAT_TREE_CODE; + else if (*format == 'L') + type = FAT_LANGUAGES; + else if (*format == 'O') + type = FAT_TREE_CODE | FAT_TREE_CODE_BINOP; + else if (*format == 'P') + type = FAT_INTEGER | FAT_FUNCPARAM; + else if (*format == 'Q') + type = FAT_TREE_CODE | FAT_TREE_CODE_ASSOP; + else if (*format == 'V') + type = FAT_TREE | FAT_TREE_CV; + else + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + (*format == 'c' + || *format == 's' + || *format == 'i' || *format == 'd' + || *format == 'o' || *format == 'u' || *format == 'x' + || *format == 'H' + ? xasprintf (_("In the directive number %u, flags are not allowed before '%c'."), spec.directives, *format) + : INVALID_CONVERSION_SPECIFIER (spec.directives, + *format)); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + } + + if (number) + { + /* Numbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (unnumbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = type; + spec.numbered_arg_count++; + } + else + { + /* Unnumbered argument. */ + + /* Numbered and unnumbered specifications are exclusive. */ + if (spec.numbered_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == unnumbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1; + spec.numbered[unnumbered_arg_count].type = type; + unnumbered_arg_count++; + } + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Convert the unnumbered argument array to numbered arguments. */ + if (unnumbered_arg_count > 0) + spec.numbered_arg_count = unnumbered_arg_count; + /* Sort the numbered argument array, and eliminate duplicates. */ + else if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + format_arg_type_t type1 = spec.numbered[i].type; + format_arg_type_t type2 = spec.numbered[j-1].type; + format_arg_type_t type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + /* Check that the use of err_no is the same. */ + if (spec1->uses_err_no != spec2->uses_err_no) + { + if (error_logger) + { + if (spec1->uses_err_no) + error_logger (_("'%s' uses %%m but '%s' doesn't"), + pretty_msgid, pretty_msgstr); + else + error_logger (_("'%s' does not use %%m but '%s' uses %%m"), + pretty_msgid, pretty_msgstr); + } + err = true; + } + + return err; +} + + +struct formatstring_parser formatstring_gcc_internal = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + if (spec->numbered[i].type & FAT_UNSIGNED) + printf ("[unsigned]"); + switch (spec->numbered[i].type & FAT_SIZE_MASK) + { + case 0: + break; + case FAT_SIZE_LONG: + printf ("[long]"); + break; + case FAT_SIZE_LONGLONG: + printf ("[long long]"); + break; + case FAT_SIZE_WIDE: + printf ("[host-wide]"); + break; + default: + abort (); + } + switch (spec->numbered[i].type & ~(FAT_UNSIGNED | FAT_SIZE_MASK)) + { + case FAT_INTEGER: + printf ("i"); + break; + case FAT_INTEGER | FAT_FUNCPARAM: + printf ("P"); + break; + case FAT_CHAR: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_POINTER: + printf ("p"); + break; + case FAT_LOCATION: + printf ("H"); + break; + case FAT_TREE | FAT_TREE_DECL: + printf ("D"); + break; + case FAT_TREE | FAT_TREE_STATEMENT: + printf ("K"); + break; + case FAT_TREE | FAT_TREE_FUNCDECL: + printf ("F"); + break; + case FAT_TREE | FAT_TREE_TYPE: + printf ("T"); + break; + case FAT_TREE | FAT_TREE_ARGUMENT: + printf ("A"); + break; + case FAT_TREE | FAT_TREE_EXPRESSION: + printf ("E"); + break; + case FAT_TREE | FAT_TREE_CV: + printf ("V"); + break; + case FAT_TREE_CODE: + printf ("C"); + break; + case FAT_TREE_CODE | FAT_TREE_CODE_BINOP: + printf ("O"); + break; + case FAT_TREE_CODE | FAT_TREE_CODE_ASSOP: + printf ("Q"); + break; + case FAT_LANGUAGES: + printf ("L"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); + if (spec->uses_err_no) + printf (" ERR_NO"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-gcc-internal.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-gfc-internal.c b/gettext-tools/src/format-gfc-internal.c new file mode 100644 index 0000000..4fbbc66 --- /dev/null +++ b/gettext-tools/src/format-gfc-internal.c @@ -0,0 +1,504 @@ +/* GFC (GNU Fortran Compiler) internal format strings. + Copyright (C) 2003-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2009. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* GFC internal format strings consist of format directives that are specific + to the GNU Fortran Compiler frontend of GCC, implemented in + gcc-4.3.3/gcc/fortran/error.c (function error_print). + + A directive + - starts with '%', + - either is finished by '%', that needs no argument, + - or is continued like this: + - optionally 'm$' where m is a positive integer, + - finished by a specifier + - 'C', that needs no argument but uses a particular variable + (but for the purposes of 'm$' numbering it consumes an + argument nevertheless, of 'void' type), + - 'L', that needs a 'locus *' argument, + - 'i', 'd', that need a signed integer argument, + - 'u', that needs an unsigned integer argument, + - 'li', 'ld', that need a signed long integer argument, + - 'lu', that needs an unsigned long integer argument, + - 'c', that needs a character argument, + - 's', that needs a string argument. + + Numbered ('%m$') and unnumbered argument specifications can be used in the + same string. The effect of '%m$' is to set the current argument number to + m. The current argument number is incremented after processing a directive. + + When numbered argument specifications are used, specifying the Nth argument + requires that all the leading arguments, from the first to the (N-1)th, are + specified in the format string. */ + +enum format_arg_type +{ + FAT_NONE = 0, + /* Basic types */ + FAT_VOID = 1, + FAT_INTEGER = 2, + FAT_CHAR = 3, + FAT_STRING = 4, + FAT_LOCUS = 5, + /* Flags */ + FAT_UNSIGNED = 1 << 3, + FAT_SIZE_LONG = 1 << 4, + /* Bitmasks */ + FAT_SIZE_MASK = FAT_SIZE_LONG +}; +#ifdef __cplusplus +typedef int format_arg_type_t; +#else +typedef enum format_arg_type format_arg_type_t; +#endif + +struct numbered_arg +{ + unsigned int number; + format_arg_type_t type; +}; + +struct unnumbered_arg +{ + format_arg_type_t type; +}; + +struct spec +{ + unsigned int directives; + unsigned int unnumbered_arg_count; + struct unnumbered_arg *unnumbered; + bool uses_currentloc; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; + struct spec *result; + unsigned int number; + + spec.directives = 0; + numbered_arg_count = 0; + allocated = 0; + numbered = NULL; + spec.uses_currentloc = false; + number = 1; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (*format != '%') + { + format_arg_type_t type; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + format = ++f; + } + } + + if (*format == 'C') + { + type = FAT_VOID; + spec.uses_currentloc = true; + } + else if (*format == 'L') + type = FAT_LOCUS; + else if (*format == 'c') + type = FAT_CHAR; + else if (*format == 's') + type = FAT_STRING; + else + { + format_arg_type_t size = 0; + + if (*format == 'l') + { + ++format; + size = FAT_SIZE_LONG; + } + + if (*format == 'i' || *format == 'd') + type = FAT_INTEGER | size; + else if (*format == 'u') + type = FAT_INTEGER | FAT_UNSIGNED | size; + else + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + } + + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = number; + numbered[numbered_arg_count].type = type; + numbered_arg_count++; + + number++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (numbered, numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < numbered_arg_count; i++) + if (j > 0 && numbered[i].number == numbered[j-1].number) + { + format_arg_type_t type1 = numbered[i].type; + format_arg_type_t type2 = numbered[j-1].type; + format_arg_type_t type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number); + err = true; + } + + numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + numbered[j].number = numbered[i].number; + numbered[j].type = numbered[i].type; + } + j++; + } + numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + /* Verify that the format strings uses all arguments up to the highest + numbered one. */ + { + unsigned int i; + + for (i = 0; i < numbered_arg_count; i++) + if (numbered[i].number != i + 1) + { + *invalid_reason = + xasprintf (_("The string refers to argument number %u but ignores argument number %u."), numbered[i].number, i + 1); + goto bad_format; + } + } + + /* So now the numbered arguments array is equivalent to a sequence + of unnumbered arguments. Eliminate the FAT_VOID placeholders. */ + { + unsigned int i; + + spec.unnumbered_arg_count = 0; + for (i = 0; i < numbered_arg_count; i++) + if (numbered[i].type != FAT_VOID) + spec.unnumbered_arg_count++; + + if (spec.unnumbered_arg_count > 0) + { + unsigned int j; + + spec.unnumbered = XNMALLOC (spec.unnumbered_arg_count, struct unnumbered_arg); + j = 0; + for (i = 0; i < numbered_arg_count; i++) + if (numbered[i].type != FAT_VOID) + spec.unnumbered[j++].type = numbered[i].type; + } + else + spec.unnumbered = NULL; + } + free (numbered); + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (numbered != NULL) + free (numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->unnumbered != NULL) + free (spec->unnumbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + unsigned int i; + + /* Check the argument types are the same. */ + if (equality + ? spec1->unnumbered_arg_count != spec2->unnumbered_arg_count + : spec1->unnumbered_arg_count < spec2->unnumbered_arg_count) + { + if (error_logger) + error_logger (_("number of format specifications in '%s' and '%s' does not match"), + pretty_msgid, pretty_msgstr); + err = true; + } + else + for (i = 0; i < spec2->unnumbered_arg_count; i++) + if (spec1->unnumbered[i].type != spec2->unnumbered[i].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, i + 1); + err = true; + } + + /* Check that the use of currentloc is the same. */ + if (spec1->uses_currentloc != spec2->uses_currentloc) + { + if (error_logger) + { + if (spec1->uses_currentloc) + error_logger (_("'%s' uses %%C but '%s' doesn't"), + pretty_msgid, pretty_msgstr); + else + error_logger (_("'%s' does not use %%C but '%s' uses %%C"), + pretty_msgid, pretty_msgstr); + } + err = true; + } + + return err; +} + + +struct formatstring_parser formatstring_gfc_internal = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + for (i = 0; i < spec->unnumbered_arg_count; i++) + { + if (i > 0) + printf (" "); + if (spec->unnumbered[i].type & FAT_UNSIGNED) + printf ("[unsigned]"); + switch (spec->unnumbered[i].type & FAT_SIZE_MASK) + { + case 0: + break; + case FAT_SIZE_LONG: + printf ("[long]"); + break; + default: + abort (); + } + switch (spec->unnumbered[i].type & ~(FAT_UNSIGNED | FAT_SIZE_MASK)) + { + case FAT_INTEGER: + printf ("i"); + break; + case FAT_CHAR: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_LOCUS: + printf ("L"); + break; + default: + abort (); + } + } + printf (")"); + if (spec->uses_currentloc) + printf (" C"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-gfc-internal.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-invalid.h b/gettext-tools/src/format-invalid.h new file mode 100644 index 0000000..3ad60aa --- /dev/null +++ b/gettext-tools/src/format-invalid.h @@ -0,0 +1,40 @@ +/* Common reasons that make a format string invalid. + Copyright (C) 2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* These macros return freshly allocated error message strings, intended + to be stored in *invalid_reason. */ + +#define INVALID_UNTERMINATED_DIRECTIVE() \ + xstrdup (_("The string ends in the middle of a directive.")) + +#define INVALID_MIXES_NUMBERED_UNNUMBERED() \ + xstrdup (_("The string refers to arguments both through absolute argument numbers and through unnumbered argument specifications.")) + +#define INVALID_ARGNO_0(directive_number) \ + xasprintf (_("In the directive number %u, the argument number 0 is not a positive integer."), directive_number) +#define INVALID_WIDTH_ARGNO_0(directive_number) \ + xasprintf (_("In the directive number %u, the width's argument number 0 is not a positive integer."), directive_number) +#define INVALID_PRECISION_ARGNO_0(directive_number) \ + xasprintf (_("In the directive number %u, the precision's argument number 0 is not a positive integer."), directive_number) + +#define INVALID_CONVERSION_SPECIFIER(directive_number,conv_char) \ + (c_isprint (conv_char) \ + ? xasprintf (_("In the directive number %u, the character '%c' is not a valid conversion specifier."), directive_number, conv_char) \ + : xasprintf (_("The character that terminates the directive number %u is not a valid conversion specifier."), directive_number)) + +#define INVALID_INCOMPATIBLE_ARG_TYPES(arg_number) \ + xasprintf (_("The string refers to argument number %u in incompatible ways."), arg_number) diff --git a/gettext-tools/src/format-java.c b/gettext-tools/src/format-java.c new file mode 100644 index 0000000..5196835 --- /dev/null +++ b/gettext-tools/src/format-java.c @@ -0,0 +1,893 @@ +/* Java format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +#include +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Java format strings are described in java/text/MessageFormat.html. + See also the ICU documentation class_MessageFormat.html. + + messageFormatPattern := string ( "{" messageFormatElement "}" string )* + + messageFormatElement := argument { "," elementFormat } + + elementFormat := "time" { "," datetimeStyle } + | "date" { "," datetimeStyle } + | "number" { "," numberStyle } + | "choice" { "," choiceStyle } + + datetimeStyle := "short" + | "medium" + | "long" + | "full" + | dateFormatPattern + + numberStyle := "currency" + | "percent" + | "integer" + | numberFormatPattern + + choiceStyle := choiceFormatPattern + + dateFormatPattern see SimpleDateFormat.applyPattern + + numberFormatPattern see DecimalFormat.applyPattern + + choiceFormatPattern see ChoiceFormat constructor + + In strings, literal curly braces can be used if quoted between single + quotes. A real single quote is represented by ''. + + If a pattern is used, then unquoted braces in the pattern, if any, must + match: that is, "ab {0} de" and "ab '}' de" are ok, but "ab {0'}' de" and + "ab } de" are not. + + The argument is a number from 0 to 9, which corresponds to the arguments + presented in an array to be formatted. + + It is ok to have unused arguments in the array. + + Adding a dateFormatPattern / numberFormatPattern / choiceFormatPattern + to an elementFormat is equivalent to creating a SimpleDateFormat / + DecimalFormat / ChoiceFormat and use of setFormat. For example, + + MessageFormat form = + new MessageFormat("The disk \"{1}\" contains {0,choice,0#no files|1#one file|2#{0,number} files}."); + + is equivalent to + + MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}."); + form.setFormat(1, // Number of {} occurrence in the string! + new ChoiceFormat(new double[] { 0, 1, 2 }, + new String[] { "no files", "one file", + "{0,number} files" })); + + Note: The behaviour of quotes inside a choiceFormatPattern is not clear. + Example 1: + "abc{1,choice,0#{1,number,00';'000}}def" + JDK 1.1.x: exception + JDK 1.3.x: behaves like "abc{1,choice,0#{1,number,00;000}}def" + Example 2: + "abc{1,choice,0#{1,number,00';'}}def" + JDK 1.1.x: interprets the semicolon as number suffix + JDK 1.3.x: behaves like "abc{1,choice,0#{1,number,00;}}def" + */ + +enum format_arg_type +{ + FAT_NONE, + FAT_OBJECT, /* java.lang.Object */ + FAT_NUMBER, /* java.lang.Number */ + FAT_DATE /* java.util.Date */ +}; + +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + + +/* Forward declaration of local functions. */ +static bool date_format_parse (const char *format); +static bool number_format_parse (const char *format); +static bool choice_format_parse (const char *format, struct spec *spec, + char **invalid_reason); + + +/* Quote handling: + - When we see a single-quote, ignore it, but toggle the quoting flag. + - When we see a double single-quote, ignore the first of the two. + Assumes local variables format, quoting. */ +#define HANDLE_QUOTE \ + if (*format == '\'' && *++format != '\'') \ + quoting = !quoting; + +/* Note that message_format_parse and choice_format_parse are mutually + recursive. This is because MessageFormat can use some ChoiceFormats, + and a ChoiceFormat is made up from several MessageFormats. */ + +/* Return true if a format is a valid messageFormatPattern. + Extracts argument type information into spec. */ +static bool +message_format_parse (const char *format, char *fdi, struct spec *spec, + char **invalid_reason) +{ + const char *const format_start = format; + bool quoting = false; + + for (;;) + { + HANDLE_QUOTE; + if (!quoting && *format == '{') + { + unsigned int depth; + const char *element_start; + const char *element_end; + size_t n; + char *element_alloced; + char *element; + unsigned int number; + enum format_arg_type type; + + FDI_SET (format, FMTDIR_START); + spec->directives++; + + element_start = ++format; + depth = 0; + for (; *format != '\0'; format++) + { + if (*format == '{') + depth++; + else if (*format == '}') + { + if (depth == 0) + break; + else + depth--; + } + } + if (*format == '\0') + { + *invalid_reason = + xstrdup (_("The string ends in the middle of a directive: found '{' without matching '}'.")); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + element_end = format++; + + n = element_end - element_start; + element = element_alloced = (char *) xmalloca (n + 1); + memcpy (element, element_start, n); + element[n] = '\0'; + + if (!c_isdigit (*element)) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '{' is not followed by an argument number."), spec->directives); + FDI_SET (format - 1, FMTDIR_ERROR); + freea (element_alloced); + return false; + } + number = 0; + do + { + number = 10 * number + (*element - '0'); + element++; + } + while (c_isdigit (*element)); + + type = FAT_OBJECT; + if (*element == '\0') + ; + else if (strncmp (element, ",time", 5) == 0 + || strncmp (element, ",date", 5) == 0) + { + type = FAT_DATE; + element += 5; + if (*element == '\0') + ; + else if (*element == ',') + { + element++; + if (strcmp (element, "short") == 0 + || strcmp (element, "medium") == 0 + || strcmp (element, "long") == 0 + || strcmp (element, "full") == 0 + || date_format_parse (element)) + ; + else + { + *invalid_reason = + xasprintf (_("In the directive number %u, the substring \"%s\" is not a valid date/time style."), spec->directives, element); + FDI_SET (format - 1, FMTDIR_ERROR); + freea (element_alloced); + return false; + } + } + else + { + *element = '\0'; + element -= 4; + *invalid_reason = + xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element); + FDI_SET (format - 1, FMTDIR_ERROR); + freea (element_alloced); + return false; + } + } + else if (strncmp (element, ",number", 7) == 0) + { + type = FAT_NUMBER; + element += 7; + if (*element == '\0') + ; + else if (*element == ',') + { + element++; + if (strcmp (element, "currency") == 0 + || strcmp (element, "percent") == 0 + || strcmp (element, "integer") == 0 + || number_format_parse (element)) + ; + else + { + *invalid_reason = + xasprintf (_("In the directive number %u, the substring \"%s\" is not a valid number style."), spec->directives, element); + FDI_SET (format - 1, FMTDIR_ERROR); + freea (element_alloced); + return false; + } + } + else + { + *element = '\0'; + element -= 6; + *invalid_reason = + xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element); + FDI_SET (format - 1, FMTDIR_ERROR); + freea (element_alloced); + return false; + } + } + else if (strncmp (element, ",choice", 7) == 0) + { + type = FAT_NUMBER; /* because ChoiceFormat extends NumberFormat */ + element += 7; + if (*element == '\0') + ; + else if (*element == ',') + { + element++; + if (choice_format_parse (element, spec, invalid_reason)) + ; + else + { + FDI_SET (format - 1, FMTDIR_ERROR); + freea (element_alloced); + return false; + } + } + else + { + *element = '\0'; + element -= 6; + *invalid_reason = + xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element); + FDI_SET (format - 1, FMTDIR_ERROR); + freea (element_alloced); + return false; + } + } + else + { + *invalid_reason = + 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"); + FDI_SET (format - 1, FMTDIR_ERROR); + freea (element_alloced); + return false; + } + freea (element_alloced); + + if (spec->allocated == spec->numbered_arg_count) + { + spec->allocated = 2 * spec->allocated + 1; + spec->numbered = (struct numbered_arg *) xrealloc (spec->numbered, spec->allocated * sizeof (struct numbered_arg)); + } + spec->numbered[spec->numbered_arg_count].number = number; + spec->numbered[spec->numbered_arg_count].type = type; + spec->numbered_arg_count++; + + FDI_SET (format - 1, FMTDIR_END); + } + /* The doc says "ab}de" is invalid. Even though JDK accepts it. */ + else if (!quoting && *format == '}') + { + FDI_SET (format, FMTDIR_START); + *invalid_reason = + xstrdup (_("The string starts in the middle of a directive: found '}' without matching '{'.")); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + else if (*format != '\0') + format++; + else + break; + } + + return true; +} + +/* Return true if a format is a valid dateFormatPattern. */ +static bool +date_format_parse (const char *format) +{ + /* Any string is valid. Single-quote starts a quoted section, to be + terminated at the next single-quote or string end. Double single-quote + gives a single single-quote. Non-quoted ASCII letters are first grouped + into blocks of equal letters. Then each block (e.g. 'yyyy') is + interpreted according to some rules. */ + return true; +} + +/* Return true if a format is a valid numberFormatPattern. */ +static bool +number_format_parse (const char *format) +{ + /* Pattern Syntax: + pattern := pos_pattern{';' neg_pattern} + pos_pattern := {prefix}number{suffix} + neg_pattern := {prefix}number{suffix} + number := integer{'.' fraction}{exponent} + prefix := '\u0000'..'\uFFFD' - special_characters + suffix := '\u0000'..'\uFFFD' - special_characters + integer := min_int | '#' | '#' integer | '#' ',' integer + min_int := '0' | '0' min_int | '0' ',' min_int + fraction := '0'* '#'* + exponent := 'E' '0' '0'* + Notation: + X* 0 or more instances of X + { X } 0 or 1 instances of X + X | Y either X or Y + X..Y any character from X up to Y, inclusive + S - T characters in S, except those in T + Single-quote starts a quoted section, to be terminated at the next + single-quote or string end. Double single-quote gives a single + single-quote. + */ + bool quoting = false; + bool seen_semicolon = false; + + HANDLE_QUOTE; + for (;;) + { + /* Parse prefix. */ + while (*format != '\0' + && !(!quoting && (*format == '0' || *format == '#'))) + { + if (format[0] == '\\') + { + if (format[1] == 'u' + && c_isxdigit (format[2]) + && c_isxdigit (format[3]) + && c_isxdigit (format[4]) + && c_isxdigit (format[5])) + format += 6; + else + format += 2; + } + else + format += 1; + HANDLE_QUOTE; + } + + /* Parse integer. */ + if (!(!quoting && (*format == '0' || *format == '#'))) + return false; + while (!quoting && *format == '#') + { + format++; + HANDLE_QUOTE; + if (!quoting && *format == ',') + { + format++; + HANDLE_QUOTE; + } + } + while (!quoting && *format == '0') + { + format++; + HANDLE_QUOTE; + if (!quoting && *format == ',') + { + format++; + HANDLE_QUOTE; + } + } + + /* Parse fraction. */ + if (!quoting && *format == '.') + { + format++; + HANDLE_QUOTE; + while (!quoting && *format == '0') + { + format++; + HANDLE_QUOTE; + } + while (!quoting && *format == '#') + { + format++; + HANDLE_QUOTE; + } + } + + /* Parse exponent. */ + if (!quoting && *format == 'E') + { + const char *format_save = format; + format++; + HANDLE_QUOTE; + if (!quoting && *format == '0') + { + do + { + format++; + HANDLE_QUOTE; + } + while (!quoting && *format == '0'); + } + else + { + /* Back up. */ + format = format_save; + quoting = false; + } + } + + /* Parse suffix. */ + while (*format != '\0' + && (seen_semicolon || !(!quoting && *format == ';'))) + { + if (format[0] == '\\') + { + if (format[1] == 'u' + && c_isxdigit (format[2]) + && c_isxdigit (format[3]) + && c_isxdigit (format[4]) + && c_isxdigit (format[5])) + format += 6; + else + format += 2; + } + else + format += 1; + HANDLE_QUOTE; + } + + if (seen_semicolon || !(!quoting && *format == ';')) + break; + } + + return (*format == '\0'); +} + +/* Return true if a format is a valid choiceFormatPattern. + Extracts argument type information into spec. */ +static bool +choice_format_parse (const char *format, struct spec *spec, + char **invalid_reason) +{ + /* Pattern syntax: + pattern := | choice | choice '|' pattern + choice := number separator messageformat + separator := '<' | '#' | '\u2264' + Single-quote starts a quoted section, to be terminated at the next + single-quote or string end. Double single-quote gives a single + single-quote. + */ + bool quoting = false; + + HANDLE_QUOTE; + if (*format == '\0') + return true; + for (;;) + { + /* Don't bother looking too precisely into the syntax of the number. + It can contain various Unicode characters. */ + bool number_nonempty; + char *msgformat; + char *mp; + bool msgformat_valid; + + /* Parse number. */ + number_nonempty = false; + while (*format != '\0' + && !(!quoting && (*format == '<' || *format == '#' + || strncmp (format, "\\u2264", 6) == 0 + || *format == '|'))) + { + if (format[0] == '\\') + { + if (format[1] == 'u' + && c_isxdigit (format[2]) + && c_isxdigit (format[3]) + && c_isxdigit (format[4]) + && c_isxdigit (format[5])) + format += 6; + else + format += 2; + } + else + format += 1; + number_nonempty = true; + HANDLE_QUOTE; + } + + /* Short clause at end of pattern is valid and is ignored! */ + if (*format == '\0') + break; + + if (!number_nonempty) + { + *invalid_reason = + xasprintf (_("In the directive number %u, a choice contains no number."), spec->directives); + return false; + } + + if (*format == '<' || *format == '#') + format += 1; + else if (strncmp (format, "\\u2264", 6) == 0) + format += 6; + else + { + *invalid_reason = + xasprintf (_("In the directive number %u, a choice contains a number that is not followed by '<', '#' or '%s'."), spec->directives, "\\u2264"); + return false; + } + HANDLE_QUOTE; + + msgformat = (char *) xmalloca (strlen (format) + 1); + mp = msgformat; + + while (*format != '\0' && !(!quoting && *format == '|')) + { + *mp++ = *format++; + HANDLE_QUOTE; + } + *mp = '\0'; + + msgformat_valid = + message_format_parse (msgformat, NULL, spec, invalid_reason); + + freea (msgformat); + + if (!msgformat_valid) + return false; + + if (*format == '\0') + break; + + format++; + HANDLE_QUOTE; + } + + return true; +} + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + + if (!message_format_parse (format, fdi, &spec, invalid_reason)) + goto bad_format; + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + enum format_arg_type type1 = spec.numbered[i].type; + enum format_arg_type type2 = spec.numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2 || type2 == FAT_OBJECT) + type_both = type1; + else if (type1 == FAT_OBJECT) + type_both = type2; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument {%u}, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument {%u} doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument {%u} are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_java = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 0; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + switch (spec->numbered[i].type) + { + case FAT_OBJECT: + printf ("*"); + break; + case FAT_NUMBER: + printf ("Number"); + break; + case FAT_DATE: + printf ("Date"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-javascript.c b/gettext-tools/src/format-javascript.c new file mode 100644 index 0000000..d76a8d7 --- /dev/null +++ b/gettext-tools/src/format-javascript.c @@ -0,0 +1,333 @@ +/* JavaScript format strings. + Copyright (C) 2001-2004, 2006-2009, 2013 Free Software Foundation, Inc. + Written by Andreas Stricker , 2010. + It's based on python format module from Bruno Haible. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Although JavaScript specification itself does not define any format + strings, many implementations provide printf-like functions. + We provide a permissive parser which accepts commonly used format + strings, where: + + A directive + - starts with '%', + - is optionally followed by any of the characters '0', '-', ' ', + or, each of which acts as a flag, + - is optionally followed by a width specification: a nonempty digit + sequence, + - is optionally followed by '.' and a precision specification: a nonempty + digit sequence, + - is finished by a specifier + - 's', that needs a string argument, + - 'b', 'd', 'u', 'o', 'x', 'X', that need an integer argument, + - 'f', that need a floating-point argument, + - 'c', that needs a character argument. + - 'j', that needs an argument of any type. + Additionally there is the directive '%%', which takes no argument. */ + +enum format_arg_type +{ + FAT_NONE, + FAT_ANY, + FAT_CHARACTER, + FAT_STRING, + FAT_INTEGER, + FAT_FLOAT +}; + +struct spec +{ + unsigned int directives; + unsigned int format_args_count; + unsigned int allocated; + enum format_arg_type *format_args; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.format_args_count = 0; + spec.allocated = 0; + spec.format_args = NULL; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + enum format_arg_type type; + + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + while (*format == '-' || *format == '+' || *format == ' ' + || *format == '0' || *format == 'I') + format++; + + while (isdigit (*format)) + format++; + + if (*format == '.') + { + format++; + + while (isdigit (*format)) + format++; + } + + switch (*format) + { + case '%': + type = FAT_NONE; + break; + case 'c': + type = FAT_CHARACTER; + break; + case 's': + type = FAT_STRING; + break; + case 'b': case 'd': case 'o': case 'x': case 'X': + type = FAT_INTEGER; + break; + case 'f': + type = FAT_FLOAT; + break; + case 'j': + type = FAT_ANY; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (*format != '%') + { + if (spec.allocated == spec.format_args_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.format_args = (enum format_arg_type *) xrealloc (spec.format_args, spec.allocated * sizeof (enum format_arg_type)); + } + spec.format_args[spec.format_args_count] = type; + spec.format_args_count++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.format_args != NULL) + free (spec.format_args); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->format_args != NULL) + free (spec->format_args); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->format_args_count + spec2->format_args_count > 0) + { + unsigned int i; + + /* Check the argument types are the same. */ + if (spec1->format_args_count != spec2->format_args_count) + { + if (error_logger) + error_logger (_("number of format specifications in '%s' and '%s' does not match"), + pretty_msgid, pretty_msgstr); + err = true; + } + else + for (i = 0; i < spec2->format_args_count; i++) + if (!(spec1->format_args[i] == spec2->format_args[i] + || (!equality + && (spec1->format_args[i] == FAT_ANY + || spec2->format_args[i] == FAT_ANY)))) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, i + 1); + err = true; + } + } + + return err; +} + + +struct formatstring_parser formatstring_javascript = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + for (i = 0; i < spec->format_args_count; i++) + { + if (i > 0) + printf (" "); + switch (spec->format_args[i]) + { + case FAT_ANY: + printf ("*"); + break; + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_FLOAT: + printf ("f"); + break; + default: + abort (); + } + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-javascript.c ../gnulib-lib/libgettextlib.la" + * End: + */ +#endif /* TEST */ diff --git a/gettext-tools/src/format-kde.c b/gettext-tools/src/format-kde.c new file mode 100644 index 0000000..90e887d --- /dev/null +++ b/gettext-tools/src/format-kde.c @@ -0,0 +1,349 @@ +/* KDE format strings. + Copyright (C) 2003-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2007. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* KDE 4 format strings are processed by method + KLocalizedStringPrivate::substituteSimple(string,'%',false) in + kde4libs-3.93.0.orig/kdecore/localization/klocalizedstring.cpp . + A directive + - starts with '%', + - is followed by a non-zero digit and optionally more digits. All + the following digits are eaten up. + An unterminated directive ('%' not followed by a digit or at the end) is + not an error. + %1 denotes the first argument, %2 the second argument, etc. + The set of used argument numbers must be of the form {1,...,n} or + {1,...,n} \ {m}: one of the supplied arguments may be ignored by the + format string. This allows the processing of singular forms (msgstr[0]). + Which argument may be skipped, depends on the argument types at runtime; + since xgettext cannot extract this info, it is considered unknown here. */ + +struct numbered_arg +{ + unsigned int number; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + /* Subtract 1, because argument number 0 can only occur through overflow. */ + unsigned int n1 = ((const struct numbered_arg *) p1)->number - 1; + unsigned int n2 = ((const struct numbered_arg *) p2)->number - 1; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + + for (; *format != '\0';) + if (*format++ == '%') + { + const char *dir_start = format - 1; + + if (*format > '0' && *format <= '9') + { + /* A directive. */ + unsigned int number; + + FDI_SET (dir_start, FMTDIR_START); + spec.directives++; + + number = *format - '0'; + while (format[1] >= '0' && format[1] <= '9') + { + number = 10 * number + (format[1] - '0'); + format++; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered_arg_count++; + + FDI_SET (format, FMTDIR_END); + + format++; + } + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + ; + else + { + if (j < i) + spec.numbered[j].number = spec.numbered[i].number; + j++; + } + spec.numbered_arg_count = j; + } + /* Now spec.numbered[i] >= i + 1 for i = 0,..,spec.numbered_arg_count-1 + (since the numbered argument counts are strictly increasing, considering + 0 as overflow). */ + + /* Verify that the argument numbers are of the form {1,...,n} or + {1,...,n} \ {m}. */ + if (spec.numbered_arg_count > 0) + { + unsigned int i; + + i = 0; + for (; i < spec.numbered_arg_count; i++) + if (spec.numbered[i].number > i + 1) + { + unsigned int first_gap = i + 1; + for (; i < spec.numbered_arg_count; i++) + if (spec.numbered[i].number > i + 2) + { + unsigned int second_gap = i + 2; + *invalid_reason = + xasprintf (_("The string refers to argument number %u but ignores the arguments %u and %u."), + spec.numbered[i].number, first_gap, second_gap); + goto bad_format; + } + break; + } + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + unsigned int missing = 0; /* only used if !equality */ + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else if (missing) + { + if (error_logger) + error_logger (_("a format specification for arguments %u and %u doesn't exist in '%s', only one argument may be ignored"), + missing, spec1->numbered[i].number, + pretty_msgstr); + err = true; + break; + } + else + { + missing = spec1->numbered[i].number; + i++; + } + } + else + j++, i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_kde = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-kde.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-librep.c b/gettext-tools/src/format-librep.c new file mode 100644 index 0000000..05185ad --- /dev/null +++ b/gettext-tools/src/format-librep.c @@ -0,0 +1,463 @@ +/* librep format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* librep format strings are implemented in librep-0.14/src/streams.c. + A directive + - starts with '%' or '%m$' where m is a positive integer, + - is optionally followed by any of the characters '-', '^', '0', '+', ' ', + each of which acts as a flag, + - is optionally followed by a width specification: a nonempty digit + sequence, + - is optionally followed by '.' and a precision specification: a nonempty + digit sequence, + - is finished by a specifier + - '%', that needs no argument, + - 'c', that need a character argument, + - 'd', 'x', 'X', 'o', that need an integer argument, + - 's', that need an argument and prints it using princ, + - 'S', that need an argument and prints it using prin1. + Numbered ('%m$') and unnumbered argument specifications can be used in the + same string. The effect of '%m$' is to set the current argument number to + m. The current argument number is incremented after processing a directive. + */ + +enum format_arg_type +{ + FAT_NONE, + FAT_CHARACTER, + FAT_INTEGER, + FAT_OBJECT_PRETTY, + FAT_OBJECT +}; + +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + unsigned int number; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + number = 1; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + enum format_arg_type type; + + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$' && m > 0) + { + number = m; + format = ++f; + } + } + + /* Parse flags. */ + while (*format == '-' || *format == '^' || *format == '0' + || *format == '+' || *format == ' ') + format++; + + /* Parse width. */ + if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + } + + switch (*format) + { + case '%': + type = FAT_NONE; + break; + case 'c': + type = FAT_CHARACTER; + break; + case 'd': case 'x': case 'X': case 'o': + type = FAT_INTEGER; + break; + case 's': + type = FAT_OBJECT_PRETTY; + break; + case 'S': + type = FAT_OBJECT; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (type != FAT_NONE) + { + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = type; + spec.numbered_arg_count++; + + number++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + enum format_arg_type type1 = spec.numbered[i].type; + enum format_arg_type type2 = spec.numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_librep = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + switch (spec->numbered[i].type) + { + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_OBJECT_PRETTY: + printf ("s"); + break; + case FAT_OBJECT: + printf ("*"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-librep.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-lisp.c b/gettext-tools/src/format-lisp.c new file mode 100644 index 0000000..7d423a5 --- /dev/null +++ b/gettext-tools/src/format-lisp.c @@ -0,0 +1,3658 @@ +/* Lisp format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "gcd.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "minmax.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Assertion macros. Could be defined to empty for speed. */ +#define ASSERT(expr) if (!(expr)) abort (); +#define VERIFY_LIST(list) verify_list (list) + + +/* Lisp format strings are described in the Common Lisp HyperSpec, + chapter 22.3 "Formatted Output". */ + +/* Data structure describing format string derived constraints for an + argument list. It is a recursive list structure. Structure sharing + is not allowed. */ + +enum format_cdr_type +{ + FCT_REQUIRED, /* The format argument list cannot end before this argument. */ + FCT_OPTIONAL /* The format argument list may end before this argument. */ +}; + +enum format_arg_type +{ + FAT_OBJECT, /* Any object, type T. */ + FAT_CHARACTER_INTEGER_NULL, /* Type (OR CHARACTER INTEGER NULL). */ + FAT_CHARACTER_NULL, /* Type (OR CHARACTER NULL). */ + FAT_CHARACTER, /* Type CHARACTER. */ + FAT_INTEGER_NULL, /* Type (OR INTEGER NULL). */ + FAT_INTEGER, /* Meant for objects of type INTEGER. */ + FAT_REAL, /* Meant for objects of type REAL. */ + FAT_LIST, /* Meant for proper lists. */ + FAT_FORMATSTRING, /* Format strings. */ + FAT_FUNCTION /* Function. */ +}; + +struct format_arg +{ + unsigned int repcount; /* Number of consecutive arguments this constraint + applies to. Normally 1, but unconstrained + arguments are often repeated. */ + enum format_cdr_type presence; /* Can the argument list end right before + this argument? */ + enum format_arg_type type; /* Possible values for this argument. */ + struct format_arg_list *list; /* For FAT_LIST: List elements. */ +}; + +struct segment +{ + unsigned int count; /* Number of format_arg records used. */ + unsigned int allocated; + struct format_arg *element; /* Argument constraints. */ + unsigned int length; /* Number of arguments represented by this segment. + This is the sum of all repcounts in the segment. */ +}; + +struct format_arg_list +{ + /* The constraints for the potentially infinite argument list are assumed + to become ultimately periodic. (Too complicated argument lists without + a-priori period, like + (format t "~@{~:[-~;~S~]~}" nil t 1 t 3 nil t 4) + are described by a constraint that ends in a length 1 period of + unconstrained arguments.) Such a periodic sequence can be split into + an initial segment and an endlessly repeated loop segment. + A finite sequence is represented entirely in the initial segment; the + loop segment is empty. */ + + struct segment initial; /* Initial arguments segment. */ + struct segment repeated; /* Endlessly repeated segment. */ +}; + +struct spec +{ + unsigned int directives; + struct format_arg_list *list; +}; + + +/* Parameter for a directive. */ +enum param_type +{ + PT_NIL, /* param not present */ + PT_CHARACTER, /* character */ + PT_INTEGER, /* integer */ + PT_ARGCOUNT, /* number of remaining arguments */ + PT_V /* variable taken from argument list */ +}; + +struct param +{ + enum param_type type; + int value; /* for PT_INTEGER: the value, for PT_V: the position */ +}; + + +/* Forward declaration of local functions. */ +#define union make_union +static void verify_list (const struct format_arg_list *list); +static void free_list (struct format_arg_list *list); +static struct format_arg_list * copy_list (const struct format_arg_list *list); +static bool equal_list (const struct format_arg_list *list1, + const struct format_arg_list *list2); +static struct format_arg_list * make_intersected_list + (struct format_arg_list *list1, + struct format_arg_list *list2); +static struct format_arg_list * make_intersection_with_empty_list + (struct format_arg_list *list); +static struct format_arg_list * make_union_list + (struct format_arg_list *list1, + struct format_arg_list *list2); + + +/* ======================= Verify a format_arg_list ======================= */ + +/* Verify some invariants. */ +static void +verify_element (const struct format_arg * e) +{ + ASSERT (e->repcount > 0); + if (e->type == FAT_LIST) + verify_list (e->list); +} + +/* Verify some invariants. */ +/* Memory effects: none. */ +static void +verify_list (const struct format_arg_list *list) +{ + unsigned int i; + unsigned int total_repcount; + + ASSERT (list->initial.count <= list->initial.allocated); + total_repcount = 0; + for (i = 0; i < list->initial.count; i++) + { + verify_element (&list->initial.element[i]); + total_repcount += list->initial.element[i].repcount; + } + ASSERT (total_repcount == list->initial.length); + + ASSERT (list->repeated.count <= list->repeated.allocated); + total_repcount = 0; + for (i = 0; i < list->repeated.count; i++) + { + verify_element (&list->repeated.element[i]); + total_repcount += list->repeated.element[i].repcount; + } + ASSERT (total_repcount == list->repeated.length); +} + +#define VERIFY_LIST(list) verify_list (list) + + +/* ======================== Free a format_arg_list ======================== */ + +/* Free the data belonging to an argument list element. */ +static inline void +free_element (struct format_arg *element) +{ + if (element->type == FAT_LIST) + free_list (element->list); +} + +/* Free an argument list. */ +/* Memory effects: Frees list. */ +static void +free_list (struct format_arg_list *list) +{ + unsigned int i; + + for (i = 0; i < list->initial.count; i++) + free_element (&list->initial.element[i]); + if (list->initial.element != NULL) + free (list->initial.element); + + for (i = 0; i < list->repeated.count; i++) + free_element (&list->repeated.element[i]); + if (list->repeated.element != NULL) + free (list->repeated.element); +} + + +/* ======================== Copy a format_arg_list ======================== */ + +/* Copy the data belonging to an argument list element. */ +static inline void +copy_element (struct format_arg *newelement, + const struct format_arg *oldelement) +{ + newelement->repcount = oldelement->repcount; + newelement->presence = oldelement->presence; + newelement->type = oldelement->type; + if (oldelement->type == FAT_LIST) + newelement->list = copy_list (oldelement->list); +} + +/* Copy an argument list. */ +/* Memory effects: Freshly allocated result. */ +static struct format_arg_list * +copy_list (const struct format_arg_list *list) +{ + struct format_arg_list *newlist; + unsigned int length; + unsigned int i; + + VERIFY_LIST (list); + + newlist = XMALLOC (struct format_arg_list); + + newlist->initial.count = newlist->initial.allocated = list->initial.count; + length = 0; + if (list->initial.count == 0) + newlist->initial.element = NULL; + else + { + newlist->initial.element = + XNMALLOC (newlist->initial.allocated, struct format_arg); + for (i = 0; i < list->initial.count; i++) + { + copy_element (&newlist->initial.element[i], + &list->initial.element[i]); + length += list->initial.element[i].repcount; + } + } + ASSERT (length == list->initial.length); + newlist->initial.length = length; + + newlist->repeated.count = newlist->repeated.allocated = list->repeated.count; + length = 0; + if (list->repeated.count == 0) + newlist->repeated.element = NULL; + else + { + newlist->repeated.element = + XNMALLOC (newlist->repeated.allocated, struct format_arg); + for (i = 0; i < list->repeated.count; i++) + { + copy_element (&newlist->repeated.element[i], + &list->repeated.element[i]); + length += list->repeated.element[i].repcount; + } + } + ASSERT (length == list->repeated.length); + newlist->repeated.length = length; + + VERIFY_LIST (newlist); + + return newlist; +} + + +/* ===================== Compare two format_arg_lists ===================== */ + +/* Tests whether two normalized argument constraints are equivalent, + ignoring the repcount. */ +static bool +equal_element (const struct format_arg * e1, const struct format_arg * e2) +{ + return (e1->presence == e2->presence + && e1->type == e2->type + && (e1->type == FAT_LIST ? equal_list (e1->list, e2->list) : true)); +} + +/* Tests whether two normalized argument list constraints are equivalent. */ +/* Memory effects: none. */ +static bool +equal_list (const struct format_arg_list *list1, + const struct format_arg_list *list2) +{ + unsigned int n, i; + + VERIFY_LIST (list1); + VERIFY_LIST (list2); + + n = list1->initial.count; + if (n != list2->initial.count) + return false; + for (i = 0; i < n; i++) + { + const struct format_arg * e1 = &list1->initial.element[i]; + const struct format_arg * e2 = &list2->initial.element[i]; + + if (!(e1->repcount == e2->repcount && equal_element (e1, e2))) + return false; + } + + n = list1->repeated.count; + if (n != list2->repeated.count) + return false; + for (i = 0; i < n; i++) + { + const struct format_arg * e1 = &list1->repeated.element[i]; + const struct format_arg * e2 = &list2->repeated.element[i]; + + if (!(e1->repcount == e2->repcount && equal_element (e1, e2))) + return false; + } + + return true; +} + + +/* ===================== Incremental memory allocation ===================== */ + +/* Ensure list->initial.allocated >= newcount. */ +static inline void +ensure_initial_alloc (struct format_arg_list *list, unsigned int newcount) +{ + if (newcount > list->initial.allocated) + { + list->initial.allocated = + MAX (2 * list->initial.allocated + 1, newcount); + list->initial.element = + (struct format_arg *) + xrealloc (list->initial.element, + list->initial.allocated * sizeof (struct format_arg)); + } +} + +/* Ensure list->initial.allocated > list->initial.count. */ +static inline void +grow_initial_alloc (struct format_arg_list *list) +{ + if (list->initial.count >= list->initial.allocated) + { + list->initial.allocated = + MAX (2 * list->initial.allocated + 1, list->initial.count + 1); + list->initial.element = + (struct format_arg *) + xrealloc (list->initial.element, + list->initial.allocated * sizeof (struct format_arg)); + } +} + +/* Ensure list->repeated.allocated >= newcount. */ +static inline void +ensure_repeated_alloc (struct format_arg_list *list, unsigned int newcount) +{ + if (newcount > list->repeated.allocated) + { + list->repeated.allocated = + MAX (2 * list->repeated.allocated + 1, newcount); + list->repeated.element = + (struct format_arg *) + xrealloc (list->repeated.element, + list->repeated.allocated * sizeof (struct format_arg)); + } +} + +/* Ensure list->repeated.allocated > list->repeated.count. */ +static inline void +grow_repeated_alloc (struct format_arg_list *list) +{ + if (list->repeated.count >= list->repeated.allocated) + { + list->repeated.allocated = + MAX (2 * list->repeated.allocated + 1, list->repeated.count + 1); + list->repeated.element = + (struct format_arg *) + xrealloc (list->repeated.element, + list->repeated.allocated * sizeof (struct format_arg)); + } +} + + +/* ====================== Normalize a format_arg_list ====================== */ + +/* Normalize an argument list constraint, assuming all sublists are already + normalized. */ +/* Memory effects: Destructively modifies list. */ +static void +normalize_outermost_list (struct format_arg_list *list) +{ + unsigned int n, i, j; + + /* Step 1: Combine adjacent elements. + Copy from i to j, keeping 0 <= j <= i. */ + + n = list->initial.count; + for (i = j = 0; i < n; i++) + if (j > 0 + && equal_element (&list->initial.element[i], + &list->initial.element[j-1])) + { + list->initial.element[j-1].repcount += + list->initial.element[i].repcount; + free_element (&list->initial.element[i]); + } + else + { + if (j < i) + list->initial.element[j] = list->initial.element[i]; + j++; + } + list->initial.count = j; + + n = list->repeated.count; + for (i = j = 0; i < n; i++) + if (j > 0 + && equal_element (&list->repeated.element[i], + &list->repeated.element[j-1])) + { + list->repeated.element[j-1].repcount += + list->repeated.element[i].repcount; + free_element (&list->repeated.element[i]); + } + else + { + if (j < i) + list->repeated.element[j] = list->repeated.element[i]; + j++; + } + list->repeated.count = j; + + /* Nothing more to be done if the loop segment is empty. */ + if (list->repeated.count > 0) + { + unsigned int m, repcount0_extra; + + /* Step 2: Reduce the loop period. */ + n = list->repeated.count; + repcount0_extra = 0; + if (n > 1 + && equal_element (&list->repeated.element[0], + &list->repeated.element[n-1])) + { + repcount0_extra = list->repeated.element[n-1].repcount; + n--; + } + /* Proceed as if the loop period were n, with + list->repeated.element[0].repcount incremented by repcount0_extra. */ + for (m = 2; m <= n / 2; n++) + if ((n % m) == 0) + { + /* m is a divisor of n. Try to reduce the loop period to n. */ + bool ok = true; + + for (i = 0; i < n - m; i++) + if (!((list->repeated.element[i].repcount + + (i == 0 ? repcount0_extra : 0) + == list->repeated.element[i+m].repcount) + && equal_element (&list->repeated.element[i], + &list->repeated.element[i+m]))) + { + ok = false; + break; + } + if (ok) + { + for (i = m; i < n; i++) + free_element (&list->repeated.element[i]); + if (n < list->repeated.count) + list->repeated.element[m] = list->repeated.element[n]; + list->repeated.count = list->repeated.count - n + m; + list->repeated.length /= n / m; + break; + } + } + + /* Step 3: Roll as much as possible of the initial segment's tail + into the loop. */ + if (list->repeated.count == 1) + { + if (list->initial.count > 0 + && equal_element (&list->initial.element[list->initial.count-1], + &list->repeated.element[0])) + { + /* Roll the last element of the initial segment into the loop. + Its repcount is irrelevant. The second-to-last element is + certainly different and doesn't need to be considered. */ + list->initial.length -= + list->initial.element[list->initial.count-1].repcount; + list->initial.count--; + } + } + else + { + while (list->initial.count > 0 + && equal_element (&list->initial.element[list->initial.count-1], + &list->repeated.element[list->repeated.count-1])) + { + unsigned int moved_repcount = + MIN (list->initial.element[list->initial.count-1].repcount, + list->repeated.element[list->repeated.count-1].repcount); + + /* Add the element at the start of list->repeated. */ + if (equal_element (&list->repeated.element[0], + &list->repeated.element[list->repeated.count-1])) + list->repeated.element[0].repcount += moved_repcount; + else + { + unsigned int newcount = list->repeated.count + 1; + ensure_repeated_alloc (list, newcount); + for (i = newcount - 1; i > 0; i--) + list->repeated.element[i] = list->repeated.element[i-1]; + list->repeated.count = newcount; + copy_element (&list->repeated.element[0], + &list->repeated.element[list->repeated.count-1]); + list->repeated.element[0].repcount = moved_repcount; + } + + /* Remove the element from the end of list->repeated. */ + list->repeated.element[list->repeated.count-1].repcount -= + moved_repcount; + if (list->repeated.element[list->repeated.count-1].repcount == 0) + { + free_element (&list->repeated.element[list->repeated.count-1]); + list->repeated.count--; + } + + /* Remove the element from the end of list->initial. */ + list->initial.element[list->initial.count-1].repcount -= + moved_repcount; + if (list->initial.element[list->initial.count-1].repcount == 0) + { + free_element (&list->initial.element[list->initial.count-1]); + list->initial.count--; + } + list->initial.length -= moved_repcount; + } + } + } +} + +/* Normalize an argument list constraint. */ +/* Memory effects: Destructively modifies list. */ +static void +normalize_list (struct format_arg_list *list) +{ + unsigned int n, i; + + VERIFY_LIST (list); + + /* First normalize all elements, recursively. */ + n = list->initial.count; + for (i = 0; i < n; i++) + if (list->initial.element[i].type == FAT_LIST) + normalize_list (list->initial.element[i].list); + n = list->repeated.count; + for (i = 0; i < n; i++) + if (list->repeated.element[i].type == FAT_LIST) + normalize_list (list->repeated.element[i].list); + + /* Then normalize the top level list. */ + normalize_outermost_list (list); + + VERIFY_LIST (list); +} + + +/* ===================== Unconstrained and empty lists ===================== */ + +/* It's easier to allocate these on demand, than to be careful not to + accidentally modify statically allocated lists. */ + + +/* Create an unconstrained argument list. */ +/* Memory effects: Freshly allocated result. */ +static struct format_arg_list * +make_unconstrained_list () +{ + struct format_arg_list *list; + + list = XMALLOC (struct format_arg_list); + list->initial.count = 0; + list->initial.allocated = 0; + list->initial.element = NULL; + list->initial.length = 0; + list->repeated.count = 1; + list->repeated.allocated = 1; + list->repeated.element = XNMALLOC (1, struct format_arg); + list->repeated.element[0].repcount = 1; + list->repeated.element[0].presence = FCT_OPTIONAL; + list->repeated.element[0].type = FAT_OBJECT; + list->repeated.length = 1; + + VERIFY_LIST (list); + + return list; +} + + +/* Create an empty argument list. */ +/* Memory effects: Freshly allocated result. */ +static struct format_arg_list * +make_empty_list () +{ + struct format_arg_list *list; + + list = XMALLOC (struct format_arg_list); + list->initial.count = 0; + list->initial.allocated = 0; + list->initial.element = NULL; + list->initial.length = 0; + list->repeated.count = 0; + list->repeated.allocated = 0; + list->repeated.element = NULL; + list->repeated.length = 0; + + VERIFY_LIST (list); + + return list; +} + + +/* Test for an empty list. */ +/* Memory effects: none. */ +static bool +is_empty_list (const struct format_arg_list *list) +{ + return (list->initial.count == 0 && list->repeated.count == 0); +} + + +/* ======================== format_arg_list surgery ======================== */ + +/* Unfold list->repeated m times, where m >= 1. + Assumes list->repeated.count > 0. */ +/* Memory effects: list is destructively modified. */ +static void +unfold_loop (struct format_arg_list *list, unsigned int m) +{ + unsigned int i, j, k; + + if (m > 1) + { + unsigned int newcount = list->repeated.count * m; + ensure_repeated_alloc (list, newcount); + i = list->repeated.count; + for (k = 1; k < m; k++) + for (j = 0; j < list->repeated.count; j++, i++) + copy_element (&list->repeated.element[i], &list->repeated.element[j]); + list->repeated.count = newcount; + list->repeated.length = list->repeated.length * m; + } +} + +/* Ensure list->initial.length := m, where m >= list->initial.length. + Assumes list->repeated.count > 0. */ +/* Memory effects: list is destructively modified. */ +static void +rotate_loop (struct format_arg_list *list, unsigned int m) +{ + if (m == list->initial.length) + return; + + if (list->repeated.count == 1) + { + /* Instead of multiple copies of list->repeated.element[0], a single + copy with higher repcount is appended to list->initial. */ + unsigned int i, newcount; + + newcount = list->initial.count + 1; + ensure_initial_alloc (list, newcount); + i = list->initial.count; + copy_element (&list->initial.element[i], &list->repeated.element[0]); + list->initial.element[i].repcount = m - list->initial.length; + list->initial.count = newcount; + list->initial.length = m; + } + else + { + unsigned int n = list->repeated.length; + + /* Write m = list->initial.length + q * n + r with 0 <= r < n. */ + unsigned int q = (m - list->initial.length) / n; + unsigned int r = (m - list->initial.length) % n; + + /* Determine how many entries of list->repeated are needed for + length r. */ + unsigned int s; + unsigned int t; + + for (t = r, s = 0; + s < list->repeated.count && t >= list->repeated.element[s].repcount; + t -= list->repeated.element[s].repcount, s++) + ; + + /* s must be < list->repeated.count, otherwise r would have been >= n. */ + ASSERT (s < list->repeated.count); + + /* So we need to add to list->initial: + q full copies of list->repeated, + plus the s first elements of list->repeated, + plus, if t > 0, a splitoff of list->repeated.element[s]. */ + { + unsigned int i, j, k, newcount; + + i = list->initial.count; + newcount = i + q * list->repeated.count + s + (t > 0 ? 1 : 0); + ensure_initial_alloc (list, newcount); + for (k = 0; k < q; k++) + for (j = 0; j < list->repeated.count; j++, i++) + copy_element (&list->initial.element[i], + &list->repeated.element[j]); + for (j = 0; j < s; j++, i++) + copy_element (&list->initial.element[i], &list->repeated.element[j]); + if (t > 0) + { + copy_element (&list->initial.element[i], + &list->repeated.element[j]); + list->initial.element[i].repcount = t; + i++; + } + ASSERT (i == newcount); + list->initial.count = newcount; + /* The new length of the initial segment is + = list->initial.length + + q * list->repeated.length + + list->repeated[0..s-1].repcount + t + = list->initial.length + q * n + r + = m. + */ + list->initial.length = m; + } + + /* And rotate list->repeated. */ + if (r > 0) + { + unsigned int i, j, oldcount, newcount; + struct format_arg *newelement; + + oldcount = list->repeated.count; + newcount = list->repeated.count + (t > 0 ? 1 : 0); + newelement = XNMALLOC (newcount, struct format_arg); + i = 0; + for (j = s; j < oldcount; j++, i++) + newelement[i] = list->repeated.element[j]; + for (j = 0; j < s; j++, i++) + newelement[i] = list->repeated.element[j]; + if (t > 0) + { + copy_element (&newelement[oldcount], &newelement[0]); + newelement[0].repcount -= t; + newelement[oldcount].repcount = t; + } + free (list->repeated.element); + list->repeated.element = newelement; + } + } +} + + +/* Ensure index n in the initial segment falls on a split between elements, + i.e. if 0 < n < list->initial.length, then n-1 and n are covered by two + different adjacent elements. */ +/* Memory effects: list is destructively modified. */ +static unsigned int +initial_splitelement (struct format_arg_list *list, unsigned int n) +{ + unsigned int s; + unsigned int t; + unsigned int oldrepcount; + unsigned int newcount; + unsigned int i; + + VERIFY_LIST (list); + + if (n > list->initial.length) + { + ASSERT (list->repeated.count > 0); + rotate_loop (list, n); + ASSERT (n <= list->initial.length); + } + + /* Determine how many entries of list->initial need to be skipped. */ + for (t = n, s = 0; + s < list->initial.count && t >= list->initial.element[s].repcount; + t -= list->initial.element[s].repcount, s++) + ; + + if (t == 0) + return s; + + ASSERT (s < list->initial.count); + + /* Split the entry into two entries. */ + oldrepcount = list->initial.element[s].repcount; + newcount = list->initial.count + 1; + ensure_initial_alloc (list, newcount); + for (i = list->initial.count - 1; i > s; i--) + list->initial.element[i+1] = list->initial.element[i]; + copy_element (&list->initial.element[s+1], &list->initial.element[s]); + list->initial.element[s].repcount = t; + list->initial.element[s+1].repcount = oldrepcount - t; + list->initial.count = newcount; + + VERIFY_LIST (list); + + return s+1; +} + + +/* Ensure index n in the initial segment is not shared. Return its index. */ +/* Memory effects: list is destructively modified. */ +static unsigned int +initial_unshare (struct format_arg_list *list, unsigned int n) +{ + /* This does the same side effects as + initial_splitelement (list, n); + initial_splitelement (list, n + 1); + */ + unsigned int s; + unsigned int t; + + VERIFY_LIST (list); + + if (n >= list->initial.length) + { + ASSERT (list->repeated.count > 0); + rotate_loop (list, n + 1); + ASSERT (n < list->initial.length); + } + + /* Determine how many entries of list->initial need to be skipped. */ + for (t = n, s = 0; + s < list->initial.count && t >= list->initial.element[s].repcount; + t -= list->initial.element[s].repcount, s++) + ; + + /* s must be < list->initial.count. */ + ASSERT (s < list->initial.count); + + if (list->initial.element[s].repcount > 1) + { + /* Split the entry into at most three entries: for indices < n, + for index n, and for indices > n. */ + unsigned int oldrepcount = list->initial.element[s].repcount; + unsigned int newcount = + list->initial.count + (t == 0 || t == oldrepcount - 1 ? 1 : 2); + ensure_initial_alloc (list, newcount); + if (t == 0 || t == oldrepcount - 1) + { + unsigned int i; + + for (i = list->initial.count - 1; i > s; i--) + list->initial.element[i+1] = list->initial.element[i]; + copy_element (&list->initial.element[s+1], &list->initial.element[s]); + if (t == 0) + { + list->initial.element[s].repcount = 1; + list->initial.element[s+1].repcount = oldrepcount - 1; + } + else + { + list->initial.element[s].repcount = oldrepcount - 1; + list->initial.element[s+1].repcount = 1; + } + } + else + { + unsigned int i; + + for (i = list->initial.count - 1; i > s; i--) + list->initial.element[i+2] = list->initial.element[i]; + copy_element (&list->initial.element[s+2], &list->initial.element[s]); + copy_element (&list->initial.element[s+1], &list->initial.element[s]); + list->initial.element[s].repcount = t; + list->initial.element[s+1].repcount = 1; + list->initial.element[s+2].repcount = oldrepcount - 1 - t; + } + list->initial.count = newcount; + if (t > 0) + s++; + } + + /* Now the entry for index n has repcount 1. */ + ASSERT (list->initial.element[s].repcount == 1); + + VERIFY_LIST (list); + + return s; +} + + +/* Add n unconstrained elements at the front of the list. */ +/* Memory effects: list is destructively modified. */ +static void +shift_list (struct format_arg_list *list, unsigned int n) +{ + VERIFY_LIST (list); + + if (n > 0) + { + unsigned int i; + + grow_initial_alloc (list); + for (i = list->initial.count; i > 0; i--) + list->initial.element[i] = list->initial.element[i-1]; + list->initial.element[0].repcount = n; + list->initial.element[0].presence = FCT_REQUIRED; + list->initial.element[0].type = FAT_OBJECT; + list->initial.count++; + list->initial.length += n; + + normalize_outermost_list (list); + } + + VERIFY_LIST (list); +} + + +/* ================= Intersection of two format_arg_lists ================= */ + +/* Create the intersection (i.e. combined constraints) of two argument + constraints. Return false if the intersection is empty, i.e. if the + two constraints give a contradiction. */ +/* Memory effects: Freshly allocated element's sublist. */ +static bool +make_intersected_element (struct format_arg *re, + const struct format_arg * e1, + const struct format_arg * e2) +{ + /* Intersect the cdr types. */ + if (e1->presence == FCT_REQUIRED || e2->presence == FCT_REQUIRED) + re->presence = FCT_REQUIRED; + else + re->presence = FCT_OPTIONAL; + + /* Intersect the arg types. */ + if (e1->type == FAT_OBJECT) + { + re->type = e2->type; + if (re->type == FAT_LIST) + re->list = copy_list (e2->list); + } + else if (e2->type == FAT_OBJECT) + { + re->type = e1->type; + if (re->type == FAT_LIST) + re->list = copy_list (e1->list); + } + else if (e1->type == FAT_LIST + && (e2->type == FAT_CHARACTER_INTEGER_NULL + || e2->type == FAT_CHARACTER_NULL + || e2->type == FAT_INTEGER_NULL)) + { + re->type = e1->type; + re->list = make_intersection_with_empty_list (e1->list); + if (re->list == NULL) + return false; + } + else if (e2->type == FAT_LIST + && (e1->type == FAT_CHARACTER_INTEGER_NULL + || e1->type == FAT_CHARACTER_NULL + || e1->type == FAT_INTEGER_NULL)) + { + re->type = e2->type; + re->list = make_intersection_with_empty_list (e2->list); + if (re->list == NULL) + return false; + } + else if (e1->type == FAT_CHARACTER_INTEGER_NULL + && (e2->type == FAT_CHARACTER_NULL || e2->type == FAT_CHARACTER + || e2->type == FAT_INTEGER_NULL || e2->type == FAT_INTEGER)) + { + re->type = e2->type; + } + else if (e2->type == FAT_CHARACTER_INTEGER_NULL + && (e1->type == FAT_CHARACTER_NULL || e1->type == FAT_CHARACTER + || e1->type == FAT_INTEGER_NULL || e1->type == FAT_INTEGER)) + { + re->type = e1->type; + } + else if (e1->type == FAT_CHARACTER_NULL && e2->type == FAT_CHARACTER) + { + re->type = e2->type; + } + else if (e2->type == FAT_CHARACTER_NULL && e1->type == FAT_CHARACTER) + { + re->type = e1->type; + } + else if (e1->type == FAT_INTEGER_NULL && e2->type == FAT_INTEGER) + { + re->type = e2->type; + } + else if (e2->type == FAT_INTEGER_NULL && e1->type == FAT_INTEGER) + { + re->type = e1->type; + } + else if (e1->type == FAT_REAL && e2->type == FAT_INTEGER) + { + re->type = e2->type; + } + else if (e2->type == FAT_REAL && e1->type == FAT_INTEGER) + { + re->type = e1->type; + } + else if (e1->type == e2->type) + { + re->type = e1->type; + if (re->type == FAT_LIST) + { + re->list = make_intersected_list (copy_list (e1->list), + copy_list (e2->list)); + if (re->list == NULL) + return false; + } + } + else + /* Each of FAT_CHARACTER, FAT_INTEGER, FAT_LIST, FAT_FORMATSTRING, + FAT_FUNCTION matches only itself. Contradiction. */ + return false; + + return true; +} + +/* Append list->repeated to list->initial, and clear list->repeated. */ +/* Memory effects: list is destructively modified. */ +static void +append_repeated_to_initial (struct format_arg_list *list) +{ + if (list->repeated.count > 0) + { + /* Move list->repeated over to list->initial. */ + unsigned int i, j, newcount; + + newcount = list->initial.count + list->repeated.count; + ensure_initial_alloc (list, newcount); + i = list->initial.count; + for (j = 0; j < list->repeated.count; j++, i++) + list->initial.element[i] = list->repeated.element[j]; + list->initial.count = newcount; + list->initial.length = list->initial.length + list->repeated.length; + free (list->repeated.element); + list->repeated.element = NULL; + list->repeated.allocated = 0; + list->repeated.count = 0; + list->repeated.length = 0; + } +} + +/* Handle a contradiction during building of a format_arg_list. + The list consists only of an initial segment. The repeated segment is + empty. This function searches the last FCT_OPTIONAL and cuts off the + list at this point, or - if none is found - returns NULL. */ +/* Memory effects: list is destructively modified. If NULL is returned, + list is freed. */ +static struct format_arg_list * +backtrack_in_initial (struct format_arg_list *list) +{ + ASSERT (list->repeated.count == 0); + + while (list->initial.count > 0) + { + unsigned int i = list->initial.count - 1; + if (list->initial.element[i].presence == FCT_REQUIRED) + { + /* Throw away this element. */ + list->initial.length -= list->initial.element[i].repcount; + free_element (&list->initial.element[i]); + list->initial.count = i; + } + else /* list->initial.element[i].presence == FCT_OPTIONAL */ + { + /* The list must end here. */ + list->initial.length--; + if (list->initial.element[i].repcount > 1) + list->initial.element[i].repcount--; + else + { + free_element (&list->initial.element[i]); + list->initial.count = i; + } + VERIFY_LIST (list); + return list; + } + } + + free_list (list); + return NULL; +} + +/* Create the intersection (i.e. combined constraints) of two argument list + constraints. Free both argument lists when done. Return NULL if the + intersection is empty, i.e. if the two constraints give a contradiction. */ +/* Memory effects: list1 and list2 are freed. The result, if non-NULL, is + freshly allocated. */ +static struct format_arg_list * +make_intersected_list (struct format_arg_list *list1, + struct format_arg_list *list2) +{ + struct format_arg_list *result; + + VERIFY_LIST (list1); + VERIFY_LIST (list2); + + if (list1->repeated.length > 0 && list2->repeated.length > 0) + /* Step 1: Ensure list1->repeated.length == list2->repeated.length. */ + { + unsigned int n1 = list1->repeated.length; + unsigned int n2 = list2->repeated.length; + unsigned int g = gcd (n1, n2); + unsigned int m1 = n2 / g; /* = lcm(n1,n2) / n1 */ + unsigned int m2 = n1 / g; /* = lcm(n1,n2) / n2 */ + + unfold_loop (list1, m1); + unfold_loop (list2, m2); + /* Now list1->repeated.length = list2->repeated.length = lcm(n1,n2). */ + } + + if (list1->repeated.length > 0 || list2->repeated.length > 0) + /* Step 2: Ensure the initial segment of the result can be computed + from the initial segments of list1 and list2. If both have a + repeated segment, this means to ensure + list1->initial.length == list2->initial.length. */ + { + unsigned int m = MAX (list1->initial.length, list2->initial.length); + + if (list1->repeated.length > 0) + rotate_loop (list1, m); + if (list2->repeated.length > 0) + rotate_loop (list2, m); + } + + if (list1->repeated.length > 0 && list2->repeated.length > 0) + { + ASSERT (list1->initial.length == list2->initial.length); + ASSERT (list1->repeated.length == list2->repeated.length); + } + + /* Step 3: Allocate the result. */ + result = XMALLOC (struct format_arg_list); + result->initial.count = 0; + result->initial.allocated = 0; + result->initial.element = NULL; + result->initial.length = 0; + result->repeated.count = 0; + result->repeated.allocated = 0; + result->repeated.element = NULL; + result->repeated.length = 0; + + /* Step 4: Elementwise intersection of list1->initial, list2->initial. */ + { + struct format_arg *e1; + struct format_arg *e2; + unsigned int c1; + unsigned int c2; + + e1 = list1->initial.element; c1 = list1->initial.count; + e2 = list2->initial.element; c2 = list2->initial.count; + while (c1 > 0 && c2 > 0) + { + struct format_arg *re; + + /* Ensure room in result->initial. */ + grow_initial_alloc (result); + re = &result->initial.element[result->initial.count]; + re->repcount = MIN (e1->repcount, e2->repcount); + + /* Intersect the argument types. */ + if (!make_intersected_element (re, e1, e2)) + { + /* If re->presence == FCT_OPTIONAL, the result list ends here. */ + if (re->presence == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + goto done; + } + + result->initial.count++; + result->initial.length += re->repcount; + + e1->repcount -= re->repcount; + if (e1->repcount == 0) + { + e1++; + c1--; + } + e2->repcount -= re->repcount; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + + if (list1->repeated.count == 0 && list2->repeated.count == 0) + { + /* Intersecting two finite lists. */ + if (c1 > 0) + { + /* list1 longer than list2. */ + if (e1->presence == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + } + else if (c2 > 0) + { + /* list2 longer than list1. */ + if (e2->presence == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + } + goto done; + } + else if (list1->repeated.count == 0) + { + /* Intersecting a finite and an infinite list. */ + ASSERT (c1 == 0); + if ((c2 > 0 ? e2->presence : list2->repeated.element[0].presence) + == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + goto done; + } + else if (list2->repeated.count == 0) + { + /* Intersecting an infinite and a finite list. */ + ASSERT (c2 == 0); + if ((c1 > 0 ? e1->presence : list1->repeated.element[0].presence) + == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + goto done; + } + /* Intersecting two infinite lists. */ + ASSERT (c1 == 0 && c2 == 0); + } + + /* Step 5: Elementwise intersection of list1->repeated, list2->repeated. */ + { + struct format_arg *e1; + struct format_arg *e2; + unsigned int c1; + unsigned int c2; + + e1 = list1->repeated.element; c1 = list1->repeated.count; + e2 = list2->repeated.element; c2 = list2->repeated.count; + while (c1 > 0 && c2 > 0) + { + struct format_arg *re; + + /* Ensure room in result->repeated. */ + grow_repeated_alloc (result); + re = &result->repeated.element[result->repeated.count]; + re->repcount = MIN (e1->repcount, e2->repcount); + + /* Intersect the argument types. */ + if (!make_intersected_element (re, e1, e2)) + { + bool re_is_required = re->presence == FCT_REQUIRED; + + append_repeated_to_initial (result); + + /* If re->presence == FCT_OPTIONAL, the result list ends here. */ + if (re_is_required) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + + goto done; + } + + result->repeated.count++; + result->repeated.length += re->repcount; + + e1->repcount -= re->repcount; + if (e1->repcount == 0) + { + e1++; + c1--; + } + e2->repcount -= re->repcount; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + ASSERT (c1 == 0 && c2 == 0); + } + + done: + free_list (list1); + free_list (list2); + if (result != NULL) + { + /* Undo the loop unfolding and unrolling done above. */ + normalize_outermost_list (result); + VERIFY_LIST (result); + } + return result; +} + + +/* Create the intersection of an argument list and the empty list. + Return NULL if the intersection is empty. */ +/* Memory effects: The result, if non-NULL, is freshly allocated. */ +static struct format_arg_list * +make_intersection_with_empty_list (struct format_arg_list *list) +{ +#if 0 /* equivalent but slower */ + return make_intersected_list (copy_list (list), make_empty_list ()); +#else + if (list->initial.count > 0 + ? list->initial.element[0].presence == FCT_REQUIRED + : list->repeated.count > 0 + && list->repeated.element[0].presence == FCT_REQUIRED) + return NULL; + else + return make_empty_list (); +#endif +} + + +#ifdef unused +/* Create the intersection of two argument list constraints. NULL stands + for an impossible situation, i.e. a contradiction. */ +/* Memory effects: list1 and list2 are freed if non-NULL. The result, + if non-NULL, is freshly allocated. */ +static struct format_arg_list * +intersection (struct format_arg_list *list1, struct format_arg_list *list2) +{ + if (list1 != NULL) + { + if (list2 != NULL) + return make_intersected_list (list1, list2); + else + { + free_list (list1); + return NULL; + } + } + else + { + if (list2 != NULL) + { + free_list (list2); + return NULL; + } + else + return NULL; + } +} +#endif + + +/* ===================== Union of two format_arg_lists ===================== */ + +/* Create the union (i.e. alternative constraints) of two argument + constraints. */ +static void +make_union_element (struct format_arg *re, + const struct format_arg * e1, + const struct format_arg * e2) +{ + /* Union of the cdr types. */ + if (e1->presence == FCT_REQUIRED && e2->presence == FCT_REQUIRED) + re->presence = FCT_REQUIRED; + else /* Either one of them is FCT_OPTIONAL. */ + re->presence = FCT_OPTIONAL; + + /* Union of the arg types. */ + if (e1->type == e2->type) + { + re->type = e1->type; + if (re->type == FAT_LIST) + re->list = make_union_list (copy_list (e1->list), + copy_list (e2->list)); + } + else if (e1->type == FAT_CHARACTER_INTEGER_NULL + && (e2->type == FAT_CHARACTER_NULL || e2->type == FAT_CHARACTER + || e2->type == FAT_INTEGER_NULL || e2->type == FAT_INTEGER)) + { + re->type = e1->type; + } + else if (e2->type == FAT_CHARACTER_INTEGER_NULL + && (e1->type == FAT_CHARACTER_NULL || e1->type == FAT_CHARACTER + || e1->type == FAT_INTEGER_NULL || e1->type == FAT_INTEGER)) + { + re->type = e2->type; + } + else if (e1->type == FAT_CHARACTER_NULL && e2->type == FAT_CHARACTER) + { + re->type = e1->type; + } + else if (e2->type == FAT_CHARACTER_NULL && e1->type == FAT_CHARACTER) + { + re->type = e2->type; + } + else if (e1->type == FAT_INTEGER_NULL && e2->type == FAT_INTEGER) + { + re->type = e1->type; + } + else if (e2->type == FAT_INTEGER_NULL && e1->type == FAT_INTEGER) + { + re->type = e2->type; + } + else if (e1->type == FAT_REAL && e2->type == FAT_INTEGER) + { + re->type = e1->type; + } + else if (e2->type == FAT_REAL && e1->type == FAT_INTEGER) + { + re->type = e2->type; + } + else if (e1->type == FAT_LIST && is_empty_list (e1->list)) + { + if (e2->type == FAT_CHARACTER_INTEGER_NULL + || e2->type == FAT_CHARACTER_NULL + || e2->type == FAT_INTEGER_NULL) + re->type = e2->type; + else if (e2->type == FAT_CHARACTER) + re->type = FAT_CHARACTER_NULL; + else if (e2->type == FAT_INTEGER) + re->type = FAT_INTEGER_NULL; + else + re->type = FAT_OBJECT; + } + else if (e2->type == FAT_LIST && is_empty_list (e2->list)) + { + if (e1->type == FAT_CHARACTER_INTEGER_NULL + || e1->type == FAT_CHARACTER_NULL + || e1->type == FAT_INTEGER_NULL) + re->type = e1->type; + else if (e1->type == FAT_CHARACTER) + re->type = FAT_CHARACTER_NULL; + else if (e1->type == FAT_INTEGER) + re->type = FAT_INTEGER_NULL; + else + re->type = FAT_OBJECT; + } + else if ((e1->type == FAT_CHARACTER || e1->type == FAT_CHARACTER_NULL) + && (e2->type == FAT_INTEGER || e2->type == FAT_INTEGER_NULL)) + { + re->type = FAT_CHARACTER_INTEGER_NULL; + } + else if ((e2->type == FAT_CHARACTER || e2->type == FAT_CHARACTER_NULL) + && (e1->type == FAT_INTEGER || e1->type == FAT_INTEGER_NULL)) + { + re->type = FAT_CHARACTER_INTEGER_NULL; + } + else + { + /* Other union types are too hard to describe precisely. */ + re->type = FAT_OBJECT; + } +} + +/* Create the union (i.e. alternative constraints) of two argument list + constraints. Free both argument lists when done. */ +/* Memory effects: list1 and list2 are freed. The result is freshly + allocated. */ +static struct format_arg_list * +make_union_list (struct format_arg_list *list1, struct format_arg_list *list2) +{ + struct format_arg_list *result; + + VERIFY_LIST (list1); + VERIFY_LIST (list2); + + if (list1->repeated.length > 0 && list2->repeated.length > 0) + { + /* Step 1: Ensure list1->repeated.length == list2->repeated.length. */ + { + unsigned int n1 = list1->repeated.length; + unsigned int n2 = list2->repeated.length; + unsigned int g = gcd (n1, n2); + unsigned int m1 = n2 / g; /* = lcm(n1,n2) / n1 */ + unsigned int m2 = n1 / g; /* = lcm(n1,n2) / n2 */ + + unfold_loop (list1, m1); + unfold_loop (list2, m2); + /* Now list1->repeated.length = list2->repeated.length = lcm(n1,n2). */ + } + + /* Step 2: Ensure that list1->initial.length == list2->initial.length. */ + { + unsigned int m = MAX (list1->initial.length, list2->initial.length); + + rotate_loop (list1, m); + rotate_loop (list2, m); + } + + ASSERT (list1->initial.length == list2->initial.length); + ASSERT (list1->repeated.length == list2->repeated.length); + } + else if (list1->repeated.length > 0) + { + /* Ensure the initial segment of the result can be computed from the + initial segment of list1. */ + if (list2->initial.length >= list1->initial.length) + { + rotate_loop (list1, list2->initial.length); + if (list1->repeated.element[0].presence == FCT_REQUIRED) + rotate_loop (list1, list1->initial.length + 1); + } + } + else if (list2->repeated.length > 0) + { + /* Ensure the initial segment of the result can be computed from the + initial segment of list2. */ + if (list1->initial.length >= list2->initial.length) + { + rotate_loop (list2, list1->initial.length); + if (list2->repeated.element[0].presence == FCT_REQUIRED) + rotate_loop (list2, list2->initial.length + 1); + } + } + + /* Step 3: Allocate the result. */ + result = XMALLOC (struct format_arg_list); + result->initial.count = 0; + result->initial.allocated = 0; + result->initial.element = NULL; + result->initial.length = 0; + result->repeated.count = 0; + result->repeated.allocated = 0; + result->repeated.element = NULL; + result->repeated.length = 0; + + /* Step 4: Elementwise union of list1->initial, list2->initial. */ + { + struct format_arg *e1; + struct format_arg *e2; + unsigned int c1; + unsigned int c2; + + e1 = list1->initial.element; c1 = list1->initial.count; + e2 = list2->initial.element; c2 = list2->initial.count; + while (c1 > 0 && c2 > 0) + { + struct format_arg *re; + + /* Ensure room in result->initial. */ + grow_initial_alloc (result); + re = &result->initial.element[result->initial.count]; + re->repcount = MIN (e1->repcount, e2->repcount); + + /* Union of the argument types. */ + make_union_element (re, e1, e2); + + result->initial.count++; + result->initial.length += re->repcount; + + e1->repcount -= re->repcount; + if (e1->repcount == 0) + { + e1++; + c1--; + } + e2->repcount -= re->repcount; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + + if (c1 > 0) + { + /* list2 already terminated, but still more elements in list1->initial. + Copy them all, but turn the first presence to FCT_OPTIONAL. */ + ASSERT (list2->repeated.count == 0); + + if (e1->presence == FCT_REQUIRED) + { + struct format_arg *re; + + /* Ensure room in result->initial. */ + grow_initial_alloc (result); + re = &result->initial.element[result->initial.count]; + copy_element (re, e1); + re->presence = FCT_OPTIONAL; + re->repcount = 1; + result->initial.count++; + result->initial.length += 1; + e1->repcount -= 1; + if (e1->repcount == 0) + { + e1++; + c1--; + } + } + + /* Ensure room in result->initial. */ + ensure_initial_alloc (result, result->initial.count + c1); + while (c1 > 0) + { + struct format_arg *re; + + re = &result->initial.element[result->initial.count]; + copy_element (re, e1); + result->initial.count++; + result->initial.length += re->repcount; + e1++; + c1--; + } + } + else if (c2 > 0) + { + /* list1 already terminated, but still more elements in list2->initial. + Copy them all, but turn the first presence to FCT_OPTIONAL. */ + ASSERT (list1->repeated.count == 0); + + if (e2->presence == FCT_REQUIRED) + { + struct format_arg *re; + + /* Ensure room in result->initial. */ + grow_initial_alloc (result); + re = &result->initial.element[result->initial.count]; + copy_element (re, e2); + re->presence = FCT_OPTIONAL; + re->repcount = 1; + result->initial.count++; + result->initial.length += 1; + e2->repcount -= 1; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + + /* Ensure room in result->initial. */ + ensure_initial_alloc (result, result->initial.count + c2); + while (c2 > 0) + { + struct format_arg *re; + + re = &result->initial.element[result->initial.count]; + copy_element (re, e2); + result->initial.count++; + result->initial.length += re->repcount; + e2++; + c2--; + } + } + ASSERT (c1 == 0 && c2 == 0); + } + + if (list1->repeated.length > 0 && list2->repeated.length > 0) + /* Step 5: Elementwise union of list1->repeated, list2->repeated. */ + { + struct format_arg *e1; + struct format_arg *e2; + unsigned int c1; + unsigned int c2; + + e1 = list1->repeated.element; c1 = list1->repeated.count; + e2 = list2->repeated.element; c2 = list2->repeated.count; + while (c1 > 0 && c2 > 0) + { + struct format_arg *re; + + /* Ensure room in result->repeated. */ + grow_repeated_alloc (result); + re = &result->repeated.element[result->repeated.count]; + re->repcount = MIN (e1->repcount, e2->repcount); + + /* Union of the argument types. */ + make_union_element (re, e1, e2); + + result->repeated.count++; + result->repeated.length += re->repcount; + + e1->repcount -= re->repcount; + if (e1->repcount == 0) + { + e1++; + c1--; + } + e2->repcount -= re->repcount; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + ASSERT (c1 == 0 && c2 == 0); + } + else if (list1->repeated.length > 0) + { + /* Turning FCT_REQUIRED into FCT_OPTIONAL was already handled in the + initial segment. Just copy the repeated segment of list1. */ + unsigned int i; + + result->repeated.count = list1->repeated.count; + result->repeated.allocated = result->repeated.count; + result->repeated.element = + XNMALLOC (result->repeated.allocated, struct format_arg); + for (i = 0; i < list1->repeated.count; i++) + copy_element (&result->repeated.element[i], + &list1->repeated.element[i]); + result->repeated.length = list1->repeated.length; + } + else if (list2->repeated.length > 0) + { + /* Turning FCT_REQUIRED into FCT_OPTIONAL was already handled in the + initial segment. Just copy the repeated segment of list2. */ + unsigned int i; + + result->repeated.count = list2->repeated.count; + result->repeated.allocated = result->repeated.count; + result->repeated.element = + XNMALLOC (result->repeated.allocated, struct format_arg); + for (i = 0; i < list2->repeated.count; i++) + copy_element (&result->repeated.element[i], + &list2->repeated.element[i]); + result->repeated.length = list2->repeated.length; + } + + free_list (list1); + free_list (list2); + /* Undo the loop unfolding and unrolling done above. */ + normalize_outermost_list (result); + VERIFY_LIST (result); + return result; +} + + +/* Create the union of an argument list and the empty list. */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +make_union_with_empty_list (struct format_arg_list *list) +{ +#if 0 /* equivalent but slower */ + return make_union_list (list, make_empty_list ()); +#else + VERIFY_LIST (list); + + if (list->initial.count > 0 + ? list->initial.element[0].presence == FCT_REQUIRED + : list->repeated.count > 0 + && list->repeated.element[0].presence == FCT_REQUIRED) + { + initial_splitelement (list, 1); + ASSERT (list->initial.count > 0); + ASSERT (list->initial.element[0].repcount == 1); + ASSERT (list->initial.element[0].presence == FCT_REQUIRED); + list->initial.element[0].presence = FCT_OPTIONAL; + + /* We might need to merge list->initial.element[0] and + list->initial.element[1]. */ + normalize_outermost_list (list); + } + + VERIFY_LIST (list); + + return list; +#endif +} + + +/* Create the union of two argument list constraints. NULL stands for an + impossible situation, i.e. a contradiction. */ +/* Memory effects: list1 and list2 are freed if non-NULL. The result, + if non-NULL, is freshly allocated. */ +static struct format_arg_list * +union (struct format_arg_list *list1, struct format_arg_list *list2) +{ + if (list1 != NULL) + { + if (list2 != NULL) + return make_union_list (list1, list2); + else + return list1; + } + else + { + if (list2 != NULL) + return list2; + else + return NULL; + } +} + + +/* =========== Adding specific constraints to a format_arg_list =========== */ + + +/* Test whether arguments 0..n are required arguments in a list. */ +static bool +is_required (const struct format_arg_list *list, unsigned int n) +{ + unsigned int s; + unsigned int t; + + /* We'll check whether the first n+1 presence flags are FCT_REQUIRED. */ + t = n + 1; + + /* Walk the list->initial segment. */ + for (s = 0; + s < list->initial.count && t >= list->initial.element[s].repcount; + t -= list->initial.element[s].repcount, s++) + if (list->initial.element[s].presence != FCT_REQUIRED) + return false; + + if (t == 0) + return true; + + if (s < list->initial.count) + { + if (list->initial.element[s].presence != FCT_REQUIRED) + return false; + else + return true; + } + + /* Walk the list->repeated segment. */ + if (list->repeated.count == 0) + return false; + + for (s = 0; + s < list->repeated.count && t >= list->repeated.element[s].repcount; + t -= list->repeated.element[s].repcount, s++) + if (list->repeated.element[s].presence != FCT_REQUIRED) + return false; + + if (t == 0) + return true; + + if (s < list->repeated.count) + { + if (list->repeated.element[s].presence != FCT_REQUIRED) + return false; + else + return true; + } + + /* The list->repeated segment consists only of FCT_REQUIRED. So, + regardless how many more passes through list->repeated would be + needed until t becomes 0, the result is true. */ + return true; +} + + +/* Add a constraint to an argument list, namely that the arguments 0...n are + present. NULL stands for an impossible situation, i.e. a contradiction. */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +add_required_constraint (struct format_arg_list *list, unsigned int n) +{ + unsigned int i, rest; + + if (list == NULL) + return NULL; + + VERIFY_LIST (list); + + if (list->repeated.count == 0 && list->initial.length <= n) + { + /* list is already constrained to have at most length n. + Contradiction. */ + free_list (list); + return NULL; + } + + initial_splitelement (list, n + 1); + + for (i = 0, rest = n + 1; rest > 0; ) + { + list->initial.element[i].presence = FCT_REQUIRED; + rest -= list->initial.element[i].repcount; + i++; + } + + VERIFY_LIST (list); + + return list; +} + + +/* Add a constraint to an argument list, namely that the argument n is + never present. NULL stands for an impossible situation, i.e. a + contradiction. */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +add_end_constraint (struct format_arg_list *list, unsigned int n) +{ + unsigned int s, i; + enum format_cdr_type n_presence; + + if (list == NULL) + return NULL; + + VERIFY_LIST (list); + + if (list->repeated.count == 0 && list->initial.length <= n) + /* list is already constrained to have at most length n. */ + return list; + + s = initial_splitelement (list, n); + n_presence = + (s < list->initial.count + ? /* n < list->initial.length */ list->initial.element[s].presence + : /* n >= list->initial.length */ list->repeated.element[0].presence); + + for (i = s; i < list->initial.count; i++) + { + list->initial.length -= list->initial.element[i].repcount; + free_element (&list->initial.element[i]); + } + list->initial.count = s; + + for (i = 0; i < list->repeated.count; i++) + free_element (&list->repeated.element[i]); + if (list->repeated.element != NULL) + free (list->repeated.element); + list->repeated.element = NULL; + list->repeated.allocated = 0; + list->repeated.count = 0; + list->repeated.length = 0; + + if (n_presence == FCT_REQUIRED) + return backtrack_in_initial (list); + else + return list; +} + + +/* Add a constraint to an argument list, namely that the argument n is + of a given type. NULL stands for an impossible situation, i.e. a + contradiction. Assumes a preceding add_required_constraint (list, n). */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +add_type_constraint (struct format_arg_list *list, unsigned int n, + enum format_arg_type type) +{ + unsigned int s; + struct format_arg newconstraint; + struct format_arg tmpelement; + + if (list == NULL) + return NULL; + + /* Through the previous add_required_constraint, we can assume + list->initial.length >= n+1. */ + + s = initial_unshare (list, n); + + newconstraint.presence = FCT_OPTIONAL; + newconstraint.type = type; + if (!make_intersected_element (&tmpelement, + &list->initial.element[s], &newconstraint)) + return add_end_constraint (list, n); + free_element (&list->initial.element[s]); + list->initial.element[s].type = tmpelement.type; + list->initial.element[s].list = tmpelement.list; + + VERIFY_LIST (list); + + return list; +} + + +/* Add a constraint to an argument list, namely that the argument n is + of a given list type. NULL stands for an impossible situation, i.e. a + contradiction. Assumes a preceding add_required_constraint (list, n). */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +add_listtype_constraint (struct format_arg_list *list, unsigned int n, + enum format_arg_type type, + struct format_arg_list *sublist) +{ + unsigned int s; + struct format_arg newconstraint; + struct format_arg tmpelement; + + if (list == NULL) + return NULL; + + /* Through the previous add_required_constraint, we can assume + list->initial.length >= n+1. */ + + s = initial_unshare (list, n); + + newconstraint.presence = FCT_OPTIONAL; + newconstraint.type = type; + newconstraint.list = sublist; + if (!make_intersected_element (&tmpelement, + &list->initial.element[s], &newconstraint)) + return add_end_constraint (list, n); + free_element (&list->initial.element[s]); + list->initial.element[s].type = tmpelement.type; + list->initial.element[s].list = tmpelement.list; + + VERIFY_LIST (list); + + return list; +} + + +/* ============= Subroutines used by the format string parser ============= */ + +static void +add_req_type_constraint (struct format_arg_list **listp, + unsigned int position, enum format_arg_type type) +{ + *listp = add_required_constraint (*listp, position); + *listp = add_type_constraint (*listp, position, type); +} + + +static void +add_req_listtype_constraint (struct format_arg_list **listp, + unsigned int position, enum format_arg_type type, + struct format_arg_list *sublist) +{ + *listp = add_required_constraint (*listp, position); + *listp = add_listtype_constraint (*listp, position, type, sublist); +} + + +/* Create an endless repeated list whose elements are lists constrained + by sublist. */ +/* Memory effects: sublist is freed. The result is freshly allocated. */ +static struct format_arg_list * +make_repeated_list_of_lists (struct format_arg_list *sublist) +{ + if (sublist == NULL) + /* The list cannot have a single element. */ + return make_empty_list (); + else + { + struct format_arg_list *listlist; + + listlist = XMALLOC (struct format_arg_list); + + listlist->initial.count = 0; + listlist->initial.allocated = 0; + listlist->initial.element = NULL; + listlist->initial.length = 0; + listlist->repeated.count = 1; + listlist->repeated.allocated = 1; + listlist->repeated.element = XNMALLOC (1, struct format_arg); + listlist->repeated.element[0].repcount = 1; + listlist->repeated.element[0].presence = FCT_OPTIONAL; + listlist->repeated.element[0].type = FAT_LIST; + listlist->repeated.element[0].list = sublist; + listlist->repeated.length = 1; + + VERIFY_LIST (listlist); + + return listlist; + } +} + + +/* Create an endless repeated list which represents the union of a finite + number of copies of L, each time shifted by period: + () + L + L and (*^period L) + L and (*^period L) and (*^{2 period} L) + L and (*^period L) and (*^{2 period} L) and (*^{3 period} L) + ... + */ +/* Memory effects: sublist is freed. The result is freshly allocated. */ +static struct format_arg_list * +make_repeated_list (struct format_arg_list *sublist, unsigned int period) +{ + struct segment tmp; + struct segment *srcseg; + struct format_arg_list *list; + unsigned int p, n, i, si, ti, j, sj, tj, splitindex, newcount; + bool ended; + + VERIFY_LIST (sublist); + + ASSERT (period > 0); + + if (sublist->repeated.count == 0) + { + /* L is a finite list. */ + + if (sublist->initial.length < period) + /* L and (*^period L) is a contradition, so we need to consider + only 1 and 0 iterations. */ + return make_union_with_empty_list (sublist); + + srcseg = &sublist->initial; + p = period; + } + else + { + /* L is an infinite list. */ + /* p := lcm (period, period of L) */ + unsigned int Lp = sublist->repeated.length; + unsigned int m = period / gcd (period, Lp); /* = lcm(period,Lp) / Lp */ + + unfold_loop (sublist, m); + p = m * Lp; + + /* Concatenate the initial and the repeated segments into a single + segment. */ + tmp.count = sublist->initial.count + sublist->repeated.count; + tmp.allocated = tmp.count; + tmp.element = XNMALLOC (tmp.allocated, struct format_arg); + for (i = 0; i < sublist->initial.count; i++) + tmp.element[i] = sublist->initial.element[i]; + for (j = 0; j < sublist->repeated.count; i++, j++) + tmp.element[i] = sublist->initial.element[j]; + tmp.length = sublist->initial.length + sublist->repeated.length; + + srcseg = &tmp; + } + + n = srcseg->length; + + /* Example: n = 7, p = 2 + Let L = (A B C D E F G). + + L = A B C D E F G + L & L<

initial.count = 0; + list->initial.allocated = 0; + list->initial.element = NULL; + list->initial.length = 0; + list->repeated.count = 0; + list->repeated.allocated = 0; + list->repeated.element = NULL; + list->repeated.length = 0; + + /* Sketch: + for (i = 0; i < p; i++) + list->initial.element[i] = srcseg->element[i]; + list->initial.element[0].presence = FCT_OPTIONAL; // union with empty list + for (i = p, j = 0; i < n; i++, j++) + list->initial.element[i] = srcseg->element[i] & list->initial.element[j]; + */ + + ended = false; + + i = 0, ti = 0, si = 0; + while (i < p) + { + unsigned int k = MIN (srcseg->element[si].repcount - ti, p - i); + + /* Ensure room in list->initial. */ + grow_initial_alloc (list); + copy_element (&list->initial.element[list->initial.count], + &srcseg->element[si]); + list->initial.element[list->initial.count].repcount = k; + list->initial.count++; + list->initial.length += k; + + i += k; + ti += k; + if (ti == srcseg->element[si].repcount) + { + ti = 0; + si++; + } + } + + ASSERT (list->initial.count > 0); + if (list->initial.element[0].presence == FCT_REQUIRED) + { + initial_splitelement (list, 1); + ASSERT (list->initial.element[0].presence == FCT_REQUIRED); + ASSERT (list->initial.element[0].repcount == 1); + list->initial.element[0].presence = FCT_OPTIONAL; + } + + j = 0, tj = 0, sj = 0; + while (i < n) + { + unsigned int k = + MIN (srcseg->element[si].repcount - ti, + list->initial.element[sj].repcount - tj); + + /* Ensure room in list->initial. */ + grow_initial_alloc (list); + if (!make_intersected_element (&list->initial.element[list->initial.count], + &srcseg->element[si], + &list->initial.element[sj])) + { + if (list->initial.element[list->initial.count].presence == FCT_REQUIRED) + { + /* Contradiction. Backtrack. */ + list = backtrack_in_initial (list); + ASSERT (list != NULL); /* at least the empty list is valid */ + return list; + } + else + { + /* The list ends here. */ + ended = true; + break; + } + } + list->initial.element[list->initial.count].repcount = k; + list->initial.count++; + list->initial.length += k; + + i += k; + ti += k; + if (ti == srcseg->element[si].repcount) + { + ti = 0; + si++; + } + + j += k; + tj += k; + if (tj == list->initial.element[sj].repcount) + { + tj = 0; + sj++; + } + } + if (!ended) + ASSERT (list->initial.length == n); + + /* Add optional exit points at 0, period, 2*period etc. + FIXME: Not sure this is correct in all cases. */ + for (i = 0; i < list->initial.length; i += period) + { + si = initial_unshare (list, i); + list->initial.element[si].presence = FCT_OPTIONAL; + } + + if (!ended) + { + /* Now split off the repeated part. */ + splitindex = initial_splitelement (list, n - p); + newcount = list->initial.count - splitindex; + if (newcount > list->repeated.allocated) + { + list->repeated.allocated = newcount; + list->repeated.element = XNMALLOC (newcount, struct format_arg); + } + for (i = splitindex, j = 0; i < n; i++, j++) + list->repeated.element[j] = list->initial.element[i]; + list->repeated.count = newcount; + list->repeated.length = p; + list->initial.count = splitindex; + list->initial.length = n - p; + } + + VERIFY_LIST (list); + + return list; +} + + +/* ================= Handling of format string directives ================= */ + +/* Possible signatures of format directives. */ +static const enum format_arg_type I [1] = { FAT_INTEGER_NULL }; +static const enum format_arg_type II [2] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL +}; +static const enum format_arg_type ICCI [4] = { + FAT_INTEGER_NULL, FAT_CHARACTER_NULL, FAT_CHARACTER_NULL, FAT_INTEGER_NULL +}; +static const enum format_arg_type IIIC [4] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_CHARACTER_NULL +}; +static const enum format_arg_type IICCI [5] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_CHARACTER_NULL, FAT_CHARACTER_NULL, + FAT_INTEGER_NULL +}; +static const enum format_arg_type IIICC [5] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_CHARACTER_NULL, + FAT_CHARACTER_NULL +}; +static const enum format_arg_type IIIICCC [7] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_INTEGER_NULL, + FAT_CHARACTER_NULL, FAT_CHARACTER_NULL, FAT_CHARACTER_NULL +}; +static const enum format_arg_type THREE [3] = { + FAT_CHARACTER_INTEGER_NULL, FAT_CHARACTER_INTEGER_NULL, + FAT_CHARACTER_INTEGER_NULL +}; + + +/* Check the parameters. For V params, add the constraint to the argument + list. Return false and fill in *invalid_reason if the format string is + invalid. */ +static bool +check_params (struct format_arg_list **listp, + unsigned int paramcount, struct param *params, + unsigned int t_count, const enum format_arg_type *t_types, + unsigned int directives, char **invalid_reason) +{ + unsigned int orig_paramcount = paramcount; + unsigned int orig_t_count = t_count; + + for (; paramcount > 0 && t_count > 0; + params++, paramcount--, t_types++, t_count--) + { + switch (*t_types) + { + case FAT_CHARACTER_INTEGER_NULL: + break; + case FAT_CHARACTER_NULL: + switch (params->type) + { + case PT_NIL: case PT_CHARACTER: case PT_V: + break; + case PT_INTEGER: case PT_ARGCOUNT: + /* wrong param type */ + *invalid_reason = + xasprintf (_("In the directive number %u, parameter %u is of type '%s' but a parameter of type '%s' is expected."), directives, orig_paramcount - paramcount + 1, "integer", "character"); + return false; + } + break; + case FAT_INTEGER_NULL: + switch (params->type) + { + case PT_NIL: case PT_INTEGER: case PT_ARGCOUNT: case PT_V: + break; + case PT_CHARACTER: + /* wrong param type */ + *invalid_reason = + xasprintf (_("In the directive number %u, parameter %u is of type '%s' but a parameter of type '%s' is expected."), directives, orig_paramcount - paramcount + 1, "character", "integer"); + return false; + } + break; + default: + abort (); + } + if (params->type == PT_V) + { + int position = params->value; + if (position >= 0) + add_req_type_constraint (listp, position, *t_types); + } + } + + for (; paramcount > 0; params++, paramcount--) + switch (params->type) + { + case PT_NIL: + break; + case PT_CHARACTER: case PT_INTEGER: case PT_ARGCOUNT: + /* too many params for directive */ + *invalid_reason = + xasprintf (ngettext ("In the directive number %u, too many parameters are given; expected at most %u parameter.", + "In the directive number %u, too many parameters are given; expected at most %u parameters.", + orig_t_count), + directives, orig_t_count); + return false; + case PT_V: + /* Force argument to be NIL. */ + { + int position = params->value; + if (position >= 0) + { + struct format_arg_list *empty_list = make_empty_list (); + add_req_listtype_constraint (listp, position, + FAT_LIST, empty_list); + free_list (empty_list); + } + } + break; + } + + return true; +} + + +/* Handle the parameters, without a priori type information. + For V params, add the constraint to the argument list. + Return false and fill in *invalid_reason if the format string is + invalid. */ +static bool +nocheck_params (struct format_arg_list **listp, + unsigned int paramcount, struct param *params, + unsigned int directives, char **invalid_reason) +{ + (void) directives; + (void) invalid_reason; + + for (; paramcount > 0; params++, paramcount--) + if (params->type == PT_V) + { + int position = params->value; + add_req_type_constraint (listp, position, FAT_CHARACTER_INTEGER_NULL); + } + + return true; +} + + +/* ======================= The format string parser ======================= */ + +/* Parse a piece of format string, until the matching terminating format + directive is encountered. + format is the remainder of the format string. + position is the position in this argument list, if known, or -1 if unknown. + list represents the argument list constraints at the current parse point. + NULL stands for a contradiction. + escape represents the union of the argument list constraints at all the + currently pending FORMAT-UP-AND-OUT points. NULL stands for a contradiction + or an empty union. + All four are updated upon valid return. + *separatorp is set to true if the parse terminated due to a ~; separator, + more precisely to 2 if with colon, or to 1 if without colon. + spec is the global struct spec. + terminator is the directive that terminates this parse. + separator specifies if ~; separators are allowed. + fdi is an array to be filled with format directive indicators, or NULL. + If the format string is invalid, false is returned and *invalid_reason is + set to an error message explaining why. */ +static bool +parse_upto (const char **formatp, + int *positionp, struct format_arg_list **listp, + struct format_arg_list **escapep, int *separatorp, + struct spec *spec, char terminator, bool separator, + char *fdi, char **invalid_reason) +{ + const char *format = *formatp; + const char *const format_start = format; + int position = *positionp; + struct format_arg_list *list = *listp; + struct format_arg_list *escape = *escapep; + + for (; *format != '\0'; ) + if (*format++ == '~') + { + bool colon_p = false; + bool atsign_p = false; + unsigned int paramcount = 0; + struct param *params = NULL; + + FDI_SET (format - 1, FMTDIR_START); + + /* Count number of directives. */ + spec->directives++; + + /* Parse parameters. */ + for (;;) + { + enum param_type type = PT_NIL; + int value = 0; + + if (c_isdigit (*format)) + { + type = PT_INTEGER; + do + { + value = 10 * value + (*format - '0'); + format++; + } + while (c_isdigit (*format)); + } + else if (*format == '+' || *format == '-') + { + bool negative = (*format == '-'); + type = PT_INTEGER; + format++; + if (!c_isdigit (*format)) + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + xasprintf (_("In the directive number %u, '%c' is not followed by a digit."), spec->directives, format[-1]); + FDI_SET (format, FMTDIR_ERROR); + } + return false; + } + do + { + value = 10 * value + (*format - '0'); + format++; + } + while (c_isdigit (*format)); + if (negative) + value = -value; + } + else if (*format == '\'') + { + type = PT_CHARACTER; + format++; + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + format++; + } + else if (*format == 'V' || *format == 'v') + { + type = PT_V; + format++; + value = position; + /* Consumes an argument. */ + if (position >= 0) + position++; + } + else if (*format == '#') + { + type = PT_ARGCOUNT; + format++; + } + + params = + (struct param *) + xrealloc (params, (paramcount + 1) * sizeof (struct param)); + params[paramcount].type = type; + params[paramcount].value = value; + paramcount++; + + if (*format == ',') + format++; + else + break; + } + + /* Parse modifiers. */ + for (;;) + { + if (*format == ':') + { + format++; + colon_p = true; + } + else if (*format == '@') + { + format++; + atsign_p = true; + } + else + break; + } + + /* Parse directive. */ + switch (*format++) + { + case 'A': case 'a': /* 22.3.4.1 FORMAT-ASCII */ + case 'S': case 's': /* 22.3.4.2 FORMAT-S-EXPRESSION */ + if (!check_params (&list, paramcount, params, 4, IIIC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + break; + + case 'W': case 'w': /* 22.3.4.3 FORMAT-WRITE */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + break; + + case 'D': case 'd': /* 22.3.2.2 FORMAT-DECIMAL */ + case 'B': case 'b': /* 22.3.2.3 FORMAT-BINARY */ + case 'O': case 'o': /* 22.3.2.4 FORMAT-OCTAL */ + case 'X': case 'x': /* 22.3.2.5 FORMAT-HEXADECIMAL */ + if (!check_params (&list, paramcount, params, 4, ICCI, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_INTEGER); + break; + + case 'R': case 'r': /* 22.3.2.1 FORMAT-RADIX */ + if (!check_params (&list, paramcount, params, 5, IICCI, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_INTEGER); + break; + + case 'P': case 'p': /* 22.3.8.3 FORMAT-PLURAL */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (colon_p) + { + /* Go back by 1 argument. */ + if (position > 0) + position--; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + break; + + case 'C': case 'c': /* 22.3.1.1 FORMAT-CHARACTER */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_CHARACTER); + break; + + case 'F': case 'f': /* 22.3.3.1 FORMAT-FIXED-FLOAT */ + if (!check_params (&list, paramcount, params, 5, IIICC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_REAL); + break; + + case 'E': case 'e': /* 22.3.3.2 FORMAT-EXPONENTIAL-FLOAT */ + case 'G': case 'g': /* 22.3.3.3 FORMAT-GENERAL-FLOAT */ + if (!check_params (&list, paramcount, params, 7, IIIICCC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_REAL); + break; + + case '$': /* 22.3.3.4 FORMAT-DOLLARS-FLOAT */ + if (!check_params (&list, paramcount, params, 4, IIIC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_REAL); + break; + + case '%': /* 22.3.1.2 FORMAT-TERPRI */ + case '&': /* 22.3.1.3 FORMAT-FRESH-LINE */ + case '|': /* 22.3.1.4 FORMAT-PAGE */ + case '~': /* 22.3.1.5 FORMAT-TILDE */ + case 'I': case 'i': /* 22.3.5.3 */ + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + break; + + case '\n': /* 22.3.9.3 #\Newline */ + case '_': /* 22.3.5.1 */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + break; + + case 'T': case 't': /* 22.3.6.1 FORMAT-TABULATE */ + if (!check_params (&list, paramcount, params, 2, II, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + break; + + case '*': /* 22.3.7.1 FORMAT-GOTO */ + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + { + int n; /* value of first parameter */ + if (paramcount == 0 + || (paramcount >= 1 && params[0].type == PT_NIL)) + n = (atsign_p ? 0 : 1); + else if (paramcount >= 1 && params[0].type == PT_INTEGER) + n = params[0].value; + else + { + /* Unknown argument, leads to an unknown position. */ + position = -1; + break; + } + if (n < 0) + { + /* invalid argument */ + *invalid_reason = + xasprintf (_("In the directive number %u, the argument %d is negative."), spec->directives, n); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (atsign_p) + { + /* Absolute goto. */ + position = n; + } + else if (colon_p) + { + /* Backward goto. */ + if (n > 0) + { + if (position >= 0) + { + if (position >= n) + position -= n; + else + position = 0; + } + else + position = -1; + } + } + else + { + /* Forward goto. */ + if (position >= 0) + position += n; + } + } + break; + + case '?': /* 22.3.7.6 FORMAT-INDIRECTION */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_FORMATSTRING); + if (atsign_p) + position = -1; + else + if (position >= 0) + { + struct format_arg_list *sublist = make_unconstrained_list (); + add_req_listtype_constraint (&list, position++, + FAT_LIST, sublist); + free_list (sublist); + } + break; + + case '/': /* 22.3.5.4 FORMAT-CALL-USER-FUNCTION */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + while (*format != '\0' && *format != '/') + format++; + if (*format == '\0') + { + *invalid_reason = + xstrdup (_("The string ends in the middle of a ~/.../ directive.")); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + format++; + break; + + case '(': /* 22.3.8.1 FORMAT-CASE-CONVERSION */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + { + if (!parse_upto (formatp, positionp, listp, escapep, + NULL, spec, ')', false, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + } + format = *formatp; + position = *positionp; + list = *listp; + escape = *escapep; + break; + + case ')': /* 22.3.8.2 FORMAT-CASE-CONVERSION-END */ + if (terminator != ')') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), ')', '('); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + return true; + + case '[': /* 22.3.7.2 FORMAT-CONDITIONAL */ + if (atsign_p && colon_p) + { + *invalid_reason = + xasprintf (_("In the directive number %u, both the @ and the : modifiers are given."), spec->directives); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + else if (atsign_p) + { + struct format_arg_list *nil_list; + struct format_arg_list *union_list; + + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + + *formatp = format; + *escapep = escape; + + /* First alternative: argument is NIL. */ + nil_list = (list != NULL ? copy_list (list) : NULL); + if (position >= 0) + { + struct format_arg_list *empty_list = make_empty_list (); + add_req_listtype_constraint (&nil_list, position, + FAT_LIST, empty_list); + free_list (empty_list); + } + + /* Second alternative: use sub-format. */ + { + int sub_position = position; + struct format_arg_list *sub_list = + (list != NULL ? copy_list (list) : NULL); + if (!parse_upto (formatp, &sub_position, &sub_list, escapep, + NULL, spec, ']', false, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (sub_list != NULL) + { + if (position >= 0) + { + if (sub_position == position + 1) + /* new position is branch independent */ + position = position + 1; + else + /* new position is branch dependent */ + position = -1; + } + } + else + { + if (position >= 0) + position = position + 1; + } + union_list = union (nil_list, sub_list); + } + + format = *formatp; + escape = *escapep; + + if (list != NULL) + free_list (list); + list = union_list; + } + else if (colon_p) + { + int union_position; + struct format_arg_list *union_list; + + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + + *formatp = format; + *escapep = escape; + union_position = -2; + union_list = NULL; + + /* First alternative. */ + { + int sub_position = position; + struct format_arg_list *sub_list = + (list != NULL ? copy_list (list) : NULL); + int sub_separator = 0; + if (position >= 0) + { + struct format_arg_list *empty_list = make_empty_list (); + add_req_listtype_constraint (&sub_list, position - 1, + FAT_LIST, empty_list); + free_list (empty_list); + } + if (!parse_upto (formatp, &sub_position, &sub_list, escapep, + &sub_separator, spec, ']', true, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (!sub_separator) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '~:[' is not followed by two clauses, separated by '~;'."), spec->directives); + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (sub_list != NULL) + union_position = sub_position; + union_list = union (union_list, sub_list); + } + + /* Second alternative. */ + { + int sub_position = position; + struct format_arg_list *sub_list = + (list != NULL ? copy_list (list) : NULL); + if (!parse_upto (formatp, &sub_position, &sub_list, escapep, + NULL, spec, ']', false, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (sub_list != NULL) + { + if (union_position == -2) + union_position = sub_position; + else if (sub_position < 0 + || sub_position != union_position) + union_position = -1; + } + union_list = union (union_list, sub_list); + } + + format = *formatp; + escape = *escapep; + + if (union_position != -2) + position = union_position; + if (list != NULL) + free_list (list); + list = union_list; + } + else + { + int arg_position; + int union_position; + struct format_arg_list *union_list; + bool last_alternative; + + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + + /* If there was no first parameter, an argument is consumed. */ + arg_position = -1; + if (!(paramcount >= 1 && params[0].type != PT_NIL)) + if (position >= 0) + { + arg_position = position; + add_req_type_constraint (&list, position++, FAT_OBJECT); + } + + *formatp = format; + *escapep = escape; + + union_position = -2; + union_list = NULL; + last_alternative = false; + for (;;) + { + /* Next alternative. */ + int sub_position = position; + struct format_arg_list *sub_list = + (list != NULL ? copy_list (list) : NULL); + int sub_separator = 0; + if (!parse_upto (formatp, &sub_position, &sub_list, escapep, + &sub_separator, spec, ']', !last_alternative, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + /* If this alternative is chosen, the argument arg_position + is an integer, namely the index of this alternative. */ + if (!last_alternative && arg_position >= 0) + add_req_type_constraint (&sub_list, arg_position, + FAT_INTEGER); + if (sub_list != NULL) + { + if (union_position == -2) + union_position = sub_position; + else if (sub_position < 0 + || sub_position != union_position) + union_position = -1; + } + union_list = union (union_list, sub_list); + if (sub_separator == 2) + last_alternative = true; + if (!sub_separator) + break; + } + if (!last_alternative) + { + /* An implicit default alternative. */ + if (union_position == -2) + union_position = position; + else if (position < 0 || position != union_position) + union_position = -1; + if (list != NULL) + union_list = union (union_list, copy_list (list)); + } + + format = *formatp; + escape = *escapep; + + if (union_position != -2) + position = union_position; + if (list != NULL) + free_list (list); + list = union_list; + } + break; + + case ']': /* 22.3.7.3 FORMAT-CONDITIONAL-END */ + if (terminator != ']') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), ']', '['); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + return true; + + case '{': /* 22.3.7.4 FORMAT-ITERATION */ + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + { + int sub_position = 0; + struct format_arg_list *sub_list = make_unconstrained_list (); + struct format_arg_list *sub_escape = NULL; + struct spec sub_spec; + sub_spec.directives = 0; + sub_spec.list = sub_list; + if (!parse_upto (formatp, &sub_position, &sub_list, &sub_escape, + NULL, &sub_spec, '}', false, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + spec->directives += sub_spec.directives; + + /* If the sub-formatstring is empty, except for the terminating + ~} directive, a formatstring argument is consumed. */ + if (*format == '~' && sub_spec.directives == 1) + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_FORMATSTRING); + + if (colon_p) + { + /* Each iteration uses a new sublist. */ + struct format_arg_list *listlist; + + /* ~{ catches ~^. */ + sub_list = union (sub_list, sub_escape); + + listlist = make_repeated_list_of_lists (sub_list); + + sub_list = listlist; + } + else + { + /* Each iteration's arguments are all concatenated in a + single list. */ + struct format_arg_list *looplist; + + /* FIXME: This is far from correct. Test cases: + abc~{~^~} + abc~{~S~^~S~} + abc~{~D~^~C~} + abc~{~D~^~D~} + abc~{~D~^~S~} + abc~{~D~^~C~}~:*~{~S~^~D~} + */ + + /* ~{ catches ~^. */ + sub_list = union (sub_list, sub_escape); + + if (sub_list == NULL) + looplist = make_empty_list (); + else + if (sub_position < 0 || sub_position == 0) + /* Too hard to track the possible argument types + when the iteration is performed 2 times or more. + So be satisfied with the constraints of executing + the iteration 1 or 0 times. */ + looplist = make_union_with_empty_list (sub_list); + else + looplist = make_repeated_list (sub_list, sub_position); + + sub_list = looplist; + } + + if (atsign_p) + { + /* All remaining arguments are used. */ + if (list != NULL && position >= 0) + { + shift_list (sub_list, position); + list = make_intersected_list (list, sub_list); + } + position = -1; + } + else + { + /* The argument is a list. */ + if (position >= 0) + add_req_listtype_constraint (&list, position++, + FAT_LIST, sub_list); + } + } + format = *formatp; + break; + + case '}': /* 22.3.7.5 FORMAT-ITERATION-END */ + if (terminator != '}') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), '}', '{'); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + return true; + + case '<': /* 22.3.6.2, 22.3.5.2 FORMAT-JUSTIFICATION */ + if (!check_params (&list, paramcount, params, 4, IIIC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + { + struct format_arg_list *sub_escape = NULL; + + *formatp = format; + *positionp = position; + *listp = list; + + for (;;) + { + int sub_separator = 0; + if (!parse_upto (formatp, positionp, listp, &sub_escape, + &sub_separator, spec, '>', true, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (!sub_separator) + break; + } + + format = *formatp; + position = *positionp; + list = *listp; + + /* ~< catches ~^. */ + if (sub_escape != NULL) + position = -1; + list = union (list, sub_escape); + } + break; + + case '>': /* 22.3.6.3 FORMAT-JUSTIFICATION-END */ + if (terminator != '>') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), '>', '<'); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + return true; + + case '^': /* 22.3.9.2 FORMAT-UP-AND-OUT */ + if (!check_params (&list, paramcount, params, 3, THREE, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0 && list != NULL && is_required (list, position)) + /* This ~^ can never be executed. Ignore it. */ + break; + if (list != NULL) + { + struct format_arg_list *this_escape = copy_list (list); + if (position >= 0) + this_escape = add_end_constraint (this_escape, position); + escape = union (escape, this_escape); + } + if (position >= 0) + list = add_required_constraint (list, position); + break; + + case ';': /* 22.3.9.1 FORMAT-SEPARATOR */ + if (!separator) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '~;' is used in an invalid position."), spec->directives); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (terminator == '>') + { + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + } + else + { + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + *separatorp = (colon_p ? 2 : 1); + return true; + + case '!': /* FORMAT-CALL, a CLISP extension */ + if (!nocheck_params (&list, paramcount, params, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + { + add_req_type_constraint (&list, position++, FAT_FUNCTION); + add_req_type_constraint (&list, position++, FAT_OBJECT); + } + break; + + default: + --format; + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec->directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + return false; + } + + FDI_SET (format - 1, FMTDIR_END); + + free (params); + } + + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + if (terminator != '\0') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), terminator - 1, terminator); + return false; + } + return true; +} + + +/* ============== Top level format string handling functions ============== */ + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + struct spec spec; + struct spec *result; + int position = 0; + struct format_arg_list *escape; + + spec.directives = 0; + spec.list = make_unconstrained_list (); + escape = NULL; + + if (!parse_upto (&format, &position, &spec.list, &escape, + NULL, &spec, '\0', false, + fdi, invalid_reason)) + /* Invalid format string. */ + return NULL; + + /* Catch ~^ here. */ + spec.list = union (spec.list, escape); + + if (spec.list == NULL) + { + /* Contradictory argument type information. */ + *invalid_reason = + xstrdup (_("The string refers to some argument in incompatible ways.")); + return NULL; + } + + /* Normalize the result. */ + normalize_list (spec.list); + + result = XMALLOC (struct spec); + *result = spec; + return result; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + free_list (spec->list); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (equality) + { + if (!equal_list (spec1->list, spec2->list)) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' are not equivalent"), + pretty_msgid, pretty_msgstr); + err = true; + } + } + else + { + struct format_arg_list *intersection = + make_intersected_list (copy_list (spec1->list), + copy_list (spec2->list)); + + if (!(intersection != NULL + && (normalize_list (intersection), + equal_list (intersection, spec2->list)))) + { + if (error_logger) + error_logger (_("format specifications in '%s' are not a subset of those in '%s'"), + pretty_msgstr, pretty_msgid); + err = true; + } + } + + return err; +} + + +struct formatstring_parser formatstring_lisp = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +/* ============================= Testing code ============================= */ + +#undef union + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void print_list (struct format_arg_list *list); + +static void +print_element (struct format_arg *element) +{ + switch (element->presence) + { + case FCT_REQUIRED: + break; + case FCT_OPTIONAL: + printf (". "); + break; + default: + abort (); + } + + switch (element->type) + { + case FAT_OBJECT: + printf ("*"); + break; + case FAT_CHARACTER_INTEGER_NULL: + printf ("ci()"); + break; + case FAT_CHARACTER_NULL: + printf ("c()"); + break; + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_INTEGER_NULL: + printf ("i()"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_REAL: + printf ("r"); + break; + case FAT_LIST: + print_list (element->list); + break; + case FAT_FORMATSTRING: + printf ("~"); + break; + case FAT_FUNCTION: + printf ("f"); + break; + default: + abort (); + } +} + +static void +print_list (struct format_arg_list *list) +{ + unsigned int i, j; + + printf ("("); + + for (i = 0; i < list->initial.count; i++) + for (j = 0; j < list->initial.element[i].repcount; j++) + { + if (i > 0 || j > 0) + printf (" "); + print_element (&list->initial.element[i]); + } + + if (list->repeated.count > 0) + { + printf (" |"); + for (i = 0; i < list->repeated.count; i++) + for (j = 0; j < list->repeated.element[i].repcount; j++) + { + printf (" "); + print_element (&list->repeated.element[i]); + } + } + + printf (")"); +} + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + print_list (spec->list); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-lisp.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-lua.c b/gettext-tools/src/format-lua.c new file mode 100644 index 0000000..272def4 --- /dev/null +++ b/gettext-tools/src/format-lua.c @@ -0,0 +1,348 @@ +/* Lua format strings. + Copyright (C) 2012 Free Software Foundation, Inc. + Written by Ľubomír Remák , 2012. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "format.h" +#include "gettext.h" +#include "xalloc.h" +#include "format-invalid.h" +#include "c-ctype.h" +#include "xvasprintf.h" + +#define _(str) gettext (str) + +/* The Lua format strings are described in the Lua manual, + which can be found at: + http://www.lua.org/manual/5.2/manual.html + + A directive + - starts with '%' + - is optionally followed by any of the characters '0', '-', ' ', or + each of which acts as a flag, + - is optionally followed by a width specification: a nonempty digit + sequence, + - is optionally followed by '.' and a precision specification: a nonempty + digit sequence, + - is finished by a specifier + - 's', 'q', that needs a string argument, + - 'd', 'i', 'o', 'u', 'X', 'x', that need an integer argument, + - 'A', 'a', 'E', 'e', 'f', 'G', 'g', that need a floating-point argument, + - 'c', that needs a character argument. + Additionally there is the directive '%%', which takes no argument. + + Note: Lua does not distinguish between integer, floating-point + and character arguments, since it has a number data type only. + However, we should not allow users to use %d instead of %c. + The same applies to %s and %q - we should not allow intermixing them. + */ + +enum format_arg_type +{ + FAT_INTEGER, + FAT_CHARACTER, + FAT_FLOAT, + FAT_STRING, + FAT_ESCAPED_STRING +}; + +struct spec +{ + unsigned int directives; + unsigned int format_args_count; + unsigned int allocated; + enum format_arg_type *format_args; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + +static void format_free (void *descr); + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + + const char *format_start = format; + const char *fatstr = format; + struct spec *result = NULL; + result = XMALLOC (struct spec); + result->directives = 0; + result->allocated = 0; + result->format_args_count = 0; + result->format_args = NULL; + + + for (; *fatstr != '\0';) + { + if (*fatstr++ == '%') + { + FDI_SET (fatstr - 1, FMTDIR_START); + result->directives++; + + if (*fatstr != '%') + { + enum format_arg_type type; + + /* Remove width. */ + while (isdigit (*fatstr)) + fatstr++; + + if (*fatstr == '.') + { + fatstr++; + + /* Remove precision. */ + while (isdigit (*fatstr)) + fatstr++; + } + + switch (*fatstr) + { + case 'c': + type = FAT_CHARACTER; + break; + case 'd': + case 'i': + case 'o': + case 'u': + case 'X': + case 'x': + type = FAT_INTEGER; + break; + case 'a': + case 'A': + case 'E': + case 'e': + case 'f': + case 'g': + case 'G': + type = FAT_FLOAT; + break; + case 's': + type = FAT_STRING; + break; + case 'q': + type = FAT_ESCAPED_STRING; + break; + default: + if (*fatstr == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (fatstr - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (result-> + format_args_count + 1, + *fatstr); + FDI_SET (fatstr, FMTDIR_ERROR); + } + goto fmt_error; + } + + if (result->format_args_count == result->allocated) + { + result->allocated = 2 * result->allocated + 10; + result->format_args = + xrealloc (result->format_args, + result->allocated * + sizeof (enum format_arg_type)); + } + result->format_args[result->format_args_count++] = type; + } + FDI_SET (fatstr, FMTDIR_END); + fatstr++; + } + } + + return result; + +fmt_error: + format_free (result); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->format_args != NULL) + free (spec->format_args); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + + if (spec1->format_args_count + spec2->format_args_count > 0) + { + unsigned int i, n1, n2; + + n1 = spec1->format_args_count; + n2 = spec2->format_args_count; + + for (i = 0; i < n1 || i < n2; i++) + { + if (i >= n1) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + i + 1, pretty_msgstr, pretty_msgid); + return true; + } + else if (i >= n2) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + i + 1, pretty_msgstr); + return true; + } + else if (spec1->format_args[i] != spec2->format_args[i]) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, i + 1); + return true; + } + } + } + + return false; +} + +struct formatstring_parser formatstring_lua = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + for (i = 0; i < spec->format_args_count; i++) + { + if (i > 0) + printf (" "); + switch (spec->format_args[i]) + { + case FAT_INTEGER: + printf ("i"); + break; + case FAT_FLOAT: + printf ("f"); + break; + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_ESCAPED_STRING: + printf ("q"); + break; + default: + abort (); + } + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-lua.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-pascal.c b/gettext-tools/src/format-pascal.c new file mode 100644 index 0000000..7e0c505 --- /dev/null +++ b/gettext-tools/src/format-pascal.c @@ -0,0 +1,548 @@ +/* Object Pascal format strings. + Copyright (C) 2001-2004, 2006-2007, 2009-2010 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Object Pascal format strings are usable with the "format" function in the + "sysutils" unit. They are described in + + and are implemented in fpc-2.4.0/rtl/objpas/sysutils/sysformt.inc. + Another implementation exists in Borland Delphi. The GNU Pascal's + "sysutils" doesn't (yet?) have the "format" function. + + A directive + - starts with '%', + - either + - is finished with '%', or + - - is optionally followed by an index specification: '*' (reads an + argument, must be of type integer) or a nonempty digit sequence + or nothing (equivalent to 0), followed by ':', + - is optionally followed by '-', which acts as a flag, + - is optionally followed by a width specification: '*' (reads an + argument, must be of type integer) or a nonempty digit sequence, + - is optionally followed by '.' and a precision specification: '*' + (reads an argument, must be of type integer) or a nonempty digit + sequence, + - is finished by a case-insensitive specifier. If no index was + specified, it reads an argument; otherwise is uses the index-th + argument, 0-based. + - 'd', 'u', 'x', needs an 'integer' or 'int64' or 'qword' argument, + - 'e', 'f', 'g', 'n', 'm', need an 'extended' or 'currency' floating- + point argument, + - 's', needs a 'string', 'char', 'pchar', 'widestring', 'widechar', + 'pwidechar' or 'ansistring' argument, + - 'p', needs a 'pointer' argument. + Numbered and unnumbered argument specifications can be used in the same + string. Numbered argument specifications have no influence on the + "current argument index", that is incremented each time an argument is read. + */ + +enum format_arg_type +{ + FAT_INTEGER, /* integer, int64, qword */ + FAT_FLOAT, /* extended, currency */ + FAT_STRING, /* string, char, pchar, widestring, widechar, pwidechar, + ansistring */ + FAT_POINTER +}; + +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; + unsigned int unnumbered_arg_count; + struct spec *result; + + enum arg_index + { + index_numbered, /* index given by a fixed integer */ + index_unnumbered, /* index given by unnumbered_arg_count++ */ + index_unknown /* index is only known at run time */ + }; + + directives = 0; + numbered_arg_count = 0; + allocated = 0; + numbered = NULL; + unnumbered_arg_count = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + FDI_SET (format - 1, FMTDIR_START); + directives++; + + if (*format != '%') + { + /* A complex directive. */ + enum arg_index main_arg = index_unnumbered; + unsigned int main_number = 0; + enum format_arg_type type; + + if (isdigit (*format) || *format == ':') + { + const char *f = format; + unsigned int m = 0; + + while (isdigit (*f)) + { + m = 10 * m + (*f - '0'); + f++; + } + + if (*f == ':') + { + main_number = m; + main_arg = index_numbered; + format = ++f; + } + } + else if (*format == '*') + { + if (format[1] == ':') + { + main_arg = index_unknown; + format += 2; + } + } + + /* Parse flags. */ + if (*format == '-') + format++; + + /* Parse width. */ + if (isdigit (*format)) + { + do + format++; + while (isdigit (*format)); + } + else if (*format == '*') + { + /* Unnumbered argument of type FAT_INTEGER. */ + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = unnumbered_arg_count; + numbered[numbered_arg_count].type = FAT_INTEGER; + numbered_arg_count++; + unnumbered_arg_count++; + + format++; + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (isdigit (*format)) + { + do + format++; + while (isdigit (*format)); + } + else if (*format == '*') + { + /* Unnumbered argument of type FAT_INTEGER. */ + if (allocated == unnumbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = unnumbered_arg_count; + numbered[numbered_arg_count].type = FAT_INTEGER; + numbered_arg_count++; + unnumbered_arg_count++; + + format++; + } + else + --format; /* will jump to bad_format */ + } + + switch (c_tolower (*format)) + { + case 'd': case 'u': case 'x': + type = FAT_INTEGER; + break; + case 'e': case 'f': case 'g': case 'n': case 'm': + type = FAT_FLOAT; + break; + case 's': + type = FAT_STRING; + break; + case 'p': + type = FAT_POINTER; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + switch (main_arg) + { + case index_unnumbered: + numbered[numbered_arg_count].number = unnumbered_arg_count; + numbered[numbered_arg_count].type = type; + unnumbered_arg_count++; + break; + case index_numbered: + numbered[numbered_arg_count].number = main_number; + numbered[numbered_arg_count].type = type; + break; + case index_unknown: + numbered[numbered_arg_count].number = unnumbered_arg_count; + numbered[numbered_arg_count].type = FAT_INTEGER; + unnumbered_arg_count++; + break; + default: + abort (); + } + numbered_arg_count++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (numbered, numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < numbered_arg_count; i++) + if (j > 0 && numbered[i].number == numbered[j-1].number) + { + enum format_arg_type type1 = numbered[i].type; + enum format_arg_type type2 = numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = type1; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number); + err = true; + } + + numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + numbered[j].number = numbered[i].number; + numbered[j].type = numbered[i].type; + } + j++; + } + numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + result->directives = directives; + result->numbered_arg_count = numbered_arg_count; + result->allocated = allocated; + result->numbered = numbered; + return result; + + bad_format: + if (numbered != NULL) + free (numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_pascal = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 0; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + switch (spec->numbered[i].type) + { + case FAT_INTEGER: + printf ("i"); + break; + case FAT_FLOAT: + printf ("f"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_POINTER: + printf ("p"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-pascal.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-perl-brace.c b/gettext-tools/src/format-perl-brace.c new file mode 100644 index 0000000..3ab05ce --- /dev/null +++ b/gettext-tools/src/format-perl-brace.c @@ -0,0 +1,294 @@ +/* Perl brace format strings. + Copyright (C) 2004, 2006-2007 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include "format.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Perl brace format strings are supported by Guido Flohr's libintl-perl + package, more precisely by the __expand and __x functions therein. + A format string directive here consists of + - an opening brace '{', + - an identifier [_A-Za-z][_0-9A-Za-z]*, + - a closing brace '}'. + */ + +struct named_arg +{ + char *name; +}; + +struct spec +{ + unsigned int directives; + unsigned int named_arg_count; + unsigned int allocated; + struct named_arg *named; +}; + + +static int +named_arg_compare (const void *p1, const void *p2) +{ + return strcmp (((const struct named_arg *) p1)->name, + ((const struct named_arg *) p2)->name); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.named_arg_count = 0; + spec.allocated = 0; + spec.named = NULL; + + for (; *format != '\0';) + if (*format++ == '{') + { + const char *f = format; + char c; + + c = *f; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') + { + do + c = *++f; + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' + || (c >= '0' && c <= '9')); + if (c == '}') + { + /* A directive. */ + char *name; + const char *name_start = format; + const char *name_end = f; + size_t n = name_end - name_start; + + FDI_SET (format - 1, FMTDIR_START); + + name = XNMALLOC (n + 1, char); + memcpy (name, name_start, n); + name[n] = '\0'; + + spec.directives++; + + if (spec.allocated == spec.named_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg)); + } + spec.named[spec.named_arg_count].name = name; + spec.named_arg_count++; + + FDI_SET (f, FMTDIR_END); + + format = ++f; + } + } + } + + /* Sort the named argument array, and eliminate duplicates. */ + if (spec.named_arg_count > 1) + { + unsigned int i, j; + + qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg), + named_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + for (i = j = 0; i < spec.named_arg_count; i++) + if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0) + free (spec.named[i].name); + else + { + if (j < i) + spec.named[j].name = spec.named[i].name; + j++; + } + spec.named_arg_count = j; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->named != NULL) + { + unsigned int i; + for (i = 0; i < spec->named_arg_count; i++) + free (spec->named[i].name); + free (spec->named); + } + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->named_arg_count + spec2->named_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->named_arg_count; + unsigned int n2 = spec2->named_arg_count; + + /* Check the argument names in spec1 are contained in those of spec2. + Additional arguments in spec2 are allowed; they expand to themselves + (including the surrounding braces) at runtime. + Both arrays are sorted. We search for the differences. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + strcmp (spec1->named[i].name, spec2->named[j].name)); + + if (cmp > 0) + j++; + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"), + spec1->named[i].name, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_perl_brace = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("{"); + for (i = 0; i < spec->named_arg_count; i++) + { + if (i > 0) + printf (", "); + printf ("'%s'", spec->named[i].name); + } + printf ("}"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-perl-brace.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-perl.c b/gettext-tools/src/format-perl.c new file mode 100644 index 0000000..730d190 --- /dev/null +++ b/gettext-tools/src/format-perl.c @@ -0,0 +1,768 @@ +/* Perl format strings. + Copyright (C) 2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Perl format strings are implemented in function Perl_sv_vcatpvfn in + perl-5.8.0/sv.c. + A directive + - starts with '%' or '%m$' where m is a positive integer starting with a + nonzero digit, + - is optionally followed by any of the characters '#', '0', '-', ' ', '+', + each of which acts as a flag, + - is optionally followed by a vector specification: 'v' or '*v' (reads an + argument) or '*m$v' where m is a positive integer starting with a nonzero + digit, + - is optionally followed by a width specification: '*' (reads an argument) + or '*m$' where m is a positive integer starting with a nonzero digit or + a nonempty digit sequence starting with a nonzero digit, + - is optionally followed by '.' and a precision specification: '*' (reads + an argument) or '*m$' where m is a positive integer starting with a + nonzero digit or a digit sequence, + - is optionally followed by a size specifier, one of 'h' 'l' 'll' 'L' 'q' + 'V' 'I32' 'I64' 'I', + - is finished by a specifier + - '%', that needs no argument, + - 'c', that needs a small integer argument, + - 's', that needs a string argument, + - '_', that needs a scalar vector argument, + - 'p', that needs a pointer argument, + - 'i', 'd', 'D', that need an integer argument, + - 'u', 'U', 'b', 'o', 'O', 'x', 'X', that need an unsigned integer + argument, + - 'e', 'E', 'f', 'F', 'g', 'G', that need a floating-point argument, + - 'n', that needs a pointer to integer. + So there can be numbered argument specifications: + - '%m$' for the format string, + - '*m$v' for the vector, + - '*m$' for the width, + - '.*m$' for the precision. + Numbered and unnumbered argument specifications can be used in the same + string. The effect of '%m$' is to take argument number m, without affecting + the current argument number. The current argument number is incremented + after processing a directive with an unnumbered argument specification. + */ + +enum format_arg_type +{ + FAT_NONE = 0, + /* Basic types */ + FAT_INTEGER = 1, + FAT_DOUBLE = 2, + FAT_CHAR = 3, + FAT_STRING = 4, + FAT_SCALAR_VECTOR = 5, + FAT_POINTER = 6, + FAT_COUNT_POINTER = 7, + /* Flags */ + FAT_UNSIGNED = 1 << 3, + FAT_SIZE_SHORT = 1 << 4, + FAT_SIZE_V = 2 << 4, + FAT_SIZE_PTR = 3 << 4, + FAT_SIZE_LONG = 4 << 4, + FAT_SIZE_LONGLONG = 5 << 4, + /* Bitmasks */ + FAT_SIZE_MASK = (FAT_SIZE_SHORT | FAT_SIZE_V | FAT_SIZE_PTR + | FAT_SIZE_LONG | FAT_SIZE_LONGLONG) +}; +#ifdef __cplusplus +typedef int format_arg_type_t; +#else +typedef enum format_arg_type format_arg_type_t; +#endif + +struct numbered_arg +{ + unsigned int number; + format_arg_type_t type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + +/* Locale independent test for a nonzero decimal digit. */ +#define isnonzerodigit(c) ((unsigned int) ((c) - '1') < 9) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; + unsigned int unnumbered_arg_count; + struct spec *result; + + directives = 0; + numbered_arg_count = 0; + unnumbered_arg_count = 0; + allocated = 0; + numbered = NULL; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + unsigned int number = 0; + bool vectorize = false; + format_arg_type_t type; + format_arg_type_t size; + + FDI_SET (format - 1, FMTDIR_START); + directives++; + + if (isnonzerodigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + number = m; + format = ++f; + } + } + + /* Parse flags. */ + while (*format == ' ' || *format == '+' || *format == '-' + || *format == '#' || *format == '0') + format++; + + /* Parse vector. */ + if (*format == 'v') + { + format++; + vectorize = true; + } + else if (*format == '*') + { + const char *f = format; + + f++; + if (*f == 'v') + { + format = ++f; + vectorize = true; + + /* Unnumbered argument. */ + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = ++unnumbered_arg_count; + numbered[numbered_arg_count].type = FAT_SCALAR_VECTOR; /* or FAT_STRING? */ + numbered_arg_count++; + } + else if (isnonzerodigit (*f)) + { + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + f++; + if (*f == 'v') + { + unsigned int vector_number = m; + + format = ++f; + vectorize = true; + + /* Numbered argument. */ + /* Note: As of perl-5.8.0, this is not correctly + implemented in perl's sv.c. */ + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = vector_number; + numbered[numbered_arg_count].type = FAT_SCALAR_VECTOR; /* or FAT_STRING? */ + numbered_arg_count++; + } + } + } + } + + if (vectorize) + { + /* Numbered or unnumbered argument. */ + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = (number ? number : ++unnumbered_arg_count); + numbered[numbered_arg_count].type = FAT_SCALAR_VECTOR; + numbered_arg_count++; + } + + /* Parse width. */ + if (*format == '*') + { + unsigned int width_number = 0; + + format++; + + if (isnonzerodigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + width_number = m; + format = ++f; + } + } + + /* Numbered or unnumbered argument. */ + /* Note: As of perl-5.8.0, this is not correctly + implemented in perl's sv.c. */ + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = (width_number ? width_number : ++unnumbered_arg_count); + numbered[numbered_arg_count].type = FAT_INTEGER; + numbered_arg_count++; + } + else if (isnonzerodigit (*format)) + { + do format++; while (isdigit (*format)); + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (*format == '*') + { + unsigned int precision_number = 0; + + format++; + + if (isnonzerodigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + precision_number = m; + format = ++f; + } + } + + /* Numbered or unnumbered argument. */ + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = (precision_number ? precision_number : ++unnumbered_arg_count); + numbered[numbered_arg_count].type = FAT_INTEGER; + numbered_arg_count++; + } + else + { + while (isdigit (*format)) format++; + } + } + + /* Parse size. */ + size = 0; + if (*format == 'h') + { + size = FAT_SIZE_SHORT; + format++; + } + else if (*format == 'l') + { + if (format[1] == 'l') + { + size = FAT_SIZE_LONGLONG; + format += 2; + } + else + { + size = FAT_SIZE_LONG; + format++; + } + } + else if (*format == 'L' || *format == 'q') + { + size = FAT_SIZE_LONGLONG; + format++; + } + else if (*format == 'V') + { + size = FAT_SIZE_V; + format++; + } + else if (*format == 'I') + { + if (format[1] == '6' && format[2] == '4') + { + size = FAT_SIZE_LONGLONG; + format += 3; + } + else if (format[1] == '3' && format[2] == '2') + { + size = 0; /* FAT_SIZE_INT */ + format += 3; + } + else + { + size = FAT_SIZE_PTR; + format++; + } + } + + switch (*format) + { + case '%': + type = FAT_NONE; + break; + case 'c': + type = FAT_CHAR; + break; + case 's': + type = FAT_STRING; + break; + case '_': + type = FAT_SCALAR_VECTOR; + break; + case 'D': + type = FAT_INTEGER | FAT_SIZE_V; + break; + case 'i': case 'd': + type = FAT_INTEGER | size; + break; + case 'U': case 'O': + type = FAT_INTEGER | FAT_UNSIGNED | FAT_SIZE_V; + break; + case 'u': case 'b': case 'o': case 'x': case 'X': + type = FAT_INTEGER | FAT_UNSIGNED | size; + break; + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + if (size == FAT_SIZE_SHORT || size == FAT_SIZE_LONG) + { + *invalid_reason = + xasprintf (_("In the directive number %u, the size specifier is incompatible with the conversion specifier '%c'."), directives, *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + type = FAT_DOUBLE | size; + break; + case 'p': + type = FAT_POINTER; + break; + case 'n': + type = FAT_COUNT_POINTER | size; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (type != FAT_NONE && !vectorize) + { + /* Numbered or unnumbered argument. */ + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = (number ? number : ++unnumbered_arg_count); + numbered[numbered_arg_count].type = type; + numbered_arg_count++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (numbered, numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < numbered_arg_count; i++) + if (j > 0 && numbered[i].number == numbered[j-1].number) + { + format_arg_type_t type1 = numbered[i].type; + format_arg_type_t type2 = numbered[j-1].type; + format_arg_type_t type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number); + err = true; + } + + numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + numbered[j].number = numbered[i].number; + numbered[j].type = numbered[i].type; + } + j++; + } + numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + result->directives = directives; + result->numbered_arg_count = numbered_arg_count; + result->allocated = allocated; + result->numbered = numbered; + return result; + + bad_format: + if (numbered != NULL) + free (numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_perl = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + if (spec->numbered[i].type & FAT_UNSIGNED) + printf ("[unsigned]"); + switch (spec->numbered[i].type & FAT_SIZE_MASK) + { + case 0: + break; + case FAT_SIZE_SHORT: + printf ("[short]"); + break; + case FAT_SIZE_V: + printf ("[IV]"); + break; + case FAT_SIZE_PTR: + printf ("[PTR]"); + break; + case FAT_SIZE_LONG: + printf ("[long]"); + break; + case FAT_SIZE_LONGLONG: + printf ("[long long]"); + break; + default: + abort (); + } + switch (spec->numbered[i].type & ~(FAT_UNSIGNED | FAT_SIZE_MASK)) + { + case FAT_INTEGER: + printf ("i"); + break; + case FAT_DOUBLE: + printf ("f"); + break; + case FAT_CHAR: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_SCALAR_VECTOR: + printf ("sv"); + break; + case FAT_POINTER: + printf ("p"); + break; + case FAT_COUNT_POINTER: + printf ("n"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-perl.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-php.c b/gettext-tools/src/format-php.c new file mode 100644 index 0000000..5dd6d7f --- /dev/null +++ b/gettext-tools/src/format-php.c @@ -0,0 +1,501 @@ +/* PHP format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* PHP format strings are described in phpdoc-4.0.6, file + phpdoc/manual/function.sprintf.html, and are implemented in + php-4.1.0/ext/standard/formatted_print.c. + A directive + - starts with '%' or '%m$' where m is a positive integer, + - is optionally followed by any of the characters '0', '-', ' ', or + "'", each of which acts as a flag, + - is optionally followed by a width specification: a nonempty digit + sequence, + - is optionally followed by '.' and a precision specification: a nonempty + digit sequence, + - is optionally followed by a size specifier 'l', which is ignored, + - is finished by a specifier + - 's', that needs a string argument, + - 'b', 'd', 'u', 'o', 'x', 'X', that need an integer argument, + - 'e', 'f', that need a floating-point argument, + - 'c', that needs a character argument. + Additionally there is the directive '%%', which takes no argument. + Numbered and unnumbered argument specifications can be used in the same + string. Numbered argument specifications have no influence on the + "current argument index", that is incremented each time an argument is read. + */ + +enum format_arg_type +{ + FAT_INTEGER, + FAT_FLOAT, + FAT_CHARACTER, + FAT_STRING +}; + +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; + unsigned int unnumbered_arg_count; + struct spec *result; + + directives = 0; + numbered_arg_count = 0; + allocated = 0; + numbered = NULL; + unnumbered_arg_count = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + FDI_SET (format - 1, FMTDIR_START); + directives++; + + if (*format != '%') + { + /* A complex directive. */ + unsigned int number; + enum format_arg_type type; + + number = ++unnumbered_arg_count; + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_ARGNO_0 (directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + format = ++f; + --unnumbered_arg_count; + } + } + + /* Parse flags. */ + for (;;) + { + if (*format == '0' || *format == '-' || *format == ' ') + format++; + else if (*format == '\'') + { + format++; + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + format++; + } + else + break; + } + + /* Parse width. */ + if (isdigit (*format)) + { + do + format++; + while (isdigit (*format)); + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (isdigit (*format)) + { + do + format++; + while (isdigit (*format)); + } + else + --format; /* will jump to bad_format */ + } + + /* Parse size. */ + if (*format == 'l') + format++; + + switch (*format) + { + case 'b': case 'd': case 'u': case 'o': case 'x': case 'X': + type = FAT_INTEGER; + break; + case 'e': case 'f': + type = FAT_FLOAT; + break; + case 'c': + type = FAT_CHARACTER; + break; + case 's': + type = FAT_STRING; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (allocated == numbered_arg_count) + { + allocated = 2 * allocated + 1; + numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg)); + } + numbered[numbered_arg_count].number = number; + numbered[numbered_arg_count].type = type; + numbered_arg_count++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (numbered, numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < numbered_arg_count; i++) + if (j > 0 && numbered[i].number == numbered[j-1].number) + { + enum format_arg_type type1 = numbered[i].type; + enum format_arg_type type2 = numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = type1; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number); + err = true; + } + + numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + numbered[j].number = numbered[i].number; + numbered[j].type = numbered[i].type; + } + j++; + } + numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + result->directives = directives; + result->numbered_arg_count = numbered_arg_count; + result->allocated = allocated; + result->numbered = numbered; + return result; + + bad_format: + if (numbered != NULL) + free (numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_php = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + switch (spec->numbered[i].type) + { + case FAT_INTEGER: + printf ("i"); + break; + case FAT_FLOAT: + printf ("f"); + break; + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-php.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-python-brace.c b/gettext-tools/src/format-python-brace.c new file mode 100644 index 0000000..141d3df --- /dev/null +++ b/gettext-tools/src/format-python-brace.c @@ -0,0 +1,542 @@ +/* Python brace format strings. + Copyright (C) 2004, 2006-2007, 2013 Free Software Foundation, Inc. + Written by Daiki Ueno , 2013. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Python brace format strings are defined by PEP3101 together with + 'format' method of string class. + A format string directive here consists of + - an opening brace '{', + - an identifier [_A-Za-z][_0-9A-Za-z]*|[0-9]+, + - an optional getattr ('.') or getitem ('['..']') operator with + an identifier as argument, + - an optional format specifier starting with ':', with a + (unnested) format string as argument, + - a closing brace '}'. + Brace characters '{' and '}' can be escaped by doubles '{{' and '}}'. +*/ + +struct named_arg +{ + char *name; +}; + +struct spec +{ + unsigned int directives; + unsigned int named_arg_count; + unsigned int allocated; + struct named_arg *named; +}; + + +static bool parse_upto (struct spec *spec, const char **formatp, + bool is_toplevel, char terminator, + bool translated, char *fdi, char **invalid_reason); +static void free_named_args (struct spec *spec); + + +/* All the parse_* functions (except parse_upto) follow the same + calling convention. FORMATP shall point to the beginning of a token. + If parsing succeeds, FORMATP will point to the next character after + the token, and true is returned. Otherwise, FORMATP will be + unchanged and false is returned. */ + +static bool +parse_named_field (struct spec *spec, + const char **formatp, bool translated, char *fdi, + char **invalid_reason) +{ + const char *format = *formatp; + char c; + + c = *format; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') + { + do + c = *++format; + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' + || (c >= '0' && c <= '9')); + *formatp = format; + return true; + } + return false; +} + +static bool +parse_numeric_field (struct spec *spec, + const char **formatp, bool translated, char *fdi, + char **invalid_reason) +{ + const char *format = *formatp; + char c; + + c = *format; + if (c >= '0' && c <= '9') + { + do + c = *++format; + while (c >= '0' && c <= '9'); + *formatp = format; + return true; + } + return false; +} + +static bool +parse_directive (struct spec *spec, + const char **formatp, bool is_toplevel, + bool translated, char *fdi, char **invalid_reason) +{ + const char *format = *formatp; + const char *const format_start = format; + const char *name_start; + char c; + + c = *++format; + if (c == '{') + { + *formatp = ++format; + return true; + } + + name_start = format; + if (!parse_named_field (spec, &format, translated, fdi, invalid_reason) + && !parse_numeric_field (spec, &format, translated, fdi, invalid_reason)) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '%c' cannot start a field name."), spec->directives, *format); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + + c = *format; + if (c == '.') + { + format++; + if (!parse_named_field (spec, &format, translated, fdi, + invalid_reason)) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '%c' cannot start a getattr argument."), spec->directives, *format); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + c = *format; + } + else if (c == '[') + { + format++; + if (!parse_named_field (spec, &format, translated, fdi, + invalid_reason) + && !parse_numeric_field (spec, &format, translated, fdi, + invalid_reason)) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '%c' cannot start a getitem argument."), spec->directives, *format); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + + c = *format++; + if (c != ']') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + c = *format; + } + + if (c == ':') + { + if (!is_toplevel) + { + *invalid_reason = + xasprintf (_("In the directive number %u, no more nesting is allowed in a format specifier."), spec->directives); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + + /* Format specifiers. Although a format specifier can be any + string in theory, we can only recognize two types of format + specifiers below, because otherwise we would need to evaluate + Python expressions by ourselves: + + - A nested format directive expanding to the whole string + - The Standard Format Specifiers, as described in PEP3101, + not including a nested format directive */ + format++; + if (*format == '{') + { + /* Nested format directive. */ + if (!parse_directive (spec, &format, false, translated, fdi, + invalid_reason)) + { + /* FDI and INVALID_REASON will be set by a recursive call of + parse_directive. */ + return false; + } + + if (*format != '}') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + } + else + { + /* Standard format specifiers is in the form: + [[fill]align][sign][#][0][minimumwidth][.precision][type] */ + + /* Look ahead two characters to skip [[fill]align]. */ + int c1, c2; + + c1 = format[0]; + c2 = format[1]; + + if (c2 == '<' || c2 == '>' || c2 == '=' || c2 == '^') + format += 2; + else if (c1 == '<' || c1 == '>' || c1 == '=' || c1 == '^') + format++; + if (*format == '+' || *format == '-' || *format == ' ') + format++; + if (*format == '#') + format++; + if (*format == '0') + format++; + while (c_isdigit (*format)) + format++; + if (*format == '.') + { + format++; + while (c_isdigit (*format)) + format++; + } + switch (*format) + { + case 'b': case 'c': case 'd': case 'o': case 'x': case 'X': + case 'n': + case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': + case '%': + format++; + break; + default: + break; + } + if (*format != '}') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + } + c = *format; + } + + if (c != '}') + { + *invalid_reason = + xasprintf (_("In the directive number %u, there is an unterminated format directive."), spec->directives); + FDI_SET (format, FMTDIR_ERROR); + return false; + } + + if (is_toplevel) + { + char *name; + size_t n = format - name_start; + + FDI_SET (name_start - 1, FMTDIR_START); + + name = XNMALLOC (n + 1, char); + memcpy (name, name_start, n); + name[n] = '\0'; + + spec->directives++; + + if (spec->allocated == spec->named_arg_count) + { + spec->allocated = 2 * spec->allocated + 1; + spec->named = (struct named_arg *) xrealloc (spec->named, spec->allocated * sizeof (struct named_arg)); + } + spec->named[spec->named_arg_count].name = name; + spec->named_arg_count++; + + FDI_SET (format, FMTDIR_END); + } + + *formatp = ++format; + return true; +} + +static bool +parse_upto (struct spec *spec, + const char **formatp, bool is_toplevel, char terminator, + bool translated, char *fdi, char **invalid_reason) +{ + const char *format = *formatp; + + for (; *format != terminator && *format != '\0';) + { + if (*format == '{') + { + if (!parse_directive (spec, &format, is_toplevel, translated, fdi, + invalid_reason)) + return false; + } + else + format++; + } + + *formatp = format; + return true; +} + +static int +named_arg_compare (const void *p1, const void *p2) +{ + return strcmp (((const struct named_arg *) p1)->name, + ((const struct named_arg *) p2)->name); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.named_arg_count = 0; + spec.allocated = 0; + spec.named = NULL; + + if (!parse_upto (&spec, &format, true, '\0', translated, fdi, invalid_reason)) + { + free_named_args (&spec); + return NULL; + } + + /* Sort the named argument array, and eliminate duplicates. */ + if (spec.named_arg_count > 1) + { + unsigned int i, j; + + qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg), + named_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + for (i = j = 0; i < spec.named_arg_count; i++) + if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0) + free (spec.named[i].name); + else + { + if (j < i) + spec.named[j].name = spec.named[i].name; + j++; + } + spec.named_arg_count = j; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; +} + +static void +free_named_args (struct spec *spec) +{ + if (spec->named != NULL) + { + unsigned int i; + for (i = 0; i < spec->named_arg_count; i++) + free (spec->named[i].name); + free (spec->named); + } +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + free_named_args (spec); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->named_arg_count + spec2->named_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->named_arg_count; + unsigned int n2 = spec2->named_arg_count; + + /* Check the argument names in spec1 are contained in those of spec2. + Both arrays are sorted. We search for the differences. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + strcmp (spec1->named[i].name, spec2->named[j].name)); + + if (cmp > 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"), + spec2->named[i].name, pretty_msgid); + err = true; + break; + } + else + j++; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"), + spec1->named[i].name, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_python_brace = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("{"); + for (i = 0; i < spec->named_arg_count; i++) + { + if (i > 0) + printf (", "); + printf ("'%s'", spec->named[i].name); + } + printf ("}"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-python-brace.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-python.c b/gettext-tools/src/format-python.c new file mode 100644 index 0000000..d6a1338 --- /dev/null +++ b/gettext-tools/src/format-python.c @@ -0,0 +1,695 @@ +/* Python format strings. + Copyright (C) 2001-2004, 2006-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Python format strings are described in + Python Library reference + 2. Built-in Types, Exceptions and Functions + 2.1. Built-in Types + 2.1.5. Sequence Types + 2.1.5.2. String Formatting Operations + Any string or Unicode string can act as format string via the '%' operator, + implemented in stringobject.c and unicodeobject.c. + A directive + - starts with '%' + - is optionally followed by '(ident)' where ident is any sequence of + characters with balanced left and right parentheses, + - is optionally followed by any of the characters '-' (left justification), + '+' (sign), ' ' (blank), '#' (alt), '0' (zero), each of which acts as a + flag, + - is optionally followed by a width specification: '*' (reads an argument) + or a nonempty digit sequence, + - is optionally followed by '.' and a precision specification: '*' (reads + an argument) or a nonempty digit sequence, + - is optionally followed by a size specifier, one of 'h' 'l' 'L'. + - is finished by a specifier + - '%', that needs no argument, + - 'c', that needs a character argument, + - 's', 'r', that need a string argument (or, when a precision of 0 is + given, an argument of any type), + - 'i', 'd', 'u', 'o', 'x', 'X', that need an integer argument, + - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument. + Use of '(ident)' and use of unnamed argument specifications are exclusive, + because the first requires a mapping as argument, while the second requires + a tuple as argument. When unnamed arguments are used, the number of + arguments in the format string and the number of elements in the argument + tuple (to the right of the '%' operator) must be the same. + */ + +enum format_arg_type +{ + FAT_NONE, + FAT_ANY, + FAT_CHARACTER, + FAT_STRING, + FAT_INTEGER, + FAT_FLOAT +}; + +struct named_arg +{ + char *name; + enum format_arg_type type; +}; + +struct unnamed_arg +{ + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int named_arg_count; + unsigned int unnamed_arg_count; + unsigned int allocated; + struct named_arg *named; + struct unnamed_arg *unnamed; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +named_arg_compare (const void *p1, const void *p2) +{ + return strcmp (((const struct named_arg *) p1)->name, + ((const struct named_arg *) p2)->name); +} + +#define INVALID_MIXES_NAMED_UNNAMED() \ + xstrdup (_("The string refers to arguments both through argument names and through unnamed argument specifications.")) + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.named_arg_count = 0; + spec.unnamed_arg_count = 0; + spec.allocated = 0; + spec.named = NULL; + spec.unnamed = NULL; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + char *name = NULL; + bool zero_precision = false; + enum format_arg_type type; + + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (*format == '(') + { + unsigned int depth; + const char *name_start; + const char *name_end; + size_t n; + + name_start = ++format; + depth = 0; + for (; *format != '\0'; format++) + { + if (*format == '(') + depth++; + else if (*format == ')') + { + if (depth == 0) + break; + else + depth--; + } + } + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + name_end = format++; + + n = name_end - name_start; + name = XNMALLOC (n + 1, char); + memcpy (name, name_start, n); + name[n] = '\0'; + } + + while (*format == '-' || *format == '+' || *format == ' ' + || *format == '#' || *format == '0') + format++; + + if (*format == '*') + { + format++; + + /* Named and unnamed specifications are exclusive. */ + if (spec.named_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.unnamed_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); + } + spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER; + spec.unnamed_arg_count++; + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + + if (*format == '.') + { + format++; + + if (*format == '*') + { + format++; + + /* Named and unnamed specifications are exclusive. */ + if (spec.named_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.unnamed_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); + } + spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER; + spec.unnamed_arg_count++; + } + else if (isdigit (*format)) + { + zero_precision = true; + do + { + if (*format != '0') + zero_precision = false; + format++; + } + while (isdigit (*format)); + } + } + + if (*format == 'h' || *format == 'l' || *format == 'L') + format++; + + switch (*format) + { + case '%': + type = FAT_NONE; + break; + case 'c': + type = FAT_CHARACTER; + break; + case 's': case 'r': + type = (zero_precision ? FAT_ANY : FAT_STRING); + break; + case 'i': case 'd': case 'u': case 'o': case 'x': case 'X': + type = FAT_INTEGER; + break; + case 'e': case 'E': case 'f': case 'g': case 'G': + type = FAT_FLOAT; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (name != NULL) + { + /* Named argument. */ + + /* Named and unnamed specifications are exclusive. */ + if (spec.unnamed_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.named_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg)); + } + spec.named[spec.named_arg_count].name = name; + spec.named[spec.named_arg_count].type = type; + spec.named_arg_count++; + } + else if (*format != '%') + { + /* Unnamed argument. */ + + /* Named and unnamed specifications are exclusive. */ + if (spec.named_arg_count > 0) + { + *invalid_reason = INVALID_MIXES_NAMED_UNNAMED (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (spec.allocated == spec.unnamed_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg)); + } + spec.unnamed[spec.unnamed_arg_count].type = type; + spec.unnamed_arg_count++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the named argument array, and eliminate duplicates. */ + if (spec.named_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg), + named_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.named_arg_count; i++) + if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0) + { + enum format_arg_type type1 = spec.named[i].type; + enum format_arg_type type2 = spec.named[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2 || type2 == FAT_ANY) + type_both = type1; + else if (type1 == FAT_ANY) + type_both = type2; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + xasprintf (_("The string refers to the argument named '%s' in incompatible ways."), spec.named[i].name); + err = true; + } + + spec.named[j-1].type = type_both; + free (spec.named[i].name); + } + else + { + if (j < i) + { + spec.named[j].name = spec.named[i].name; + spec.named[j].type = spec.named[i].type; + } + j++; + } + spec.named_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.named != NULL) + { + unsigned int i; + for (i = 0; i < spec.named_arg_count; i++) + free (spec.named[i].name); + free (spec.named); + } + if (spec.unnamed != NULL) + free (spec.unnamed); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->named != NULL) + { + unsigned int i; + for (i = 0; i < spec->named_arg_count; i++) + free (spec->named[i].name); + free (spec->named); + } + if (spec->unnamed != NULL) + free (spec->unnamed); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->named_arg_count > 0 && spec2->unnamed_arg_count > 0) + { + if (error_logger) + error_logger (_("format specifications in '%s' expect a mapping, those in '%s' expect a tuple"), + pretty_msgid, pretty_msgstr); + err = true; + } + else if (spec1->unnamed_arg_count > 0 && spec2->named_arg_count > 0) + { + if (error_logger) + error_logger (_("format specifications in '%s' expect a tuple, those in '%s' expect a mapping"), + pretty_msgid, pretty_msgstr); + err = true; + } + else + { + if (spec1->named_arg_count + spec2->named_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->named_arg_count; + unsigned int n2 = spec2->named_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + strcmp (spec1->named[i].name, spec2->named[j].name)); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument '%s', as in '%s', doesn't exist in '%s'"), + spec2->named[j].name, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"), + spec1->named[i].name, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (strcmp (spec1->named[i].name, spec2->named[j].name) == 0) + { + if (!(spec1->named[i].type == spec2->named[j].type + || (!equality + && (spec1->named[i].type == FAT_ANY + || spec2->named[j].type == FAT_ANY)))) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument '%s' are not the same"), + pretty_msgid, pretty_msgstr, + spec2->named[j].name); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + if (spec1->unnamed_arg_count + spec2->unnamed_arg_count > 0) + { + unsigned int i; + + /* Check the argument types are the same. */ + if (spec1->unnamed_arg_count != spec2->unnamed_arg_count) + { + if (error_logger) + error_logger (_("number of format specifications in '%s' and '%s' does not match"), + pretty_msgid, pretty_msgstr); + err = true; + } + else + for (i = 0; i < spec2->unnamed_arg_count; i++) + if (!(spec1->unnamed[i].type == spec2->unnamed[i].type + || (!equality + && (spec1->unnamed[i].type == FAT_ANY + || spec2->unnamed[i].type == FAT_ANY)))) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, i + 1); + err = true; + } + } + } + + return err; +} + + +struct formatstring_parser formatstring_python = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +unsigned int +get_python_format_unnamed_arg_count (const char *string) +{ + /* Parse the format string. */ + char *invalid_reason = NULL; + struct spec *descr = + (struct spec *) format_parse (string, false, NULL, &invalid_reason); + + if (descr != NULL) + { + unsigned int result = descr->unnamed_arg_count; + + format_free (descr); + return result; + } + else + { + free (invalid_reason); + return 0; + } +} + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + if (spec->named_arg_count > 0) + { + if (spec->unnamed_arg_count > 0) + abort (); + + printf ("{"); + for (i = 0; i < spec->named_arg_count; i++) + { + if (i > 0) + printf (", "); + printf ("'%s':", spec->named[i].name); + switch (spec->named[i].type) + { + case FAT_ANY: + printf ("*"); + break; + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_FLOAT: + printf ("f"); + break; + default: + abort (); + } + } + printf ("}"); + } + else + { + printf ("("); + for (i = 0; i < spec->unnamed_arg_count; i++) + { + if (i > 0) + printf (" "); + switch (spec->unnamed[i].type) + { + case FAT_ANY: + printf ("*"); + break; + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_FLOAT: + printf ("f"); + break; + default: + abort (); + } + } + printf (")"); + } +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-python.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-qt-plural.c b/gettext-tools/src/format-qt-plural.c new file mode 100644 index 0000000..5301d42 --- /dev/null +++ b/gettext-tools/src/format-qt-plural.c @@ -0,0 +1,194 @@ +/* Qt plural format strings. + Copyright (C) 2003-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2009. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Qt plural format strings are processed by QObject::tr and are documented in + qt-x11-opensource-src-4.3.1/doc/html/qobject.html#tr. + A directive + - starts with '%', + - is optionally followed by 'L' (a no-op), + - is followed by 'n'. + Every directive is replaced by the numeric argument N passed to QObject::tr. + */ + +struct spec +{ + /* Number of format directives. */ + unsigned int directives; +}; + + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + const char *dir_start = format - 1; + + if (*format == 'L') + format++; + if (*format == 'n') + { + /* A directive. */ + FDI_SET (dir_start, FMTDIR_START); + spec.directives++; + FDI_SET (format, FMTDIR_END); + + format++; + } + } + + result = XMALLOC (struct spec); + *result = spec; + return result; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + /* Check the argument is used. */ + if ((spec1->directives == 0 && spec2->directives > 0) + || (equality && spec1->directives > 0 && spec2->directives == 0)) + { + if (error_logger) + error_logger (_("number of format specifications in '%s' and '%s' does not match"), + pretty_msgid, pretty_msgstr); + err = true; + } + + return err; +} + + +struct formatstring_parser formatstring_qt_plural = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + if (spec->directives > 0) + printf ("*"); + else + printf ("_"); + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-qt-plural.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-qt.c b/gettext-tools/src/format-qt.c new file mode 100644 index 0000000..3f759a2 --- /dev/null +++ b/gettext-tools/src/format-qt.c @@ -0,0 +1,266 @@ +/* Qt format strings. + Copyright (C) 2003-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Qt format strings are processed by QString::arg and are documented in + qt-4.3.0/doc/html/qstring.html. + A directive + - starts with '%', + - is optionally followed by 'L' (indicates locale-dependent processing), + - is followed by one or two digits ('0' to '9'). %0n is equivalent to %n. + An unterminated directive ('%' or '%L' not followed by a digit or at the + end) is not an error. + The first .arg() invocation replaces the %n with the lowest numbered n, + the next .arg() invocation then replaces the %n with the second-lowest + numbered n, and so on. + This is inherently buggy because a '%' in the first replacement confuses + the second .arg() invocation. + To reduce this problem and introduce another one, there are also .arg() + methods that take up to 9 strings and perform the replacements in one swoop. + But this method works only on strings that contain no 'L' flags and only + single-digit argument designators. + Although %0 is supported, usually %1 denotes the first argument, %2 the + second argument etc. */ + +struct spec +{ + /* Number of format directives. */ + unsigned int directives; + + /* True if the string supports the multi-argument .arg() methods, i.e. if it + contains no 'L' flags and only single-digit argument designators. */ + bool simple; + + /* Booleans telling which %nn was seen. */ + unsigned int arg_count; + bool args_used[100]; +}; + + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.simple = true; + spec.arg_count = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + const char *dir_start = format - 1; + bool locale_flag = false; + + if (*format == 'L') + { + locale_flag = true; + format++; + } + if (*format >= '0' && *format <= '9') + { + /* A directive. */ + unsigned int number; + + FDI_SET (dir_start, FMTDIR_START); + spec.directives++; + if (locale_flag) + spec.simple = false; + + number = *format - '0'; + if (format[1] >= '0' && format[1] <= '9') + { + number = 10 * number + (format[1] - '0'); + spec.simple = false; + format++; + } + + while (spec.arg_count <= number) + spec.args_used[spec.arg_count++] = false; + spec.args_used[number] = true; + + FDI_SET (format, FMTDIR_END); + + format++; + } + } + + result = XMALLOC (struct spec); + *result = spec; + return result; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + unsigned int i; + + if (spec1->simple && !spec2->simple) + { + if (error_logger) + error_logger (_("'%s' is a simple format string, but '%s' is not: it contains an 'L' flag or a double-digit argument number"), + pretty_msgid, pretty_msgstr); + err = true; + } + + if (!err) + for (i = 0; i < spec1->arg_count || i < spec2->arg_count; i++) + { + bool arg_used1 = (i < spec1->arg_count && spec1->args_used[i]); + bool arg_used2 = (i < spec2->arg_count && spec2->args_used[i]); + + /* The translator cannot omit a %n from the msgstr because that would + yield a "Argument missing" warning at runtime. */ + if (arg_used1 != arg_used2) + { + if (error_logger) + { + if (arg_used1) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + i, pretty_msgstr); + else + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + i, pretty_msgstr, pretty_msgid); + } + err = true; + break; + } + } + + return err; +} + + +struct formatstring_parser formatstring_qt = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + for (i = 0; i < spec->arg_count; i++) + { + if (i > 0) + printf (" "); + if (spec->args_used[i]) + printf ("*"); + else + printf ("_"); + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-qt.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-scheme.c b/gettext-tools/src/format-scheme.c new file mode 100644 index 0000000..02c6182 --- /dev/null +++ b/gettext-tools/src/format-scheme.c @@ -0,0 +1,3581 @@ +/* Scheme format strings. + Copyright (C) 2001-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "gcd.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "minmax.h" +#include "error.h" +#include "error-progname.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Assertion macros. Could be defined to empty for speed. */ +#define ASSERT(expr) if (!(expr)) abort (); +#define VERIFY_LIST(list) verify_list (list) + + +/* Scheme format strings are described in the GNU guile documentation, + section "Formatted Output". They are implemented in + guile-1.6.4/ice-9/format.scm. */ + +/* Data structure describing format string derived constraints for an + argument list. It is a recursive list structure. Structure sharing + is not allowed. */ + +enum format_cdr_type +{ + FCT_REQUIRED, /* The format argument list cannot end before this argument. */ + FCT_OPTIONAL /* The format argument list may end before this argument. */ +}; + +enum format_arg_type +{ + FAT_OBJECT, /* Any object, type T. */ + FAT_CHARACTER_INTEGER_NULL, /* Type (OR CHARACTER INTEGER NULL). */ + FAT_CHARACTER_NULL, /* Type (OR CHARACTER NULL). */ + FAT_CHARACTER, /* Type CHARACTER. */ + FAT_INTEGER_NULL, /* Type (OR INTEGER NULL). */ + FAT_INTEGER, /* Meant for objects of type INTEGER. */ + FAT_REAL, /* Meant for objects of type REAL. */ + FAT_COMPLEX, /* Meant for objects of type COMPLEX. */ + FAT_LIST, /* Meant for proper lists. */ + FAT_FORMATSTRING /* Format strings. */ +}; + +struct format_arg +{ + unsigned int repcount; /* Number of consecutive arguments this constraint + applies to. Normally 1, but unconstrained + arguments are often repeated. */ + enum format_cdr_type presence; /* Can the argument list end right before + this argument? */ + enum format_arg_type type; /* Possible values for this argument. */ + struct format_arg_list *list; /* For FAT_LIST: List elements. */ +}; + +struct segment +{ + unsigned int count; /* Number of format_arg records used. */ + unsigned int allocated; + struct format_arg *element; /* Argument constraints. */ + unsigned int length; /* Number of arguments represented by this segment. + This is the sum of all repcounts in the segment. */ +}; + +struct format_arg_list +{ + /* The constraints for the potentially infinite argument list are assumed + to become ultimately periodic. (Too complicated argument lists without + a-priori period, like + (format t "~@{~:[-~;~S~]~}" nil t 1 t 3 nil t 4) + are described by a constraint that ends in a length 1 period of + unconstrained arguments.) Such a periodic sequence can be split into + an initial segment and an endlessly repeated loop segment. + A finite sequence is represented entirely in the initial segment; the + loop segment is empty. */ + + struct segment initial; /* Initial arguments segment. */ + struct segment repeated; /* Endlessly repeated segment. */ +}; + +struct spec +{ + unsigned int directives; + struct format_arg_list *list; +}; + + +/* Parameter for a directive. */ +enum param_type +{ + PT_NIL, /* param not present */ + PT_CHARACTER, /* character */ + PT_INTEGER, /* integer */ + PT_ARGCOUNT, /* number of remaining arguments */ + PT_V /* variable taken from argument list */ +}; + +struct param +{ + enum param_type type; + int value; /* for PT_INTEGER: the value, for PT_V: the position */ +}; + + +/* Forward declaration of local functions. */ +#define union make_union +static void verify_list (const struct format_arg_list *list); +static void free_list (struct format_arg_list *list); +static struct format_arg_list * copy_list (const struct format_arg_list *list); +static bool equal_list (const struct format_arg_list *list1, + const struct format_arg_list *list2); +static struct format_arg_list * make_intersected_list + (struct format_arg_list *list1, + struct format_arg_list *list2); +static struct format_arg_list * make_intersection_with_empty_list + (struct format_arg_list *list); +static struct format_arg_list * make_union_list + (struct format_arg_list *list1, + struct format_arg_list *list2); + + +/* ======================= Verify a format_arg_list ======================= */ + +/* Verify some invariants. */ +static void +verify_element (const struct format_arg * e) +{ + ASSERT (e->repcount > 0); + if (e->type == FAT_LIST) + verify_list (e->list); +} + +/* Verify some invariants. */ +/* Memory effects: none. */ +static void +verify_list (const struct format_arg_list *list) +{ + unsigned int i; + unsigned int total_repcount; + + ASSERT (list->initial.count <= list->initial.allocated); + total_repcount = 0; + for (i = 0; i < list->initial.count; i++) + { + verify_element (&list->initial.element[i]); + total_repcount += list->initial.element[i].repcount; + } + ASSERT (total_repcount == list->initial.length); + + ASSERT (list->repeated.count <= list->repeated.allocated); + total_repcount = 0; + for (i = 0; i < list->repeated.count; i++) + { + verify_element (&list->repeated.element[i]); + total_repcount += list->repeated.element[i].repcount; + } + ASSERT (total_repcount == list->repeated.length); +} + +#define VERIFY_LIST(list) verify_list (list) + + +/* ======================== Free a format_arg_list ======================== */ + +/* Free the data belonging to an argument list element. */ +static inline void +free_element (struct format_arg *element) +{ + if (element->type == FAT_LIST) + free_list (element->list); +} + +/* Free an argument list. */ +/* Memory effects: Frees list. */ +static void +free_list (struct format_arg_list *list) +{ + unsigned int i; + + for (i = 0; i < list->initial.count; i++) + free_element (&list->initial.element[i]); + if (list->initial.element != NULL) + free (list->initial.element); + + for (i = 0; i < list->repeated.count; i++) + free_element (&list->repeated.element[i]); + if (list->repeated.element != NULL) + free (list->repeated.element); +} + + +/* ======================== Copy a format_arg_list ======================== */ + +/* Copy the data belonging to an argument list element. */ +static inline void +copy_element (struct format_arg *newelement, + const struct format_arg *oldelement) +{ + newelement->repcount = oldelement->repcount; + newelement->presence = oldelement->presence; + newelement->type = oldelement->type; + if (oldelement->type == FAT_LIST) + newelement->list = copy_list (oldelement->list); +} + +/* Copy an argument list. */ +/* Memory effects: Freshly allocated result. */ +static struct format_arg_list * +copy_list (const struct format_arg_list *list) +{ + struct format_arg_list *newlist; + unsigned int length; + unsigned int i; + + VERIFY_LIST (list); + + newlist = XMALLOC (struct format_arg_list); + + newlist->initial.count = newlist->initial.allocated = list->initial.count; + length = 0; + if (list->initial.count == 0) + newlist->initial.element = NULL; + else + { + newlist->initial.element = + XNMALLOC (newlist->initial.allocated, struct format_arg); + for (i = 0; i < list->initial.count; i++) + { + copy_element (&newlist->initial.element[i], + &list->initial.element[i]); + length += list->initial.element[i].repcount; + } + } + ASSERT (length == list->initial.length); + newlist->initial.length = length; + + newlist->repeated.count = newlist->repeated.allocated = list->repeated.count; + length = 0; + if (list->repeated.count == 0) + newlist->repeated.element = NULL; + else + { + newlist->repeated.element = + XNMALLOC (newlist->repeated.allocated, struct format_arg); + for (i = 0; i < list->repeated.count; i++) + { + copy_element (&newlist->repeated.element[i], + &list->repeated.element[i]); + length += list->repeated.element[i].repcount; + } + } + ASSERT (length == list->repeated.length); + newlist->repeated.length = length; + + VERIFY_LIST (newlist); + + return newlist; +} + + +/* ===================== Compare two format_arg_lists ===================== */ + +/* Tests whether two normalized argument constraints are equivalent, + ignoring the repcount. */ +static bool +equal_element (const struct format_arg * e1, const struct format_arg * e2) +{ + return (e1->presence == e2->presence + && e1->type == e2->type + && (e1->type == FAT_LIST ? equal_list (e1->list, e2->list) : true)); +} + +/* Tests whether two normalized argument list constraints are equivalent. */ +/* Memory effects: none. */ +static bool +equal_list (const struct format_arg_list *list1, + const struct format_arg_list *list2) +{ + unsigned int n, i; + + VERIFY_LIST (list1); + VERIFY_LIST (list2); + + n = list1->initial.count; + if (n != list2->initial.count) + return false; + for (i = 0; i < n; i++) + { + const struct format_arg * e1 = &list1->initial.element[i]; + const struct format_arg * e2 = &list2->initial.element[i]; + + if (!(e1->repcount == e2->repcount && equal_element (e1, e2))) + return false; + } + + n = list1->repeated.count; + if (n != list2->repeated.count) + return false; + for (i = 0; i < n; i++) + { + const struct format_arg * e1 = &list1->repeated.element[i]; + const struct format_arg * e2 = &list2->repeated.element[i]; + + if (!(e1->repcount == e2->repcount && equal_element (e1, e2))) + return false; + } + + return true; +} + + +/* ===================== Incremental memory allocation ===================== */ + +/* Ensure list->initial.allocated >= newcount. */ +static inline void +ensure_initial_alloc (struct format_arg_list *list, unsigned int newcount) +{ + if (newcount > list->initial.allocated) + { + list->initial.allocated = + MAX (2 * list->initial.allocated + 1, newcount); + list->initial.element = + (struct format_arg *) + xrealloc (list->initial.element, + list->initial.allocated * sizeof (struct format_arg)); + } +} + +/* Ensure list->initial.allocated > list->initial.count. */ +static inline void +grow_initial_alloc (struct format_arg_list *list) +{ + if (list->initial.count >= list->initial.allocated) + { + list->initial.allocated = + MAX (2 * list->initial.allocated + 1, list->initial.count + 1); + list->initial.element = + (struct format_arg *) + xrealloc (list->initial.element, + list->initial.allocated * sizeof (struct format_arg)); + } +} + +/* Ensure list->repeated.allocated >= newcount. */ +static inline void +ensure_repeated_alloc (struct format_arg_list *list, unsigned int newcount) +{ + if (newcount > list->repeated.allocated) + { + list->repeated.allocated = + MAX (2 * list->repeated.allocated + 1, newcount); + list->repeated.element = + (struct format_arg *) + xrealloc (list->repeated.element, + list->repeated.allocated * sizeof (struct format_arg)); + } +} + +/* Ensure list->repeated.allocated > list->repeated.count. */ +static inline void +grow_repeated_alloc (struct format_arg_list *list) +{ + if (list->repeated.count >= list->repeated.allocated) + { + list->repeated.allocated = + MAX (2 * list->repeated.allocated + 1, list->repeated.count + 1); + list->repeated.element = + (struct format_arg *) + xrealloc (list->repeated.element, + list->repeated.allocated * sizeof (struct format_arg)); + } +} + + +/* ====================== Normalize a format_arg_list ====================== */ + +/* Normalize an argument list constraint, assuming all sublists are already + normalized. */ +/* Memory effects: Destructively modifies list. */ +static void +normalize_outermost_list (struct format_arg_list *list) +{ + unsigned int n, i, j; + + /* Step 1: Combine adjacent elements. + Copy from i to j, keeping 0 <= j <= i. */ + + n = list->initial.count; + for (i = j = 0; i < n; i++) + if (j > 0 + && equal_element (&list->initial.element[i], + &list->initial.element[j-1])) + { + list->initial.element[j-1].repcount += + list->initial.element[i].repcount; + free_element (&list->initial.element[i]); + } + else + { + if (j < i) + list->initial.element[j] = list->initial.element[i]; + j++; + } + list->initial.count = j; + + n = list->repeated.count; + for (i = j = 0; i < n; i++) + if (j > 0 + && equal_element (&list->repeated.element[i], + &list->repeated.element[j-1])) + { + list->repeated.element[j-1].repcount += + list->repeated.element[i].repcount; + free_element (&list->repeated.element[i]); + } + else + { + if (j < i) + list->repeated.element[j] = list->repeated.element[i]; + j++; + } + list->repeated.count = j; + + /* Nothing more to be done if the loop segment is empty. */ + if (list->repeated.count > 0) + { + unsigned int m, repcount0_extra; + + /* Step 2: Reduce the loop period. */ + n = list->repeated.count; + repcount0_extra = 0; + if (n > 1 + && equal_element (&list->repeated.element[0], + &list->repeated.element[n-1])) + { + repcount0_extra = list->repeated.element[n-1].repcount; + n--; + } + /* Proceed as if the loop period were n, with + list->repeated.element[0].repcount incremented by repcount0_extra. */ + for (m = 2; m <= n / 2; n++) + if ((n % m) == 0) + { + /* m is a divisor of n. Try to reduce the loop period to n. */ + bool ok = true; + + for (i = 0; i < n - m; i++) + if (!((list->repeated.element[i].repcount + + (i == 0 ? repcount0_extra : 0) + == list->repeated.element[i+m].repcount) + && equal_element (&list->repeated.element[i], + &list->repeated.element[i+m]))) + { + ok = false; + break; + } + if (ok) + { + for (i = m; i < n; i++) + free_element (&list->repeated.element[i]); + if (n < list->repeated.count) + list->repeated.element[m] = list->repeated.element[n]; + list->repeated.count = list->repeated.count - n + m; + list->repeated.length /= n / m; + break; + } + } + + /* Step 3: Roll as much as possible of the initial segment's tail + into the loop. */ + if (list->repeated.count == 1) + { + if (list->initial.count > 0 + && equal_element (&list->initial.element[list->initial.count-1], + &list->repeated.element[0])) + { + /* Roll the last element of the initial segment into the loop. + Its repcount is irrelevant. The second-to-last element is + certainly different and doesn't need to be considered. */ + list->initial.length -= + list->initial.element[list->initial.count-1].repcount; + list->initial.count--; + } + } + else + { + while (list->initial.count > 0 + && equal_element (&list->initial.element[list->initial.count-1], + &list->repeated.element[list->repeated.count-1])) + { + unsigned int moved_repcount = + MIN (list->initial.element[list->initial.count-1].repcount, + list->repeated.element[list->repeated.count-1].repcount); + + /* Add the element at the start of list->repeated. */ + if (equal_element (&list->repeated.element[0], + &list->repeated.element[list->repeated.count-1])) + list->repeated.element[0].repcount += moved_repcount; + else + { + unsigned int newcount = list->repeated.count + 1; + ensure_repeated_alloc (list, newcount); + for (i = newcount - 1; i > 0; i--) + list->repeated.element[i] = list->repeated.element[i-1]; + list->repeated.count = newcount; + copy_element (&list->repeated.element[0], + &list->repeated.element[list->repeated.count-1]); + list->repeated.element[0].repcount = moved_repcount; + } + + /* Remove the element from the end of list->repeated. */ + list->repeated.element[list->repeated.count-1].repcount -= + moved_repcount; + if (list->repeated.element[list->repeated.count-1].repcount == 0) + { + free_element (&list->repeated.element[list->repeated.count-1]); + list->repeated.count--; + } + + /* Remove the element from the end of list->initial. */ + list->initial.element[list->initial.count-1].repcount -= + moved_repcount; + if (list->initial.element[list->initial.count-1].repcount == 0) + { + free_element (&list->initial.element[list->initial.count-1]); + list->initial.count--; + } + list->initial.length -= moved_repcount; + } + } + } +} + +/* Normalize an argument list constraint. */ +/* Memory effects: Destructively modifies list. */ +static void +normalize_list (struct format_arg_list *list) +{ + unsigned int n, i; + + VERIFY_LIST (list); + + /* First normalize all elements, recursively. */ + n = list->initial.count; + for (i = 0; i < n; i++) + if (list->initial.element[i].type == FAT_LIST) + normalize_list (list->initial.element[i].list); + n = list->repeated.count; + for (i = 0; i < n; i++) + if (list->repeated.element[i].type == FAT_LIST) + normalize_list (list->repeated.element[i].list); + + /* Then normalize the top level list. */ + normalize_outermost_list (list); + + VERIFY_LIST (list); +} + + +/* ===================== Unconstrained and empty lists ===================== */ + +/* It's easier to allocate these on demand, than to be careful not to + accidentally modify statically allocated lists. */ + + +/* Create an unconstrained argument list. */ +/* Memory effects: Freshly allocated result. */ +static struct format_arg_list * +make_unconstrained_list () +{ + struct format_arg_list *list; + + list = XMALLOC (struct format_arg_list); + list->initial.count = 0; + list->initial.allocated = 0; + list->initial.element = NULL; + list->initial.length = 0; + list->repeated.count = 1; + list->repeated.allocated = 1; + list->repeated.element = XNMALLOC (1, struct format_arg); + list->repeated.element[0].repcount = 1; + list->repeated.element[0].presence = FCT_OPTIONAL; + list->repeated.element[0].type = FAT_OBJECT; + list->repeated.length = 1; + + VERIFY_LIST (list); + + return list; +} + + +/* Create an empty argument list. */ +/* Memory effects: Freshly allocated result. */ +static struct format_arg_list * +make_empty_list () +{ + struct format_arg_list *list; + + list = XMALLOC (struct format_arg_list); + list->initial.count = 0; + list->initial.allocated = 0; + list->initial.element = NULL; + list->initial.length = 0; + list->repeated.count = 0; + list->repeated.allocated = 0; + list->repeated.element = NULL; + list->repeated.length = 0; + + VERIFY_LIST (list); + + return list; +} + + +/* Test for an empty list. */ +/* Memory effects: none. */ +static bool +is_empty_list (const struct format_arg_list *list) +{ + return (list->initial.count == 0 && list->repeated.count == 0); +} + + +/* ======================== format_arg_list surgery ======================== */ + +/* Unfold list->repeated m times, where m >= 1. + Assumes list->repeated.count > 0. */ +/* Memory effects: list is destructively modified. */ +static void +unfold_loop (struct format_arg_list *list, unsigned int m) +{ + unsigned int i, j, k; + + if (m > 1) + { + unsigned int newcount = list->repeated.count * m; + ensure_repeated_alloc (list, newcount); + i = list->repeated.count; + for (k = 1; k < m; k++) + for (j = 0; j < list->repeated.count; j++, i++) + copy_element (&list->repeated.element[i], &list->repeated.element[j]); + list->repeated.count = newcount; + list->repeated.length = list->repeated.length * m; + } +} + +/* Ensure list->initial.length := m, where m >= list->initial.length. + Assumes list->repeated.count > 0. */ +/* Memory effects: list is destructively modified. */ +static void +rotate_loop (struct format_arg_list *list, unsigned int m) +{ + if (m == list->initial.length) + return; + + if (list->repeated.count == 1) + { + /* Instead of multiple copies of list->repeated.element[0], a single + copy with higher repcount is appended to list->initial. */ + unsigned int i, newcount; + + newcount = list->initial.count + 1; + ensure_initial_alloc (list, newcount); + i = list->initial.count; + copy_element (&list->initial.element[i], &list->repeated.element[0]); + list->initial.element[i].repcount = m - list->initial.length; + list->initial.count = newcount; + list->initial.length = m; + } + else + { + unsigned int n = list->repeated.length; + + /* Write m = list->initial.length + q * n + r with 0 <= r < n. */ + unsigned int q = (m - list->initial.length) / n; + unsigned int r = (m - list->initial.length) % n; + + /* Determine how many entries of list->repeated are needed for + length r. */ + unsigned int s; + unsigned int t; + + for (t = r, s = 0; + s < list->repeated.count && t >= list->repeated.element[s].repcount; + t -= list->repeated.element[s].repcount, s++) + ; + + /* s must be < list->repeated.count, otherwise r would have been >= n. */ + ASSERT (s < list->repeated.count); + + /* So we need to add to list->initial: + q full copies of list->repeated, + plus the s first elements of list->repeated, + plus, if t > 0, a splitoff of list->repeated.element[s]. */ + { + unsigned int i, j, k, newcount; + + i = list->initial.count; + newcount = i + q * list->repeated.count + s + (t > 0 ? 1 : 0); + ensure_initial_alloc (list, newcount); + for (k = 0; k < q; k++) + for (j = 0; j < list->repeated.count; j++, i++) + copy_element (&list->initial.element[i], + &list->repeated.element[j]); + for (j = 0; j < s; j++, i++) + copy_element (&list->initial.element[i], &list->repeated.element[j]); + if (t > 0) + { + copy_element (&list->initial.element[i], + &list->repeated.element[j]); + list->initial.element[i].repcount = t; + i++; + } + ASSERT (i == newcount); + list->initial.count = newcount; + /* The new length of the initial segment is + = list->initial.length + + q * list->repeated.length + + list->repeated[0..s-1].repcount + t + = list->initial.length + q * n + r + = m. + */ + list->initial.length = m; + } + + /* And rotate list->repeated. */ + if (r > 0) + { + unsigned int i, j, oldcount, newcount; + struct format_arg *newelement; + + oldcount = list->repeated.count; + newcount = list->repeated.count + (t > 0 ? 1 : 0); + newelement = XNMALLOC (newcount, struct format_arg); + i = 0; + for (j = s; j < oldcount; j++, i++) + newelement[i] = list->repeated.element[j]; + for (j = 0; j < s; j++, i++) + newelement[i] = list->repeated.element[j]; + if (t > 0) + { + copy_element (&newelement[oldcount], &newelement[0]); + newelement[0].repcount -= t; + newelement[oldcount].repcount = t; + } + free (list->repeated.element); + list->repeated.element = newelement; + } + } +} + + +/* Ensure index n in the initial segment falls on a split between elements, + i.e. if 0 < n < list->initial.length, then n-1 and n are covered by two + different adjacent elements. */ +/* Memory effects: list is destructively modified. */ +static unsigned int +initial_splitelement (struct format_arg_list *list, unsigned int n) +{ + unsigned int s; + unsigned int t; + unsigned int oldrepcount; + unsigned int newcount; + unsigned int i; + + VERIFY_LIST (list); + + if (n > list->initial.length) + { + ASSERT (list->repeated.count > 0); + rotate_loop (list, n); + ASSERT (n <= list->initial.length); + } + + /* Determine how many entries of list->initial need to be skipped. */ + for (t = n, s = 0; + s < list->initial.count && t >= list->initial.element[s].repcount; + t -= list->initial.element[s].repcount, s++) + ; + + if (t == 0) + return s; + + ASSERT (s < list->initial.count); + + /* Split the entry into two entries. */ + oldrepcount = list->initial.element[s].repcount; + newcount = list->initial.count + 1; + ensure_initial_alloc (list, newcount); + for (i = list->initial.count - 1; i > s; i--) + list->initial.element[i+1] = list->initial.element[i]; + copy_element (&list->initial.element[s+1], &list->initial.element[s]); + list->initial.element[s].repcount = t; + list->initial.element[s+1].repcount = oldrepcount - t; + list->initial.count = newcount; + + VERIFY_LIST (list); + + return s+1; +} + + +/* Ensure index n in the initial segment is not shared. Return its index. */ +/* Memory effects: list is destructively modified. */ +static unsigned int +initial_unshare (struct format_arg_list *list, unsigned int n) +{ + /* This does the same side effects as + initial_splitelement (list, n); + initial_splitelement (list, n + 1); + */ + unsigned int s; + unsigned int t; + + VERIFY_LIST (list); + + if (n >= list->initial.length) + { + ASSERT (list->repeated.count > 0); + rotate_loop (list, n + 1); + ASSERT (n < list->initial.length); + } + + /* Determine how many entries of list->initial need to be skipped. */ + for (t = n, s = 0; + s < list->initial.count && t >= list->initial.element[s].repcount; + t -= list->initial.element[s].repcount, s++) + ; + + /* s must be < list->initial.count. */ + ASSERT (s < list->initial.count); + + if (list->initial.element[s].repcount > 1) + { + /* Split the entry into at most three entries: for indices < n, + for index n, and for indices > n. */ + unsigned int oldrepcount = list->initial.element[s].repcount; + unsigned int newcount = + list->initial.count + (t == 0 || t == oldrepcount - 1 ? 1 : 2); + ensure_initial_alloc (list, newcount); + if (t == 0 || t == oldrepcount - 1) + { + unsigned int i; + + for (i = list->initial.count - 1; i > s; i--) + list->initial.element[i+1] = list->initial.element[i]; + copy_element (&list->initial.element[s+1], &list->initial.element[s]); + if (t == 0) + { + list->initial.element[s].repcount = 1; + list->initial.element[s+1].repcount = oldrepcount - 1; + } + else + { + list->initial.element[s].repcount = oldrepcount - 1; + list->initial.element[s+1].repcount = 1; + } + } + else + { + unsigned int i; + + for (i = list->initial.count - 1; i > s; i--) + list->initial.element[i+2] = list->initial.element[i]; + copy_element (&list->initial.element[s+2], &list->initial.element[s]); + copy_element (&list->initial.element[s+1], &list->initial.element[s]); + list->initial.element[s].repcount = t; + list->initial.element[s+1].repcount = 1; + list->initial.element[s+2].repcount = oldrepcount - 1 - t; + } + list->initial.count = newcount; + if (t > 0) + s++; + } + + /* Now the entry for index n has repcount 1. */ + ASSERT (list->initial.element[s].repcount == 1); + + VERIFY_LIST (list); + + return s; +} + + +/* Add n unconstrained elements at the front of the list. */ +/* Memory effects: list is destructively modified. */ +static void +shift_list (struct format_arg_list *list, unsigned int n) +{ + VERIFY_LIST (list); + + if (n > 0) + { + unsigned int i; + + grow_initial_alloc (list); + for (i = list->initial.count; i > 0; i--) + list->initial.element[i] = list->initial.element[i-1]; + list->initial.element[0].repcount = n; + list->initial.element[0].presence = FCT_REQUIRED; + list->initial.element[0].type = FAT_OBJECT; + list->initial.count++; + list->initial.length += n; + + normalize_outermost_list (list); + } + + VERIFY_LIST (list); +} + + +/* ================= Intersection of two format_arg_lists ================= */ + +/* Create the intersection (i.e. combined constraints) of two argument + constraints. Return false if the intersection is empty, i.e. if the + two constraints give a contradiction. */ +/* Memory effects: Freshly allocated element's sublist. */ +static bool +make_intersected_element (struct format_arg *re, + const struct format_arg * e1, + const struct format_arg * e2) +{ + /* Intersect the cdr types. */ + if (e1->presence == FCT_REQUIRED || e2->presence == FCT_REQUIRED) + re->presence = FCT_REQUIRED; + else + re->presence = FCT_OPTIONAL; + + /* Intersect the arg types. */ + if (e1->type == FAT_OBJECT) + { + re->type = e2->type; + if (re->type == FAT_LIST) + re->list = copy_list (e2->list); + } + else if (e2->type == FAT_OBJECT) + { + re->type = e1->type; + if (re->type == FAT_LIST) + re->list = copy_list (e1->list); + } + else if (e1->type == FAT_LIST + && (e2->type == FAT_CHARACTER_INTEGER_NULL + || e2->type == FAT_CHARACTER_NULL + || e2->type == FAT_INTEGER_NULL)) + { + re->type = e1->type; + re->list = make_intersection_with_empty_list (e1->list); + if (re->list == NULL) + return false; + } + else if (e2->type == FAT_LIST + && (e1->type == FAT_CHARACTER_INTEGER_NULL + || e1->type == FAT_CHARACTER_NULL + || e1->type == FAT_INTEGER_NULL)) + { + re->type = e2->type; + re->list = make_intersection_with_empty_list (e2->list); + if (re->list == NULL) + return false; + } + else if (e1->type == FAT_CHARACTER_INTEGER_NULL + && (e2->type == FAT_CHARACTER_NULL || e2->type == FAT_CHARACTER + || e2->type == FAT_INTEGER_NULL || e2->type == FAT_INTEGER)) + { + re->type = e2->type; + } + else if (e2->type == FAT_CHARACTER_INTEGER_NULL + && (e1->type == FAT_CHARACTER_NULL || e1->type == FAT_CHARACTER + || e1->type == FAT_INTEGER_NULL || e1->type == FAT_INTEGER)) + { + re->type = e1->type; + } + else if (e1->type == FAT_CHARACTER_NULL && e2->type == FAT_CHARACTER) + { + re->type = e2->type; + } + else if (e2->type == FAT_CHARACTER_NULL && e1->type == FAT_CHARACTER) + { + re->type = e1->type; + } + else if (e1->type == FAT_INTEGER_NULL && e2->type == FAT_INTEGER) + { + re->type = e2->type; + } + else if (e2->type == FAT_INTEGER_NULL && e1->type == FAT_INTEGER) + { + re->type = e1->type; + } + else if (e1->type == FAT_REAL && e2->type == FAT_INTEGER) + { + re->type = e2->type; + } + else if (e2->type == FAT_REAL && e1->type == FAT_INTEGER) + { + re->type = e1->type; + } + else if (e1->type == FAT_COMPLEX + && (e2->type == FAT_REAL || e2->type == FAT_INTEGER)) + { + re->type = e2->type; + } + else if (e2->type == FAT_COMPLEX + && (e1->type == FAT_REAL || e1->type == FAT_INTEGER)) + { + re->type = e1->type; + } + else if (e1->type == e2->type) + { + re->type = e1->type; + if (re->type == FAT_LIST) + { + re->list = make_intersected_list (copy_list (e1->list), + copy_list (e2->list)); + if (re->list == NULL) + return false; + } + } + else + /* Each of FAT_CHARACTER, FAT_INTEGER, FAT_LIST, FAT_FORMATSTRING + matches only itself. Contradiction. */ + return false; + + return true; +} + +/* Append list->repeated to list->initial, and clear list->repeated. */ +/* Memory effects: list is destructively modified. */ +static void +append_repeated_to_initial (struct format_arg_list *list) +{ + if (list->repeated.count > 0) + { + /* Move list->repeated over to list->initial. */ + unsigned int i, j, newcount; + + newcount = list->initial.count + list->repeated.count; + ensure_initial_alloc (list, newcount); + i = list->initial.count; + for (j = 0; j < list->repeated.count; j++, i++) + list->initial.element[i] = list->repeated.element[j]; + list->initial.count = newcount; + list->initial.length = list->initial.length + list->repeated.length; + free (list->repeated.element); + list->repeated.element = NULL; + list->repeated.allocated = 0; + list->repeated.count = 0; + list->repeated.length = 0; + } +} + +/* Handle a contradiction during building of a format_arg_list. + The list consists only of an initial segment. The repeated segment is + empty. This function searches the last FCT_OPTIONAL and cuts off the + list at this point, or - if none is found - returns NULL. */ +/* Memory effects: list is destructively modified. If NULL is returned, + list is freed. */ +static struct format_arg_list * +backtrack_in_initial (struct format_arg_list *list) +{ + ASSERT (list->repeated.count == 0); + + while (list->initial.count > 0) + { + unsigned int i = list->initial.count - 1; + if (list->initial.element[i].presence == FCT_REQUIRED) + { + /* Throw away this element. */ + list->initial.length -= list->initial.element[i].repcount; + free_element (&list->initial.element[i]); + list->initial.count = i; + } + else /* list->initial.element[i].presence == FCT_OPTIONAL */ + { + /* The list must end here. */ + list->initial.length--; + if (list->initial.element[i].repcount > 1) + list->initial.element[i].repcount--; + else + { + free_element (&list->initial.element[i]); + list->initial.count = i; + } + VERIFY_LIST (list); + return list; + } + } + + free_list (list); + return NULL; +} + +/* Create the intersection (i.e. combined constraints) of two argument list + constraints. Free both argument lists when done. Return NULL if the + intersection is empty, i.e. if the two constraints give a contradiction. */ +/* Memory effects: list1 and list2 are freed. The result, if non-NULL, is + freshly allocated. */ +static struct format_arg_list * +make_intersected_list (struct format_arg_list *list1, + struct format_arg_list *list2) +{ + struct format_arg_list *result; + + VERIFY_LIST (list1); + VERIFY_LIST (list2); + + if (list1->repeated.length > 0 && list2->repeated.length > 0) + /* Step 1: Ensure list1->repeated.length == list2->repeated.length. */ + { + unsigned int n1 = list1->repeated.length; + unsigned int n2 = list2->repeated.length; + unsigned int g = gcd (n1, n2); + unsigned int m1 = n2 / g; /* = lcm(n1,n2) / n1 */ + unsigned int m2 = n1 / g; /* = lcm(n1,n2) / n2 */ + + unfold_loop (list1, m1); + unfold_loop (list2, m2); + /* Now list1->repeated.length = list2->repeated.length = lcm(n1,n2). */ + } + + if (list1->repeated.length > 0 || list2->repeated.length > 0) + /* Step 2: Ensure the initial segment of the result can be computed + from the initial segments of list1 and list2. If both have a + repeated segment, this means to ensure + list1->initial.length == list2->initial.length. */ + { + unsigned int m = MAX (list1->initial.length, list2->initial.length); + + if (list1->repeated.length > 0) + rotate_loop (list1, m); + if (list2->repeated.length > 0) + rotate_loop (list2, m); + } + + if (list1->repeated.length > 0 && list2->repeated.length > 0) + { + ASSERT (list1->initial.length == list2->initial.length); + ASSERT (list1->repeated.length == list2->repeated.length); + } + + /* Step 3: Allocate the result. */ + result = XMALLOC (struct format_arg_list); + result->initial.count = 0; + result->initial.allocated = 0; + result->initial.element = NULL; + result->initial.length = 0; + result->repeated.count = 0; + result->repeated.allocated = 0; + result->repeated.element = NULL; + result->repeated.length = 0; + + /* Step 4: Elementwise intersection of list1->initial, list2->initial. */ + { + struct format_arg *e1; + struct format_arg *e2; + unsigned int c1; + unsigned int c2; + + e1 = list1->initial.element; c1 = list1->initial.count; + e2 = list2->initial.element; c2 = list2->initial.count; + while (c1 > 0 && c2 > 0) + { + struct format_arg *re; + + /* Ensure room in result->initial. */ + grow_initial_alloc (result); + re = &result->initial.element[result->initial.count]; + re->repcount = MIN (e1->repcount, e2->repcount); + + /* Intersect the argument types. */ + if (!make_intersected_element (re, e1, e2)) + { + /* If re->presence == FCT_OPTIONAL, the result list ends here. */ + if (re->presence == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + goto done; + } + + result->initial.count++; + result->initial.length += re->repcount; + + e1->repcount -= re->repcount; + if (e1->repcount == 0) + { + e1++; + c1--; + } + e2->repcount -= re->repcount; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + + if (list1->repeated.count == 0 && list2->repeated.count == 0) + { + /* Intersecting two finite lists. */ + if (c1 > 0) + { + /* list1 longer than list2. */ + if (e1->presence == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + } + else if (c2 > 0) + { + /* list2 longer than list1. */ + if (e2->presence == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + } + goto done; + } + else if (list1->repeated.count == 0) + { + /* Intersecting a finite and an infinite list. */ + ASSERT (c1 == 0); + if ((c2 > 0 ? e2->presence : list2->repeated.element[0].presence) + == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + goto done; + } + else if (list2->repeated.count == 0) + { + /* Intersecting an infinite and a finite list. */ + ASSERT (c2 == 0); + if ((c1 > 0 ? e1->presence : list1->repeated.element[0].presence) + == FCT_REQUIRED) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + goto done; + } + /* Intersecting two infinite lists. */ + ASSERT (c1 == 0 && c2 == 0); + } + + /* Step 5: Elementwise intersection of list1->repeated, list2->repeated. */ + { + struct format_arg *e1; + struct format_arg *e2; + unsigned int c1; + unsigned int c2; + + e1 = list1->repeated.element; c1 = list1->repeated.count; + e2 = list2->repeated.element; c2 = list2->repeated.count; + while (c1 > 0 && c2 > 0) + { + struct format_arg *re; + + /* Ensure room in result->repeated. */ + grow_repeated_alloc (result); + re = &result->repeated.element[result->repeated.count]; + re->repcount = MIN (e1->repcount, e2->repcount); + + /* Intersect the argument types. */ + if (!make_intersected_element (re, e1, e2)) + { + bool re_is_required = re->presence == FCT_REQUIRED; + + append_repeated_to_initial (result); + + /* If re->presence == FCT_OPTIONAL, the result list ends here. */ + if (re_is_required) + /* Contradiction. Backtrack. */ + result = backtrack_in_initial (result); + + goto done; + } + + result->repeated.count++; + result->repeated.length += re->repcount; + + e1->repcount -= re->repcount; + if (e1->repcount == 0) + { + e1++; + c1--; + } + e2->repcount -= re->repcount; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + ASSERT (c1 == 0 && c2 == 0); + } + + done: + free_list (list1); + free_list (list2); + if (result != NULL) + { + /* Undo the loop unfolding and unrolling done above. */ + normalize_outermost_list (result); + VERIFY_LIST (result); + } + return result; +} + + +/* Create the intersection of an argument list and the empty list. + Return NULL if the intersection is empty. */ +/* Memory effects: The result, if non-NULL, is freshly allocated. */ +static struct format_arg_list * +make_intersection_with_empty_list (struct format_arg_list *list) +{ +#if 0 /* equivalent but slower */ + return make_intersected_list (copy_list (list), make_empty_list ()); +#else + if (list->initial.count > 0 + ? list->initial.element[0].presence == FCT_REQUIRED + : list->repeated.count > 0 + && list->repeated.element[0].presence == FCT_REQUIRED) + return NULL; + else + return make_empty_list (); +#endif +} + + +#ifdef unused +/* Create the intersection of two argument list constraints. NULL stands + for an impossible situation, i.e. a contradiction. */ +/* Memory effects: list1 and list2 are freed if non-NULL. The result, + if non-NULL, is freshly allocated. */ +static struct format_arg_list * +intersection (struct format_arg_list *list1, struct format_arg_list *list2) +{ + if (list1 != NULL) + { + if (list2 != NULL) + return make_intersected_list (list1, list2); + else + { + free_list (list1); + return NULL; + } + } + else + { + if (list2 != NULL) + { + free_list (list2); + return NULL; + } + else + return NULL; + } +} +#endif + + +/* ===================== Union of two format_arg_lists ===================== */ + +/* Create the union (i.e. alternative constraints) of two argument + constraints. */ +static void +make_union_element (struct format_arg *re, + const struct format_arg * e1, + const struct format_arg * e2) +{ + /* Union of the cdr types. */ + if (e1->presence == FCT_REQUIRED && e2->presence == FCT_REQUIRED) + re->presence = FCT_REQUIRED; + else /* Either one of them is FCT_OPTIONAL. */ + re->presence = FCT_OPTIONAL; + + /* Union of the arg types. */ + if (e1->type == e2->type) + { + re->type = e1->type; + if (re->type == FAT_LIST) + re->list = make_union_list (copy_list (e1->list), + copy_list (e2->list)); + } + else if (e1->type == FAT_CHARACTER_INTEGER_NULL + && (e2->type == FAT_CHARACTER_NULL || e2->type == FAT_CHARACTER + || e2->type == FAT_INTEGER_NULL || e2->type == FAT_INTEGER)) + { + re->type = e1->type; + } + else if (e2->type == FAT_CHARACTER_INTEGER_NULL + && (e1->type == FAT_CHARACTER_NULL || e1->type == FAT_CHARACTER + || e1->type == FAT_INTEGER_NULL || e1->type == FAT_INTEGER)) + { + re->type = e2->type; + } + else if (e1->type == FAT_CHARACTER_NULL && e2->type == FAT_CHARACTER) + { + re->type = e1->type; + } + else if (e2->type == FAT_CHARACTER_NULL && e1->type == FAT_CHARACTER) + { + re->type = e2->type; + } + else if (e1->type == FAT_INTEGER_NULL && e2->type == FAT_INTEGER) + { + re->type = e1->type; + } + else if (e2->type == FAT_INTEGER_NULL && e1->type == FAT_INTEGER) + { + re->type = e2->type; + } + else if (e1->type == FAT_REAL && e2->type == FAT_INTEGER) + { + re->type = e1->type; + } + else if (e2->type == FAT_REAL && e1->type == FAT_INTEGER) + { + re->type = e2->type; + } + else if (e1->type == FAT_COMPLEX + && (e2->type == FAT_REAL || e2->type == FAT_INTEGER)) + { + re->type = e1->type; + } + else if (e2->type == FAT_COMPLEX + && (e1->type == FAT_REAL || e1->type == FAT_INTEGER)) + { + re->type = e2->type; + } + else if (e1->type == FAT_LIST && is_empty_list (e1->list)) + { + if (e2->type == FAT_CHARACTER_INTEGER_NULL + || e2->type == FAT_CHARACTER_NULL + || e2->type == FAT_INTEGER_NULL) + re->type = e2->type; + else if (e2->type == FAT_CHARACTER) + re->type = FAT_CHARACTER_NULL; + else if (e2->type == FAT_INTEGER) + re->type = FAT_INTEGER_NULL; + else + re->type = FAT_OBJECT; + } + else if (e2->type == FAT_LIST && is_empty_list (e2->list)) + { + if (e1->type == FAT_CHARACTER_INTEGER_NULL + || e1->type == FAT_CHARACTER_NULL + || e1->type == FAT_INTEGER_NULL) + re->type = e1->type; + else if (e1->type == FAT_CHARACTER) + re->type = FAT_CHARACTER_NULL; + else if (e1->type == FAT_INTEGER) + re->type = FAT_INTEGER_NULL; + else + re->type = FAT_OBJECT; + } + else if ((e1->type == FAT_CHARACTER || e1->type == FAT_CHARACTER_NULL) + && (e2->type == FAT_INTEGER || e2->type == FAT_INTEGER_NULL)) + { + re->type = FAT_CHARACTER_INTEGER_NULL; + } + else if ((e2->type == FAT_CHARACTER || e2->type == FAT_CHARACTER_NULL) + && (e1->type == FAT_INTEGER || e1->type == FAT_INTEGER_NULL)) + { + re->type = FAT_CHARACTER_INTEGER_NULL; + } + else + { + /* Other union types are too hard to describe precisely. */ + re->type = FAT_OBJECT; + } +} + +/* Create the union (i.e. alternative constraints) of two argument list + constraints. Free both argument lists when done. */ +/* Memory effects: list1 and list2 are freed. The result is freshly + allocated. */ +static struct format_arg_list * +make_union_list (struct format_arg_list *list1, struct format_arg_list *list2) +{ + struct format_arg_list *result; + + VERIFY_LIST (list1); + VERIFY_LIST (list2); + + if (list1->repeated.length > 0 && list2->repeated.length > 0) + { + /* Step 1: Ensure list1->repeated.length == list2->repeated.length. */ + { + unsigned int n1 = list1->repeated.length; + unsigned int n2 = list2->repeated.length; + unsigned int g = gcd (n1, n2); + unsigned int m1 = n2 / g; /* = lcm(n1,n2) / n1 */ + unsigned int m2 = n1 / g; /* = lcm(n1,n2) / n2 */ + + unfold_loop (list1, m1); + unfold_loop (list2, m2); + /* Now list1->repeated.length = list2->repeated.length = lcm(n1,n2). */ + } + + /* Step 2: Ensure that list1->initial.length == list2->initial.length. */ + { + unsigned int m = MAX (list1->initial.length, list2->initial.length); + + rotate_loop (list1, m); + rotate_loop (list2, m); + } + + ASSERT (list1->initial.length == list2->initial.length); + ASSERT (list1->repeated.length == list2->repeated.length); + } + else if (list1->repeated.length > 0) + { + /* Ensure the initial segment of the result can be computed from the + initial segment of list1. */ + if (list2->initial.length >= list1->initial.length) + { + rotate_loop (list1, list2->initial.length); + if (list1->repeated.element[0].presence == FCT_REQUIRED) + rotate_loop (list1, list1->initial.length + 1); + } + } + else if (list2->repeated.length > 0) + { + /* Ensure the initial segment of the result can be computed from the + initial segment of list2. */ + if (list1->initial.length >= list2->initial.length) + { + rotate_loop (list2, list1->initial.length); + if (list2->repeated.element[0].presence == FCT_REQUIRED) + rotate_loop (list2, list2->initial.length + 1); + } + } + + /* Step 3: Allocate the result. */ + result = XMALLOC (struct format_arg_list); + result->initial.count = 0; + result->initial.allocated = 0; + result->initial.element = NULL; + result->initial.length = 0; + result->repeated.count = 0; + result->repeated.allocated = 0; + result->repeated.element = NULL; + result->repeated.length = 0; + + /* Step 4: Elementwise union of list1->initial, list2->initial. */ + { + struct format_arg *e1; + struct format_arg *e2; + unsigned int c1; + unsigned int c2; + + e1 = list1->initial.element; c1 = list1->initial.count; + e2 = list2->initial.element; c2 = list2->initial.count; + while (c1 > 0 && c2 > 0) + { + struct format_arg *re; + + /* Ensure room in result->initial. */ + grow_initial_alloc (result); + re = &result->initial.element[result->initial.count]; + re->repcount = MIN (e1->repcount, e2->repcount); + + /* Union of the argument types. */ + make_union_element (re, e1, e2); + + result->initial.count++; + result->initial.length += re->repcount; + + e1->repcount -= re->repcount; + if (e1->repcount == 0) + { + e1++; + c1--; + } + e2->repcount -= re->repcount; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + + if (c1 > 0) + { + /* list2 already terminated, but still more elements in list1->initial. + Copy them all, but turn the first presence to FCT_OPTIONAL. */ + ASSERT (list2->repeated.count == 0); + + if (e1->presence == FCT_REQUIRED) + { + struct format_arg *re; + + /* Ensure room in result->initial. */ + grow_initial_alloc (result); + re = &result->initial.element[result->initial.count]; + copy_element (re, e1); + re->presence = FCT_OPTIONAL; + re->repcount = 1; + result->initial.count++; + result->initial.length += 1; + e1->repcount -= 1; + if (e1->repcount == 0) + { + e1++; + c1--; + } + } + + /* Ensure room in result->initial. */ + ensure_initial_alloc (result, result->initial.count + c1); + while (c1 > 0) + { + struct format_arg *re; + + re = &result->initial.element[result->initial.count]; + copy_element (re, e1); + result->initial.count++; + result->initial.length += re->repcount; + e1++; + c1--; + } + } + else if (c2 > 0) + { + /* list1 already terminated, but still more elements in list2->initial. + Copy them all, but turn the first presence to FCT_OPTIONAL. */ + ASSERT (list1->repeated.count == 0); + + if (e2->presence == FCT_REQUIRED) + { + struct format_arg *re; + + /* Ensure room in result->initial. */ + grow_initial_alloc (result); + re = &result->initial.element[result->initial.count]; + copy_element (re, e2); + re->presence = FCT_OPTIONAL; + re->repcount = 1; + result->initial.count++; + result->initial.length += 1; + e2->repcount -= 1; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + + /* Ensure room in result->initial. */ + ensure_initial_alloc (result, result->initial.count + c2); + while (c2 > 0) + { + struct format_arg *re; + + re = &result->initial.element[result->initial.count]; + copy_element (re, e2); + result->initial.count++; + result->initial.length += re->repcount; + e2++; + c2--; + } + } + ASSERT (c1 == 0 && c2 == 0); + } + + if (list1->repeated.length > 0 && list2->repeated.length > 0) + /* Step 5: Elementwise union of list1->repeated, list2->repeated. */ + { + struct format_arg *e1; + struct format_arg *e2; + unsigned int c1; + unsigned int c2; + + e1 = list1->repeated.element; c1 = list1->repeated.count; + e2 = list2->repeated.element; c2 = list2->repeated.count; + while (c1 > 0 && c2 > 0) + { + struct format_arg *re; + + /* Ensure room in result->repeated. */ + grow_repeated_alloc (result); + re = &result->repeated.element[result->repeated.count]; + re->repcount = MIN (e1->repcount, e2->repcount); + + /* Union of the argument types. */ + make_union_element (re, e1, e2); + + result->repeated.count++; + result->repeated.length += re->repcount; + + e1->repcount -= re->repcount; + if (e1->repcount == 0) + { + e1++; + c1--; + } + e2->repcount -= re->repcount; + if (e2->repcount == 0) + { + e2++; + c2--; + } + } + ASSERT (c1 == 0 && c2 == 0); + } + else if (list1->repeated.length > 0) + { + /* Turning FCT_REQUIRED into FCT_OPTIONAL was already handled in the + initial segment. Just copy the repeated segment of list1. */ + unsigned int i; + + result->repeated.count = list1->repeated.count; + result->repeated.allocated = result->repeated.count; + result->repeated.element = + XNMALLOC (result->repeated.allocated, struct format_arg); + for (i = 0; i < list1->repeated.count; i++) + copy_element (&result->repeated.element[i], + &list1->repeated.element[i]); + result->repeated.length = list1->repeated.length; + } + else if (list2->repeated.length > 0) + { + /* Turning FCT_REQUIRED into FCT_OPTIONAL was already handled in the + initial segment. Just copy the repeated segment of list2. */ + unsigned int i; + + result->repeated.count = list2->repeated.count; + result->repeated.allocated = result->repeated.count; + result->repeated.element = + XNMALLOC (result->repeated.allocated, struct format_arg); + for (i = 0; i < list2->repeated.count; i++) + copy_element (&result->repeated.element[i], + &list2->repeated.element[i]); + result->repeated.length = list2->repeated.length; + } + + free_list (list1); + free_list (list2); + /* Undo the loop unfolding and unrolling done above. */ + normalize_outermost_list (result); + VERIFY_LIST (result); + return result; +} + + +/* Create the union of an argument list and the empty list. */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +make_union_with_empty_list (struct format_arg_list *list) +{ +#if 0 /* equivalent but slower */ + return make_union_list (list, make_empty_list ()); +#else + VERIFY_LIST (list); + + if (list->initial.count > 0 + ? list->initial.element[0].presence == FCT_REQUIRED + : list->repeated.count > 0 + && list->repeated.element[0].presence == FCT_REQUIRED) + { + initial_splitelement (list, 1); + ASSERT (list->initial.count > 0); + ASSERT (list->initial.element[0].repcount == 1); + ASSERT (list->initial.element[0].presence == FCT_REQUIRED); + list->initial.element[0].presence = FCT_OPTIONAL; + + /* We might need to merge list->initial.element[0] and + list->initial.element[1]. */ + normalize_outermost_list (list); + } + + VERIFY_LIST (list); + + return list; +#endif +} + + +/* Create the union of two argument list constraints. NULL stands for an + impossible situation, i.e. a contradiction. */ +/* Memory effects: list1 and list2 are freed if non-NULL. The result, + if non-NULL, is freshly allocated. */ +static struct format_arg_list * +union (struct format_arg_list *list1, struct format_arg_list *list2) +{ + if (list1 != NULL) + { + if (list2 != NULL) + return make_union_list (list1, list2); + else + return list1; + } + else + { + if (list2 != NULL) + return list2; + else + return NULL; + } +} + + +/* =========== Adding specific constraints to a format_arg_list =========== */ + + +/* Test whether arguments 0..n are required arguments in a list. */ +static bool +is_required (const struct format_arg_list *list, unsigned int n) +{ + unsigned int s; + unsigned int t; + + /* We'll check whether the first n+1 presence flags are FCT_REQUIRED. */ + t = n + 1; + + /* Walk the list->initial segment. */ + for (s = 0; + s < list->initial.count && t >= list->initial.element[s].repcount; + t -= list->initial.element[s].repcount, s++) + if (list->initial.element[s].presence != FCT_REQUIRED) + return false; + + if (t == 0) + return true; + + if (s < list->initial.count) + { + if (list->initial.element[s].presence != FCT_REQUIRED) + return false; + else + return true; + } + + /* Walk the list->repeated segment. */ + if (list->repeated.count == 0) + return false; + + for (s = 0; + s < list->repeated.count && t >= list->repeated.element[s].repcount; + t -= list->repeated.element[s].repcount, s++) + if (list->repeated.element[s].presence != FCT_REQUIRED) + return false; + + if (t == 0) + return true; + + if (s < list->repeated.count) + { + if (list->repeated.element[s].presence != FCT_REQUIRED) + return false; + else + return true; + } + + /* The list->repeated segment consists only of FCT_REQUIRED. So, + regardless how many more passes through list->repeated would be + needed until t becomes 0, the result is true. */ + return true; +} + + +/* Add a constraint to an argument list, namely that the arguments 0...n are + present. NULL stands for an impossible situation, i.e. a contradiction. */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +add_required_constraint (struct format_arg_list *list, unsigned int n) +{ + unsigned int i, rest; + + if (list == NULL) + return NULL; + + VERIFY_LIST (list); + + if (list->repeated.count == 0 && list->initial.length <= n) + { + /* list is already constrained to have at most length n. + Contradiction. */ + free_list (list); + return NULL; + } + + initial_splitelement (list, n + 1); + + for (i = 0, rest = n + 1; rest > 0; ) + { + list->initial.element[i].presence = FCT_REQUIRED; + rest -= list->initial.element[i].repcount; + i++; + } + + VERIFY_LIST (list); + + return list; +} + + +/* Add a constraint to an argument list, namely that the argument n is + never present. NULL stands for an impossible situation, i.e. a + contradiction. */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +add_end_constraint (struct format_arg_list *list, unsigned int n) +{ + unsigned int s, i; + enum format_cdr_type n_presence; + + if (list == NULL) + return NULL; + + VERIFY_LIST (list); + + if (list->repeated.count == 0 && list->initial.length <= n) + /* list is already constrained to have at most length n. */ + return list; + + s = initial_splitelement (list, n); + n_presence = + (s < list->initial.count + ? /* n < list->initial.length */ list->initial.element[s].presence + : /* n >= list->initial.length */ list->repeated.element[0].presence); + + for (i = s; i < list->initial.count; i++) + { + list->initial.length -= list->initial.element[i].repcount; + free_element (&list->initial.element[i]); + } + list->initial.count = s; + + for (i = 0; i < list->repeated.count; i++) + free_element (&list->repeated.element[i]); + if (list->repeated.element != NULL) + free (list->repeated.element); + list->repeated.element = NULL; + list->repeated.allocated = 0; + list->repeated.count = 0; + list->repeated.length = 0; + + if (n_presence == FCT_REQUIRED) + return backtrack_in_initial (list); + else + return list; +} + + +/* Add a constraint to an argument list, namely that the argument n is + of a given type. NULL stands for an impossible situation, i.e. a + contradiction. Assumes a preceding add_required_constraint (list, n). */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +add_type_constraint (struct format_arg_list *list, unsigned int n, + enum format_arg_type type) +{ + unsigned int s; + struct format_arg newconstraint; + struct format_arg tmpelement; + + if (list == NULL) + return NULL; + + /* Through the previous add_required_constraint, we can assume + list->initial.length >= n+1. */ + + s = initial_unshare (list, n); + + newconstraint.presence = FCT_OPTIONAL; + newconstraint.type = type; + if (!make_intersected_element (&tmpelement, + &list->initial.element[s], &newconstraint)) + return add_end_constraint (list, n); + free_element (&list->initial.element[s]); + list->initial.element[s].type = tmpelement.type; + list->initial.element[s].list = tmpelement.list; + + VERIFY_LIST (list); + + return list; +} + + +/* Add a constraint to an argument list, namely that the argument n is + of a given list type. NULL stands for an impossible situation, i.e. a + contradiction. Assumes a preceding add_required_constraint (list, n). */ +/* Memory effects: list is freed. The result is freshly allocated. */ +static struct format_arg_list * +add_listtype_constraint (struct format_arg_list *list, unsigned int n, + enum format_arg_type type, + struct format_arg_list *sublist) +{ + unsigned int s; + struct format_arg newconstraint; + struct format_arg tmpelement; + + if (list == NULL) + return NULL; + + /* Through the previous add_required_constraint, we can assume + list->initial.length >= n+1. */ + + s = initial_unshare (list, n); + + newconstraint.presence = FCT_OPTIONAL; + newconstraint.type = type; + newconstraint.list = sublist; + if (!make_intersected_element (&tmpelement, + &list->initial.element[s], &newconstraint)) + return add_end_constraint (list, n); + free_element (&list->initial.element[s]); + list->initial.element[s].type = tmpelement.type; + list->initial.element[s].list = tmpelement.list; + + VERIFY_LIST (list); + + return list; +} + + +/* ============= Subroutines used by the format string parser ============= */ + +static void +add_req_type_constraint (struct format_arg_list **listp, + unsigned int position, enum format_arg_type type) +{ + *listp = add_required_constraint (*listp, position); + *listp = add_type_constraint (*listp, position, type); +} + + +static void +add_req_listtype_constraint (struct format_arg_list **listp, + unsigned int position, enum format_arg_type type, + struct format_arg_list *sublist) +{ + *listp = add_required_constraint (*listp, position); + *listp = add_listtype_constraint (*listp, position, type, sublist); +} + + +/* Create an endless repeated list whose elements are lists constrained + by sublist. */ +/* Memory effects: sublist is freed. The result is freshly allocated. */ +static struct format_arg_list * +make_repeated_list_of_lists (struct format_arg_list *sublist) +{ + if (sublist == NULL) + /* The list cannot have a single element. */ + return make_empty_list (); + else + { + struct format_arg_list *listlist; + + listlist = XMALLOC (struct format_arg_list); + + listlist->initial.count = 0; + listlist->initial.allocated = 0; + listlist->initial.element = NULL; + listlist->initial.length = 0; + listlist->repeated.count = 1; + listlist->repeated.allocated = 1; + listlist->repeated.element = XNMALLOC (1, struct format_arg); + listlist->repeated.element[0].repcount = 1; + listlist->repeated.element[0].presence = FCT_OPTIONAL; + listlist->repeated.element[0].type = FAT_LIST; + listlist->repeated.element[0].list = sublist; + listlist->repeated.length = 1; + + VERIFY_LIST (listlist); + + return listlist; + } +} + + +/* Create an endless repeated list which represents the union of a finite + number of copies of L, each time shifted by period: + () + L + L and (*^period L) + L and (*^period L) and (*^{2 period} L) + L and (*^period L) and (*^{2 period} L) and (*^{3 period} L) + ... + */ +/* Memory effects: sublist is freed. The result is freshly allocated. */ +static struct format_arg_list * +make_repeated_list (struct format_arg_list *sublist, unsigned int period) +{ + struct segment tmp; + struct segment *srcseg; + struct format_arg_list *list; + unsigned int p, n, i, si, ti, j, sj, tj, splitindex, newcount; + bool ended; + + VERIFY_LIST (sublist); + + ASSERT (period > 0); + + if (sublist->repeated.count == 0) + { + /* L is a finite list. */ + + if (sublist->initial.length < period) + /* L and (*^period L) is a contradition, so we need to consider + only 1 and 0 iterations. */ + return make_union_with_empty_list (sublist); + + srcseg = &sublist->initial; + p = period; + } + else + { + /* L is an infinite list. */ + /* p := lcm (period, period of L) */ + unsigned int Lp = sublist->repeated.length; + unsigned int m = period / gcd (period, Lp); /* = lcm(period,Lp) / Lp */ + + unfold_loop (sublist, m); + p = m * Lp; + + /* Concatenate the initial and the repeated segments into a single + segment. */ + tmp.count = sublist->initial.count + sublist->repeated.count; + tmp.allocated = tmp.count; + tmp.element = XNMALLOC (tmp.allocated, struct format_arg); + for (i = 0; i < sublist->initial.count; i++) + tmp.element[i] = sublist->initial.element[i]; + for (j = 0; j < sublist->repeated.count; i++, j++) + tmp.element[i] = sublist->initial.element[j]; + tmp.length = sublist->initial.length + sublist->repeated.length; + + srcseg = &tmp; + } + + n = srcseg->length; + + /* Example: n = 7, p = 2 + Let L = (A B C D E F G). + + L = A B C D E F G + L & L<

initial.count = 0; + list->initial.allocated = 0; + list->initial.element = NULL; + list->initial.length = 0; + list->repeated.count = 0; + list->repeated.allocated = 0; + list->repeated.element = NULL; + list->repeated.length = 0; + + /* Sketch: + for (i = 0; i < p; i++) + list->initial.element[i] = srcseg->element[i]; + list->initial.element[0].presence = FCT_OPTIONAL; // union with empty list + for (i = p, j = 0; i < n; i++, j++) + list->initial.element[i] = srcseg->element[i] & list->initial.element[j]; + */ + + ended = false; + + i = 0, ti = 0, si = 0; + while (i < p) + { + unsigned int k = MIN (srcseg->element[si].repcount - ti, p - i); + + /* Ensure room in list->initial. */ + grow_initial_alloc (list); + copy_element (&list->initial.element[list->initial.count], + &srcseg->element[si]); + list->initial.element[list->initial.count].repcount = k; + list->initial.count++; + list->initial.length += k; + + i += k; + ti += k; + if (ti == srcseg->element[si].repcount) + { + ti = 0; + si++; + } + } + + ASSERT (list->initial.count > 0); + if (list->initial.element[0].presence == FCT_REQUIRED) + { + initial_splitelement (list, 1); + ASSERT (list->initial.element[0].presence == FCT_REQUIRED); + ASSERT (list->initial.element[0].repcount == 1); + list->initial.element[0].presence = FCT_OPTIONAL; + } + + j = 0, tj = 0, sj = 0; + while (i < n) + { + unsigned int k = + MIN (srcseg->element[si].repcount - ti, + list->initial.element[sj].repcount - tj); + + /* Ensure room in list->initial. */ + grow_initial_alloc (list); + if (!make_intersected_element (&list->initial.element[list->initial.count], + &srcseg->element[si], + &list->initial.element[sj])) + { + if (list->initial.element[list->initial.count].presence == FCT_REQUIRED) + { + /* Contradiction. Backtrack. */ + list = backtrack_in_initial (list); + ASSERT (list != NULL); /* at least the empty list is valid */ + return list; + } + else + { + /* The list ends here. */ + ended = true; + break; + } + } + list->initial.element[list->initial.count].repcount = k; + list->initial.count++; + list->initial.length += k; + + i += k; + ti += k; + if (ti == srcseg->element[si].repcount) + { + ti = 0; + si++; + } + + j += k; + tj += k; + if (tj == list->initial.element[sj].repcount) + { + tj = 0; + sj++; + } + } + if (!ended) + ASSERT (list->initial.length == n); + + /* Add optional exit points at 0, period, 2*period etc. + FIXME: Not sure this is correct in all cases. */ + for (i = 0; i < list->initial.length; i += period) + { + si = initial_unshare (list, i); + list->initial.element[si].presence = FCT_OPTIONAL; + } + + if (!ended) + { + /* Now split off the repeated part. */ + splitindex = initial_splitelement (list, n - p); + newcount = list->initial.count - splitindex; + if (newcount > list->repeated.allocated) + { + list->repeated.allocated = newcount; + list->repeated.element = XNMALLOC (newcount, struct format_arg); + } + for (i = splitindex, j = 0; i < n; i++, j++) + list->repeated.element[j] = list->initial.element[i]; + list->repeated.count = newcount; + list->repeated.length = p; + list->initial.count = splitindex; + list->initial.length = n - p; + } + + VERIFY_LIST (list); + + return list; +} + + +/* ================= Handling of format string directives ================= */ + +/* Possible signatures of format directives. */ +static const enum format_arg_type I [1] = { FAT_INTEGER_NULL }; +static const enum format_arg_type II [2] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL +}; +static const enum format_arg_type IIC [3] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_CHARACTER_NULL +}; +static const enum format_arg_type ICCI [4] = { + FAT_INTEGER_NULL, FAT_CHARACTER_NULL, FAT_CHARACTER_NULL, FAT_INTEGER_NULL +}; +static const enum format_arg_type IIIC [4] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_CHARACTER_NULL +}; +static const enum format_arg_type IICCI [5] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_CHARACTER_NULL, FAT_CHARACTER_NULL, + FAT_INTEGER_NULL +}; +static const enum format_arg_type IIICC [5] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_CHARACTER_NULL, + FAT_CHARACTER_NULL +}; +static const enum format_arg_type IIIICCC [7] = { + FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_INTEGER_NULL, FAT_INTEGER_NULL, + FAT_CHARACTER_NULL, FAT_CHARACTER_NULL, FAT_CHARACTER_NULL +}; +static const enum format_arg_type THREE [3] = { + FAT_CHARACTER_INTEGER_NULL, FAT_CHARACTER_INTEGER_NULL, + FAT_CHARACTER_INTEGER_NULL +}; + + +/* Check the parameters. For V params, add the constraint to the argument + list. Return false and fill in *invalid_reason if the format string is + invalid. */ +static bool +check_params (struct format_arg_list **listp, + unsigned int paramcount, struct param *params, + unsigned int t_count, const enum format_arg_type *t_types, + unsigned int directives, char **invalid_reason) +{ + unsigned int orig_paramcount = paramcount; + unsigned int orig_t_count = t_count; + + for (; paramcount > 0 && t_count > 0; + params++, paramcount--, t_types++, t_count--) + { + switch (*t_types) + { + case FAT_CHARACTER_INTEGER_NULL: + break; + case FAT_CHARACTER_NULL: + switch (params->type) + { + case PT_NIL: case PT_CHARACTER: case PT_V: + break; + case PT_INTEGER: case PT_ARGCOUNT: + /* wrong param type */ + *invalid_reason = + xasprintf (_("In the directive number %u, parameter %u is of type '%s' but a parameter of type '%s' is expected."), directives, orig_paramcount - paramcount + 1, "integer", "character"); + return false; + } + break; + case FAT_INTEGER_NULL: + switch (params->type) + { + case PT_NIL: case PT_INTEGER: case PT_ARGCOUNT: case PT_V: + break; + case PT_CHARACTER: + /* wrong param type */ + *invalid_reason = + xasprintf (_("In the directive number %u, parameter %u is of type '%s' but a parameter of type '%s' is expected."), directives, orig_paramcount - paramcount + 1, "character", "integer"); + return false; + } + break; + default: + abort (); + } + if (params->type == PT_V) + { + int position = params->value; + if (position >= 0) + add_req_type_constraint (listp, position, *t_types); + } + } + + for (; paramcount > 0; params++, paramcount--) + switch (params->type) + { + case PT_NIL: + break; + case PT_CHARACTER: case PT_INTEGER: case PT_ARGCOUNT: + /* too many params for directive */ + *invalid_reason = + xasprintf (ngettext ("In the directive number %u, too many parameters are given; expected at most %u parameter.", + "In the directive number %u, too many parameters are given; expected at most %u parameters.", + orig_t_count), + directives, orig_t_count); + return false; + case PT_V: + /* Force argument to be NIL. */ + { + int position = params->value; + if (position >= 0) + { + struct format_arg_list *empty_list = make_empty_list (); + add_req_listtype_constraint (listp, position, + FAT_LIST, empty_list); + free_list (empty_list); + } + } + break; + } + + return true; +} + + +/* ======================= The format string parser ======================= */ + +/* Parse a piece of format string, until the matching terminating format + directive is encountered. + format is the remainder of the format string. + position is the position in this argument list, if known, or -1 if unknown. + list represents the argument list constraints at the current parse point. + NULL stands for a contradiction. + escape represents the union of the argument list constraints at all the + currently pending FORMAT-UP-AND-OUT points. NULL stands for a contradiction + or an empty union. + All four are updated upon valid return. + *separatorp is set to true if the parse terminated due to a ~; separator, + more precisely to 2 if with colon, or to 1 if without colon. + spec is the global struct spec. + terminator is the directive that terminates this parse. + separator specifies if ~; separators are allowed. + fdi is an array to be filled with format directive indicators, or NULL. + If the format string is invalid, false is returned and *invalid_reason is + set to an error message explaining why. */ +static bool +parse_upto (const char **formatp, + int *positionp, struct format_arg_list **listp, + struct format_arg_list **escapep, int *separatorp, + struct spec *spec, char terminator, bool separator, + char *fdi, char **invalid_reason) +{ + const char *format = *formatp; + const char *const format_start = format; + int position = *positionp; + struct format_arg_list *list = *listp; + struct format_arg_list *escape = *escapep; + + for (; *format != '\0'; ) + if (*format++ == '~') + { + bool colon_p = false; + bool atsign_p = false; + unsigned int paramcount = 0; + struct param *params = NULL; + + FDI_SET (format - 1, FMTDIR_START); + + /* Count number of directives. */ + spec->directives++; + + /* Parse parameters. */ + for (;;) + { + enum param_type type = PT_NIL; + int value = 0; + + if (c_isdigit (*format)) + { + type = PT_INTEGER; + do + { + value = 10 * value + (*format - '0'); + format++; + } + while (c_isdigit (*format)); + } + else if (*format == '+' || *format == '-') + { + bool negative = (*format == '-'); + type = PT_INTEGER; + format++; + if (!c_isdigit (*format)) + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + xasprintf (_("In the directive number %u, '%c' is not followed by a digit."), spec->directives, format[-1]); + FDI_SET (format, FMTDIR_ERROR); + } + return false; + } + do + { + value = 10 * value + (*format - '0'); + format++; + } + while (c_isdigit (*format)); + if (negative) + value = -value; + } + else if (*format == '\'') + { + type = PT_CHARACTER; + format++; + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + format++; + } + else if (*format == 'V' || *format == 'v') + { + type = PT_V; + format++; + value = position; + /* Consumes an argument. */ + if (position >= 0) + position++; + } + else if (*format == '#') + { + type = PT_ARGCOUNT; + format++; + } + + params = + (struct param *) + xrealloc (params, (paramcount + 1) * sizeof (struct param)); + params[paramcount].type = type; + params[paramcount].value = value; + paramcount++; + + if (*format == ',') + format++; + else + break; + } + + /* Parse modifiers. */ + for (;;) + { + if (*format == ':') + { + format++; + colon_p = true; + } + else if (*format == '@') + { + format++; + atsign_p = true; + } + else + break; + } + + /* Parse directive. */ + switch (*format++) + { + case 'A': case 'a': /* 22.3.4.1 FORMAT-ASCII */ + case 'S': case 's': /* 22.3.4.2 FORMAT-S-EXPRESSION */ + if (!check_params (&list, paramcount, params, 4, IIIC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + break; + + case 'C': case 'c': /* FORMAT-CHARACTER */ + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (paramcount == 0 + || (paramcount == 1 && params[0].type == PT_NIL)) + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_CHARACTER); + break; + + case 'D': case 'd': /* 22.3.2.2 FORMAT-DECIMAL */ + case 'B': case 'b': /* 22.3.2.3 FORMAT-BINARY */ + case 'O': case 'o': /* 22.3.2.4 FORMAT-OCTAL */ + case 'X': case 'x': /* 22.3.2.5 FORMAT-HEXADECIMAL */ + if (!check_params (&list, paramcount, params, 4, ICCI, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_INTEGER); + break; + + case 'R': case 'r': /* 22.3.2.1 FORMAT-RADIX */ + if (!check_params (&list, paramcount, params, 5, IICCI, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_INTEGER); + break; + + case 'P': case 'p': /* 22.3.8.3 FORMAT-PLURAL */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (colon_p) + { + /* Go back by 1 argument. */ + if (position > 0) + position--; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + break; + + case 'F': case 'f': /* 22.3.3.1 FORMAT-FIXED-FLOAT */ + if (!check_params (&list, paramcount, params, 5, IIICC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_REAL); + break; + + case 'E': case 'e': /* 22.3.3.2 FORMAT-EXPONENTIAL-FLOAT */ + case 'G': case 'g': /* 22.3.3.3 FORMAT-GENERAL-FLOAT */ + if (!check_params (&list, paramcount, params, 7, IIIICCC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_REAL); + break; + + case '$': /* 22.3.3.4 FORMAT-DOLLARS-FLOAT */ + if (!check_params (&list, paramcount, params, 4, IIIC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_REAL); + break; + + case 'I': case 'i': /* FORMAT-FIXED-FLOAT-COMPLEX */ + if (!check_params (&list, paramcount, params, 5, IIICC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_COMPLEX); + break; + + case 'Y': case 'y': /* FORMAT-PRETTY */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + break; + + case '%': /* 22.3.1.2 FORMAT-TERPRI */ + case '&': /* 22.3.1.3 FORMAT-FRESH-LINE */ + case '_': /* FORMAT-SPACE */ + case '/': /* FORMAT-TAB */ + case '|': /* 22.3.1.4 FORMAT-PAGE */ + case '~': /* 22.3.1.5 FORMAT-TILDE */ + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + break; + + case '!': /* FORMAT-FORCE-OUTPUT */ + case '\n': /* 22.3.9.3 #\Newline */ + case 'Q': case 'q': /* FORMAT-IMPLEMENTATION */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + break; + + case 'T': case 't': /* FORMAT-TABULATE */ + if (!check_params (&list, paramcount, params, 3, IIC, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + break; + + case '*': /* 22.3.7.1 FORMAT-GOTO */ + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + { + int n; /* value of first parameter */ + if (paramcount == 0 + || (paramcount >= 1 && params[0].type == PT_NIL)) + n = (atsign_p ? 0 : 1); + else if (paramcount >= 1 && params[0].type == PT_INTEGER) + n = params[0].value; + else + { + /* Unknown argument, leads to an unknown position. */ + position = -1; + break; + } + if (n < 0) + { + /* invalid argument */ + *invalid_reason = + xasprintf (_("In the directive number %u, the argument %d is negative."), spec->directives, n); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (atsign_p) + { + /* Absolute goto. */ + position = n; + } + else if (colon_p) + { + /* Backward goto. */ + if (n > 0) + { + if (position >= 0) + { + if (position >= n) + position -= n; + else + position = 0; + } + else + position = -1; + } + } + else + { + /* Forward goto. */ + if (position >= 0) + position += n; + } + } + break; + + case '?': case 'K': case 'k': /* 22.3.7.6 FORMAT-INDIRECTION */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_FORMATSTRING); + if (atsign_p) + position = -1; + else + if (position >= 0) + { + struct format_arg_list *sublist = make_unconstrained_list (); + add_req_listtype_constraint (&list, position++, + FAT_LIST, sublist); + free_list (sublist); + } + break; + + case '(': /* 22.3.8.1 FORMAT-CASE-CONVERSION */ + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + { + if (!parse_upto (formatp, positionp, listp, escapep, + NULL, spec, ')', false, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + } + format = *formatp; + position = *positionp; + list = *listp; + escape = *escapep; + break; + + case ')': /* 22.3.8.2 FORMAT-CASE-CONVERSION-END */ + if (terminator != ')') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), ')', '('); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + return true; + + case '[': /* 22.3.7.2 FORMAT-CONDITIONAL */ + if (atsign_p && colon_p) + { + *invalid_reason = + xasprintf (_("In the directive number %u, both the @ and the : modifiers are given."), spec->directives); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + else if (atsign_p) + { + struct format_arg_list *nil_list; + struct format_arg_list *union_list; + + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + + *formatp = format; + *escapep = escape; + + /* First alternative: argument is NIL. */ + nil_list = (list != NULL ? copy_list (list) : NULL); + if (position >= 0) + { + struct format_arg_list *empty_list = make_empty_list (); + add_req_listtype_constraint (&nil_list, position, + FAT_LIST, empty_list); + free_list (empty_list); + } + + /* Second alternative: use sub-format. */ + { + int sub_position = position; + struct format_arg_list *sub_list = + (list != NULL ? copy_list (list) : NULL); + if (!parse_upto (formatp, &sub_position, &sub_list, escapep, + NULL, spec, ']', false, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (sub_list != NULL) + { + if (position >= 0) + { + if (sub_position == position + 1) + /* new position is branch independent */ + position = position + 1; + else + /* new position is branch dependent */ + position = -1; + } + } + else + { + if (position >= 0) + position = position + 1; + } + union_list = union (nil_list, sub_list); + } + + format = *formatp; + escape = *escapep; + + if (list != NULL) + free_list (list); + list = union_list; + } + else if (colon_p) + { + int union_position; + struct format_arg_list *union_list; + + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_OBJECT); + + *formatp = format; + *escapep = escape; + union_position = -2; + union_list = NULL; + + /* First alternative. */ + { + int sub_position = position; + struct format_arg_list *sub_list = + (list != NULL ? copy_list (list) : NULL); + int sub_separator = 0; + if (position >= 0) + { + struct format_arg_list *empty_list = make_empty_list (); + add_req_listtype_constraint (&sub_list, position - 1, + FAT_LIST, empty_list); + free_list (empty_list); + } + if (!parse_upto (formatp, &sub_position, &sub_list, escapep, + &sub_separator, spec, ']', true, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (!sub_separator) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '~:[' is not followed by two clauses, separated by '~;'."), spec->directives); + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (sub_list != NULL) + union_position = sub_position; + union_list = union (union_list, sub_list); + } + + /* Second alternative. */ + { + int sub_position = position; + struct format_arg_list *sub_list = + (list != NULL ? copy_list (list) : NULL); + if (!parse_upto (formatp, &sub_position, &sub_list, escapep, + NULL, spec, ']', false, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + if (sub_list != NULL) + { + if (union_position == -2) + union_position = sub_position; + else if (sub_position < 0 + || sub_position != union_position) + union_position = -1; + } + union_list = union (union_list, sub_list); + } + + format = *formatp; + escape = *escapep; + + if (union_position != -2) + position = union_position; + if (list != NULL) + free_list (list); + list = union_list; + } + else + { + int arg_position; + int union_position; + struct format_arg_list *union_list; + bool last_alternative; + + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + + /* If there was no first parameter, an argument is consumed. */ + arg_position = -1; + if (!(paramcount >= 1 && params[0].type != PT_NIL)) + if (position >= 0) + { + arg_position = position; + add_req_type_constraint (&list, position++, FAT_OBJECT); + } + + *formatp = format; + *escapep = escape; + + union_position = -2; + union_list = NULL; + last_alternative = false; + for (;;) + { + /* Next alternative. */ + int sub_position = position; + struct format_arg_list *sub_list = + (list != NULL ? copy_list (list) : NULL); + int sub_separator = 0; + if (!parse_upto (formatp, &sub_position, &sub_list, escapep, + &sub_separator, spec, ']', !last_alternative, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + /* If this alternative is chosen, the argument arg_position + is an integer, namely the index of this alternative. */ + if (!last_alternative && arg_position >= 0) + add_req_type_constraint (&sub_list, arg_position, + FAT_INTEGER); + if (sub_list != NULL) + { + if (union_position == -2) + union_position = sub_position; + else if (sub_position < 0 + || sub_position != union_position) + union_position = -1; + } + union_list = union (union_list, sub_list); + if (sub_separator == 2) + last_alternative = true; + if (!sub_separator) + break; + } + if (!last_alternative) + { + /* An implicit default alternative. */ + if (union_position == -2) + union_position = position; + else if (position < 0 || position != union_position) + union_position = -1; + if (list != NULL) + union_list = union (union_list, copy_list (list)); + } + + format = *formatp; + escape = *escapep; + + if (union_position != -2) + position = union_position; + if (list != NULL) + free_list (list); + list = union_list; + } + break; + + case ']': /* 22.3.7.3 FORMAT-CONDITIONAL-END */ + if (terminator != ']') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), ']', '['); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + return true; + + case '{': /* 22.3.7.4 FORMAT-ITERATION */ + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + { + int sub_position = 0; + struct format_arg_list *sub_list = make_unconstrained_list (); + struct format_arg_list *sub_escape = NULL; + struct spec sub_spec; + sub_spec.directives = 0; + sub_spec.list = sub_list; + if (!parse_upto (formatp, &sub_position, &sub_list, &sub_escape, + NULL, &sub_spec, '}', false, + NULL, invalid_reason)) + { + FDI_SET (**formatp == '\0' ? *formatp - 1 : *formatp, + FMTDIR_ERROR); + return false; + } + spec->directives += sub_spec.directives; + + /* If the sub-formatstring is empty, except for the terminating + ~} directive, a formatstring argument is consumed. */ + if (*format == '~' && sub_spec.directives == 1) + if (position >= 0) + add_req_type_constraint (&list, position++, FAT_FORMATSTRING); + + if (colon_p) + { + /* Each iteration uses a new sublist. */ + struct format_arg_list *listlist; + + /* ~{ catches ~^. */ + sub_list = union (sub_list, sub_escape); + + listlist = make_repeated_list_of_lists (sub_list); + + sub_list = listlist; + } + else + { + /* Each iteration's arguments are all concatenated in a + single list. */ + struct format_arg_list *looplist; + + /* FIXME: This is far from correct. Test cases: + abc~{~^~} + abc~{~S~^~S~} + abc~{~D~^~C~} + abc~{~D~^~D~} + abc~{~D~^~S~} + abc~{~D~^~C~}~:*~{~S~^~D~} + */ + + /* ~{ catches ~^. */ + sub_list = union (sub_list, sub_escape); + + if (sub_list == NULL) + looplist = make_empty_list (); + else + if (sub_position < 0 || sub_position == 0) + /* Too hard to track the possible argument types + when the iteration is performed 2 times or more. + So be satisfied with the constraints of executing + the iteration 1 or 0 times. */ + looplist = make_union_with_empty_list (sub_list); + else + looplist = make_repeated_list (sub_list, sub_position); + + sub_list = looplist; + } + + if (atsign_p) + { + /* All remaining arguments are used. */ + if (list != NULL && position >= 0) + { + shift_list (sub_list, position); + list = make_intersected_list (list, sub_list); + } + position = -1; + } + else + { + /* The argument is a list. */ + if (position >= 0) + add_req_listtype_constraint (&list, position++, + FAT_LIST, sub_list); + } + } + format = *formatp; + break; + + case '}': /* 22.3.7.5 FORMAT-ITERATION-END */ + if (terminator != '}') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), '}', '{'); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + return true; + + case '^': /* 22.3.9.2 FORMAT-UP-AND-OUT */ + if (!check_params (&list, paramcount, params, 3, THREE, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (position >= 0 && list != NULL && is_required (list, position)) + /* This ~^ can never be executed. Ignore it. */ + break; + if (list != NULL) + { + struct format_arg_list *this_escape = copy_list (list); + if (position >= 0) + this_escape = add_end_constraint (this_escape, position); + escape = union (escape, this_escape); + } + if (position >= 0) + list = add_required_constraint (list, position); + break; + + case ';': /* 22.3.9.1 FORMAT-SEPARATOR */ + if (!separator) + { + *invalid_reason = + xasprintf (_("In the directive number %u, '~;' is used in an invalid position."), spec->directives); + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + if (terminator == '>') + { + if (!check_params (&list, paramcount, params, 1, I, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + } + else + { + if (!check_params (&list, paramcount, params, 0, NULL, + spec->directives, invalid_reason)) + { + FDI_SET (format - 1, FMTDIR_ERROR); + return false; + } + } + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + *separatorp = (colon_p ? 2 : 1); + return true; + + default: + --format; + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec->directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + return false; + } + + FDI_SET (format - 1, FMTDIR_END); + + free (params); + } + + *formatp = format; + *positionp = position; + *listp = list; + *escapep = escape; + if (terminator != '\0') + { + *invalid_reason = + xasprintf (_("Found '~%c' without matching '~%c'."), terminator - 1, terminator); + return false; + } + return true; +} + + +/* ============== Top level format string handling functions ============== */ + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + struct spec spec; + struct spec *result; + int position = 0; + struct format_arg_list *escape; + + spec.directives = 0; + spec.list = make_unconstrained_list (); + escape = NULL; + + if (!parse_upto (&format, &position, &spec.list, &escape, + NULL, &spec, '\0', false, + fdi, invalid_reason)) + /* Invalid format string. */ + return NULL; + + /* Catch ~^ here. */ + spec.list = union (spec.list, escape); + + if (spec.list == NULL) + { + /* Contradictory argument type information. */ + *invalid_reason = + xstrdup (_("The string refers to some argument in incompatible ways.")); + return NULL; + } + + /* Normalize the result. */ + normalize_list (spec.list); + + result = XMALLOC (struct spec); + *result = spec; + return result; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + free_list (spec->list); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (equality) + { + if (!equal_list (spec1->list, spec2->list)) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' are not equivalent"), + pretty_msgid, pretty_msgstr); + err = true; + } + } + else + { + struct format_arg_list *intersection = + make_intersected_list (copy_list (spec1->list), + copy_list (spec2->list)); + + if (!(intersection != NULL + && (normalize_list (intersection), + equal_list (intersection, spec2->list)))) + { + if (error_logger) + error_logger (_("format specifications in '%s' are not a subset of those in '%s'"), + pretty_msgstr, pretty_msgid); + err = true; + } + } + + return err; +} + + +struct formatstring_parser formatstring_scheme = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +/* ============================= Testing code ============================= */ + +#undef union + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void print_list (struct format_arg_list *list); + +static void +print_element (struct format_arg *element) +{ + switch (element->presence) + { + case FCT_REQUIRED: + break; + case FCT_OPTIONAL: + printf (". "); + break; + default: + abort (); + } + + switch (element->type) + { + case FAT_OBJECT: + printf ("*"); + break; + case FAT_CHARACTER_INTEGER_NULL: + printf ("ci()"); + break; + case FAT_CHARACTER_NULL: + printf ("c()"); + break; + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_INTEGER_NULL: + printf ("i()"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_REAL: + printf ("r"); + break; + case FAT_COMPLEX: + printf ("C"); + break; + case FAT_LIST: + print_list (element->list); + break; + case FAT_FORMATSTRING: + printf ("~"); + break; + default: + abort (); + } +} + +static void +print_list (struct format_arg_list *list) +{ + unsigned int i, j; + + printf ("("); + + for (i = 0; i < list->initial.count; i++) + for (j = 0; j < list->initial.element[i].repcount; j++) + { + if (i > 0 || j > 0) + printf (" "); + print_element (&list->initial.element[i]); + } + + if (list->repeated.count > 0) + { + printf (" |"); + for (i = 0; i < list->repeated.count; i++) + for (j = 0; j < list->repeated.element[i].repcount; j++) + { + printf (" "); + print_element (&list->repeated.element[i]); + } + } + + printf (")"); +} + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + print_list (spec->list); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-scheme.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-sh.c b/gettext-tools/src/format-sh.c new file mode 100644 index 0000000..b796fcd --- /dev/null +++ b/gettext-tools/src/format-sh.c @@ -0,0 +1,403 @@ +/* Shell format strings. + Copyright (C) 2003-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Shell format strings are simply strings subjects to variable substitution. + A variable substitution starts with '$' and is finished by either + - a nonempty sequence of alphanumeric ASCII characters, the first being + not a digit, or + - an opening brace '{', a nonempty sequence of alphanumeric ASCII + characters, the first being not a digit, and a closing brace '}'. + We don't support variable references like $1, $$ or $? since they make + no sense when 'envsubst' is invoked. + We don't support non-ASCII variable names, to avoid dependencies w.r.t. the + current encoding: While "${\xe0}" looks like a variable access in ISO-8859-1 + encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030, + SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these + encodings. + We don't support the POSIX syntax for default or alternate values: + ${variable-default} ${variable:-default} + ${variable=default} ${variable:=default} + ${variable+replacement} ${variable:+replacement} + ${variable?ignored} ${variable:?ignored} + because the translator might be tempted to change the default value; if + we allow it we have a security problem; if we don't allow it the translator + will be surprised. + */ + +struct named_arg +{ + char *name; +}; + +struct spec +{ + unsigned int directives; + unsigned int named_arg_count; + unsigned int allocated; + struct named_arg *named; +}; + + +static int +named_arg_compare (const void *p1, const void *p2) +{ + return strcmp (((const struct named_arg *) p1)->name, + ((const struct named_arg *) p2)->name); +} + +#define INVALID_NON_ASCII_VARIABLE() \ + xstrdup (_("The string refers to a shell variable with a non-ASCII name.")) +#define INVALID_SHELL_SYNTAX() \ + xstrdup (_("The string refers to a shell variable with complex shell brace syntax. This syntax is unsupported here due to security reasons.")) +#define INVALID_CONTEXT_DEPENDENT_VARIABLE() \ + xstrdup (_("The string refers to a shell variable whose value may be different inside shell functions.")) +#define INVALID_EMPTY_VARIABLE() \ + xstrdup (_("The string refers to a shell variable with an empty name.")) + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.named_arg_count = 0; + spec.allocated = 0; + spec.named = NULL; + + for (; *format != '\0';) + if (*format++ == '$') + { + /* A variable substitution. */ + char *name; + + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (*format == '{') + { + const char *name_start; + const char *name_end; + size_t n; + + name_start = ++format; + for (; *format != '\0'; format++) + { + if (*format == '}') + break; + if (!c_isascii (*format)) + { + *invalid_reason = INVALID_NON_ASCII_VARIABLE (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (format > name_start + && (*format == '-' || *format == '=' || *format == '+' + || *format == '?' || *format == ':')) + { + *invalid_reason = INVALID_SHELL_SYNTAX (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (!(c_isalnum (*format) || *format == '_') + || (format == name_start && c_isdigit (*format))) + { + *invalid_reason = INVALID_CONTEXT_DEPENDENT_VARIABLE (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + } + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + name_end = format++; + + n = name_end - name_start; + if (n == 0) + { + *invalid_reason = INVALID_EMPTY_VARIABLE (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + name = XNMALLOC (n + 1, char); + memcpy (name, name_start, n); + name[n] = '\0'; + } + else if (c_isalpha (*format) || *format == '_') + { + const char *name_start; + const char *name_end; + size_t n; + + name_start = format; + do + format++; + while (*format != '\0' && (c_isalnum (*format) || *format == '_')); + name_end = format; + + n = name_end - name_start; + name = XNMALLOC (n + 1, char); + memcpy (name, name_start, n); + name[n] = '\0'; + } + else if (*format != '\0') + { + if (!c_isascii (*format)) + { + *invalid_reason = INVALID_NON_ASCII_VARIABLE (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + else + { + *invalid_reason = INVALID_CONTEXT_DEPENDENT_VARIABLE (); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + } + else + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + + /* Named argument. */ + if (spec.allocated == spec.named_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg)); + } + spec.named[spec.named_arg_count].name = name; + spec.named_arg_count++; + + FDI_SET (format - 1, FMTDIR_END); + } + + /* Sort the named argument array, and eliminate duplicates. */ + if (spec.named_arg_count > 1) + { + unsigned int i, j; + + qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg), + named_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + for (i = j = 0; i < spec.named_arg_count; i++) + if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0) + free (spec.named[i].name); + else + { + if (j < i) + spec.named[j].name = spec.named[i].name; + j++; + } + spec.named_arg_count = j; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.named != NULL) + { + unsigned int i; + for (i = 0; i < spec.named_arg_count; i++) + free (spec.named[i].name); + free (spec.named); + } + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->named != NULL) + { + unsigned int i; + for (i = 0; i < spec->named_arg_count; i++) + free (spec->named[i].name); + free (spec->named); + } + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->named_arg_count + spec2->named_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->named_arg_count; + unsigned int n2 = spec2->named_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + strcmp (spec1->named[i].name, spec2->named[j].name)); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument '%s', as in '%s', doesn't exist in '%s'"), + spec2->named[j].name, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"), + spec1->named[i].name, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_sh = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("{"); + for (i = 0; i < spec->named_arg_count; i++) + { + if (i > 0) + printf (", "); + printf ("'%s'", spec->named[i].name); + } + printf ("}"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-sh.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-tcl.c b/gettext-tools/src/format-tcl.c new file mode 100644 index 0000000..e338c6d --- /dev/null +++ b/gettext-tools/src/format-tcl.c @@ -0,0 +1,550 @@ +/* Tcl format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Tcl format strings are described in the tcl8.3.3/doc/format.n manual + page and implemented in the function Tcl_FormatObjCmd in + tcl8.3.3/generic/tclCmdAH.c. + A directive + - starts with '%' or '%m$' where m is a positive integer, + - is optionally followed by any of the characters '#', '0', '-', ' ', '+', + each of which acts as a flag, + - is optionally followed by a width specification: '*' (reads an argument) + or a nonempty digit sequence, + - is optionally followed by '.' and a precision specification: '*' (reads + an argument) or a nonempty digit sequence, + - is optionally followed by a size specifier, 'h' or 'l'. 'l' is ignored. + - is finished by a specifier + - '%', that needs no argument, + - 'c', that needs a character argument, + - 's', that needs a string argument, + - 'i', 'd', that need a signed integer argument, + - 'o', 'u', 'x', 'X', that need an unsigned integer argument, + - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument. + Numbered ('%m$') and unnumbered argument specifications cannot be used + in the same string. + */ + +enum format_arg_type +{ + FAT_NONE, + FAT_CHARACTER, + FAT_STRING, + FAT_INTEGER, + FAT_UNSIGNED_INTEGER, + FAT_SHORT_INTEGER, + FAT_SHORT_UNSIGNED_INTEGER, + FAT_FLOAT +}; + +struct numbered_arg +{ + unsigned int number; + enum format_arg_type type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + bool seen_numbered_arg; + bool seen_unnumbered_arg; + unsigned int number; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + seen_numbered_arg = false; + seen_unnumbered_arg = false; + number = 1; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (*format != '%') + { + bool is_numbered_arg; + bool short_flag; + enum format_arg_type type; + + is_numbered_arg = false; + if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + format = ++f; + + /* Numbered and unnumbered specifications are exclusive. */ + if (seen_unnumbered_arg) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + is_numbered_arg = true; + seen_numbered_arg = true; + } + } + + /* Numbered and unnumbered specifications are exclusive. */ + if (!is_numbered_arg) + { + if (seen_numbered_arg) + { + *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED (); + FDI_SET (format - 1, FMTDIR_ERROR); + goto bad_format; + } + seen_unnumbered_arg = true; + } + + /* Parse flags. */ + while (*format == ' ' || *format == '+' || *format == '-' + || *format == '#' || *format == '0') + format++; + + /* Parse width. */ + if (*format == '*') + { + format++; + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + + number++; + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (*format == '*') + { + format++; + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER; + spec.numbered_arg_count++; + + number++; + } + else if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + } + } + + /* Parse optional size specification. */ + short_flag = false; + if (*format == 'h') + short_flag = true, format++; + else if (*format == 'l') + format++; + + switch (*format) + { + case 'c': + type = FAT_CHARACTER; + break; + case 's': + type = FAT_STRING; + break; + case 'i': case 'd': + type = (short_flag ? FAT_SHORT_INTEGER : FAT_INTEGER); + break; + case 'u': case 'o': case 'x': case 'X': + type = (short_flag ? FAT_SHORT_UNSIGNED_INTEGER : FAT_UNSIGNED_INTEGER); + break; + case 'e': case 'E': case 'f': case 'g': case 'G': + type = FAT_FLOAT; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = type; + spec.numbered_arg_count++; + + number++; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + enum format_arg_type type1 = spec.numbered[i].type; + enum format_arg_type type2 = spec.numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_tcl = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int last; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + last = 1; + for (i = 0; i < spec->numbered_arg_count; i++) + { + unsigned int number = spec->numbered[i].number; + + if (i > 0) + printf (" "); + if (number < last) + abort (); + for (; last < number; last++) + printf ("_ "); + switch (spec->numbered[i].type) + { + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_STRING: + printf ("s"); + break; + case FAT_INTEGER: + printf ("i"); + break; + case FAT_UNSIGNED_INTEGER: + printf ("[unsigned]i"); + break; + case FAT_SHORT_INTEGER: + printf ("hi"); + break; + case FAT_SHORT_UNSIGNED_INTEGER: + printf ("[unsigned]hi"); + break; + case FAT_FLOAT: + printf ("f"); + break; + default: + abort (); + } + last = number + 1; + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-tcl.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-ycp.c b/gettext-tools/src/format-ycp.c new file mode 100644 index 0000000..762a5d7 --- /dev/null +++ b/gettext-tools/src/format-ycp.c @@ -0,0 +1,250 @@ +/* YCP and Smalltalk format strings. + Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* YCP sformat strings are described in libycp documentation YCP-builtins.html. + A directive starts with '%' and is followed by '%' or a nonzero digit ('1' + to '9'). + GNU Smalltalk format strings are described in the CharArray documentation, + methods 'bindWith:' and 'bindWithArguments:'. They have the same syntax. + */ + +struct spec +{ + unsigned int directives; + unsigned int arg_count; + bool args_used[9]; +}; + + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + + spec.directives = 0; + spec.arg_count = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (*format == '%') + format++; + else if (*format >= '1' && *format <= '9') + { + unsigned int number = *format - '1'; + + while (spec.arg_count <= number) + spec.args_used[spec.arg_count++] = false; + spec.args_used[number] = true; + + format++; + } + else + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + (c_isprint (*format) + ? xasprintf (_("In the directive number %u, the character '%c' is not a digit between 1 and 9."), spec.directives, *format) + : xasprintf (_("The character that terminates the directive number %u is not a digit between 1 and 9."), spec.directives)); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + FDI_SET (format - 1, FMTDIR_END); + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + unsigned int i; + + for (i = 0; i < spec1->arg_count || i < spec2->arg_count; i++) + { + bool arg_used1 = (i < spec1->arg_count && spec1->args_used[i]); + bool arg_used2 = (i < spec2->arg_count && spec2->args_used[i]); + + if (equality ? (arg_used1 != arg_used2) : (!arg_used1 && arg_used2)) + { + if (error_logger) + { + if (arg_used1) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + i + 1, pretty_msgstr); + else + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + i + 1, pretty_msgstr, pretty_msgid); + } + err = true; + break; + } + } + + return err; +} + + +struct formatstring_parser formatstring_ycp = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +struct formatstring_parser formatstring_smalltalk = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + for (i = 0; i < spec->arg_count; i++) + { + if (i > 0) + printf (" "); + if (spec->args_used[i]) + printf ("*"); + else + printf ("_"); + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * 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-ycp.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format.c b/gettext-tools/src/format.c new file mode 100644 index 0000000..c73ad7d --- /dev/null +++ b/gettext-tools/src/format.c @@ -0,0 +1,197 @@ +/* Format strings. + Copyright (C) 2001-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "format.h" + +#include +#include +#include + +#include "message.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Table of all format string parsers. */ +struct formatstring_parser *formatstring_parsers[NFORMATS] = +{ + /* format_c */ &formatstring_c, + /* format_objc */ &formatstring_objc, + /* format_sh */ &formatstring_sh, + /* format_python */ &formatstring_python, + /* format_python_brace */ &formatstring_python_brace, + /* format_lisp */ &formatstring_lisp, + /* format_elisp */ &formatstring_elisp, + /* format_librep */ &formatstring_librep, + /* format_scheme */ &formatstring_scheme, + /* format_smalltalk */ &formatstring_smalltalk, + /* format_java */ &formatstring_java, + /* format_csharp */ &formatstring_csharp, + /* format_awk */ &formatstring_awk, + /* format_pascal */ &formatstring_pascal, + /* format_ycp */ &formatstring_ycp, + /* format_tcl */ &formatstring_tcl, + /* format_perl */ &formatstring_perl, + /* format_perl_brace */ &formatstring_perl_brace, + /* format_php */ &formatstring_php, + /* format_gcc_internal */ &formatstring_gcc_internal, + /* format_gfc_internal */ &formatstring_gfc_internal, + /* format_qt */ &formatstring_qt, + /* format_qt_plural */ &formatstring_qt_plural, + /* format_kde */ &formatstring_kde, + /* format_boost */ &formatstring_boost, + /* format_lua */ &formatstring_lua, + /* format_javascript */ &formatstring_javascript +}; + +/* Check whether both formats strings contain compatible format + specifications for format type i (0 <= i < NFORMATS). */ +int +check_msgid_msgstr_format_i (const char *msgid, const char *msgid_plural, + const char *msgstr, size_t msgstr_len, + size_t i, + struct argument_range range, + const struct plural_distribution *distribution, + formatstring_error_logger_t error_logger) +{ + int seen_errors = 0; + + /* At runtime, we can assume the program passes arguments that fit well for + msgid. We must signal an error if msgstr wants more arguments that msgid + accepts. + If msgstr wants fewer arguments than msgid, it wouldn't lead to a crash + at runtime, but we nevertheless give an error because + 1) this situation occurs typically after the programmer has added some + arguments to msgid, so we must make the translator specially aware + of it (more than just "fuzzy"), + 2) it is generally wrong if a translation wants to ignore arguments that + are used by other translations. */ + + struct formatstring_parser *parser = formatstring_parsers[i]; + char *invalid_reason = NULL; + void *msgid_descr = + parser->parse (msgid_plural != NULL ? msgid_plural : msgid, false, NULL, + &invalid_reason); + + if (msgid_descr != NULL) + { + const char *pretty_msgid = + (msgid_plural != NULL ? "msgid_plural" : "msgid"); + char buf[18+1]; + const char *pretty_msgstr = "msgstr"; + bool has_plural_translations = (strlen (msgstr) + 1 < msgstr_len); + const char *p_end = msgstr + msgstr_len; + const char *p; + unsigned int j; + + for (p = msgstr, j = 0; p < p_end; p += strlen (p) + 1, j++) + { + void *msgstr_descr; + + if (msgid_plural != NULL) + { + sprintf (buf, "msgstr[%u]", j); + pretty_msgstr = buf; + } + + msgstr_descr = parser->parse (p, true, NULL, &invalid_reason); + + if (msgstr_descr != NULL) + { + /* Use strict checking (require same number of format + directives on both sides) if the message has no plurals, + or if msgid_plural exists but on the msgstr[] side + there is only msgstr[0], or if distribution->often[j] + indicates that the variant applies to infinitely many + values of N and the N range is not restricted in a way + that the variant applies to only one N. + Use relaxed checking when there are at least two + msgstr[] forms and the distribution does not give more + precise information. */ + bool strict_checking = + (msgid_plural == NULL + || !has_plural_translations + || (distribution != NULL + && distribution->often != NULL + && j < distribution->often_length + && distribution->often[j] + && !(has_range_p (range) + && distribution->histogram (distribution, + range.min, range.max, j) + <= 1))); + + if (parser->check (msgid_descr, msgstr_descr, + strict_checking, + error_logger, pretty_msgid, pretty_msgstr)) + seen_errors++; + + parser->free (msgstr_descr); + } + else + { + error_logger (_("\ +'%s' is not a valid %s format string, unlike '%s'. Reason: %s"), + pretty_msgstr, format_language_pretty[i], + pretty_msgid, invalid_reason); + seen_errors++; + free (invalid_reason); + } + } + + parser->free (msgid_descr); + } + else + free (invalid_reason); + + return seen_errors; +} + +/* Check whether both formats strings contain compatible format + specifications. + Return the number of errors that were seen. */ +int +check_msgid_msgstr_format (const char *msgid, const char *msgid_plural, + const char *msgstr, size_t msgstr_len, + const enum is_format is_format[NFORMATS], + struct argument_range range, + const struct plural_distribution *distribution, + formatstring_error_logger_t error_logger) +{ + int seen_errors = 0; + size_t i; + + /* We check only those messages for which the msgid's is_format flag + is one of 'yes' or 'possible'. We don't check msgids with is_format + 'no' or 'impossible', to obey the programmer's order. We don't check + msgids with is_format 'undecided' because that would introduce too + many checks, thus forcing the programmer to add "xgettext: no-c-format" + anywhere where a translator wishes to use a percent sign. */ + for (i = 0; i < NFORMATS; i++) + if (possible_format_p (is_format[i])) + seen_errors += check_msgid_msgstr_format_i (msgid, msgid_plural, + msgstr, msgstr_len, i, + range, + distribution, + error_logger); + + return seen_errors; +} diff --git a/gettext-tools/src/format.h b/gettext-tools/src/format.h new file mode 100644 index 0000000..d92532d --- /dev/null +++ b/gettext-tools/src/format.h @@ -0,0 +1,175 @@ +/* Format strings. + Copyright (C) 2001-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _FORMAT_H +#define _FORMAT_H + +#include + +#include "pos.h" /* Get lex_pos_ty. */ +#include "message.h" /* Get NFORMATS. */ +#include "plural-distrib.h" /* Get struct plural_distribution. */ +#include "error.h" /* Get fallback definition of __attribute__. */ + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* These indicators are set by the parse function at the appropriate + positions. */ +enum +{ + /* Set on the first byte of a format directive. */ + FMTDIR_START = 1 << 0, + /* Set on the last byte of a format directive. */ + FMTDIR_END = 1 << 1, + /* Set on the last byte of an invalid format directive, where a parse error + was recognized. */ + FMTDIR_ERROR = 1 << 2 +}; + +/* Macro for use inside a parser: + Sets an indicator at the position corresponding to PTR. + Assumes local variables 'fdi' and 'format_start' are defined. */ +#define FDI_SET(ptr, flag) \ + if (fdi != NULL) \ + fdi[(ptr) - format_start] |= (flag)/*;*/ + +/* This type of callback is responsible for showing an error. */ +typedef void (*formatstring_error_logger_t) (const char *format, ...) +#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) + __attribute__ ((__format__ (__printf__, 1, 2))) +#endif +; + +/* This structure describes a format string parser for a language. */ +struct formatstring_parser +{ + /* Parse the given string as a format string. + If translated is true, some extensions available only to msgstr but not + to msgid strings are recognized. + If fdi is non-NULL, it must be a an array of strlen (string) zero bytes. + Return a freshly allocated structure describing + 1. the argument types/names needed for the format string, + 2. the total number of format directives. + Return NULL if the string is not a valid format string. In this case, + also set *invalid_reason to an error message explaining why. + In both cases, set FMTDIR_* bits at the appropriate positions in fdi. */ + void * (*parse) (const char *string, bool translated, char *fdi, char **invalid_reason); + + /* Free a format string descriptor, returned by parse(). */ + void (*free) (void *descr); + + /* Return the number of format directives. + A string that can be output literally has 0 format directives. */ + int (*get_number_of_directives) (void *descr); + + /* Return true if the format string, although valid, contains directives that + make it appear unlikely that the string was meant as a format string. + A NULL function is equivalent to a function that always returns false. */ + bool (*is_unlikely_intentional) (void *descr); + + /* Verify that the argument types/names in msgid_descr and those in + msgstr_descr are the same (if equality=true), or (if equality=false) + that those of msgid_descr extend those of msgstr_descr (i.e. + msgstr_descr may omit some of the arguments of msgid_descr). + If not, signal an error using error_logger (only if error_logger != NULL) + and return true. Otherwise return false. */ + bool (*check) (void *msgid_descr, void *msgstr_descr, bool equality, formatstring_error_logger_t error_logger, const char *pretty_msgid, const char *pretty_msgstr); +}; + +/* Format string parsers, each defined in its own file. */ +extern DLL_VARIABLE struct formatstring_parser formatstring_c; +extern DLL_VARIABLE struct formatstring_parser formatstring_objc; +extern DLL_VARIABLE struct formatstring_parser formatstring_sh; +extern DLL_VARIABLE struct formatstring_parser formatstring_python; +extern DLL_VARIABLE struct formatstring_parser formatstring_python_brace; +extern DLL_VARIABLE struct formatstring_parser formatstring_lisp; +extern DLL_VARIABLE struct formatstring_parser formatstring_elisp; +extern DLL_VARIABLE struct formatstring_parser formatstring_librep; +extern DLL_VARIABLE struct formatstring_parser formatstring_scheme; +extern DLL_VARIABLE struct formatstring_parser formatstring_smalltalk; +extern DLL_VARIABLE struct formatstring_parser formatstring_java; +extern DLL_VARIABLE struct formatstring_parser formatstring_csharp; +extern DLL_VARIABLE struct formatstring_parser formatstring_awk; +extern DLL_VARIABLE struct formatstring_parser formatstring_pascal; +extern DLL_VARIABLE struct formatstring_parser formatstring_ycp; +extern DLL_VARIABLE struct formatstring_parser formatstring_tcl; +extern DLL_VARIABLE struct formatstring_parser formatstring_perl; +extern DLL_VARIABLE struct formatstring_parser formatstring_perl_brace; +extern DLL_VARIABLE struct formatstring_parser formatstring_php; +extern DLL_VARIABLE struct formatstring_parser formatstring_gcc_internal; +extern DLL_VARIABLE struct formatstring_parser formatstring_gfc_internal; +extern DLL_VARIABLE struct formatstring_parser formatstring_qt; +extern DLL_VARIABLE struct formatstring_parser formatstring_qt_plural; +extern DLL_VARIABLE struct formatstring_parser formatstring_kde; +extern DLL_VARIABLE struct formatstring_parser formatstring_boost; +extern DLL_VARIABLE struct formatstring_parser formatstring_lua; +extern DLL_VARIABLE struct formatstring_parser formatstring_javascript; + +/* Table of all format string parsers. */ +extern DLL_VARIABLE struct formatstring_parser *formatstring_parsers[NFORMATS]; + +/* Returns an array of the ISO C 99 format directives and other + format flags or directives with a system dependent expansion contained in + the argument string. *intervalsp is assigned to a freshly allocated array + of intervals (startpos pointing to '<', endpos to the character after '>'), + and *lengthp is assigned to the number of intervals in this array. */ +struct interval +{ + size_t startpos; + size_t endpos; +}; +extern void + get_sysdep_c_format_directives (const char *string, bool translated, + struct interval **intervalsp, size_t *lengthp); + +/* Returns the number of unnamed arguments consumed by a Python format + string. */ +extern unsigned int get_python_format_unnamed_arg_count (const char *string); + +/* Check whether both formats strings contain compatible format + specifications for format type i (0 <= i < NFORMATS). + Return the number of errors that were seen. */ +extern int + check_msgid_msgstr_format_i (const char *msgid, const char *msgid_plural, + const char *msgstr, size_t msgstr_len, + size_t i, + struct argument_range range, + const struct plural_distribution *distribution, + formatstring_error_logger_t error_logger); + +/* Check whether both formats strings contain compatible format + specifications. + Return the number of errors that were seen. */ +extern int + check_msgid_msgstr_format (const char *msgid, const char *msgid_plural, + const char *msgstr, size_t msgstr_len, + const enum is_format is_format[NFORMATS], + struct argument_range range, + const struct plural_distribution *distribution, + formatstring_error_logger_t error_logger); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _FORMAT_H */ diff --git a/gettext-tools/src/gnu/gettext/DumpResource.java b/gettext-tools/src/gnu/gettext/DumpResource.java new file mode 100644 index 0000000..77c6e01 --- /dev/null +++ b/gettext-tools/src/gnu/gettext/DumpResource.java @@ -0,0 +1,236 @@ +/* GNU gettext for Java + * Copyright (C) 2001-2003, 2007 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package gnu.gettext; + +import java.lang.reflect.*; +import java.util.*; +import java.io.*; + +/** + * This programs dumps a resource as a PO file. The resource must be + * accessible through the CLASSPATH. + * + * @author Bruno Haible + */ +public class DumpResource { + private Writer out; + private void dumpString (String str) throws IOException { + int n = str.length(); + out.write('"'); + for (int i = 0; i < n; i++) { + char c = str.charAt(i); + if (c == 0x0008) { + out.write('\\'); out.write('b'); + } else if (c == 0x000c) { + out.write('\\'); out.write('f'); + } else if (c == 0x000a) { + out.write('\\'); out.write('n'); + } else if (c == 0x000d) { + out.write('\\'); out.write('r'); + } else if (c == 0x0009) { + out.write('\\'); out.write('t'); + } else if (c == '\\' || c == '"') { + out.write('\\'); out.write(c); + } else + out.write(c); + } + out.write('"'); + } + private void dumpMessage (String msgid, String msgid_plural, Object msgstr) throws IOException { + int separatorPos = msgid.indexOf('\u0004'); + if (separatorPos >= 0) { + String msgctxt = msgid.substring(0,separatorPos); + msgid = msgid.substring(separatorPos+1); + out.write("msgctxt "); dumpString(msgctxt); + } + out.write("msgid "); dumpString(msgid); out.write('\n'); + if (msgid_plural != null) { + out.write("msgid_plural "); dumpString(msgid_plural); out.write('\n'); + for (int i = 0; i < ((String[])msgstr).length; i++) { + out.write("msgstr[" + i + "] "); + dumpString(((String[])msgstr)[i]); + out.write('\n'); + } + } else { + out.write("msgstr "); dumpString((String)msgstr); out.write('\n'); + } + out.write('\n'); + } + private ResourceBundle catalog; + private Method lookupMethod; + // Lookup the value corresponding to a key found in catalog.getKeys(). + // Here we assume that the catalog returns a non-inherited value for + // these keys. FIXME: Not true. Better see whether handleGetObject is + // public - it is in ListResourceBundle and PropertyResourceBundle. + private Object lookup (String key) { + Object value = null; + if (lookupMethod != null) { + try { + value = lookupMethod.invoke(catalog, new Object[] { key }); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.getTargetException().printStackTrace(); + } + } else { + try { + value = catalog.getObject(key); + } catch (MissingResourceException e) { + } + } + return value; + } + private void dump () throws IOException { + lookupMethod = null; + try { + lookupMethod = catalog.getClass().getMethod("lookup", new Class[] { java.lang.String.class }); + } catch (NoSuchMethodException e) { + } catch (SecurityException e) { + } + Method pluralMethod = null; + try { + pluralMethod = catalog.getClass().getMethod("get_msgid_plural_table", new Class[0]); + } catch (NoSuchMethodException e) { + } catch (SecurityException e) { + } + Field pluralField = null; + try { + pluralField = catalog.getClass().getField("plural"); + } catch (NoSuchFieldException e) { + } catch (SecurityException e) { + } + // Search for the header entry. + { + Object header_entry = null; + Enumeration keys = catalog.getKeys(); + while (keys.hasMoreElements()) + if ("".equals(keys.nextElement())) { + header_entry = lookup(""); + break; + } + // If there is no header entry, fake one. + // FIXME: This is not needed; right after po_lex_charset_init set + // the PO charset to UTF-8. + if (header_entry == null) + header_entry = "Content-Type: text/plain; charset=UTF-8\n"; + dumpMessage("",null,header_entry); + } + // Now the other messages. + { + Enumeration keys = catalog.getKeys(); + Object plural = null; + if (pluralMethod != null) { + // msgfmt versions > 0.13.1 create a static get_msgid_plural_table() + // method. + try { + plural = pluralMethod.invoke(catalog, new Object[0]); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.getTargetException().printStackTrace(); + } + } else if (pluralField != null) { + // msgfmt versions <= 0.13.1 create a static plural field. + try { + plural = pluralField.get(catalog); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + if (plural instanceof String[]) { + // A GNU gettext created class with plural handling, Java2 format. + int i = 0; + while (keys.hasMoreElements()) { + String key = (String)keys.nextElement(); + Object value = lookup(key); + String key_plural = (value instanceof String[] ? ((String[])plural)[i++] : null); + if (!"".equals(key)) + dumpMessage(key,key_plural,value); + } + if (i != ((String[])plural).length) + throw new RuntimeException("wrong plural field length"); + } else if (plural instanceof Hashtable) { + // A GNU gettext created class with plural handling, Java format. + while (keys.hasMoreElements()) { + String key = (String)keys.nextElement(); + if (!"".equals(key)) { + Object value = lookup(key); + String key_plural = (value instanceof String[] ? (String)((Hashtable)plural).get(key) : null); + dumpMessage(key,key_plural,value); + } + } + } else if (plural == null) { + // No plural handling. + while (keys.hasMoreElements()) { + String key = (String)keys.nextElement(); + if (!"".equals(key)) + dumpMessage(key,null,lookup(key)); + } + } else + throw new RuntimeException("wrong plural field value"); + } + } + + public DumpResource (String resource_name, String locale_name) { + // Split locale_name into language_country_variant. + String language; + String country; + String variant; + language = locale_name; + { + int i = language.indexOf('_'); + if (i >= 0) { + country = language.substring(i+1); + language = language.substring(0,i); + } else + country = ""; + } + { + int j = country.indexOf('_'); + if (j >= 0) { + variant = country.substring(j+1); + country = country.substring(0,j); + } else + variant = ""; + } + Locale locale = new Locale(language,country,variant); + // Get the resource. + ResourceBundle catalog = ResourceBundle.getBundle(resource_name,locale); + // We are only interested in the messsages belonging to the locale + // itself, not in the inherited messages. But catalog.getLocale() exists + // only in Java2 and sometimes differs from the given locale. + try { + Writer w1 = new OutputStreamWriter(System.out,"UTF8"); + Writer w2 = new BufferedWriter(w1); + this.out = w2; + this.catalog = catalog; + dump(); + w2.close(); + w1.close(); + System.out.flush(); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } + + public static void main (String[] args) { + new DumpResource(args[0], args.length > 1 ? args[1] : ""); + System.exit(0); + } +} diff --git a/gettext-tools/src/gnu/gettext/GetURL.java b/gettext-tools/src/gnu/gettext/GetURL.java new file mode 100644 index 0000000..f42ccc4 --- /dev/null +++ b/gettext-tools/src/gnu/gettext/GetURL.java @@ -0,0 +1,81 @@ +/* Fetch an URL's contents. + * Copyright (C) 2001, 2008 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package gnu.gettext; + +import java.io.*; +import java.net.*; + +/** + * Fetch an URL's contents and emit it to standard output. + * Exit code: 0 = success + * 1 = failure + * 2 = timeout + * @author Bruno Haible + */ +public class GetURL { + // Use a separate thread to signal a timeout error if the URL cannot + // be accessed and completely read within a given amount of time. + private static long timeout = 30*1000; // 30 seconds + private boolean done; + private Thread timeoutThread; + public void fetch (String s) { + URL url; + try { + url = new URL(s); + } catch (MalformedURLException e) { + System.exit(1); + return; + } + done = false; + timeoutThread = + new Thread() { + public void run () { + try { + sleep(timeout); + if (!done) { + System.exit(2); + } + } catch (InterruptedException e) { + } + } + }; + timeoutThread.start(); + try { + InputStream istream = new BufferedInputStream(url.openStream()); + OutputStream ostream = new BufferedOutputStream(System.out); + for (;;) { + int b = istream.read(); + if (b < 0) break; + ostream.write(b); + } + ostream.close(); + System.out.flush(); + istream.close(); + } catch (IOException e) { + //e.printStackTrace(); + System.exit(1); + } + done = true; + } + public static void main (String[] args) { + if (args.length != 1) + System.exit(1); + (new GetURL()).fetch(args[0]); + System.exit(0); + } +} diff --git a/gettext-tools/src/hostname.c b/gettext-tools/src/hostname.c new file mode 100644 index 0000000..a7032b3 --- /dev/null +++ b/gettext-tools/src/hostname.c @@ -0,0 +1,389 @@ +/* Display hostname in various forms. + Copyright (C) 2001-2003, 2006-2007, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#if defined _WIN32 || defined __WIN32__ +# define WIN32_NATIVE +#endif + +/* Get gethostname(). */ +#include + +#ifdef WIN32_NATIVE +/* Native Woe32 API lacks gethostname() but has GetComputerName() instead. */ +# include +#else +/* Some systems, like early Solaris versions, lack gethostname() but + have uname() instead. */ +# if !HAVE_GETHOSTNAME +# include +# endif +#endif + +/* Get MAXHOSTNAMELEN. */ +#if HAVE_SYS_PARAM_H +# include +#endif +#ifndef MAXHOSTNAMELEN +# define MAXHOSTNAMELEN 64 +#endif + +/* Support for using gethostbyname(). */ +#if HAVE_GETHOSTBYNAME +# include +# include /* defines AF_INET, AF_INET6 */ +# include /* declares ntohs(), defines struct sockaddr_in */ +# if HAVE_ARPA_INET_H +# include /* declares inet_ntoa(), inet_ntop() */ +# endif +# if HAVE_IPV6 +# if !defined(__CYGWIN__) /* Cygwin has only s6_addr, no s6_addr16 */ +# if defined(__APPLE__) && defined(__MACH__) /* MacOS X */ +# define in6_u __u6_addr +# define u6_addr16 __u6_addr16 +# endif + /* Use s6_addr16 for portability. See RFC 2553. */ +# ifndef s6_addr16 +# define s6_addr16 in6_u.u6_addr16 +# endif +# define HAVE_IN6_S6_ADDR16 1 +# endif +# endif +# include /* defines struct hostent, declares gethostbyname() */ +#endif + +/* Include this after , to avoid a syntax error on BeOS. */ +#include + +#include "closeout.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "xalloc.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Output format. */ +static enum { default_format, short_format, long_format, ip_format } format; + +/* Long options. */ +static const struct option long_options[] = +{ + { "fqdn", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "ip-address", no_argument, NULL, 'i' }, + { "long", no_argument, NULL, 'f' }, + { "short", no_argument, NULL, 's' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void print_hostname (void); + +int +main (int argc, char *argv[]) +{ + int optchar; + bool do_help; + bool do_version; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + format = default_format; + + /* Parse command line options. */ + while ((optchar = getopt_long (argc, argv, "fhisV", long_options, NULL)) + != EOF) + switch (optchar) + { + case '\0': /* Long option. */ + break; + case 'f': + format = long_format; + break; + case 's': + format = short_format; + break; + case 'i': + format = ip_format; + break; + case 'h': + do_help = true; + break; + case 'V': + do_version = true; + break; + default: + usage (EXIT_FAILURE); + /* NOTREACHED */ + } + + /* Version information requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2003, 2006-2007"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test for extraneous arguments. */ + if (optind != argc) + error (EXIT_FAILURE, 0, _("too many arguments")); + + /* Get and print the hostname. */ + print_hostname (); + + exit (EXIT_SUCCESS); +} + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]\n\ +"), program_name); + printf ("\n"); + printf (_("\ +Print the machine's hostname.\n")); + printf ("\n"); + printf (_("\ +Output format:\n")); + printf (_("\ + -s, --short short host name\n")); + printf (_("\ + -f, --fqdn, --long long host name, includes fully qualified domain\n\ + name, and aliases\n")); + printf (_("\ + -i, --ip-address addresses for the hostname\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + +/* Returns an xmalloc()ed string containing the machine's host name. */ +static char * +xgethostname () +{ +#ifdef WIN32_NATIVE + char hostname[MAX_COMPUTERNAME_LENGTH+1]; + DWORD size = sizeof (hostname); + + if (!GetComputerName (hostname, &size)) + error (EXIT_FAILURE, 0, _("could not get host name")); + return xstrdup (hostname); +#elif HAVE_GETHOSTNAME + char hostname[MAXHOSTNAMELEN+1]; + + if (gethostname (hostname, MAXHOSTNAMELEN) < 0) + error (EXIT_FAILURE, errno, _("could not get host name")); + hostname[MAXHOSTNAMELEN] = '\0'; + return xstrdup (hostname); +#else + struct utsname utsname; + + if (uname (&utsname) < 0) + error (EXIT_FAILURE, errno, _("could not get host name")); + return xstrdup (utsname.nodename); +#endif +} + +/* Converts an AF_INET address to a printable, presentable format. + BUFFER is an array with at least 15+1 bytes. ADDR is 'struct in_addr'. */ +#if HAVE_INET_NTOP +# define ipv4_ntop(buffer,addr) \ + inet_ntop (AF_INET, &addr, buffer, 15+1) +#else +# define ipv4_ntop(buffer,addr) \ + strcpy (buffer, inet_ntoa (addr)) +#endif + +#if HAVE_IPV6 +/* Converts an AF_INET6 address to a printable, presentable format. + BUFFER is an array with at least 45+1 bytes. ADDR is 'struct in6_addr'. */ +# if HAVE_INET_NTOP +# define ipv6_ntop(buffer,addr) \ + inet_ntop (AF_INET6, &addr, buffer, 45+1) +# elif HAVE_IN6_S6_ADDR16 +# define ipv6_ntop(buffer,addr) \ + sprintf (buffer, "%x:%x:%x:%x:%x:%x:%x:%x", \ + ntohs ((addr).s6_addr16[0]), \ + ntohs ((addr).s6_addr16[1]), \ + ntohs ((addr).s6_addr16[2]), \ + ntohs ((addr).s6_addr16[3]), \ + ntohs ((addr).s6_addr16[4]), \ + ntohs ((addr).s6_addr16[5]), \ + ntohs ((addr).s6_addr16[6]), \ + ntohs ((addr).s6_addr16[7])) +# else +# define ipv6_ntop(buffer,addr) \ + sprintf (buffer, "%x:%x:%x:%x:%x:%x:%x:%x", \ + ((addr).s6_addr[0] << 8) | (addr).s6_addr[1], \ + ((addr).s6_addr[2] << 8) | (addr).s6_addr[3], \ + ((addr).s6_addr[4] << 8) | (addr).s6_addr[5], \ + ((addr).s6_addr[6] << 8) | (addr).s6_addr[7], \ + ((addr).s6_addr[8] << 8) | (addr).s6_addr[9], \ + ((addr).s6_addr[10] << 8) | (addr).s6_addr[11], \ + ((addr).s6_addr[12] << 8) | (addr).s6_addr[13], \ + ((addr).s6_addr[14] << 8) | (addr).s6_addr[15]) +# endif +#endif + +/* Print the hostname according to the specified format. */ +static void +print_hostname () +{ + char *hostname; + char *dot; +#if HAVE_GETHOSTBYNAME + struct hostent *h; + size_t i; +#endif + + hostname = xgethostname (); + + switch (format) + { + case default_format: + /* Print the hostname, as returned by the system call. */ + printf ("%s\n", hostname); + break; + + case short_format: + /* Print only the part before the first dot. */ + dot = strchr (hostname, '.'); + if (dot != NULL) + *dot = '\0'; + printf ("%s\n", hostname); + break; + + case long_format: + /* Look for netwide usable hostname and aliases using gethostbyname(). */ +#if HAVE_GETHOSTBYNAME + h = gethostbyname (hostname); + if (h != NULL) + { + printf ("%s\n", h->h_name); + if (h->h_aliases != NULL) + for (i = 0; h->h_aliases[i] != NULL; i++) + printf ("%s\n", h->h_aliases[i]); + } + else +#endif + printf ("%s\n", hostname); + break; + + case ip_format: + /* Look for netwide usable IP addresses using gethostbyname(). */ +#if HAVE_GETHOSTBYNAME + h = gethostbyname (hostname); + if (h != NULL && h->h_addr_list != NULL) + for (i = 0; h->h_addr_list[i] != NULL; i++) + { +#if HAVE_IPV6 + if (h->h_addrtype == AF_INET6) + { + char buffer[45+1]; + ipv6_ntop (buffer, *(const struct in6_addr*) h->h_addr_list[i]); + printf("[%s]\n", buffer); + } + else +#endif + if (h->h_addrtype == AF_INET) + { + char buffer[15+1]; + ipv4_ntop (buffer, *(const struct in_addr*) h->h_addr_list[i]); + printf("[%s]\n", buffer); + } + } +#endif + break; + + default: + abort (); + } +} diff --git a/gettext-tools/src/lang-table.c b/gettext-tools/src/lang-table.c new file mode 100644 index 0000000..9cc1f55 --- /dev/null +++ b/gettext-tools/src/lang-table.c @@ -0,0 +1,315 @@ +/* Table of languages. + Copyright (C) 2001-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2005. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "lang-table.h" + +/* Derived from ISO 639. */ +struct language_table_entry language_table[] = + { + { "aa", "Afar" }, + { "ab", "Abkhazian" }, + { "ace", "Achinese" }, + { "ae", "Avestan" }, + { "af", "Afrikaans" }, + { "ak", "Akan" }, + { "am", "Amharic" }, + { "an", "Aragonese" }, + { "ang", "Old English" }, + { "ar", "Arabic" }, + { "arn", "Mapudungun" }, + { "as", "Assamese" }, + { "ast", "Asturian" }, + { "av", "Avaric" }, + { "awa", "Awadhi" }, + { "ay", "Aymara" }, + { "az", "Azerbaijani" }, + { "ba", "Bashkir" }, + { "bal", "Baluchi" }, + { "ban", "Balinese" }, + { "be", "Belarusian" }, + { "bej", "Beja" }, + { "bem", "Bemba" }, + { "bg", "Bulgarian" }, + { "bh", "Bihari" }, + { "bho", "Bhojpuri" }, + { "bi", "Bislama" }, + { "bik", "Bikol" }, + { "bin", "Bini" }, + { "bm", "Bambara" }, + { "bn", "Bengali" }, + { "bo", "Tibetan" }, + { "br", "Breton" }, + { "bs", "Bosnian" }, + { "bug", "Buginese" }, + { "ca", "Catalan" }, + { "ce", "Chechen" }, + { "ceb", "Cebuano" }, + { "ch", "Chamorro" }, + { "co", "Corsican" }, + { "cr", "Cree" }, + { "crh", "Crimean Tatar" }, + { "cs", "Czech" }, + { "csb", "Kashubian" }, + { "cu", "Church Slavic" }, + { "cv", "Chuvash" }, + { "cy", "Welsh" }, + { "da", "Danish" }, + { "de", "German" }, + { "din", "Dinka" }, + { "doi", "Dogri" }, + { "dsb", "Lower Sorbian" }, + { "dv", "Divehi" }, + { "dz", "Dzongkha" }, + { "ee", "Ewe" }, + { "el", "Greek" }, + { "en", "English" }, + { "eo", "Esperanto" }, + { "es", "Spanish" }, + { "et", "Estonian" }, + { "eu", "Basque" }, + { "fa", "Persian" }, + { "ff", "Fulah" }, + { "fi", "Finnish" }, + { "fil", "Filipino" }, + { "fj", "Fijian" }, + { "fo", "Faroese" }, + { "fon", "Fon" }, + { "fr", "French" }, + { "fur", "Friulian" }, + { "fy", "Western Frisian" }, + { "ga", "Irish" }, + { "gd", "Scottish Gaelic" }, + { "gl", "Galician" }, + { "gn", "Guarani" }, + { "gon", "Gondi" }, + { "gsw", "Swiss German" }, /* can also be "Alsatian" */ + { "gu", "Gujarati" }, + { "gv", "Manx" }, + { "ha", "Hausa" }, + { "he", "Hebrew" }, + { "hi", "Hindi" }, + { "hil", "Hiligaynon" }, + { "hmn", "Hmong" }, + { "ho", "Hiri Motu" }, + { "hr", "Croatian" }, + { "hsb", "Upper Sorbian" }, + { "ht", "Haitian" }, + { "hu", "Hungarian" }, + { "hy", "Armenian" }, + { "hz", "Herero" }, + { "ia", "Interlingua" }, + { "id", "Indonesian" }, + { "ie", "Interlingue" }, + { "ig", "Igbo" }, + { "ii", "Sichuan Yi" }, + { "ik", "Inupiak" }, + { "ilo", "Iloko" }, + { "is", "Icelandic" }, + { "it", "Italian" }, + { "iu", "Inuktitut" }, + { "ja", "Japanese" }, + { "jab", "Hyam" }, + { "jv", "Javanese" }, + { "ka", "Georgian" }, + { "kab", "Kabyle" }, + { "kaj", "Jju" }, + { "kam", "Kamba" }, + { "kbd", "Kabardian" }, + { "kcg", "Tyap" }, + { "kdm", "Kagoma" }, + { "kg", "Kongo" }, + { "ki", "Kikuyu" }, + { "kj", "Kuanyama" }, + { "kk", "Kazakh" }, + { "kl", "Kalaallisut" }, + { "km", "Central Khmer" }, + { "kmb", "Kimbundu" }, + { "kn", "Kannada" }, + { "ko", "Korean" }, + { "kr", "Kanuri" }, + { "kru", "Kurukh" }, + { "ks", "Kashmiri" }, + { "ku", "Kurdish" }, + { "kv", "Komi" }, + { "kw", "Cornish" }, + { "ky", "Kirghiz" }, + { "kok", "Konkani" }, + { "la", "Latin" }, + { "lb", "Letzeburgesch" }, + { "lg", "Ganda" }, + { "li", "Limburgish" }, + { "ln", "Lingala" }, + { "lo", "Laotian" }, + { "lt", "Lithuanian" }, + { "lu", "Luba-Katanga" }, + { "lua", "Luba-Lulua" }, + { "luo", "Luo" }, + { "lv", "Latvian" }, + { "mad", "Madurese" }, + { "mag", "Magahi" }, + { "mai", "Maithili" }, + { "mak", "Makasar" }, + { "man", "Mandingo" }, + { "men", "Mende" }, + { "mg", "Malagasy" }, + { "mh", "Marshallese" }, + { "mi", "Maori" }, + { "min", "Minangkabau" }, + { "mk", "Macedonian" }, + { "ml", "Malayalam" }, + { "mn", "Mongolian" }, + { "mni", "Manipuri" }, + { "mo", "Moldavian" }, + { "moh", "Mohawk" }, + { "mos", "Mossi" }, + { "mr", "Marathi" }, + { "ms", "Malay" }, + { "mt", "Maltese" }, + { "mwr", "Marwari" }, + { "my", "Burmese" }, + { "myn", "Mayan" }, + { "na", "Nauru" }, + { "nap", "Neapolitan" }, + { "nah", "Nahuatl" }, + { "nb", "Norwegian Bokmal" }, + { "nd", "North Ndebele" }, + { "nds", "Low Saxon" }, + { "ne", "Nepali" }, + { "ng", "Ndonga" }, + { "nl", "Dutch" }, + { "nn", "Norwegian Nynorsk" }, + { "no", "Norwegian" }, + { "nr", "South Ndebele" }, + { "nso", "Northern Sotho" }, + { "nv", "Navajo" }, + { "ny", "Nyanja" }, + { "nym", "Nyamwezi" }, + { "nyn", "Nyankole" }, + { "oc", "Occitan" }, + { "oj", "Ojibwa" }, + { "om", "(Afan) Oromo" }, + { "or", "Oriya" }, + { "os", "Ossetian" }, + { "pa", "Punjabi" }, + { "pag", "Pangasinan" }, + { "pam", "Pampanga" }, + { "pap", "Papiamento" }, + { "pbb", "Páez" }, + { "pi", "Pali" }, + { "pl", "Polish" }, + { "ps", "Pashto" }, + { "pt", "Portuguese" }, + { "qu", "Quechua" }, + { "raj", "Rajasthani" }, + { "rm", "Romansh" }, + { "rn", "Kirundi" }, + { "ro", "Romanian" }, + { "ru", "Russian" }, + { "rw", "Kinyarwanda" }, + { "sa", "Sanskrit" }, + { "sah", "Yakut" }, + { "sas", "Sasak" }, + { "sat", "Santali" }, + { "sc", "Sardinian" }, + { "scn", "Sicilian" }, + { "sd", "Sindhi" }, + { "se", "Northern Sami" }, + { "sg", "Sango" }, + { "shn", "Shan" }, + { "si", "Sinhala" }, + { "sid", "Sidamo" }, + { "sk", "Slovak" }, + { "sl", "Slovenian" }, + { "sm", "Samoan" }, + { "sma", "Southern Sami" }, + { "smj", "Lule Sami" }, + { "smn", "Inari Sami" }, + { "sms", "Skolt Sami" }, + { "sn", "Shona" }, + { "so", "Somali" }, + { "sq", "Albanian" }, + { "sr", "Serbian" }, + { "srr", "Serer" }, + { "ss", "Siswati" }, + { "st", "Sesotho" }, + { "su", "Sundanese" }, + { "suk", "Sukuma" }, + { "sus", "Susu" }, + { "sv", "Swedish" }, + { "sw", "Swahili" }, + { "ta", "Tamil" }, + { "te", "Telugu" }, + { "tem", "Timne" }, + { "tet", "Tetum" }, + { "tg", "Tajik" }, + { "th", "Thai" }, + { "ti", "Tigrinya" }, + { "tiv", "Tiv" }, + { "tk", "Turkmen" }, + { "tl", "Tagalog" }, + { "tn", "Setswana" }, + { "to", "Tonga" }, + { "tr", "Turkish" }, + { "ts", "Tsonga" }, + { "tt", "Tatar" }, + { "tum", "Tumbuka" }, + { "tw", "Twi" }, + { "ty", "Tahitian" }, + { "ug", "Uighur" }, + { "uk", "Ukrainian" }, + { "umb", "Umbundu" }, + { "ur", "Urdu" }, + { "uz", "Uzbek" }, + { "ve", "Venda" }, + { "vi", "Vietnamese" }, + { "vo", "Volapuk" }, + { "wal", "Walamo" }, + { "war", "Waray" }, + { "wen", "Sorbian" }, + { "wo", "Wolof" }, + { "xh", "Xhosa" }, + { "yao", "Yao" }, + { "yi", "Yiddish" }, + { "yo", "Yoruba" }, + { "za", "Zhuang" }, + { "zh", "Chinese" }, + { "zu", "Zulu" }, + { "zap", "Zapotec" } + }; +const size_t language_table_size = sizeof (language_table) / sizeof (language_table[0]); + +/* The language names for variants of languages, according to the catalog name + (usually built from the language code and territory code). + Should be consistent with the list of languages found on the TP site, see + the URL contained in gettext-tools/projects/TP/teams.url. */ +struct language_table_entry language_variant_table[] = + { + { "de_AT", "Austrian" }, + { "en_GB", "English (British)" }, + { "es_AR", "Argentinian" }, + { "es_IC", "Spanish (Canary Islands)" }, + { "pt_BR", "Brazilian Portuguese" }, + { "zh_CN", "Chinese (simplified)" }, + { "zh_HK", "Chinese (Hong Kong)" }, + { "zh_TW", "Chinese (traditional)" } + }; +const size_t language_variant_table_size = sizeof (language_variant_table) / sizeof (language_variant_table[0]); diff --git a/gettext-tools/src/lang-table.h b/gettext-tools/src/lang-table.h new file mode 100644 index 0000000..e72e4c4 --- /dev/null +++ b/gettext-tools/src/lang-table.h @@ -0,0 +1,35 @@ +/* Table of languages. + Copyright (C) 2001-2007 Free Software Foundation, Inc. + Written by Bruno Haible , 2005. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _LANG_TABLE_H +#define _LANG_TABLE_H + +#include + +struct language_table_entry +{ + const char *code; + const char *english; +}; + +extern struct language_table_entry language_table[]; +extern const size_t language_table_size; + +extern struct language_table_entry language_variant_table[]; +extern const size_t language_variant_table_size; + +#endif /* _LANG_TABLE_H */ diff --git a/gettext-tools/src/libexpat-compat.c b/gettext-tools/src/libexpat-compat.c new file mode 100644 index 0000000..ad680db --- /dev/null +++ b/gettext-tools/src/libexpat-compat.c @@ -0,0 +1,326 @@ +/* xgettext libexpat compatibility. + Copyright (C) 2002-2003, 2005-2009, 2013 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#if DYNLOAD_LIBEXPAT +# include +#else +# if HAVE_LIBEXPAT +# include +# endif +#endif + +/* Keep the references to XML_GetCurrent{Line,Column}Number symbols + before loading libexpat-compat.h, since they are redefined to + rpl_XML_GetCurrent{Line,Column}Number . */ +#if !DYNLOAD_LIBEXPAT && XML_MAJOR_VERSION >= 2 +static void *p_XML_GetCurrentLineNumber = (void *) &XML_GetCurrentLineNumber; +static void *p_XML_GetCurrentColumnNumber = (void *) &XML_GetCurrentColumnNumber; +#endif + +#include "libexpat-compat.h" + +/* ======================= Different libexpat ABIs. ======================= */ + +/* There are three different ABIs of libexpat, regarding the functions + XML_GetCurrentLineNumber and XML_GetCurrentColumnNumber. + In expat < 2.0, they return an 'int'. + In expat >= 2.0, they return + - a 'long' if expat was compiled with the default flags, or + - a 'long long' if expat was compiled with -DXML_LARGE_SIZE. + But the include file does not contain the information whether + expat was compiled with -DXML_LARGE_SIZE; so the include file is lying! + For this information, we need to call XML_GetFeatureList(), for + expat >= 2.0.1; for expat = 2.0.0, we have to assume the default flags. */ + +#if !DYNLOAD_LIBEXPAT && XML_MAJOR_VERSION >= 2 + +/* expat >= 2.0 -> Return type is 'int64_t' worst-case. */ + +/* Return true if libexpat was compiled with -DXML_LARGE_SIZE. */ +static bool +is_XML_LARGE_SIZE_ABI (void) +{ + static bool tested; + static bool is_large; + + if (!tested) + { + const XML_Feature *features; + + is_large = false; + for (features = XML_GetFeatureList (); features->name != NULL; features++) + if (strcmp (features->name, "XML_LARGE_SIZE") == 0) + { + is_large = true; + break; + } + + tested = true; + } + return is_large; +} + +int64_t +rpl_XML_GetCurrentLineNumber (XML_Parser parser) +{ + if (is_XML_LARGE_SIZE_ABI ()) + return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); + else + return ((long (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); +} + +int64_t +rpl_XML_GetCurrentColumnNumber (XML_Parser parser) +{ + if (is_XML_LARGE_SIZE_ABI ()) + return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); + else + return ((long (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); +} +#endif + + +/* ===================== Dynamic loading of libexpat. ===================== */ + +#if DYNLOAD_LIBEXPAT + +static XML_Expat_Version (*p_XML_ExpatVersionInfo) (void); + +XML_Expat_Version +XML_ExpatVersionInfo (void) +{ + return (*p_XML_ExpatVersionInfo) (); +} + +static const XML_Feature * (*p_XML_GetFeatureList) (void); + +const XML_Feature * +XML_GetFeatureList (void) +{ + return (*p_XML_GetFeatureList) (); +} + +enum XML_Size_ABI +get_XML_Size_ABI (void) +{ + static bool tested; + static enum XML_Size_ABI abi; + + if (!tested) + { + if (XML_ExpatVersionInfo () .major >= 2) + /* expat >= 2.0 -> XML_Size is 'int64_t' or 'long'. */ + { + const XML_Feature *features; + + abi = is_long; + for (features = XML_GetFeatureList (); + features->name != NULL; + features++) + if (strcmp (features->name, "XML_LARGE_SIZE") == 0) + { + abi = is_int64_t; + break; + } + } + else + /* expat < 2.0 -> XML_Size is 'int'. */ + abi = is_int; + tested = true; + } + return abi; +} + +static XML_Parser (*p_XML_ParserCreate) (const XML_Char *encoding); + +XML_Parser +XML_ParserCreate (const XML_Char *encoding) +{ + return (*p_XML_ParserCreate) (encoding); +} + +static void (*p_XML_SetElementHandler) (XML_Parser parser, + XML_StartElementHandler start, + XML_EndElementHandler end); + +void +XML_SetElementHandler (XML_Parser parser, + XML_StartElementHandler start, + XML_EndElementHandler end) +{ + (*p_XML_SetElementHandler) (parser, start, end); +} + + +static void (*p_XML_SetCharacterDataHandler) (XML_Parser parser, + XML_CharacterDataHandler handler); + +void +XML_SetCharacterDataHandler (XML_Parser parser, + XML_CharacterDataHandler handler) +{ + (*p_XML_SetCharacterDataHandler) (parser, handler); +} + + +static void (*p_XML_SetCommentHandler) (XML_Parser parser, + XML_CommentHandler handler); + +void +XML_SetCommentHandler (XML_Parser parser, XML_CommentHandler handler) +{ + (*p_XML_SetCommentHandler) (parser, handler); +} + + +static int (*p_XML_Parse) (XML_Parser parser, const char *s, + int len, int isFinal); + +int +XML_Parse (XML_Parser parser, const char *s, int len, int isFinal) +{ + return (*p_XML_Parse) (parser, s, len, isFinal); +} + + +static enum XML_Error (*p_XML_GetErrorCode) (XML_Parser parser); + +enum XML_Error +XML_GetErrorCode (XML_Parser parser) +{ + return (*p_XML_GetErrorCode) (parser); +} + + +static void *p_XML_GetCurrentLineNumber; + +int64_t +XML_GetCurrentLineNumber (XML_Parser parser) +{ + switch (get_XML_Size_ABI ()) + { + case is_int: + return ((int (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); + case is_long: + return ((long (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); + case is_int64_t: + return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); + default: + abort (); + } +} + +static void *p_XML_GetCurrentColumnNumber; + +int64_t +XML_GetCurrentColumnNumber (XML_Parser parser) +{ + switch (get_XML_Size_ABI ()) + { + case is_int: + return ((int (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); + case is_long: + return ((long (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); + case is_int64_t: + return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); + default: + abort (); + } +} + + +static const XML_LChar * (*p_XML_ErrorString) (int code); + +const XML_LChar * +XML_ErrorString (int code) +{ + return (*p_XML_ErrorString) (code); +} + +static void (*p_XML_ParserFree) (XML_Parser parser); + +void +XML_ParserFree (XML_Parser parser) +{ + return (*p_XML_ParserFree) (parser); +} + +static int libexpat_loaded = 0; + +bool +load_libexpat () +{ + if (libexpat_loaded == 0) + { + void *handle; + + /* Try to load libexpat-2.x. */ + handle = dlopen ("libexpat.so.1", RTLD_LAZY); + if (handle == NULL) + /* Try to load libexpat-1.x. */ + handle = dlopen ("libexpat.so.0", RTLD_LAZY); + if (handle != NULL + && (p_XML_ExpatVersionInfo = + (XML_Expat_Version (*) (void)) + dlsym (handle, "XML_ExpatVersionInfo")) != NULL + && (p_XML_GetFeatureList = + (const XML_Feature * (*) (void)) + dlsym (handle, "XML_GetFeatureList")) != NULL + && (p_XML_ParserCreate = + (XML_Parser (*) (const XML_Char *)) + dlsym (handle, "XML_ParserCreate")) != NULL + && (p_XML_SetElementHandler = + (void (*) (XML_Parser, XML_StartElementHandler, XML_EndElementHandler)) + dlsym (handle, "XML_SetElementHandler")) != NULL + && (p_XML_SetCharacterDataHandler = + (void (*) (XML_Parser, XML_CharacterDataHandler)) + dlsym (handle, "XML_SetCharacterDataHandler")) != NULL + && (p_XML_SetCommentHandler = + (void (*) (XML_Parser, XML_CommentHandler)) + dlsym (handle, "XML_SetCommentHandler")) != NULL + && (p_XML_Parse = + (int (*) (XML_Parser, const char *, int, int)) + dlsym (handle, "XML_Parse")) != NULL + && (p_XML_GetErrorCode = + (enum XML_Error (*) (XML_Parser)) + dlsym (handle, "XML_GetErrorCode")) != NULL + && (p_XML_GetCurrentLineNumber = + dlsym (handle, "XML_GetCurrentLineNumber")) != NULL + && (p_XML_GetCurrentColumnNumber = + dlsym (handle, "XML_GetCurrentColumnNumber")) != NULL + && (p_XML_ParserFree = + (void (*) (XML_Parser)) + dlsym (handle, "XML_ParserFree")) != NULL + && (p_XML_ErrorString = + (const XML_LChar * (*) (int)) + dlsym (handle, "XML_ErrorString")) != NULL) + libexpat_loaded = 1; + else + libexpat_loaded = -1; + } + return libexpat_loaded >= 0; +} + +#endif diff --git a/gettext-tools/src/libexpat-compat.h b/gettext-tools/src/libexpat-compat.h new file mode 100644 index 0000000..2ff6465 --- /dev/null +++ b/gettext-tools/src/libexpat-compat.h @@ -0,0 +1,94 @@ +/* xgettext libexpat compatibility. + Copyright (C) 2002-2003, 2005-2009, 2013 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include +#include +#if DYNLOAD_LIBEXPAT +# include +#else +# if HAVE_LIBEXPAT +# include +# endif +#endif + +#if !DYNLOAD_LIBEXPAT && XML_MAJOR_VERSION >= 2 +int64_t rpl_XML_GetCurrentLineNumber (XML_Parser parser); +# undef XML_GetCurrentLineNumber +# define XML_GetCurrentLineNumber rpl_XML_GetCurrentLineNumber + +int64_t rpl_XML_GetCurrentColumnNumber (XML_Parser parser); +# undef XML_GetCurrentColumnNumber +# define XML_GetCurrentColumnNumber rpl_XML_GetCurrentColumnNumber +#endif + +/* ===================== Dynamic loading of libexpat. ===================== */ + +#if DYNLOAD_LIBEXPAT +typedef struct + { + int major; + int minor; + int micro; + } + XML_Expat_Version; +enum XML_FeatureEnum { XML_FEATURE_END = 0 }; +typedef struct + { + enum XML_FeatureEnum feature; + const char *name; + long int value; + } + XML_Feature; +typedef void *XML_Parser; +typedef char XML_Char; +typedef char XML_LChar; +enum XML_Error { XML_ERROR_NONE }; +typedef void (*XML_StartElementHandler) (void *userData, const XML_Char *name, const XML_Char **atts); +typedef void (*XML_EndElementHandler) (void *userData, const XML_Char *name); +typedef void (*XML_CharacterDataHandler) (void *userData, const XML_Char *s, int len); +typedef void (*XML_CommentHandler) (void *userData, const XML_Char *data); + +XML_Expat_Version XML_ExpatVersionInfo (void); +const XML_Feature * XML_GetFeatureList (void); + +enum XML_Size_ABI { is_int, is_long, is_int64_t }; +enum XML_Size_ABI get_XML_Size_ABI (void); + +XML_Parser XML_ParserCreate (const XML_Char *encoding); +void XML_SetElementHandler (XML_Parser parser, + XML_StartElementHandler start, + XML_EndElementHandler end); +void XML_SetCharacterDataHandler (XML_Parser parser, + XML_CharacterDataHandler handler); +void XML_SetCommentHandler (XML_Parser parser, XML_CommentHandler handler); +int XML_Parse (XML_Parser parser, const char *s, int len, int isFinal); +enum XML_Error XML_GetErrorCode (XML_Parser parser); +int64_t XML_GetCurrentLineNumber (XML_Parser parser); +int64_t XML_GetCurrentColumnNumber (XML_Parser parser); +const XML_LChar * XML_ErrorString (int code); +void XML_ParserFree (XML_Parser parser); + +bool load_libexpat (); + +#define LIBEXPAT_AVAILABLE() (load_libexpat ()) + +#elif HAVE_LIBEXPAT + +#define LIBEXPAT_AVAILABLE() true + +#endif diff --git a/gettext-tools/src/message.c b/gettext-tools/src/message.c new file mode 100644 index 0000000..586675f --- /dev/null +++ b/gettext-tools/src/message.c @@ -0,0 +1,914 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2009 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "message.h" + +#include +#include + +#include "fstrcmp.h" +#include "hash.h" +#include "xalloc.h" +#include "xmalloca.h" + + +const char *const format_language[NFORMATS] = +{ + /* format_c */ "c", + /* format_objc */ "objc", + /* format_sh */ "sh", + /* format_python */ "python", + /* format_python_brace */ "python-brace", + /* format_lisp */ "lisp", + /* format_elisp */ "elisp", + /* format_librep */ "librep", + /* format_scheme */ "scheme", + /* format_smalltalk */ "smalltalk", + /* format_java */ "java", + /* format_csharp */ "csharp", + /* format_awk */ "awk", + /* format_pascal */ "object-pascal", + /* format_ycp */ "ycp", + /* format_tcl */ "tcl", + /* format_perl */ "perl", + /* format_perl_brace */ "perl-brace", + /* format_php */ "php", + /* format_gcc_internal */ "gcc-internal", + /* format_gfc_internal */ "gfc-internal", + /* format_qt */ "qt", + /* format_qt_plursl */ "qt-plural", + /* format_kde */ "kde", + /* format_boost */ "boost", + /* format_lua */ "lua", + /* format_javascript */ "javascript" +}; + +const char *const format_language_pretty[NFORMATS] = +{ + /* format_c */ "C", + /* format_objc */ "Objective C", + /* format_sh */ "Shell", + /* format_python */ "Python", + /* format_python_brace */ "Python brace", + /* format_lisp */ "Lisp", + /* format_elisp */ "Emacs Lisp", + /* format_librep */ "librep", + /* format_scheme */ "Scheme", + /* format_smalltalk */ "Smalltalk", + /* format_java */ "Java", + /* format_csharp */ "C#", + /* format_awk */ "awk", + /* format_pascal */ "Object Pascal", + /* format_ycp */ "YCP", + /* format_tcl */ "Tcl", + /* format_perl */ "Perl", + /* format_perl_brace */ "Perl brace", + /* format_php */ "PHP", + /* format_gcc_internal */ "GCC internal", + /* format_gfc_internal */ "GFC internal", + /* format_qt */ "Qt", + /* format_qt_plural */ "Qt plural", + /* format_kde */ "KDE", + /* format_boost */ "Boost", + /* format_lua */ "Lua", + /* format_javascript */ "JavaScript" +}; + + +bool +possible_format_p (enum is_format is_format) +{ + return is_format == possible + || is_format == yes_according_to_context + || is_format == yes; +} + + +message_ty * +message_alloc (const char *msgctxt, + const char *msgid, const char *msgid_plural, + const char *msgstr, size_t msgstr_len, + const lex_pos_ty *pp) +{ + message_ty *mp; + size_t i; + + mp = XMALLOC (message_ty); + mp->msgctxt = msgctxt; + mp->msgid = msgid; + mp->msgid_plural = (msgid_plural != NULL ? xstrdup (msgid_plural) : NULL); + mp->msgstr = msgstr; + mp->msgstr_len = msgstr_len; + mp->pos = *pp; + mp->comment = NULL; + mp->comment_dot = NULL; + mp->filepos_count = 0; + mp->filepos = NULL; + mp->is_fuzzy = false; + for (i = 0; i < NFORMATS; i++) + mp->is_format[i] = undecided; + mp->range.min = -1; + mp->range.max = -1; + mp->do_wrap = undecided; + mp->prev_msgctxt = NULL; + mp->prev_msgid = NULL; + mp->prev_msgid_plural = NULL; + mp->used = 0; + mp->obsolete = false; + return mp; +} + + +void +message_free (message_ty *mp) +{ + size_t j; + + free ((char *) mp->msgid); + if (mp->msgid_plural != NULL) + free ((char *) mp->msgid_plural); + free ((char *) mp->msgstr); + if (mp->comment != NULL) + string_list_free (mp->comment); + if (mp->comment_dot != NULL) + string_list_free (mp->comment_dot); + for (j = 0; j < mp->filepos_count; ++j) + free ((char *) mp->filepos[j].file_name); + if (mp->filepos != NULL) + free (mp->filepos); + if (mp->prev_msgctxt != NULL) + free ((char *) mp->prev_msgctxt); + if (mp->prev_msgid != NULL) + free ((char *) mp->prev_msgid); + if (mp->prev_msgid_plural != NULL) + free ((char *) mp->prev_msgid_plural); + free (mp); +} + + +void +message_comment_append (message_ty *mp, const char *s) +{ + if (mp->comment == NULL) + mp->comment = string_list_alloc (); + string_list_append (mp->comment, s); +} + + +void +message_comment_dot_append (message_ty *mp, const char *s) +{ + if (mp->comment_dot == NULL) + mp->comment_dot = string_list_alloc (); + string_list_append (mp->comment_dot, s); +} + + +void +message_comment_filepos (message_ty *mp, const char *name, size_t line) +{ + size_t j; + size_t nbytes; + lex_pos_ty *pp; + + /* See if we have this position already. */ + for (j = 0; j < mp->filepos_count; j++) + { + pp = &mp->filepos[j]; + if (strcmp (pp->file_name, name) == 0 && pp->line_number == line) + return; + } + + /* Extend the list so that we can add a position to it. */ + nbytes = (mp->filepos_count + 1) * sizeof (mp->filepos[0]); + mp->filepos = xrealloc (mp->filepos, nbytes); + + /* Insert the position at the end. Don't sort the file positions here. */ + pp = &mp->filepos[mp->filepos_count++]; + pp->file_name = xstrdup (name); + pp->line_number = line; +} + + +message_ty * +message_copy (message_ty *mp) +{ + message_ty *result; + size_t j, i; + + result = message_alloc (mp->msgctxt != NULL ? xstrdup (mp->msgctxt) : NULL, + xstrdup (mp->msgid), mp->msgid_plural, + mp->msgstr, mp->msgstr_len, &mp->pos); + + if (mp->comment) + { + for (j = 0; j < mp->comment->nitems; ++j) + message_comment_append (result, mp->comment->item[j]); + } + if (mp->comment_dot) + { + for (j = 0; j < mp->comment_dot->nitems; ++j) + message_comment_dot_append (result, mp->comment_dot->item[j]); + } + result->is_fuzzy = mp->is_fuzzy; + for (i = 0; i < NFORMATS; i++) + result->is_format[i] = mp->is_format[i]; + result->range = mp->range; + result->do_wrap = mp->do_wrap; + for (j = 0; j < mp->filepos_count; ++j) + { + lex_pos_ty *pp = &mp->filepos[j]; + message_comment_filepos (result, pp->file_name, pp->line_number); + } + result->prev_msgctxt = + (mp->prev_msgctxt != NULL ? xstrdup (mp->prev_msgctxt) : NULL); + result->prev_msgid = + (mp->prev_msgid != NULL ? xstrdup (mp->prev_msgid) : NULL); + result->prev_msgid_plural = + (mp->prev_msgid_plural != NULL ? xstrdup (mp->prev_msgid_plural) : NULL); + return result; +} + + +message_list_ty * +message_list_alloc (bool use_hashtable) +{ + message_list_ty *mlp; + + mlp = XMALLOC (message_list_ty); + mlp->nitems = 0; + mlp->nitems_max = 0; + mlp->item = NULL; + if ((mlp->use_hashtable = use_hashtable)) + hash_init (&mlp->htable, 10); + return mlp; +} + + +void +message_list_free (message_list_ty *mlp, int keep_messages) +{ + size_t j; + + if (keep_messages == 0) + for (j = 0; j < mlp->nitems; ++j) + message_free (mlp->item[j]); + if (mlp->item) + free (mlp->item); + if (mlp->use_hashtable) + hash_destroy (&mlp->htable); + free (mlp); +} + + +static int +message_list_hash_insert_entry (hash_table *htable, message_ty *mp) +{ + char *alloced_key; + const char *key; + size_t keylen; + int found; + + if (mp->msgctxt != NULL) + { + /* Concatenate mp->msgctxt and mp->msgid, to form the hash table key. */ + size_t msgctxt_len = strlen (mp->msgctxt); + size_t msgid_len = strlen (mp->msgid); + keylen = msgctxt_len + 1 + msgid_len + 1; + alloced_key = (char *) xmalloca (keylen); + memcpy (alloced_key, mp->msgctxt, msgctxt_len); + alloced_key[msgctxt_len] = MSGCTXT_SEPARATOR; + memcpy (alloced_key + msgctxt_len + 1, mp->msgid, msgid_len + 1); + key = alloced_key; + } + else + { + alloced_key = NULL; + key = mp->msgid; + keylen = strlen (mp->msgid) + 1; + } + + found = (hash_insert_entry (htable, key, keylen, mp) == NULL); + + if (mp->msgctxt != NULL) + freea (alloced_key); + + return found; +} + + +void +message_list_append (message_list_ty *mlp, message_ty *mp) +{ + if (mlp->nitems >= mlp->nitems_max) + { + size_t nbytes; + + mlp->nitems_max = mlp->nitems_max * 2 + 4; + nbytes = mlp->nitems_max * sizeof (message_ty *); + mlp->item = xrealloc (mlp->item, nbytes); + } + mlp->item[mlp->nitems++] = mp; + + if (mlp->use_hashtable) + if (message_list_hash_insert_entry (&mlp->htable, mp)) + /* A message list has duplicates, although it was allocated with the + assertion that it wouldn't have duplicates. It is a bug. */ + abort (); +} + + +void +message_list_prepend (message_list_ty *mlp, message_ty *mp) +{ + size_t j; + + if (mlp->nitems >= mlp->nitems_max) + { + size_t nbytes; + + mlp->nitems_max = mlp->nitems_max * 2 + 4; + nbytes = mlp->nitems_max * sizeof (message_ty *); + mlp->item = xrealloc (mlp->item, nbytes); + } + for (j = mlp->nitems; j > 0; j--) + mlp->item[j] = mlp->item[j - 1]; + mlp->item[0] = mp; + mlp->nitems++; + + if (mlp->use_hashtable) + if (message_list_hash_insert_entry (&mlp->htable, mp)) + /* A message list has duplicates, although it was allocated with the + assertion that it wouldn't have duplicates. It is a bug. */ + abort (); +} + + +void +message_list_insert_at (message_list_ty *mlp, size_t n, message_ty *mp) +{ + size_t j; + + if (mlp->nitems >= mlp->nitems_max) + { + size_t nbytes; + + mlp->nitems_max = mlp->nitems_max * 2 + 4; + nbytes = mlp->nitems_max * sizeof (message_ty *); + mlp->item = xrealloc (mlp->item, nbytes); + } + for (j = mlp->nitems; j > n; j--) + mlp->item[j] = mlp->item[j - 1]; + mlp->item[j] = mp; + mlp->nitems++; + + if (mlp->use_hashtable) + if (message_list_hash_insert_entry (&mlp->htable, mp)) + /* A message list has duplicates, although it was allocated with the + assertion that it wouldn't have duplicates. It is a bug. */ + abort (); +} + + +#if 0 /* unused */ +void +message_list_delete_nth (message_list_ty *mlp, size_t n) +{ + size_t j; + + if (n >= mlp->nitems) + return; + message_free (mlp->item[n]); + for (j = n + 1; j < mlp->nitems; ++j) + mlp->item[j - 1] = mlp->item[j]; + mlp->nitems--; + + if (mlp->use_hashtable) + { + /* Our simple-minded hash tables don't support removal. */ + hash_destroy (&mlp->htable); + mlp->use_hashtable = false; + } +} +#endif + + +void +message_list_remove_if_not (message_list_ty *mlp, + message_predicate_ty *predicate) +{ + size_t i, j; + + for (j = 0, i = 0; j < mlp->nitems; j++) + if (predicate (mlp->item[j])) + mlp->item[i++] = mlp->item[j]; + if (mlp->use_hashtable && i < mlp->nitems) + { + /* Our simple-minded hash tables don't support removal. */ + hash_destroy (&mlp->htable); + mlp->use_hashtable = false; + } + mlp->nitems = i; +} + + +bool +message_list_msgids_changed (message_list_ty *mlp) +{ + if (mlp->use_hashtable) + { + unsigned long int size = mlp->htable.size; + size_t j; + + hash_destroy (&mlp->htable); + hash_init (&mlp->htable, size); + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (message_list_hash_insert_entry (&mlp->htable, mp)) + /* A message list has duplicates, although it was allocated with + the assertion that it wouldn't have duplicates, and before the + msgids changed it indeed didn't have duplicates. */ + { + hash_destroy (&mlp->htable); + mlp->use_hashtable = false; + return true; + } + } + } + return false; +} + + +message_list_ty * +message_list_copy (message_list_ty *mlp, int copy_level) +{ + message_list_ty *result; + size_t j; + + result = message_list_alloc (mlp->use_hashtable); + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + message_list_append (result, copy_level ? mp : message_copy (mp)); + } + + return result; +} + + +message_ty * +message_list_search (message_list_ty *mlp, + const char *msgctxt, const char *msgid) +{ + if (mlp->use_hashtable) + { + char *alloced_key; + const char *key; + size_t keylen; + + if (msgctxt != NULL) + { + /* Concatenate the msgctxt and msgid, to form the hash table key. */ + size_t msgctxt_len = strlen (msgctxt); + size_t msgid_len = strlen (msgid); + keylen = msgctxt_len + 1 + msgid_len + 1; + alloced_key = (char *) xmalloca (keylen); + memcpy (alloced_key, msgctxt, msgctxt_len); + alloced_key[msgctxt_len] = MSGCTXT_SEPARATOR; + memcpy (alloced_key + msgctxt_len + 1, msgid, msgid_len + 1); + key = alloced_key; + } + else + { + alloced_key = NULL; + key = msgid; + keylen = strlen (msgid) + 1; + } + + { + void *htable_value; + int found = !hash_find_entry (&mlp->htable, key, keylen, &htable_value); + + if (msgctxt != NULL) + freea (alloced_key); + + if (found) + return (message_ty *) htable_value; + else + return NULL; + } + } + else + { + size_t j; + + for (j = 0; j < mlp->nitems; ++j) + { + message_ty *mp; + + mp = mlp->item[j]; + if ((msgctxt != NULL + ? mp->msgctxt != NULL && strcmp (msgctxt, mp->msgctxt) == 0 + : mp->msgctxt == NULL) + && strcmp (msgid, mp->msgid) == 0) + return mp; + } + return NULL; + } +} + + +double +fuzzy_search_goal_function (const message_ty *mp, + const char *msgctxt, const char *msgid, + double lower_bound) +{ + double bonus = 0.0; + /* A translation for a context is a good proposal also for another. But + give mp a small advantage if mp is valid regardless of any context or + has the same context as the one being looked up. */ + if (mp->msgctxt == NULL + || (msgctxt != NULL && strcmp (msgctxt, mp->msgctxt) == 0)) + { + bonus = 0.00001; + /* Since we will consider (weight + bonus) at the end, we are only + interested in weights that are >= lower_bound - bonus. Subtract + a little more than the bonus, in order to avoid trouble due to + rounding errors. */ + lower_bound -= bonus * 1.01; + } + + { + /* The use of 'volatile' guarantees that excess precision bits are dropped + before the addition and before the following comparison at the caller's + site. It is necessary on x86 systems where double-floats are not IEEE + compliant by default, to avoid that msgmerge results become platform and + compiler option dependent. 'volatile' is a portable alternative to + gcc's -ffloat-store option. */ + volatile double weight = fstrcmp_bounded (msgid, mp->msgid, lower_bound); + + weight += bonus; + + return weight; + } +} + + +static message_ty * +message_list_search_fuzzy_inner (message_list_ty *mlp, + const char *msgctxt, const char *msgid, + double *best_weight_p) +{ + size_t j; + message_ty *best_mp; + + best_mp = NULL; + for (j = 0; j < mlp->nitems; ++j) + { + message_ty *mp; + + mp = mlp->item[j]; + + if (mp->msgstr != NULL && mp->msgstr[0] != '\0') + { + double weight = + fuzzy_search_goal_function (mp, msgctxt, msgid, *best_weight_p); + if (weight > *best_weight_p) + { + *best_weight_p = weight; + best_mp = mp; + } + } + } + return best_mp; +} + + +message_ty * +message_list_search_fuzzy (message_list_ty *mlp, + const char *msgctxt, const char *msgid) +{ + double best_weight; + + best_weight = FUZZY_THRESHOLD; + return message_list_search_fuzzy_inner (mlp, msgctxt, msgid, &best_weight); +} + + +message_list_list_ty * +message_list_list_alloc () +{ + message_list_list_ty *mllp; + + mllp = XMALLOC (message_list_list_ty); + mllp->nitems = 0; + mllp->nitems_max = 0; + mllp->item = NULL; + return mllp; +} + + +void +message_list_list_free (message_list_list_ty *mllp, int keep_level) +{ + size_t j; + + if (keep_level < 2) + for (j = 0; j < mllp->nitems; ++j) + message_list_free (mllp->item[j], keep_level); + if (mllp->item) + free (mllp->item); + free (mllp); +} + + +void +message_list_list_append (message_list_list_ty *mllp, message_list_ty *mlp) +{ + if (mllp->nitems >= mllp->nitems_max) + { + size_t nbytes; + + mllp->nitems_max = mllp->nitems_max * 2 + 4; + nbytes = mllp->nitems_max * sizeof (message_list_ty *); + mllp->item = xrealloc (mllp->item, nbytes); + } + mllp->item[mllp->nitems++] = mlp; +} + + +void +message_list_list_append_list (message_list_list_ty *mllp, + message_list_list_ty *mllp2) +{ + size_t j; + + for (j = 0; j < mllp2->nitems; ++j) + message_list_list_append (mllp, mllp2->item[j]); +} + + +message_ty * +message_list_list_search (message_list_list_ty *mllp, + const char *msgctxt, const char *msgid) +{ + message_ty *best_mp; + int best_weight; /* 0: not found, 1: found without msgstr, 2: translated */ + size_t j; + + best_mp = NULL; + best_weight = 0; + for (j = 0; j < mllp->nitems; ++j) + { + message_list_ty *mlp; + message_ty *mp; + + mlp = mllp->item[j]; + mp = message_list_search (mlp, msgctxt, msgid); + if (mp) + { + int weight = (mp->msgstr_len == 1 && mp->msgstr[0] == '\0' ? 1 : 2); + if (weight > best_weight) + { + best_mp = mp; + best_weight = weight; + } + } + } + return best_mp; +} + + +#if 0 /* unused */ +message_ty * +message_list_list_search_fuzzy (message_list_list_ty *mllp, + const char *msgctxt, const char *msgid) +{ + size_t j; + double best_weight; + message_ty *best_mp; + + best_weight = FUZZY_THRESHOLD; + best_mp = NULL; + for (j = 0; j < mllp->nitems; ++j) + { + message_list_ty *mlp; + message_ty *mp; + + mlp = mllp->item[j]; + mp = message_list_search_fuzzy_inner (mlp, msgctxt, msgid, &best_weight); + if (mp) + best_mp = mp; + } + return best_mp; +} +#endif + + +msgdomain_ty* +msgdomain_alloc (const char *domain, bool use_hashtable) +{ + msgdomain_ty *mdp; + + mdp = XMALLOC (msgdomain_ty); + mdp->domain = domain; + mdp->messages = message_list_alloc (use_hashtable); + return mdp; +} + + +void +msgdomain_free (msgdomain_ty *mdp) +{ + message_list_free (mdp->messages, 0); + free (mdp); +} + + +msgdomain_list_ty * +msgdomain_list_alloc (bool use_hashtable) +{ + msgdomain_list_ty *mdlp; + + mdlp = XMALLOC (msgdomain_list_ty); + /* Put the default domain first, so that when we output it, + we can omit the 'domain' directive. */ + mdlp->nitems = 1; + mdlp->nitems_max = 1; + mdlp->item = XNMALLOC (mdlp->nitems_max, msgdomain_ty *); + mdlp->item[0] = msgdomain_alloc (MESSAGE_DOMAIN_DEFAULT, use_hashtable); + mdlp->use_hashtable = use_hashtable; + mdlp->encoding = NULL; + return mdlp; +} + + +void +msgdomain_list_free (msgdomain_list_ty *mdlp) +{ + size_t j; + + for (j = 0; j < mdlp->nitems; ++j) + msgdomain_free (mdlp->item[j]); + if (mdlp->item) + free (mdlp->item); + free (mdlp); +} + + +void +msgdomain_list_append (msgdomain_list_ty *mdlp, msgdomain_ty *mdp) +{ + if (mdlp->nitems >= mdlp->nitems_max) + { + size_t nbytes; + + mdlp->nitems_max = mdlp->nitems_max * 2 + 4; + nbytes = mdlp->nitems_max * sizeof (msgdomain_ty *); + mdlp->item = xrealloc (mdlp->item, nbytes); + } + mdlp->item[mdlp->nitems++] = mdp; +} + + +#if 0 /* unused */ +void +msgdomain_list_append_list (msgdomain_list_ty *mdlp, msgdomain_list_ty *mdlp2) +{ + size_t j; + + for (j = 0; j < mdlp2->nitems; ++j) + msgdomain_list_append (mdlp, mdlp2->item[j]); +} +#endif + + +message_list_ty * +msgdomain_list_sublist (msgdomain_list_ty *mdlp, const char *domain, + bool create) +{ + size_t j; + + for (j = 0; j < mdlp->nitems; j++) + if (strcmp (mdlp->item[j]->domain, domain) == 0) + return mdlp->item[j]->messages; + + if (create) + { + msgdomain_ty *mdp = msgdomain_alloc (domain, mdlp->use_hashtable); + msgdomain_list_append (mdlp, mdp); + return mdp->messages; + } + else + return NULL; +} + + +/* Copy a message domain list. + If copy_level = 0, also copy the messages. If copy_level = 1, share the + messages but copy the domains. If copy_level = 2, share the domains. */ +msgdomain_list_ty * +msgdomain_list_copy (msgdomain_list_ty *mdlp, int copy_level) +{ + msgdomain_list_ty *result; + size_t j; + + result = XMALLOC (msgdomain_list_ty); + result->nitems = 0; + result->nitems_max = 0; + result->item = NULL; + result->use_hashtable = mdlp->use_hashtable; + result->encoding = mdlp->encoding; + + for (j = 0; j < mdlp->nitems; j++) + { + msgdomain_ty *mdp = mdlp->item[j]; + + if (copy_level < 2) + { + msgdomain_ty *result_mdp = XMALLOC (msgdomain_ty); + + result_mdp->domain = mdp->domain; + result_mdp->messages = message_list_copy (mdp->messages, copy_level); + + msgdomain_list_append (result, result_mdp); + } + else + msgdomain_list_append (result, mdp); + } + + return result; +} + + +#if 0 /* unused */ +message_ty * +msgdomain_list_search (msgdomain_list_ty *mdlp, + const char *msgctxt, const char *msgid) +{ + size_t j; + + for (j = 0; j < mdlp->nitems; ++j) + { + msgdomain_ty *mdp; + message_ty *mp; + + mdp = mdlp->item[j]; + mp = message_list_search (mdp->messages, msgctxt, msgid); + if (mp) + return mp; + } + return NULL; +} +#endif + + +#if 0 /* unused */ +message_ty * +msgdomain_list_search_fuzzy (msgdomain_list_ty *mdlp, + const char *msgctxt, const char *msgid) +{ + size_t j; + double best_weight; + message_ty *best_mp; + + best_weight = FUZZY_THRESHOLD; + best_mp = NULL; + for (j = 0; j < mdlp->nitems; ++j) + { + msgdomain_ty *mdp; + message_ty *mp; + + mdp = mdlp->item[j]; + mp = message_list_search_fuzzy_inner (mdp->messages, msgctxt, msgid, + &best_weight); + if (mp) + best_mp = mp; + } + return best_mp; +} +#endif diff --git a/gettext-tools/src/message.h b/gettext-tools/src/message.h new file mode 100644 index 0000000..bf2215a --- /dev/null +++ b/gettext-tools/src/message.h @@ -0,0 +1,370 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2009 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MESSAGE_H +#define _MESSAGE_H + +#include "str-list.h" +#include "pos.h" +#include "hash.h" + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* According to Sun's Uniforum proposal the default message domain is + named 'messages'. */ +#define MESSAGE_DOMAIN_DEFAULT "messages" + + +/* Separator between msgctxt and msgid in .mo files. */ +#define MSGCTXT_SEPARATOR '\004' /* EOT */ + + +/* Kinds of format strings. */ +enum format_type +{ + format_c, + format_objc, + format_sh, + format_python, + format_python_brace, + format_lisp, + format_elisp, + format_librep, + format_scheme, + format_smalltalk, + format_java, + format_csharp, + format_awk, + format_pascal, + format_ycp, + format_tcl, + format_perl, + format_perl_brace, + format_php, + format_gcc_internal, + format_gfc_internal, + format_qt, + format_qt_plural, + format_kde, + format_boost, + format_lua, + format_javascript +}; +#define NFORMATS 27 /* Number of format_type enum values. */ +extern DLL_VARIABLE const char *const format_language[NFORMATS]; +extern DLL_VARIABLE const char *const format_language_pretty[NFORMATS]; + +/* Is current msgid a format string? */ +enum is_format +{ + undecided, + yes, + no, + yes_according_to_context, + possible, + impossible +}; + +extern bool + possible_format_p (enum is_format); + + +/* Range of an unsigned integer argument. */ +struct argument_range +{ + int min; + int max; +}; + +/* Tests whether a range is present. */ +#define has_range_p(range) ((range).min >= 0 && (range).max >= 0) + + +/* Is current msgid wrappable? */ +#if 0 +enum is_wrap +{ + undecided, + yes, + no +}; +#else /* HACK - C's enum concept is so stupid */ +#define is_wrap is_format +#endif + + +struct altstr +{ + const char *msgstr; + size_t msgstr_len; + const char *msgstr_end; + string_list_ty *comment; + string_list_ty *comment_dot; + char *id; +}; + + +typedef struct message_ty message_ty; +struct message_ty +{ + /* The msgctxt string, if present. */ + const char *msgctxt; + + /* The msgid string. */ + const char *msgid; + + /* The msgid's plural, if present. */ + const char *msgid_plural; + + /* The msgstr strings. */ + const char *msgstr; + /* The number of bytes in msgstr, including the terminating NUL. */ + size_t msgstr_len; + + /* Position in the source PO file. */ + lex_pos_ty pos; + + /* Plain comments (#) appearing before the message. */ + string_list_ty *comment; + + /* Extracted comments (#.) appearing before the message. */ + string_list_ty *comment_dot; + + /* File position comments (#:) appearing before the message, one for + each unique file position instance, sorted by file name and then + by line. */ + size_t filepos_count; + lex_pos_ty *filepos; + + /* Informations from special comments (#,). + Some of them come from extracted comments. They are manipulated by + the tools, e.g. msgmerge. */ + + /* Fuzzy means "needs translator review". */ + bool is_fuzzy; + + /* Designation of format string syntax requirements for specific + programming languages. */ + enum is_format is_format[NFORMATS]; + + /* Lower and upper bound for the argument whose format directive can be + omitted in specific cases of singular or plural. */ + struct argument_range range; + + /* Do we want the string to be wrapped in the emitted PO file? */ + enum is_wrap do_wrap; + + /* The prev_msgctxt, prev_msgid and prev_msgid_plural strings appearing + before the message, if present. Generated by msgmerge. */ + const char *prev_msgctxt; + const char *prev_msgid; + const char *prev_msgid_plural; + + /* If set the message is obsolete and while writing out it should be + commented out. */ + bool obsolete; + + /* Used for checking that messages have been used, in the msgcmp, + msgmerge, msgcomm and msgcat programs. */ + int used; + + /* Used for looking up the target message, in the msgcat program. */ + message_ty *tmp; + + /* Used for combining alternative translations, in the msgcat program. */ + int alternative_count; + struct altstr *alternative; +}; + +extern message_ty * + message_alloc (const char *msgctxt, + const char *msgid, const char *msgid_plural, + const char *msgstr, size_t msgstr_len, + const lex_pos_ty *pp); +#define is_header(mp) ((mp)->msgctxt == NULL && (mp)->msgid[0] == '\0') +extern void + message_free (message_ty *mp); +extern void + message_comment_append (message_ty *mp, const char *comment); +extern void + message_comment_dot_append (message_ty *mp, const char *comment); +extern void + message_comment_filepos (message_ty *mp, const char *name, size_t line); +extern message_ty * + message_copy (message_ty *mp); + + +typedef struct message_list_ty message_list_ty; +struct message_list_ty +{ + message_ty **item; + size_t nitems; + size_t nitems_max; + bool use_hashtable; + hash_table htable; /* Table mapping msgid to 'message_ty *'. */ +}; + +/* Create a fresh message list. + If USE_HASHTABLE is true, a hash table will be used to speed up + message_list_search(). USE_HASHTABLE can only be set to true if it is + known that the message list will not contain duplicate msgids. */ +extern message_list_ty * + message_list_alloc (bool use_hashtable); +/* Free a message list. + If keep_messages = 0, also free the messages. If keep_messages = 1, don't + free the messages. */ +extern void + message_list_free (message_list_ty *mlp, int keep_messages); +extern void + message_list_append (message_list_ty *mlp, message_ty *mp); +extern void + message_list_prepend (message_list_ty *mlp, message_ty *mp); +extern void + message_list_insert_at (message_list_ty *mlp, size_t n, message_ty *mp); +extern void + message_list_delete_nth (message_list_ty *mlp, size_t n); +typedef bool message_predicate_ty (const message_ty *mp); +extern void + message_list_remove_if_not (message_list_ty *mlp, + message_predicate_ty *predicate); +/* Recompute the hash table of a message list after the msgids or msgctxts + changed. */ +extern bool + message_list_msgids_changed (message_list_ty *mlp); +/* Copy a message list. + If copy_level = 0, also copy the messages. If copy_level = 1, share the + messages. */ +extern message_list_ty * + message_list_copy (message_list_ty *mlp, int copy_level); +extern message_ty * + message_list_search (message_list_ty *mlp, + const char *msgctxt, const char *msgid); +/* Return the message in MLP which maximizes the fuzzy_search_goal_function. + Only messages with a fuzzy_search_goal_function > FUZZY_THRESHOLD are + considered. In case of several messages with the same goal function value, + the one with the smaller index is returned. */ +extern message_ty * + message_list_search_fuzzy (message_list_ty *mlp, + const char *msgctxt, const char *msgid); + + +typedef struct message_list_list_ty message_list_list_ty; +struct message_list_list_ty +{ + message_list_ty **item; + size_t nitems; + size_t nitems_max; +}; + +extern message_list_list_ty * + message_list_list_alloc (void); +/* Free a list of message lists. + If keep_level = 0, also free the messages. If keep_level = 1, don't free + the messages but free the lists. If keep_level = 2, don't free the + the messages and the lists. */ +extern void + message_list_list_free (message_list_list_ty *mllp, int keep_level); +extern void + message_list_list_append (message_list_list_ty *mllp, + message_list_ty *mlp); +extern void + message_list_list_append_list (message_list_list_ty *mllp, + message_list_list_ty *mllp2); +extern message_ty * + message_list_list_search (message_list_list_ty *mllp, + const char *msgctxt, const char *msgid); +extern message_ty * + message_list_list_search_fuzzy (message_list_list_ty *mllp, + const char *msgctxt, const char *msgid); + + +typedef struct msgdomain_ty msgdomain_ty; +struct msgdomain_ty +{ + const char *domain; + message_list_ty *messages; +}; + +extern msgdomain_ty * + msgdomain_alloc (const char *domain, bool use_hashtable); +extern void + msgdomain_free (msgdomain_ty *mdp); + + +typedef struct msgdomain_list_ty msgdomain_list_ty; +struct msgdomain_list_ty +{ + msgdomain_ty **item; + size_t nitems; + size_t nitems_max; + bool use_hashtable; + const char *encoding; /* canonicalized encoding or NULL if unknown */ +}; + +extern msgdomain_list_ty * + msgdomain_list_alloc (bool use_hashtable); +extern void + msgdomain_list_free (msgdomain_list_ty *mdlp); +extern void + msgdomain_list_append (msgdomain_list_ty *mdlp, msgdomain_ty *mdp); +extern void + msgdomain_list_append_list (msgdomain_list_ty *mdlp, + msgdomain_list_ty *mdlp2); +extern message_list_ty * + msgdomain_list_sublist (msgdomain_list_ty *mdlp, const char *domain, + bool create); +/* Copy a message domain list. + If copy_level = 0, also copy the messages. If copy_level = 1, share the + messages but copy the domains. If copy_level = 2, share the domains. */ +extern msgdomain_list_ty * + msgdomain_list_copy (msgdomain_list_ty *mdlp, int copy_level); +extern message_ty * + msgdomain_list_search (msgdomain_list_ty *mdlp, + const char *msgctxt, const char *msgid); +extern message_ty * + msgdomain_list_search_fuzzy (msgdomain_list_ty *mdlp, + const char *msgctxt, const char *msgid); + + +/* The goal function used in fuzzy search. + Higher values indicate a closer match. + If the result is < LOWER_BOUND, an arbitrary other value < LOWER_BOUND can + be returned. */ +extern double + fuzzy_search_goal_function (const message_ty *mp, + const char *msgctxt, const char *msgid, + double lower_bound); + +/* The threshold for fuzzy-searching. + A message is considered only if + fuzzy_search_goal_function (mp, given, 0.0) > FUZZY_THRESHOLD. */ +#define FUZZY_THRESHOLD 0.6 + + +#ifdef __cplusplus +} +#endif + + +#endif /* message.h */ diff --git a/gettext-tools/src/msgattrib.c b/gettext-tools/src/msgattrib.c new file mode 100644 index 0000000..326f28c --- /dev/null +++ b/gettext-tools/src/msgattrib.c @@ -0,0 +1,683 @@ +/* Manipulates attributes of messages in translation catalogs. + Copyright (C) 2001-2007, 2009-2010, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "propername.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Bit mask of subsets to remove. */ +enum +{ + REMOVE_UNTRANSLATED = 1 << 0, + REMOVE_TRANSLATED = 1 << 1, + REMOVE_FUZZY = 1 << 2, + REMOVE_NONFUZZY = 1 << 3, + REMOVE_OBSOLETE = 1 << 4, + REMOVE_NONOBSOLETE = 1 << 5 +}; +static int to_remove; + +/* Bit mask of actions to perform on all messages. */ +enum +{ + SET_FUZZY = 1 << 0, + RESET_FUZZY = 1 << 1, + SET_OBSOLETE = 1 << 2, + RESET_OBSOLETE = 1 << 3, + REMOVE_PREV = 1 << 4, + ADD_PREV = 1 << 5, + REMOVE_TRANSLATION = 1 << 6 +}; +static int to_change; + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "clear-fuzzy", no_argument, NULL, CHAR_MAX + 8 }, + { "clear-obsolete", no_argument, NULL, CHAR_MAX + 10 }, + { "clear-previous", no_argument, NULL, CHAR_MAX + 18 }, + { "empty", no_argument, NULL, CHAR_MAX + 23 }, + { "color", optional_argument, NULL, CHAR_MAX + 19 }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "force-po", no_argument, &force_po, 1 }, + { "fuzzy", no_argument, NULL, CHAR_MAX + 11 }, + { "help", no_argument, NULL, 'h' }, + { "ignore-file", required_argument, NULL, CHAR_MAX + 15 }, + { "indent", no_argument, NULL, 'i' }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-fuzzy", no_argument, NULL, CHAR_MAX + 3 }, + { "no-location", no_argument, NULL, CHAR_MAX + 22 }, + { "no-obsolete", no_argument, NULL, CHAR_MAX + 5 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 13 }, + { "obsolete", no_argument, NULL, CHAR_MAX + 12 }, + { "only-file", required_argument, NULL, CHAR_MAX + 14 }, + { "only-fuzzy", no_argument, NULL, CHAR_MAX + 4 }, + { "only-obsolete", no_argument, NULL, CHAR_MAX + 6 }, + { "output-file", required_argument, NULL, 'o' }, + { "previous", no_argument, NULL, CHAR_MAX + 21 }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "set-fuzzy", no_argument, NULL, CHAR_MAX + 7 }, + { "set-obsolete", no_argument, NULL, CHAR_MAX + 9 }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 16 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 17 }, + { "strict", no_argument, NULL, 'S' }, + { "style", required_argument, NULL, CHAR_MAX + 20 }, + { "translated", no_argument, NULL, CHAR_MAX + 1 }, + { "untranslated", no_argument, NULL, CHAR_MAX + 2 }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp, + msgdomain_list_ty *only_mdlp, + msgdomain_list_ty *ignore_mdlp); + + +int +main (int argc, char **argv) +{ + int optchar; + bool do_help; + bool do_version; + char *output_file; + const char *input_file; + const char *only_file; + const char *ignore_file; + msgdomain_list_ty *only_mdlp; + msgdomain_list_ty *ignore_mdlp; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_msgid = false; + bool sort_by_filepos = false; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + input_file = NULL; + only_file = NULL; + ignore_file = NULL; + + while ((optchar = getopt_long (argc, argv, "D:eEFhino:pPsVw:", long_options, + NULL)) != EOF) + switch (optchar) + { + case '\0': /* Long option. */ + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: /* --translated */ + to_remove |= REMOVE_UNTRANSLATED; + break; + + case CHAR_MAX + 2: /* --untranslated */ + to_remove |= REMOVE_TRANSLATED; + break; + + case CHAR_MAX + 3: /* --no-fuzzy */ + to_remove |= REMOVE_FUZZY; + break; + + case CHAR_MAX + 4: /* --only-fuzzy */ + to_remove |= REMOVE_NONFUZZY; + break; + + case CHAR_MAX + 5: /* --no-obsolete */ + to_remove |= REMOVE_OBSOLETE; + break; + + case CHAR_MAX + 6: /* --only-obsolete */ + to_remove |= REMOVE_NONOBSOLETE; + break; + + case CHAR_MAX + 7: /* --set-fuzzy */ + to_change |= SET_FUZZY; + break; + + case CHAR_MAX + 8: /* --clear-fuzzy */ + to_change |= RESET_FUZZY; + break; + + case CHAR_MAX + 9: /* --set-obsolete */ + to_change |= SET_OBSOLETE; + break; + + case CHAR_MAX + 10: /* --clear-obsolete */ + to_change |= RESET_OBSOLETE; + break; + + case CHAR_MAX + 11: /* --fuzzy */ + to_remove |= REMOVE_NONFUZZY; + to_change |= RESET_FUZZY; + break; + + case CHAR_MAX + 12: /* --obsolete */ + to_remove |= REMOVE_NONOBSOLETE; + to_change |= RESET_OBSOLETE; + break; + + case CHAR_MAX + 13: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 14: /* --only-file */ + only_file = optarg; + break; + + case CHAR_MAX + 15: /* --ignore-file */ + ignore_file = optarg; + break; + + case CHAR_MAX + 16: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 17: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 18: /* --clear-previous */ + to_change |= REMOVE_PREV; + break; + + case CHAR_MAX + 19: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 20: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 21: /* --previous */ + to_change |= ADD_PREV; + break; + + case CHAR_MAX + 22: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + case CHAR_MAX + 23: /* --empty */ + to_change |= REMOVE_TRANSLATION; + break; + + default: + usage (EXIT_FAILURE); + /* NOTREACHED */ + } + + /* Version information requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test whether we have an .po file name as argument. */ + if (optind == argc) + input_file = "-"; + else if (optind + 1 == argc) + input_file = argv[optind]; + else + { + error (EXIT_SUCCESS, 0, _("at most one input file allowed")); + usage (EXIT_FAILURE); + } + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* Read input file. */ + result = read_catalog_file (input_file, input_syntax); + + /* Read optional files that limit the extent of the attribute changes. */ + only_mdlp = (only_file != NULL + ? read_catalog_file (only_file, input_syntax) + : NULL); + ignore_mdlp = (ignore_file != NULL + ? read_catalog_file (ignore_file, input_syntax) + : NULL); + + /* Filter the messages and manipulate the attributes. */ + result = process_msgdomain_list (result, only_mdlp, ignore_mdlp); + + /* Sorting the list of messages. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Write the PO file. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [INPUTFILE]\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Filters the messages of a translation catalog according to their attributes,\n\ +and manipulates the attributes.\n")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + INPUTFILE input PO file\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If no input file is given or if it is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +Message selection:\n")); + printf (_("\ + --translated keep translated, remove untranslated messages\n")); + printf (_("\ + --untranslated keep untranslated, remove translated messages\n")); + printf (_("\ + --no-fuzzy remove 'fuzzy' marked messages\n")); + printf (_("\ + --only-fuzzy keep 'fuzzy' marked messages\n")); + printf (_("\ + --no-obsolete remove obsolete #~ messages\n")); + printf (_("\ + --only-obsolete keep obsolete #~ messages\n")); + printf ("\n"); + printf (_("\ +Attribute manipulation:\n")); + printf (_("\ + --set-fuzzy set all messages 'fuzzy'\n")); + printf (_("\ + --clear-fuzzy set all messages non-'fuzzy'\n")); + printf (_("\ + --set-obsolete set all messages obsolete\n")); + printf (_("\ + --clear-obsolete set all messages non-obsolete\n")); + printf (_("\ + --previous when setting 'fuzzy', keep previous msgids\n\ + of translated messages.\n")); + printf (_("\ + --clear-previous remove the \"previous msgid\" from all messages\n")); + printf (_("\ + --empty when removing 'fuzzy', also set msgstr empty\n")); + printf (_("\ + --only-file=FILE.po manipulate only entries listed in FILE.po\n")); + printf (_("\ + --ignore-file=FILE.po manipulate only entries not listed in FILE.po\n")); + printf (_("\ + --fuzzy synonym for --only-fuzzy --clear-fuzzy\n")); + printf (_("\ + --obsolete synonym for --only-obsolete --clear-obsolete\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input file is in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent write the .po file using indented style\n")); + printf (_("\ + --no-location do not write '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location generate '#: filename:line' lines (default)\n")); + printf (_("\ + --strict write out strict Uniforum conforming .po file\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + + +/* Return true if a message should be kept. */ +static bool +is_message_selected (const message_ty *mp) +{ + /* Always keep the header entry. */ + if (is_header (mp)) + return true; + + if ((to_remove & (REMOVE_UNTRANSLATED | REMOVE_TRANSLATED)) + && (mp->msgstr[0] == '\0' + ? to_remove & REMOVE_UNTRANSLATED + : to_remove & REMOVE_TRANSLATED)) + return false; + + if ((to_remove & (REMOVE_FUZZY | REMOVE_NONFUZZY)) + && (mp->is_fuzzy + ? to_remove & REMOVE_FUZZY + : to_remove & REMOVE_NONFUZZY)) + return false; + + if ((to_remove & (REMOVE_OBSOLETE | REMOVE_NONOBSOLETE)) + && (mp->obsolete + ? to_remove & REMOVE_OBSOLETE + : to_remove & REMOVE_NONOBSOLETE)) + return false; + + return true; +} + + +static void +process_message_list (message_list_ty *mlp, + message_list_ty *only_mlp, message_list_ty *ignore_mlp) +{ + /* Keep only the selected messages. */ + message_list_remove_if_not (mlp, is_message_selected); + + /* Change the attributes. */ + if (to_change) + { + size_t j; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + /* Attribute changes only affect messages listed in --only-file + and not listed in --ignore-file. */ + if ((only_mlp + ? message_list_search (only_mlp, mp->msgctxt, mp->msgid) != NULL + : true) + && (ignore_mlp + ? message_list_search (ignore_mlp, mp->msgctxt, mp->msgid) == NULL + : true)) + { + if (to_change & SET_FUZZY) + { + if ((to_change & ADD_PREV) && !is_header (mp) + && !mp->is_fuzzy && mp->msgstr[0] != '\0') + { + mp->prev_msgctxt = + (mp->msgctxt != NULL ? xstrdup (mp->msgctxt) : NULL); + mp->prev_msgid = + (mp->msgid != NULL ? xstrdup (mp->msgid) : NULL); + mp->prev_msgid_plural = + (mp->msgid_plural != NULL + ? xstrdup (mp->msgid_plural) + : NULL); + } + mp->is_fuzzy = true; + } + + if (to_change & RESET_FUZZY) + { + if ((to_change & REMOVE_TRANSLATION) + && mp->is_fuzzy && !mp->obsolete) + { + unsigned long int nplurals = 0; + char *msgstr; + size_t pos; + + for (pos = 0; pos < mp->msgstr_len; ++pos) + if (!mp->msgstr[pos]) + ++nplurals; + free ((char *) mp->msgstr); + msgstr = XNMALLOC (nplurals, char); + memset (msgstr, '\0', nplurals); + mp->msgstr = msgstr; + mp->msgstr_len = nplurals; + } + mp->is_fuzzy = false; + } + /* Always keep the header entry non-obsolete. */ + if ((to_change & SET_OBSOLETE) && !is_header (mp)) + mp->obsolete = true; + if (to_change & RESET_OBSOLETE) + mp->obsolete = false; + if (to_change & REMOVE_PREV) + { + mp->prev_msgctxt = NULL; + mp->prev_msgid = NULL; + mp->prev_msgid_plural = NULL; + } + } + } + } +} + + +static msgdomain_list_ty * +process_msgdomain_list (msgdomain_list_ty *mdlp, + msgdomain_list_ty *only_mdlp, + msgdomain_list_ty *ignore_mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + process_message_list (mdlp->item[k]->messages, + only_mdlp + ? msgdomain_list_sublist (only_mdlp, + mdlp->item[k]->domain, + true) + : NULL, + ignore_mdlp + ? msgdomain_list_sublist (ignore_mdlp, + mdlp->item[k]->domain, + false) + : NULL); + + return mdlp; +} diff --git a/gettext-tools/src/msgcat.c b/gettext-tools/src/msgcat.c new file mode 100644 index 0000000..0cc432c --- /dev/null +++ b/gettext-tools/src/msgcat.c @@ -0,0 +1,485 @@ +/* Concatenates several translation catalogs. + Copyright (C) 2001-2007, 2009-2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "str-list.h" +#include "file-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "msgl-cat.h" +#include "msgl-header.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Target encoding. */ +static const char *to_code; + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "color", optional_argument, NULL, CHAR_MAX + 5 }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "files-from", required_argument, NULL, 'f' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, 'i' }, + { "lang", required_argument, NULL, CHAR_MAX + 7 }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-location", no_argument, NULL, CHAR_MAX + 8 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 2 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 }, + { "style", required_argument, NULL, CHAR_MAX + 6 }, + { "to-code", required_argument, NULL, 't' }, + { "unique", no_argument, NULL, 'u' }, + { "use-first", no_argument, NULL, CHAR_MAX + 1 }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { "more-than", required_argument, NULL, '>', }, + { "less-than", required_argument, NULL, '<', }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; + + +int +main (int argc, char **argv) +{ + int cnt; + int optchar; + bool do_help; + bool do_version; + char *output_file; + const char *files_from; + string_list_ty *file_list; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_msgid = false; + bool sort_by_filepos = false; + /* Language (ISO-639 code) and optional territory (ISO-3166 code). */ + const char *catalogname = NULL; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + files_from = NULL; + more_than = 0; + less_than = INT_MAX; + use_first = false; + + while ((optchar = getopt_long (argc, argv, "<:>:D:eEf:Fhino:pPst:uVw:", + long_options, NULL)) != EOF) + switch (optchar) + { + case '\0': /* Long option. */ + break; + + case '>': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + more_than = value; + } + break; + + case '<': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + less_than = value; + } + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'f': + files_from = optarg; + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 't': + to_code = optarg; + break; + + case 'u': + less_than = 2; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: + use_first = true; + break; + + case CHAR_MAX + 2: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 3: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 4: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 5: /* --color */ + if (handle_color_option (optarg)) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 6: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 7: /* --lang */ + catalogname = optarg; + break; + + case CHAR_MAX + 8: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + /* NOTREACHED */ + } + + /* Version information requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + if (color_test_mode) + { + print_color_test (); + exit (EXIT_SUCCESS); + } + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* Check the message selection criteria for sanity. */ + if (more_than >= less_than || less_than < 2) + error (EXIT_FAILURE, 0, + _("impossible selection criteria specified (%d < n < %d)"), + more_than, less_than); + + /* Determine list of files we have to process. */ + if (files_from != NULL) + file_list = read_names_from_file (files_from); + else + file_list = string_list_alloc (); + /* Append names from command line. */ + for (cnt = optind; cnt < argc; ++cnt) + string_list_append_unique (file_list, argv[cnt]); + + /* Read input files, then filter, convert and merge messages. */ + result = + catenate_msgdomain_list (file_list, input_syntax, + output_syntax->requires_utf8 ? "UTF-8" : to_code); + + string_list_free (file_list); + + /* Sorting the list of messages. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Set the Language field in the header. */ + if (catalogname != NULL) + msgdomain_list_set_header_field (result, "Language:", catalogname); + + /* Write the PO file. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [INPUTFILE]...\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Concatenates and merges the specified PO files.\n\ +Find messages which are common to two or more of the specified PO files.\n\ +By using the --more-than option, greater commonality may be requested\n\ +before messages are printed. Conversely, the --less-than option may be\n\ +used to specify less commonality before messages are printed (i.e.\n\ +--less-than=2 will only print the unique messages). Translations,\n\ +comments, extracted comments, and file positions will be cumulated, except\n\ +that if --use-first is specified, they will be taken from the first PO file\n\ +to define them.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + INPUTFILE ... input files\n")); + printf (_("\ + -f, --files-from=FILE get list of input files from FILE\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If input file is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +Message selection:\n")); + printf (_("\ + -<, --less-than=NUMBER print messages with less than this many\n\ + definitions, defaults to infinite if not set\n")); + printf (_("\ + ->, --more-than=NUMBER print messages with more than this many\n\ + definitions, defaults to 0 if not set\n")); + printf (_("\ + -u, --unique shorthand for --less-than=2, requests\n\ + that only unique messages be printed\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input files are in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input files are in NeXTstep/GNUstep .strings\n\ + syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + -t, --to-code=NAME encoding for output\n")); + printf (_("\ + --use-first use first available translation for each\n\ + message, don't merge several translations\n")); + printf (_("\ + --lang=CATALOGNAME set 'Language' field in the header entry\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent write the .po file using indented style\n")); + printf (_("\ + --no-location do not write '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location generate '#: filename:line' lines (default)\n")); + printf (_("\ + --strict write out strict Uniforum conforming .po file\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} diff --git a/gettext-tools/src/msgcmp.c b/gettext-tools/src/msgcmp.c new file mode 100644 index 0000000..f82131d --- /dev/null +++ b/gettext-tools/src/msgcmp.c @@ -0,0 +1,555 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2010, 2012 Free Software Foundation, Inc. + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "xmalloca.h" +#include "po-charset.h" +#include "msgl-iconv.h" +#include "msgl-fsearch.h" +#include "c-strstr.h" +#include "c-strcase.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Apply the .pot file to each of the domains in the PO file. */ +static bool multi_domain_mode = false; + +/* Determines whether to use fuzzy matching. */ +static bool use_fuzzy_matching = true; + +/* Whether to consider fuzzy messages as translations. */ +static bool include_fuzzies = false; + +/* Whether to consider untranslated messages as translations. */ +static bool include_untranslated = false; + +/* Long options. */ +static const struct option long_options[] = +{ + { "directory", required_argument, NULL, 'D' }, + { "help", no_argument, NULL, 'h' }, + { "multi-domain", no_argument, NULL, 'm' }, + { "no-fuzzy-matching", no_argument, NULL, 'N' }, + { "properties-input", no_argument, NULL, 'P' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 }, + { "use-fuzzy", no_argument, NULL, CHAR_MAX + 2 }, + { "use-untranslated", no_argument, NULL, CHAR_MAX + 3 }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void compare (const char *fn1, const char *fn2, + catalog_input_format_ty input_syntax); + + +int +main (int argc, char *argv[]) +{ + int optchar; + bool do_help; + bool do_version; + catalog_input_format_ty input_syntax = &input_format_po; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + gram_max_allowed_errors = UINT_MAX; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + do_help = false; + do_version = false; + while ((optchar = getopt_long (argc, argv, "D:hmNPV", long_options, NULL)) + != EOF) + switch (optchar) + { + case '\0': /* long option */ + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'h': + do_help = true; + break; + + case 'm': + multi_domain_mode = true; + break; + + case 'N': + use_fuzzy_matching = false; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 'V': + do_version = true; + break; + + case CHAR_MAX + 1: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 2: /* --use-fuzzy */ + include_fuzzies = true; + break; + + case CHAR_MAX + 3: /* --use-untranslated */ + include_untranslated = true; + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "1995-1998, 2000-2010"); + printf (_("Written by %s.\n"), proper_name ("Peter Miller")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test whether we have an .po file name as argument. */ + if (optind >= argc) + { + error (EXIT_SUCCESS, 0, _("no input files given")); + usage (EXIT_FAILURE); + } + if (optind + 2 != argc) + { + error (EXIT_SUCCESS, 0, _("exactly 2 input files required")); + usage (EXIT_FAILURE); + } + + /* compare the two files */ + compare (argv[optind], argv[optind + 1], input_syntax); + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] def.po ref.pot\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Compare two Uniforum style .po files to check that both contain the same\n\ +set of msgid strings. The def.po file is an existing PO file with the\n\ +translations. The ref.pot file is the last created PO file, or a PO Template\n\ +file (generally created by xgettext). This is useful for checking that\n\ +you have translated each and every message in your program. Where an exact\n\ +match cannot be found, fuzzy matching is used to produce better diagnostics.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + def.po translations\n")); + printf (_("\ + ref.pot references to the sources\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf ("\n"); + printf (_("\ +Operation modifiers:\n")); + printf (_("\ + -m, --multi-domain apply ref.pot to each of the domains in def.po\n")); + printf (_("\ + -N, --no-fuzzy-matching do not use fuzzy matching\n")); + printf (_("\ + --use-fuzzy consider fuzzy entries\n")); + printf (_("\ + --use-untranslated consider untranslated entries\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input files are in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input files are in NeXTstep/GNUstep .strings\n\ + syntax\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), stdout); + } + + exit (status); +} + + +/* Return true if a message should be kept. */ +static bool +is_message_selected (const message_ty *mp) +{ + /* Always keep the header entry. */ + if (is_header (mp)) + return true; + + return !mp->obsolete; +} + + +/* Remove obsolete messages from a message list. Return the modified list. */ +static msgdomain_list_ty * +remove_obsoletes (msgdomain_list_ty *mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + message_list_remove_if_not (mdlp->item[k]->messages, is_message_selected); + + return mdlp; +} + + +static void +match_domain (const char *fn1, const char *fn2, + message_list_ty *defmlp, message_fuzzy_index_ty **defmlp_findex, + const char *def_canon_charset, + message_list_ty *refmlp, + int *nerrors) +{ + size_t j; + + for (j = 0; j < refmlp->nitems; j++) + { + message_ty *refmsg; + message_ty *defmsg; + + refmsg = refmlp->item[j]; + + /* See if it is in the other file. */ + defmsg = message_list_search (defmlp, refmsg->msgctxt, refmsg->msgid); + if (defmsg) + { + if (!include_untranslated && defmsg->msgstr[0] == '\0') + { + (*nerrors)++; + po_gram_error_at_line (&defmsg->pos, _("\ +this message is untranslated")); + } + else if (!include_fuzzies && defmsg->is_fuzzy && !is_header (defmsg)) + { + (*nerrors)++; + po_gram_error_at_line (&defmsg->pos, _("\ +this message needs to be reviewed by the translator")); + } + else + defmsg->used = 1; + } + else + { + /* If the message was not defined at all, try to find a very + similar message, it could be a typo, or the suggestion may + help. */ + (*nerrors)++; + if (use_fuzzy_matching) + { + if (false) + { + /* Old, slow code. */ + defmsg = + message_list_search_fuzzy (defmlp, + refmsg->msgctxt, refmsg->msgid); + } + else + { + /* Speedup through early abort in fstrcmp(), combined with + pre-sorting of the messages through a hashed index. */ + /* Create the fuzzy index lazily. */ + if (*defmlp_findex == NULL) + *defmlp_findex = + message_fuzzy_index_alloc (defmlp, def_canon_charset); + defmsg = + message_fuzzy_index_search (*defmlp_findex, + refmsg->msgctxt, refmsg->msgid, + FUZZY_THRESHOLD, false); + } + } + else + defmsg = NULL; + if (defmsg) + { + po_gram_error_at_line (&refmsg->pos, _("\ +this message is used but not defined...")); + error_message_count--; + po_gram_error_at_line (&defmsg->pos, _("\ +...but this definition is similar")); + defmsg->used = 1; + } + else + po_gram_error_at_line (&refmsg->pos, _("\ +this message is used but not defined in %s"), fn1); + } + } +} + + +static void +compare (const char *fn1, const char *fn2, catalog_input_format_ty input_syntax) +{ + msgdomain_list_ty *def; + msgdomain_list_ty *ref; + int nerrors; + size_t j, k; + const char *def_canon_charset; + message_list_ty *empty_list; + + /* This is the master file, created by a human. */ + def = remove_obsoletes (read_catalog_file (fn1, input_syntax)); + + /* This is the generated file, created by groping the sources with + the xgettext program. */ + ref = remove_obsoletes (read_catalog_file (fn2, input_syntax)); + + /* The references file can be either in ASCII or in UTF-8. If it is + in UTF-8, we have to convert the definitions to UTF-8 as well. */ + { + bool was_utf8 = false; + for (k = 0; k < ref->nitems; k++) + { + message_list_ty *mlp = ref->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) /* && !mlp->item[j]->obsolete */) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + if (len == strlen ("UTF-8") + && c_strncasecmp (charsetstr, "UTF-8", len) == 0) + was_utf8 = true; + } + } + } + } + if (was_utf8) + def = iconv_msgdomain_list (def, "UTF-8", true, fn1); + } + + /* Determine canonicalized encoding name of the definitions now, after + conversion. Only used for fuzzy matching. */ + if (use_fuzzy_matching) + { + def_canon_charset = def->encoding; + if (def_canon_charset == NULL) + { + char *charset = NULL; + + /* Get the encoding of the definitions file. */ + for (k = 0; k < def->nitems; k++) + { + message_list_ty *mlp = def->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + break; + } + } + } + if (charset != NULL) + break; + } + if (charset != NULL) + def_canon_charset = po_charset_canonicalize (charset); + if (def_canon_charset == NULL) + /* Unspecified encoding. Assume unibyte encoding. */ + def_canon_charset = po_charset_ascii; + } + } + else + def_canon_charset = NULL; + + empty_list = message_list_alloc (false); + + /* Every entry in the xgettext generated file must be matched by a + (single) entry in the human created file. */ + nerrors = 0; + if (!multi_domain_mode) + for (k = 0; k < ref->nitems; k++) + { + const char *domain = ref->item[k]->domain; + message_list_ty *refmlp = ref->item[k]->messages; + message_list_ty *defmlp; + message_fuzzy_index_ty *defmlp_findex; + + defmlp = msgdomain_list_sublist (def, domain, false); + if (defmlp == NULL) + defmlp = empty_list; + + defmlp_findex = NULL; + + match_domain (fn1, fn2, defmlp, &defmlp_findex, def_canon_charset, + refmlp, &nerrors); + + if (defmlp_findex != NULL) + message_fuzzy_index_free (defmlp_findex); + } + else + { + /* Apply the references messages in the default domain to each of + the definition domains. */ + message_list_ty *refmlp = ref->item[0]->messages; + + for (k = 0; k < def->nitems; k++) + { + message_list_ty *defmlp = def->item[k]->messages; + + /* Ignore the default message domain if it has no messages. */ + if (k > 0 || defmlp->nitems > 0) + { + message_fuzzy_index_ty *defmlp_findex = NULL; + + match_domain (fn1, fn2, defmlp, &defmlp_findex, def_canon_charset, + refmlp, &nerrors); + + if (defmlp_findex != NULL) + message_fuzzy_index_free (defmlp_findex); + } + } + } + + /* Look for messages in the definition file, which are not present + in the reference file, indicating messages which defined but not + used in the program. */ + for (k = 0; k < def->nitems; ++k) + { + message_list_ty *defmlp = def->item[k]->messages; + + for (j = 0; j < defmlp->nitems; j++) + { + message_ty *defmsg = defmlp->item[j]; + + if (!defmsg->used) + po_gram_error_at_line (&defmsg->pos, + _("warning: this message is not used")); + } + } + + /* Exit with status 1 on any error. */ + if (nerrors > 0) + error (EXIT_FAILURE, 0, + ngettext ("found %d fatal error", "found %d fatal errors", nerrors), + nerrors); +} diff --git a/gettext-tools/src/msgcomm.c b/gettext-tools/src/msgcomm.c new file mode 100644 index 0000000..daddf2b --- /dev/null +++ b/gettext-tools/src/msgcomm.c @@ -0,0 +1,471 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1997-1998, 2000-2007, 2009-2012 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "str-list.h" +#include "file-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "msgl-cat.h" +#include "propername.h" +#include "gettext.h" + + +/* A convenience macro. I don't like writing gettext() every time. */ +#define _(str) gettext (str) + + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Target encoding. */ +static const char *to_code; + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "color", optional_argument, NULL, CHAR_MAX + 5 }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "files-from", required_argument, NULL, 'f' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, 'i' }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-location", no_argument, NULL, CHAR_MAX + 7 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 2 }, + { "omit-header", no_argument, NULL, CHAR_MAX + 1 }, + { "output", required_argument, NULL, 'o' }, /* for backward compatibility */ + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 }, + { "style", required_argument, NULL, CHAR_MAX + 6 }, + { "to-code", required_argument, NULL, 't' }, + { "unique", no_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { "more-than", required_argument, NULL, '>', }, + { "less-than", required_argument, NULL, '<', }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ > 4) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; + + +int +main (int argc, char *argv[]) +{ + int cnt; + int optchar; + bool do_help = false; + bool do_version = false; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_msgid = false; + bool sort_by_filepos = false; + const char *files_from = NULL; + string_list_ty *file_list; + char *output_file = NULL; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + more_than = -1; + less_than = -1; + use_first = false; + + while ((optchar = getopt_long (argc, argv, "<:>:D:eEf:Fhino:pPst:uVw:", + long_options, NULL)) != EOF) + switch (optchar) + { + case '\0': /* Long option. */ + break; + + case '>': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + more_than = value; + } + break; + + case '<': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + less_than = value; + } + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'f': + files_from = optarg; + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 't': + to_code = optarg; + break; + + case 'u': + less_than = 2; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: + omit_header = true; + break; + + case CHAR_MAX + 2: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 3: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 4: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 5: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 6: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 7: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + /* NOTREACHED */ + } + + /* Version information requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "1995-1998, 2000-2010"); + printf (_("Written by %s.\n"), proper_name ("Peter Miller")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* Determine list of files we have to process. */ + if (files_from != NULL) + file_list = read_names_from_file (files_from); + else + file_list = string_list_alloc (); + /* Append names from command line. */ + for (cnt = optind; cnt < argc; ++cnt) + string_list_append_unique (file_list, argv[cnt]); + + /* Test whether sufficient input files were given. */ + if (file_list->nitems < 2) + { + error (EXIT_SUCCESS, 0, _("at least two files must be specified")); + usage (EXIT_FAILURE); + } + + /* Default the message selection criteria, and check them for sanity. */ + if (more_than < 0) + more_than = (less_than < 0 ? 1 : 0); + if (less_than < 0) + less_than = INT_MAX; + if (more_than >= less_than || less_than < 2) + error (EXIT_FAILURE, 0, + _("impossible selection criteria specified (%d < n < %d)"), + more_than, less_than); + + /* Read input files, then filter, convert and merge messages. */ + allow_duplicates = true; + msgcomm_mode = true; + result = catenate_msgdomain_list (file_list, input_syntax, to_code); + + string_list_free (file_list); + + /* Sorting the list of messages. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Write the PO file. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [INPUTFILE]...\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Find messages which are common to two or more of the specified PO files.\n\ +By using the --more-than option, greater commonality may be requested\n\ +before messages are printed. Conversely, the --less-than option may be\n\ +used to specify less commonality before messages are printed (i.e.\n\ +--less-than=2 will only print the unique messages). Translations,\n\ +comments and extracted comments will be preserved, but only from the first\n\ +PO file to define them. File positions from all PO files will be\n\ +cumulated.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + INPUTFILE ... input files\n")); + printf (_("\ + -f, --files-from=FILE get list of input files from FILE\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If input file is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +Message selection:\n")); + printf (_("\ + -<, --less-than=NUMBER print messages with less than this many\n\ + definitions, defaults to infinite if not set\n")); + printf (_("\ + ->, --more-than=NUMBER print messages with more than this many\n\ + definitions, defaults to 1 if not set\n")); + printf (_("\ + -u, --unique shorthand for --less-than=2, requests\n\ + that only unique messages be printed\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input files are in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input files are in NeXTstep/GNUstep .strings\n\ + syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent write the .po file using indented style\n")); + printf (_("\ + --no-location do not write '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location generate '#: filename:line' lines (default)\n")); + printf (_("\ + --strict write out strict Uniforum conforming .po file\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf (_("\ + --omit-header don't write header with 'msgid \"\"' entry\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} diff --git a/gettext-tools/src/msgconv.c b/gettext-tools/src/msgconv.c new file mode 100644 index 0000000..5c9f7ea --- /dev/null +++ b/gettext-tools/src/msgconv.c @@ -0,0 +1,401 @@ +/* Converts a translation catalog to a different character encoding. + Copyright (C) 2001-2007, 2009-2010, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "msgl-iconv.h" +#include "localcharset.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Target encoding. */ +static const char *to_code; + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "color", optional_argument, NULL, CHAR_MAX + 4 }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, 'i' }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-location", no_argument, NULL, CHAR_MAX + 6 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 1 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 2 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 3 }, + { "style", required_argument, NULL, CHAR_MAX + 5 }, + { "to-code", required_argument, NULL, 't' }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; + + +int +main (int argc, char **argv) +{ + int opt; + bool do_help; + bool do_version; + char *output_file; + const char *input_file; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_filepos = false; + bool sort_by_msgid = false; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + input_file = NULL; + + while ((opt = getopt_long (argc, argv, "D:eEFhin:o:pPst:Vw:", long_options, + NULL)) + != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 't': + to_code = optarg; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 2: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 3: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 4: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 5: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 6: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test whether we have an .po file name as argument. */ + if (optind == argc) + input_file = "-"; + else if (optind + 1 == argc) + input_file = argv[optind]; + else + { + error (EXIT_SUCCESS, 0, _("at most one input file allowed")); + usage (EXIT_FAILURE); + } + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* Default for target encoding is current locale's encoding. */ + if (to_code == NULL) + to_code = locale_charset (); + + /* Read input file. */ + result = read_catalog_file (input_file, input_syntax); + + /* Convert if and only if the output syntax supports different encodings. */ + if (!output_syntax->requires_utf8) + result = iconv_msgdomain_list (result, to_code, true, input_file); + + /* Sort the results. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Write the merged message list out. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [INPUTFILE]\n\ +"), program_name); + printf ("\n"); + printf (_("\ +Converts a translation catalog to a different character encoding.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + INPUTFILE input PO file\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If no input file is given or if it is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +Conversion target:\n")); + printf (_("\ + -t, --to-code=NAME encoding for output\n")); + printf (_("\ +The default encoding is the current locale's encoding.\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input file is in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent indented output style\n")); + printf (_("\ + --no-location suppress '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location preserve '#: filename:line' lines (default)\n")); + printf (_("\ + --strict strict Uniforum output style\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} diff --git a/gettext-tools/src/msgen.c b/gettext-tools/src/msgen.c new file mode 100644 index 0000000..e33c61a --- /dev/null +++ b/gettext-tools/src/msgen.c @@ -0,0 +1,397 @@ +/* Creates an English translation catalog. + Copyright (C) 2001-2007, 2009-2010, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "msgl-english.h" +#include "msgl-header.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "color", optional_argument, NULL, CHAR_MAX + 5 }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, 'i' }, + { "lang", required_argument, NULL, CHAR_MAX + 4 }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-location", no_argument, NULL, CHAR_MAX + 7 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 1 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 2 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 3 }, + { "style", required_argument, NULL, CHAR_MAX + 6 }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; + + +int +main (int argc, char **argv) +{ + int opt; + bool do_help; + bool do_version; + char *output_file; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_filepos = false; + bool sort_by_msgid = false; + /* Language (ISO-639 code) and optional territory (ISO-3166 code). */ + const char *catalogname = NULL; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + + while ((opt = getopt_long (argc, argv, + "D:eEFhin:o:pPsVw:", + long_options, NULL)) != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 2: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 3: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 4: /* --lang */ + catalogname = optarg; + break; + + case CHAR_MAX + 5: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 6: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 7: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test whether we have an .po file name as argument. */ + if (optind >= argc) + { + error (EXIT_SUCCESS, 0, _("no input file given")); + usage (EXIT_FAILURE); + } + if (optind + 1 != argc) + { + error (EXIT_SUCCESS, 0, _("exactly one input file required")); + usage (EXIT_FAILURE); + } + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* Read input file. */ + result = read_catalog_file (argv[optind], input_syntax); + + /* Add English translations. */ + result = msgdomain_list_english (result); + + /* Sort the results. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Set the Language field in the header. */ + if (catalogname != NULL) + msgdomain_list_set_header_field (result, "Language:", catalogname); + + /* Write the merged message list out. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] INPUTFILE\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Creates an English translation catalog. The input file is the last\n\ +created English PO file, or a PO Template file (generally created by\n\ +xgettext). Untranslated entries are assigned a translation that is\n\ +identical to the msgid.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + INPUTFILE input PO or POT file\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If input file is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input file is in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --lang=CATALOGNAME set 'Language' field in the header entry\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent indented output style\n")); + printf (_("\ + --no-location suppress '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location preserve '#: filename:line' lines (default)\n")); + printf (_("\ + --strict strict Uniforum output style\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} diff --git a/gettext-tools/src/msgexec.c b/gettext-tools/src/msgexec.c new file mode 100644 index 0000000..5239518 --- /dev/null +++ b/gettext-tools/src/msgexec.c @@ -0,0 +1,468 @@ +/* Pass translations to a subprocess. + Copyright (C) 2001-2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "error.h" +#include "xvasprintf.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "msgl-charset.h" +#include "xalloc.h" +#include "full-write.h" +#include "findprog.h" +#include "spawn-pipe.h" +#include "wait-process.h" +#include "xsetenv.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#ifndef STDOUT_FILENO +# define STDOUT_FILENO 1 +#endif + + +/* Name of the subprogram. */ +static const char *sub_name; + +/* Pathname of the subprogram. */ +static const char *sub_path; + +/* Argument list for the subprogram. */ +static char **sub_argv; +static int sub_argc; + +/* Maximum exit code encountered. */ +static int exitcode; + +/* Long options. */ +static const struct option long_options[] = +{ + { "directory", required_argument, NULL, 'D' }, + { "help", no_argument, NULL, 'h' }, + { "input", required_argument, NULL, 'i' }, + { "properties-input", no_argument, NULL, 'P' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void process_msgdomain_list (const msgdomain_list_ty *mdlp); + + +int +main (int argc, char **argv) +{ + int opt; + bool do_help; + bool do_version; + const char *input_file; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + size_t i; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + input_file = NULL; + + /* The '+' in the options string causes option parsing to terminate when + the first non-option, i.e. the subprogram name, is encountered. */ + while ((opt = getopt_long (argc, argv, "+D:hi:PV", long_options, NULL)) + != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'h': + do_help = true; + break; + + case 'i': + if (input_file != NULL) + { + error (EXIT_SUCCESS, 0, _("at most one input file allowed")); + usage (EXIT_FAILURE); + } + input_file = optarg; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 'V': + do_version = true; + break; + + case CHAR_MAX + 1: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test for the subprogram name. */ + if (optind == argc) + error (EXIT_FAILURE, 0, _("missing command name")); + sub_name = argv[optind]; + + /* Build argument list for the program. */ + sub_argc = argc - optind; + sub_argv = XNMALLOC (sub_argc + 1, char *); + for (i = 0; i < sub_argc; i++) + sub_argv[i] = argv[optind + i]; + sub_argv[i] = NULL; + + /* By default, input comes from standard input. */ + if (input_file == NULL) + input_file = "-"; + + /* Read input file. */ + result = read_catalog_file (input_file, input_syntax); + + if (strcmp (sub_name, "0") != 0) + { + /* Warn if the current locale is not suitable for this PO file. */ + compare_po_locale_charsets (result); + + /* Block SIGPIPE for this process and for the subprocesses. + The subprogram may have side effects (additionally to producing some + output), therefore if there are no readers on stdout, processing of the + strings must continue nevertheless. */ + { + sigset_t sigpipe_set; + + sigemptyset (&sigpipe_set); + sigaddset (&sigpipe_set, SIGPIPE); + sigprocmask (SIG_UNBLOCK, &sigpipe_set, NULL); + } + + /* Attempt to locate the program. + This is an optimization, to avoid that spawn/exec searches the PATH + on every call. */ + sub_path = find_in_path (sub_name); + + /* Finish argument list for the program. */ + sub_argv[0] = (char *) sub_path; + } + + exitcode = 0; /* = EXIT_SUCCESS */ + + /* Apply the subprogram. */ + process_msgdomain_list (result); + + exit (exitcode); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Applies a command to all translations of a translation catalog.\n\ +The COMMAND can be any program that reads a translation from standard\n\ +input. It is invoked once for each translation. Its output becomes\n\ +msgexec's output. msgexec's return code is the maximum return code\n\ +across all invocations.\n\ +")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +A special builtin command called '0' outputs the translation, followed by a\n\ +null byte. The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + -i, --input=INPUTFILE input PO file\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If no input file is given or if it is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input file is in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + + +#ifdef EINTR + +/* EINTR handling for close(). + These functions can return -1/EINTR even though we don't have any + signal handlers set up, namely when we get interrupted via SIGSTOP. */ + +static inline int +nonintr_close (int fd) +{ + int retval; + + do + retval = close (fd); + while (retval < 0 && errno == EINTR); + + return retval; +} +#define close nonintr_close + +#endif + + +/* Pipe a string STR of size LEN bytes to the subprogram. + The byte after STR is known to be a '\0' byte. */ +static void +process_string (const message_ty *mp, const char *str, size_t len) +{ + if (strcmp (sub_name, "0") == 0) + { + /* Built-in command "0". */ + if (full_write (STDOUT_FILENO, str, len + 1) < len + 1) + error (EXIT_FAILURE, errno, _("write to stdout failed")); + } + else + { + /* General command. */ + char *location; + pid_t child; + int fd[1]; + void (*orig_sigpipe_handler)(int); + int exitstatus; + + /* Set environment variables for the subprocess. + Note: These environment variables, especially MSGEXEC_MSGCTXT and + MSGEXEC_MSGID, may contain non-ASCII characters. The subprocess + may not interpret these values correctly if the locale encoding is + different from the PO file's encoding. We want about this situation, + above. + On Unix, this problem is often harmless. On Windows, however, - both + native Windows and Cygwin - the values of environment variables *must* + be in the encoding that is the value of GetACP(), because the system + may convert the environment from char** to wchar_t** before spawning + the subprocess and back from wchar_t** to char** in the subprocess, + and it does so using the GetACP() codepage. */ + if (mp->msgctxt != NULL) + xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1); + else + unsetenv ("MSGEXEC_MSGCTXT"); + xsetenv ("MSGEXEC_MSGID", mp->msgid, 1); + if (mp->msgid_plural != NULL) + xsetenv ("MSGEXEC_MSGID_PLURAL", mp->msgid_plural, 1); + else + unsetenv ("MSGEXEC_MSGID_PLURAL"); + location = xasprintf ("%s:%ld", mp->pos.file_name, + (long) mp->pos.line_number); + xsetenv ("MSGEXEC_LOCATION", location, 1); + free (location); + if (mp->prev_msgctxt != NULL) + xsetenv ("MSGEXEC_PREV_MSGCTXT", mp->prev_msgctxt, 1); + else + unsetenv ("MSGEXEC_PREV_MSGCTXT"); + if (mp->prev_msgid != NULL) + xsetenv ("MSGEXEC_PREV_MSGID", mp->prev_msgid, 1); + else + unsetenv ("MSGEXEC_PREV_MSGID"); + if (mp->prev_msgid_plural != NULL) + xsetenv ("MSGEXEC_PREV_MSGID_PLURAL", mp->prev_msgid_plural, 1); + else + unsetenv ("MSGEXEC_PREV_MSGID_PLURAL"); + + /* Open a pipe to a subprocess. */ + child = create_pipe_out (sub_name, sub_path, sub_argv, NULL, false, true, + true, fd); + + /* Ignore SIGPIPE here. We don't care if the subprocesses terminates + successfully without having read all of the input that we feed it. */ + orig_sigpipe_handler = signal (SIGPIPE, SIG_IGN); + + if (full_write (fd[0], str, len) < len) + if (errno != EPIPE) + error (EXIT_FAILURE, errno, + _("write to %s subprocess failed"), sub_name); + + close (fd[0]); + + signal (SIGPIPE, orig_sigpipe_handler); + + /* Remove zombie process from process list, and retrieve exit status. */ + /* FIXME: Should ignore_sigpipe be set to true here? It depends on the + semantics of the subprogram... */ + exitstatus = + wait_subprocess (child, sub_name, false, false, true, true, NULL); + if (exitcode < exitstatus) + exitcode = exitstatus; + } +} + + +static void +process_message (const message_ty *mp) +{ + const char *msgstr = mp->msgstr; + size_t msgstr_len = mp->msgstr_len; + const char *p; + size_t k; + + /* Process each NUL delimited substring separately. */ + for (p = msgstr, k = 0; p < msgstr + msgstr_len; k++) + { + size_t length = strlen (p); + + if (mp->msgid_plural != NULL) + { + char *plural_form_string = xasprintf ("%zu", k); + + xsetenv ("MSGEXEC_PLURAL_FORM", plural_form_string, 1); + free (plural_form_string); + } + else + unsetenv ("MSGEXEC_PLURAL_FORM"); + process_string (mp, p, length); + + p += length + 1; + } +} + + +static void +process_message_list (const message_list_ty *mlp) +{ + size_t j; + + for (j = 0; j < mlp->nitems; j++) + process_message (mlp->item[j]); +} + + +static void +process_msgdomain_list (const msgdomain_list_ty *mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + process_message_list (mdlp->item[k]->messages); +} diff --git a/gettext-tools/src/msgfilter.c b/gettext-tools/src/msgfilter.c new file mode 100644 index 0000000..f9cb1cc --- /dev/null +++ b/gettext-tools/src/msgfilter.c @@ -0,0 +1,753 @@ +/* Edit translations using a subprocess. + Copyright (C) 2001-2010, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "error.h" +#include "xvasprintf.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "msgl-charset.h" +#include "xalloc.h" +#include "findprog.h" +#include "pipe-filter.h" +#include "xsetenv.h" +#include "filters.h" +#include "msgl-iconv.h" +#include "po-charset.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* We use a child process, and communicate through a bidirectional pipe. */ + + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Keep the header entry unmodified. */ +static int keep_header; + +/* Name of the subprogram. */ +static const char *sub_name; + +/* Pathname of the subprogram. */ +static const char *sub_path; + +/* Argument list for the subprogram. */ +static const char **sub_argv; +static int sub_argc; + +/* Filter function. */ +static void (*filter) (const char *str, size_t len, char **resultp, size_t *lengthp); + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "color", optional_argument, NULL, CHAR_MAX + 6 }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, CHAR_MAX + 1 }, + { "input", required_argument, NULL, 'i' }, + { "keep-header", no_argument, &keep_header, 1 }, + { "no-escape", no_argument, NULL, CHAR_MAX + 2 }, + { "no-location", no_argument, NULL, CHAR_MAX + 8 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 3 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 4 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 5 }, + { "style", required_argument, NULL, CHAR_MAX + 7 }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp); +static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp); + + +int +main (int argc, char **argv) +{ + int opt; + bool do_help; + bool do_version; + char *output_file; + const char *input_file; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_filepos = false; + bool sort_by_msgid = false; + int i; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + input_file = NULL; + + /* The '+' in the options string causes option parsing to terminate when + the first non-option, i.e. the subprogram name, is encountered. */ + while ((opt = getopt_long (argc, argv, "+D:EFhi:n:o:pPsVw:", long_options, + NULL)) + != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + if (input_file != NULL) + { + error (EXIT_SUCCESS, 0, _("at most one input file allowed")); + usage (EXIT_FAILURE); + } + input_file = optarg; + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: + message_print_style_indent (); + break; + + case CHAR_MAX + 2: + message_print_style_escape (false); + break; + + case CHAR_MAX + 3: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 4: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 5: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 6: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 7: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 8: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test for the subprogram name. */ + if (optind == argc) + error (EXIT_FAILURE, 0, _("missing filter name")); + sub_name = argv[optind]; + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* Build argument list for the program. */ + sub_argc = argc - optind; + sub_argv = XNMALLOC (sub_argc + 1, const char *); + for (i = 0; i < sub_argc; i++) + sub_argv[i] = argv[optind + i]; + sub_argv[i] = NULL; + + /* Extra checks for sed scripts. */ + if (strcmp (sub_name, "sed") == 0) + { + if (sub_argc == 1) + error (EXIT_FAILURE, 0, + _("at least one sed script must be specified")); + + /* Replace GNU sed specific options with portable sed options. */ + for (i = 1; i < sub_argc; i++) + { + if (strcmp (sub_argv[i], "--expression") == 0) + sub_argv[i] = "-e"; + else if (strcmp (sub_argv[i], "--file") == 0) + sub_argv[i] = "-f"; + else if (strcmp (sub_argv[i], "--quiet") == 0 + || strcmp (sub_argv[i], "--silent") == 0) + sub_argv[i] = "-n"; + + if (strcmp (sub_argv[i], "-e") == 0 + || strcmp (sub_argv[i], "-f") == 0) + i++; + } + } + + /* By default, input comes from standard input. */ + if (input_file == NULL) + input_file = "-"; + + /* Read input file. */ + result = read_catalog_file (input_file, input_syntax); + + /* Recognize special programs as built-ins. */ + if (strcmp (sub_name, "recode-sr-latin") == 0 && sub_argc == 1) + { + filter = serbian_to_latin; + + /* Convert the input to UTF-8 first. */ + result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file); + } + else if (strcmp (sub_name, "quot") == 0 && sub_argc == 1) + { + filter = ascii_quote_to_unicode; + + /* Convert the input to UTF-8 first. */ + result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file); + } + else if (strcmp (sub_name, "boldquot") == 0 && sub_argc == 1) + { + filter = ascii_quote_to_unicode_bold; + + /* Convert the input to UTF-8 first. */ + result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file); + } + else + { + filter = generic_filter; + + /* Warn if the current locale is not suitable for this PO file. */ + compare_po_locale_charsets (result); + + /* Attempt to locate the program. + This is an optimization, to avoid that spawn/exec searches the PATH + on every call. */ + sub_path = find_in_path (sub_name); + + /* Finish argument list for the program. */ + sub_argv[0] = sub_path; + } + + /* Apply the subprogram. */ + result = process_msgdomain_list (result); + + /* Sort the results. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Write the merged message list out. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] FILTER [FILTER-OPTION]\n\ +"), program_name); + printf ("\n"); + printf (_("\ +Applies a filter to all translations of a translation catalog.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + -i, --input=INPUTFILE input PO file\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If no input file is given or if it is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +The FILTER can be any program that reads a translation from standard input\n\ +and writes a modified translation to standard output.\n\ +")); + printf ("\n"); + printf (_("\ +Useful FILTER-OPTIONs when the FILTER is 'sed':\n")); + printf (_("\ + -e, --expression=SCRIPT add SCRIPT to the commands to be executed\n")); + printf (_("\ + -f, --file=SCRIPTFILE add the contents of SCRIPTFILE to the commands\n\ + to be executed\n")); + printf (_("\ + -n, --quiet, --silent suppress automatic printing of pattern space\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input file is in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + --indent indented output style\n")); + printf (_("\ + --keep-header keep header entry unmodified, don't filter it\n")); + printf (_("\ + --no-location suppress '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location preserve '#: filename:line' lines (default)\n")); + printf (_("\ + --strict strict Uniforum output style\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + + +/* Callbacks called from pipe_filter_ii_execute. */ + +struct locals +{ + /* String being written. */ + const char *str; + size_t len; + /* String being read and accumulated. */ + char *result; + size_t allocated; + size_t length; +}; + +static const void * +prepare_write (size_t *num_bytes_p, void *private_data) +{ + struct locals *l = (struct locals *) private_data; + + if (l->len > 0) + { + *num_bytes_p = l->len; + return l->str; + } + else + return NULL; +} + +static void +done_write (void *data_written, size_t num_bytes_written, void *private_data) +{ + struct locals *l = (struct locals *) private_data; + + l->str += num_bytes_written; + l->len -= num_bytes_written; +} + +static void * +prepare_read (size_t *num_bytes_p, void *private_data) +{ + struct locals *l = (struct locals *) private_data; + + if (l->length == l->allocated) + { + l->allocated = l->allocated + (l->allocated >> 1) + 1; + l->result = (char *) xrealloc (l->result, l->allocated); + } + *num_bytes_p = l->allocated - l->length; + return l->result + l->length; +} + +static void +done_read (void *data_read, size_t num_bytes_read, void *private_data) +{ + struct locals *l = (struct locals *) private_data; + + l->length += num_bytes_read; +} + + +/* Process a string STR of size LEN bytes through the subprogram. + Store the freshly allocated result at *RESULTP and its length at *LENGTHP. + */ +static void +generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp) +{ + struct locals l; + + l.str = str; + l.len = len; + l.allocated = len + (len >> 2) + 1; + l.result = XNMALLOC (l.allocated, char); + l.length = 0; + + pipe_filter_ii_execute (sub_name, sub_path, sub_argv, false, true, + prepare_write, done_write, prepare_read, done_read, + &l); + + *resultp = l.result; + *lengthp = l.length; +} + + +/* Process a string STR of size LEN bytes, then remove NUL bytes. + Store the freshly allocated result at *RESULTP and its length at *LENGTHP. + */ +static void +process_string (const char *str, size_t len, char **resultp, size_t *lengthp) +{ + char *result; + size_t length; + + filter (str, len, &result, &length); + + /* Remove NUL bytes from result. */ + { + char *p = result; + char *pend = result + length; + + for (; p < pend; p++) + if (*p == '\0') + { + char *q; + + q = p; + for (; p < pend; p++) + if (*p != '\0') + *q++ = *p; + length = q - result; + break; + } + } + + *resultp = result; + *lengthp = length; +} + + +static void +process_message (message_ty *mp) +{ + const char *msgstr = mp->msgstr; + size_t msgstr_len = mp->msgstr_len; + char *location; + size_t nsubstrings; + char **substrings; + size_t total_len; + char *total_str; + const char *p; + char *q; + size_t k; + + /* Keep the header entry unmodified, if --keep-header was given. */ + if (is_header (mp) && keep_header) + return; + + /* Set environment variables for the subprocess. + Note: These environment variables, especially MSGFILTER_MSGCTXT and + MSGFILTER_MSGID, may contain non-ASCII characters. The subprocess + may not interpret these values correctly if the locale encoding is + different from the PO file's encoding. We want about this situation, + above. + On Unix, this problem is often harmless. On Windows, however, - both + native Windows and Cygwin - the values of environment variables *must* + be in the encoding that is the value of GetACP(), because the system + may convert the environment from char** to wchar_t** before spawning + the subprocess and back from wchar_t** to char** in the subprocess, + and it does so using the GetACP() codepage. */ + if (mp->msgctxt != NULL) + xsetenv ("MSGFILTER_MSGCTXT", mp->msgctxt, 1); + else + unsetenv ("MSGFILTER_MSGCTXT"); + xsetenv ("MSGFILTER_MSGID", mp->msgid, 1); + if (mp->msgid_plural != NULL) + xsetenv ("MSGFILTER_MSGID_PLURAL", mp->msgid_plural, 1); + else + unsetenv ("MSGFILTER_MSGID_PLURAL"); + location = xasprintf ("%s:%ld", mp->pos.file_name, + (long) mp->pos.line_number); + xsetenv ("MSGFILTER_LOCATION", location, 1); + free (location); + if (mp->prev_msgctxt != NULL) + xsetenv ("MSGFILTER_PREV_MSGCTXT", mp->prev_msgctxt, 1); + else + unsetenv ("MSGFILTER_PREV_MSGCTXT"); + if (mp->prev_msgid != NULL) + xsetenv ("MSGFILTER_PREV_MSGID", mp->prev_msgid, 1); + else + unsetenv ("MSGFILTER_PREV_MSGID"); + if (mp->prev_msgid_plural != NULL) + xsetenv ("MSGFILTER_PREV_MSGID_PLURAL", mp->prev_msgid_plural, 1); + else + unsetenv ("MSGFILTER_PREV_MSGID_PLURAL"); + + /* Count NUL delimited substrings. */ + for (p = msgstr, nsubstrings = 0; + p < msgstr + msgstr_len; + p += strlen (p) + 1, nsubstrings++); + + /* Process each NUL delimited substring separately. */ + substrings = XNMALLOC (nsubstrings, char *); + for (p = msgstr, k = 0, total_len = 0; k < nsubstrings; k++) + { + char *result; + size_t length; + + if (mp->msgid_plural != NULL) + { + char *plural_form_string = xasprintf ("%zu", k); + + xsetenv ("MSGFILTER_PLURAL_FORM", plural_form_string, 1); + free (plural_form_string); + } + else + unsetenv ("MSGFILTER_PLURAL_FORM"); + process_string (p, strlen (p), &result, &length); + result = (char *) xrealloc (result, length + 1); + result[length] = '\0'; + substrings[k] = result; + total_len += length + 1; + + p += strlen (p) + 1; + } + + /* Concatenate the results, including the NUL after each. */ + total_str = XNMALLOC (total_len, char); + for (k = 0, q = total_str; k < nsubstrings; k++) + { + size_t length = strlen (substrings[k]); + + memcpy (q, substrings[k], length + 1); + free (substrings[k]); + q += length + 1; + } + free (substrings); + + mp->msgstr = total_str; + mp->msgstr_len = total_len; +} + + +static void +process_message_list (message_list_ty *mlp) +{ + size_t j; + + for (j = 0; j < mlp->nitems; j++) + process_message (mlp->item[j]); +} + + +static msgdomain_list_ty * +process_msgdomain_list (msgdomain_list_ty *mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + process_message_list (mdlp->item[k]->messages); + + return mdlp; +} diff --git a/gettext-tools/src/msgfmt.c b/gettext-tools/src/msgfmt.c new file mode 100644 index 0000000..3fabd87 --- /dev/null +++ b/gettext-tools/src/msgfmt.c @@ -0,0 +1,1473 @@ +/* Converts Uniforum style .po files to binary .mo files + Copyright (C) 1995-1998, 2000-2007, 2009-2010, 2012 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "str-list.h" +#include "dir-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "xalloc.h" +#include "msgfmt.h" +#include "write-mo.h" +#include "write-java.h" +#include "write-csharp.h" +#include "write-resources.h" +#include "write-tcl.h" +#include "write-qt.h" +#include "write-desktop.h" +#include "propername.h" +#include "message.h" +#include "open-catalog.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "read-desktop.h" +#include "po-charset.h" +#include "msgl-check.h" +#include "msgl-iconv.h" +#include "concat-filename.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Contains exit status for case in which no premature exit occurs. */ +static int exit_status; + +/* If true include even fuzzy translations in output file. */ +static bool include_fuzzies = false; + +/* If true include even untranslated messages in output file. */ +static bool include_untranslated = false; + +/* Specifies name of the output file. */ +static const char *output_file_name; + +/* Java mode output file specification. */ +static bool java_mode; +static bool assume_java2; +static const char *java_resource_name; +static const char *java_locale_name; +static const char *java_class_directory; +static bool java_output_source; + +/* C# mode output file specification. */ +static bool csharp_mode; +static const char *csharp_resource_name; +static const char *csharp_locale_name; +static const char *csharp_base_directory; + +/* C# resources mode output file specification. */ +static bool csharp_resources_mode; + +/* Tcl mode output file specification. */ +static bool tcl_mode; +static const char *tcl_locale_name; +static const char *tcl_base_directory; + +/* Qt mode output file specification. */ +static bool qt_mode; + +/* Desktop Entry mode output file specification. */ +static bool desktop_mode; +static const char *desktop_locale_name; +static const char *desktop_template_name; +static const char *desktop_base_directory; +static hash_table desktop_keywords; +static bool desktop_default_keywords = true; + +/* We may have more than one input file. Domains with same names in + different files have to merged. So we need a list of tables for + each output file. */ +struct msg_domain +{ + /* List for mapping message IDs to message strings. */ + message_list_ty *mlp; + /* Name of domain these ID/String pairs are part of. */ + const char *domain_name; + /* Output file name. */ + const char *file_name; + /* Link to the next domain. */ + struct msg_domain *next; +}; +static struct msg_domain *domain_list; +static struct msg_domain *current_domain; + +/* Be more verbose. Use only 'fprintf' and 'multiline_warning' but not + 'error' or 'multiline_error' to emit verbosity messages, because 'error' + and 'multiline_error' during PO file parsing cause the program to exit + with EXIT_FAILURE. See function lex_end(). */ +int verbose = 0; + +/* If true check strings according to format string rules for the + language. */ +static bool check_format_strings = false; + +/* If true check the header entry is present and complete. */ +static bool check_header = false; + +/* Check that domain directives can be satisfied. */ +static bool check_domain = false; + +/* Check that msgfmt's behaviour is semantically compatible with + X/Open msgfmt or XView msgfmt. */ +static bool check_compatibility = false; + +/* If true, consider that strings containing an '&' are menu items and + the '&' designates a keyboard accelerator, and verify that the translations + also have a keyboard accelerator. */ +static bool check_accelerators = false; +static char accelerator_char = '&'; + +/* Counters for statistics on translations for the processed files. */ +static int msgs_translated; +static int msgs_untranslated; +static int msgs_fuzzy; + +/* If not zero print statistics about translation at the end. */ +static int do_statistics; + +/* Long options. */ +static const struct option long_options[] = +{ + { "alignment", required_argument, NULL, 'a' }, + { "check", no_argument, NULL, 'c' }, + { "check-accelerators", optional_argument, NULL, CHAR_MAX + 1 }, + { "check-compatibility", no_argument, NULL, 'C' }, + { "check-domain", no_argument, NULL, CHAR_MAX + 2 }, + { "check-format", no_argument, NULL, CHAR_MAX + 3 }, + { "check-header", no_argument, NULL, CHAR_MAX + 4 }, + { "csharp", no_argument, NULL, CHAR_MAX + 10 }, + { "csharp-resources", no_argument, NULL, CHAR_MAX + 11 }, + { "desktop", no_argument, NULL, CHAR_MAX + 15 }, + { "directory", required_argument, NULL, 'D' }, + { "endianness", required_argument, NULL, CHAR_MAX + 13 }, + { "help", no_argument, NULL, 'h' }, + { "java", no_argument, NULL, 'j' }, + { "java2", no_argument, NULL, CHAR_MAX + 5 }, + { "keyword", required_argument, NULL, 'k' }, + { "locale", required_argument, NULL, 'l' }, + { "no-hash", no_argument, NULL, CHAR_MAX + 6 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "qt", no_argument, NULL, CHAR_MAX + 9 }, + { "resource", required_argument, NULL, 'r' }, + { "source", no_argument, NULL, CHAR_MAX + 14 }, + { "statistics", no_argument, &do_statistics, 1 }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 8 }, + { "tcl", no_argument, NULL, CHAR_MAX + 7 }, + { "template", required_argument, NULL, CHAR_MAX + 16 }, + { "use-fuzzy", no_argument, NULL, 'f' }, + { "use-untranslated", no_argument, NULL, CHAR_MAX + 12 }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static const char *add_mo_suffix (const char *); +static struct msg_domain *new_domain (const char *name, const char *file_name); +static bool is_nonobsolete (const message_ty *mp); +static void read_catalog_file_msgfmt (char *filename, + catalog_input_format_ty input_syntax); +static string_list_ty *get_languages (const char *directory); +static int msgfmt_desktop_bulk (const char *directory, + const char *template_file_name, + hash_table *keywords, + const char *file_name); + + +int +main (int argc, char *argv[]) +{ + int opt; + bool do_help = false; + bool do_version = false; + bool strict_uniforum = false; + catalog_input_format_ty input_syntax = &input_format_po; + int arg_i; + const char *canon_encoding; + struct msg_domain *domain; + + /* Set default value for global variables. */ + alignment = DEFAULT_OUTPUT_ALIGNMENT; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + error_one_per_line = 1; + exit_status = EXIT_SUCCESS; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + while ((opt = getopt_long (argc, argv, "a:cCd:D:fhjl:o:Pr:vV", long_options, + NULL)) + != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + case 'a': + { + char *endp; + size_t new_align = strtoul (optarg, &endp, 0); + + if (endp != optarg) + alignment = new_align; + } + break; + case 'c': + check_domain = true; + check_format_strings = true; + check_header = true; + break; + case 'C': + check_compatibility = true; + break; + case 'd': + java_class_directory = optarg; + csharp_base_directory = optarg; + tcl_base_directory = optarg; + desktop_base_directory = optarg; + break; + case 'D': + dir_list_append (optarg); + break; + case 'f': + include_fuzzies = true; + break; + case 'h': + do_help = true; + break; + case 'j': + java_mode = true; + break; + case 'k': + if (optarg == NULL) + desktop_default_keywords = false; + else + { + if (desktop_keywords.table == NULL) + { + hash_init (&desktop_keywords, 100); + desktop_default_keywords = false; + } + + desktop_add_keyword (&desktop_keywords, optarg, false); + } + break; + case 'l': + java_locale_name = optarg; + csharp_locale_name = optarg; + tcl_locale_name = optarg; + desktop_locale_name = optarg; + break; + case 'o': + output_file_name = optarg; + break; + case 'P': + input_syntax = &input_format_properties; + break; + case 'r': + java_resource_name = optarg; + csharp_resource_name = optarg; + break; + case 'S': + strict_uniforum = true; + break; + case 'v': + verbose++; + break; + case 'V': + do_version = true; + break; + case CHAR_MAX + 1: /* --check-accelerators */ + check_accelerators = true; + if (optarg != NULL) + { + if (optarg[0] != '\0' && ispunct ((unsigned char) optarg[0]) + && optarg[1] == '\0') + accelerator_char = optarg[0]; + else + error (EXIT_FAILURE, 0, + _("the argument to %s should be a single punctuation character"), + "--check-accelerators"); + } + break; + case CHAR_MAX + 2: /* --check-domain */ + check_domain = true; + break; + case CHAR_MAX + 3: /* --check-format */ + check_format_strings = true; + break; + case CHAR_MAX + 4: /* --check-header */ + check_header = true; + break; + case CHAR_MAX + 5: /* --java2 */ + java_mode = true; + assume_java2 = true; + break; + case CHAR_MAX + 6: /* --no-hash */ + no_hash_table = true; + break; + case CHAR_MAX + 7: /* --tcl */ + tcl_mode = true; + break; + case CHAR_MAX + 8: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + case CHAR_MAX + 9: /* --qt */ + qt_mode = true; + break; + case CHAR_MAX + 10: /* --csharp */ + csharp_mode = true; + break; + case CHAR_MAX + 11: /* --csharp-resources */ + csharp_resources_mode = true; + break; + case CHAR_MAX + 12: /* --use-untranslated (undocumented) */ + include_untranslated = true; + break; + case CHAR_MAX + 13: /* --endianness={big|little} */ + { + int endianness; + + if (strcmp (optarg, "big") == 0) + endianness = 1; + else if (strcmp (optarg, "little") == 0) + endianness = 0; + else + error (EXIT_FAILURE, 0, _("invalid endianness: %s"), optarg); + + byteswap = endianness ^ ENDIANNESS; + } + break; + case CHAR_MAX + 14: /* --source */ + java_output_source = true; + break; + case CHAR_MAX + 15: /* --desktop */ + desktop_mode = true; + break; + case CHAR_MAX + 16: /* --template=TEMPLATE */ + desktop_template_name = optarg; + break; + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "1995-1998, 2000-2010"); + printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test whether we have a .po file name as argument. */ + if (optind >= argc && !(desktop_mode && desktop_base_directory)) + { + error (EXIT_SUCCESS, 0, _("no input file given")); + usage (EXIT_FAILURE); + } + if (optind < argc && desktop_mode && desktop_base_directory) + { + error (EXIT_SUCCESS, 0, + _("no input file should be given if %s and %s are specified"), + "--desktop", "-d"); + usage (EXIT_FAILURE); + } + + /* Check for contradicting options. */ + { + unsigned int modes = + (java_mode ? 1 : 0) + | (csharp_mode ? 2 : 0) + | (csharp_resources_mode ? 4 : 0) + | (tcl_mode ? 8 : 0) + | (qt_mode ? 16 : 0) + | (desktop_mode ? 32 : 0); + static const char *mode_options[] = + { "--java", "--csharp", "--csharp-resources", "--tcl", "--qt", + "--desktop" }; + /* More than one bit set? */ + if (modes & (modes - 1)) + { + const char *first_option; + const char *second_option; + unsigned int i; + for (i = 0; ; i++) + if (modes & (1 << i)) + break; + first_option = mode_options[i]; + for (i = i + 1; ; i++) + if (modes & (1 << i)) + break; + second_option = mode_options[i]; + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + first_option, second_option); + } + } + if (java_mode) + { + if (output_file_name != NULL) + { + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--java", "--output-file"); + } + if (java_class_directory == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-d directory\" specification"), + "--java"); + usage (EXIT_FAILURE); + } + } + else if (csharp_mode) + { + if (output_file_name != NULL) + { + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--csharp", "--output-file"); + } + if (csharp_locale_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-l locale\" specification"), + "--csharp"); + usage (EXIT_FAILURE); + } + if (csharp_base_directory == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-d directory\" specification"), + "--csharp"); + usage (EXIT_FAILURE); + } + } + else if (tcl_mode) + { + if (output_file_name != NULL) + { + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--tcl", "--output-file"); + } + if (tcl_locale_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-l locale\" specification"), + "--tcl"); + usage (EXIT_FAILURE); + } + if (tcl_base_directory == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-d directory\" specification"), + "--tcl"); + usage (EXIT_FAILURE); + } + } + else if (desktop_mode) + { + if (desktop_template_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"--template template\" specification"), + "--desktop"); + usage (EXIT_FAILURE); + } + if (output_file_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-o file\" specification"), + "--desktop"); + usage (EXIT_FAILURE); + } + if (desktop_base_directory != NULL && desktop_locale_name != NULL) + error (EXIT_FAILURE, 0, + _("%s and %s are mutually exclusive in %s"), + "-d", "-l", "--desktop"); + if (desktop_base_directory == NULL && desktop_locale_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-l locale\" specification"), + "--desktop"); + usage (EXIT_FAILURE); + } + } + else + { + if (java_resource_name != NULL) + { + error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"), + "--resource", "--java", "--csharp"); + usage (EXIT_FAILURE); + } + if (java_locale_name != NULL) + { + error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"), + "--locale", "--java", "--csharp", "--tcl"); + usage (EXIT_FAILURE); + } + if (java_class_directory != NULL) + { + error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"), + "-d", "--java", "--csharp", "--tcl"); + usage (EXIT_FAILURE); + } + } + + if (desktop_mode && desktop_default_keywords) + { + if (desktop_keywords.table == NULL) + hash_init (&desktop_keywords, 100); + desktop_add_default_keywords (&desktop_keywords); + } + + /* Bulk processing mode for .desktop files. + Process all .po files in desktop_base_directory. */ + if (desktop_mode && desktop_base_directory) + { + exit_status = msgfmt_desktop_bulk (desktop_base_directory, + desktop_template_name, + &desktop_keywords, + output_file_name); + if (desktop_keywords.table != NULL) + hash_destroy (&desktop_keywords); + exit (exit_status); + } + + /* The -o option determines the name of the domain and therefore + the output file. */ + if (output_file_name != NULL) + current_domain = + new_domain (output_file_name, + strict_uniforum && !csharp_resources_mode && !qt_mode + ? add_mo_suffix (output_file_name) + : output_file_name); + + /* Process all given .po files. */ + for (arg_i = optind; arg_i < argc; arg_i++) + { + /* Remember that we currently have not specified any domain. This + is of course not true when we saw the -o option. */ + if (output_file_name == NULL) + current_domain = NULL; + + /* And process the input file. */ + read_catalog_file_msgfmt (argv[arg_i], input_syntax); + } + + /* We know a priori that some input_syntax->parse() functions convert + strings to UTF-8. */ + canon_encoding = (input_syntax->produces_utf8 ? po_charset_utf8 : NULL); + + /* Remove obsolete messages. They were only needed for duplicate + checking. */ + for (domain = domain_list; domain != NULL; domain = domain->next) + message_list_remove_if_not (domain->mlp, is_nonobsolete); + + /* Perform all kinds of checks: plural expressions, format strings, ... */ + { + int nerrors = 0; + + for (domain = domain_list; domain != NULL; domain = domain->next) + nerrors += + check_message_list (domain->mlp, + /* Untranslated and fuzzy messages have already + been dealt with during parsing, see below in + msgfmt_frob_new_message. */ + 0, 0, + 1, check_format_strings, check_header, + check_compatibility, + check_accelerators, accelerator_char); + + /* Exit with status 1 on any error. */ + if (nerrors > 0) + { + error (0, 0, + ngettext ("found %d fatal error", "found %d fatal errors", + nerrors), + nerrors); + exit_status = EXIT_FAILURE; + } + } + + /* Now write out all domains. */ + for (domain = domain_list; domain != NULL; domain = domain->next) + { + if (java_mode) + { + if (msgdomain_write_java (domain->mlp, canon_encoding, + java_resource_name, java_locale_name, + java_class_directory, assume_java2, + java_output_source)) + exit_status = EXIT_FAILURE; + } + else if (csharp_mode) + { + if (msgdomain_write_csharp (domain->mlp, canon_encoding, + csharp_resource_name, csharp_locale_name, + csharp_base_directory)) + exit_status = EXIT_FAILURE; + } + else if (csharp_resources_mode) + { + if (msgdomain_write_csharp_resources (domain->mlp, canon_encoding, + domain->domain_name, + domain->file_name)) + exit_status = EXIT_FAILURE; + } + else if (tcl_mode) + { + if (msgdomain_write_tcl (domain->mlp, canon_encoding, + tcl_locale_name, tcl_base_directory)) + exit_status = EXIT_FAILURE; + } + else if (qt_mode) + { + if (msgdomain_write_qt (domain->mlp, canon_encoding, + domain->domain_name, domain->file_name)) + exit_status = EXIT_FAILURE; + } + else if (desktop_mode) + { + if (msgdomain_write_desktop (domain->mlp, canon_encoding, + desktop_locale_name, + desktop_template_name, + &desktop_keywords, + domain->file_name)) + exit_status = EXIT_FAILURE; + + if (desktop_keywords.table != NULL) + hash_destroy (&desktop_keywords); + } + else + { + if (msgdomain_write_mo (domain->mlp, domain->domain_name, + domain->file_name)) + exit_status = EXIT_FAILURE; + } + + /* List is not used anymore. */ + message_list_free (domain->mlp, 0); + } + + /* Print statistics if requested. */ + if (verbose || do_statistics) + { + if (do_statistics + verbose >= 2 && optind < argc) + { + /* Print the input file name(s) in front of the statistics line. */ + char *all_input_file_names; + + { + string_list_ty input_file_names; + + string_list_init (&input_file_names);; + for (arg_i = optind; arg_i < argc; arg_i++) + string_list_append (&input_file_names, argv[arg_i]); + all_input_file_names = + string_list_join (&input_file_names, ", ", '\0', false); + string_list_destroy (&input_file_names); + } + + /* TRANSLATORS: The prefix before a statistics message. The argument + is a file name or a comma separated list of file names. */ + fprintf (stderr, _("%s: "), all_input_file_names); + free (all_input_file_names); + } + fprintf (stderr, + ngettext ("%d translated message", "%d translated messages", + msgs_translated), + msgs_translated); + if (msgs_fuzzy > 0) + fprintf (stderr, + ngettext (", %d fuzzy translation", ", %d fuzzy translations", + msgs_fuzzy), + msgs_fuzzy); + if (msgs_untranslated > 0) + fprintf (stderr, + ngettext (", %d untranslated message", + ", %d untranslated messages", + msgs_untranslated), + msgs_untranslated); + fputs (".\n", stderr); + } + + exit (exit_status); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] filename.po ...\n\ +"), program_name); + printf ("\n"); + printf (_("\ +Generate binary message catalog from textual translation description.\n\ +")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +Similarly for optional arguments.\n\ +")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + filename.po ... input files\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If input file is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Operation mode:\n")); + printf (_("\ + -j, --java Java mode: generate a Java ResourceBundle class\n")); + printf (_("\ + --java2 like --java, and assume Java2 (JDK 1.2 or higher)\n")); + printf (_("\ + --csharp C# mode: generate a .NET .dll file\n")); + printf (_("\ + --csharp-resources C# resources mode: generate a .NET .resources file\n")); + printf (_("\ + --tcl Tcl mode: generate a tcl/msgcat .msg file\n")); + printf (_("\ + --qt Qt mode: generate a Qt .qm file\n")); + printf (_("\ + --desktop Desktop Entry mode: generate a .desktop file\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ + --strict enable strict Uniforum mode\n")); + printf (_("\ +If output file is -, output is written to standard output.\n")); + printf ("\n"); + printf (_("\ +Output file location in Java mode:\n")); + printf (_("\ + -r, --resource=RESOURCE resource name\n")); + printf (_("\ + -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); + printf (_("\ + --source produce a .java file, instead of a .class file\n")); + printf (_("\ + -d DIRECTORY base directory of classes directory hierarchy\n")); + printf (_("\ +The class name is determined by appending the locale name to the resource name,\n\ +separated with an underscore. The -d option is mandatory. The class is\n\ +written under the specified directory.\n\ +")); + printf ("\n"); + printf (_("\ +Output file location in C# mode:\n")); + printf (_("\ + -r, --resource=RESOURCE resource name\n")); + printf (_("\ + -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); + printf (_("\ + -d DIRECTORY base directory for locale dependent .dll files\n")); + printf (_("\ +The -l and -d options are mandatory. The .dll file is written in a\n\ +subdirectory of the specified directory whose name depends on the locale.\n")); + printf ("\n"); + printf (_("\ +Output file location in Tcl mode:\n")); + printf (_("\ + -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); + printf (_("\ + -d DIRECTORY base directory of .msg message catalogs\n")); + printf (_("\ +The -l and -d options are mandatory. The .msg file is written in the\n\ +specified directory.\n")); + printf ("\n"); + printf (_("\ +Desktop Entry mode options:\n")); + printf (_("\ + -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ + --template=TEMPLATE a .desktop file used as a template\n")); + printf (_("\ + -d DIRECTORY base directory of .po files\n")); + printf (_("\ + -kWORD, --keyword=WORD look for WORD as an additional keyword\n\ + -k, --keyword do not to use default keywords\n")); + printf (_("\ +The -l, -o, and --template options are mandatory. If -D is specified, input\n\ +files are read from the directory instead of the command line arguments.\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input files are in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input files are in NeXTstep/GNUstep .strings\n\ + syntax\n")); + printf ("\n"); + printf (_("\ +Input file interpretation:\n")); + printf (_("\ + -c, --check perform all the checks implied by\n\ + --check-format, --check-header, --check-domain\n")); + printf (_("\ + --check-format check language dependent format strings\n")); + printf (_("\ + --check-header verify presence and contents of the header entry\n")); + printf (_("\ + --check-domain check for conflicts between domain directives\n\ + and the --output-file option\n")); + printf (_("\ + -C, --check-compatibility check that GNU msgfmt behaves like X/Open msgfmt\n")); + printf (_("\ + --check-accelerators[=CHAR] check presence of keyboard accelerators for\n\ + menu items\n")); + printf (_("\ + -f, --use-fuzzy use fuzzy entries in output\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + -a, --alignment=NUMBER align strings to NUMBER bytes (default: %d)\n"), DEFAULT_OUTPUT_ALIGNMENT); + printf (_("\ + --endianness=BYTEORDER write out 32-bit numbers in the given byte order\n\ + (big or little, default depends on platform)\n")); + printf (_("\ + --no-hash binary file will not include the hash table\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf (_("\ + --statistics print statistics about translations\n")); + printf (_("\ + -v, --verbose increase verbosity level\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), stdout); + } + + exit (status); +} + + +static const char * +add_mo_suffix (const char *fname) +{ + size_t len; + char *result; + + len = strlen (fname); + if (len > 3 && memcmp (fname + len - 3, ".mo", 3) == 0) + return fname; + if (len > 4 && memcmp (fname + len - 4, ".gmo", 4) == 0) + return fname; + result = XNMALLOC (len + 4, char); + stpcpy (stpcpy (result, fname), ".mo"); + return result; +} + + +static struct msg_domain * +new_domain (const char *name, const char *file_name) +{ + struct msg_domain **p_dom = &domain_list; + + while (*p_dom != NULL && strcmp (name, (*p_dom)->domain_name) != 0) + p_dom = &(*p_dom)->next; + + if (*p_dom == NULL) + { + struct msg_domain *domain; + + domain = XMALLOC (struct msg_domain); + domain->mlp = message_list_alloc (true); + domain->domain_name = name; + domain->file_name = file_name; + domain->next = NULL; + *p_dom = domain; + } + + return *p_dom; +} + + +static bool +is_nonobsolete (const message_ty *mp) +{ + return !mp->obsolete; +} + + +/* The rest of the file defines a subclass msgfmt_catalog_reader_ty of + default_catalog_reader_ty. Its particularities are: + - The header entry check is performed on-the-fly. + - Comments are not stored, they are discarded right away. + (This is achieved by setting handle_comments = false.) + - The multi-domain handling is adapted to our domain_list. + */ + + +/* This structure defines a derived class of the default_catalog_reader_ty + class. (See read-catalog-abstract.h for an explanation.) */ +typedef struct msgfmt_catalog_reader_ty msgfmt_catalog_reader_ty; +struct msgfmt_catalog_reader_ty +{ + /* inherited instance variables, etc */ + DEFAULT_CATALOG_READER_TY + + bool has_header_entry; + bool has_nonfuzzy_header_entry; +}; + + +/* Prepare for first message. */ +static void +msgfmt_constructor (abstract_catalog_reader_ty *that) +{ + msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that; + + /* Invoke superclass constructor. */ + default_constructor (that); + + this->has_header_entry = false; + this->has_nonfuzzy_header_entry = false; +} + + +/* Some checks after whole file is read. */ +static void +msgfmt_parse_debrief (abstract_catalog_reader_ty *that) +{ + msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that; + + /* Invoke superclass method. */ + default_parse_debrief (that); + + /* Test whether header entry was found. */ + if (check_header) + { + if (!this->has_header_entry) + { + multiline_error (xasprintf ("%s: ", this->file_name), + xasprintf (_("\ +warning: PO file header missing or invalid\n"))); + multiline_error (NULL, + xasprintf (_("\ +warning: charset conversion will not work\n"))); + } + else if (!this->has_nonfuzzy_header_entry) + { + /* Has only a fuzzy header entry. Since the versions 0.10.xx + ignore a fuzzy header entry and even give an error on it, we + give a warning, to increase operability with these older + msgfmt versions. This warning can go away in January 2003. */ + multiline_warning (xasprintf ("%s: ", this->file_name), + xasprintf (_("warning: PO file header fuzzy\n"))); + multiline_warning (NULL, + xasprintf (_("\ +warning: older versions of msgfmt will give an error on this\n"))); + } + } +} + + +/* Set 'domain' directive when seen in .po file. */ +static void +msgfmt_set_domain (default_catalog_reader_ty *this, char *name) +{ + /* If no output file was given, we change it with each 'domain' + directive. */ + if (!java_mode && !csharp_mode && !csharp_resources_mode && !tcl_mode + && !qt_mode && !desktop_mode && output_file_name == NULL) + { + size_t correct; + + correct = strcspn (name, INVALID_PATH_CHAR); + if (name[correct] != '\0') + { + exit_status = EXIT_FAILURE; + if (correct == 0) + { + error (0, 0, _("\ +domain name \"%s\" not suitable as file name"), name); + return; + } + else + error (0, 0, _("\ +domain name \"%s\" not suitable as file name: will use prefix"), name); + name[correct] = '\0'; + } + + /* Set new domain. */ + current_domain = new_domain (name, add_mo_suffix (name)); + this->domain = current_domain->domain_name; + this->mlp = current_domain->mlp; + } + else + { + if (check_domain) + po_gram_error_at_line (&gram_pos, + _("'domain %s' directive ignored"), name); + + /* NAME was allocated in po-gram-gen.y but is not used anywhere. */ + free (name); + } +} + + +static void +msgfmt_add_message (default_catalog_reader_ty *this, + char *msgctxt, + char *msgid, + lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete) +{ + /* Check whether already a domain is specified. If not, use default + domain. */ + if (current_domain == NULL) + { + current_domain = new_domain (MESSAGE_DOMAIN_DEFAULT, + add_mo_suffix (MESSAGE_DOMAIN_DEFAULT)); + /* Keep current_domain and this->domain synchronized. */ + this->domain = current_domain->domain_name; + this->mlp = current_domain->mlp; + } + + /* Invoke superclass method. */ + default_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural, + msgstr, msgstr_len, msgstr_pos, + prev_msgctxt, prev_msgid, prev_msgid_plural, + force_fuzzy, obsolete); +} + + +static void +msgfmt_frob_new_message (default_catalog_reader_ty *that, message_ty *mp, + const lex_pos_ty *msgid_pos, + const lex_pos_ty *msgstr_pos) +{ + msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that; + + if (!mp->obsolete) + { + /* Don't emit untranslated entries. + Also don't emit fuzzy entries, unless --use-fuzzy was specified. + But ignore fuzziness of the header entry. */ + if ((!include_untranslated && mp->msgstr[0] == '\0') + || (!include_fuzzies && mp->is_fuzzy && !is_header (mp))) + { + if (check_compatibility) + { + error_with_progname = false; + error_at_line (0, 0, mp->pos.file_name, mp->pos.line_number, + (mp->msgstr[0] == '\0' + ? _("empty 'msgstr' entry ignored") + : _("fuzzy 'msgstr' entry ignored"))); + error_with_progname = true; + } + + /* Increment counter for fuzzy/untranslated messages. */ + if (mp->msgstr[0] == '\0') + ++msgs_untranslated; + else + ++msgs_fuzzy; + + mp->obsolete = true; + } + else + { + /* Test for header entry. */ + if (is_header (mp)) + { + this->has_header_entry = true; + if (!mp->is_fuzzy) + this->has_nonfuzzy_header_entry = true; + } + else + /* We don't count the header entry in the statistic so place + the counter incrementation here. */ + if (mp->is_fuzzy) + ++msgs_fuzzy; + else + ++msgs_translated; + } + } +} + + +/* Test for '#, fuzzy' comments and warn. */ +static void +msgfmt_comment_special (abstract_catalog_reader_ty *that, const char *s) +{ + msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that; + + /* Invoke superclass method. */ + default_comment_special (that, s); + + if (this->is_fuzzy) + { + static bool warned = false; + + if (!include_fuzzies && check_compatibility && !warned) + { + warned = true; + error (0, 0, _("\ +%s: warning: source file contains fuzzy translation"), + gram_pos.file_name); + } + } +} + + +/* So that the one parser can be used for multiple programs, and also + use good data hiding and encapsulation practices, an object + oriented approach has been taken. An object instance is allocated, + and all actions resulting from the parse will be through + invocations of method functions of that object. */ + +static default_catalog_reader_class_ty msgfmt_methods = +{ + { + sizeof (msgfmt_catalog_reader_ty), + msgfmt_constructor, + default_destructor, + default_parse_brief, + msgfmt_parse_debrief, + default_directive_domain, + default_directive_message, + default_comment, + default_comment_dot, + default_comment_filepos, + msgfmt_comment_special + }, + msgfmt_set_domain, /* set_domain */ + msgfmt_add_message, /* add_message */ + msgfmt_frob_new_message /* frob_new_message */ +}; + + +/* Read .po file FILENAME and store translation pairs. */ +static void +read_catalog_file_msgfmt (char *filename, catalog_input_format_ty input_syntax) +{ + char *real_filename; + FILE *fp = open_catalog_file (filename, &real_filename, true); + default_catalog_reader_ty *pop; + + pop = default_catalog_reader_alloc (&msgfmt_methods); + pop->handle_comments = false; + pop->allow_domain_directives = true; + pop->allow_duplicates = false; + pop->allow_duplicates_if_same_msgstr = false; + pop->file_name = real_filename; + pop->mdlp = NULL; + pop->mlp = NULL; + if (current_domain != NULL) + { + /* Keep current_domain and this->domain synchronized. */ + pop->domain = current_domain->domain_name; + pop->mlp = current_domain->mlp; + } + po_lex_pass_obsolete_entries (true); + catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename, + filename, input_syntax); + catalog_reader_free ((abstract_catalog_reader_ty *) pop); + + if (fp != stdin) + fclose (fp); +} + +static void +add_languages (string_list_ty *languages, string_list_ty *desired_languages, + const char *line, size_t length) +{ + char *start; + + /* Split the line by whitespace and build the languages list. */ + for (start = (char *) line; start - line < length; ) + { + char *p; + + /* Skip whitespace before the string. */ + while (*start == ' ' || *start == '\t') + start++; + + p = start; + while (*p != '\0' && *p != ' ' && *p != '\t') + p++; + + *p = '\0'; + if (desired_languages == NULL + || string_list_member (desired_languages, start)) + string_list_append_unique (languages, start); + start = p + 1; + } +} + +/* Compute the languages list by reading the "LINGUAS" envvar or the + LINGUAS file under DIRECTORY. */ +static string_list_ty * +get_languages (const char *directory) +{ + char *envval; + string_list_ty *languages; + string_list_ty *desired_languages = NULL; + char *linguas_file_name; + struct stat statbuf; + FILE *fp; + size_t line_len = 0; + char *line_buf = NULL; + + languages = string_list_alloc (); + envval = getenv ("LINGUAS"); + if (envval) + { + desired_languages = string_list_alloc (); + add_languages (desired_languages, NULL, envval, strlen (envval)); + } + + linguas_file_name = xconcatenated_filename (directory, "LINGUAS", NULL); + if (stat (linguas_file_name, &statbuf) < 0) + { + error (EXIT_SUCCESS, 0, _("%s does not exist"), linguas_file_name); + string_list_free (languages); + if (desired_languages != NULL) + string_list_free (desired_languages); + free (linguas_file_name); + return NULL; + } + + fp = fopen (linguas_file_name, "r"); + if (fp == NULL) + { + error (EXIT_SUCCESS, 0, _("%s exists but cannot read"), + linguas_file_name); + string_list_free (languages); + if (desired_languages != NULL) + string_list_free (desired_languages); + free (linguas_file_name); + return NULL; + } + + while (!feof (fp)) + { + /* Read next line from file. */ + int len = getline (&line_buf, &line_len, fp); + + /* In case of an error leave loop. */ + if (len < 0) + break; + + /* Remove trailing '\n' and trailing whitespace. */ + if (len > 0 && line_buf[len - 1] == '\n') + line_buf[--len] = '\0'; + while (len > 0 + && (line_buf[len - 1] == ' ' + || line_buf[len - 1] == '\t' + || line_buf[len - 1] == '\r')) + line_buf[--len] = '\0'; + + /* Test if we have to ignore the line. */ + if (*line_buf == '\0' || *line_buf == '#') + continue; + + add_languages (languages, desired_languages, line_buf, len); + } + + free (line_buf); + fclose (fp); + if (desired_languages != NULL) + string_list_free (desired_languages); + free (linguas_file_name); + + return languages; +} + +/* Helper function to support 'bulk' operation mode of --desktop. + This reads all .po files in DIRECTORY and merges them into a + .desktop file FILE_NAME. Currently it does not support some + options available in 'iterative' mode, such as --statistics. */ +static int +msgfmt_desktop_bulk (const char *directory, + const char *template_file_name, + hash_table *keywords, + const char *file_name) +{ + string_list_ty *languages = NULL; + message_list_ty **messages = NULL; + void *saved_dir_list; + int retval = 0; + size_t i; + + languages = get_languages (directory); + if (!languages) + return EXIT_FAILURE; + + /* Reset the directory search list so only .po files under DIRECTORY + will be read. */ + saved_dir_list = dir_list_save_reset (); + dir_list_append (directory); + + /* Read all .po files. */ + messages = XNMALLOC (languages->nitems, message_list_ty *); + for (i = 0; i < languages->nitems; i++) + { + const char *language = languages->item[i]; + char *input_file_name; + int nerrors; + + current_domain = new_domain (file_name, file_name); + + input_file_name = xconcatenated_filename ("", language, ".po"); + read_catalog_file_msgfmt (input_file_name, &input_format_po); + free (input_file_name); + + /* The domain directive is not supported by --desktop mode. + Thus, domain_list should always contain a single domain. */ + assert (current_domain == domain_list && domain_list->next == NULL); + messages[i] = current_domain->mlp; + free (current_domain); + current_domain = domain_list = NULL; + + /* Remove obsolete messages. They were only needed for duplicate + checking. */ + message_list_remove_if_not (messages[i], is_nonobsolete); + + /* Perform all kinds of checks: plural expressions, format + strings, ... */ + nerrors = + check_message_list (messages[i], + /* Untranslated and fuzzy messages have already + been dealt with during parsing, see below in + msgfmt_frob_new_message. */ + 0, 0, + 1, check_format_strings, check_header, + check_compatibility, + check_accelerators, accelerator_char); + + /* Exit with status 1 on any error. */ + if (nerrors > 0) + { + error (0, 0, + ngettext ("found %d fatal error", "found %d fatal errors", + nerrors), + nerrors); + retval = EXIT_FAILURE; + goto out; + } + + /* Convert the messages to Unicode. */ + iconv_message_list (messages[i], NULL, po_charset_utf8, NULL); + } + + /* Write the messages into .desktop file. */ + if (msgdomain_write_desktop_bulk (languages, + messages, + template_file_name, + keywords, + file_name)) + { + retval = EXIT_FAILURE; + goto out; + } + + out: + dir_list_restore (saved_dir_list); + for (i = 0; i < languages->nitems; i++) + message_list_free (messages[i], 0); + free (messages); + string_list_free (languages); + + return retval; +} diff --git a/gettext-tools/src/msgfmt.cs b/gettext-tools/src/msgfmt.cs new file mode 100644 index 0000000..e2f9e7d --- /dev/null +++ b/gettext-tools/src/msgfmt.cs @@ -0,0 +1,119 @@ +/* GNU gettext for C# + * Copyright (C) 2003 Free Software Foundation, Inc. + * Written by Bruno Haible , 2003. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * This program creates a .resources file from a set of key/value pairs given + * on standard input. + */ + +using System; /* String, Console, Exception */ +using System.IO; /* Stream, BufferedStream, StreamReader */ +using System.Text; /* StringBuilder, UTF8Encoding */ +using System.Resources; /* ResourceWriter */ + +namespace GNU.Gettext { + public class WriteResource { + private StreamReader reader; + // Read a NUL-terminated UTF-8 encoded string. + private String ReadString () { + StringBuilder b = new StringBuilder(); + for (;;) { + int c = reader.Read(); + if (c < 0) // EOF? + return null; + if (c == 0) // End of String? + break; + b.Append((char)c); + } + return b.ToString(); + } + // Read all msgid/msgstr pairs, register them in the ResourceWriter, + // and write the binary contents to the output stream. + private void ReadAllInput (ResourceWriter rw) { + for (;;) { + String msgid = ReadString(); + if (msgid == null) + break; + String msgstr = ReadString(); + if (msgstr == null) + break; + rw.AddResource(msgid, msgstr); + } + rw.Generate(); + } + // Read all msgid/msgstr pairs (each string being NUL-terminated and + // UTF-8 encoded) and write the .resources file to the given filename. + WriteResource (String filename) { + Stream input = new BufferedStream(Console.OpenStandardInput()); + reader = new StreamReader(input, new UTF8Encoding()); + if (filename.Equals("-")) { + BufferedStream output = new BufferedStream(Console.OpenStandardOutput()); + // A temporary output stream is needed because ResourceWriter.Generate + // expects to be able to seek in the Stream. + MemoryStream tmpoutput = new MemoryStream(); + ResourceWriter rw = new ResourceWriter(tmpoutput); + ReadAllInput(rw); +#if __CSCC__ + // Use the ResourceReader to check against pnet-0.6.0 ResourceWriter + // bug. + try { + ResourceReader rr = new ResourceReader(new MemoryStream(tmpoutput.ToArray())); + foreach (System.Collections.DictionaryEntry entry in rr); + } catch (IOException e) { + throw new Exception("class ResourceWriter is buggy", e); + } +#endif + tmpoutput.WriteTo(output); + rw.Close(); + output.Close(); + } else { +#if __CSCC__ + MemoryStream tmpoutput = new MemoryStream(); + ResourceWriter rw = new ResourceWriter(tmpoutput); + ReadAllInput(rw); + // Use the ResourceReader to check against pnet-0.6.0 ResourceWriter + // bug. + try { + ResourceReader rr = new ResourceReader(new MemoryStream(tmpoutput.ToArray())); + foreach (System.Collections.DictionaryEntry entry in rr); + } catch (IOException e) { + throw new Exception("class ResourceWriter is buggy", e); + } + BufferedStream output = new BufferedStream(new FileStream(filename, FileMode.Create, FileAccess.Write)); + tmpoutput.WriteTo(output); + rw.Close(); + output.Close(); +#else + ResourceWriter rw = new ResourceWriter(filename); + ReadAllInput(rw); + rw.Close(); +#endif + } + } + public static int Main (String[] args) { + try { + new WriteResource(args[0]); + } catch (Exception e) { + Console.Error.WriteLine(e); + Console.Error.WriteLine(e.StackTrace); + return 1; + } + return 0; + } + } +} diff --git a/gettext-tools/src/msgfmt.h b/gettext-tools/src/msgfmt.h new file mode 100644 index 0000000..e61f72c --- /dev/null +++ b/gettext-tools/src/msgfmt.h @@ -0,0 +1,27 @@ +/* msgfmt specifics + Copyright (C) 1995-1998, 2000-2001, 2009 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGFMT_H +#define _MSGFMT_H + +/* Be more verbose. Use only 'fprintf' and 'multiline_warning' but not + 'error' or 'multiline_error' to emit verbosity messages, because 'error' + and 'multiline_error' during PO file parsing cause the program to exit + with EXIT_FAILURE. See function lex_end(). */ +extern int verbose; + +#endif /* _MSGFMT_H */ diff --git a/gettext-tools/src/msggrep.c b/gettext-tools/src/msggrep.c new file mode 100644 index 0000000..73ccb94 --- /dev/null +++ b/gettext-tools/src/msggrep.c @@ -0,0 +1,860 @@ +/* Extract some translations of a translation catalog. + Copyright (C) 2001-2007, 2009-2010, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if defined _MSC_VER || defined __MINGW32__ +# include +#endif + +#include + +#include "closeout.h" +#include "dir-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "str-list.h" +#include "msgl-charset.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "libgrep.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Output only non-matching messages. */ +static bool invert_match = false; + +/* Selected source files. */ +static string_list_ty *location_files; + +/* Selected domain names. */ +static string_list_ty *domain_names; + +/* Task for each grep pass. */ +struct grep_task { + matcher_t *matcher; + size_t pattern_count; + char *patterns; + size_t patterns_size; + bool case_insensitive; + void *compiled_patterns; +}; +static struct grep_task grep_task[5]; + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "color", optional_argument, NULL, CHAR_MAX + 9 }, + { "comment", no_argument, NULL, 'C' }, + { "directory", required_argument, NULL, 'D' }, + { "domain", required_argument, NULL, 'M' }, + { "escape", no_argument, NULL, CHAR_MAX + 1 }, + { "extended-regexp", no_argument, NULL, 'E' }, + { "extracted-comment", no_argument, NULL, 'X' }, + { "file", required_argument, NULL, 'f' }, + { "fixed-strings", no_argument, NULL, 'F' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "ignore-case", no_argument, NULL, 'i' }, + { "indent", no_argument, NULL, CHAR_MAX + 2 }, + { "invert-match", no_argument, NULL, 'v' }, + { "location", required_argument, NULL, 'N' }, + { "msgctxt", no_argument, NULL, 'J' }, + { "msgid", no_argument, NULL, 'K' }, + { "msgstr", no_argument, NULL, 'T' }, + { "no-escape", no_argument, NULL, CHAR_MAX + 3 }, + { "no-location", no_argument, NULL, CHAR_MAX + 11 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 6 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "regexp", required_argument, NULL, 'e' }, + { "sort-by-file", no_argument, NULL, CHAR_MAX + 4 }, + { "sort-output", no_argument, NULL, CHAR_MAX + 5 }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 7 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 8 }, + { "style", required_argument, NULL, CHAR_MAX + 10 }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w' }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void no_pass (int opt) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp); + + +int +main (int argc, char **argv) +{ + int opt; + bool do_help; + bool do_version; + char *output_file; + const char *input_file; + int grep_pass; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_filepos = false; + bool sort_by_msgid = false; + size_t i; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + input_file = NULL; + grep_pass = -1; + location_files = string_list_alloc (); + domain_names = string_list_alloc (); + + for (i = 0; i < 5; i++) + { + struct grep_task *gt = &grep_task[i]; + + gt->matcher = &matcher_grep; + gt->pattern_count = 0; + gt->patterns = NULL; + gt->patterns_size = 0; + gt->case_insensitive = false; + } + + while ((opt = getopt_long (argc, argv, "CD:e:Ef:FhiJKM:n:N:o:pPTvVw:X", + long_options, NULL)) + != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + + case 'C': + grep_pass = 3; + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + if (grep_pass < 0) + no_pass (opt); + { + struct grep_task *gt = &grep_task[grep_pass]; + /* Append optarg and a newline to gt->patterns. */ + size_t len = strlen (optarg); + gt->patterns = + (char *) xrealloc (gt->patterns, gt->patterns_size + len + 1); + memcpy (gt->patterns + gt->patterns_size, optarg, len); + gt->patterns_size += len; + *(gt->patterns + gt->patterns_size) = '\n'; + gt->patterns_size += 1; + gt->pattern_count++; + } + break; + + case 'E': + if (grep_pass < 0) + no_pass (opt); + grep_task[grep_pass].matcher = &matcher_egrep; + break; + + case 'f': + if (grep_pass < 0) + no_pass (opt); + { + struct grep_task *gt = &grep_task[grep_pass]; + /* Append the contents of the specified file to gt->patterns. */ + FILE *fp = fopen (optarg, "r"); + + if (fp == NULL) + error (EXIT_FAILURE, errno, _("\ +error while opening \"%s\" for reading"), optarg); + + while (!feof (fp)) + { + char buf[4096]; + size_t count = fread (buf, 1, sizeof buf, fp); + + if (count == 0) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), optarg); + /* EOF reached. */ + break; + } + + gt->patterns = + (char *) xrealloc (gt->patterns, gt->patterns_size + count); + memcpy (gt->patterns + gt->patterns_size, buf, count); + gt->patterns_size += count; + } + + /* Append a final newline if file ended in a non-newline. */ + if (gt->patterns_size > 0 + && *(gt->patterns + gt->patterns_size - 1) != '\n') + { + gt->patterns = + (char *) xrealloc (gt->patterns, gt->patterns_size + 1); + *(gt->patterns + gt->patterns_size) = '\n'; + gt->patterns_size += 1; + } + + fclose (fp); + gt->pattern_count++; + } + break; + + case 'F': + if (grep_pass < 0) + no_pass (opt); + grep_task[grep_pass].matcher = &matcher_fgrep; + break; + + case 'h': + do_help = true; + break; + + case 'i': + if (grep_pass < 0) + no_pass (opt); + grep_task[grep_pass].case_insensitive = true; + break; + + case 'J': + grep_pass = 0; + break; + + case 'K': + grep_pass = 1; + break; + + case 'M': + string_list_append (domain_names, optarg); + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'N': + string_list_append (location_files, optarg); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 'T': + grep_pass = 2; + break; + + case 'v': + invert_match = true; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case 'X': + grep_pass = 4; + break; + + case CHAR_MAX + 1: + message_print_style_escape (true); + break; + + case CHAR_MAX + 2: + message_print_style_indent (); + break; + + case CHAR_MAX + 3: + message_print_style_escape (false); + break; + + case CHAR_MAX + 4: + sort_by_filepos = true; + break; + + case CHAR_MAX + 5: + sort_by_msgid = true; + break; + + case CHAR_MAX + 6: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 7: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 8: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 9: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 10: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 11: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test whether we have an .po file name as argument. */ + if (optind == argc) + input_file = "-"; + else if (optind + 1 == argc) + input_file = argv[optind]; + else + { + error (EXIT_SUCCESS, 0, _("at most one input file allowed")); + usage (EXIT_FAILURE); + } + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* Compile the patterns. */ + for (grep_pass = 0; grep_pass < 5; grep_pass++) + { + struct grep_task *gt = &grep_task[grep_pass]; + + if (gt->pattern_count > 0) + { + if (gt->patterns_size > 0) + { + /* Strip trailing newline. */ + assert (gt->patterns[gt->patterns_size - 1] == '\n'); + gt->patterns_size--; + } + gt->compiled_patterns = + gt->matcher->compile (gt->patterns, gt->patterns_size, + gt->case_insensitive, false, false, '\n'); + } + } + + /* Read input file. */ + result = read_catalog_file (input_file, input_syntax); + + if (grep_task[0].pattern_count > 0 + || grep_task[1].pattern_count > 0 + || grep_task[2].pattern_count > 0 + || grep_task[3].pattern_count > 0 + || grep_task[4].pattern_count > 0) + { + /* Warn if the current locale is not suitable for this PO file. */ + compare_po_locale_charsets (result); + } + + /* Select the messages. */ + result = process_msgdomain_list (result); + + /* Sort the results. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Write the merged message list out. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + exit (EXIT_SUCCESS); +} + + +static void +no_pass (int opt) +{ + error (EXIT_SUCCESS, 0, + _("option '%c' cannot be used before 'J' or 'K' or 'T' or 'C' or 'X' has been specified"), + opt); + usage (EXIT_FAILURE); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [INPUTFILE]\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Extracts all messages of a translation catalog that match a given pattern\n\ +or belong to some given source files.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + INPUTFILE input PO file\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If no input file is given or if it is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Message selection:\n\ + [-N SOURCEFILE]... [-M DOMAINNAME]...\n\ + [-J MSGCTXT-PATTERN] [-K MSGID-PATTERN] [-T MSGSTR-PATTERN]\n\ + [-C COMMENT-PATTERN] [-X EXTRACTED-COMMENT-PATTERN]\n\ +A message is selected if it comes from one of the specified source files,\n\ +or if it comes from one of the specified domains,\n\ +or if -J is given and its context (msgctxt) matches MSGCTXT-PATTERN,\n\ +or if -K is given and its key (msgid or msgid_plural) matches MSGID-PATTERN,\n\ +or if -T is given and its translation (msgstr) matches MSGSTR-PATTERN,\n\ +or if -C is given and the translator's comment matches COMMENT-PATTERN,\n\ +or if -X is given and the extracted comment matches EXTRACTED-COMMENT-PATTERN.\n\ +\n\ +When more than one selection criterion is specified, the set of selected\n\ +messages is the union of the selected messages of each criterion.\n\ +\n\ +MSGCTXT-PATTERN or MSGID-PATTERN or MSGSTR-PATTERN or COMMENT-PATTERN or\n\ +EXTRACTED-COMMENT-PATTERN syntax:\n\ + [-E | -F] [-e PATTERN | -f FILE]...\n\ +PATTERNs are basic regular expressions by default, or extended regular\n\ +expressions if -E is given, or fixed strings if -F is given.\n\ +\n\ + -N, --location=SOURCEFILE select messages extracted from SOURCEFILE\n\ + -M, --domain=DOMAINNAME select messages belonging to domain DOMAINNAME\n\ + -J, --msgctxt start of patterns for the msgctxt\n\ + -K, --msgid start of patterns for the msgid\n\ + -T, --msgstr start of patterns for the msgstr\n\ + -C, --comment start of patterns for the translator's comment\n\ + -X, --extracted-comment start of patterns for the extracted comment\n\ + -E, --extended-regexp PATTERN is an extended regular expression\n\ + -F, --fixed-strings PATTERN is a set of newline-separated strings\n\ + -e, --regexp=PATTERN use PATTERN as a regular expression\n\ + -f, --file=FILE obtain PATTERN from FILE\n\ + -i, --ignore-case ignore case distinctions\n\ + -v, --invert-match output only the messages that do not match any\n\ + selection criterion\n\ +")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input file is in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + --indent indented output style\n")); + printf (_("\ + --no-location suppress '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location preserve '#: filename:line' lines (default)\n")); + printf (_("\ + --strict strict Uniforum output style\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + --sort-output generate sorted output\n")); + printf (_("\ + --sort-by-file sort output by file location\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + + +/* Return 1 if FILENAME is contained in a list of filename patterns, + 0 otherwise. */ +static bool +filename_list_match (const string_list_ty *slp, const char *filename) +{ + size_t j; + + for (j = 0; j < slp->nitems; ++j) + if (fnmatch (slp->item[j], filename, FNM_PATHNAME) == 0) + return true; + return false; +} + + +#ifdef EINTR + +/* EINTR handling for close(). + These functions can return -1/EINTR even though we don't have any + signal handlers set up, namely when we get interrupted via SIGSTOP. */ + +static inline int +nonintr_close (int fd) +{ + int retval; + + do + retval = close (fd); + while (retval < 0 && errno == EINTR); + + return retval; +} +#define close nonintr_close + +#endif + + +/* Process a string STR of size LEN bytes through grep, and return true + if it matches. */ +static bool +is_string_selected (int grep_pass, const char *str, size_t len) +{ + const struct grep_task *gt = &grep_task[grep_pass]; + + if (gt->pattern_count > 0) + { + size_t match_size; + size_t match_offset; + + match_offset = + gt->matcher->execute (gt->compiled_patterns, str, len, + &match_size, false); + return (match_offset != (size_t) -1); + } + else + return 0; +} + + +/* Return true if a message matches, considering only the positive selection + criteria and ignoring --invert-match. */ +static bool +is_message_selected_no_invert (const message_ty *mp) +{ + size_t i; + const char *msgstr; + size_t msgstr_len; + const char *p; + + /* Test whether one of mp->filepos[] is selected. */ + for (i = 0; i < mp->filepos_count; i++) + if (filename_list_match (location_files, mp->filepos[i].file_name)) + return true; + + /* Test msgctxt using the --msgctxt arguments. */ + if (mp->msgctxt != NULL + && is_string_selected (0, mp->msgctxt, strlen (mp->msgctxt))) + return true; + + /* Test msgid and msgid_plural using the --msgid arguments. */ + if (is_string_selected (1, mp->msgid, strlen (mp->msgid))) + return true; + if (mp->msgid_plural != NULL + && is_string_selected (1, mp->msgid_plural, strlen (mp->msgid_plural))) + return true; + + /* Test msgstr using the --msgstr arguments. */ + msgstr = mp->msgstr; + msgstr_len = mp->msgstr_len; + /* Process each NUL delimited substring separately. */ + for (p = msgstr; p < msgstr + msgstr_len; ) + { + size_t length = strlen (p); + + if (is_string_selected (2, p, length)) + return true; + + p += length + 1; + } + + /* Test translator comments using the --comment arguments. */ + if (grep_task[3].pattern_count > 0 + && mp->comment != NULL && mp->comment->nitems > 0) + { + size_t length; + char *total_comment; + char *q; + size_t j; + bool selected; + + length = 0; + for (j = 0; j < mp->comment->nitems; j++) + length += strlen (mp->comment->item[j]) + 1; + total_comment = (char *) xmalloca (length); + + q = total_comment; + for (j = 0; j < mp->comment->nitems; j++) + { + size_t l = strlen (mp->comment->item[j]); + + memcpy (q, mp->comment->item[j], l); + q += l; + *q++ = '\n'; + } + if (q != total_comment + length) + abort (); + + selected = is_string_selected (3, total_comment, length); + + freea (total_comment); + + if (selected) + return true; + } + + /* Test extracted comments using the --extracted-comment arguments. */ + if (grep_task[4].pattern_count > 0 + && mp->comment_dot != NULL && mp->comment_dot->nitems > 0) + { + size_t length; + char *total_comment; + char *q; + size_t j; + bool selected; + + length = 0; + for (j = 0; j < mp->comment_dot->nitems; j++) + length += strlen (mp->comment_dot->item[j]) + 1; + total_comment = (char *) xmalloca (length); + + q = total_comment; + for (j = 0; j < mp->comment_dot->nitems; j++) + { + size_t l = strlen (mp->comment_dot->item[j]); + + memcpy (q, mp->comment_dot->item[j], l); + q += l; + *q++ = '\n'; + } + if (q != total_comment + length) + abort (); + + selected = is_string_selected (4, total_comment, length); + + freea (total_comment); + + if (selected) + return true; + } + + return false; +} + + +/* Return true if a message matches. */ +static bool +is_message_selected (const message_ty *mp) +{ + bool result; + + /* Always keep the header entry. */ + if (is_header (mp)) + return true; + + result = is_message_selected_no_invert (mp); + + if (invert_match) + return !result; + else + return result; +} + + +static void +process_message_list (const char *domain, message_list_ty *mlp) +{ + if (string_list_member (domain_names, domain)) + /* Keep all the messages in the list. */ + ; + else + /* Keep only the selected messages. */ + message_list_remove_if_not (mlp, is_message_selected); +} + + +static msgdomain_list_ty * +process_msgdomain_list (msgdomain_list_ty *mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + process_message_list (mdlp->item[k]->domain, mdlp->item[k]->messages); + + return mdlp; +} diff --git a/gettext-tools/src/msginit.c b/gettext-tools/src/msginit.c new file mode 100644 index 0000000..951aa2e --- /dev/null +++ b/gettext-tools/src/msginit.c @@ -0,0 +1,1776 @@ +/* Initializes a new PO file. + Copyright (C) 2001-2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_PWD_H +# include +#endif + +#include + +#if HAVE_DIRENT_H +# include +#endif + +#if HAVE_DIRENT_H +# define HAVE_DIR 1 +#else +# define HAVE_DIR 0 +#endif + +#include "closeout.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "c-strstr.h" +#include "c-strcase.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "po-charset.h" +#include "localcharset.h" +#include "localename.h" +#include "po-time.h" +#include "plural-table.h" +#include "lang-table.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "concat-filename.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "msgl-english.h" +#include "plural-count.h" +#include "spawn-pipe.h" +#include "wait-process.h" +#include "xsetenv.h" +#include "str-list.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) +#define N_(str) (str) + +/* Get F_OK. It is lacking from on Woe32. */ +#ifndef F_OK +# define F_OK 0 +#endif + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + +extern const char * _nl_expand_alias (const char *name); + +/* Locale name. */ +static const char *locale; + +/* Language (ISO-639 code) and optional territory (ISO-3166 code). */ +static const char *catalogname; + +/* Language (ISO-639 code). */ +static const char *language; + +/* If true, the user is not considered to be the translator. */ +static bool no_translator; + +/* Long options. */ +static const struct option long_options[] = +{ + { "color", optional_argument, NULL, CHAR_MAX + 5 }, + { "help", no_argument, NULL, 'h' }, + { "input", required_argument, NULL, 'i' }, + { "locale", required_argument, NULL, 'l' }, + { "no-translator", no_argument, NULL, CHAR_MAX + 1 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 2 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 }, + { "style", required_argument, NULL, CHAR_MAX + 6 }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w' }, + { NULL, 0, NULL, 0 } +}; + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static const char *find_pot (void); +static const char *catalogname_for_locale (const char *locale); +static const char *language_of_locale (const char *locale); +static char *get_field (const char *header, const char *field); +static msgdomain_list_ty *fill_header (msgdomain_list_ty *mdlp); +static msgdomain_list_ty *update_msgstr_plurals (msgdomain_list_ty *mdlp); + + +int +main (int argc, char **argv) +{ + int opt; + bool do_help; + bool do_version; + char *output_file; + const char *input_file; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + input_file = NULL; + locale = NULL; + + while ((opt = getopt_long (argc, argv, "hi:l:o:pPVw:", long_options, NULL)) + != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + + case 'h': + do_help = true; + break; + + case 'i': + if (input_file != NULL) + { + error (EXIT_SUCCESS, 0, _("at most one input file allowed")); + usage (EXIT_FAILURE); + } + input_file = optarg; + break; + + case 'l': + locale = optarg; + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: + no_translator = true; + break; + + case CHAR_MAX + 2: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 3: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 4: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 5: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 6: /* --style */ + handle_style_option (optarg); + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test for extraneous arguments. */ + if (optind != argc) + error (EXIT_FAILURE, 0, _("too many arguments")); + + /* Search for the input file. */ + if (input_file == NULL) + input_file = find_pot (); + + /* Determine target locale. */ + if (locale == NULL) + { + locale = gl_locale_name (LC_MESSAGES, "LC_MESSAGES"); + if (strcmp (locale, "C") == 0) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +You are in a language indifferent environment. Please set\n\ +your LANG environment variable, as described in the ABOUT-NLS\n\ +file. This is necessary so you can test your translations.\n"))); + exit (EXIT_FAILURE); + } + } + { + const char *alias = _nl_expand_alias (locale); + if (alias != NULL) + locale = alias; + } + catalogname = catalogname_for_locale (locale); + language = language_of_locale (locale); + + /* Default output file name is CATALOGNAME.po. */ + if (output_file == NULL) + { + output_file = xasprintf ("%s.po", catalogname); + + /* But don't overwrite existing PO files. */ + if (access (output_file, F_OK) == 0) + { + multiline_error (xstrdup (""), + xasprintf (_("\ +Output file %s already exists.\n\ +Please specify the locale through the --locale option or\n\ +the output .po file through the --output-file option.\n"), + output_file)); + exit (EXIT_FAILURE); + } + } + + /* Read input file. */ + result = read_catalog_file (input_file, input_syntax); + + /* Fill the header entry. */ + result = fill_header (result); + + /* Initialize translations. */ + if (strcmp (language, "en") == 0) + result = msgdomain_list_english (result); + else + result = update_msgstr_plurals (result); + + /* Write the modified message list out. */ + msgdomain_list_print (result, output_file, output_syntax, true, false); + + if (!no_translator) + fprintf (stderr, "\n"); + fprintf (stderr, _("Created %s.\n"), output_file); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Creates a new PO file, initializing the meta information with values from the\n\ +user's environment.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + -i, --input=INPUTFILE input POT file\n")); + printf (_("\ +If no input file is given, the current directory is searched for the POT file.\n\ +If it is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified PO file\n")); + printf (_("\ +If no output file is given, it depends on the --locale option or the user's\n\ +locale setting. If it is -, the results are written to standard output.\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input file is in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + -l, --locale=LL_CC set target locale\n")); + printf (_("\ + --no-translator assume the PO file is automatically generated\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + + +/* Search for the POT file and return its name. */ +static const char * +find_pot () +{ +#if HAVE_DIR + DIR *dirp; + char *found = NULL; + + dirp = opendir ("."); + if (dirp != NULL) + { + for (;;) + { + struct dirent *dp; + + errno = 0; + dp = readdir (dirp); + if (dp != NULL) + { + const char *name = dp->d_name; + size_t namlen = strlen (name); + + if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0) + { + if (found == NULL) + found = xstrdup (name); + else + { + multiline_error (xstrdup (""), + xstrdup (_("\ +Found more than one .pot file.\n\ +Please specify the input .pot file through the --input option.\n"))); + usage (EXIT_FAILURE); + } + } + } + else if (errno != 0) + error (EXIT_FAILURE, errno, _("error reading current directory")); + else + break; + } + if (closedir (dirp)) + error (EXIT_FAILURE, errno, _("error reading current directory")); + + if (found != NULL) + return found; + } +#endif + + multiline_error (xstrdup (""), + xstrdup (_("\ +Found no .pot file in the current directory.\n\ +Please specify the input .pot file through the --input option.\n"))); + usage (EXIT_FAILURE); + /* NOTREACHED */ + return NULL; +} + + +/* Return the gettext catalog name corresponding to a locale. If the locale + consists of a language and a territory, and the language is mainly spoken + in that territory, the territory is removed from the locale name. + For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de", + because the resulting catalog can be used as a default for all "de_XX", + such as "de_AT". */ +static const char * +catalogname_for_locale (const char *locale) +{ + static const char *locales_with_principal_territory[] = { + /* Language Main territory */ + "ace_ID", /* Achinese Indonesia */ + "af_ZA", /* Afrikaans South Africa */ + "ak_GH", /* Akan Ghana */ + "am_ET", /* Amharic Ethiopia */ + "an_ES", /* Aragonese Spain */ + "ang_GB", /* Old English Britain */ + "arn_CL", /* Mapudungun Chile */ + "as_IN", /* Assamese India */ + "ast_ES", /* Asturian Spain */ + "av_RU", /* Avaric Russia */ + "awa_IN", /* Awadhi India */ + "az_AZ", /* Azerbaijani Azerbaijan */ + "ban_ID", /* Balinese Indonesia */ + "be_BY", /* Belarusian Belarus */ + "bej_SD", /* Beja Sudan */ + "bem_ZM", /* Bemba Zambia */ + "bg_BG", /* Bulgarian Bulgaria */ + "bho_IN", /* Bhojpuri India */ + "bik_PH", /* Bikol Philippines */ + "bin_NG", /* Bini Nigeria */ + "bm_ML", /* Bambara Mali */ + "bn_IN", /* Bengali India */ + "bo_CN", /* Tibetan China */ + "br_FR", /* Breton France */ + "bs_BA", /* Bosnian Bosnia */ + "bug_ID", /* Buginese Indonesia */ + "ca_ES", /* Catalan Spain */ + "ce_RU", /* Chechen Russia */ + "ceb_PH", /* Cebuano Philippines */ + "co_FR", /* Corsican France */ + "cr_CA", /* Cree Canada */ + /* Don't put "crh_UZ" or "crh_UA" here. That would be asking for fruitless + political discussion. */ + "cs_CZ", /* Czech Czech Republic */ + "csb_PL", /* Kashubian Poland */ + "cy_GB", /* Welsh Britain */ + "da_DK", /* Danish Denmark */ + "de_DE", /* German Germany */ + "din_SD", /* Dinka Sudan */ + "doi_IN", /* Dogri India */ + "dsb_DE", /* Lower Sorbian Germany */ + "dv_MV", /* Divehi Maldives */ + "dz_BT", /* Dzongkha Bhutan */ + "ee_GH", /* Éwé Ghana */ + "el_GR", /* Greek Greece */ + /* Don't put "en_GB" or "en_US" here. That would be asking for fruitless + political discussion. */ + "es_ES", /* Spanish Spain */ + "et_EE", /* Estonian Estonia */ + "fa_IR", /* Persian Iran */ + "fi_FI", /* Finnish Finland */ + "fil_PH", /* Filipino Philippines */ + "fj_FJ", /* Fijian Fiji */ + "fo_FO", /* Faroese Faeroe Islands */ + "fon_BJ", /* Fon Benin */ + "fr_FR", /* French France */ + "fur_IT", /* Friulian Italy */ + "fy_NL", /* Western Frisian Netherlands */ + "ga_IE", /* Irish Ireland */ + "gd_GB", /* Scottish Gaelic Britain */ + "gon_IN", /* Gondi India */ + "gsw_CH", /* Swiss German Switzerland */ + "gu_IN", /* Gujarati India */ + "he_IL", /* Hebrew Israel */ + "hi_IN", /* Hindi India */ + "hil_PH", /* Hiligaynon Philippines */ + "hr_HR", /* Croatian Croatia */ + "hsb_DE", /* Upper Sorbian Germany */ + "ht_HT", /* Haitian Haiti */ + "hu_HU", /* Hungarian Hungary */ + "hy_AM", /* Armenian Armenia */ + "id_ID", /* Indonesian Indonesia */ + "ig_NG", /* Igbo Nigeria */ + "ii_CN", /* Sichuan Yi China */ + "ilo_PH", /* Iloko Philippines */ + "is_IS", /* Icelandic Iceland */ + "it_IT", /* Italian Italy */ + "ja_JP", /* Japanese Japan */ + "jab_NG", /* Hyam Nigeria */ + "jv_ID", /* Javanese Indonesia */ + "ka_GE", /* Georgian Georgia */ + "kab_DZ", /* Kabyle Algeria */ + "kaj_NG", /* Jju Nigeria */ + "kam_KE", /* Kamba Kenya */ + "kmb_AO", /* Kimbundu Angola */ + "kcg_NG", /* Tyap Nigeria */ + "kdm_NG", /* Kagoma Nigeria */ + "kg_CD", /* Kongo Democratic Republic of Congo */ + "kk_KZ", /* Kazakh Kazakhstan */ + "kl_GL", /* Kalaallisut Greenland */ + "km_KH", /* Central Khmer Cambodia */ + "kn_IN", /* Kannada India */ + "ko_KR", /* Korean Korea (South) */ + "kok_IN", /* Konkani India */ + "kr_NG", /* Kanuri Nigeria */ + "kru_IN", /* Kurukh India */ + "lg_UG", /* Ganda Uganda */ + "li_BE", /* Limburgish Belgium */ + "lo_LA", /* Laotian Laos */ + "lt_LT", /* Lithuanian Lithuania */ + "lu_CD", /* Luba-Katanga Democratic Republic of Congo */ + "lua_CD", /* Luba-Lulua Democratic Republic of Congo */ + "luo_KE", /* Luo Kenya */ + "lv_LV", /* Latvian Latvia */ + "mad_ID", /* Madurese Indonesia */ + "mag_IN", /* Magahi India */ + "mai_IN", /* Maithili India */ + "mak_ID", /* Makasar Indonesia */ + "man_ML", /* Mandingo Mali */ + "men_SL", /* Mende Sierra Leone */ + "mg_MG", /* Malagasy Madagascar */ + "mi_NZ", /* Maori New Zealand */ + "min_ID", /* Minangkabau Indonesia */ + "mk_MK", /* Macedonian Macedonia */ + "ml_IN", /* Malayalam India */ + "mn_MN", /* Mongolian Mongolia */ + "mni_IN", /* Manipuri India */ + "mos_BF", /* Mossi Burkina Faso */ + "mr_IN", /* Marathi India */ + "ms_MY", /* Malay Malaysia */ + "mt_MT", /* Maltese Malta */ + "mwr_IN", /* Marwari India */ + "my_MM", /* Burmese Myanmar */ + "na_NR", /* Nauru Nauru */ + "nah_MX", /* Nahuatl Mexico */ + "nap_IT", /* Neapolitan Italy */ + "nb_NO", /* Norwegian Bokmål Norway */ + "nds_DE", /* Low Saxon Germany */ + "ne_NP", /* Nepali Nepal */ + "nl_NL", /* Dutch Netherlands */ + "nn_NO", /* Norwegian Nynorsk Norway */ + "no_NO", /* Norwegian Norway */ + "nr_ZA", /* South Ndebele South Africa */ + "nso_ZA", /* Northern Sotho South Africa */ + "nym_TZ", /* Nyamwezi Tanzania */ + "nyn_UG", /* Nyankole Uganda */ + "oc_FR", /* Occitan France */ + "oj_CA", /* Ojibwa Canada */ + "or_IN", /* Oriya India */ + "pa_IN", /* Punjabi India */ + "pag_PH", /* Pangasinan Philippines */ + "pam_PH", /* Pampanga Philippines */ + "pap_AN", /* Papiamento Netherlands Antilles */ + "pbb_CO", /* Páez Colombia */ + "pl_PL", /* Polish Poland */ + "ps_AF", /* Pashto Afghanistan */ + "pt_PT", /* Portuguese Portugal */ + "raj_IN", /* Rajasthani India */ + "rm_CH", /* Romansh Switzerland */ + "rn_BI", /* Kirundi Burundi */ + "ro_RO", /* Romanian Romania */ + "ru_RU", /* Russian Russia */ + "sa_IN", /* Sanskrit India */ + "sah_RU", /* Yakut Russia */ + "sas_ID", /* Sasak Indonesia */ + "sat_IN", /* Santali India */ + "sc_IT", /* Sardinian Italy */ + "scn_IT", /* Sicilian Italy */ + "sg_CF", /* Sango Central African Republic */ + "shn_MM", /* Shan Myanmar */ + "si_LK", /* Sinhala Sri Lanka */ + "sid_ET", /* Sidamo Ethiopia */ + "sk_SK", /* Slovak Slovakia */ + "sl_SI", /* Slovenian Slovenia */ + "smn_FI", /* Inari Sami Finland */ + "sms_FI", /* Skolt Sami Finland */ + "so_SO", /* Somali Somalia */ + "sq_AL", /* Albanian Albania */ + "sr_RS", /* Serbian Serbia */ + "sr_YU", /* Serbian Yugoslavia - this line can be removed in 2010 */ + "srr_SN", /* Serer Senegal */ + "suk_TZ", /* Sukuma Tanzania */ + "sus_GN", /* Susu Guinea */ + "sv_SE", /* Swedish Sweden */ + "te_IN", /* Telugu India */ + "tem_SL", /* Timne Sierra Leone */ + "tet_ID", /* Tetum Indonesia */ + "tg_TJ", /* Tajik Tajikistan */ + "th_TH", /* Thai Thailand */ + "tiv_NG", /* Tiv Nigeria */ + "tk_TM", /* Turkmen Turkmenistan */ + "tl_PH", /* Tagalog Philippines */ + "to_TO", /* Tonga Tonga */ + "tr_TR", /* Turkish Turkey */ + "tum_MW", /* Tumbuka Malawi */ + "ug_CN", /* Uighur China */ + "uk_UA", /* Ukrainian Ukraine */ + "umb_AO", /* Umbundu Angola */ + "ur_PK", /* Urdu Pakistan */ + "uz_UZ", /* Uzbek Uzbekistan */ + "ve_ZA", /* Venda South Africa */ + "vi_VN", /* Vietnamese Vietnam */ + "wa_BE", /* Walloon Belgium */ + "wal_ET", /* Walamo Ethiopia */ + "war_PH", /* Waray Philippines */ + "wen_DE", /* Sorbian Germany */ + "yao_MW", /* Yao Malawi */ + "zap_MX" /* Zapotec Mexico */ + }; + const char *dot; + size_t i; + + /* Remove the ".codeset" part from the locale. */ + dot = strchr (locale, '.'); + if (dot != NULL) + { + const char *codeset_end; + char *shorter_locale; + + codeset_end = strpbrk (dot + 1, "_@"); + if (codeset_end == NULL) + codeset_end = dot + strlen (dot); + + shorter_locale = XNMALLOC (strlen (locale), char); + memcpy (shorter_locale, locale, dot - locale); + strcpy (shorter_locale + (dot - locale), codeset_end); + locale = shorter_locale; + } + + /* If the territory is the language's principal territory, drop it. */ + for (i = 0; i < SIZEOF (locales_with_principal_territory); i++) + if (strcmp (locale, locales_with_principal_territory[i]) == 0) + { + const char *language_end; + size_t len; + char *shorter_locale; + + language_end = strchr (locale, '_'); + if (language_end == NULL) + abort (); + + len = language_end - locale; + shorter_locale = XNMALLOC (len + 1, char); + memcpy (shorter_locale, locale, len); + shorter_locale[len] = '\0'; + locale = shorter_locale; + break; + } + + return locale; +} + + +/* Return the language of a locale. */ +static const char * +language_of_locale (const char *locale) +{ + const char *language_end; + + language_end = strpbrk (locale, "_.@"); + if (language_end != NULL) + { + size_t len; + char *result; + + len = language_end - locale; + result = XNMALLOC (len + 1, char); + memcpy (result, locale, len); + result[len] = '\0'; + + return result; + } + else + return locale; +} + + +/* Return the most likely desired charset for the PO file, as a portable + charset name. */ +static const char * +canonical_locale_charset () +{ + const char *tmp; + char *old_LC_ALL; + const char *charset; + + /* Save LC_ALL environment variable. */ + + tmp = getenv ("LC_ALL"); + old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL); + + xsetenv ("LC_ALL", locale, 1); + +#ifdef HAVE_SETLOCALE + if (setlocale (LC_ALL, "") == NULL) + /* Nonexistent locale. Use anything. */ + charset = ""; + else +#endif + /* Get the locale's charset. */ + charset = locale_charset (); + + /* Restore LC_ALL environment variable. */ + + if (old_LC_ALL != NULL) + xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL); + else + unsetenv ("LC_ALL"); + +#ifdef HAVE_SETLOCALE + setlocale (LC_ALL, ""); +#endif + + /* Canonicalize it. */ + charset = po_charset_canonicalize (charset); + if (charset == NULL) + charset = po_charset_ascii; + + return charset; +} + + +/* Return the English name of the language. */ +static const char * +englishname_of_language () +{ + size_t i; + + for (i = 0; i < language_table_size; i++) + if (strcmp (language_table[i].code, language) == 0) + return language_table[i].english; + + return xasprintf ("Language %s", language); +} + + +/* Construct the value for the PACKAGE name. */ +static const char * +project_id (const char *header) +{ + const char *old_field; + const char *gettextlibdir; + char *prog; + char *argv[3]; + pid_t child; + int fd[1]; + FILE *fp; + char *line; + size_t linesize; + size_t linelen; + int exitstatus; + + /* Return the first part of the Project-Id-Version field if present, assuming + it was already filled in by xgettext. */ + old_field = get_field (header, "Project-Id-Version"); + if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0) + { + /* Remove the last word from old_field. */ + const char *last_space; + + last_space = strrchr (old_field, ' '); + if (last_space != NULL) + { + while (last_space > old_field && last_space[-1] == ' ') + last_space--; + if (last_space > old_field) + { + size_t package_len = last_space - old_field; + char *package = XNMALLOC (package_len + 1, char); + memcpy (package, old_field, package_len); + package[package_len] = '\0'; + + return package; + } + } + /* It contains no version, just a package name. */ + return old_field; + } + + gettextlibdir = getenv ("GETTEXTLIBDIR"); + if (gettextlibdir == NULL || gettextlibdir[0] == '\0') + gettextlibdir = relocate (LIBDIR "/gettext"); + + prog = xconcatenated_filename (gettextlibdir, "project-id", NULL); + + /* Call the project-id shell script. */ + argv[0] = "/bin/sh"; + argv[1] = prog; + argv[2] = NULL; + child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, + fd); + if (child == -1) + goto failed; + + /* Retrieve its result. */ + fp = fdopen (fd[0], "r"); + if (fp == NULL) + { + error (0, errno, _("fdopen() failed")); + goto failed; + } + + line = NULL; linesize = 0; + linelen = getline (&line, &linesize, fp); + if (linelen == (size_t)(-1)) + { + error (0, 0, _("%s subprocess I/O error"), prog); + fclose (fp); + goto failed; + } + if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL); + if (exitstatus != 0) + { + error (0, 0, _("%s subprocess failed with exit code %d"), + prog, exitstatus); + goto failed; + } + + return line; + +failed: + return "PACKAGE"; +} + + +/* Construct the value for the Project-Id-Version field. */ +static const char * +project_id_version (const char *header) +{ + const char *old_field; + const char *gettextlibdir; + char *prog; + char *argv[4]; + pid_t child; + int fd[1]; + FILE *fp; + char *line; + size_t linesize; + size_t linelen; + int exitstatus; + + /* Return the old value if present, assuming it was already filled in by + xgettext. */ + old_field = get_field (header, "Project-Id-Version"); + if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0) + return old_field; + + gettextlibdir = getenv ("GETTEXTLIBDIR"); + if (gettextlibdir == NULL || gettextlibdir[0] == '\0') + gettextlibdir = relocate (LIBDIR "/gettext"); + + prog = xconcatenated_filename (gettextlibdir, "project-id", NULL); + + /* Call the project-id shell script. */ + argv[0] = "/bin/sh"; + argv[1] = prog; + argv[2] = "yes"; + argv[3] = NULL; + child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, + fd); + if (child == -1) + goto failed; + + /* Retrieve its result. */ + fp = fdopen (fd[0], "r"); + if (fp == NULL) + { + error (0, errno, _("fdopen() failed")); + goto failed; + } + + line = NULL; linesize = 0; + linelen = getline (&line, &linesize, fp); + if (linelen == (size_t)(-1)) + { + error (0, 0, _("%s subprocess I/O error"), prog); + fclose (fp); + goto failed; + } + if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL); + if (exitstatus != 0) + { + error (0, 0, _("%s subprocess failed with exit code %d"), + prog, exitstatus); + goto failed; + } + + return line; + +failed: + return "PACKAGE VERSION"; +} + + +/* Construct the value for the PO-Revision-Date field. */ +static const char * +po_revision_date (const char *header) +{ + if (no_translator) + /* Because the PO file is automatically generated, we use the + POT-Creation-Date, not the current time. */ + return get_field (header, "POT-Creation-Date"); + else + { + /* Assume the translator will modify the PO file now. */ + time_t now; + + time (&now); + return po_strftime (&now); + } +} + + +/* Returns the struct passwd entry for the current user. */ +static struct passwd * +get_user_pwd () +{ +#if HAVE_PWD_H /* Only Unix, not native Woe32. */ + const char *username; + struct passwd *userpasswd; + + /* 1. attempt: getpwnam(getenv("USER")) */ + username = getenv ("USER"); + if (username != NULL) + { + errno = 0; + userpasswd = getpwnam (username); + if (userpasswd != NULL) + return userpasswd; + if (errno != 0) + error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username); + } + + /* 2. attempt: getpwnam(getlogin()) */ + username = getlogin (); + if (username != NULL) + { + errno = 0; + userpasswd = getpwnam (username); + if (userpasswd != NULL) + return userpasswd; + if (errno != 0) + error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username); + } + + /* 3. attempt: getpwuid(getuid()) */ + errno = 0; + userpasswd = getpwuid (getuid ()); + if (userpasswd != NULL) + return userpasswd; + if (errno != 0) + error (EXIT_FAILURE, errno, "getpwuid(%ju)", (uintmax_t) getuid ()); +#endif + + return NULL; +} + + +/* Return the user's full name. */ +static const char * +get_user_fullname () +{ + struct passwd *pwd; + + pwd = get_user_pwd (); +#if HAVE_PWD_H + if (pwd != NULL) + { + const char *fullname; + const char *fullname_end; + char *result; + + /* Return the pw_gecos field, up to the first comma (if any). */ + fullname = pwd->pw_gecos; + fullname_end = strchr (fullname, ','); + if (fullname_end == NULL) + fullname_end = fullname + strlen (fullname); + + result = XNMALLOC (fullname_end - fullname + 1, char); + memcpy (result, fullname, fullname_end - fullname); + result[fullname_end - fullname] = '\0'; + + return result; + } +#endif + + return NULL; +} + + +/* Return the user's email address. */ +static const char * +get_user_email () +{ + const char *prog = relocate (LIBDIR "/gettext/user-email"); + char *argv[4]; + pid_t child; + int fd[1]; + FILE *fp; + char *line; + size_t linesize; + size_t linelen; + int exitstatus; + + /* Ask the user for his email address. */ + argv[0] = "/bin/sh"; + argv[1] = (char *) prog; + argv[2] = (char *) _("\ +The new message catalog should contain your email address, so that users can\n\ +give you feedback about the translations, and so that maintainers can contact\n\ +you in case of unexpected technical problems.\n"); + argv[3] = NULL; + child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, + fd); + if (child == -1) + goto failed; + + /* Retrieve his answer. */ + fp = fdopen (fd[0], "r"); + if (fp == NULL) + { + error (0, errno, _("fdopen() failed")); + goto failed; + } + + line = NULL; linesize = 0; + linelen = getline (&line, &linesize, fp); + if (linelen == (size_t)(-1)) + { + error (0, 0, _("%s subprocess I/O error"), prog); + fclose (fp); + goto failed; + } + if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL); + if (exitstatus != 0) + { + error (0, 0, _("%s subprocess failed with exit code %d"), + prog, exitstatus); + goto failed; + } + + return line; + +failed: + return "EMAIL@ADDRESS"; +} + + +/* Construct the value for the Last-Translator field. */ +static const char * +last_translator () +{ + if (no_translator) + return "Automatically generated"; + else + { + const char *fullname = get_user_fullname (); + const char *email = get_user_email (); + + if (fullname != NULL) + return xasprintf ("%s <%s>", fullname, email); + else + return xasprintf ("<%s>", email); + } +} + + +/* Return the name of the language used by the language team, in English. */ +static const char * +language_team_englishname () +{ + size_t i; + + /* Search for a name depending on the catalogname. */ + for (i = 0; i < language_variant_table_size; i++) + if (strcmp (language_variant_table[i].code, catalogname) == 0) + return language_variant_table[i].english; + + /* Search for a name depending on the language only. */ + return englishname_of_language (); +} + + +/* Return the language team's mailing list address or homepage URL. */ +static const char * +language_team_address () +{ + const char *prog = relocate (PROJECTSDIR "/team-address"); + char *argv[7]; + pid_t child; + int fd[1]; + FILE *fp; + char *line; + size_t linesize; + size_t linelen; + int exitstatus; + + /* Call the team-address shell script. */ + argv[0] = "/bin/sh"; + argv[1] = (char *) prog; + argv[2] = (char *) relocate (PROJECTSDIR); + argv[3] = (char *) relocate (LIBDIR "/gettext"); + argv[4] = (char *) catalogname; + argv[5] = (char *) language; + argv[6] = NULL; + child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, + fd); + if (child == -1) + goto failed; + + /* Retrieve its result. */ + fp = fdopen (fd[0], "r"); + if (fp == NULL) + { + error (0, errno, _("fdopen() failed")); + goto failed; + } + + line = NULL; linesize = 0; + linelen = getline (&line, &linesize, fp); + if (linelen == (size_t)(-1)) + line = ""; + else if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL); + if (exitstatus != 0) + { + error (0, 0, _("%s subprocess failed with exit code %d"), + prog, exitstatus); + goto failed; + } + + return line; + +failed: + return ""; +} + + +/* Construct the value for the Language-Team field. */ +static const char * +language_team () +{ + if (no_translator) + return "none"; + else + { + const char *englishname = language_team_englishname (); + const char *address = language_team_address (); + + if (address != NULL && address[0] != '\0') + return xasprintf ("%s %s", englishname, address); + else + return englishname; + } +} + + +/* Construct the value for the Language field. */ +static const char * +language_value () +{ + return catalogname; +} + + +/* Construct the value for the MIME-Version field. */ +static const char * +mime_version () +{ + return "1.0"; +} + + +/* Construct the value for the Content-Type field. */ +static const char * +content_type (const char *header) +{ + bool was_utf8; + const char *old_field; + + /* If the POT file contains charset=UTF-8, it means that the POT file + contains non-ASCII characters, and we keep the UTF-8 encoding. + Otherwise, when the POT file is plain ASCII, we use the locale's + encoding. */ + was_utf8 = false; + old_field = get_field (header, "Content-Type"); + if (old_field != NULL) + { + const char *charsetstr = c_strstr (old_field, "charset="); + + if (charsetstr != NULL) + { + charsetstr += strlen ("charset="); + was_utf8 = (c_strcasecmp (charsetstr, "UTF-8") == 0); + } + } + return xasprintf ("text/plain; charset=%s", + was_utf8 ? "UTF-8" : canonical_locale_charset ()); +} + + +/* Construct the value for the Content-Transfer-Encoding field. */ +static const char * +content_transfer_encoding () +{ + return "8bit"; +} + + +/* Construct the value for the Plural-Forms field. */ +static const char * +plural_forms () +{ + size_t i; + + /* Search for a formula depending on the catalogname. */ + for (i = 0; i < plural_table_size; i++) + if (strcmp (plural_table[i].lang, catalogname) == 0) + return plural_table[i].value; + + /* Search for a formula depending on the language only. */ + for (i = 0; i < plural_table_size; i++) + if (strcmp (plural_table[i].lang, language) == 0) + return plural_table[i].value; + + return NULL; +} + + +static struct +{ + const char *name; + const char * (*getter0) (void); + const char * (*getter1) (const char *header); +} +fields[] = + { + { "Project-Id-Version", NULL, project_id_version }, + { "PO-Revision-Date", NULL, po_revision_date }, + { "Last-Translator", last_translator, NULL }, + { "Language-Team", language_team, NULL }, + { "Language", language_value, NULL }, + { "MIME-Version", mime_version, NULL }, + { "Content-Type", NULL, content_type }, + { "Content-Transfer-Encoding", content_transfer_encoding, NULL }, + { "Plural-Forms", plural_forms, NULL } + }; + +#define NFIELDS SIZEOF (fields) +#define FIELD_LAST_TRANSLATOR 2 + + +/* Retrieve a freshly allocated copy of a field's value. */ +static char * +get_field (const char *header, const char *field) +{ + size_t len = strlen (field); + const char *line; + + for (line = header;;) + { + if (strncmp (line, field, len) == 0 && line[len] == ':') + { + const char *value_start; + const char *value_end; + char *value; + + value_start = line + len + 1; + if (*value_start == ' ') + value_start++; + value_end = strchr (value_start, '\n'); + if (value_end == NULL) + value_end = value_start + strlen (value_start); + + value = XNMALLOC (value_end - value_start + 1, char); + memcpy (value, value_start, value_end - value_start); + value[value_end - value_start] = '\0'; + + return value; + } + + line = strchr (line, '\n'); + if (line != NULL) + line++; + else + break; + } + + return NULL; +} + +/* Add a field with value to a header, and return the new header. */ +static char * +put_field (const char *old_header, const char *field, const char *value) +{ + size_t len = strlen (field); + const char *line; + char *new_header; + char *p; + + for (line = old_header;;) + { + if (strncmp (line, field, len) == 0 && line[len] == ':') + { + const char *value_start; + const char *value_end; + + value_start = line + len + 1; + if (*value_start == ' ') + value_start++; + value_end = strchr (value_start, '\n'); + if (value_end == NULL) + value_end = value_start + strlen (value_start); + + new_header = XNMALLOC (strlen (old_header) + - (value_end - value_start) + + strlen (value) + + (*value_end != '\n' ? 1 : 0) + + 1, + char); + p = new_header; + memcpy (p, old_header, value_start - old_header); + p += value_start - old_header; + memcpy (p, value, strlen (value)); + p += strlen (value); + if (*value_end != '\n') + *p++ = '\n'; + strcpy (p, value_end); + + return new_header; + } + + line = strchr (line, '\n'); + if (line != NULL) + line++; + else + break; + } + + new_header = XNMALLOC (strlen (old_header) + 1 + + len + 2 + strlen (value) + 1 + + 1, + char); + p = new_header; + memcpy (p, old_header, strlen (old_header)); + p += strlen (old_header); + if (p > new_header && p[-1] != '\n') + *p++ = '\n'; + memcpy (p, field, len); + p += len; + *p++ = ':'; + *p++ = ' '; + memcpy (p, value, strlen (value)); + p += strlen (value); + *p++ = '\n'; + *p = '\0'; + + return new_header; +} + + +/* Return the title format string. */ +static const char * +get_title () +{ + /* This is tricky. We want the translation in the given locale specified by + the command line, not the current locale. But we want it in the encoding + that we put into the header entry, not the encoding of that locale. + We could avoid the use of OUTPUT_CHARSET by using a separate message + catalog and bind_textdomain_codeset(), but that doesn't seem worth the + trouble for one single message. */ + const char *encoding; + const char *tmp; + char *old_LC_ALL; + char *old_LANGUAGE; + char *old_OUTPUT_CHARSET; + const char *msgid; + const char *english; + const char *result; + + encoding = canonical_locale_charset (); + + /* First, the English title. */ + english = xasprintf ("%s translations for %%s package", + englishname_of_language ()); + + /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */ + + tmp = getenv ("LC_ALL"); + old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL); + + tmp = getenv ("LANGUAGE"); + old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL); + + tmp = getenv ("OUTPUT_CHARSET"); + old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL); + + xsetenv ("LC_ALL", locale, 1); + unsetenv ("LANGUAGE"); + xsetenv ("OUTPUT_CHARSET", encoding, 1); + +#ifdef HAVE_SETLOCALE + if (setlocale (LC_ALL, "") == NULL) + /* Nonexistent locale. Use the English title. */ + result = english; + else +#endif + { + /* Fetch the translation. */ + /* TRANSLATORS: "English" needs to be replaced by your language. + For example in it.po write "Traduzioni italiani ...", + *not* "Traduzioni inglesi ...". */ + msgid = N_("English translations for %s package"); + result = gettext (msgid); + if (result != msgid && strcmp (result, msgid) != 0) + /* Use the English and the foreign title. */ + result = xasprintf ("%s\n%s", english, result); + else + /* No translation found. Use the English title. */ + result = english; + } + + /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */ + + if (old_LC_ALL != NULL) + xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL); + else + unsetenv ("LC_ALL"); + + if (old_LANGUAGE != NULL) + xsetenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE); + else + unsetenv ("LANGUAGE"); + + if (old_OUTPUT_CHARSET != NULL) + xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET); + else + unsetenv ("OUTPUT_CHARSET"); + +#ifdef HAVE_SETLOCALE + setlocale (LC_ALL, ""); +#endif + + return result; +} + + +/* Perform a set of substitutions in a string and return the resulting + string. When subst[j][0] found, it is replaced with subst[j][1]. + subst[j][0] must not be the empty string. */ +static const char * +subst_string (const char *str, + unsigned int nsubst, const char *(*subst)[2]) +{ + if (nsubst > 0) + { + char *malloced = NULL; + size_t *substlen; + size_t i; + unsigned int j; + + substlen = (size_t *) xmalloca (nsubst * sizeof (size_t)); + for (j = 0; j < nsubst; j++) + { + substlen[j] = strlen (subst[j][0]); + if (substlen[j] == 0) + abort (); + } + + for (i = 0;;) + { + if (str[i] == '\0') + break; + for (j = 0; j < nsubst; j++) + if (*(str + i) == *subst[j][0] + && strncmp (str + i, subst[j][0], substlen[j]) == 0) + { + size_t replacement_len = strlen (subst[j][1]); + size_t new_len = strlen (str) - substlen[j] + replacement_len; + char *new_str = XNMALLOC (new_len + 1, char); + memcpy (new_str, str, i); + memcpy (new_str + i, subst[j][1], replacement_len); + strcpy (new_str + i + replacement_len, str + i + substlen[j]); + if (malloced != NULL) + free (malloced); + str = new_str; + malloced = new_str; + i += replacement_len; + break; + } + if (j == nsubst) + i++; + } + + freea (substlen); + } + + return str; +} + +/* Perform a set of substitutions on each string of a string list. + When subst[j][0] found, it is replaced with subst[j][1]. subst[j][0] + must not be the empty string. */ +static void +subst_string_list (string_list_ty *slp, + unsigned int nsubst, const char *(*subst)[2]) +{ + size_t j; + + for (j = 0; j < slp->nitems; j++) + slp->item[j] = subst_string (slp->item[j], nsubst, subst); +} + + +/* Fill the templates in all fields of the header entry. */ +static msgdomain_list_ty * +fill_header (msgdomain_list_ty *mdlp) +{ + /* Cache the strings filled in, for use when there are multiple domains + and a header entry for each domain. */ + const char *field_value[NFIELDS]; + size_t k, j, i; + + for (i = 0; i < NFIELDS; i++) + field_value[i] = NULL; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + if (mlp->nitems > 0) + { + message_ty *header_mp = NULL; + char *header; + + /* Search the header entry. */ + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + header_mp = mlp->item[j]; + break; + } + + /* If it wasn't found, provide one. */ + if (header_mp == NULL) + { + static lex_pos_ty pos = { __FILE__, __LINE__ }; + + header_mp = message_alloc (NULL, "", NULL, "", 1, &pos); + message_list_prepend (mlp, header_mp); + } + + header = xstrdup (header_mp->msgstr); + + /* Fill in the fields. */ + for (i = 0; i < NFIELDS; i++) + { + if (field_value[i] == NULL) + field_value[i] = + (fields[i].getter1 != NULL + ? fields[i].getter1 (header) + : fields[i].getter0 ()); + + if (field_value[i] != NULL) + { + char *old_header = header; + header = put_field (header, fields[i].name, field_value[i]); + free (old_header); + } + } + + /* Replace the old translation in the header entry. */ + header_mp->msgstr = header; + header_mp->msgstr_len = strlen (header) + 1; + + /* Update the comments in the header entry. */ + if (header_mp->comment != NULL) + { + const char *subst[4][2]; + const char *id; + time_t now; + + id = project_id (header); + subst[0][0] = "SOME DESCRIPTIVE TITLE"; + subst[0][1] = xasprintf (get_title (), id, id); + subst[1][0] = "PACKAGE"; + subst[1][1] = id; + subst[2][0] = "FIRST AUTHOR "; + subst[2][1] = field_value[FIELD_LAST_TRANSLATOR]; + subst[3][0] = "YEAR"; + subst[3][1] = + xasprintf ("%d", + (time (&now), (localtime (&now))->tm_year + 1900)); + subst_string_list (header_mp->comment, SIZEOF (subst), subst); + } + + /* Finally remove the fuzzy attribute. */ + header_mp->is_fuzzy = false; + } + } + + return mdlp; +} + + +/* Update the msgstr plural entries according to the nplurals count. */ +static msgdomain_list_ty * +update_msgstr_plurals (msgdomain_list_ty *mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + message_ty *header_entry; + unsigned long int nplurals; + char *untranslated_plural_msgstr; + size_t j; + + header_entry = message_list_search (mlp, NULL, ""); + nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL); + untranslated_plural_msgstr = XNMALLOC (nplurals, char); + memset (untranslated_plural_msgstr, '\0', nplurals); + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + bool is_untranslated; + const char *p; + const char *pend; + + if (mp->msgid_plural != NULL) + { + /* Test if mp is untranslated. (It most likely is.) */ + is_untranslated = true; + for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++) + if (*p != '\0') + { + is_untranslated = false; + break; + } + if (is_untranslated) + { + /* Change mp->msgstr_len consecutive empty strings into + nplurals consecutive empty strings. */ + if (nplurals > mp->msgstr_len) + mp->msgstr = untranslated_plural_msgstr; + mp->msgstr_len = nplurals; + } + } + } + } + return mdlp; +} diff --git a/gettext-tools/src/msgl-ascii.c b/gettext-tools/src/msgl-ascii.c new file mode 100644 index 0000000..e3ca953 --- /dev/null +++ b/gettext-tools/src/msgl-ascii.c @@ -0,0 +1,112 @@ +/* Message list test for ASCII character set. + Copyright (C) 2001-2002, 2005-2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "msgl-ascii.h" + +#include "c-ctype.h" + + +/* This file's structure parallels msgl-iconv.c. */ + + +bool +is_ascii_string (const char *string) +{ + for (; *string; string++) + if (!c_isascii ((unsigned char) *string)) + return false; + return true; +} + +bool +is_ascii_string_list (string_list_ty *slp) +{ + size_t i; + + if (slp != NULL) + for (i = 0; i < slp->nitems; i++) + if (!is_ascii_string (slp->item[i])) + return false; + return true; +} + +bool +is_ascii_message (message_ty *mp) +{ + const char *p = mp->msgstr; + const char *p_end = p + mp->msgstr_len; + + for (; p < p_end; p++) + if (!c_isascii ((unsigned char) *p)) + return false; + + if (!is_ascii_string_list (mp->comment)) + return false; + if (!is_ascii_string_list (mp->comment_dot)) + return false; + + /* msgid and msgid_plural are normally ASCII, so why checking? + Because in complete UTF-8 environments they can be UTF-8, not ASCII. */ + if (!is_ascii_string (mp->msgid)) + return false; + if (mp->msgid_plural != NULL && !is_ascii_string (mp->msgid_plural)) + return false; + + /* Likewise for msgctxt. */ + if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)) + return false; + + /* Likewise for the prev_* fields. */ + if (mp->prev_msgctxt != NULL && !is_ascii_string (mp->prev_msgctxt)) + return false; + if (mp->prev_msgid != NULL && !is_ascii_string (mp->prev_msgid)) + return false; + if (mp->prev_msgid_plural != NULL && !is_ascii_string (mp->prev_msgid_plural)) + return false; + + return true; +} + +bool +is_ascii_message_list (message_list_ty *mlp) +{ + size_t j; + + for (j = 0; j < mlp->nitems; j++) + if (!is_ascii_message (mlp->item[j])) + return false; + + return true; +} + +bool +is_ascii_msgdomain_list (msgdomain_list_ty *mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + if (!is_ascii_message_list (mdlp->item[k]->messages)) + return false; + + return true; +} diff --git a/gettext-tools/src/msgl-ascii.h b/gettext-tools/src/msgl-ascii.h new file mode 100644 index 0000000..fee41a9 --- /dev/null +++ b/gettext-tools/src/msgl-ascii.h @@ -0,0 +1,48 @@ +/* Message list test for ASCII character set. + Copyright (C) 2001-2003, 2005 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_ASCII_H +#define _MSGL_ASCII_H + +#include "message.h" + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +extern bool + is_ascii_string (const char *string); +extern bool + is_ascii_string_list (string_list_ty *slp); +extern bool + is_ascii_message (message_ty *mp); +extern bool + is_ascii_message_list (message_list_ty *mlp); +extern bool + is_ascii_msgdomain_list (msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MSGL_ASCII_H */ diff --git a/gettext-tools/src/msgl-cat.c b/gettext-tools/src/msgl-cat.c new file mode 100644 index 0000000..0bd58d4 --- /dev/null +++ b/gettext-tools/src/msgl-cat.c @@ -0,0 +1,799 @@ +/* Message list concatenation and duplicate handling. + Copyright (C) 2001-2003, 2005-2008, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +/* Specification. */ +#include "msgl-cat.h" + +#include +#include +#include +#include +#include + +#include "error.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "message.h" +#include "read-catalog.h" +#include "po-charset.h" +#include "msgl-ascii.h" +#include "msgl-equal.h" +#include "msgl-iconv.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "c-strstr.h" +#include "basename.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* These variables control which messages are selected. */ +int more_than; +int less_than; + +/* If true, use the first available translation. + If false, merge all available translations into one and fuzzy it. */ +bool use_first; + +/* If true, merge like msgcomm. + If false, merge like msgcat and msguniq. */ +bool msgcomm_mode = false; + +/* If true, omit the header entry. + If false, keep the header entry present in the input. */ +bool omit_header = false; + + +static bool +is_message_selected (const message_ty *tmp) +{ + int used = (tmp->used >= 0 ? tmp->used : - tmp->used); + + return (is_header (tmp) + ? !omit_header /* keep the header entry */ + : (used > more_than && used < less_than)); +} + + +static bool +is_message_needed (const message_ty *mp) +{ + if (!msgcomm_mode + && ((!is_header (mp) && mp->is_fuzzy) || mp->msgstr[0] == '\0')) + /* Weak translation. Needed if there are only weak translations. */ + return mp->tmp->used < 0 && is_message_selected (mp->tmp); + else + /* Good translation. */ + return is_message_selected (mp->tmp); +} + + +/* The use_first logic. */ +static bool +is_message_first_needed (const message_ty *mp) +{ + if (mp->tmp->obsolete && is_message_needed (mp)) + { + mp->tmp->obsolete = false; + return true; + } + else + return false; +} + + +msgdomain_list_ty * +catenate_msgdomain_list (string_list_ty *file_list, + catalog_input_format_ty input_syntax, + const char *to_code) +{ + const char * const *files = file_list->item; + size_t nfiles = file_list->nitems; + msgdomain_list_ty **mdlps; + const char ***canon_charsets; + const char ***identifications; + msgdomain_list_ty *total_mdlp; + const char *canon_to_code; + size_t n, j; + + /* Read input files. */ + mdlps = XNMALLOC (nfiles, msgdomain_list_ty *); + for (n = 0; n < nfiles; n++) + mdlps[n] = read_catalog_file (files[n], input_syntax); + + /* Determine the canonical name of each input file's encoding. */ + canon_charsets = XNMALLOC (nfiles, const char **); + for (n = 0; n < nfiles; n++) + { + msgdomain_list_ty *mdlp = mdlps[n]; + size_t k; + + canon_charsets[n] = XNMALLOC (mdlp->nitems, const char *); + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + const char *canon_from_code = NULL; + + if (mlp->nitems > 0) + { + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + char *charset; + const char *canon_charset; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + + canon_charset = po_charset_canonicalize (charset); + if (canon_charset == NULL) + { + /* Don't give an error for POT files, because + POT files usually contain only ASCII + msgids. */ + const char *filename = files[n]; + size_t filenamelen = strlen (filename); + + if (filenamelen >= 4 + && memcmp (filename + filenamelen - 4, + ".pot", 4) == 0 + && strcmp (charset, "CHARSET") == 0) + canon_charset = po_charset_ascii; + else + error (EXIT_FAILURE, 0, + _("\ +present charset \"%s\" is not a portable encoding name"), + charset); + } + + freea (charset); + + if (canon_from_code == NULL) + canon_from_code = canon_charset; + else if (canon_from_code != canon_charset) + error (EXIT_FAILURE, 0, + _("\ +two different charsets \"%s\" and \"%s\" in input file"), + canon_from_code, canon_charset); + } + } + } + if (canon_from_code == NULL) + { + if (is_ascii_message_list (mlp)) + canon_from_code = po_charset_ascii; + else if (mdlp->encoding != NULL) + canon_from_code = mdlp->encoding; + else + { + if (k == 0) + error (EXIT_FAILURE, 0, _("\ +input file '%s' doesn't contain a header entry with a charset specification"), + files[n]); + else + error (EXIT_FAILURE, 0, _("\ +domain \"%s\" in input file '%s' doesn't contain a header entry with a charset specification"), + mdlp->item[k]->domain, files[n]); + } + } + } + canon_charsets[n][k] = canon_from_code; + } + } + + /* Determine textual identifications of each file/domain combination. */ + identifications = XNMALLOC (nfiles, const char **); + for (n = 0; n < nfiles; n++) + { + const char *filename = basename (files[n]); + msgdomain_list_ty *mdlp = mdlps[n]; + size_t k; + + identifications[n] = XNMALLOC (mdlp->nitems, const char *); + for (k = 0; k < mdlp->nitems; k++) + { + const char *domain = mdlp->item[k]->domain; + message_list_ty *mlp = mdlp->item[k]->messages; + char *project_id = NULL; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *cp = c_strstr (header, "Project-Id-Version:"); + + if (cp != NULL) + { + const char *endp; + + cp += sizeof ("Project-Id-Version:") - 1; + + endp = strchr (cp, '\n'); + if (endp == NULL) + endp = cp + strlen (cp); + + while (cp < endp && *cp == ' ') + cp++; + + if (cp < endp) + { + size_t len = endp - cp; + project_id = XNMALLOC (len + 1, char); + memcpy (project_id, cp, len); + project_id[len] = '\0'; + } + break; + } + } + } + + identifications[n][k] = + (project_id != NULL + ? (k > 0 ? xasprintf ("%s:%s (%s)", filename, domain, project_id) + : xasprintf ("%s (%s)", filename, project_id)) + : (k > 0 ? xasprintf ("%s:%s", filename, domain) + : xasprintf ("%s", filename))); + } + } + + /* Create list of resulting messages, but don't fill it. Only count + the number of translations for each message. + If for a message, there is at least one non-fuzzy, non-empty translation, + use only the non-fuzzy, non-empty translations. Otherwise use the + fuzzy or empty translations as well. */ + total_mdlp = msgdomain_list_alloc (true); + for (n = 0; n < nfiles; n++) + { + msgdomain_list_ty *mdlp = mdlps[n]; + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + { + const char *domain = mdlp->item[k]->domain; + message_list_ty *mlp = mdlp->item[k]->messages; + message_list_ty *total_mlp; + + total_mlp = msgdomain_list_sublist (total_mdlp, domain, true); + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + message_ty *tmp; + size_t i; + + tmp = message_list_search (total_mlp, mp->msgctxt, mp->msgid); + if (tmp == NULL) + { + tmp = message_alloc (mp->msgctxt, mp->msgid, mp->msgid_plural, + NULL, 0, &mp->pos); + tmp->is_fuzzy = true; /* may be set to false later */ + for (i = 0; i < NFORMATS; i++) + tmp->is_format[i] = undecided; /* may be set to yes/no later */ + tmp->range.min = - INT_MAX; + tmp->range.max = - INT_MAX; + tmp->do_wrap = yes; /* may be set to no later */ + tmp->obsolete = true; /* may be set to false later */ + tmp->alternative_count = 0; + tmp->alternative = NULL; + message_list_append (total_mlp, tmp); + } + + if (!msgcomm_mode + && ((!is_header (mp) && mp->is_fuzzy) + || mp->msgstr[0] == '\0')) + /* Weak translation. Counted as negative tmp->used. */ + { + if (tmp->used <= 0) + tmp->used--; + } + else + /* Good translation. Counted as positive tmp->used. */ + { + if (tmp->used < 0) + tmp->used = 0; + tmp->used++; + } + mp->tmp = tmp; + } + } + } + + /* Remove messages that are not used and need not be converted. */ + for (n = 0; n < nfiles; n++) + { + msgdomain_list_ty *mdlp = mdlps[n]; + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + message_list_remove_if_not (mlp, + use_first + ? is_message_first_needed + : is_message_needed); + + /* If no messages are remaining, drop the charset. */ + if (mlp->nitems == 0) + canon_charsets[n][k] = NULL; + } + } + { + size_t k; + + for (k = 0; k < total_mdlp->nitems; k++) + { + message_list_ty *mlp = total_mdlp->item[k]->messages; + + message_list_remove_if_not (mlp, is_message_selected); + } + } + + /* Determine the common known a-priori encoding, if any. */ + if (nfiles > 0) + { + bool all_same_encoding = true; + + for (n = 1; n < nfiles; n++) + if (mdlps[n]->encoding != mdlps[0]->encoding) + { + all_same_encoding = false; + break; + } + + if (all_same_encoding) + total_mdlp->encoding = mdlps[0]->encoding; + } + + /* Determine the target encoding for the remaining messages. */ + if (to_code != NULL) + { + /* Canonicalize target encoding. */ + canon_to_code = po_charset_canonicalize (to_code); + if (canon_to_code == NULL) + error (EXIT_FAILURE, 0, + _("target charset \"%s\" is not a portable encoding name."), + to_code); + } + else + { + /* No target encoding was specified. Test whether the messages are + all in a single encoding. If so, conversion is not needed. */ + const char *first = NULL; + const char *second = NULL; + bool with_ASCII = false; + bool with_UTF8 = false; + bool all_ASCII_compatible = true; + + for (n = 0; n < nfiles; n++) + { + msgdomain_list_ty *mdlp = mdlps[n]; + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + if (canon_charsets[n][k] != NULL) + { + if (canon_charsets[n][k] == po_charset_ascii) + with_ASCII = true; + else + { + if (first == NULL) + first = canon_charsets[n][k]; + else if (canon_charsets[n][k] != first && second == NULL) + second = canon_charsets[n][k]; + + if (strcmp (canon_charsets[n][k], "UTF-8") == 0) + with_UTF8 = true; + + if (!po_charset_ascii_compatible (canon_charsets[n][k])) + all_ASCII_compatible = false; + } + } + } + + if (with_ASCII && !all_ASCII_compatible) + { + /* assert (first != NULL); */ + if (second == NULL) + second = po_charset_ascii; + } + + if (second != NULL) + { + /* A conversion is needed. Warn the user since he hasn't asked + for it and might be surprised. */ + if (with_UTF8) + multiline_warning (xasprintf (_("warning: ")), + xasprintf (_("\ +Input files contain messages in different encodings, UTF-8 among others.\n\ +Converting the output to UTF-8.\n\ +"))); + else + multiline_warning (xasprintf (_("warning: ")), + xasprintf (_("\ +Input files contain messages in different encodings, %s and %s among others.\n\ +Converting the output to UTF-8.\n\ +To select a different output encoding, use the --to-code option.\n\ +"), first, second)); + canon_to_code = po_charset_utf8; + } + else if (first != NULL && with_ASCII && all_ASCII_compatible) + { + /* The conversion is a no-op conversion. Don't warn the user, + but still perform the conversion, in order to check that the + input was really ASCII. */ + canon_to_code = first; + } + else + { + /* No conversion needed. */ + canon_to_code = NULL; + } + } + + /* Now convert the remaining messages to to_code. */ + if (canon_to_code != NULL) + for (n = 0; n < nfiles; n++) + { + msgdomain_list_ty *mdlp = mdlps[n]; + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + if (canon_charsets[n][k] != NULL) + /* If the user hasn't given a to_code, don't bother doing a noop + conversion that would only replace the charset name in the + header entry with its canonical equivalent. */ + if (!(to_code == NULL && canon_charsets[n][k] == canon_to_code)) + if (iconv_message_list (mdlp->item[k]->messages, + canon_charsets[n][k], canon_to_code, + files[n])) + { + multiline_error (xstrdup (""), + xasprintf (_("\ +Conversion of file %s from %s encoding to %s encoding\n\ +changes some msgids or msgctxts.\n\ +Either change all msgids and msgctxts to be pure ASCII, or ensure they are\n\ +UTF-8 encoded from the beginning, i.e. already in your source code files.\n"), + files[n], canon_charsets[n][k], + canon_to_code)); + exit (EXIT_FAILURE); + } + } + + /* Fill the resulting messages. */ + for (n = 0; n < nfiles; n++) + { + msgdomain_list_ty *mdlp = mdlps[n]; + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + message_ty *tmp = mp->tmp; + size_t i; + + /* No need to discard unneeded weak translations here; + they have already been filtered out above. */ + if (use_first || tmp->used == 1 || tmp->used == -1) + { + /* Copy mp, as only message, into tmp. */ + tmp->msgstr = mp->msgstr; + tmp->msgstr_len = mp->msgstr_len; + tmp->pos = mp->pos; + if (mp->comment) + for (i = 0; i < mp->comment->nitems; i++) + message_comment_append (tmp, mp->comment->item[i]); + if (mp->comment_dot) + for (i = 0; i < mp->comment_dot->nitems; i++) + message_comment_dot_append (tmp, + mp->comment_dot->item[i]); + for (i = 0; i < mp->filepos_count; i++) + message_comment_filepos (tmp, mp->filepos[i].file_name, + mp->filepos[i].line_number); + tmp->is_fuzzy = mp->is_fuzzy; + for (i = 0; i < NFORMATS; i++) + tmp->is_format[i] = mp->is_format[i]; + tmp->range = mp->range; + tmp->do_wrap = mp->do_wrap; + tmp->prev_msgctxt = mp->prev_msgctxt; + tmp->prev_msgid = mp->prev_msgid; + tmp->prev_msgid_plural = mp->prev_msgid_plural; + tmp->obsolete = mp->obsolete; + } + else if (msgcomm_mode) + { + /* Copy mp, as only message, into tmp. */ + if (tmp->msgstr == NULL) + { + tmp->msgstr = mp->msgstr; + tmp->msgstr_len = mp->msgstr_len; + tmp->pos = mp->pos; + tmp->is_fuzzy = mp->is_fuzzy; + tmp->prev_msgctxt = mp->prev_msgctxt; + tmp->prev_msgid = mp->prev_msgid; + tmp->prev_msgid_plural = mp->prev_msgid_plural; + } + if (mp->comment && tmp->comment == NULL) + for (i = 0; i < mp->comment->nitems; i++) + message_comment_append (tmp, mp->comment->item[i]); + if (mp->comment_dot && tmp->comment_dot == NULL) + for (i = 0; i < mp->comment_dot->nitems; i++) + message_comment_dot_append (tmp, + mp->comment_dot->item[i]); + for (i = 0; i < mp->filepos_count; i++) + message_comment_filepos (tmp, mp->filepos[i].file_name, + mp->filepos[i].line_number); + for (i = 0; i < NFORMATS; i++) + if (tmp->is_format[i] == undecided) + tmp->is_format[i] = mp->is_format[i]; + if (tmp->range.min == - INT_MAX + && tmp->range.max == - INT_MAX) + tmp->range = mp->range; + else if (has_range_p (mp->range) && has_range_p (tmp->range)) + { + if (mp->range.min < tmp->range.min) + tmp->range.min = mp->range.min; + if (mp->range.max > tmp->range.max) + tmp->range.max = mp->range.max; + } + else + { + tmp->range.min = -1; + tmp->range.max = -1; + } + if (tmp->do_wrap == undecided) + tmp->do_wrap = mp->do_wrap; + tmp->obsolete = false; + } + else + { + /* Copy mp, among others, into tmp. */ + char *id = xasprintf ("#-#-#-#-# %s #-#-#-#-#", + identifications[n][k]); + size_t nbytes; + + if (tmp->alternative_count == 0) + tmp->pos = mp->pos; + + i = tmp->alternative_count; + nbytes = (i + 1) * sizeof (struct altstr); + tmp->alternative = xrealloc (tmp->alternative, nbytes); + tmp->alternative[i].msgstr = mp->msgstr; + tmp->alternative[i].msgstr_len = mp->msgstr_len; + tmp->alternative[i].msgstr_end = + tmp->alternative[i].msgstr + tmp->alternative[i].msgstr_len; + tmp->alternative[i].comment = mp->comment; + tmp->alternative[i].comment_dot = mp->comment_dot; + tmp->alternative[i].id = id; + tmp->alternative_count = i + 1; + + for (i = 0; i < mp->filepos_count; i++) + message_comment_filepos (tmp, mp->filepos[i].file_name, + mp->filepos[i].line_number); + if (!mp->is_fuzzy) + tmp->is_fuzzy = false; + for (i = 0; i < NFORMATS; i++) + if (mp->is_format[i] == yes) + tmp->is_format[i] = yes; + else if (mp->is_format[i] == no + && tmp->is_format[i] == undecided) + tmp->is_format[i] = no; + if (tmp->range.min == - INT_MAX + && tmp->range.max == - INT_MAX) + tmp->range = mp->range; + else if (has_range_p (mp->range) && has_range_p (tmp->range)) + { + if (mp->range.min < tmp->range.min) + tmp->range.min = mp->range.min; + if (mp->range.max > tmp->range.max) + tmp->range.max = mp->range.max; + } + else + { + tmp->range.min = -1; + tmp->range.max = -1; + } + if (mp->do_wrap == no) + tmp->do_wrap = no; + /* Don't fill tmp->prev_msgid in this case. */ + if (!mp->obsolete) + tmp->obsolete = false; + } + } + } + } + { + size_t k; + + for (k = 0; k < total_mdlp->nitems; k++) + { + message_list_ty *mlp = total_mdlp->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *tmp = mlp->item[j]; + + if (tmp->alternative_count > 0) + { + /* Test whether all alternative translations are equal. */ + struct altstr *first = &tmp->alternative[0]; + size_t i; + + for (i = 0; i < tmp->alternative_count; i++) + if (!(tmp->alternative[i].msgstr_len == first->msgstr_len + && memcmp (tmp->alternative[i].msgstr, first->msgstr, + first->msgstr_len) == 0)) + break; + + if (i == tmp->alternative_count) + { + /* All alternatives are equal. */ + tmp->msgstr = first->msgstr; + tmp->msgstr_len = first->msgstr_len; + } + else + { + /* Concatenate the alternative msgstrs into a single one, + separated by markers. */ + size_t len; + const char *p; + const char *p_end; + char *new_msgstr; + char *np; + + len = 0; + for (i = 0; i < tmp->alternative_count; i++) + { + size_t id_len = strlen (tmp->alternative[i].id); + + len += tmp->alternative[i].msgstr_len; + + p = tmp->alternative[i].msgstr; + p_end = tmp->alternative[i].msgstr_end; + for (; p < p_end; p += strlen (p) + 1) + len += id_len + 2; + } + + new_msgstr = XNMALLOC (len, char); + np = new_msgstr; + for (;;) + { + /* Test whether there's one more plural form to + process. */ + for (i = 0; i < tmp->alternative_count; i++) + if (tmp->alternative[i].msgstr + < tmp->alternative[i].msgstr_end) + break; + if (i == tmp->alternative_count) + break; + + /* Process next plural form. */ + for (i = 0; i < tmp->alternative_count; i++) + if (tmp->alternative[i].msgstr + < tmp->alternative[i].msgstr_end) + { + if (np > new_msgstr && np[-1] != '\0' + && np[-1] != '\n') + *np++ = '\n'; + + len = strlen (tmp->alternative[i].id); + memcpy (np, tmp->alternative[i].id, len); + np += len; + *np++ = '\n'; + + len = strlen (tmp->alternative[i].msgstr); + memcpy (np, tmp->alternative[i].msgstr, len); + np += len; + tmp->alternative[i].msgstr += len + 1; + } + + /* Plural forms are separated by NUL bytes. */ + *np++ = '\0'; + } + tmp->msgstr = new_msgstr; + tmp->msgstr_len = np - new_msgstr; + + tmp->is_fuzzy = true; + } + + /* Test whether all alternative comments are equal. */ + for (i = 0; i < tmp->alternative_count; i++) + if (tmp->alternative[i].comment == NULL + || !string_list_equal (tmp->alternative[i].comment, + first->comment)) + break; + + if (i == tmp->alternative_count) + /* All alternatives are equal. */ + tmp->comment = first->comment; + else + /* Concatenate the alternative comments into a single one, + separated by markers. */ + for (i = 0; i < tmp->alternative_count; i++) + { + string_list_ty *slp = tmp->alternative[i].comment; + + if (slp != NULL) + { + size_t l; + + message_comment_append (tmp, tmp->alternative[i].id); + for (l = 0; l < slp->nitems; l++) + message_comment_append (tmp, slp->item[l]); + } + } + + /* Test whether all alternative dot comments are equal. */ + for (i = 0; i < tmp->alternative_count; i++) + if (tmp->alternative[i].comment_dot == NULL + || !string_list_equal (tmp->alternative[i].comment_dot, + first->comment_dot)) + break; + + if (i == tmp->alternative_count) + /* All alternatives are equal. */ + tmp->comment_dot = first->comment_dot; + else + /* Concatenate the alternative dot comments into a single one, + separated by markers. */ + for (i = 0; i < tmp->alternative_count; i++) + { + string_list_ty *slp = tmp->alternative[i].comment_dot; + + if (slp != NULL) + { + size_t l; + + message_comment_dot_append (tmp, + tmp->alternative[i].id); + for (l = 0; l < slp->nitems; l++) + message_comment_dot_append (tmp, slp->item[l]); + } + } + } + } + } + } + + return total_mdlp; +} diff --git a/gettext-tools/src/msgl-cat.h b/gettext-tools/src/msgl-cat.h new file mode 100644 index 0000000..4de35d5 --- /dev/null +++ b/gettext-tools/src/msgl-cat.h @@ -0,0 +1,60 @@ +/* Message list concatenation and duplicate handling. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_CAT_H +#define _MSGL_CAT_H + +#include + +#include "message.h" +#include "str-list.h" +#include "read-catalog-abstract.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* These variables control which messages are selected. */ +extern DLL_VARIABLE int more_than; +extern DLL_VARIABLE int less_than; + +/* If true, use the first available translation. + If false, merge all available translations into one and fuzzy it. */ +extern DLL_VARIABLE bool use_first; + +/* If true, merge like msgcomm. + If false, merge like msgcat and msguniq. */ +extern DLL_VARIABLE bool msgcomm_mode; + +/* If true, omit the header entry. + If false, keep the header entry present in the input. */ +extern DLL_VARIABLE bool omit_header; + +extern msgdomain_list_ty * + catenate_msgdomain_list (string_list_ty *file_list, + catalog_input_format_ty input_syntax, + const char *to_code); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MSGL_CAT_H */ diff --git a/gettext-tools/src/msgl-charset.c b/gettext-tools/src/msgl-charset.c new file mode 100644 index 0000000..e8deb58 --- /dev/null +++ b/gettext-tools/src/msgl-charset.c @@ -0,0 +1,133 @@ +/* Message list charset and locale charset handling. + Copyright (C) 2001-2003, 2005-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +/* Specification. */ +#include "msgl-charset.h" + +#include +#include +#include + +#include "po-charset.h" +#include "localcharset.h" +#include "error.h" +#include "progname.h" +#include "basename.h" +#include "xmalloca.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "message.h" +#include "c-strstr.h" +#include "gettext.h" + +#define _(str) gettext (str) + +void +compare_po_locale_charsets (const msgdomain_list_ty *mdlp) +{ + const char *locale_code; + const char *canon_locale_code; + bool warned; + size_t j, k; + + /* Check whether the locale encoding and the PO file's encoding are the + same. Otherwise emit a warning. */ + locale_code = locale_charset (); + canon_locale_code = po_charset_canonicalize (locale_code); + warned = false; + for (k = 0; k < mdlp->nitems; k++) + { + const message_list_ty *mlp = mdlp->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + char *charset; + const char *canon_charset; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + + canon_charset = po_charset_canonicalize (charset); + if (canon_charset == NULL) + error (EXIT_FAILURE, 0, + _("\ +present charset \"%s\" is not a portable encoding name"), + charset); + freea (charset); + if (canon_locale_code != canon_charset) + { + multiline_warning (xasprintf (_("warning: ")), + xasprintf (_("\ +Locale charset \"%s\" is different from\n\ +input file charset \"%s\".\n\ +Output of '%s' might be incorrect.\n\ +Possible workarounds are:\n\ +"), locale_code, canon_charset, basename (program_name))); + multiline_warning (NULL, + xasprintf (_("\ +- Set LC_ALL to a locale with encoding %s.\n\ +"), canon_charset)); + if (canon_locale_code != NULL) + multiline_warning (NULL, + xasprintf (_("\ +- Convert the translation catalog to %s using 'msgconv',\n\ + then apply '%s',\n\ + then convert back to %s using 'msgconv'.\n\ +"), canon_locale_code, basename (program_name), canon_charset)); + if (strcmp (canon_charset, "UTF-8") != 0 + && (canon_locale_code == NULL + || strcmp (canon_locale_code, "UTF-8") != 0)) + multiline_warning (NULL, + xasprintf (_("\ +- Set LC_ALL to a locale with encoding %s,\n\ + convert the translation catalog to %s using 'msgconv',\n\ + then apply '%s',\n\ + then convert back to %s using 'msgconv'.\n\ +"), "UTF-8", "UTF-8", basename (program_name), canon_charset)); + warned = true; + } + } + } + } + } + if (canon_locale_code == NULL && !warned) + multiline_warning (xasprintf (_("warning: ")), + xasprintf (_("\ +Locale charset \"%s\" is not a portable encoding name.\n\ +Output of '%s' might be incorrect.\n\ +A possible workaround is to set LC_ALL=C.\n\ +"), locale_code, basename (program_name))); +} diff --git a/gettext-tools/src/msgl-charset.h b/gettext-tools/src/msgl-charset.h new file mode 100644 index 0000000..647a394 --- /dev/null +++ b/gettext-tools/src/msgl-charset.h @@ -0,0 +1,38 @@ +/* Message list charset and locale charset handling. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_CHARSET_H +#define _MSGL_CHARSET_H + +#include "message.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +extern void + compare_po_locale_charsets (const msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MSGL_CHARSET_H */ diff --git a/gettext-tools/src/msgl-check.c b/gettext-tools/src/msgl-check.c new file mode 100644 index 0000000..d6f4a3d --- /dev/null +++ b/gettext-tools/src/msgl-check.c @@ -0,0 +1,914 @@ +/* Checking of messages in PO files. + Copyright (C) 1995-1998, 2000-2008, 2010-2012 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "msgl-check.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "po-xerror.h" +#include "format.h" +#include "plural-exp.h" +#include "plural-eval.h" +#include "plural-table.h" +#include "c-strstr.h" +#include "message.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* Evaluates the plural formula for min <= n <= max + and returns the estimated number of times the value j was assumed. */ +static unsigned int +plural_expression_histogram (const struct plural_distribution *self, + int min, int max, unsigned long j) +{ + if (min < 0) + min = 0; + /* Limit the number of evaluations. Nothing interesting happens beyond + 1000. */ + if (max - min > 1000) + max = min + 1000; + if (min <= max) + { + const struct expression *expr = self->expr; + unsigned long n; + unsigned int count; + + /* Protect against arithmetic exceptions. */ + install_sigfpe_handler (); + + count = 0; + for (n = min; n <= max; n++) + { + unsigned long val = plural_eval (expr, n); + + if (val == j) + count++; + } + + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + + return count; + } + else + return 0; +} + + +/* Check the values returned by plural_eval. + Signals the errors through po_xerror. + Return the number of errors that were seen. + If no errors, returns in *DISTRIBUTION information about the plural_eval + values distribution. */ +int +check_plural_eval (const struct expression *plural_expr, + unsigned long nplurals_value, + const message_ty *header, + struct plural_distribution *distribution) +{ + /* Do as if the plural formula assumes a value N infinitely often if it + assumes it at least 5 times. */ +#define OFTEN 5 + unsigned char * volatile array; + + /* Allocate a distribution array. */ + if (nplurals_value <= 100) + array = XCALLOC (nplurals_value, unsigned char); + else + /* nplurals_value is nonsense. Don't risk an out-of-memory. */ + array = NULL; + + if (sigsetjmp (sigfpe_exit, 1) == 0) + { + unsigned long n; + + /* Protect against arithmetic exceptions. */ + install_sigfpe_handler (); + + for (n = 0; n <= 1000; n++) + { + unsigned long val = plural_eval (plural_expr, n); + + if ((long) val < 0) + { + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + + po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, + _("plural expression can produce negative values")); + free (array); + return 1; + } + else if (val >= nplurals_value) + { + char *msg; + + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + + msg = xasprintf (_("nplurals = %lu but plural expression can produce values as large as %lu"), + nplurals_value, val); + po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg); + free (msg); + free (array); + return 1; + } + + if (array != NULL && array[val] < OFTEN) + array[val]++; + } + + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + + /* Normalize the array[val] statistics. */ + if (array != NULL) + { + unsigned long val; + + for (val = 0; val < nplurals_value; val++) + array[val] = (array[val] == OFTEN ? 1 : 0); + } + + distribution->expr = plural_expr; + distribution->often = array; + distribution->often_length = (array != NULL ? nplurals_value : 0); + distribution->histogram = plural_expression_histogram; + + return 0; + } + else + { + /* Caught an arithmetic exception. */ + const char *msg; + + /* End of protection against arithmetic exceptions. */ + uninstall_sigfpe_handler (); + +#if USE_SIGINFO + switch (sigfpe_code) +#endif + { +#if USE_SIGINFO +# ifdef FPE_INTDIV + case FPE_INTDIV: + msg = _("plural expression can produce division by zero"); + break; +# endif +# ifdef FPE_INTOVF + case FPE_INTOVF: + msg = _("plural expression can produce integer overflow"); + break; +# endif + default: +#endif + msg = _("plural expression can produce arithmetic exceptions, possibly division by zero"); + } + + po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg); + + free (array); + + return 1; + } +#undef OFTEN +} + + +/* Try to help the translator by looking up the right plural formula for her. + Return a freshly allocated multiline help string, or NULL. */ +static char * +plural_help (const char *nullentry) +{ + struct plural_table_entry *ptentry = NULL; + + { + const char *language; + + language = c_strstr (nullentry, "Language: "); + if (language != NULL) + { + size_t len; + + language += 10; + len = strcspn (language, " \t\n"); + if (len > 0) + { + size_t j; + + for (j = 0; j < plural_table_size; j++) + if (len == strlen (plural_table[j].lang) + && strncmp (language, plural_table[j].lang, len) == 0) + { + ptentry = &plural_table[j]; + break; + } + } + } + } + + if (ptentry == NULL) + { + const char *language; + + language = c_strstr (nullentry, "Language-Team: "); + if (language != NULL) + { + size_t j; + + language += 15; + for (j = 0; j < plural_table_size; j++) + if (strncmp (language, + plural_table[j].language, + strlen (plural_table[j].language)) == 0) + { + ptentry = &plural_table[j]; + break; + } + } + } + + if (ptentry != NULL) + { + char *helpline1 = + xasprintf (_("Try using the following, valid for %s:"), + ptentry->language); + char *help = + xasprintf ("%s\n\"Plural-Forms: %s\\n\"\n", + helpline1, ptentry->value); + free (helpline1); + return help; + } + return NULL; +} + + +/* Perform plural expression checking. + Return the number of errors that were seen. + If no errors, returns in *DISTRIBUTION information about the plural_eval + values distribution. */ +static int +check_plural (message_list_ty *mlp, + int ignore_untranslated_messages, + int ignore_fuzzy_messages, + struct plural_distribution *distributionp) +{ + int seen_errors = 0; + const message_ty *has_plural; + unsigned long min_nplurals; + const message_ty *min_pos; + unsigned long max_nplurals; + const message_ty *max_pos; + struct plural_distribution distribution; + size_t j; + message_ty *header; + + /* Determine whether mlp has plural entries. */ + has_plural = NULL; + min_nplurals = ULONG_MAX; + min_pos = NULL; + max_nplurals = 0; + max_pos = NULL; + distribution.expr = NULL; + distribution.often = NULL; + distribution.often_length = 0; + distribution.histogram = NULL; + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (!mp->obsolete + && !(ignore_untranslated_messages && mp->msgstr[0] == '\0') + && !(ignore_fuzzy_messages && (mp->is_fuzzy && !is_header (mp))) + && mp->msgid_plural != NULL) + { + const char *p; + const char *p_end; + unsigned long n; + + if (has_plural == NULL) + has_plural = mp; + + n = 0; + for (p = mp->msgstr, p_end = p + mp->msgstr_len; + p < p_end; + p += strlen (p) + 1) + n++; + if (min_nplurals > n) + { + min_nplurals = n; + min_pos = mp; + } + if (max_nplurals < n) + { + max_nplurals = n; + max_pos = mp; + } + } + } + + /* Look at the plural entry for this domain. + Cf, function extract_plural_expression. */ + header = message_list_search (mlp, NULL, ""); + if (header != NULL && !header->obsolete) + { + const char *nullentry; + const char *plural; + const char *nplurals; + + nullentry = header->msgstr; + + plural = c_strstr (nullentry, "plural="); + nplurals = c_strstr (nullentry, "nplurals="); + if (plural == NULL && has_plural != NULL) + { + const char *msg1 = + _("message catalog has plural form translations"); + const char *msg2 = + _("but header entry lacks a \"plural=EXPRESSION\" attribute"); + char *help = plural_help (nullentry); + + if (help != NULL) + { + char *msg2ext = xasprintf ("%s\n%s", msg2, help); + po_xerror2 (PO_SEVERITY_ERROR, + has_plural, NULL, 0, 0, false, msg1, + header, NULL, 0, 0, true, msg2ext); + free (msg2ext); + free (help); + } + else + po_xerror2 (PO_SEVERITY_ERROR, + has_plural, NULL, 0, 0, false, msg1, + header, NULL, 0, 0, false, msg2); + + seen_errors++; + } + if (nplurals == NULL && has_plural != NULL) + { + const char *msg1 = + _("message catalog has plural form translations"); + const char *msg2 = + _("but header entry lacks a \"nplurals=INTEGER\" attribute"); + char *help = plural_help (nullentry); + + if (help != NULL) + { + char *msg2ext = xasprintf ("%s\n%s", msg2, help); + po_xerror2 (PO_SEVERITY_ERROR, + has_plural, NULL, 0, 0, false, msg1, + header, NULL, 0, 0, true, msg2ext); + free (msg2ext); + free (help); + } + else + po_xerror2 (PO_SEVERITY_ERROR, + has_plural, NULL, 0, 0, false, msg1, + header, NULL, 0, 0, false, msg2); + + seen_errors++; + } + if (plural != NULL && nplurals != NULL) + { + const char *endp; + unsigned long int nplurals_value; + struct parse_args args; + const struct expression *plural_expr; + + /* First check the number. */ + nplurals += 9; + while (*nplurals != '\0' && c_isspace ((unsigned char) *nplurals)) + ++nplurals; + endp = nplurals; + nplurals_value = 0; + if (*nplurals >= '0' && *nplurals <= '9') + nplurals_value = strtoul (nplurals, (char **) &endp, 10); + if (nplurals == endp) + { + const char *msg = _("invalid nplurals value"); + char *help = plural_help (nullentry); + + if (help != NULL) + { + char *msgext = xasprintf ("%s\n%s", msg, help); + po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true, + msgext); + free (msgext); + free (help); + } + else + po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg); + + seen_errors++; + } + + /* Then check the expression. */ + plural += 7; + args.cp = plural; + if (parse_plural_expression (&args) != 0) + { + const char *msg = _("invalid plural expression"); + char *help = plural_help (nullentry); + + if (help != NULL) + { + char *msgext = xasprintf ("%s\n%s", msg, help); + po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true, + msgext); + free (msgext); + free (help); + } + else + po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg); + + seen_errors++; + } + plural_expr = args.res; + + /* See whether nplurals and plural fit together. */ + if (!seen_errors) + seen_errors = + check_plural_eval (plural_expr, nplurals_value, header, + &distribution); + + /* Check the number of plurals of the translations. */ + if (!seen_errors) + { + if (min_nplurals < nplurals_value) + { + char *msg1 = + xasprintf (_("nplurals = %lu"), nplurals_value); + char *msg2 = + xasprintf (ngettext ("but some messages have only one plural form", + "but some messages have only %lu plural forms", + min_nplurals), + min_nplurals); + po_xerror2 (PO_SEVERITY_ERROR, + header, NULL, 0, 0, false, msg1, + min_pos, NULL, 0, 0, false, msg2); + free (msg2); + free (msg1); + seen_errors++; + } + else if (max_nplurals > nplurals_value) + { + char *msg1 = + xasprintf (_("nplurals = %lu"), nplurals_value); + char *msg2 = + xasprintf (ngettext ("but some messages have one plural form", + "but some messages have %lu plural forms", + max_nplurals), + max_nplurals); + po_xerror2 (PO_SEVERITY_ERROR, + header, NULL, 0, 0, false, msg1, + max_pos, NULL, 0, 0, false, msg2); + free (msg2); + free (msg1); + seen_errors++; + } + /* The only valid case is max_nplurals <= n <= min_nplurals, + which means either has_plural == NULL or + max_nplurals = n = min_nplurals. */ + } + } + else + goto no_plural; + } + else + { + if (has_plural != NULL) + { + po_xerror (PO_SEVERITY_ERROR, has_plural, NULL, 0, 0, false, + _("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\"")); + seen_errors++; + } + no_plural: + /* By default, the Germanic formula (n != 1) is used. */ + distribution.expr = &germanic_plural; + { + unsigned char *array = XCALLOC (2, unsigned char); + array[1] = 1; + distribution.often = array; + } + distribution.often_length = 2; + distribution.histogram = plural_expression_histogram; + } + + /* distribution is not needed if we report errors. + Also, if there was an error due to max_nplurals > nplurals_value, + we must not use distribution because we would be doing out-of-bounds + array accesses. */ + if (seen_errors > 0) + free ((unsigned char *) distribution.often); + else + *distributionp = distribution; + + return seen_errors; +} + + +/* Signal an error when checking format strings. */ +static const message_ty *curr_mp; +static lex_pos_ty curr_msgid_pos; +static void +formatstring_error_logger (const char *format, ...) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ > 2) + __attribute__ ((__format__ (__printf__, 1, 2))) +#endif +; +static void +formatstring_error_logger (const char *format, ...) +{ + va_list args; + char *msg; + + va_start (args, format); + if (vasprintf (&msg, format, args) < 0) + error (EXIT_FAILURE, 0, _("memory exhausted")); + va_end (args); + po_xerror (PO_SEVERITY_ERROR, + curr_mp, curr_msgid_pos.file_name, curr_msgid_pos.line_number, + (size_t)(-1), false, msg); + free (msg); +} + + +/* Perform miscellaneous checks on a message. + PLURAL_DISTRIBUTION is either NULL or an array of nplurals elements, + PLURAL_DISTRIBUTION[j] being true if the value j appears to be assumed + infinitely often by the plural formula. + PLURAL_DISTRIBUTION_LENGTH is the length of the PLURAL_DISTRIBUTION + array. */ +static int +check_pair (const message_ty *mp, + const char *msgid, + const lex_pos_ty *msgid_pos, + const char *msgid_plural, + const char *msgstr, size_t msgstr_len, + const enum is_format is_format[NFORMATS], + int check_newlines, + int check_format_strings, + const struct plural_distribution *distribution, + int check_compatibility, + int check_accelerators, char accelerator_char) +{ + int seen_errors; + int has_newline; + unsigned int j; + + /* If the msgid string is empty we have the special entry reserved for + information about the translation. */ + if (msgid[0] == '\0') + return 0; + + seen_errors = 0; + + if (check_newlines) + { + /* Test 1: check whether all or none of the strings begin with a '\n'. */ + has_newline = (msgid[0] == '\n'); +#define TEST_NEWLINE(p) (p[0] == '\n') + if (msgid_plural != NULL) + { + const char *p; + + if (TEST_NEWLINE(msgid_plural) != has_newline) + { + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, _("\ +'msgid' and 'msgid_plural' entries do not both begin with '\\n'")); + seen_errors++; + } + for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++) + if (TEST_NEWLINE(p) != has_newline) + { + char *msg = + xasprintf (_("\ +'msgid' and 'msgstr[%u]' entries do not both begin with '\\n'"), j); + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, msg); + free (msg); + seen_errors++; + } + } + else + { + if (TEST_NEWLINE(msgstr) != has_newline) + { + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, _("\ +'msgid' and 'msgstr' entries do not both begin with '\\n'")); + seen_errors++; + } + } +#undef TEST_NEWLINE + + /* Test 2: check whether all or none of the strings end with a '\n'. */ + has_newline = (msgid[strlen (msgid) - 1] == '\n'); +#define TEST_NEWLINE(p) (p[0] != '\0' && p[strlen (p) - 1] == '\n') + if (msgid_plural != NULL) + { + const char *p; + + if (TEST_NEWLINE(msgid_plural) != has_newline) + { + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, _("\ +'msgid' and 'msgid_plural' entries do not both end with '\\n'")); + seen_errors++; + } + for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++) + if (TEST_NEWLINE(p) != has_newline) + { + char *msg = + xasprintf (_("\ +'msgid' and 'msgstr[%u]' entries do not both end with '\\n'"), j); + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, msg); + free (msg); + seen_errors++; + } + } + else + { + if (TEST_NEWLINE(msgstr) != has_newline) + { + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, _("\ +'msgid' and 'msgstr' entries do not both end with '\\n'")); + seen_errors++; + } + } +#undef TEST_NEWLINE + } + + if (check_compatibility && msgid_plural != NULL) + { + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, _("\ +plural handling is a GNU gettext extension")); + seen_errors++; + } + + if (check_format_strings) + /* Test 3: Check whether both formats strings contain the same number + of format specifications. */ + { + curr_mp = mp; + curr_msgid_pos = *msgid_pos; + seen_errors += + check_msgid_msgstr_format (msgid, msgid_plural, msgstr, msgstr_len, + is_format, mp->range, distribution, + formatstring_error_logger); + } + + if (check_accelerators && msgid_plural == NULL) + /* Test 4: Check that if msgid is a menu item with a keyboard accelerator, + the msgstr has an accelerator as well. A keyboard accelerator is + designated by an immediately preceding '&'. We cannot check whether + two accelerators collide, only whether the translator has bothered + thinking about them. */ + { + const char *p; + + /* We are only interested in msgids that contain exactly one '&'. */ + p = strchr (msgid, accelerator_char); + if (p != NULL && strchr (p + 1, accelerator_char) == NULL) + { + /* Count the number of '&' in msgstr, but ignore '&&'. */ + unsigned int count = 0; + + for (p = msgstr; (p = strchr (p, accelerator_char)) != NULL; p++) + if (p[1] == accelerator_char) + p++; + else + count++; + + if (count == 0) + { + char *msg = + xasprintf (_("msgstr lacks the keyboard accelerator mark '%c'"), + accelerator_char); + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, msg); + free (msg); + seen_errors++; + } + else if (count > 1) + { + char *msg = + xasprintf (_("msgstr has too many keyboard accelerator marks '%c'"), + accelerator_char); + po_xerror (PO_SEVERITY_ERROR, + mp, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, msg); + free (msg); + seen_errors++; + } + } + } + + return seen_errors; +} + + +/* Perform miscellaneous checks on a header entry. */ +static int +check_header_entry (const message_ty *mp, const char *msgstr_string) +{ + static const char *required_fields[] = + { + "Project-Id-Version", "PO-Revision-Date", "Last-Translator", + "Language-Team", "MIME-Version", "Content-Type", + "Content-Transfer-Encoding", + /* These are recommended but not yet required. */ + "Language" + }; + static const char *default_values[] = + { + "PACKAGE VERSION", "YEAR-MO-DA HO:MI+ZONE", "FULL NAME ", "LANGUAGE ", NULL, + "text/plain; charset=CHARSET", "ENCODING", + "" + }; + const size_t nfields = SIZEOF (required_fields); + /* FIXME: We could check if a required header field is missing and + report it as error. However, it's could be too rigorous and + break backward compatibility. */ +#if 0 + const size_t nrequiredfields = nfields - 1; +#endif + int seen_errors = 0; + int cnt; + + for (cnt = 0; cnt < nfields; ++cnt) + { +#if 0 + int severity = + (cnt < nrequiredfields ? PO_SEVERITY_ERROR : PO_SEVERITY_WARNING); +#else + int severity = + PO_SEVERITY_WARNING; +#endif + const char *field = required_fields[cnt]; + size_t len = strlen (field); + const char *line; + + for (line = msgstr_string; *line != '\0'; ) + { + if (strncmp (line, field, len) == 0 && line[len] == ':') + { + const char *p = line + len + 1; + + /* Test whether the field's value, starting at p, is the default + value. */ + if (*p == ' ') + p++; + if (default_values[cnt] != NULL + && strncmp (p, default_values[cnt], + strlen (default_values[cnt])) == 0) + { + p += strlen (default_values[cnt]); + if (*p == '\0' || *p == '\n') + { + char *msg = + xasprintf (_("header field '%s' still has the initial default value\n"), + field); + po_xerror (severity, mp, NULL, 0, 0, true, msg); + free (msg); + if (severity == PO_SEVERITY_ERROR) + seen_errors++; + } + } + break; + } + line = strchrnul (line, '\n'); + if (*line == '\n') + line++; + } + if (*line == '\0') + { + char *msg = + xasprintf (_("header field '%s' missing in header\n"), + field); + po_xerror (severity, mp, NULL, 0, 0, true, msg); + free (msg); + if (severity == PO_SEVERITY_ERROR) + seen_errors++; + } + } + return seen_errors; +} + + +/* Perform all checks on a non-obsolete message. + Return the number of errors that were seen. */ +int +check_message (const message_ty *mp, + const lex_pos_ty *msgid_pos, + int check_newlines, + int check_format_strings, + const struct plural_distribution *distribution, + int check_header, + int check_compatibility, + int check_accelerators, char accelerator_char) +{ + int seen_errors = 0; + + if (check_header && is_header (mp)) + seen_errors += check_header_entry (mp, mp->msgstr); + + seen_errors += check_pair (mp, + mp->msgid, msgid_pos, mp->msgid_plural, + mp->msgstr, mp->msgstr_len, + mp->is_format, + check_newlines, + check_format_strings, + distribution, + check_compatibility, + check_accelerators, accelerator_char); + return seen_errors; +} + + +/* Perform all checks on a message list. + Return the number of errors that were seen. */ +int +check_message_list (message_list_ty *mlp, + int ignore_untranslated_messages, + int ignore_fuzzy_messages, + int check_newlines, + int check_format_strings, + int check_header, + int check_compatibility, + int check_accelerators, char accelerator_char) +{ + int seen_errors = 0; + struct plural_distribution distribution; + size_t j; + + distribution.expr = NULL; + distribution.often = NULL; + distribution.often_length = 0; + distribution.histogram = NULL; + + if (check_header) + seen_errors += check_plural (mlp, ignore_untranslated_messages, + ignore_fuzzy_messages, &distribution); + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (!mp->obsolete + && !(ignore_untranslated_messages && mp->msgstr[0] == '\0') + && !(ignore_fuzzy_messages && (mp->is_fuzzy && !is_header (mp)))) + seen_errors += check_message (mp, &mp->pos, + check_newlines, + check_format_strings, + &distribution, + check_header, check_compatibility, + check_accelerators, accelerator_char); + } + + return seen_errors; +} diff --git a/gettext-tools/src/msgl-check.h b/gettext-tools/src/msgl-check.h new file mode 100644 index 0000000..f03300c --- /dev/null +++ b/gettext-tools/src/msgl-check.h @@ -0,0 +1,68 @@ +/* Checking of messages in PO files. + Copyright (C) 2005, 2008, 2010 Free Software Foundation, Inc. + Written by Bruno Haible , 2005. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_CHECK_H +#define _MSGL_CHECK_H 1 + +#include "message.h" +#include "pos.h" +#include "plural-eval.h" +#include "plural-distrib.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Check the values returned by plural_eval. + Signals the errors through po_xerror. + Return the number of errors that were seen. + If no errors, returns in *DISTRIBUTION information about the plural_eval + values distribution. */ +extern int check_plural_eval (const struct expression *plural_expr, + unsigned long nplurals_value, + const message_ty *header, + struct plural_distribution *distribution); + +/* Perform all checks on a non-obsolete message. */ +extern int check_message (const message_ty *mp, + const lex_pos_ty *msgid_pos, + int check_newlines, + int check_format_strings, + const struct plural_distribution *distribution, + int check_header, + int check_compatibility, + int check_accelerators, char accelerator_char); + +/* Perform all checks on a message list. + Return the number of errors that were seen. */ +extern int check_message_list (message_list_ty *mlp, + int ignore_untranslated_messages, + int ignore_fuzzy_messages, + int check_newlines, + int check_format_strings, + int check_header, + int check_compatibility, + int check_accelerators, char accelerator_char); + + +#ifdef __cplusplus +} +#endif + +#endif /* _MSGL_CHECK_H */ diff --git a/gettext-tools/src/msgl-english.c b/gettext-tools/src/msgl-english.c new file mode 100644 index 0000000..69d95a9 --- /dev/null +++ b/gettext-tools/src/msgl-english.c @@ -0,0 +1,70 @@ +/* Message translation initialization for English. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "msgl-english.h" + +#include + +#include "xalloc.h" + + +msgdomain_list_ty * +msgdomain_list_english (msgdomain_list_ty *mdlp) +{ + size_t j, k; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (mp->msgid_plural == NULL) + { + if (mp->msgstr_len == 1 && mp->msgstr[0] == '\0') + { + mp->msgstr = mp->msgid; /* no need for xstrdup */ + mp->msgstr_len = strlen (mp->msgid) + 1; + } + } + else + { + if (mp->msgstr_len == 2 + && mp->msgstr[0] == '\0' && mp->msgstr[1] == '\0') + { + size_t len0 = strlen (mp->msgid) + 1; + size_t len1 = strlen (mp->msgid_plural) + 1; + char *cp = XNMALLOC (len0 + len1, char); + memcpy (cp, mp->msgid, len0); + memcpy (cp + len0, mp->msgid_plural, len1); + mp->msgstr = cp; + mp->msgstr_len = len0 + len1; + } + } + } + } + + return mdlp; +} diff --git a/gettext-tools/src/msgl-english.h b/gettext-tools/src/msgl-english.h new file mode 100644 index 0000000..ec77d2a --- /dev/null +++ b/gettext-tools/src/msgl-english.h @@ -0,0 +1,38 @@ +/* Message translation initialization for English. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_ENGLISH_H +#define _MSGL_ENGLISH_H + +#include "message.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +extern msgdomain_list_ty * + msgdomain_list_english (msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MSGL_ENGLISH_H */ diff --git a/gettext-tools/src/msgl-equal.c b/gettext-tools/src/msgl-equal.c new file mode 100644 index 0000000..3c9efad --- /dev/null +++ b/gettext-tools/src/msgl-equal.c @@ -0,0 +1,250 @@ +/* Message list test for equality. + Copyright (C) 2001-2002, 2005-2006, 2008 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "msgl-equal.h" + +#include +#include + + +static inline bool +msgstr_equal (const char *msgstr1, size_t msgstr1_len, + const char *msgstr2, size_t msgstr2_len) +{ + return (msgstr1_len == msgstr2_len + && memcmp (msgstr1, msgstr2, msgstr1_len) == 0); +} + +static bool +msgstr_equal_ignoring_potcdate (const char *msgstr1, size_t msgstr1_len, + const char *msgstr2, size_t msgstr2_len) +{ + const char *msgstr1_end = msgstr1 + msgstr1_len; + const char *msgstr2_end = msgstr2 + msgstr2_len; + const char *ptr1; + const char *ptr2; + const char *const field = "POT-Creation-Date:"; + const ptrdiff_t fieldlen = sizeof ("POT-Creation-Date:") - 1; + + /* Search for the occurrence of field in msgstr1. */ + for (ptr1 = msgstr1;;) + { + if (msgstr1_end - ptr1 < fieldlen) + { + ptr1 = NULL; + break; + } + if (memcmp (ptr1, field, fieldlen) == 0) + break; + ptr1 = (const char *) memchr (ptr1, '\n', msgstr1_end - ptr1); + if (ptr1 == NULL) + break; + ptr1++; + } + + /* Search for the occurrence of field in msgstr2. */ + for (ptr2 = msgstr2;;) + { + if (msgstr2_end - ptr2 < fieldlen) + { + ptr2 = NULL; + break; + } + if (memcmp (ptr2, field, fieldlen) == 0) + break; + ptr2 = (const char *) memchr (ptr2, '\n', msgstr2_end - ptr2); + if (ptr2 == NULL) + break; + ptr2++; + } + + if (ptr1 == NULL) + { + if (ptr2 == NULL) + return msgstr_equal (msgstr1, msgstr1_len, msgstr2, msgstr2_len); + } + else + { + if (ptr2 != NULL) + { + /* Compare, ignoring the lines starting at ptr1 and ptr2. */ + if (msgstr_equal (msgstr1, ptr1 - msgstr1, msgstr2, ptr2 - msgstr2)) + { + ptr1 = (const char *) memchr (ptr1, '\n', msgstr1_end - ptr1); + if (ptr1 == NULL) + ptr1 = msgstr1_end; + + ptr2 = (const char *) memchr (ptr2, '\n', msgstr2_end - ptr2); + if (ptr2 == NULL) + ptr2 = msgstr2_end; + + return msgstr_equal (ptr1, msgstr1_end - ptr1, + ptr2, msgstr2_end - ptr2); + } + } + } + return false; +} + +static inline bool +pos_equal (const lex_pos_ty *pos1, const lex_pos_ty *pos2) +{ + return ((pos1->file_name == pos2->file_name + || strcmp (pos1->file_name, pos2->file_name) == 0) + && pos1->line_number == pos2->line_number); +} + +bool +string_list_equal (const string_list_ty *slp1, const string_list_ty *slp2) +{ + size_t i, i1, i2; + + i1 = (slp1 != NULL ? slp1->nitems : 0); + i2 = (slp2 != NULL ? slp2->nitems : 0); + if (i1 != i2) + return false; + for (i = 0; i < i1; i++) + if (strcmp (slp1->item[i], slp2->item[i]) != 0) + return false; + return true; +} + +bool +message_equal (const message_ty *mp1, const message_ty *mp2, + bool ignore_potcdate) +{ + size_t i, i1, i2; + + if (!(mp1->msgctxt != NULL + ? mp2->msgctxt != NULL && strcmp (mp1->msgctxt, mp2->msgctxt) == 0 + : mp2->msgctxt == NULL)) + return false; + + if (strcmp (mp1->msgid, mp2->msgid) != 0) + return false; + + if (!(mp1->msgid_plural != NULL + ? mp2->msgid_plural != NULL + && strcmp (mp1->msgid_plural, mp2->msgid_plural) == 0 + : mp2->msgid_plural == NULL)) + return false; + + if (is_header (mp1) && ignore_potcdate + ? !msgstr_equal_ignoring_potcdate (mp1->msgstr, mp1->msgstr_len, + mp2->msgstr, mp2->msgstr_len) + : !msgstr_equal (mp1->msgstr, mp1->msgstr_len, + mp2->msgstr, mp2->msgstr_len)) + return false; + + if (!pos_equal (&mp1->pos, &mp2->pos)) + return false; + + if (!string_list_equal (mp1->comment, mp2->comment)) + return false; + + if (!string_list_equal (mp1->comment_dot, mp2->comment_dot)) + return false; + + i1 = mp1->filepos_count; + i2 = mp2->filepos_count; + if (i1 != i2) + return false; + for (i = 0; i < i1; i++) + if (!pos_equal (&mp1->filepos[i], &mp2->filepos[i])) + return false; + + if (mp1->is_fuzzy != mp2->is_fuzzy) + return false; + + for (i = 0; i < NFORMATS; i++) + if (mp1->is_format[i] != mp2->is_format[i]) + return false; + + if (!(mp1->range.min == mp2->range.min && mp1->range.max == mp2->range.max)) + return false; + + if (!(mp1->prev_msgctxt != NULL + ? mp2->prev_msgctxt != NULL + && strcmp (mp1->prev_msgctxt, mp2->prev_msgctxt) == 0 + : mp2->prev_msgctxt == NULL)) + return false; + + if (!(mp1->prev_msgid != NULL + ? mp2->prev_msgid != NULL + && strcmp (mp1->prev_msgid, mp2->prev_msgid) == 0 + : mp2->prev_msgid == NULL)) + return false; + + if (!(mp1->prev_msgid_plural != NULL + ? mp2->prev_msgid_plural != NULL + && strcmp (mp1->prev_msgid_plural, mp2->prev_msgid_plural) == 0 + : mp2->prev_msgid_plural == NULL)) + return false; + + if (mp1->obsolete != mp2->obsolete) + return false; + + return true; +} + +bool +message_list_equal (const message_list_ty *mlp1, const message_list_ty *mlp2, + bool ignore_potcdate) +{ + size_t i, i1, i2; + + i1 = mlp1->nitems; + i2 = mlp2->nitems; + if (i1 != i2) + return false; + for (i = 0; i < i1; i++) + if (!message_equal (mlp1->item[i], mlp2->item[i], ignore_potcdate)) + return false; + return true; +} + +static inline bool +msgdomain_equal (const msgdomain_ty *mdp1, const msgdomain_ty *mdp2, + bool ignore_potcdate) +{ + return (strcmp (mdp1->domain, mdp2->domain) == 0 + && message_list_equal (mdp1->messages, mdp2->messages, + ignore_potcdate)); +} + +bool +msgdomain_list_equal (const msgdomain_list_ty *mdlp1, + const msgdomain_list_ty *mdlp2, + bool ignore_potcdate) +{ + size_t i, i1, i2; + + i1 = mdlp1->nitems; + i2 = mdlp2->nitems; + if (i1 != i2) + return false; + for (i = 0; i < i1; i++) + if (!msgdomain_equal (mdlp1->item[i], mdlp2->item[i], ignore_potcdate)) + return false; + return true; +} diff --git a/gettext-tools/src/msgl-equal.h b/gettext-tools/src/msgl-equal.h new file mode 100644 index 0000000..6497a7f --- /dev/null +++ b/gettext-tools/src/msgl-equal.h @@ -0,0 +1,56 @@ +/* Message list test for equality. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_EQUAL_H +#define _MSGL_EQUAL_H + +#include + +#include "message.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +extern bool + string_list_equal (const string_list_ty *slp1, + const string_list_ty *slp2); + +/* Test whether the written representation of two messages / message lists + would be the same. */ + +extern bool + message_equal (const message_ty *mp1, const message_ty *mp2, + bool ignore_potcdate); +extern bool + message_list_equal (const message_list_ty *mlp1, + const message_list_ty *mlp2, + bool ignore_potcdate); +extern bool + msgdomain_list_equal (const msgdomain_list_ty *mdlp1, + const msgdomain_list_ty *mdlp2, + bool ignore_potcdate); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MSGL_EQUAL_H */ diff --git a/gettext-tools/src/msgl-fsearch.c b/gettext-tools/src/msgl-fsearch.c new file mode 100644 index 0000000..d2aa865 --- /dev/null +++ b/gettext-tools/src/msgl-fsearch.c @@ -0,0 +1,668 @@ +/* Fast fuzzy searching among messages. + Copyright (C) 2006, 2008, 2011 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "msgl-fsearch.h" + +#include +#include + +#include "xalloc.h" +#include "po-charset.h" + + +/* Fuzzy searching of L strings in a large set of N messages (assuming + they have all the same small size) takes O(L * N) when using repeated + fstrcmp() calls. When for example L = 800 and N = 69000, this is slow. + + So we preprocess the set of N messages, yielding a data structure + that allows to select the similar messages for any given string, and + apply fstrcmp() only to the search result. This allows to reduce the + time to something between O(L * 1) and O(L * N) - depending on the + structure of the strings. In the example with L = 800 and N = 69000, + memory use increases by a factor of 2 and the time decreases from + 9068 s to 230 s. + + The data structure is a hash table mapping each n-gram (here with n=4) + occurring in the message strings to the list of messages that contains + it. For example, if the message list is + [0] "close" + [1] "losers" + the index is a hash table mapping + "clos" -> { 0 } + "lose" -> { 0, 1 } + "oser" -> { 1 } + "sers" -> { 1 } + Searching the similar messages to, say, "closest", is done by looking up + all its 4-grams in the hash table and summing up the results: + "clos" -> { 0 } + "lose" -> { 0, 1 } + "oses" -> {} + "sest" -> {} + => total: { 2x0, 1x1 } + The list is sorted according to decreasing frequency: { 0, 1, ... } + and only the first few messages from this frequency list are passed to + fstrcmp(). + + The n-gram similarity and the fstrcmp() similarity are quite different + metrics. For example, "close" and "c l o s e" have no n-grams in common + (even for n as small as n = 2); however, fstrcmp() will find that they + are quite similar (= 0.71). Conversely, "AAAA BBBB ... ZZZZ" and + "ZZZZ ... BBBB AAAA" have many 4-grams in common, but their fstrcmp() is + only 0.02. Also, repeated n-grams don't have the same effect on the + two similarity measures. But all this doesn't matter much in practice. + + We chose n = 4 because for alphabetic languages, with n = 3 the occurrence + lists are likely too long. (Well, with ideographic languages like Chinese, + n = 3 might actually be quite good. Anyway, n = 4 is not bad in this case + either.) + + The units are characters in the current encoding. Not just bytes, + because 4 consecutive bytes in UTF-8 or GB18030 don't mean much. */ + +/* Each message is represented by its index in the message list. */ +typedef unsigned int index_ty; + +/* An index list has its allocated size and length tacked at the front. + The indices are sorted in ascending order. */ +typedef index_ty *index_list_ty; +#define IL_ALLOCATED 0 +#define IL_LENGTH 1 + +/* Create a new index list containing only a given index. */ +static inline index_list_ty +new_index (index_ty idx) +{ + index_ty *list = XNMALLOC (2 + 1, index_ty); + list[IL_ALLOCATED] = 1; + list[IL_LENGTH] = 1; + list[2] = idx; + + return list; +} + +/* Add a given index, greater or equal than all previous indices, to an + index list. + Return the new index list, if it had to be reallocated, or NULL if it + didn't change. */ +static inline index_list_ty +addlast_index (index_list_ty list, index_ty idx) +{ + index_list_ty result; + size_t length = list[IL_LENGTH]; + + /* Look whether it should be inserted. */ + if (length > 0 && list[2 + (length - 1)] == idx) + return NULL; + + /* Now make room for one more list element. */ + result = NULL; + if (length == list[IL_ALLOCATED]) + { + size_t new_allocated = 2 * length - (length >> 6); + list = (index_ty *) xrealloc (list, (2 + new_allocated) * sizeof (index_ty)); + list[IL_ALLOCATED] = new_allocated; + result = list; + } + list[2 + length] = idx; + list[IL_LENGTH] = length + 1; + return result; +} + +/* Add a given index to an index list. + Return the new index list, if it had to be reallocated, or NULL if it + didn't change. */ +static inline index_list_ty +add_index (index_list_ty list, index_ty idx) +{ + index_list_ty result; + size_t length = list[IL_LENGTH]; + + /* Look where it should be inserted. */ + size_t lo = 0; + size_t hi = length; + while (lo < hi) + { + /* Here we know that idx must be inserted at a position >= lo, <= hi. */ + size_t mid = (lo + hi) / 2; /* lo <= mid < hi */ + index_ty val = list[2 + mid]; + if (val < idx) + lo = mid + 1; + else if (val > idx) + hi = mid; + else + return NULL; + } + + /* Now make room for one more list element. */ + result = NULL; + if (length == list[IL_ALLOCATED]) + { + size_t new_allocated = 2 * length - (length >> 6); + list = (index_ty *) xrealloc (list, (2 + new_allocated) * sizeof (index_ty)); + list[IL_ALLOCATED] = new_allocated; + result = list; + } + list[IL_LENGTH] = length + 1; + for (; length > hi; length--) + list[2 + length] = list[1 + length]; + list[2 + length] = idx; + return result; +} + +/* We use 4-grams, therefore strings with less than 4 characters cannot be + handled through the 4-grams table and need to be handled specially. + Since every character occupies at most 4 bytes (see po-charset.c), + this means the size of such short strings is bounded by: */ +#define SHORT_STRING_MAX_CHARACTERS (4 - 1) +#define SHORT_STRING_MAX_BYTES (SHORT_STRING_MAX_CHARACTERS * 4) + +/* Such short strings are handled by direct comparison with all messages + of appropriate size. Note that for two strings of length 0 <= l1 <= l2, + fstrcmp() is <= 2 * l1 / (l1 + l2). Since we are only interested in + fstrcmp() values >= FUZZY_THRESHOLD, we can for a given string of length l + limit the search to lengths l' in the range + l / (2 / FUZZY_THRESHOLD - 1) <= l' <= l * (2 / FUZZY_THRESHOLD - 1) + Thus we need the list of the short strings up to length: */ +#if !defined __SUNPRO_C +# define SHORT_MSG_MAX (int) (SHORT_STRING_MAX_BYTES * (2 / FUZZY_THRESHOLD - 1)) +#else +/* Sun C on Solaris 8 cannot compute this constant expression. */ +# define SHORT_MSG_MAX 28 +#endif + +/* A fuzzy index contains a hash table mapping all n-grams to their + occurrences list. */ +struct message_fuzzy_index_ty +{ + message_ty **messages; + character_iterator_t iterator; + hash_table gram4; + size_t firstfew; + message_list_ty **short_messages; +}; + +/* Allocate a fuzzy index corresponding to a given list of messages. + The list of messages and the msgctxt and msgid fields of the messages + inside it must not be modified while the returned fuzzy index is in use. */ +message_fuzzy_index_ty * +message_fuzzy_index_alloc (const message_list_ty *mlp, + const char *canon_charset) +{ + message_fuzzy_index_ty *findex = XMALLOC (message_fuzzy_index_ty); + size_t count = mlp->nitems; + size_t j; + size_t l; + + findex->messages = mlp->item; + findex->iterator = po_charset_character_iterator (canon_charset); + + /* Setup hash table. */ + if (hash_init (&findex->gram4, 10 * count) < 0) + xalloc_die (); + for (j = 0; j < count; j++) + { + message_ty *mp = mlp->item[j]; + + if (mp->msgstr != NULL && mp->msgstr[0] != '\0') + { + const char *str = mp->msgid; + + /* Let p0 < p1 < p2 < p3 < p4 walk through the string. */ + const char *p0 = str; + if (*p0 != '\0') + { + const char *p1 = p0 + findex->iterator (p0); + if (*p1 != '\0') + { + const char *p2 = p1 + findex->iterator (p1); + if (*p2 != '\0') + { + const char *p3 = p2 + findex->iterator (p2); + if (*p3 != '\0') + { + const char *p4 = p3 + findex->iterator (p3); + for (;;) + { + /* The segment from p0 to p4 is a 4-gram of + characters. Add a hash table entry that maps + it to the index j, or extend the existing + hash table entry accordingly. */ + void *found; + + if (hash_find_entry (&findex->gram4, p0, p4 - p0, + &found) == 0) + { + index_list_ty list = (index_list_ty) found; + list = addlast_index (list, j); + if (list != NULL) + hash_set_value (&findex->gram4, p0, p4 - p0, + list); + } + else + hash_insert_entry (&findex->gram4, p0, p4 - p0, + new_index (j)); + + /* Advance. */ + if (*p4 == '\0') + break; + p0 = p1; + p1 = p2; + p2 = p3; + p3 = p4; + p4 = p4 + findex->iterator (p4); + } + } + } + } + } + } + } + + /* Shrink memory used by the hash table. */ + { + void *iter; + const void *key; + size_t keylen; + void **valuep; + + iter = NULL; + while (hash_iterate_modify (&findex->gram4, &iter, &key, &keylen, &valuep) + == 0) + { + index_list_ty list = (index_list_ty) *valuep; + index_ty length = list[IL_LENGTH]; + + if (length < list[IL_ALLOCATED]) + { + list[IL_ALLOCATED] = length; + *valuep = xrealloc (list, (2 + length) * sizeof (index_ty)); + } + } + } + + findex->firstfew = (int) sqrt ((double) count); + if (findex->firstfew < 10) + findex->firstfew = 10; + + /* Setup lists of short messages. */ + findex->short_messages = XNMALLOC (SHORT_MSG_MAX + 1, message_list_ty *); + for (l = 0; l <= SHORT_MSG_MAX; l++) + findex->short_messages[l] = message_list_alloc (false); + for (j = 0; j < count; j++) + { + message_ty *mp = mlp->item[j]; + + if (mp->msgstr != NULL && mp->msgstr[0] != '\0') + { + const char *str = mp->msgid; + size_t len = strlen (str); + + if (len <= SHORT_MSG_MAX) + message_list_append (findex->short_messages[len], mp); + } + } + + /* Shrink memory used by the lists of short messages. */ + for (l = 0; l <= SHORT_MSG_MAX; l++) + { + message_list_ty *mlp = findex->short_messages[l]; + + if (mlp->nitems < mlp->nitems_max) + { + mlp->nitems_max = mlp->nitems; + mlp->item = + (message_ty **) + xrealloc (mlp->item, mlp->nitems_max * sizeof (message_ty *)); + } + } + + return findex; +} + +/* An index with multiplicity. */ +struct mult_index +{ + index_ty index; + unsigned int count; +}; + +/* A list of indices with multiplicity. + The indices are sorted in ascending order. */ +struct mult_index_list +{ + struct mult_index *item; + size_t nitems; + size_t nitems_max; + /* Work area. */ + struct mult_index *item2; + size_t nitems2_max; +}; + +/* Initialize an empty list of indices with multiplicity. */ +static inline void +mult_index_list_init (struct mult_index_list *accu) +{ + accu->nitems = 0; + accu->nitems_max = 0; + accu->item = NULL; + accu->nitems2_max = 0; + accu->item2 = NULL; +} + +/* Add an index list to a list of indices with multiplicity. */ +static inline void +mult_index_list_accumulate (struct mult_index_list *accu, index_list_ty list) +{ + size_t len1 = accu->nitems; + size_t len2 = list[IL_LENGTH]; + size_t need = len1 + len2; + struct mult_index *ptr1; + struct mult_index *ptr1_end; + index_ty *ptr2; + index_ty *ptr2_end; + struct mult_index *destptr; + + /* Make the work area large enough. */ + if (accu->nitems2_max < need) + { + size_t new_max = 2 * accu->nitems2_max + 1; + + if (new_max < need) + new_max = need; + if (accu->item2 != NULL) + free (accu->item2); + accu->item2 = XNMALLOC (new_max, struct mult_index); + accu->nitems2_max = new_max; + } + + /* Make a linear pass through accu and list simultaneously. */ + ptr1 = accu->item; + ptr1_end = ptr1 + len1; + ptr2 = list + 2; + ptr2_end = ptr2 + len2; + destptr = accu->item2; + while (ptr1 < ptr1_end && ptr2 < ptr2_end) + { + if (ptr1->index < *ptr2) + { + *destptr = *ptr1; + ptr1++; + } + else if (ptr1->index > *ptr2) + { + destptr->index = *ptr2; + destptr->count = 1; + ptr2++; + } + else /* ptr1->index == list[2 + i2] */ + { + destptr->index = ptr1->index; + destptr->count = ptr1->count + 1; + ptr1++; + ptr2++; + } + destptr++; + } + while (ptr1 < ptr1_end) + { + *destptr = *ptr1; + ptr1++; + destptr++; + } + while (ptr2 < ptr2_end) + { + destptr->index = *ptr2; + destptr->count = 1; + ptr2++; + destptr++; + } + + /* Swap accu->item and accu->item2. */ + { + struct mult_index *dest = accu->item2; + size_t dest_max = accu->nitems2_max; + + accu->item2 = accu->item; + accu->nitems2_max = accu->nitems_max; + + accu->item = dest; + accu->nitems = destptr - dest; + accu->nitems_max = dest_max; + } +} + +/* Compares two indices with multiplicity, according to their multiplicity. */ +static int +mult_index_compare (const void *p1, const void *p2) +{ + const struct mult_index *ptr1 = (const struct mult_index *) p1; + const struct mult_index *ptr2 = (const struct mult_index *) p2; + + if (ptr1->count < ptr2->count) + return 1; + if (ptr1->count > ptr2->count) + return -1; + /* For reproduceable results, also take into account the indices. */ + if (ptr1->index > ptr2->index) + return 1; + if (ptr1->index < ptr2->index) + return -1; + return 0; +} + +/* Sort a list of indices with multiplicity according to decreasing + multiplicity. */ +static inline void +mult_index_list_sort (struct mult_index_list *accu) +{ + if (accu->nitems > 1) + qsort (accu->item, accu->nitems, sizeof (struct mult_index), + mult_index_compare); +} + +/* Frees a list of indices with multiplicity. */ +static inline void +mult_index_list_free (struct mult_index_list *accu) +{ + if (accu->item != NULL) + free (accu->item); + if (accu->item2 != NULL) + free (accu->item2); +} + +/* Find a good match for the given msgctxt and msgid in the given fuzzy index. + The match does not need to be optimal. + Ignore matches for which the fuzzy_search_goal_function is < LOWER_BOUND. + LOWER_BOUND must be >= FUZZY_THRESHOLD. + If HEURISTIC is true, only the few best messages among the list - according + to a certain heuristic - are considered. If HEURISTIC is false, all + messages with a fuzzy_search_goal_function > FUZZY_THRESHOLD are considered, + like in message_list_search_fuzzy (except that in ambiguous cases where + several best matches exist, message_list_search_fuzzy chooses the one with + the smallest index whereas message_fuzzy_index_search makes a better + choice). */ +message_ty * +message_fuzzy_index_search (message_fuzzy_index_ty *findex, + const char *msgctxt, const char *msgid, + double lower_bound, + bool heuristic) +{ + const char *str = msgid; + + /* Let p0 < p1 < p2 < p3 < p4 walk through the string. */ + const char *p0 = str; + if (*p0 != '\0') + { + const char *p1 = p0 + findex->iterator (p0); + if (*p1 != '\0') + { + const char *p2 = p1 + findex->iterator (p1); + if (*p2 != '\0') + { + const char *p3 = p2 + findex->iterator (p2); + if (*p3 != '\0') + { + const char *p4 = p3 + findex->iterator (p3); + struct mult_index_list accu; + + mult_index_list_init (&accu); + for (;;) + { + /* The segment from p0 to p4 is a 4-gram of + characters. Get the hash table entry containing + a list of indices, and add it to the accu. */ + void *found; + + if (hash_find_entry (&findex->gram4, p0, p4 - p0, + &found) == 0) + { + index_list_ty list = (index_list_ty) found; + mult_index_list_accumulate (&accu, list); + } + + /* Advance. */ + if (*p4 == '\0') + break; + p0 = p1; + p1 = p2; + p2 = p3; + p3 = p4; + p4 = p4 + findex->iterator (p4); + } + + /* Sort in decreasing count order. */ + mult_index_list_sort (&accu); + + /* Iterate over this sorted list, and maximize the + fuzzy_search_goal_function() result. + If HEURISTIC is true, take only the first few messages. + If HEURISTIC is false, consider all messages - to match + the behaviour of message_list_search_fuzzy -, but process + them in the order of the sorted list. This increases + the chances that the later calls to fstrcmp_bounded() (via + fuzzy_search_goal_function()) terminate quickly, thanks + to the best_weight which will be quite high already after + the first few messages. */ + { + size_t count; + struct mult_index *ptr; + message_ty *best_mp; + double best_weight; + + count = accu.nitems; + if (heuristic) + { + if (count > findex->firstfew) + count = findex->firstfew; + } + + best_weight = lower_bound; + best_mp = NULL; + for (ptr = accu.item; count > 0; ptr++, count--) + { + message_ty *mp = findex->messages[ptr->index]; + double weight = + fuzzy_search_goal_function (mp, msgctxt, msgid, + best_weight); + + if (weight > best_weight) + { + best_weight = weight; + best_mp = mp; + } + } + + mult_index_list_free (&accu); + + return best_mp; + } + } + } + } + } + + /* The string had less than 4 characters. */ + { + size_t l = strlen (str); + size_t lmin, lmax; + message_ty *best_mp; + double best_weight; + + if (!(l <= SHORT_STRING_MAX_BYTES)) + abort (); + + /* Walk through those short messages which have an appropriate length. + See the comment before SHORT_MSG_MAX. */ + lmin = (int) ceil (l / (2 / FUZZY_THRESHOLD - 1)); + lmax = (int) (l * (2 / FUZZY_THRESHOLD - 1)); + if (!(lmax <= SHORT_MSG_MAX)) + abort (); + + best_weight = lower_bound; + best_mp = NULL; + for (l = lmin; l <= lmax; l++) + { + message_list_ty *mlp = findex->short_messages[l]; + size_t j; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + double weight = + fuzzy_search_goal_function (mp, msgctxt, msgid, best_weight); + + if (weight > best_weight) + { + best_weight = weight; + best_mp = mp; + } + } + } + + return best_mp; + } +} + +/* Free a fuzzy index. */ +void +message_fuzzy_index_free (message_fuzzy_index_ty *findex) +{ + size_t l; + void *iter; + const void *key; + size_t keylen; + void *data; + + /* Free the short lists. */ + for (l = 0; l <= SHORT_MSG_MAX; l++) + message_list_free (findex->short_messages[l], 1); + free (findex->short_messages); + + /* Free the index lists occurring as values in the hash tables. */ + iter = NULL; + while (hash_iterate (&findex->gram4, &iter, &key, &keylen, &data) == 0) + free ((index_list_ty *) data); + /* Free the hash table itself. */ + hash_destroy (&findex->gram4); + + free (findex); +} diff --git a/gettext-tools/src/msgl-fsearch.h b/gettext-tools/src/msgl-fsearch.h new file mode 100644 index 0000000..c523d93 --- /dev/null +++ b/gettext-tools/src/msgl-fsearch.h @@ -0,0 +1,69 @@ +/* Fast fuzzy searching among messages. + Copyright (C) 2006, 2008 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_FSEARCH_H +#define _MSGL_FSEARCH_H 1 + +#include "message.h" + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* A fuzzy index is a data structure that corresponds to a set of messages, + allowing for fuzzy searching of a message. It is optimized for large sets + of messages. */ +typedef struct message_fuzzy_index_ty message_fuzzy_index_ty; + +/* Allocate a fuzzy index corresponding to a given list of messages. + The list of messages and the msgctxt and msgid fields of the messages + inside it must not be modified while the returned fuzzy index is in use. */ +extern message_fuzzy_index_ty * + message_fuzzy_index_alloc (const message_list_ty *mlp, + const char *canon_charset); + +/* Find a good match for the given msgctxt and msgid in the given fuzzy index. + The match does not need to be optimal. + Ignore matches for which the fuzzy_search_goal_function is < LOWER_BOUND. + LOWER_BOUND must be >= FUZZY_THRESHOLD. + If HEURISTIC is true, only the few best messages among the list - according + to a certain heuristic - are considered. If HEURISTIC is false, all + messages with a fuzzy_search_goal_function > FUZZY_THRESHOLD are considered, + like in message_list_search_fuzzy (except that in ambiguous cases where + several best matches exist, message_list_search_fuzzy chooses the one with + the smallest index whereas message_fuzzy_index_search makes a better + choice). */ +extern message_ty * + message_fuzzy_index_search (message_fuzzy_index_ty *findex, + const char *msgctxt, const char *msgid, + double lower_bound, + bool heuristic); + +/* Free a fuzzy index. */ +extern void + message_fuzzy_index_free (message_fuzzy_index_ty *findex); + + +#ifdef __cplusplus +} +#endif + +#endif /* _MSGL_FSEARCH_H */ diff --git a/gettext-tools/src/msgl-header.c b/gettext-tools/src/msgl-header.c new file mode 100644 index 0000000..d1dafdb --- /dev/null +++ b/gettext-tools/src/msgl-header.c @@ -0,0 +1,170 @@ +/* Message list header manipulation. + Copyright (C) 2007 Free Software Foundation, Inc. + Written by Bruno Haible , 2007. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "msgl-header.h" + +#include + +#include "xalloc.h" + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +void +msgdomain_list_set_header_field (msgdomain_list_ty *mdlp, + const char *field, const char *value) +{ + /* The known fields in their usual order. */ + static const struct + { + const char *name; + size_t len; + } + known_fields[] = + { + { "Project-Id-Version:", sizeof ("Project-Id-Version:") - 1 }, + { "Report-Msgid-Bugs-To:", sizeof ("Report-Msgid-Bugs-To:") - 1 }, + { "POT-Creation-Date:", sizeof ("POT-Creation-Date:") - 1 }, + { "PO-Revision-Date:", sizeof ("PO-Revision-Date:") - 1 }, + { "Last-Translator:", sizeof ("Last-Translator:") - 1 }, + { "Language-Team:", sizeof ("Language-Team:") - 1 }, + { "Language:", sizeof ("Language:") - 1 }, + { "MIME-Version:", sizeof ("MIME-Version:") - 1 }, + { "Content-Type:", sizeof ("Content-Type:") - 1 }, + { "Content-Transfer-Encoding:", + sizeof ("Content-Transfer-Encoding:") - 1 } + }; + size_t field_len; + int field_index; + size_t k, i; + + field_len = strlen (field); + + /* Search the field in known_fields[]. */ + field_index = -1; + for (k = 0; k < SIZEOF (known_fields); k++) + if (strcmp (known_fields[k].name, field) == 0) + { + field_index = k; + break; + } + + for (i = 0; i < mdlp->nitems; i++) + { + message_list_ty *mlp = mdlp->item[i]->messages; + size_t j; + + /* Search the header entry. */ + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + message_ty *mp = mlp->item[j]; + + /* Modify the header entry. */ + const char *header = mp->msgstr; + char *new_header = + XNMALLOC (strlen (header) + 1 + + strlen (field) + 1 + strlen (value) + 1 + 1, + char); + + /* Test whether the field already occurs in the header entry. */ + const char *h; + + for (h = header; *h != '\0'; ) + { + if (strncmp (h, field, field_len) == 0) + break; + h = strchr (h, '\n'); + if (h == NULL) + break; + h++; + } + if (h != NULL && *h != '\0') + { + /* Replace the field. */ + char *p = new_header; + memcpy (p, header, h - header); + p += h - header; + p = stpcpy (p, field); + p = stpcpy (stpcpy (stpcpy (p, " "), value), "\n"); + h = strchr (h, '\n'); + if (h != NULL) + { + h++; + stpcpy (p, h); + } + } + else if (field_index < 0) + { + /* An unknown field. Append it at the end. */ + char *p = new_header; + p = stpcpy (p, header); + if (p > new_header && p[-1] != '\n') + *p++ = '\n'; + p = stpcpy (p, field); + stpcpy (stpcpy (stpcpy (p, " "), value), "\n"); + } + else + { + /* Find the appropriate position for inserting the field. */ + for (h = header; *h != '\0'; ) + { + /* Test whether h starts with a field name whose index is + > field_index. */ + for (k = field_index + 1; k < SIZEOF (known_fields); k++) + if (strncmp (h, known_fields[k].name, known_fields[k].len) + == 0) + break; + if (k < SIZEOF (known_fields)) + break; + h = strchr (h, '\n'); + if (h == NULL) + break; + h++; + } + if (h != NULL && *h != '\0') + { + /* Insert the field at position h. */ + char *p = new_header; + memcpy (p, header, h - header); + p += h - header; + p = stpcpy (p, field); + p = stpcpy (stpcpy (stpcpy (p, " "), value), "\n"); + stpcpy (p, h); + } + else + { + /* Append it at the end. */ + char *p = new_header; + p = stpcpy (p, header); + if (p > new_header && p[-1] != '\n') + *p++ = '\n'; + p = stpcpy (p, field); + stpcpy (stpcpy (stpcpy (p, " "), value), "\n"); + } + } + + mp->msgstr = new_header; + } + } +} diff --git a/gettext-tools/src/msgl-header.h b/gettext-tools/src/msgl-header.h new file mode 100644 index 0000000..f26b1a8 --- /dev/null +++ b/gettext-tools/src/msgl-header.h @@ -0,0 +1,43 @@ +/* Message list header manipulation. + Copyright (C) 2007 Free Software Foundation, Inc. + Written by Bruno Haible , 2007. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_HEADER_H +#define _MSGL_HEADER_H + +#include "message.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Set the given field to the given value. + The FIELD name ends in a colon. + The VALUE will have a space prepended and a newline appended by this + function. */ +extern void + msgdomain_list_set_header_field (msgdomain_list_ty *mdlp, + const char *field, const char *value); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MSGL_HEADER_H */ diff --git a/gettext-tools/src/msgl-iconv.c b/gettext-tools/src/msgl-iconv.c new file mode 100644 index 0000000..958455b --- /dev/null +++ b/gettext-tools/src/msgl-iconv.c @@ -0,0 +1,597 @@ +/* Message list charset and locale charset handling. + Copyright (C) 2001-2003, 2005-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +/* Specification. */ +#include "msgl-iconv.h" + +#include +#include +#include + +#if HAVE_ICONV +# include +#endif + +#include "progname.h" +#include "basename.h" +#include "message.h" +#include "po-charset.h" +#include "xstriconv.h" +#include "xstriconveh.h" +#include "msgl-ascii.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "c-strstr.h" +#include "xvasprintf.h" +#include "po-xerror.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +#if HAVE_ICONV + +static void conversion_error (const struct conversion_context* context) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void +conversion_error (const struct conversion_context* context) +{ + if (context->to_code == po_charset_utf8) + /* If a conversion to UTF-8 fails, the problem lies in the input. */ + po_xerror (PO_SEVERITY_FATAL_ERROR, context->message, NULL, 0, 0, false, + xasprintf (_("%s: input is not valid in \"%s\" encoding"), + context->from_filename, context->from_code)); + else + po_xerror (PO_SEVERITY_FATAL_ERROR, context->message, NULL, 0, 0, false, + xasprintf (_("\ +%s: error while converting from \"%s\" encoding to \"%s\" encoding"), + context->from_filename, context->from_code, + context->to_code)); + /* NOTREACHED */ + abort (); +} + +char * +convert_string_directly (iconv_t cd, const char *string, + const struct conversion_context* context) +{ + size_t len = strlen (string) + 1; + char *result = NULL; + size_t resultlen = 0; + + if (xmem_cd_iconv (string, len, cd, &result, &resultlen) == 0) + /* Verify the result has exactly one NUL byte, at the end. */ + if (resultlen > 0 && result[resultlen - 1] == '\0' + && strlen (result) == resultlen - 1) + return result; + + conversion_error (context); + /* NOTREACHED */ + return NULL; +} + +static char * +convert_string (const iconveh_t *cd, const char *string, + const struct conversion_context* context) +{ + size_t len = strlen (string) + 1; + char *result = NULL; + size_t resultlen = 0; + + if (xmem_cd_iconveh (string, len, cd, iconveh_error, NULL, + &result, &resultlen) == 0) + /* Verify the result has exactly one NUL byte, at the end. */ + if (resultlen > 0 && result[resultlen - 1] == '\0' + && strlen (result) == resultlen - 1) + return result; + + conversion_error (context); + /* NOTREACHED */ + return NULL; +} + +static void +convert_string_list (const iconveh_t *cd, string_list_ty *slp, + const struct conversion_context* context) +{ + size_t i; + + if (slp != NULL) + for (i = 0; i < slp->nitems; i++) + slp->item[i] = convert_string (cd, slp->item[i], context); +} + +static void +convert_prev_msgid (const iconveh_t *cd, message_ty *mp, + const struct conversion_context* context) +{ + if (mp->prev_msgctxt != NULL) + mp->prev_msgctxt = convert_string (cd, mp->prev_msgctxt, context); + if (mp->prev_msgid != NULL) + mp->prev_msgid = convert_string (cd, mp->prev_msgid, context); + if (mp->prev_msgid_plural != NULL) + mp->prev_msgid_plural = convert_string (cd, mp->prev_msgid_plural, context); +} + +static void +convert_msgid (const iconveh_t *cd, message_ty *mp, + const struct conversion_context* context) +{ + if (mp->msgctxt != NULL) + mp->msgctxt = convert_string (cd, mp->msgctxt, context); + mp->msgid = convert_string (cd, mp->msgid, context); + if (mp->msgid_plural != NULL) + mp->msgid_plural = convert_string (cd, mp->msgid_plural, context); +} + +static void +convert_msgstr (const iconveh_t *cd, message_ty *mp, + const struct conversion_context* context) +{ + char *result = NULL; + size_t resultlen = 0; + + if (!(mp->msgstr_len > 0 && mp->msgstr[mp->msgstr_len - 1] == '\0')) + abort (); + + if (xmem_cd_iconveh (mp->msgstr, mp->msgstr_len, cd, iconveh_error, NULL, + &result, &resultlen) == 0) + /* Verify the result has a NUL byte at the end. */ + if (resultlen > 0 && result[resultlen - 1] == '\0') + /* Verify the result has the same number of NUL bytes. */ + { + const char *p; + const char *pend; + int nulcount1; + int nulcount2; + + for (p = mp->msgstr, pend = p + mp->msgstr_len, nulcount1 = 0; + p < pend; + p += strlen (p) + 1, nulcount1++); + for (p = result, pend = p + resultlen, nulcount2 = 0; + p < pend; + p += strlen (p) + 1, nulcount2++); + + if (nulcount1 == nulcount2) + { + mp->msgstr = result; + mp->msgstr_len = resultlen; + return; + } + } + + conversion_error (context); +} + +#endif + + +static bool +iconv_message_list_internal (message_list_ty *mlp, + const char *canon_from_code, + const char *canon_to_code, + bool update_header, + const char *from_filename) +{ + bool canon_from_code_overridden = (canon_from_code != NULL); + bool msgids_changed; + size_t j; + + /* If the list is empty, nothing to do. */ + if (mlp->nitems == 0) + return false; + + /* Search the header entry, and extract and replace the charset name. */ + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + char *charset; + const char *canon_charset; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + + canon_charset = po_charset_canonicalize (charset); + if (canon_charset == NULL) + { + if (!canon_from_code_overridden) + { + /* Don't give an error for POT files, because POT + files usually contain only ASCII msgids. */ + const char *filename = from_filename; + size_t filenamelen; + + if (filename != NULL + && (filenamelen = strlen (filename)) >= 4 + && memcmp (filename + filenamelen - 4, ".pot", 4) + == 0 + && strcmp (charset, "CHARSET") == 0) + canon_charset = po_charset_ascii; + else + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, + false, xasprintf (_("\ +present charset \"%s\" is not a portable encoding name"), + charset)); + } + } + else + { + if (canon_from_code == NULL) + canon_from_code = canon_charset; + else if (canon_from_code != canon_charset) + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, + false, + xasprintf (_("\ +two different charsets \"%s\" and \"%s\" in input file"), + canon_from_code, canon_charset)); + } + freea (charset); + + if (update_header) + { + size_t len1, len2, len3; + char *new_header; + + len1 = charsetstr - header; + len2 = strlen (canon_to_code); + len3 = (header + strlen (header)) - (charsetstr + len); + new_header = XNMALLOC (len1 + len2 + len3 + 1, char); + memcpy (new_header, header, len1); + memcpy (new_header + len1, canon_to_code, len2); + memcpy (new_header + len1 + len2, charsetstr + len, + len3 + 1); + mlp->item[j]->msgstr = new_header; + mlp->item[j]->msgstr_len = len1 + len2 + len3 + 1; + } + } + } + } + if (canon_from_code == NULL) + { + if (is_ascii_message_list (mlp)) + canon_from_code = po_charset_ascii; + else + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + _("\ +input file doesn't contain a header entry with a charset specification")); + } + + msgids_changed = false; + + /* If the two encodings are the same, nothing to do. */ + if (canon_from_code != canon_to_code) + { +#if HAVE_ICONV + iconveh_t cd; + struct conversion_context context; + + if (iconveh_open (canon_to_code, canon_from_code, &cd) < 0) + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf (_("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(), \ +and iconv() does not support this conversion."), + canon_from_code, canon_to_code, + basename (program_name))); + + context.from_code = canon_from_code; + context.to_code = canon_to_code; + context.from_filename = from_filename; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if ((mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)) + || !is_ascii_string (mp->msgid)) + msgids_changed = true; + context.message = mp; + convert_string_list (&cd, mp->comment, &context); + convert_string_list (&cd, mp->comment_dot, &context); + convert_prev_msgid (&cd, mp, &context); + convert_msgid (&cd, mp, &context); + convert_msgstr (&cd, mp, &context); + } + + iconveh_close (&cd); + + if (msgids_changed) + if (message_list_msgids_changed (mlp)) + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf (_("\ +Conversion from \"%s\" to \"%s\" introduces duplicates: \ +some different msgids become equal."), + canon_from_code, canon_to_code)); +#else + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf (_("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(). \ +This version was built without iconv()."), + canon_from_code, canon_to_code, + basename (program_name))); +#endif + } + + return msgids_changed; +} + +bool +iconv_message_list (message_list_ty *mlp, + const char *canon_from_code, const char *canon_to_code, + const char *from_filename) +{ + return iconv_message_list_internal (mlp, + canon_from_code, canon_to_code, true, + from_filename); +} + +msgdomain_list_ty * +iconv_msgdomain_list (msgdomain_list_ty *mdlp, + const char *to_code, + bool update_header, + const char *from_filename) +{ + const char *canon_to_code; + size_t k; + + /* Canonicalize target encoding. */ + canon_to_code = po_charset_canonicalize (to_code); + if (canon_to_code == NULL) + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf (_("\ +target charset \"%s\" is not a portable encoding name."), + to_code)); + + for (k = 0; k < mdlp->nitems; k++) + iconv_message_list_internal (mdlp->item[k]->messages, + mdlp->encoding, canon_to_code, update_header, + from_filename); + + mdlp->encoding = canon_to_code; + return mdlp; +} + +#if HAVE_ICONV + +static bool +iconvable_string (const iconveh_t *cd, const char *string) +{ + size_t len = strlen (string) + 1; + char *result = NULL; + size_t resultlen = 0; + + if (xmem_cd_iconveh (string, len, cd, iconveh_error, NULL, + &result, &resultlen) == 0) + { + /* Test if the result has exactly one NUL byte, at the end. */ + bool ok = (resultlen > 0 && result[resultlen - 1] == '\0' + && strlen (result) == resultlen - 1); + free (result); + return ok; + } + return false; +} + +static bool +iconvable_string_list (const iconveh_t *cd, string_list_ty *slp) +{ + size_t i; + + if (slp != NULL) + for (i = 0; i < slp->nitems; i++) + if (!iconvable_string (cd, slp->item[i])) + return false; + return true; +} + +static bool +iconvable_prev_msgid (const iconveh_t *cd, message_ty *mp) +{ + if (mp->prev_msgctxt != NULL) + if (!iconvable_string (cd, mp->prev_msgctxt)) + return false; + if (mp->prev_msgid != NULL) + if (!iconvable_string (cd, mp->prev_msgid)) + return false; + if (mp->prev_msgid_plural != NULL) + if (!iconvable_string (cd, mp->prev_msgid_plural)) + return false; + return true; +} + +static bool +iconvable_msgid (const iconveh_t *cd, message_ty *mp) +{ + if (mp->msgctxt != NULL) + if (!iconvable_string (cd, mp->msgctxt)) + return false; + if (!iconvable_string (cd, mp->msgid)) + return false; + if (mp->msgid_plural != NULL) + if (!iconvable_string (cd, mp->msgid_plural)) + return false; + return true; +} + +static bool +iconvable_msgstr (const iconveh_t *cd, message_ty *mp) +{ + char *result = NULL; + size_t resultlen = 0; + + if (!(mp->msgstr_len > 0 && mp->msgstr[mp->msgstr_len - 1] == '\0')) + abort (); + + if (xmem_cd_iconveh (mp->msgstr, mp->msgstr_len, cd, iconveh_error, NULL, + &result, &resultlen) == 0) + { + bool ok = false; + + /* Test if the result has a NUL byte at the end. */ + if (resultlen > 0 && result[resultlen - 1] == '\0') + /* Test if the result has the same number of NUL bytes. */ + { + const char *p; + const char *pend; + int nulcount1; + int nulcount2; + + for (p = mp->msgstr, pend = p + mp->msgstr_len, nulcount1 = 0; + p < pend; + p += strlen (p) + 1, nulcount1++); + for (p = result, pend = p + resultlen, nulcount2 = 0; + p < pend; + p += strlen (p) + 1, nulcount2++); + + if (nulcount1 == nulcount2) + ok = true; + } + + free (result); + return ok; + } + return false; +} + +#endif + +bool +is_message_list_iconvable (message_list_ty *mlp, + const char *canon_from_code, + const char *canon_to_code) +{ + bool canon_from_code_overridden = (canon_from_code != NULL); + size_t j; + + /* If the list is empty, nothing to check. */ + if (mlp->nitems == 0) + return true; + + /* Search the header entry, and extract the charset name. */ + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + char *charset; + const char *canon_charset; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + + canon_charset = po_charset_canonicalize (charset); + if (canon_charset == NULL) + { + if (!canon_from_code_overridden) + { + /* Don't give an error for POT files, because POT + files usually contain only ASCII msgids. */ + if (strcmp (charset, "CHARSET") == 0) + canon_charset = po_charset_ascii; + else + { + /* charset is not a portable encoding name. */ + freea (charset); + return false; + } + } + } + else + { + if (canon_from_code == NULL) + canon_from_code = canon_charset; + else if (canon_from_code != canon_charset) + { + /* Two different charsets in input file. */ + freea (charset); + return false; + } + } + freea (charset); + } + } + } + if (canon_from_code == NULL) + { + if (is_ascii_message_list (mlp)) + canon_from_code = po_charset_ascii; + else + /* Input file lacks a header entry with a charset specification. */ + return false; + } + + /* If the two encodings are the same, nothing to check. */ + if (canon_from_code != canon_to_code) + { +#if HAVE_ICONV + iconveh_t cd; + + if (iconveh_open (canon_to_code, canon_from_code, &cd) < 0) + /* iconv() doesn't support this conversion. */ + return false; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (!(iconvable_string_list (&cd, mp->comment) + && iconvable_string_list (&cd, mp->comment_dot) + && iconvable_prev_msgid (&cd, mp) + && iconvable_msgid (&cd, mp) + && iconvable_msgstr (&cd, mp))) + return false; + } + + iconveh_close (&cd); +#else + /* This version was built without iconv(). */ + return false; +#endif + } + + return true; +} diff --git a/gettext-tools/src/msgl-iconv.h b/gettext-tools/src/msgl-iconv.h new file mode 100644 index 0000000..5a7fc00 --- /dev/null +++ b/gettext-tools/src/msgl-iconv.h @@ -0,0 +1,87 @@ +/* Message list character set conversion. + Copyright (C) 2001-2003, 2005-2006, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGL_ICONV_H +#define _MSGL_ICONV_H + +#include +#if HAVE_ICONV +#include +#endif + +#include "message.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#if HAVE_ICONV + +/* A context, used for accurate error messages. */ +struct conversion_context +{ + const char *from_code; /* canonicalized encoding name for input */ + const char *to_code; /* canonicalized encoding name for output */ + const char *from_filename; /* file name where the input comes from */ + const message_ty *message; /* message being converted, or NULL */ +}; + +/* Converts the STRING through the conversion descriptor CD. + Assumes that either FROM_CODE or TO_CODE is UTF-8. */ +extern char *convert_string_directly (iconv_t cd, const char *string, + const struct conversion_context* context); + +#endif + +/* Converts the message list MLP to the (already canonicalized) encoding + CANON_TO_CODE. The (already canonicalized) encoding before conversion + can be passed as CANON_FROM_CODE; if NULL is passed instead, the + encoding is looked up in the header entry. Returns true if and only if + some msgctxt or msgid changed due to the conversion. */ +extern bool + iconv_message_list (message_list_ty *mlp, + const char *canon_from_code, + const char *canon_to_code, + const char *from_filename); + +/* Converts all the message lists in MDLP to the encoding TO_CODE. + UPDATE_HEADER specifies whether to update the "charset=..." specification + in the header; it should normally be true. */ +extern msgdomain_list_ty * + iconv_msgdomain_list (msgdomain_list_ty *mdlp, + const char *to_code, + bool update_header, + const char *from_filename); + +/* Tests whether the message list MLP could be converted to CANON_TO_CODE. + The (already canonicalized) encoding before conversion can be passed as + CANON_FROM_CODE; if NULL is passed instead, the encoding is looked up + in the header entry. */ +extern bool + is_message_list_iconvable (message_list_ty *mlp, + const char *canon_from_code, + const char *canon_to_code); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _MSGL_ICONV_H */ diff --git a/gettext-tools/src/msgmerge.c b/gettext-tools/src/msgmerge.c new file mode 100644 index 0000000..d8e02ae --- /dev/null +++ b/gettext-tools/src/msgmerge.c @@ -0,0 +1,2061 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2010, 2012 Free Software Foundation, Inc. + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "format.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "obstack.h" +#include "c-strstr.h" +#include "c-strcase.h" +#include "po-charset.h" +#include "msgl-iconv.h" +#include "msgl-equal.h" +#include "msgl-fsearch.h" +#include "glthread/lock.h" +#include "lang-table.h" +#include "plural-exp.h" +#include "plural-count.h" +#include "msgl-check.h" +#include "po-xerror.h" +#include "backupfile.h" +#include "copy-file.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#define obstack_chunk_alloc xmalloc +#define obstack_chunk_free free + + +/* If true do not print unneeded messages. */ +static bool quiet; + +/* Verbosity level. */ +static int verbosity_level; + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Apply the .pot file to each of the domains in the PO file. */ +static bool multi_domain_mode = false; + +/* Determines whether to use fuzzy matching. */ +static bool use_fuzzy_matching = true; + +/* Determines whether to keep old msgids as previous msgids. */ +static bool keep_previous = false; + +/* Language (ISO-639 code) and optional territory (ISO-3166 code). */ +static const char *catalogname = NULL; + +/* List of user-specified compendiums. */ +static message_list_list_ty *compendiums; + +/* List of corresponding filenames. */ +static string_list_ty *compendium_filenames; + +/* Update mode. */ +static bool update_mode = false; +static const char *version_control_string; +static const char *backup_suffix_string; + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "backup", required_argument, NULL, CHAR_MAX + 1 }, + { "color", optional_argument, NULL, CHAR_MAX + 9 }, + { "compendium", required_argument, NULL, 'C', }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, 'i' }, + { "lang", required_argument, NULL, CHAR_MAX + 8 }, + { "multi-domain", no_argument, NULL, 'm' }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-fuzzy-matching", no_argument, NULL, 'N' }, + { "no-location", no_argument, NULL, CHAR_MAX + 11 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 4 }, + { "output-file", required_argument, NULL, 'o' }, + { "previous", no_argument, NULL, CHAR_MAX + 7 }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "quiet", no_argument, NULL, 'q' }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "silent", no_argument, NULL, 'q' }, + { "strict", no_argument, NULL, CHAR_MAX + 2 }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 5 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 6 }, + { "style", required_argument, NULL, CHAR_MAX + 10 }, + { "suffix", required_argument, NULL, CHAR_MAX + 3 }, + { "update", no_argument, NULL, 'U' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { NULL, 0, NULL, 0 } +}; + + +struct statistics +{ + size_t merged; + size_t fuzzied; + size_t missing; + size_t obsolete; +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void compendium (const char *filename); +static void msgdomain_list_stablesort_by_obsolete (msgdomain_list_ty *mdlp); +static msgdomain_list_ty *merge (const char *fn1, const char *fn2, + catalog_input_format_ty input_syntax, + msgdomain_list_ty **defp); + + +int +main (int argc, char **argv) +{ + int opt; + bool do_help; + bool do_version; + char *output_file; + char *color; + msgdomain_list_ty *def; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_filepos = false; + bool sort_by_msgid = false; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + verbosity_level = 0; + quiet = false; + gram_max_allowed_errors = UINT_MAX; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + color = NULL; + + while ((opt = getopt_long (argc, argv, "C:D:eEFhimn:No:pPqsUvVw:", + long_options, NULL)) + != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + + case 'C': + compendium (optarg); + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'm': + multi_domain_mode = true; + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'N': + use_fuzzy_matching = false; + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 'q': + quiet = true; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'U': + update_mode = true; + break; + + case 'v': + ++verbosity_level; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: /* --backup */ + version_control_string = optarg; + break; + + case CHAR_MAX + 2: /* --strict */ + message_print_style_uniforum (); + break; + + case CHAR_MAX + 3: /* --suffix */ + backup_suffix_string = optarg; + break; + + case CHAR_MAX + 4: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 5: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 6: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 7: /* --previous */ + keep_previous = true; + break; + + case CHAR_MAX + 8: /* --lang */ + catalogname = optarg; + break; + + case CHAR_MAX + 9: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + color = optarg; + break; + + case CHAR_MAX + 10: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 11: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "1995-1998, 2000-2010"); + printf (_("Written by %s.\n"), proper_name ("Peter Miller")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test whether we have an .po file name as argument. */ + if (optind >= argc) + { + error (EXIT_SUCCESS, 0, _("no input files given")); + usage (EXIT_FAILURE); + } + if (optind + 2 != argc) + { + error (EXIT_SUCCESS, 0, _("exactly 2 input files required")); + usage (EXIT_FAILURE); + } + + /* Verify selected options. */ + if (update_mode) + { + if (output_file != NULL) + { + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--update", "--output-file"); + } + if (color != NULL) + { + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--update", "--color"); + } + if (style_file_name != NULL) + { + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--update", "--style"); + } + } + else + { + if (version_control_string != NULL) + { + error (EXIT_SUCCESS, 0, _("%s is only valid with %s"), + "--backup", "--update"); + usage (EXIT_FAILURE); + } + if (backup_suffix_string != NULL) + { + error (EXIT_SUCCESS, 0, _("%s is only valid with %s"), + "--suffix", "--update"); + usage (EXIT_FAILURE); + } + } + + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* In update mode, --properties-input implies --properties-output. */ + if (update_mode && input_syntax == &input_format_properties) + output_syntax = &output_format_properties; + /* In update mode, --stringtable-input implies --stringtable-output. */ + if (update_mode && input_syntax == &input_format_stringtable) + output_syntax = &output_format_stringtable; + + /* Merge the two files. */ + result = merge (argv[optind], argv[optind + 1], input_syntax, &def); + + /* Sort the results. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + if (update_mode) + { + /* Before comparing result with def, sort the result into the same order + as would be done implicitly by output_syntax->print. */ + if (output_syntax->sorts_obsoletes_to_end) + msgdomain_list_stablesort_by_obsolete (result); + + /* Do nothing if the original file and the result are equal. Also do + nothing if the original file and the result differ only by the + POT-Creation-Date in the header entry; this is needed for projects + which don't put the .pot file under CVS. */ + if (!msgdomain_list_equal (def, result, true)) + { + /* Back up def.po. */ + enum backup_type backup_type; + char *backup_file; + + output_file = argv[optind]; + + if (backup_suffix_string == NULL) + { + backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + if (backup_suffix_string != NULL + && backup_suffix_string[0] == '\0') + backup_suffix_string = NULL; + } + if (backup_suffix_string != NULL) + simple_backup_suffix = backup_suffix_string; + + backup_type = xget_version (_("backup type"), version_control_string); + if (backup_type != none) + { + backup_file = find_backup_file_name (output_file, backup_type); + copy_file_preserving (output_file, backup_file); + } + + /* Write the merged message list out. */ + msgdomain_list_print (result, output_file, output_syntax, true, + false); + } + } + else + { + /* Write the merged message list out. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, + false); + } + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] def.po ref.pot\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Merges two Uniforum style .po files together. The def.po file is an\n\ +existing PO file with translations which will be taken over to the newly\n\ +created file as long as they still match; comments will be preserved,\n\ +but extracted comments and file positions will be discarded. The ref.pot\n\ +file is the last created PO file with up-to-date source references but\n\ +old translations, or a PO Template file (generally created by xgettext);\n\ +any translations or comments in the file will be discarded, however dot\n\ +comments and file positions will be preserved. Where an exact match\n\ +cannot be found, fuzzy matching is used to produce better results.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + def.po translations referring to old sources\n")); + printf (_("\ + ref.pot references to new sources\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ + -C, --compendium=FILE additional library of message translations,\n\ + may be specified more than once\n")); + printf ("\n"); + printf (_("\ +Operation mode:\n")); + printf (_("\ + -U, --update update def.po,\n\ + do nothing if def.po already up to date\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +Output file location in update mode:\n")); + printf (_("\ +The result is written back to def.po.\n")); + printf (_("\ + --backup=CONTROL make a backup of def.po\n")); + printf (_("\ + --suffix=SUFFIX override the usual backup suffix\n")); + printf (_("\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n")); + printf (_("\ +The backup suffix is '~', unless set with --suffix or the SIMPLE_BACKUP_SUFFIX\n\ +environment variable.\n\ +")); + printf ("\n"); + printf (_("\ +Operation modifiers:\n")); + printf (_("\ + -m, --multi-domain apply ref.pot to each of the domains in def.po\n")); + printf (_("\ + -N, --no-fuzzy-matching do not use fuzzy matching\n")); + printf (_("\ + --previous keep previous msgids of translated messages\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input files are in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input files are in NeXTstep/GNUstep .strings\n\ + syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --lang=CATALOGNAME set 'Language' field in the header entry\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent indented output style\n")); + printf (_("\ + --no-location suppress '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location preserve '#: filename:line' lines (default)\n")); + printf (_("\ + --strict strict Uniforum output style\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf (_("\ + -v, --verbose increase verbosity level\n")); + printf (_("\ + -q, --quiet, --silent suppress progress indicators\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + + +static void +compendium (const char *filename) +{ + msgdomain_list_ty *mdlp; + size_t k; + + mdlp = read_catalog_file (filename, &input_format_po); + if (compendiums == NULL) + { + compendiums = message_list_list_alloc (); + compendium_filenames = string_list_alloc (); + } + for (k = 0; k < mdlp->nitems; k++) + { + message_list_list_append (compendiums, mdlp->item[k]->messages); + string_list_append (compendium_filenames, filename); + } +} + + +/* Sorts obsolete messages to the end, for every domain. */ +static void +msgdomain_list_stablesort_by_obsolete (msgdomain_list_ty *mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + /* Sort obsolete messages to the end. */ + if (mlp->nitems > 0) + { + message_ty **l1 = XNMALLOC (mlp->nitems, message_ty *); + size_t n1; + message_ty **l2 = XNMALLOC (mlp->nitems, message_ty *); + size_t n2; + size_t j; + + /* Sort the non-obsolete messages into l1 and the obsolete messages + into l2. */ + n1 = 0; + n2 = 0; + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (mp->obsolete) + l2[n2++] = mp; + else + l1[n1++] = mp; + } + if (n1 > 0 && n2 > 0) + { + memcpy (mlp->item, l1, n1 * sizeof (message_ty *)); + memcpy (mlp->item + n1, l2, n2 * sizeof (message_ty *)); + } + free (l2); + free (l1); + } + } +} + + +/* Data structure representing the messages with known translations. + They are composed of + - A message list from def.po, + - The compendiums. + The data structure is optimized for exact and fuzzy searches. */ +typedef struct definitions_ty definitions_ty; +struct definitions_ty +{ + /* A list of message lists. The first comes from def.po, the other ones + from the compendiums. Each message list has a built-in hash table, + for speed when doing the exact searches. */ + message_list_list_ty *lists; + + /* A fuzzy index of the current list of non-compendium messages, for speed + when doing fuzzy searches. Used only if use_fuzzy_matching is true. */ + message_fuzzy_index_ty *curr_findex; + /* A once-only execution guard for the initialization of the fuzzy index. + Needed for OpenMP. */ + gl_lock_define(, curr_findex_init_lock) + + /* A fuzzy index of the compendiums, for speed when doing fuzzy searches. + Used only if use_fuzzy_matching is true and compendiums != NULL. */ + message_fuzzy_index_ty *comp_findex; + /* A once-only execution guard for the initialization of the fuzzy index. + Needed for OpenMP. */ + gl_lock_define(, comp_findex_init_lock) + + /* The canonical encoding of the definitions and the compendiums. + Only used for fuzzy matching. */ + const char *canon_charset; +}; + +static inline void +definitions_init (definitions_ty *definitions, const char *canon_charset) +{ + definitions->lists = message_list_list_alloc (); + message_list_list_append (definitions->lists, NULL); + if (compendiums != NULL) + message_list_list_append_list (definitions->lists, compendiums); + definitions->curr_findex = NULL; + gl_lock_init (definitions->curr_findex_init_lock); + definitions->comp_findex = NULL; + gl_lock_init (definitions->comp_findex_init_lock); + definitions->canon_charset = canon_charset; +} + +/* Return the current list of non-compendium messages. */ +static inline message_list_ty * +definitions_current_list (const definitions_ty *definitions) +{ + return definitions->lists->item[0]; +} + +/* Set the current list of non-compendium messages. */ +static inline void +definitions_set_current_list (definitions_ty *definitions, message_list_ty *mlp) +{ + definitions->lists->item[0] = mlp; + if (definitions->curr_findex != NULL) + { + message_fuzzy_index_free (definitions->curr_findex); + definitions->curr_findex = NULL; + } +} + +/* Create the fuzzy index for the current list of non-compendium messages. + Used only if use_fuzzy_matching is true. */ +static inline void +definitions_init_curr_findex (definitions_ty *definitions) +{ + /* Protect against concurrent execution. */ + gl_lock_lock (definitions->curr_findex_init_lock); + if (definitions->curr_findex == NULL) + definitions->curr_findex = + message_fuzzy_index_alloc (definitions_current_list (definitions), + definitions->canon_charset); + gl_lock_unlock (definitions->curr_findex_init_lock); +} + +/* Create the fuzzy index for the compendium messages. + Used only if use_fuzzy_matching is true and compendiums != NULL. */ +static inline void +definitions_init_comp_findex (definitions_ty *definitions) +{ + /* Protect against concurrent execution. */ + gl_lock_lock (definitions->comp_findex_init_lock); + if (definitions->comp_findex == NULL) + { + /* Combine all the compendium message lists into a single one. Don't + bother checking for duplicates. */ + message_list_ty *all_compendium; + size_t i; + + all_compendium = message_list_alloc (false); + for (i = 0; i < compendiums->nitems; i++) + { + message_list_ty *mlp = compendiums->item[i]; + size_t j; + + for (j = 0; j < mlp->nitems; j++) + message_list_append (all_compendium, mlp->item[j]); + } + + /* Create the fuzzy index from it. */ + definitions->comp_findex = + message_fuzzy_index_alloc (all_compendium, definitions->canon_charset); + } + gl_lock_unlock (definitions->comp_findex_init_lock); +} + +/* Exact search. */ +static inline message_ty * +definitions_search (const definitions_ty *definitions, + const char *msgctxt, const char *msgid) +{ + return message_list_list_search (definitions->lists, msgctxt, msgid); +} + +/* Fuzzy search. + Used only if use_fuzzy_matching is true. */ +static inline message_ty * +definitions_search_fuzzy (definitions_ty *definitions, + const char *msgctxt, const char *msgid) +{ + message_ty *mp1; + + if (false) + { + /* Old, slow code. */ + mp1 = + message_list_search_fuzzy (definitions_current_list (definitions), + msgctxt, msgid); + } + else + { + /* Speedup through early abort in fstrcmp(), combined with pre-sorting + of the messages through a hashed index. */ + /* Create the fuzzy index lazily. */ + if (definitions->curr_findex == NULL) + definitions_init_curr_findex (definitions); + mp1 = message_fuzzy_index_search (definitions->curr_findex, + msgctxt, msgid, + FUZZY_THRESHOLD, false); + } + + if (compendiums != NULL) + { + double lower_bound_for_mp2; + message_ty *mp2; + + lower_bound_for_mp2 = + (mp1 != NULL + ? fuzzy_search_goal_function (mp1, msgctxt, msgid, 0.0) + : FUZZY_THRESHOLD); + /* This lower bound must be >= FUZZY_THRESHOLD. */ + if (!(lower_bound_for_mp2 >= FUZZY_THRESHOLD)) + abort (); + + /* Create the fuzzy index lazily. */ + if (definitions->comp_findex == NULL) + definitions_init_comp_findex (definitions); + + mp2 = message_fuzzy_index_search (definitions->comp_findex, + msgctxt, msgid, + lower_bound_for_mp2, true); + + /* Choose the best among mp1, mp2. */ + if (mp1 == NULL + || (mp2 != NULL + && (fuzzy_search_goal_function (mp2, msgctxt, msgid, + lower_bound_for_mp2) + > lower_bound_for_mp2))) + mp1 = mp2; + } + + return mp1; +} + +static inline void +definitions_destroy (definitions_ty *definitions) +{ + message_list_list_free (definitions->lists, 2); + if (definitions->curr_findex != NULL) + message_fuzzy_index_free (definitions->curr_findex); + if (definitions->comp_findex != NULL) + message_fuzzy_index_free (definitions->comp_findex); +} + + +/* A silent error logger. We are only interested in knowing whether errors + occurred at all. */ +static void +silent_error_logger (const char *format, ...) + __attribute__ ((__format__ (__printf__, 1, 2))); +static void +silent_error_logger (const char *format, ...) +{ +} + + +/* Another silent error logger. */ +static void +silent_xerror (int severity, + const struct message_ty *message, + const char *filename, size_t lineno, size_t column, + int multiline_p, const char *message_text) +{ +} + + +static message_ty * +message_merge (message_ty *def, message_ty *ref, bool force_fuzzy, + const struct plural_distribution *distribution) +{ + const char *msgstr; + size_t msgstr_len; + const char *prev_msgctxt; + const char *prev_msgid; + const char *prev_msgid_plural; + message_ty *result; + size_t j, i; + + /* Take the msgid from the reference. When fuzzy matches are made, + the definition will not be unique, but the reference will be - + usually because it has only been slightly changed. */ + + /* Take the msgstr from the definition. The msgstr of the reference + is usually empty, as it was generated by xgettext. If we currently + process the header entry we have to merge the msgstr by using the + Report-Msgid-Bugs-To and POT-Creation-Date fields from the reference. */ + if (is_header (ref)) + { + /* Oh, oh. The header entry and we have something to fill in. */ + static const struct + { + const char *name; + size_t len; + } known_fields[] = + { + { "Project-Id-Version:", sizeof ("Project-Id-Version:") - 1 }, +#define PROJECT_ID 0 + { "Report-Msgid-Bugs-To:", sizeof ("Report-Msgid-Bugs-To:") - 1 }, +#define REPORT_MSGID_BUGS_TO 1 + { "POT-Creation-Date:", sizeof ("POT-Creation-Date:") - 1 }, +#define POT_CREATION_DATE 2 + { "PO-Revision-Date:", sizeof ("PO-Revision-Date:") - 1 }, +#define PO_REVISION_DATE 3 + { "Last-Translator:", sizeof ("Last-Translator:") - 1 }, +#define LAST_TRANSLATOR 4 + { "Language-Team:", sizeof ("Language-Team:") - 1 }, +#define LANGUAGE_TEAM 5 + { "Language:", sizeof ("Language:") - 1 }, +#define LANGUAGE 6 + { "MIME-Version:", sizeof ("MIME-Version:") - 1 }, +#define MIME_VERSION 7 + { "Content-Type:", sizeof ("Content-Type:") - 1 }, +#define CONTENT_TYPE 8 + { "Content-Transfer-Encoding:", + sizeof ("Content-Transfer-Encoding:") - 1 } +#define CONTENT_TRANSFER 9 + }; +#define UNKNOWN 10 + struct + { + const char *string; + size_t len; + } header_fields[UNKNOWN + 1]; + struct obstack pool; + const char *cp; + char *newp; + size_t len, cnt; + + /* Clear all fields. */ + memset (header_fields, '\0', sizeof (header_fields)); + + /* Prepare a temporary memory pool. */ + obstack_init (&pool); + + cp = def->msgstr; + while (*cp != '\0') + { + const char *endp = strchr (cp, '\n'); + int terminated = endp != NULL; + + if (!terminated) + { + /* Add a trailing newline. */ + char *copy; + endp = strchr (cp, '\0'); + + len = endp - cp + 1; + + copy = (char *) obstack_alloc (&pool, len + 1); + stpcpy (stpcpy (copy, cp), "\n"); + cp = copy; + } + else + { + len = (endp - cp) + 1; + ++endp; + } + + /* Compare with any of the known fields. */ + for (cnt = 0; + cnt < sizeof (known_fields) / sizeof (known_fields[0]); + ++cnt) + if (c_strncasecmp (cp, known_fields[cnt].name, known_fields[cnt].len) + == 0) + break; + + if (cnt < sizeof (known_fields) / sizeof (known_fields[0])) + { + header_fields[cnt].string = &cp[known_fields[cnt].len]; + header_fields[cnt].len = len - known_fields[cnt].len; + } + else + { + /* It's an unknown field. Append content to what is already + known. */ + char *extended = + (char *) obstack_alloc (&pool, + header_fields[UNKNOWN].len + len + 1); + memcpy (extended, header_fields[UNKNOWN].string, + header_fields[UNKNOWN].len); + memcpy (&extended[header_fields[UNKNOWN].len], cp, len); + extended[header_fields[UNKNOWN].len + len] = '\0'; + header_fields[UNKNOWN].string = extended; + header_fields[UNKNOWN].len += len; + } + + cp = endp; + } + + /* Set the Language field if specified on the command line. */ + if (catalogname != NULL) + { + /* Prepend a space and append a newline. */ + size_t len = strlen (catalogname); + char *copy = (char *) obstack_alloc (&pool, 1 + len + 1 + 1); + stpcpy (stpcpy (stpcpy (copy, " "), catalogname), "\n"); + header_fields[LANGUAGE].string = copy; + header_fields[LANGUAGE].len = strlen (header_fields[LANGUAGE].string); + } + /* Add a Language field to PO files that don't have one. The Language + field was introduced in gettext-0.18. */ + else if (header_fields[LANGUAGE].string == NULL) + { + const char *language_team_ptr = header_fields[LANGUAGE_TEAM].string; + + if (language_team_ptr != NULL) + { + size_t language_team_len = header_fields[LANGUAGE_TEAM].len; + + /* Trim leading blanks. */ + while (language_team_len > 0 + && (*language_team_ptr == ' ' + || *language_team_ptr == '\t')) + { + language_team_ptr++; + language_team_len--; + } + + /* Trim trailing blanks. */ + while (language_team_len > 0 + && (language_team_ptr[language_team_len - 1] == ' ' + || language_team_ptr[language_team_len - 1] == '\t')) + language_team_len--; + + /* Trim last word, if it looks like an URL or email address. */ + { + size_t i; + + for (i = language_team_len; i > 0; i--) + if (language_team_ptr[i - 1] == ' ' + || language_team_ptr[i - 1] == '\t') + break; + /* The last word: language_team_ptr[i..language_team_len-1]. */ + if (i < language_team_len + && (language_team_ptr[i] == '<' + || language_team_ptr[language_team_len - 1] == '>' + || memchr (language_team_ptr, '@', language_team_len) + != NULL + || memchr (language_team_ptr, '/', language_team_len) + != NULL)) + { + /* Trim last word and blanks before it. */ + while (i > 0 + && (language_team_ptr[i - 1] == ' ' + || language_team_ptr[i - 1] == '\t')) + i--; + language_team_len = i; + } + } + + /* The rest of the Language-Team field should be the english name + of the languge. Convert to ISO 639 and ISO 3166 syntax. */ + { + size_t i; + + for (i = 0; i < language_variant_table_size; i++) + if (strlen (language_variant_table[i].english) + == language_team_len + && memcmp (language_variant_table[i].english, + language_team_ptr, language_team_len) == 0) + { + header_fields[LANGUAGE].string = + language_variant_table[i].code; + break; + } + } + if (header_fields[LANGUAGE].string == NULL) + { + size_t i; + + for (i = 0; i < language_table_size; i++) + if (strlen (language_table[i].english) == language_team_len + && memcmp (language_table[i].english, + language_team_ptr, language_team_len) == 0) + { + header_fields[LANGUAGE].string = language_table[i].code; + break; + } + } + if (header_fields[LANGUAGE].string != NULL) + { + /* Prepend a space and append a newline. */ + const char *str = header_fields[LANGUAGE].string; + size_t len = strlen (str); + char *copy = (char *) obstack_alloc (&pool, 1 + len + 1 + 1); + stpcpy (stpcpy (stpcpy (copy, " "), str), "\n"); + header_fields[LANGUAGE].string = copy; + } + else + header_fields[LANGUAGE].string = " \n"; + header_fields[LANGUAGE].len = + strlen (header_fields[LANGUAGE].string); + } + } + + { + const char *msgid_bugs_ptr; + + msgid_bugs_ptr = c_strstr (ref->msgstr, "Report-Msgid-Bugs-To:"); + if (msgid_bugs_ptr != NULL) + { + size_t msgid_bugs_len; + const char *endp; + + msgid_bugs_ptr += sizeof ("Report-Msgid-Bugs-To:") - 1; + + endp = strchr (msgid_bugs_ptr, '\n'); + if (endp == NULL) + { + /* Add a trailing newline. */ + char *extended; + endp = strchr (msgid_bugs_ptr, '\0'); + msgid_bugs_len = (endp - msgid_bugs_ptr) + 1; + extended = (char *) obstack_alloc (&pool, msgid_bugs_len + 1); + stpcpy (stpcpy (extended, msgid_bugs_ptr), "\n"); + msgid_bugs_ptr = extended; + } + else + msgid_bugs_len = (endp - msgid_bugs_ptr) + 1; + + header_fields[REPORT_MSGID_BUGS_TO].string = msgid_bugs_ptr; + header_fields[REPORT_MSGID_BUGS_TO].len = msgid_bugs_len; + } + } + + { + const char *pot_date_ptr; + + pot_date_ptr = c_strstr (ref->msgstr, "POT-Creation-Date:"); + if (pot_date_ptr != NULL) + { + size_t pot_date_len; + const char *endp; + + pot_date_ptr += sizeof ("POT-Creation-Date:") - 1; + + endp = strchr (pot_date_ptr, '\n'); + if (endp == NULL) + { + /* Add a trailing newline. */ + char *extended; + endp = strchr (pot_date_ptr, '\0'); + pot_date_len = (endp - pot_date_ptr) + 1; + extended = (char *) obstack_alloc (&pool, pot_date_len + 1); + stpcpy (stpcpy (extended, pot_date_ptr), "\n"); + pot_date_ptr = extended; + } + else + pot_date_len = (endp - pot_date_ptr) + 1; + + header_fields[POT_CREATION_DATE].string = pot_date_ptr; + header_fields[POT_CREATION_DATE].len = pot_date_len; + } + } + + /* Concatenate all the various fields. */ + len = 0; + for (cnt = 0; cnt < UNKNOWN; ++cnt) + if (header_fields[cnt].string != NULL) + len += known_fields[cnt].len + header_fields[cnt].len; + len += header_fields[UNKNOWN].len; + + cp = newp = XNMALLOC (len + 1, char); + newp[len] = '\0'; + +#define IF_FILLED(idx) \ + if (header_fields[idx].string) \ + newp = stpncpy (stpcpy (newp, known_fields[idx].name), \ + header_fields[idx].string, header_fields[idx].len) + + IF_FILLED (PROJECT_ID); + IF_FILLED (REPORT_MSGID_BUGS_TO); + IF_FILLED (POT_CREATION_DATE); + IF_FILLED (PO_REVISION_DATE); + IF_FILLED (LAST_TRANSLATOR); + IF_FILLED (LANGUAGE_TEAM); + IF_FILLED (LANGUAGE); + IF_FILLED (MIME_VERSION); + IF_FILLED (CONTENT_TYPE); + IF_FILLED (CONTENT_TRANSFER); + if (header_fields[UNKNOWN].string != NULL) + stpcpy (newp, header_fields[UNKNOWN].string); + +#undef IF_FILLED + + /* Free the temporary memory pool. */ + obstack_free (&pool, NULL); + + msgstr = cp; + msgstr_len = strlen (cp) + 1; + + prev_msgctxt = NULL; + prev_msgid = NULL; + prev_msgid_plural = NULL; + } + else + { + msgstr = def->msgstr; + msgstr_len = def->msgstr_len; + + if (def->is_fuzzy) + { + prev_msgctxt = def->prev_msgctxt; + prev_msgid = def->prev_msgid; + prev_msgid_plural = def->prev_msgid_plural; + } + else + { + prev_msgctxt = def->msgctxt; + prev_msgid = def->msgid; + prev_msgid_plural = def->msgid_plural; + } + } + + result = message_alloc (ref->msgctxt != NULL ? xstrdup (ref->msgctxt) : NULL, + xstrdup (ref->msgid), ref->msgid_plural, + msgstr, msgstr_len, &def->pos); + + /* Take the comments from the definition file. There will be none at + all in the reference file, as it was generated by xgettext. */ + if (def->comment) + for (j = 0; j < def->comment->nitems; ++j) + message_comment_append (result, def->comment->item[j]); + + /* Take the dot comments from the reference file, as they are + generated by xgettext. Any in the definition file are old ones + collected by previous runs of xgettext and msgmerge. */ + if (ref->comment_dot) + for (j = 0; j < ref->comment_dot->nitems; ++j) + message_comment_dot_append (result, ref->comment_dot->item[j]); + + /* The flags are mixed in a special way. Some informations come + from the reference message (such as format/no-format), others + come from the definition file (fuzzy or not). */ + result->is_fuzzy = def->is_fuzzy | force_fuzzy; + + /* If ref and def have the same msgid but different msgid_plural, it's + a reason to mark the result fuzzy. */ + if (!result->is_fuzzy + && (ref->msgid_plural != NULL + ? def->msgid_plural == NULL + || strcmp (ref->msgid_plural, def->msgid_plural) != 0 + : def->msgid_plural != NULL)) + result->is_fuzzy = true; + + for (i = 0; i < NFORMATS; i++) + { + result->is_format[i] = ref->is_format[i]; + + /* If the reference message is marked as being a format specifier, + but the definition message is not, we check if the resulting + message would pass "msgfmt -c". If yes, then all is fine. If + not, we add a fuzzy marker, because + 1. the message needs the translator's attention, + 2. msgmerge must not transform a PO file which passes "msgfmt -c" + into a PO file which doesn't. */ + if (!result->is_fuzzy + && possible_format_p (ref->is_format[i]) + && !possible_format_p (def->is_format[i]) + && check_msgid_msgstr_format_i (ref->msgid, ref->msgid_plural, + msgstr, msgstr_len, i, ref->range, + distribution, silent_error_logger) + > 0) + result->is_fuzzy = true; + } + + result->range = ref->range; + /* If the definition message was assuming a certain range, but the reference + message does not specify a range any more or specifies a range that is + not the same or a subset, we add a fuzzy marker, because + 1. the message needs the translator's attention, + 2. msgmerge must not transform a PO file which passes "msgfmt -c" + into a PO file which doesn't. */ + if (!result->is_fuzzy + && has_range_p (def->range) + && !(has_range_p (ref->range) + && ref->range.min >= def->range.min + && ref->range.max <= def->range.max)) + result->is_fuzzy = true; + + result->do_wrap = ref->do_wrap; + + /* Insert previous msgid, commented out with "#|". + Do so only when --previous is specified, for backward compatibility. + Since the "previous msgid" represents the original msgid that led to + the current msgstr, + - we can omit it if the resulting message is not fuzzy or is + untranslated (but do this in a later pass, since result->is_fuzzy + is not finalized at this point), + - otherwise, if the corresponding message from the definition file + was translated (not fuzzy), we use that message's msgid, + - otherwise, we use that message's prev_msgid. */ + if (keep_previous) + { + result->prev_msgctxt = prev_msgctxt; + result->prev_msgid = prev_msgid; + result->prev_msgid_plural = prev_msgid_plural; + } + + /* If the reference message was obsolete, make the resulting message + obsolete. This case doesn't occur for POT files, but users sometimes + use PO files that are themselves the result of msgmerge instead of POT + files. */ + result->obsolete = ref->obsolete; + + /* Take the file position comments from the reference file, as they + are generated by xgettext. Any in the definition file are old ones + collected by previous runs of xgettext and msgmerge. */ + for (j = 0; j < ref->filepos_count; ++j) + { + lex_pos_ty *pp = &ref->filepos[j]; + message_comment_filepos (result, pp->file_name, pp->line_number); + } + + /* Special postprocessing is needed if the reference message is a + plural form and the definition message isn't, or vice versa. */ + if (ref->msgid_plural != NULL) + { + if (def->msgid_plural == NULL) + result->used = 1; + } + else + { + if (def->msgid_plural != NULL) + result->used = 2; + } + + /* All done, return the merged message to the caller. */ + return result; +} + + +#define DOT_FREQUENCY 10 + +static void +match_domain (const char *fn1, const char *fn2, + definitions_ty *definitions, message_list_ty *refmlp, + message_list_ty *resultmlp, + struct statistics *stats, unsigned int *processed) +{ + message_ty *header_entry; + unsigned long int nplurals; + const struct expression *plural_expr; + char *untranslated_plural_msgstr; + struct plural_distribution distribution; + struct search_result { message_ty *found; bool fuzzy; } *search_results; + size_t j; + + header_entry = + message_list_search (definitions_current_list (definitions), NULL, ""); + extract_plural_expression (header_entry ? header_entry->msgstr : NULL, + &plural_expr, &nplurals); + untranslated_plural_msgstr = XNMALLOC (nplurals, char); + memset (untranslated_plural_msgstr, '\0', nplurals); + + /* Determine the plural distribution of the plural_expr formula. */ + { + /* Disable error output temporarily. */ + void (*old_po_xerror) (int, const struct message_ty *, const char *, size_t, + size_t, int, const char *) + = po_xerror; + po_xerror = silent_xerror; + + if (check_plural_eval (plural_expr, nplurals, header_entry, + &distribution) > 0) + { + distribution.expr = NULL; + distribution.often = NULL; + distribution.often_length = 0; + distribution.histogram = NULL; + } + + po_xerror = old_po_xerror; + } + + /* Most of the time is spent in definitions_search_fuzzy. + Perform it in a separate loop that can be parallelized by an OpenMP + capable compiler. */ + search_results = XNMALLOC (refmlp->nitems, struct search_result); + { + long int nn = refmlp->nitems; + long int jj; + + /* Tell the OpenMP capable compiler to distribute this loop across + several threads. The schedule is dynamic, because for some messages + the loop body can be executed very quickly, whereas for others it takes + a long time. + Note: The Sun Workshop 6.2 C compiler does not allow a space between + '#' and 'pragma'. */ + #ifdef _OPENMP + #pragma omp parallel for schedule(dynamic) + #endif + for (jj = 0; jj < nn; jj++) + { + message_ty *refmsg = refmlp->item[jj]; + message_ty *defmsg; + + /* Because merging can take a while we print something to signal + we are not dead. */ + if (!quiet && verbosity_level <= 1 && *processed % DOT_FREQUENCY == 0) + fputc ('.', stderr); + #ifdef _OPENMP + #pragma omp atomic + #endif + (*processed)++; + + /* See if it is in the other file. */ + defmsg = + definitions_search (definitions, refmsg->msgctxt, refmsg->msgid); + if (defmsg != NULL) + { + search_results[jj].found = defmsg; + search_results[jj].fuzzy = false; + } + else if (!is_header (refmsg) + /* If the message was not defined at all, try to find a very + similar message, it could be a typo, or the suggestion may + help. */ + && use_fuzzy_matching + && ((defmsg = + definitions_search_fuzzy (definitions, + refmsg->msgctxt, + refmsg->msgid)) != NULL)) + { + search_results[jj].found = defmsg; + search_results[jj].fuzzy = true; + } + else + search_results[jj].found = NULL; + } + } + + for (j = 0; j < refmlp->nitems; j++) + { + message_ty *refmsg = refmlp->item[j]; + + /* See if it is in the other file. + This used definitions_search. */ + if (search_results[j].found != NULL && !search_results[j].fuzzy) + { + message_ty *defmsg = search_results[j].found; + /* Merge the reference with the definition: take the #. and + #: comments from the reference, take the # comments from + the definition, take the msgstr from the definition. Add + this merged entry to the output message list. */ + message_ty *mp = + message_merge (defmsg, refmsg, false, &distribution); + + message_list_append (resultmlp, mp); + + /* Remember that this message has been used, when we scan + later to see if anything was omitted. */ + defmsg->used = 1; + stats->merged++; + } + else if (!is_header (refmsg)) + { + /* If the message was not defined at all, try to find a very + similar message, it could be a typo, or the suggestion may + help. This search assumed use_fuzzy_matching and used + definitions_search_fuzzy. */ + if (search_results[j].found != NULL && search_results[j].fuzzy) + { + message_ty *defmsg = search_results[j].found; + message_ty *mp; + + if (verbosity_level > 1) + { + po_gram_error_at_line (&refmsg->pos, _("\ +this message is used but not defined...")); + error_message_count--; + po_gram_error_at_line (&defmsg->pos, _("\ +...but this definition is similar")); + } + + /* Merge the reference with the definition: take the #. and + #: comments from the reference, take the # comments from + the definition, take the msgstr from the definition. Add + this merged entry to the output message list. */ + mp = message_merge (defmsg, refmsg, true, &distribution); + + message_list_append (resultmlp, mp); + + /* Remember that this message has been used, when we scan + later to see if anything was omitted. */ + defmsg->used = 1; + stats->fuzzied++; + if (!quiet && verbosity_level <= 1) + /* Always print a dot if we handled a fuzzy match. */ + fputc ('.', stderr); + } + else + { + message_ty *mp; + bool is_untranslated; + const char *p; + const char *pend; + + if (verbosity_level > 1) + po_gram_error_at_line (&refmsg->pos, _("\ +this message is used but not defined in %s"), fn1); + + mp = message_copy (refmsg); + + if (mp->msgid_plural != NULL) + { + /* Test if mp is untranslated. (It most likely is.) */ + is_untranslated = true; + for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++) + if (*p != '\0') + { + is_untranslated = false; + break; + } + if (is_untranslated) + { + /* Change mp->msgstr_len consecutive empty strings into + nplurals consecutive empty strings. */ + if (nplurals > mp->msgstr_len) + mp->msgstr = untranslated_plural_msgstr; + mp->msgstr_len = nplurals; + } + } + + message_list_append (resultmlp, mp); + stats->missing++; + } + } + } + + free (search_results); + + /* Now postprocess the problematic merges. This is needed because we + want the result to pass the "msgfmt -c -v" check. */ + { + /* message_merge sets mp->used to 1 or 2, depending on the problem. + Compute the bitwise OR of all these. */ + int problematic = 0; + + for (j = 0; j < resultmlp->nitems; j++) + problematic |= resultmlp->item[j]->used; + + if (problematic) + { + unsigned long int nplurals = 0; + + if (problematic & 1) + { + /* Need to know nplurals of the result domain. */ + message_ty *header_entry = + message_list_search (resultmlp, NULL, ""); + + nplurals = get_plural_count (header_entry + ? header_entry->msgstr + : NULL); + } + + for (j = 0; j < resultmlp->nitems; j++) + { + message_ty *mp = resultmlp->item[j]; + + if ((mp->used & 1) && (nplurals > 0)) + { + /* ref->msgid_plural != NULL but def->msgid_plural == NULL. + Use a copy of def->msgstr for each possible plural form. */ + size_t new_msgstr_len; + char *new_msgstr; + char *p; + unsigned long i; + + if (verbosity_level > 1) + { + po_gram_error_at_line (&mp->pos, _("\ +this message should define plural forms")); + } + + new_msgstr_len = nplurals * mp->msgstr_len; + new_msgstr = XNMALLOC (new_msgstr_len, char); + for (i = 0, p = new_msgstr; i < nplurals; i++) + { + memcpy (p, mp->msgstr, mp->msgstr_len); + p += mp->msgstr_len; + } + mp->msgstr = new_msgstr; + mp->msgstr_len = new_msgstr_len; + mp->is_fuzzy = true; + } + + if ((mp->used & 2) && (mp->msgstr_len > strlen (mp->msgstr) + 1)) + { + /* ref->msgid_plural == NULL but def->msgid_plural != NULL. + Use only the first among the plural forms. */ + + if (verbosity_level > 1) + { + po_gram_error_at_line (&mp->pos, _("\ +this message should not define plural forms")); + } + + mp->msgstr_len = strlen (mp->msgstr) + 1; + mp->is_fuzzy = true; + } + + /* Postprocessing of this message is done. */ + mp->used = 0; + } + } + } + + /* Now that mp->is_fuzzy is finalized for all messages, remove the + "previous msgid" information from all messages that are not fuzzy or + are untranslated. */ + for (j = 0; j < resultmlp->nitems; j++) + { + message_ty *mp = resultmlp->item[j]; + + if (!mp->is_fuzzy || mp->msgstr[0] == '\0') + { + mp->prev_msgctxt = NULL; + mp->prev_msgid = NULL; + mp->prev_msgid_plural = NULL; + } + } +} + +static msgdomain_list_ty * +merge (const char *fn1, const char *fn2, catalog_input_format_ty input_syntax, + msgdomain_list_ty **defp) +{ + msgdomain_list_ty *def; + msgdomain_list_ty *ref; + size_t j, k; + unsigned int processed; + struct statistics stats; + msgdomain_list_ty *result; + const char *def_canon_charset; + definitions_ty definitions; + message_list_ty *empty_list; + + stats.merged = stats.fuzzied = stats.missing = stats.obsolete = 0; + + /* This is the definitions file, created by a human. */ + def = read_catalog_file (fn1, input_syntax); + + /* This is the references file, created by groping the sources with + the xgettext program. */ + ref = read_catalog_file (fn2, input_syntax); + /* Add a dummy header entry, if the references file contains none. */ + for (k = 0; k < ref->nitems; k++) + if (message_list_search (ref->item[k]->messages, NULL, "") == NULL) + { + static lex_pos_ty pos = { __FILE__, __LINE__ }; + message_ty *refheader = message_alloc (NULL, "", NULL, "", 1, &pos); + + message_list_prepend (ref->item[k]->messages, refheader); + } + + /* The references file can be either in ASCII or in UTF-8. If it is + in UTF-8, we have to convert the definitions and the compendiums to + UTF-8 as well. */ + { + bool was_utf8 = false; + for (k = 0; k < ref->nitems; k++) + { + message_list_ty *mlp = ref->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + if (len == strlen ("UTF-8") + && c_strncasecmp (charsetstr, "UTF-8", len) == 0) + was_utf8 = true; + } + } + } + } + if (was_utf8) + { + def = iconv_msgdomain_list (def, "UTF-8", true, fn1); + if (compendiums != NULL) + for (k = 0; k < compendiums->nitems; k++) + iconv_message_list (compendiums->item[k], NULL, po_charset_utf8, + compendium_filenames->item[k]); + } + else if (compendiums != NULL && compendiums->nitems > 0) + { + /* Ensure that the definitions and the compendiums are in the same + encoding. Prefer the encoding of the definitions file, if + possible; otherwise, if the definitions file is empty and the + compendiums are all in the same encoding, use that encoding; + otherwise, use UTF-8. */ + bool conversion_done = false; + { + char *charset = NULL; + + /* Get the encoding of the definitions file. */ + for (k = 0; k < def->nitems; k++) + { + message_list_ty *mlp = def->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + break; + } + } + } + if (charset != NULL) + break; + } + if (charset != NULL) + { + const char *canon_charset = po_charset_canonicalize (charset); + + if (canon_charset != NULL) + { + bool all_compendiums_iconvable = true; + + if (compendiums != NULL) + for (k = 0; k < compendiums->nitems; k++) + if (!is_message_list_iconvable (compendiums->item[k], + NULL, canon_charset)) + { + all_compendiums_iconvable = false; + break; + } + + if (all_compendiums_iconvable) + { + /* Convert the compendiums to def's encoding. */ + if (compendiums != NULL) + for (k = 0; k < compendiums->nitems; k++) + iconv_message_list (compendiums->item[k], + NULL, canon_charset, + compendium_filenames->item[k]); + conversion_done = true; + } + } + freea (charset); + } + } + if (!conversion_done) + { + if (def->nitems == 0 + || (def->nitems == 1 && def->item[0]->messages->nitems == 0)) + { + /* The definitions file is empty. + Compare the encodings of the compendiums. */ + const char *common_canon_charset = NULL; + + for (k = 0; k < compendiums->nitems; k++) + { + message_list_ty *mlp = compendiums->item[k]; + char *charset = NULL; + const char *canon_charset = NULL; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = + c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + + break; + } + } + } + if (charset != NULL) + { + canon_charset = po_charset_canonicalize (charset); + freea (charset); + } + /* If no charset declaration was found in this file, + or if it is not a valid encoding name, or if it + differs from the common charset found so far, + we have no common charset. */ + if (canon_charset == NULL + || (common_canon_charset != NULL + && canon_charset != common_canon_charset)) + { + common_canon_charset = NULL; + break; + } + common_canon_charset = canon_charset; + } + + if (common_canon_charset != NULL) + /* No conversion needed in this case. */ + conversion_done = true; + } + if (!conversion_done) + { + /* It's too hairy to find out what would be the optimal target + encoding. So, convert everything to UTF-8. */ + def = iconv_msgdomain_list (def, "UTF-8", true, fn1); + if (compendiums != NULL) + for (k = 0; k < compendiums->nitems; k++) + iconv_message_list (compendiums->item[k], + NULL, po_charset_utf8, + compendium_filenames->item[k]); + } + } + } + } + + /* Determine canonicalized encoding name of the definitions now, after + conversion. Only used for fuzzy matching. */ + if (use_fuzzy_matching) + { + def_canon_charset = def->encoding; + if (def_canon_charset == NULL) + { + char *charset = NULL; + + /* Get the encoding of the definitions file. */ + for (k = 0; k < def->nitems; k++) + { + message_list_ty *mlp = def->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + const char *header = mlp->item[j]->msgstr; + + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + break; + } + } + } + if (charset != NULL) + break; + } + if (charset != NULL) + def_canon_charset = po_charset_canonicalize (charset); + if (def_canon_charset == NULL) + /* Unspecified encoding. Assume unibyte encoding. */ + def_canon_charset = po_charset_ascii; + } + } + else + def_canon_charset = NULL; + + /* Initialize and preprocess the total set of message definitions. */ + definitions_init (&definitions, def_canon_charset); + empty_list = message_list_alloc (false); + + result = msgdomain_list_alloc (false); + processed = 0; + + /* Every reference must be matched with its definition. */ + if (!multi_domain_mode) + for (k = 0; k < ref->nitems; k++) + { + const char *domain = ref->item[k]->domain; + message_list_ty *refmlp = ref->item[k]->messages; + message_list_ty *resultmlp = + msgdomain_list_sublist (result, domain, true); + message_list_ty *defmlp; + + defmlp = msgdomain_list_sublist (def, domain, false); + if (defmlp == NULL) + defmlp = empty_list; + definitions_set_current_list (&definitions, defmlp); + + match_domain (fn1, fn2, &definitions, refmlp, resultmlp, + &stats, &processed); + } + else + { + /* Apply the references messages in the default domain to each of + the definition domains. */ + message_list_ty *refmlp = ref->item[0]->messages; + + for (k = 0; k < def->nitems; k++) + { + const char *domain = def->item[k]->domain; + message_list_ty *defmlp = def->item[k]->messages; + + /* Ignore the default message domain if it has no messages. */ + if (k > 0 || defmlp->nitems > 0) + { + message_list_ty *resultmlp = + msgdomain_list_sublist (result, domain, true); + + definitions_set_current_list (&definitions, defmlp); + + match_domain (fn1, fn2, &definitions, refmlp, resultmlp, + &stats, &processed); + } + } + } + + definitions_destroy (&definitions); + + /* Look for messages in the definition file, which are not present + in the reference file, indicating messages which defined but not + used in the program. Don't scan the compendium(s). */ + for (k = 0; k < def->nitems; ++k) + { + const char *domain = def->item[k]->domain; + message_list_ty *defmlp = def->item[k]->messages; + + for (j = 0; j < defmlp->nitems; j++) + { + message_ty *defmsg = defmlp->item[j]; + + if (!defmsg->used) + { + /* Remember the old translation although it is not used anymore. + But we mark it as obsolete. */ + message_ty *mp; + + mp = message_copy (defmsg); + /* Clear the extracted comments. */ + if (mp->comment_dot != NULL) + { + string_list_free (mp->comment_dot); + mp->comment_dot = NULL; + } + /* Clear the file position comments. */ + if (mp->filepos != NULL) + { + size_t i; + + for (i = 0; i < mp->filepos_count; i++) + free ((char *) mp->filepos[i].file_name); + mp->filepos_count = 0; + free (mp->filepos); + mp->filepos = NULL; + } + /* Mark as obsolete. */ + mp->obsolete = true; + + message_list_append (msgdomain_list_sublist (result, domain, true), + mp); + stats.obsolete++; + } + } + } + + /* Determine the known a-priori encoding, if any. */ + if (def->encoding == ref->encoding) + result->encoding = def->encoding; + + /* Report some statistics. */ + if (verbosity_level > 0) + fprintf (stderr, _("%s\ +Read %ld old + %ld reference, \ +merged %ld, fuzzied %ld, missing %ld, obsolete %ld.\n"), + !quiet && verbosity_level <= 1 ? "\n" : "", + (long) def->nitems, (long) ref->nitems, + (long) stats.merged, (long) stats.fuzzied, (long) stats.missing, + (long) stats.obsolete); + else if (!quiet) + fputs (_(" done.\n"), stderr); + + /* Return results. */ + *defp = def; + return result; +} diff --git a/gettext-tools/src/msgunfmt.c b/gettext-tools/src/msgunfmt.c new file mode 100644 index 0000000..1e6dbd2 --- /dev/null +++ b/gettext-tools/src/msgunfmt.c @@ -0,0 +1,555 @@ +/* msgunfmt - converts binary .mo files to Uniforum style .po files + Copyright (C) 1995-1998, 2000-2007, 2009-2010, 2012 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "msgunfmt.h" +#include "read-mo.h" +#include "read-java.h" +#include "read-csharp.h" +#include "read-resources.h" +#include "read-tcl.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Be more verbose. */ +bool verbose; + +/* Java mode input file specification. */ +static bool java_mode; +static const char *java_resource_name; +static const char *java_locale_name; + +/* C# mode input file specification. */ +static bool csharp_mode; +static const char *csharp_resource_name; +static const char *csharp_locale_name; +static const char *csharp_base_directory; + +/* C# resources mode input file specification. */ +static bool csharp_resources_mode; + +/* Tcl mode input file specification. */ +static bool tcl_mode; +static const char *tcl_locale_name; +static const char *tcl_base_directory; + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Long options. */ +static const struct option long_options[] = +{ + { "color", optional_argument, NULL, CHAR_MAX + 6 }, + { "csharp", no_argument, NULL, CHAR_MAX + 4 }, + { "csharp-resources", no_argument, NULL, CHAR_MAX + 5 }, + { "escape", no_argument, NULL, 'E' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, 'i' }, + { "java", no_argument, NULL, 'j' }, + { "locale", required_argument, NULL, 'l' }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 2 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-output", no_argument, NULL, 'p' }, + { "resource", required_argument, NULL, 'r' }, + { "sort-output", no_argument, NULL, 's' }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 3 }, + { "style", required_argument, NULL, CHAR_MAX + 7 }, + { "tcl", no_argument, NULL, CHAR_MAX + 1 }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void read_one_file (message_list_ty *mlp, const char *filename); + + +int +main (int argc, char **argv) +{ + int optchar; + bool do_help = false; + bool do_version = false; + const char *output_file = "-"; + msgdomain_list_ty *result; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_msgid = false; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + while ((optchar = getopt_long (argc, argv, "d:eEhijl:o:pr:svVw:", + long_options, NULL)) + != EOF) + switch (optchar) + { + case '\0': + /* long option */ + break; + + case 'd': + csharp_base_directory = optarg; + tcl_base_directory = optarg; + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'j': + java_mode = true; + break; + + case 'l': + java_locale_name = optarg; + csharp_locale_name = optarg; + tcl_locale_name = optarg; + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'r': + java_resource_name = optarg; + csharp_resource_name = optarg; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 'v': + verbose = true; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: /* --tcl */ + tcl_mode = true; + break; + + case CHAR_MAX + 2: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 3: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 4: /* --csharp */ + csharp_mode = true; + break; + + case CHAR_MAX + 5: /* --csharp-resources */ + csharp_resources_mode = true; + break; + + case CHAR_MAX + 6: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 7: /* --style */ + handle_style_option (optarg); + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "1995-1998, 2000-2010"); + printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Check for contradicting options. */ + { + unsigned int modes = + (java_mode ? 1 : 0) + | (csharp_mode ? 2 : 0) + | (csharp_resources_mode ? 4 : 0) + | (tcl_mode ? 8 : 0); + static const char *mode_options[] = + { "--java", "--csharp", "--csharp-resources", "--tcl" }; + /* More than one bit set? */ + if (modes & (modes - 1)) + { + const char *first_option; + const char *second_option; + unsigned int i; + for (i = 0; ; i++) + if (modes & (1 << i)) + break; + first_option = mode_options[i]; + for (i = i + 1; ; i++) + if (modes & (1 << i)) + break; + second_option = mode_options[i]; + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + first_option, second_option); + } + } + if (java_mode) + { + if (optind < argc) + { + error (EXIT_FAILURE, 0, + _("%s and explicit file names are mutually exclusive"), + "--java"); + } + } + else if (csharp_mode) + { + if (optind < argc) + { + error (EXIT_FAILURE, 0, + _("%s and explicit file names are mutually exclusive"), + "--csharp"); + } + if (csharp_locale_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-l locale\" specification"), + "--csharp"); + usage (EXIT_FAILURE); + } + if (csharp_base_directory == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-d directory\" specification"), + "--csharp"); + usage (EXIT_FAILURE); + } + } + else if (tcl_mode) + { + if (optind < argc) + { + error (EXIT_FAILURE, 0, + _("%s and explicit file names are mutually exclusive"), + "--tcl"); + } + if (tcl_locale_name == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-l locale\" specification"), + "--tcl"); + usage (EXIT_FAILURE); + } + if (tcl_base_directory == NULL) + { + error (EXIT_SUCCESS, 0, + _("%s requires a \"-d directory\" specification"), + "--tcl"); + usage (EXIT_FAILURE); + } + } + else + { + if (java_resource_name != NULL) + { + error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"), + "--resource", "--java", "--csharp"); + usage (EXIT_FAILURE); + } + if (java_locale_name != NULL) + { + error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"), + "--locale", "--java", "--csharp"); + usage (EXIT_FAILURE); + } + } + + /* Read the given .mo file. */ + if (java_mode) + { + result = msgdomain_read_java (java_resource_name, java_locale_name); + } + else if (csharp_mode) + { + result = msgdomain_read_csharp (csharp_resource_name, csharp_locale_name, + csharp_base_directory); + } + else if (tcl_mode) + { + result = msgdomain_read_tcl (tcl_locale_name, tcl_base_directory); + } + else + { + message_list_ty *mlp; + + mlp = message_list_alloc (false); + if (optind < argc) + { + do + read_one_file (mlp, argv[optind]); + while (++optind < argc); + } + else + read_one_file (mlp, "-"); + + result = msgdomain_list_alloc (false); + result->item[0]->messages = mlp; + } + + /* Sorting the list of messages. */ + if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Write the resulting message list to the given .po file. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + /* No problems. */ + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [FILE]...\n\ +"), program_name); + printf ("\n"); + printf (_("\ +Convert binary message catalog to Uniforum style .po file.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Operation mode:\n")); + printf (_("\ + -j, --java Java mode: input is a Java ResourceBundle class\n")); + printf (_("\ + --csharp C# mode: input is a .NET .dll file\n")); + printf (_("\ + --csharp-resources C# resources mode: input is a .NET .resources file\n")); + printf (_("\ + --tcl Tcl mode: input is a tcl/msgcat .msg file\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + FILE ... input .mo files\n")); + printf (_("\ +If no input file is given or if it is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Input file location in Java mode:\n")); + printf (_("\ + -r, --resource=RESOURCE resource name\n")); + printf (_("\ + -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); + printf (_("\ +The class name is determined by appending the locale name to the resource name,\n\ +separated with an underscore. The class is located using the CLASSPATH.\n\ +")); + printf ("\n"); + printf (_("\ +Input file location in C# mode:\n")); + printf (_("\ + -r, --resource=RESOURCE resource name\n")); + printf (_("\ + -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); + printf (_("\ + -d DIRECTORY base directory for locale dependent .dll files\n")); + printf (_("\ +The -l and -d options are mandatory. The .dll file is located in a\n\ +subdirectory of the specified directory whose name depends on the locale.\n")); + printf ("\n"); + printf (_("\ +Input file location in Tcl mode:\n")); + printf (_("\ + -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); + printf (_("\ + -d DIRECTORY base directory of .msg message catalogs\n")); + printf (_("\ +The -l and -d options are mandatory. The .msg file is located in the\n\ +specified directory.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent write indented output style\n")); + printf (_("\ + --strict write strict uniforum style\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf (_("\ + -v, --verbose increase verbosity level\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + + +static void +read_one_file (message_list_ty *mlp, const char *filename) +{ + if (csharp_resources_mode) + read_resources_file (mlp, filename); + else + read_mo_file (mlp, filename); +} diff --git a/gettext-tools/src/msgunfmt.cs b/gettext-tools/src/msgunfmt.cs new file mode 100644 index 0000000..28a66b8 --- /dev/null +++ b/gettext-tools/src/msgunfmt.cs @@ -0,0 +1,241 @@ +/* GNU gettext for C# + * Copyright (C) 2003-2004, 2007 Free Software Foundation, Inc. + * Written by Bruno Haible , 2003. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * This program dumps a GettextResourceSet subclass (in a satellite assembly) + * or a .resources file as a PO file. + */ + +using System; /* Object, String, Type, Console, Exception */ +using System.Reflection; /* Assembly, MethodInfo, ConstructorInfo */ +using System.Collections; /* Hashtable, DictionaryEntry */ +using System.IO; /* BufferedStream, StreamWriter, TextWriter, FileNotFoundException, Path */ +using System.Text; /* StringBuilder, UTF8Encoding */ +using System.Resources; /* ResourceReader */ +using GNU.Gettext; /* GettextResourceSet */ + +namespace GNU.Gettext { + public class DumpResource { + private TextWriter Out; + private void DumpString (String str) { + int n = str.Length; + Out.Write('"'); + for (int i = 0; i < n; i++) { + char c = str[i]; + if (c == 0x0008) { + Out.Write('\\'); Out.Write('b'); + } else if (c == 0x000c) { + Out.Write('\\'); Out.Write('f'); + } else if (c == 0x000a) { + Out.Write('\\'); Out.Write('n'); + } else if (c == 0x000d) { + Out.Write('\\'); Out.Write('r'); + } else if (c == 0x0009) { + Out.Write('\\'); Out.Write('t'); + } else if (c == '\\' || c == '"') { + Out.Write('\\'); Out.Write(c); + } else + Out.Write(c); + } + Out.Write('"'); + } + private void DumpMessage (String msgid, String msgid_plural, Object msgstr) { + int separatorPos = msgid.IndexOf('\u0004'); + if (separatorPos >= 0) { + String msgctxt = msgid.Substring(0,separatorPos); + msgid = msgid.Substring(separatorPos+1); + Out.Write("msgctxt "); DumpString(msgctxt); + } + Out.Write("msgid "); DumpString(msgid); Out.Write('\n'); + if (msgid_plural != null) { + Out.Write("msgid_plural "); DumpString(msgid_plural); Out.Write('\n'); + for (int i = 0; i < (msgstr as String[]).Length; i++) { + Out.Write("msgstr[" + i + "] "); + DumpString((msgstr as String[])[i]); + Out.Write('\n'); + } + } else { + Out.Write("msgstr "); DumpString(msgstr as String); Out.Write('\n'); + } + Out.Write('\n'); + } + + // ---------------- Dumping a GettextResourceSet ---------------- + + private void Dump (GettextResourceSet catalog) { + MethodInfo pluralMethod = + catalog.GetType().GetMethod("GetMsgidPluralTable", Type.EmptyTypes); + // Search for the header entry. + { + Object header_entry = catalog.GetObject(""); + // If there is no header entry, fake one. + // FIXME: This is not needed; right after po_lex_charset_init set + // the PO charset to UTF-8. + if (header_entry == null) + header_entry = "Content-Type: text/plain; charset=UTF-8\n"; + DumpMessage("", null, header_entry); + } + // Now the other messages. + { + Hashtable plural = null; + if (pluralMethod != null) + plural = pluralMethod.Invoke(catalog, new Object[0]) as Hashtable; + foreach (String key in catalog.Keys) + if (!"".Equals(key)) { + Object value = catalog.GetObject(key); + String key_plural = + (plural != null && value is String[] ? plural[key] as String : null); + DumpMessage(key, key_plural, value); + } + } + } + // Essentially taken from class GettextResourceManager. + private static Assembly GetSatelliteAssembly (String baseDirectory, String resourceName, String cultureName) { + String satelliteExpectedLocation = + baseDirectory + + Path.DirectorySeparatorChar + cultureName + + Path.DirectorySeparatorChar + resourceName + ".resources.dll"; + return Assembly.LoadFrom(satelliteExpectedLocation); + } + // Taken from class GettextResourceManager. + private static String ConstructClassName (String resourceName) { + // We could just return an arbitrary fixed class name, like "Messages", + // assuming that every assembly will only ever contain one + // GettextResourceSet subclass, but this assumption would break the day + // we want to support multi-domain PO files in the same format... + bool valid = (resourceName.Length > 0); + for (int i = 0; valid && i < resourceName.Length; i++) { + char c = resourceName[i]; + if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') + || (i > 0 && c >= '0' && c <= '9'))) + valid = false; + } + if (valid) + return resourceName; + else { + // Use hexadecimal escapes, using the underscore as escape character. + String hexdigit = "0123456789abcdef"; + StringBuilder b = new StringBuilder(); + b.Append("__UESCAPED__"); + for (int i = 0; i < resourceName.Length; i++) { + char c = resourceName[i]; + if (c >= 0xd800 && c < 0xdc00 + && i+1 < resourceName.Length + && resourceName[i+1] >= 0xdc00 && resourceName[i+1] < 0xe000) { + // Combine two UTF-16 words to a character. + char c2 = resourceName[i+1]; + int uc = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + b.Append('_'); + b.Append('U'); + b.Append(hexdigit[(uc >> 28) & 0x0f]); + b.Append(hexdigit[(uc >> 24) & 0x0f]); + b.Append(hexdigit[(uc >> 20) & 0x0f]); + b.Append(hexdigit[(uc >> 16) & 0x0f]); + b.Append(hexdigit[(uc >> 12) & 0x0f]); + b.Append(hexdigit[(uc >> 8) & 0x0f]); + b.Append(hexdigit[(uc >> 4) & 0x0f]); + b.Append(hexdigit[uc & 0x0f]); + i++; + } else if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9'))) { + int uc = c; + b.Append('_'); + b.Append('u'); + b.Append(hexdigit[(uc >> 12) & 0x0f]); + b.Append(hexdigit[(uc >> 8) & 0x0f]); + b.Append(hexdigit[(uc >> 4) & 0x0f]); + b.Append(hexdigit[uc & 0x0f]); + } else + b.Append(c); + } + return b.ToString(); + } + } + // Essentially taken from class GettextResourceManager. + private static GettextResourceSet InstantiateResourceSet (Assembly satelliteAssembly, String resourceName, String cultureName) { + Type clazz = satelliteAssembly.GetType(ConstructClassName(resourceName)+"_"+cultureName.Replace('-','_')); + ConstructorInfo constructor = clazz.GetConstructor(Type.EmptyTypes); + return constructor.Invoke(null) as GettextResourceSet; + } + public DumpResource (String baseDirectory, String resourceName, String cultureName) { + // We are only interested in the messages belonging to the locale + // itself, not in the inherited messages. Therefore we instantiate just + // the GettextResourceSet, not a GettextResourceManager. + Assembly satelliteAssembly = + GetSatelliteAssembly(baseDirectory, resourceName, cultureName); + GettextResourceSet catalog = + InstantiateResourceSet(satelliteAssembly, resourceName, cultureName); + BufferedStream stream = new BufferedStream(Console.OpenStandardOutput()); + Out = new StreamWriter(stream, new UTF8Encoding()); + Dump(catalog); + Out.Close(); + stream.Close(); + } + + // ----------------- Dumping a .resources file ------------------ + + public DumpResource (String filename) { + BufferedStream stream = new BufferedStream(Console.OpenStandardOutput()); + Out = new StreamWriter(stream, new UTF8Encoding()); + ResourceReader rr; + if (filename.Equals("-")) { + BufferedStream input = new BufferedStream(Console.OpenStandardInput()); + // A temporary output stream is needed because ResourceReader expects + // to be able to seek in the Stream. + byte[] contents; + { + MemoryStream tmpstream = new MemoryStream(); + byte[] buf = new byte[1024]; + for (;;) { + int n = input.Read(buf, 0, 1024); + if (n == 0) + break; + tmpstream.Write(buf, 0, n); + } + contents = tmpstream.ToArray(); + tmpstream.Close(); + } + MemoryStream tmpinput = new MemoryStream(contents); + rr = new ResourceReader(tmpinput); + } else { + rr = new ResourceReader(filename); + } + foreach (DictionaryEntry entry in rr) // uses rr.GetEnumerator() + DumpMessage(entry.Key as String, null, entry.Value as String); + rr.Close(); + Out.Close(); + stream.Close(); + } + + // -------------------------------------------------------------- + + public static int Main (String[] args) { + try { + if (args.Length > 1) + new DumpResource(args[0], args[1], args[2]); + else + new DumpResource(args[0]); + } catch (Exception e) { + Console.Error.WriteLine(e); + Console.Error.WriteLine(e.StackTrace); + return 1; + } + return 0; + } + } +} diff --git a/gettext-tools/src/msgunfmt.h b/gettext-tools/src/msgunfmt.h new file mode 100644 index 0000000..d1d79ec --- /dev/null +++ b/gettext-tools/src/msgunfmt.h @@ -0,0 +1,24 @@ +/* msgunfmt specifics + Copyright (C) 1995-1998, 2000, 2001 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _MSGUNFMT_H +#define _MSGUNFMT_H + +/* Be more verbose. */ +extern bool verbose; + +#endif /* _MSGUNFMT_H */ diff --git a/gettext-tools/src/msgunfmt.tcl b/gettext-tools/src/msgunfmt.tcl new file mode 100644 index 0000000..11c1d6f --- /dev/null +++ b/gettext-tools/src/msgunfmt.tcl @@ -0,0 +1,82 @@ +# Reading tcl/msgcat .msg files. +# Copyright (C) 2002 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +namespace eval msgcat { + namespace export mcset mcdump + variable header "" +} + +proc msgcat::puts_po_string {str} { + # Replace " with \" + regsub -all "\"" $str "\\\"" str + # Replace \ with \\ + regsub -all "\\\\" $str "\\\\\\" str + # Replace newline with \n + regsub -all [subst "\n"] $str "\\n" str + regsub -all [subst "\a"] $str "\\a" str + regsub -all [subst "\b"] $str "\\b" str + regsub -all [subst "\f"] $str "\\f" str + regsub -all [subst "\r"] $str "\\r" str + regsub -all [subst "\t"] $str "\\t" str + regsub -all [subst "\v"] $str "\\v" str + # Output it. + puts -nonewline "\"$str\"" +} + +proc msgcat::write_po_message {msgid msgstr} { + puts -nonewline "msgid " + puts_po_string $msgid + puts "" + puts -nonewline "msgstr " + puts_po_string $msgstr + puts "" + puts "" +} + +# This gets called once for each message in the .msg catalog. +proc msgcat::mcset {locale src {dest ""}} { + msgcat::write_po_message $src $dest +} + +# Main function. +proc msgcat::mcdump {langfile} { + if {[file exists $langfile]} { + # msgunfmt expects the output in UTF-8 encoding. + fconfigure stdout -encoding utf-8 + + set msgcat::header "" + + set fd [open $langfile r] + # In newer tcl versions, the .msg files are in UTF-8 encoding. + fconfigure $fd -encoding utf-8 + eval [read $fd] + close $fd + + if {$msgcat::header == ""} { + # Provide a minimal header. + set msgcat::header [subst "MIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n"] + } + msgcat::write_po_message "" $msgcat::header + } else { + # Tell msgunfmt to emit an internationalized error message. + exit 2 + } +} + +# Main code: call the main function on the first and only argument. +msgcat::mcdump [lindex $argv 0] + +exit 0 diff --git a/gettext-tools/src/msguniq.c b/gettext-tools/src/msguniq.c new file mode 100644 index 0000000..24f4c31 --- /dev/null +++ b/gettext-tools/src/msguniq.c @@ -0,0 +1,434 @@ +/* Remove, select or merge duplicate translations. + Copyright (C) 2001-2007, 2009-2010, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "dir-list.h" +#include "str-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "message.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "msgl-cat.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Target encoding. */ +static const char *to_code; + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-location", optional_argument, NULL, 'n' }, + { "color", optional_argument, NULL, CHAR_MAX + 5 }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "force-po", no_argument, &force_po, 1 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, 'i' }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-location", no_argument, NULL, CHAR_MAX + 7 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 2 }, + { "output-file", required_argument, NULL, 'o' }, + { "properties-input", no_argument, NULL, 'P' }, + { "properties-output", no_argument, NULL, 'p' }, + { "repeated", no_argument, NULL, 'd' }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "strict", no_argument, NULL, 'S' }, + { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 }, + { "style", required_argument, NULL, CHAR_MAX + 6 }, + { "to-code", required_argument, NULL, 't' }, + { "unique", no_argument, NULL, 'u' }, + { "use-first", no_argument, NULL, CHAR_MAX + 1 }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; + + +int +main (int argc, char **argv) +{ + int optchar; + bool do_help; + bool do_version; + char *output_file; + const char *input_file; + string_list_ty *file_list; + msgdomain_list_ty *result; + catalog_input_format_ty input_syntax = &input_format_po; + catalog_output_format_ty output_syntax = &output_format_po; + bool sort_by_msgid = false; + bool sort_by_filepos = false; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + input_file = NULL; + more_than = 0; + less_than = INT_MAX; + use_first = false; + + while ((optchar = getopt_long (argc, argv, "dD:eEFhino:pPst:uVw:", + long_options, NULL)) != EOF) + switch (optchar) + { + case '\0': /* Long option. */ + break; + + case 'd': + more_than = 1; + less_than = INT_MAX; + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + output_syntax = &output_format_properties; + break; + + case 'P': + input_syntax = &input_format_properties; + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 't': + to_code = optarg; + break; + + case 'u': + more_than = 0; + less_than = 2; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case CHAR_MAX + 1: + use_first = true; + break; + + case CHAR_MAX + 2: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 3: /* --stringtable-input */ + input_syntax = &input_format_stringtable; + break; + + case CHAR_MAX + 4: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 5: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 6: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 7: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + /* NOTREACHED */ + } + + /* Version information requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2010"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test whether we have an .po file name as argument. */ + if (optind == argc) + input_file = "-"; + else if (optind + 1 == argc) + input_file = argv[optind]; + else + { + error (EXIT_SUCCESS, 0, _("at most one input file allowed")); + usage (EXIT_FAILURE); + } + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* Determine list of files we have to process: a single file. */ + file_list = string_list_alloc (); + string_list_append (file_list, input_file); + + /* Read input files, then filter, convert and merge messages. */ + allow_duplicates = true; + result = catenate_msgdomain_list (file_list, input_syntax, to_code); + + string_list_free (file_list); + + /* Sorting the list of messages. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (result); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (result); + + /* Write the PO file. */ + msgdomain_list_print (result, output_file, output_syntax, force_po, false); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [INPUTFILE]\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Unifies duplicate translations in a translation catalog.\n\ +Finds duplicate translations of the same message ID. Such duplicates are\n\ +invalid input for other programs like msgfmt, msgmerge or msgcat. By\n\ +default, duplicates are merged together. When using the --repeated option,\n\ +only duplicates are output, and all other messages are discarded. Comments\n\ +and extracted comments will be cumulated, except that if --use-first is\n\ +specified, they will be taken from the first translation. File positions\n\ +will be cumulated. When using the --unique option, duplicates are discarded.\n\ +")); + printf ("\n"); + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + INPUTFILE input PO file\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If no input file is given or if it is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -o, --output-file=FILE write output to specified file\n")); + printf (_("\ +The results are written to standard output if no output file is specified\n\ +or if it is -.\n")); + printf ("\n"); + printf (_("\ +Message selection:\n")); + printf (_("\ + -d, --repeated print only duplicates\n")); + printf (_("\ + -u, --unique print only unique messages, discard duplicates\n")); + printf ("\n"); + printf (_("\ +Input file syntax:\n")); + printf (_("\ + -P, --properties-input input file is in Java .properties syntax\n")); + printf (_("\ + --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + -t, --to-code=NAME encoding for output\n")); + printf (_("\ + --use-first use first available translation for each\n\ + message, don't merge several translations\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent write the .po file using indented style\n")); + printf (_("\ + --no-location do not write '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location generate '#: filename:line' lines (default)\n")); + printf (_("\ + --strict write out strict Uniforum conforming .po file\n")); + printf (_("\ + -p, --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + diff --git a/gettext-tools/src/open-catalog.c b/gettext-tools/src/open-catalog.c new file mode 100644 index 0000000..27eed90 --- /dev/null +++ b/gettext-tools/src/open-catalog.c @@ -0,0 +1,128 @@ +/* open-po - search for .po file along search path list and open for reading + Copyright (C) 1995-1996, 2000-2003, 2005-2009 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "open-catalog.h" + +#include +#include +#include +#include +#include + +#include "dir-list.h" +#include "filename.h" +#include "concat-filename.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "po-xerror.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* This macro is used to determine the number of elements in an array. */ +#define SIZEOF(a) (sizeof(a)/sizeof(a[0])) + +static FILE * +try_open_catalog_file (const char *input_name, char **real_file_name_p) +{ + static const char *extension[] = { "", ".po", ".pot", }; + char *file_name; + FILE *ret_val; + int j; + size_t k; + const char *dir; + + if (strcmp (input_name, "-") == 0 || strcmp (input_name, "/dev/stdin") == 0) + { + *real_file_name_p = xstrdup (_("")); + return stdin; + } + + /* We have a real name for the input file. If the name is absolute, + try the various extensions, but ignore the directory search list. */ + if (IS_ABSOLUTE_PATH (input_name)) + { + for (k = 0; k < SIZEOF (extension); ++k) + { + file_name = xconcatenated_filename ("", input_name, extension[k]); + + ret_val = fopen (file_name, "r"); + if (ret_val != NULL || errno != ENOENT) + { + /* We found the file. */ + *real_file_name_p = file_name; + return ret_val; + } + + free (file_name); + } + } + else + { + /* For relative file names, look through the directory search list, + trying the various extensions. If no directory search list is + specified, the current directory is used. */ + for (j = 0; (dir = dir_list_nth (j)) != NULL; ++j) + for (k = 0; k < SIZEOF (extension); ++k) + { + file_name = xconcatenated_filename (dir, input_name, extension[k]); + + ret_val = fopen (file_name, "r"); + if (ret_val != NULL || errno != ENOENT) + { + *real_file_name_p = file_name; + return ret_val; + } + + free (file_name); + } + } + + /* File does not exist. */ + *real_file_name_p = xstrdup (input_name); + errno = ENOENT; + return NULL; +} + +/* Open the input file with the name INPUT_NAME. The ending .po is added + if necessary. If INPUT_NAME is not an absolute file name and the file is + not found, the list of directories in "dir-list.h" is searched. The + file's pathname is returned in *REAL_FILE_NAME_P, for error message + purposes. */ +FILE * +open_catalog_file (const char *input_name, char **real_file_name_p, + bool exit_on_error) +{ + FILE *fp = try_open_catalog_file (input_name, real_file_name_p); + + if (fp == NULL && exit_on_error) + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("error while opening \"%s\" for reading"), + *real_file_name_p), + errno_description)); + } + + return fp; +} diff --git a/gettext-tools/src/open-catalog.h b/gettext-tools/src/open-catalog.h new file mode 100644 index 0000000..503371d --- /dev/null +++ b/gettext-tools/src/open-catalog.h @@ -0,0 +1,43 @@ +/* Opening PO files. + Copyright (C) 1995-1997, 2000-2003, 2006 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _OPEN_CATALOG_H +#define _OPEN_CATALOG_H + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Open the input file with the name INPUT_NAME. The ending .po is added + if necessary. If INPUT_NAME is not an absolute file name and the file is + not found, the list of directories in "dir-list.h" is searched. The + file's pathname is returned in *REAL_FILE_NAME_P, for error message + purposes. */ +extern FILE *open_catalog_file (const char *input_name, + char **real_file_name_p, bool exit_on_error); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _OPEN_CATALOG_H */ diff --git a/gettext-tools/src/plural-count.c b/gettext-tools/src/plural-count.c new file mode 100644 index 0000000..3fd8838 --- /dev/null +++ b/gettext-tools/src/plural-count.c @@ -0,0 +1,38 @@ +/* Plural form count. + Copyright (C) 2003, 2007 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "plural-count.h" + +#include "plural-exp.h" + +/* Extract the number of plural forms from a header entry. */ + +unsigned long int +get_plural_count (const char *header) +{ + const struct expression *plural; + unsigned long int nplurals; + + extract_plural_expression (header, &plural, &nplurals); + + return nplurals; +} diff --git a/gettext-tools/src/plural-count.h b/gettext-tools/src/plural-count.h new file mode 100644 index 0000000..14eccf9 --- /dev/null +++ b/gettext-tools/src/plural-count.h @@ -0,0 +1,29 @@ +/* Plural form count. + Copyright (C) 2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Extract the number of plural forms from a header entry. */ +extern unsigned long int get_plural_count (const char *header); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/plural-distrib.h b/gettext-tools/src/plural-distrib.h new file mode 100644 index 0000000..4376f9f --- /dev/null +++ b/gettext-tools/src/plural-distrib.h @@ -0,0 +1,57 @@ +/* Value distribution of plural form expressions. + Copyright (C) 2001-2008 Free Software Foundation, Inc. + Written by Bruno Haible , 2001-2005. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PLURAL_DISTRIB_H +#define _PLURAL_DISTRIB_H + + +/* Definition of 'struct expression'. */ +#include "plural-exp.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* The value distribution of a plural formula. */ +struct plural_distribution +{ + /* The plural formula as a parsed expression. */ + const struct expression *expr; + + /* OFTEN is either NULL or an array of nplurals elements, + OFTEN[j] being true if the value j appears to be assumed infinitely often + by the plural formula. */ + const unsigned char *often; + + /* The length of the OFTEN array. */ + unsigned long often_length; + + /* A function which evaluates the plural formula for min <= n <= max + and returns the estimated number of times the value j was assumed. */ + unsigned int (*histogram) (const struct plural_distribution *self, + int min, int max, unsigned long j); +}; + + +#ifdef __cplusplus +} +#endif + + +#endif /* _PLURAL_DISTRIB_H */ diff --git a/gettext-tools/src/plural-eval.c b/gettext-tools/src/plural-eval.c new file mode 100644 index 0000000..6c63d4e --- /dev/null +++ b/gettext-tools/src/plural-eval.c @@ -0,0 +1,93 @@ +/* Expression evaluation for plural form selection. + Copyright (C) 2000-2003, 2005 Free Software Foundation, Inc. + Written by Ulrich Drepper , 2000. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "plural-eval.h" + +#include +#include + +#include "plural-exp.h" + + +#define STATIC /*extern*/ + +/* Include the expression evaluation code from libintl, this time with + 'extern' linkage. */ +#include "eval-plural.h" + + +/* Exit point. Must be set before calling install_sigfpe_handler(). */ +sigjmp_buf sigfpe_exit; + +#if USE_SIGINFO + +/* Additional information that is set before sigfpe_exit is invoked. */ +int sigfpe_code; + +/* Signal handler called in case of arithmetic exception (e.g. division + by zero) during plural_eval. */ +static void +sigfpe_handler (int sig, siginfo_t *sip, void *scp) +{ + sigfpe_code = sip->si_code; + siglongjmp (sigfpe_exit, 1); +} + +#else + +/* Signal handler called in case of arithmetic exception (e.g. division + by zero) during plural_eval. */ +static void +sigfpe_handler (int sig) +{ + siglongjmp (sigfpe_exit, 1); +} + +#endif + +void +install_sigfpe_handler (void) +{ +#if USE_SIGINFO + struct sigaction action; + action.sa_sigaction = sigfpe_handler; + action.sa_flags = SA_SIGINFO; + sigemptyset (&action.sa_mask); + sigaction (SIGFPE, &action, (struct sigaction *) NULL); +#else + signal (SIGFPE, sigfpe_handler); +#endif +} + +void +uninstall_sigfpe_handler (void) +{ +#if USE_SIGINFO + struct sigaction action; + action.sa_handler = SIG_DFL; + action.sa_flags = 0; + sigemptyset (&action.sa_mask); + sigaction (SIGFPE, &action, (struct sigaction *) NULL); +#else + signal (SIGFPE, SIG_DFL); +#endif +} diff --git a/gettext-tools/src/plural-eval.h b/gettext-tools/src/plural-eval.h new file mode 100644 index 0000000..b64c079 --- /dev/null +++ b/gettext-tools/src/plural-eval.h @@ -0,0 +1,67 @@ +/* Expression evaluation for plural form selection. + Copyright (C) 2005-2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2005. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PLURAL_EVAL_H +#define _PLURAL_EVAL_H + + +/* Definition of 'struct expression', and + declaration of extract_plural_expression() and plural_eval(). */ +#include "plural-exp.h" + + +/* Protection against signals during plural evaluation. */ + +#include + +/* Some platforms don't have the sigjmp_buf type in . */ +#if defined _MSC_VER || defined __MINGW32__ +/* Native Woe32 API. */ +# define sigjmp_buf jmp_buf +# define sigsetjmp(env,savesigs) setjmp (env) +# define siglongjmp longjmp +#endif + +/* We use siginfo to get precise information about the signal. + But siginfo doesn't work on Irix 6.5 and on Cygwin 2005. */ +#if HAVE_SIGINFO && !defined (__sgi) && !defined (__CYGWIN__) +# define USE_SIGINFO 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Exit point. Must be set before calling install_sigfpe_handler(). */ +extern sigjmp_buf sigfpe_exit; + +#if USE_SIGINFO +/* Additional information that is set before sigfpe_exit is invoked. */ +extern int sigfpe_code; +#endif + +/* Protect against signals during plural evaluation. Must be called around + calls to plural_eval(). Must be called in pairs. */ +extern void install_sigfpe_handler (void); +extern void uninstall_sigfpe_handler (void); + +#ifdef __cplusplus +} +#endif + + +#endif /* _PLURAL_EVAL_H */ diff --git a/gettext-tools/src/plural-exp.c b/gettext-tools/src/plural-exp.c new file mode 100644 index 0000000..d3f4b5f --- /dev/null +++ b/gettext-tools/src/plural-exp.c @@ -0,0 +1,21 @@ +/* Expression parsing for plural form selection. + Copyright (C) 2000-2001, 2003 Free Software Foundation, Inc. + Written by Ulrich Drepper , 2000. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Include the expression parsing code from libintl, with different function + names. */ +#include "../intl/pluralx.c" +#include "../../gettext-runtime/intl/plural-exp.c" diff --git a/gettext-tools/src/plural-table.c b/gettext-tools/src/plural-table.c new file mode 100644 index 0000000..556cbcc --- /dev/null +++ b/gettext-tools/src/plural-table.c @@ -0,0 +1,67 @@ +/* Table of known plural form expressions. + Copyright (C) 2001-2006, 2009-2010 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "plural-table.h" + +/* Formulas taken from the documentation, node "Plural forms". */ +struct plural_table_entry plural_table[] = + { + { "ja", "Japanese", "nplurals=1; plural=0;" }, + { "vi", "Vietnamese", "nplurals=1; plural=0;" }, + { "ko", "Korean", "nplurals=1; plural=0;" }, + { "en", "English", "nplurals=2; plural=(n != 1);" }, + { "de", "German", "nplurals=2; plural=(n != 1);" }, + { "nl", "Dutch", "nplurals=2; plural=(n != 1);" }, + { "sv", "Swedish", "nplurals=2; plural=(n != 1);" }, + { "da", "Danish", "nplurals=2; plural=(n != 1);" }, + { "no", "Norwegian", "nplurals=2; plural=(n != 1);" }, + { "nb", "Norwegian Bokmal", "nplurals=2; plural=(n != 1);" }, + { "nn", "Norwegian Nynorsk", "nplurals=2; plural=(n != 1);" }, + { "fo", "Faroese", "nplurals=2; plural=(n != 1);" }, + { "es", "Spanish", "nplurals=2; plural=(n != 1);" }, + { "pt", "Portuguese", "nplurals=2; plural=(n != 1);" }, + { "it", "Italian", "nplurals=2; plural=(n != 1);" }, + { "bg", "Bulgarian", "nplurals=2; plural=(n != 1);" }, + { "el", "Greek", "nplurals=2; plural=(n != 1);" }, + { "fi", "Finnish", "nplurals=2; plural=(n != 1);" }, + { "et", "Estonian", "nplurals=2; plural=(n != 1);" }, + { "he", "Hebrew", "nplurals=2; plural=(n != 1);" }, + { "eo", "Esperanto", "nplurals=2; plural=(n != 1);" }, + { "hu", "Hungarian", "nplurals=2; plural=(n != 1);" }, + { "tr", "Turkish", "nplurals=2; plural=(n != 1);" }, + { "pt_BR", "Brazilian", "nplurals=2; plural=(n > 1);" }, + { "fr", "French", "nplurals=2; plural=(n > 1);" }, + { "lv", "Latvian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" }, + { "ga", "Irish", "nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;" }, + { "ro", "Romanian", "nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;" }, + { "lt", "Lithuanian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "ru", "Russian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "uk", "Ukrainian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "be", "Belarusian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "sr", "Serbian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "hr", "Croatian", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "cs", "Czech", "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" }, + { "sk", "Slovak", "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;" }, + { "pl", "Polish", "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "sl", "Slovenian", "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" } + }; +const size_t plural_table_size = sizeof (plural_table) / sizeof (plural_table[0]); diff --git a/gettext-tools/src/plural-table.h b/gettext-tools/src/plural-table.h new file mode 100644 index 0000000..6af9bf6 --- /dev/null +++ b/gettext-tools/src/plural-table.h @@ -0,0 +1,33 @@ +/* Table of known plural form expressions. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PLURAL_TABLE_H +#define _PLURAL_TABLE_H + +#include + +struct plural_table_entry +{ + const char *lang; + const char *language; + const char *value; +}; + +extern DLL_VARIABLE struct plural_table_entry plural_table[]; +extern DLL_VARIABLE const size_t plural_table_size; + +#endif /* _PLURAL_TABLE_H */ diff --git a/gettext-tools/src/po-charset.c b/gettext-tools/src/po-charset.c new file mode 100644 index 0000000..4c0dcdb --- /dev/null +++ b/gettext-tools/src/po-charset.c @@ -0,0 +1,662 @@ +/* Charset handling while reading PO files. + Copyright (C) 2001-2007, 2010 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +/* Specification. */ +#include "po-charset.h" + +#include +#include + +#include "xmalloca.h" +#include "xvasprintf.h" +#include "po-xerror.h" +#include "basename.h" +#include "progname.h" +#include "c-strstr.h" +#include "c-strcase.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + +static const char ascii[] = "ASCII"; + +/* The canonicalized encoding name for ASCII. */ +const char *po_charset_ascii = ascii; + +static const char utf8[] = "UTF-8"; + +/* The canonicalized encoding name for UTF-8. */ +const char *po_charset_utf8 = utf8; + +/* Canonicalize an encoding name. */ +const char * +po_charset_canonicalize (const char *charset) +{ + /* The list of charsets supported by glibc's iconv() and by the portable + iconv() across platforms. Taken from intl/config.charset. */ + static const char *standard_charsets[] = + { + ascii, "ANSI_X3.4-1968", "US-ASCII", /* i = 0..2 */ + "ISO-8859-1", "ISO_8859-1", /* i = 3, 4 */ + "ISO-8859-2", "ISO_8859-2", + "ISO-8859-3", "ISO_8859-3", + "ISO-8859-4", "ISO_8859-4", + "ISO-8859-5", "ISO_8859-5", + "ISO-8859-6", "ISO_8859-6", + "ISO-8859-7", "ISO_8859-7", + "ISO-8859-8", "ISO_8859-8", + "ISO-8859-9", "ISO_8859-9", + "ISO-8859-13", "ISO_8859-13", + "ISO-8859-14", "ISO_8859-14", + "ISO-8859-15", "ISO_8859-15", /* i = 25, 26 */ + "KOI8-R", + "KOI8-U", + "KOI8-T", + "CP850", + "CP866", + "CP874", + "CP932", + "CP949", + "CP950", + "CP1250", + "CP1251", + "CP1252", + "CP1253", + "CP1254", + "CP1255", + "CP1256", + "CP1257", + "GB2312", + "EUC-JP", + "EUC-KR", + "EUC-TW", + "BIG5", + "BIG5-HKSCS", + "GBK", + "GB18030", + "SHIFT_JIS", + "JOHAB", + "TIS-620", + "VISCII", + "GEORGIAN-PS", + utf8 + }; + size_t i; + + for (i = 0; i < SIZEOF (standard_charsets); i++) + if (c_strcasecmp (charset, standard_charsets[i]) == 0) + return standard_charsets[i < 3 ? 0 : i < 27 ? ((i - 3) & ~1) + 3 : i]; + return NULL; +} + +/* Test for ASCII compatibility. */ +bool +po_charset_ascii_compatible (const char *canon_charset) +{ + /* There are only a few exceptions to ASCII compatibility. */ + if (strcmp (canon_charset, "SHIFT_JIS") == 0 + || strcmp (canon_charset, "JOHAB") == 0 + || strcmp (canon_charset, "VISCII") == 0) + return false; + else + return true; +} + +/* Test for a weird encoding, i.e. an encoding which has double-byte + characters ending in 0x5C. */ +bool po_is_charset_weird (const char *canon_charset) +{ + static const char *weird_charsets[] = + { + "BIG5", + "BIG5-HKSCS", + "GBK", + "GB18030", + "SHIFT_JIS", + "JOHAB" + }; + size_t i; + + for (i = 0; i < SIZEOF (weird_charsets); i++) + if (strcmp (canon_charset, weird_charsets[i]) == 0) + return true; + return false; +} + +/* Test for a weird CJK encoding, i.e. a weird encoding with CJK structure. + An encoding has CJK structure if every valid character stream is composed + of single bytes in the range 0x{00..7F} and of byte pairs in the range + 0x{80..FF}{30..FF}. */ +bool po_is_charset_weird_cjk (const char *canon_charset) +{ + static const char *weird_cjk_charsets[] = + { /* single bytes double bytes */ + "BIG5", /* 0x{00..7F}, 0x{A1..F9}{40..FE} */ + "BIG5-HKSCS", /* 0x{00..7F}, 0x{88..FE}{40..FE} */ + "GBK", /* 0x{00..7F}, 0x{81..FE}{40..FE} */ + "GB18030", /* 0x{00..7F}, 0x{81..FE}{30..FE} */ + "SHIFT_JIS", /* 0x{00..7F}, 0x{81..F9}{40..FC} */ + "JOHAB" /* 0x{00..7F}, 0x{84..F9}{31..FE} */ + }; + size_t i; + + for (i = 0; i < SIZEOF (weird_cjk_charsets); i++) + if (strcmp (canon_charset, weird_cjk_charsets[i]) == 0) + return true; + return false; +} + +/* Hardcoded iterator functions for all kinds of encodings. + We could also implement a general iterator function with iconv(), + but we need a fast one. */ + +/* Character iterator for 8-bit encodings. */ +static size_t +char_iterator (const char *s) +{ + return 1; +} + +/* Character iterator for GB2312. See libiconv/lib/euc_cn.h. */ +/* Character iterator for EUC-KR. See libiconv/lib/euc_kr.h. */ +static size_t +euc_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0xa1 && c < 0xff) + { + unsigned char c2 = s[1]; + if (c2 >= 0xa1 && c2 < 0xff) + return 2; + } + return 1; +} + +/* Character iterator for EUC-JP. See libiconv/lib/euc_jp.h. */ +static size_t +euc_jp_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0xa1 && c < 0xff) + { + unsigned char c2 = s[1]; + if (c2 >= 0xa1 && c2 < 0xff) + return 2; + } + else if (c == 0x8e) + { + unsigned char c2 = s[1]; + if (c2 >= 0xa1 && c2 < 0xe0) + return 2; + } + else if (c == 0x8f) + { + unsigned char c2 = s[1]; + if (c2 >= 0xa1 && c2 < 0xff) + { + unsigned char c3 = s[2]; + if (c3 >= 0xa1 && c3 < 0xff) + return 3; + } + } + return 1; +} + +/* Character iterator for EUC-TW. See libiconv/lib/euc_tw.h. */ +static size_t +euc_tw_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0xa1 && c < 0xff) + { + unsigned char c2 = s[1]; + if (c2 >= 0xa1 && c2 < 0xff) + return 2; + } + else if (c == 0x8e) + { + unsigned char c2 = s[1]; + if (c2 >= 0xa1 && c2 <= 0xb0) + { + unsigned char c3 = s[2]; + if (c3 >= 0xa1 && c3 < 0xff) + { + unsigned char c4 = s[3]; + if (c4 >= 0xa1 && c4 < 0xff) + return 4; + } + } + } + return 1; +} + +/* Character iterator for BIG5. See libiconv/lib/ces_big5.h. */ +static size_t +big5_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0xa1 && c < 0xff) + { + unsigned char c2 = s[1]; + if ((c2 >= 0x40 && c2 < 0x7f) || (c2 >= 0xa1 && c2 < 0xff)) + return 2; + } + return 1; +} + +/* Character iterator for BIG5-HKSCS. See libiconv/lib/big5hkscs.h. */ +static size_t +big5hkscs_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0x88 && c < 0xff) + { + unsigned char c2 = s[1]; + if ((c2 >= 0x40 && c2 < 0x7f) || (c2 >= 0xa1 && c2 < 0xff)) + return 2; + } + return 1; +} + +/* Character iterator for GBK. See libiconv/lib/ces_gbk.h and + libiconv/lib/gbk.h. */ +static size_t +gbk_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0x81 && c < 0xff) + { + unsigned char c2 = s[1]; + if ((c2 >= 0x40 && c2 < 0x7f) || (c2 >= 0x80 && c2 < 0xff)) + return 2; + } + return 1; +} + +/* Character iterator for GB18030. See libiconv/lib/gb18030.h. */ +static size_t +gb18030_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0x81 && c < 0xff) + { + unsigned char c2 = s[1]; + if ((c2 >= 0x40 && c2 < 0x7f) || (c2 >= 0x80 && c2 < 0xff)) + return 2; + } + if (c >= 0x81 && c <= 0x84) + { + unsigned char c2 = s[1]; + if (c2 >= 0x30 && c2 <= 0x39) + { + unsigned char c3 = s[2]; + if (c3 >= 0x81 && c3 < 0xff) + { + unsigned char c4 = s[3]; + if (c4 >= 0x30 && c4 <= 0x39) + return 4; + } + } + } + return 1; +} + +/* Character iterator for SHIFT_JIS. See libiconv/lib/sjis.h. */ +static size_t +shift_jis_character_iterator (const char *s) +{ + unsigned char c = *s; + if ((c >= 0x81 && c <= 0x9f) || (c >= 0xe0 && c <= 0xf9)) + { + unsigned char c2 = s[1]; + if ((c2 >= 0x40 && c2 <= 0x7e) || (c2 >= 0x80 && c2 <= 0xfc)) + return 2; + } + return 1; +} + +/* Character iterator for JOHAB. See libiconv/lib/johab.h and + libiconv/lib/johab_hangul.h. */ +static size_t +johab_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0x84 && c <= 0xd3) + { + unsigned char c2 = s[1]; + if ((c2 >= 0x41 && c2 < 0x7f) || (c2 >= 0x81 && c2 < 0xff)) + return 2; + } + else if (c >= 0xd9 && c <= 0xf9) + { + unsigned char c2 = s[1]; + if ((c2 >= 0x31 && c2 <= 0x7e) || (c2 >= 0x91 && c2 <= 0xfe)) + return 2; + } + return 1; +} + +/* Character iterator for UTF-8. See libiconv/lib/utf8.h. */ +static size_t +utf8_character_iterator (const char *s) +{ + unsigned char c = *s; + if (c >= 0xc2) + { + if (c < 0xe0) + { + unsigned char c2 = s[1]; + if (c2 >= 0x80 && c2 < 0xc0) + return 2; + } + else if (c < 0xf0) + { + unsigned char c2 = s[1]; + if (c2 >= 0x80 && c2 < 0xc0) + { + unsigned char c3 = s[2]; + if (c3 >= 0x80 && c3 < 0xc0) + return 3; + } + } + else if (c < 0xf8) + { + unsigned char c2 = s[1]; + if (c2 >= 0x80 && c2 < 0xc0) + { + unsigned char c3 = s[2]; + if (c3 >= 0x80 && c3 < 0xc0) + { + unsigned char c4 = s[3]; + if (c4 >= 0x80 && c4 < 0xc0) + return 4; + } + } + } + } + return 1; +} + +/* Returns a character iterator for a given encoding. + Given a pointer into a string, it returns the number occupied by the next + single character. If the piece of string is not valid or if the *s == '\0', + it returns 1. */ +character_iterator_t +po_charset_character_iterator (const char *canon_charset) +{ + if (canon_charset == utf8) + return utf8_character_iterator; + if (strcmp (canon_charset, "GB2312") == 0 + || strcmp (canon_charset, "EUC-KR") == 0) + return euc_character_iterator; + if (strcmp (canon_charset, "EUC-JP") == 0) + return euc_jp_character_iterator; + if (strcmp (canon_charset, "EUC-TW") == 0) + return euc_tw_character_iterator; + if (strcmp (canon_charset, "BIG5") == 0) + return big5_character_iterator; + if (strcmp (canon_charset, "BIG5-HKSCS") == 0) + return big5hkscs_character_iterator; + if (strcmp (canon_charset, "GBK") == 0) + return gbk_character_iterator; + if (strcmp (canon_charset, "GB18030") == 0) + return gb18030_character_iterator; + if (strcmp (canon_charset, "SHIFT_JIS") == 0) + return shift_jis_character_iterator; + if (strcmp (canon_charset, "JOHAB") == 0) + return johab_character_iterator; + return char_iterator; +} + + +/* The PO file's encoding, as specified in the header entry. */ +const char *po_lex_charset; + +#if HAVE_ICONV +/* Converter from the PO file's encoding to UTF-8. */ +iconv_t po_lex_iconv; +#endif +/* If no converter is available, some information about the structure of the + PO file's encoding. */ +bool po_lex_weird_cjk; + +void +po_lex_charset_init () +{ + po_lex_charset = NULL; +#if HAVE_ICONV + po_lex_iconv = (iconv_t)(-1); +#endif + po_lex_weird_cjk = false; +} + +void +po_lex_charset_set (const char *header_entry, const char *filename) +{ + /* Verify the validity of CHARSET. It is necessary + 1. for the correct treatment of multibyte characters containing + 0x5C bytes in the PO lexer, + 2. so that at run time, gettext() can call iconv() to convert + msgstr. */ + const char *charsetstr = c_strstr (header_entry, "charset="); + + if (charsetstr != NULL) + { + size_t len; + char *charset; + const char *canon_charset; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = (char *) xmalloca (len + 1); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + + canon_charset = po_charset_canonicalize (charset); + if (canon_charset == NULL) + { + /* Don't warn for POT files, because POT files usually contain + only ASCII msgids. */ + size_t filenamelen = strlen (filename); + + if (!(filenamelen >= 4 + && memcmp (filename + filenamelen - 4, ".pot", 4) == 0 + && strcmp (charset, "CHARSET") == 0)) + { + char *warning_message = + xasprintf (_("\ +Charset \"%s\" is not a portable encoding name.\n\ +Message conversion to user's charset might not work.\n"), + charset); + po_xerror (PO_SEVERITY_WARNING, NULL, + filename, (size_t)(-1), (size_t)(-1), true, + warning_message); + free (warning_message); + } + } + else + { + const char *envval; + + po_lex_charset = canon_charset; +#if HAVE_ICONV + if (po_lex_iconv != (iconv_t)(-1)) + iconv_close (po_lex_iconv); +#endif + + /* The old Solaris/openwin msgfmt and GNU msgfmt <= 0.10.35 + don't know about multibyte encodings, and require a spurious + backslash after every multibyte character whose last byte is + 0x5C. Some programs, like vim, distribute PO files in this + broken format. GNU msgfmt must continue to support this old + PO file format when the Makefile requests it. */ + envval = getenv ("OLD_PO_FILE_INPUT"); + if (envval != NULL && *envval != '\0') + { + /* Assume the PO file is in old format, with extraneous + backslashes. */ +#if HAVE_ICONV + po_lex_iconv = (iconv_t)(-1); +#endif + po_lex_weird_cjk = false; + } + else + { + /* Use iconv() to parse multibyte characters. */ +#if HAVE_ICONV + /* Avoid glibc-2.1 bug with EUC-KR. */ +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \ + && !defined _LIBICONV_VERSION + if (strcmp (po_lex_charset, "EUC-KR") == 0) + po_lex_iconv = (iconv_t)(-1); + else +# endif + /* Avoid Solaris 2.9 bug with GB2312, EUC-TW, BIG5, BIG5-HKSCS, + GBK, GB18030. */ +# if defined __sun && !defined _LIBICONV_VERSION + if ( strcmp (po_lex_charset, "GB2312") == 0 + || strcmp (po_lex_charset, "EUC-TW") == 0 + || strcmp (po_lex_charset, "BIG5") == 0 + || strcmp (po_lex_charset, "BIG5-HKSCS") == 0 + || strcmp (po_lex_charset, "GBK") == 0 + || strcmp (po_lex_charset, "GB18030") == 0) + po_lex_iconv = (iconv_t)(-1); + else +# endif + po_lex_iconv = iconv_open ("UTF-8", po_lex_charset); + if (po_lex_iconv == (iconv_t)(-1)) + { + char *warning_message; + const char *recommendation; + const char *note; + char *whole_message; + + warning_message = + xasprintf (_("\ +Charset \"%s\" is not supported. %s relies on iconv(),\n\ +and iconv() does not support \"%s\".\n"), + po_lex_charset, basename (program_name), + po_lex_charset); + +# if !defined _LIBICONV_VERSION + recommendation = _("\ +Installing GNU libiconv and then reinstalling GNU gettext\n\ +would fix this problem.\n"); +# else + recommendation = ""; +# endif + + /* Test for a charset which has double-byte characters + ending in 0x5C. For these encodings, the string parser + is likely to be confused if it can't see the character + boundaries. */ + po_lex_weird_cjk = po_is_charset_weird_cjk (po_lex_charset); + if (po_is_charset_weird (po_lex_charset) + && !po_lex_weird_cjk) + note = _("Continuing anyway, expect parse errors."); + else + note = _("Continuing anyway."); + + whole_message = + xasprintf ("%s%s%s\n", + warning_message, recommendation, note); + + po_xerror (PO_SEVERITY_WARNING, NULL, + filename, (size_t)(-1), (size_t)(-1), true, + whole_message); + + free (whole_message); + free (warning_message); + } +#else + /* Test for a charset which has double-byte characters + ending in 0x5C. For these encodings, the string parser + is likely to be confused if it can't see the character + boundaries. */ + po_lex_weird_cjk = po_is_charset_weird_cjk (po_lex_charset); + if (po_is_charset_weird (po_lex_charset) && !po_lex_weird_cjk) + { + char *warning_message; + const char *recommendation; + const char *note; + char *whole_message; + + warning_message = + xasprintf (_("\ +Charset \"%s\" is not supported. %s relies on iconv().\n\ +This version was built without iconv().\n"), + po_lex_charset, basename (program_name)); + + recommendation = _("\ +Installing GNU libiconv and then reinstalling GNU gettext\n\ +would fix this problem.\n"); + + note = _("Continuing anyway, expect parse errors."); + + whole_message = + xasprintf ("%s%s%s\n", + warning_message, recommendation, note); + + po_xerror (PO_SEVERITY_WARNING, NULL, + filename, (size_t)(-1), (size_t)(-1), true, + whole_message); + + free (whole_message); + free (warning_message); + } +#endif + } + } + freea (charset); + } + else + { + /* Don't warn for POT files, because POT files usually contain + only ASCII msgids. */ + size_t filenamelen = strlen (filename); + + if (!(filenamelen >= 4 + && memcmp (filename + filenamelen - 4, ".pot", 4) == 0)) + po_xerror (PO_SEVERITY_WARNING, + NULL, filename, (size_t)(-1), (size_t)(-1), true, + _("\ +Charset missing in header.\n\ +Message conversion to user's charset will not work.\n")); + } +} + +void +po_lex_charset_close () +{ + po_lex_charset = NULL; +#if HAVE_ICONV + if (po_lex_iconv != (iconv_t)(-1)) + { + iconv_close (po_lex_iconv); + po_lex_iconv = (iconv_t)(-1); + } +#endif + po_lex_weird_cjk = false; +} diff --git a/gettext-tools/src/po-charset.h b/gettext-tools/src/po-charset.h new file mode 100644 index 0000000..65798e7 --- /dev/null +++ b/gettext-tools/src/po-charset.h @@ -0,0 +1,93 @@ +/* Charset handling while reading PO files. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PO_CHARSET_H +#define _PO_CHARSET_H + +#include +#include + +#if HAVE_ICONV +#include +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Canonicalize an encoding name. + The results of this function are statically allocated and can be + compared using ==. */ +extern const char *po_charset_canonicalize (const char *charset); + +/* The canonicalized encoding name for ASCII. */ +extern DLL_VARIABLE const char *po_charset_ascii; + +/* The canonicalized encoding name for UTF-8. */ +extern DLL_VARIABLE const char *po_charset_utf8; + +/* Test for ASCII compatibility. */ +extern bool po_charset_ascii_compatible (const char *canon_charset); + +/* Test for a weird encoding, i.e. an encoding which has double-byte + characters ending in 0x5C. */ +extern bool po_is_charset_weird (const char *canon_charset); + +/* Test for a weird CJK encoding, i.e. a weird encoding with CJK structure. + An encoding has CJK structure if every valid character stream is composed + of single bytes in the range 0x{00..7F} and of byte pairs in the range + 0x{80..FF}{30..FF}. */ +extern bool po_is_charset_weird_cjk (const char *canon_charset); + +/* Returns a character iterator for a given encoding. + Given a pointer into a string, it returns the number occupied by the next + single character. If the piece of string is not valid or if the *s == '\0', + it returns 1. */ +typedef size_t (*character_iterator_t) (const char *s); +extern character_iterator_t po_charset_character_iterator (const char *canon_charset); + + +/* The PO file's encoding, as specified in the header entry. */ +extern DLL_VARIABLE const char *po_lex_charset; + +#if HAVE_ICONV +/* Converter from the PO file's encoding to UTF-8. */ +extern DLL_VARIABLE iconv_t po_lex_iconv; +#endif +/* If no converter is available, some information about the structure of the + PO file's encoding. */ +extern DLL_VARIABLE bool po_lex_weird_cjk; + +/* Initialize the PO file's encoding. */ +extern void po_lex_charset_init (void); + +/* Set the PO file's encoding from the header entry. */ +extern void po_lex_charset_set (const char *header_entry, + const char *filename); + +/* Finish up with the PO file's encoding. */ +extern void po_lex_charset_close (void); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _PO_CHARSET_H */ diff --git a/gettext-tools/src/po-error.c b/gettext-tools/src/po-error.c new file mode 100644 index 0000000..067264a --- /dev/null +++ b/gettext-tools/src/po-error.c @@ -0,0 +1,42 @@ +/* Error handling during reading and writing of PO files. + Copyright (C) 2004 Free Software Foundation, Inc. + Written by Bruno Haible , 2004. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "po-error.h" + +#include "error.h" +#include "xerror.h" + + +void (*po_error) (int status, int errnum, + const char *format, ...) + = error; + +void (*po_error_at_line) (int status, int errnum, + const char *filename, unsigned int lineno, + const char *format, ...) + = error_at_line; + +void (*po_multiline_warning) (char *prefix, char *message) + = multiline_warning; +void (*po_multiline_error) (char *prefix, char *message) + = multiline_error; diff --git a/gettext-tools/src/po-error.h b/gettext-tools/src/po-error.h new file mode 100644 index 0000000..036a69b --- /dev/null +++ b/gettext-tools/src/po-error.h @@ -0,0 +1,76 @@ +/* Error handling during reading and writing of PO files. + Copyright (C) 2004, 2006, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2004. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PO_ERROR_H +#define _PO_ERROR_H + +#ifndef __attribute__ +/* This feature is available in gcc versions 2.5 and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__ +# define __attribute__(Spec) /* empty */ +# endif +/* The __-protected variants of 'format' and 'printf' attributes + are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7) +# define __format__ format +# define __printf__ printf +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Both functions must work like the GNU error(), error_at_line() functions. + In particular, + - The functions must not return if the status code is nonzero. + - The functions must increment the error_message_count variable declared + in error.h. */ + +extern DLL_VARIABLE + void (*po_error) (int status, int errnum, + const char *format, ...) +#if (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) || __GNUC__ > 3 + __attribute__ ((__format__ (__printf__, 3, 4))) +#endif + ; +extern DLL_VARIABLE + void (*po_error_at_line) (int status, int errnum, + const char *filename, unsigned int lineno, + const char *format, ...) +#if (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) || __GNUC__ > 3 + __attribute__ ((__format__ (__printf__, 5, 6))) +#endif + ; + +/* Both functions must work like the xerror.h multiline_warning(), + multiline_error() functions. In particular, + - multiline_error must increment the error_message_count variable declared + in error.h if prefix != NULL. */ + +extern DLL_VARIABLE + void (*po_multiline_warning) (char *prefix, char *message); +extern DLL_VARIABLE + void (*po_multiline_error) (char *prefix, char *message); + + +#ifdef __cplusplus +} +#endif + +#endif /* _PO_ERROR_H */ diff --git a/gettext-tools/src/po-gram-gen.c b/gettext-tools/src/po-gram-gen.c new file mode 100644 index 0000000..655684e --- /dev/null +++ b/gettext-tools/src/po-gram-gen.c @@ -0,0 +1,1922 @@ +/* A Bison parser, made by GNU Bison 3.0.2. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2013 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "3.0.2" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + + + + +/* Copy the first part of user declarations. */ +#line 19 "po-gram-gen.y" /* yacc.c:339 */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "po-gram.h" + +#include +#include +#include +#include + +#include "str-list.h" +#include "po-lex.h" +#include "po-charset.h" +#include "error.h" +#include "xalloc.h" +#include "gettext.h" +#include "read-catalog-abstract.h" + +#define _(str) gettext (str) + +/* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc), + as well as gratuitiously global symbol names, so we can have multiple + yacc generated parsers in the same program. Note that these are only + the variables produced by yacc. If other parser generators (bison, + byacc, etc) produce additional global names that conflict at link time, + then those parser generators need to be fixed instead of adding those + names to this list. */ + +#define yymaxdepth po_gram_maxdepth +#define yyparse po_gram_parse +#define yylex po_gram_lex +#define yyerror po_gram_error +#define yylval po_gram_lval +#define yychar po_gram_char +#define yydebug po_gram_debug +#define yypact po_gram_pact +#define yyr1 po_gram_r1 +#define yyr2 po_gram_r2 +#define yydef po_gram_def +#define yychk po_gram_chk +#define yypgo po_gram_pgo +#define yyact po_gram_act +#define yyexca po_gram_exca +#define yyerrflag po_gram_errflag +#define yynerrs po_gram_nerrs +#define yyps po_gram_ps +#define yypv po_gram_pv +#define yys po_gram_s +#define yy_yys po_gram_yys +#define yystate po_gram_state +#define yytmp po_gram_tmp +#define yyv po_gram_v +#define yy_yyv po_gram_yyv +#define yyval po_gram_val +#define yylloc po_gram_lloc +#define yyreds po_gram_reds /* With YYDEBUG defined */ +#define yytoks po_gram_toks /* With YYDEBUG defined */ +#define yylhs po_gram_yylhs +#define yylen po_gram_yylen +#define yydefred po_gram_yydefred +#define yydgoto po_gram_yydgoto +#define yysindex po_gram_yysindex +#define yyrindex po_gram_yyrindex +#define yygindex po_gram_yygindex +#define yytable po_gram_yytable +#define yycheck po_gram_yycheck + +static long plural_counter; + +#define check_obsolete(value1,value2) \ + if ((value1).obsolete != (value2).obsolete) \ + po_gram_error_at_line (&(value2).pos, _("inconsistent use of #~")); + +static inline void +do_callback_message (char *msgctxt, + char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural, + char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, char *prev_msgid_plural, + bool obsolete) +{ + /* Test for header entry. Ignore fuzziness of the header entry. */ + if (msgctxt == NULL && msgid[0] == '\0' && !obsolete) + po_lex_charset_set (msgstr, gram_pos.file_name); + + po_callback_message (msgctxt, + msgid, msgid_pos, msgid_plural, + msgstr, msgstr_len, msgstr_pos, + prev_msgctxt, prev_msgid, prev_msgid_plural, + false, obsolete); +} + +#define free_message_intro(value) \ + if ((value).prev_ctxt != NULL) \ + free ((value).prev_ctxt); \ + if ((value).prev_id != NULL) \ + free ((value).prev_id); \ + if ((value).prev_id_plural != NULL) \ + free ((value).prev_id_plural); \ + if ((value).ctxt != NULL) \ + free ((value).ctxt); + + +#line 173 "po-gram-gen.c" /* yacc.c:339 */ + +# ifndef YY_NULLPTR +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* In a future release of Bison, this section will be replaced + by #include "y.tab.h". */ +#ifndef YY_YY_PO_GRAM_GEN_H_INCLUDED +# define YY_YY_PO_GRAM_GEN_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int yydebug; +#endif + +/* Token type. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + COMMENT = 258, + DOMAIN = 259, + JUNK = 260, + PREV_MSGCTXT = 261, + PREV_MSGID = 262, + PREV_MSGID_PLURAL = 263, + PREV_STRING = 264, + MSGCTXT = 265, + MSGID = 266, + MSGID_PLURAL = 267, + MSGSTR = 268, + NAME = 269, + NUMBER = 270, + STRING = 271 + }; +#endif +/* Tokens. */ +#define COMMENT 258 +#define DOMAIN 259 +#define JUNK 260 +#define PREV_MSGCTXT 261 +#define PREV_MSGID 262 +#define PREV_MSGID_PLURAL 263 +#define PREV_STRING 264 +#define MSGCTXT 265 +#define MSGID 266 +#define MSGID_PLURAL 267 +#define MSGSTR 268 +#define NAME 269 +#define NUMBER 270 +#define STRING 271 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE YYSTYPE; +union YYSTYPE +{ +#line 143 "po-gram-gen.y" /* yacc.c:355 */ + + struct { char *string; lex_pos_ty pos; bool obsolete; } string; + struct { string_list_ty stringlist; lex_pos_ty pos; bool obsolete; } stringlist; + struct { long number; lex_pos_ty pos; bool obsolete; } number; + struct { lex_pos_ty pos; bool obsolete; } pos; + struct { char *ctxt; char *id; char *id_plural; lex_pos_ty pos; bool obsolete; } prev; + struct { char *prev_ctxt; char *prev_id; char *prev_id_plural; char *ctxt; lex_pos_ty pos; bool obsolete; } message_intro; + struct { struct msgstr_def rhs; lex_pos_ty pos; bool obsolete; } rhs; + +#line 255 "po-gram-gen.c" /* yacc.c:355 */ +}; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + +extern YYSTYPE yylval; + +int yyparse (void); + +#endif /* !YY_YY_PO_GRAM_GEN_H_INCLUDED */ + +/* Copy the second part of user declarations. */ + +#line 270 "po-gram-gen.c" /* yacc.c:358 */ + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + +#ifndef YY_ATTRIBUTE +# if (defined __GNUC__ \ + && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ + || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C +# define YY_ATTRIBUTE(Spec) __attribute__(Spec) +# else +# define YY_ATTRIBUTE(Spec) /* empty */ +# endif +#endif + +#ifndef YY_ATTRIBUTE_PURE +# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) +#endif + +#if !defined _Noreturn \ + && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112) +# if defined _MSC_VER && 1200 <= _MSC_VER +# define _Noreturn __declspec (noreturn) +# else +# define _Noreturn YY_ATTRIBUTE ((__noreturn__)) +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(E) ((void) (E)) +#else +# define YYUSE(E) /* empty */ +#endif + +#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 2 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 40 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 19 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 15 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 30 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 46 + +/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned + by yylex, with out-of-bounds checking. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 271 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, without out-of-bounds checking. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 15, 2, 16, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 17, 18 +}; + +#if YYDEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 168, 168, 170, 171, 172, 173, 178, 186, 194, + 215, 239, 248, 257, 268, 277, 291, 300, 314, 320, + 331, 337, 349, 360, 371, 375, 390, 413, 421, 433, + 441 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || 0 +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "COMMENT", "DOMAIN", "JUNK", + "PREV_MSGCTXT", "PREV_MSGID", "PREV_MSGID_PLURAL", "PREV_STRING", + "MSGCTXT", "MSGID", "MSGID_PLURAL", "MSGSTR", "NAME", "'['", "']'", + "NUMBER", "STRING", "$accept", "po_file", "comment", "domain", "message", + "message_intro", "prev", "msg_intro", "prev_msg_intro", + "msgid_pluralform", "prev_msgid_pluralform", "pluralform_list", + "pluralform", "string_list", "prev_string_list", YY_NULLPTR +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 91, 93, 270, 271 +}; +# endif + +#define YYPACT_NINF -26 + +#define yypact_value_is_default(Yystate) \ + (!!((Yystate) == (-26))) + +#define YYTABLE_NINF -1 + +#define yytable_value_is_error(Yytable_value) \ + 0 + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int8 yypact[] = +{ + -26, 2, -26, -26, -26, -8, 5, -26, 0, -26, + -26, -26, -26, 0, 13, -26, 5, -26, -26, 20, + -26, -7, 8, -26, 24, -26, -26, -26, -26, 0, + 7, 15, 15, -26, 5, -26, 12, 17, 12, 21, + 15, -26, 26, 22, 0, 12 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 2, 0, 1, 6, 7, 0, 0, 20, 0, 18, + 3, 4, 5, 0, 0, 14, 0, 8, 29, 0, + 27, 0, 13, 15, 16, 21, 30, 19, 28, 0, + 0, 11, 12, 24, 0, 17, 22, 0, 9, 0, + 10, 25, 23, 0, 0, 26 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -26, -26, -26, -26, -26, -26, -26, 23, -26, -26, + -26, 9, -25, -13, -15 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 1, 10, 11, 12, 13, 14, 15, 16, 31, + 35, 32, 33, 21, 19 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_uint8 yytable[] = +{ + 22, 24, 2, 3, 27, 4, 5, 41, 6, 7, + 17, 28, 8, 9, 18, 41, 36, 38, 20, 42, + 29, 30, 37, 8, 9, 20, 28, 25, 39, 26, + 28, 45, 34, 26, 43, 26, 37, 23, 44, 0, + 40 +}; + +static const yytype_int8 yycheck[] = +{ + 13, 16, 0, 1, 11, 3, 4, 32, 6, 7, + 18, 18, 10, 11, 9, 40, 29, 30, 18, 34, + 12, 13, 15, 10, 11, 18, 18, 7, 13, 9, + 18, 44, 8, 9, 17, 9, 15, 14, 16, -1, + 31 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 20, 0, 1, 3, 4, 6, 7, 10, 11, + 21, 22, 23, 24, 25, 26, 27, 18, 9, 33, + 18, 32, 32, 26, 33, 7, 9, 11, 18, 12, + 13, 28, 30, 31, 8, 29, 32, 15, 32, 13, + 30, 31, 33, 17, 16, 32 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 19, 20, 20, 20, 20, 20, 21, 22, 23, + 23, 23, 23, 23, 24, 24, 25, 25, 26, 26, + 27, 27, 28, 29, 30, 30, 31, 32, 32, 33, + 33 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 0, 2, 2, 2, 2, 1, 2, 4, + 4, 3, 3, 2, 1, 2, 2, 3, 1, 3, + 1, 3, 2, 2, 1, 2, 5, 1, 2, 1, + 2 +}; + + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (0) + +/* Error token number */ +#define YYTERROR 1 +#define YYERRCODE 256 + + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +/* This macro is provided for backward compatibility. */ +#ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +#endif + + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*----------------------------------------. +| Print this symbol's value on YYOUTPUT. | +`----------------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) +{ + FILE *yyo = yyoutput; + YYUSE (yyo); + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# endif + YYUSE (yytype); +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) +{ + YYFPRINTF (yyoutput, "%s %s (", + yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); + + yy_symbol_value_print (yyoutput, yytype, yyvaluep); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule) +{ + unsigned long int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + yystos[yyssp[yyi + 1 - yynrhs]], + &(yyvsp[(yyi + 1) - (yynrhs)]) + ); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +static YYSIZE_T +yystrlen (const char *yystr) +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +yystpcpy (char *yydest, const char *yysrc) +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message + about the unexpected token YYTOKEN for the state stack whose top is + YYSSP. + + Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is + not large enough to hold the message. In that case, also set + *YYMSG_ALLOC to the required number of bytes. Return 2 if the + required number of bytes is too large to store. */ +static int +yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, + yytype_int16 *yyssp, int yytoken) +{ + YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); + YYSIZE_T yysize = yysize0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + /* Internationalized format string. */ + const char *yyformat = YY_NULLPTR; + /* Arguments of yyformat. */ + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + /* Number of reported tokens (one for the "unexpected", one per + "expected"). */ + int yycount = 0; + + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yychar) is if + this state is a consistent state with a default action. Thus, + detecting the absence of a lookahead is sufficient to determine + that there is no unexpected or expected token to report. In that + case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is a + consistent state with a default action. There might have been a + previous inconsistent state, consistent state with a non-default + action, or user semantic action that manipulated yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + if (yytoken != YYEMPTY) + { + int yyn = yypact[*yyssp]; + yyarg[yycount++] = yytname[yytoken]; + if (!yypact_value_is_default (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + break; + } + yyarg[yycount++] = yytname[yyx]; + { + YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); + if (! (yysize <= yysize1 + && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + } + } + } + + switch (yycount) + { +# define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +# undef YYCASE_ + } + + { + YYSIZE_T yysize1 = yysize + yystrlen (yyformat); + if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + + if (*yymsg_alloc < yysize) + { + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return 1; + } + + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyformat += 2; + } + else + { + yyp++; + yyformat++; + } + } + return 0; +} +#endif /* YYERROR_VERBOSE */ + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) +{ + YYUSE (yyvaluep); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YYUSE (yytype); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + + + +/* The lookahead symbol. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; +/* Number of syntax errors so far. */ +int yynerrs; + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (void) +{ + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + 'yyss': related to states. + 'yyvs': related to semantic values. + + Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken = 0; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yyssp = yyss = yyssa; + yyvsp = yyvs = yyvsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = yylex (); + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 7: +#line 179 "po-gram-gen.y" /* yacc.c:1646 */ + { + po_callback_comment_dispatcher ((yyvsp[0].string).string); + } +#line 1376 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 8: +#line 187 "po-gram-gen.y" /* yacc.c:1646 */ + { + po_callback_domain ((yyvsp[0].string).string); + } +#line 1384 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 9: +#line 195 "po-gram-gen.y" /* yacc.c:1646 */ + { + char *string2 = string_list_concat_destroy (&(yyvsp[-2].stringlist).stringlist); + char *string4 = string_list_concat_destroy (&(yyvsp[0].stringlist).stringlist); + + check_obsolete ((yyvsp[-3].message_intro), (yyvsp[-2].stringlist)); + check_obsolete ((yyvsp[-3].message_intro), (yyvsp[-1].pos)); + check_obsolete ((yyvsp[-3].message_intro), (yyvsp[0].stringlist)); + if (!(yyvsp[-3].message_intro).obsolete || pass_obsolete_entries) + do_callback_message ((yyvsp[-3].message_intro).ctxt, string2, &(yyvsp[-3].message_intro).pos, NULL, + string4, strlen (string4) + 1, &(yyvsp[-1].pos).pos, + (yyvsp[-3].message_intro).prev_ctxt, + (yyvsp[-3].message_intro).prev_id, (yyvsp[-3].message_intro).prev_id_plural, + (yyvsp[-3].message_intro).obsolete); + else + { + free_message_intro ((yyvsp[-3].message_intro)); + free (string2); + free (string4); + } + } +#line 1409 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 10: +#line 216 "po-gram-gen.y" /* yacc.c:1646 */ + { + char *string2 = string_list_concat_destroy (&(yyvsp[-2].stringlist).stringlist); + + check_obsolete ((yyvsp[-3].message_intro), (yyvsp[-2].stringlist)); + check_obsolete ((yyvsp[-3].message_intro), (yyvsp[-1].string)); + check_obsolete ((yyvsp[-3].message_intro), (yyvsp[0].rhs)); + if (!(yyvsp[-3].message_intro).obsolete || pass_obsolete_entries) + { + do_callback_message ((yyvsp[-3].message_intro).ctxt, string2, &(yyvsp[-3].message_intro).pos, (yyvsp[-1].string).string, + (yyvsp[0].rhs).rhs.msgstr, (yyvsp[0].rhs).rhs.msgstr_len, &(yyvsp[0].rhs).pos, + (yyvsp[-3].message_intro).prev_ctxt, + (yyvsp[-3].message_intro).prev_id, (yyvsp[-3].message_intro).prev_id_plural, + (yyvsp[-3].message_intro).obsolete); + free ((yyvsp[-1].string).string); + } + else + { + free_message_intro ((yyvsp[-3].message_intro)); + free (string2); + free ((yyvsp[-1].string).string); + free ((yyvsp[0].rhs).rhs.msgstr); + } + } +#line 1437 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 11: +#line 240 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-2].message_intro), (yyvsp[-1].stringlist)); + check_obsolete ((yyvsp[-2].message_intro), (yyvsp[0].string)); + po_gram_error_at_line (&(yyvsp[-2].message_intro).pos, _("missing 'msgstr[]' section")); + free_message_intro ((yyvsp[-2].message_intro)); + string_list_destroy (&(yyvsp[-1].stringlist).stringlist); + free ((yyvsp[0].string).string); + } +#line 1450 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 12: +#line 249 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-2].message_intro), (yyvsp[-1].stringlist)); + check_obsolete ((yyvsp[-2].message_intro), (yyvsp[0].rhs)); + po_gram_error_at_line (&(yyvsp[-2].message_intro).pos, _("missing 'msgid_plural' section")); + free_message_intro ((yyvsp[-2].message_intro)); + string_list_destroy (&(yyvsp[-1].stringlist).stringlist); + free ((yyvsp[0].rhs).rhs.msgstr); + } +#line 1463 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 13: +#line 258 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-1].message_intro), (yyvsp[0].stringlist)); + po_gram_error_at_line (&(yyvsp[-1].message_intro).pos, _("missing 'msgstr' section")); + free_message_intro ((yyvsp[-1].message_intro)); + string_list_destroy (&(yyvsp[0].stringlist).stringlist); + } +#line 1474 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 14: +#line 269 "po-gram-gen.y" /* yacc.c:1646 */ + { + (yyval.message_intro).prev_ctxt = NULL; + (yyval.message_intro).prev_id = NULL; + (yyval.message_intro).prev_id_plural = NULL; + (yyval.message_intro).ctxt = (yyvsp[0].string).string; + (yyval.message_intro).pos = (yyvsp[0].string).pos; + (yyval.message_intro).obsolete = (yyvsp[0].string).obsolete; + } +#line 1487 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 15: +#line 278 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-1].prev), (yyvsp[0].string)); + (yyval.message_intro).prev_ctxt = (yyvsp[-1].prev).ctxt; + (yyval.message_intro).prev_id = (yyvsp[-1].prev).id; + (yyval.message_intro).prev_id_plural = (yyvsp[-1].prev).id_plural; + (yyval.message_intro).ctxt = (yyvsp[0].string).string; + (yyval.message_intro).pos = (yyvsp[0].string).pos; + (yyval.message_intro).obsolete = (yyvsp[0].string).obsolete; + } +#line 1501 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 16: +#line 292 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-1].string), (yyvsp[0].stringlist)); + (yyval.prev).ctxt = (yyvsp[-1].string).string; + (yyval.prev).id = string_list_concat_destroy (&(yyvsp[0].stringlist).stringlist); + (yyval.prev).id_plural = NULL; + (yyval.prev).pos = (yyvsp[-1].string).pos; + (yyval.prev).obsolete = (yyvsp[-1].string).obsolete; + } +#line 1514 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 17: +#line 301 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-2].string), (yyvsp[-1].stringlist)); + check_obsolete ((yyvsp[-2].string), (yyvsp[0].string)); + (yyval.prev).ctxt = (yyvsp[-2].string).string; + (yyval.prev).id = string_list_concat_destroy (&(yyvsp[-1].stringlist).stringlist); + (yyval.prev).id_plural = (yyvsp[0].string).string; + (yyval.prev).pos = (yyvsp[-2].string).pos; + (yyval.prev).obsolete = (yyvsp[-2].string).obsolete; + } +#line 1528 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 18: +#line 315 "po-gram-gen.y" /* yacc.c:1646 */ + { + (yyval.string).string = NULL; + (yyval.string).pos = (yyvsp[0].pos).pos; + (yyval.string).obsolete = (yyvsp[0].pos).obsolete; + } +#line 1538 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 19: +#line 321 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-2].pos), (yyvsp[-1].stringlist)); + check_obsolete ((yyvsp[-2].pos), (yyvsp[0].pos)); + (yyval.string).string = string_list_concat_destroy (&(yyvsp[-1].stringlist).stringlist); + (yyval.string).pos = (yyvsp[0].pos).pos; + (yyval.string).obsolete = (yyvsp[0].pos).obsolete; + } +#line 1550 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 20: +#line 332 "po-gram-gen.y" /* yacc.c:1646 */ + { + (yyval.string).string = NULL; + (yyval.string).pos = (yyvsp[0].pos).pos; + (yyval.string).obsolete = (yyvsp[0].pos).obsolete; + } +#line 1560 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 21: +#line 338 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-2].pos), (yyvsp[-1].stringlist)); + check_obsolete ((yyvsp[-2].pos), (yyvsp[0].pos)); + (yyval.string).string = string_list_concat_destroy (&(yyvsp[-1].stringlist).stringlist); + (yyval.string).pos = (yyvsp[0].pos).pos; + (yyval.string).obsolete = (yyvsp[0].pos).obsolete; + } +#line 1572 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 22: +#line 350 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-1].pos), (yyvsp[0].stringlist)); + plural_counter = 0; + (yyval.string).string = string_list_concat_destroy (&(yyvsp[0].stringlist).stringlist); + (yyval.string).pos = (yyvsp[-1].pos).pos; + (yyval.string).obsolete = (yyvsp[-1].pos).obsolete; + } +#line 1584 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 23: +#line 361 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-1].pos), (yyvsp[0].stringlist)); + (yyval.string).string = string_list_concat_destroy (&(yyvsp[0].stringlist).stringlist); + (yyval.string).pos = (yyvsp[-1].pos).pos; + (yyval.string).obsolete = (yyvsp[-1].pos).obsolete; + } +#line 1595 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 24: +#line 372 "po-gram-gen.y" /* yacc.c:1646 */ + { + (yyval.rhs) = (yyvsp[0].rhs); + } +#line 1603 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 25: +#line 376 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-1].rhs), (yyvsp[0].rhs)); + (yyval.rhs).rhs.msgstr = XNMALLOC ((yyvsp[-1].rhs).rhs.msgstr_len + (yyvsp[0].rhs).rhs.msgstr_len, char); + memcpy ((yyval.rhs).rhs.msgstr, (yyvsp[-1].rhs).rhs.msgstr, (yyvsp[-1].rhs).rhs.msgstr_len); + memcpy ((yyval.rhs).rhs.msgstr + (yyvsp[-1].rhs).rhs.msgstr_len, (yyvsp[0].rhs).rhs.msgstr, (yyvsp[0].rhs).rhs.msgstr_len); + (yyval.rhs).rhs.msgstr_len = (yyvsp[-1].rhs).rhs.msgstr_len + (yyvsp[0].rhs).rhs.msgstr_len; + free ((yyvsp[-1].rhs).rhs.msgstr); + free ((yyvsp[0].rhs).rhs.msgstr); + (yyval.rhs).pos = (yyvsp[-1].rhs).pos; + (yyval.rhs).obsolete = (yyvsp[-1].rhs).obsolete; + } +#line 1619 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 26: +#line 391 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-4].pos), (yyvsp[-3].pos)); + check_obsolete ((yyvsp[-4].pos), (yyvsp[-2].number)); + check_obsolete ((yyvsp[-4].pos), (yyvsp[-1].pos)); + check_obsolete ((yyvsp[-4].pos), (yyvsp[0].stringlist)); + if ((yyvsp[-2].number).number != plural_counter) + { + if (plural_counter == 0) + po_gram_error_at_line (&(yyvsp[-4].pos).pos, _("first plural form has nonzero index")); + else + po_gram_error_at_line (&(yyvsp[-4].pos).pos, _("plural form has wrong index")); + } + plural_counter++; + (yyval.rhs).rhs.msgstr = string_list_concat_destroy (&(yyvsp[0].stringlist).stringlist); + (yyval.rhs).rhs.msgstr_len = strlen ((yyval.rhs).rhs.msgstr) + 1; + (yyval.rhs).pos = (yyvsp[-4].pos).pos; + (yyval.rhs).obsolete = (yyvsp[-4].pos).obsolete; + } +#line 1642 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 27: +#line 414 "po-gram-gen.y" /* yacc.c:1646 */ + { + string_list_init (&(yyval.stringlist).stringlist); + string_list_append (&(yyval.stringlist).stringlist, (yyvsp[0].string).string); + free ((yyvsp[0].string).string); + (yyval.stringlist).pos = (yyvsp[0].string).pos; + (yyval.stringlist).obsolete = (yyvsp[0].string).obsolete; + } +#line 1654 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 28: +#line 422 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-1].stringlist), (yyvsp[0].string)); + (yyval.stringlist).stringlist = (yyvsp[-1].stringlist).stringlist; + string_list_append (&(yyval.stringlist).stringlist, (yyvsp[0].string).string); + free ((yyvsp[0].string).string); + (yyval.stringlist).pos = (yyvsp[-1].stringlist).pos; + (yyval.stringlist).obsolete = (yyvsp[-1].stringlist).obsolete; + } +#line 1667 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 29: +#line 434 "po-gram-gen.y" /* yacc.c:1646 */ + { + string_list_init (&(yyval.stringlist).stringlist); + string_list_append (&(yyval.stringlist).stringlist, (yyvsp[0].string).string); + free ((yyvsp[0].string).string); + (yyval.stringlist).pos = (yyvsp[0].string).pos; + (yyval.stringlist).obsolete = (yyvsp[0].string).obsolete; + } +#line 1679 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + case 30: +#line 442 "po-gram-gen.y" /* yacc.c:1646 */ + { + check_obsolete ((yyvsp[-1].stringlist), (yyvsp[0].string)); + (yyval.stringlist).stringlist = (yyvsp[-1].stringlist).stringlist; + string_list_append (&(yyval.stringlist).stringlist, (yyvsp[0].string).string); + free ((yyvsp[0].string).string); + (yyval.stringlist).pos = (yyvsp[-1].stringlist).pos; + (yyval.stringlist).obsolete = (yyvsp[-1].stringlist).obsolete; + } +#line 1692 "po-gram-gen.c" /* yacc.c:1646 */ + break; + + +#line 1696 "po-gram-gen.c" /* yacc.c:1646 */ + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); + + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (YY_("syntax error")); +#else +# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ + yyssp, yytoken) + { + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = YYSYNTAX_ERROR; + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == 1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); + if (!yymsg) + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = 2; + } + else + { + yysyntax_error_status = YYSYNTAX_ERROR; + yymsgp = yymsg; + } + } + yyerror (yymsgp); + if (yysyntax_error_status == 2) + goto yyexhaustedlab; + } +# undef YYSYNTAX_ERROR +#endif + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + yystos[yystate], yyvsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined yyoverflow || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + return yyresult; +} diff --git a/gettext-tools/src/po-gram-gen.h b/gettext-tools/src/po-gram-gen.h new file mode 100644 index 0000000..3f20e75 --- /dev/null +++ b/gettext-tools/src/po-gram-gen.h @@ -0,0 +1,106 @@ +/* A Bison parser, made by GNU Bison 3.0.2. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2013 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +#ifndef YY_YY_PO_GRAM_GEN_H_INCLUDED +# define YY_YY_PO_GRAM_GEN_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int yydebug; +#endif + +/* Token type. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + COMMENT = 258, + DOMAIN = 259, + JUNK = 260, + PREV_MSGCTXT = 261, + PREV_MSGID = 262, + PREV_MSGID_PLURAL = 263, + PREV_STRING = 264, + MSGCTXT = 265, + MSGID = 266, + MSGID_PLURAL = 267, + MSGSTR = 268, + NAME = 269, + NUMBER = 270, + STRING = 271 + }; +#endif +/* Tokens. */ +#define COMMENT 258 +#define DOMAIN 259 +#define JUNK 260 +#define PREV_MSGCTXT 261 +#define PREV_MSGID 262 +#define PREV_MSGID_PLURAL 263 +#define PREV_STRING 264 +#define MSGCTXT 265 +#define MSGID 266 +#define MSGID_PLURAL 267 +#define MSGSTR 268 +#define NAME 269 +#define NUMBER 270 +#define STRING 271 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE YYSTYPE; +union YYSTYPE +{ +#line 143 "po-gram-gen.y" /* yacc.c:1909 */ + + struct { char *string; lex_pos_ty pos; bool obsolete; } string; + struct { string_list_ty stringlist; lex_pos_ty pos; bool obsolete; } stringlist; + struct { long number; lex_pos_ty pos; bool obsolete; } number; + struct { lex_pos_ty pos; bool obsolete; } pos; + struct { char *ctxt; char *id; char *id_plural; lex_pos_ty pos; bool obsolete; } prev; + struct { char *prev_ctxt; char *prev_id; char *prev_id_plural; char *ctxt; lex_pos_ty pos; bool obsolete; } message_intro; + struct { struct msgstr_def rhs; lex_pos_ty pos; bool obsolete; } rhs; + +#line 96 "po-gram-gen.h" /* yacc.c:1909 */ +}; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + +extern YYSTYPE yylval; + +int yyparse (void); + +#endif /* !YY_YY_PO_GRAM_GEN_H_INCLUDED */ diff --git a/gettext-tools/src/po-gram-gen.y b/gettext-tools/src/po-gram-gen.y new file mode 100644 index 0000000..8f888bd --- /dev/null +++ b/gettext-tools/src/po-gram-gen.y @@ -0,0 +1,450 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1996, 1998, 2000-2001, 2003, 2005-2006, 2012 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +%{ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "po-gram.h" + +#include +#include +#include +#include + +#include "str-list.h" +#include "po-lex.h" +#include "po-charset.h" +#include "error.h" +#include "xalloc.h" +#include "gettext.h" +#include "read-catalog-abstract.h" + +#define _(str) gettext (str) + +/* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc), + as well as gratuitiously global symbol names, so we can have multiple + yacc generated parsers in the same program. Note that these are only + the variables produced by yacc. If other parser generators (bison, + byacc, etc) produce additional global names that conflict at link time, + then those parser generators need to be fixed instead of adding those + names to this list. */ + +#define yymaxdepth po_gram_maxdepth +#define yyparse po_gram_parse +#define yylex po_gram_lex +#define yyerror po_gram_error +#define yylval po_gram_lval +#define yychar po_gram_char +#define yydebug po_gram_debug +#define yypact po_gram_pact +#define yyr1 po_gram_r1 +#define yyr2 po_gram_r2 +#define yydef po_gram_def +#define yychk po_gram_chk +#define yypgo po_gram_pgo +#define yyact po_gram_act +#define yyexca po_gram_exca +#define yyerrflag po_gram_errflag +#define yynerrs po_gram_nerrs +#define yyps po_gram_ps +#define yypv po_gram_pv +#define yys po_gram_s +#define yy_yys po_gram_yys +#define yystate po_gram_state +#define yytmp po_gram_tmp +#define yyv po_gram_v +#define yy_yyv po_gram_yyv +#define yyval po_gram_val +#define yylloc po_gram_lloc +#define yyreds po_gram_reds /* With YYDEBUG defined */ +#define yytoks po_gram_toks /* With YYDEBUG defined */ +#define yylhs po_gram_yylhs +#define yylen po_gram_yylen +#define yydefred po_gram_yydefred +#define yydgoto po_gram_yydgoto +#define yysindex po_gram_yysindex +#define yyrindex po_gram_yyrindex +#define yygindex po_gram_yygindex +#define yytable po_gram_yytable +#define yycheck po_gram_yycheck + +static long plural_counter; + +#define check_obsolete(value1,value2) \ + if ((value1).obsolete != (value2).obsolete) \ + po_gram_error_at_line (&(value2).pos, _("inconsistent use of #~")); + +static inline void +do_callback_message (char *msgctxt, + char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural, + char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, char *prev_msgid_plural, + bool obsolete) +{ + /* Test for header entry. Ignore fuzziness of the header entry. */ + if (msgctxt == NULL && msgid[0] == '\0' && !obsolete) + po_lex_charset_set (msgstr, gram_pos.file_name); + + po_callback_message (msgctxt, + msgid, msgid_pos, msgid_plural, + msgstr, msgstr_len, msgstr_pos, + prev_msgctxt, prev_msgid, prev_msgid_plural, + false, obsolete); +} + +#define free_message_intro(value) \ + if ((value).prev_ctxt != NULL) \ + free ((value).prev_ctxt); \ + if ((value).prev_id != NULL) \ + free ((value).prev_id); \ + if ((value).prev_id_plural != NULL) \ + free ((value).prev_id_plural); \ + if ((value).ctxt != NULL) \ + free ((value).ctxt); + +%} + +%token COMMENT +%token DOMAIN +%token JUNK +%token PREV_MSGCTXT +%token PREV_MSGID +%token PREV_MSGID_PLURAL +%token PREV_STRING +%token MSGCTXT +%token MSGID +%token MSGID_PLURAL +%token MSGSTR +%token NAME +%token '[' ']' +%token NUMBER +%token STRING + +%union +{ + struct { char *string; lex_pos_ty pos; bool obsolete; } string; + struct { string_list_ty stringlist; lex_pos_ty pos; bool obsolete; } stringlist; + struct { long number; lex_pos_ty pos; bool obsolete; } number; + struct { lex_pos_ty pos; bool obsolete; } pos; + struct { char *ctxt; char *id; char *id_plural; lex_pos_ty pos; bool obsolete; } prev; + struct { char *prev_ctxt; char *prev_id; char *prev_id_plural; char *ctxt; lex_pos_ty pos; bool obsolete; } message_intro; + struct { struct msgstr_def rhs; lex_pos_ty pos; bool obsolete; } rhs; +} + +%type STRING PREV_STRING COMMENT NAME + msg_intro prev_msg_intro msgid_pluralform prev_msgid_pluralform +%type string_list prev_string_list +%type NUMBER +%type DOMAIN + PREV_MSGCTXT PREV_MSGID PREV_MSGID_PLURAL + MSGCTXT MSGID MSGID_PLURAL MSGSTR '[' ']' +%type prev +%type message_intro +%type pluralform pluralform_list + +%right MSGSTR + +%% + +po_file + : /* empty */ + | po_file comment + | po_file domain + | po_file message + | po_file error + ; + + +comment + : COMMENT + { + po_callback_comment_dispatcher ($1.string); + } + ; + + +domain + : DOMAIN STRING + { + po_callback_domain ($2.string); + } + ; + + +message + : message_intro string_list MSGSTR string_list + { + char *string2 = string_list_concat_destroy (&$2.stringlist); + char *string4 = string_list_concat_destroy (&$4.stringlist); + + check_obsolete ($1, $2); + check_obsolete ($1, $3); + check_obsolete ($1, $4); + if (!$1.obsolete || pass_obsolete_entries) + do_callback_message ($1.ctxt, string2, &$1.pos, NULL, + string4, strlen (string4) + 1, &$3.pos, + $1.prev_ctxt, + $1.prev_id, $1.prev_id_plural, + $1.obsolete); + else + { + free_message_intro ($1); + free (string2); + free (string4); + } + } + | message_intro string_list msgid_pluralform pluralform_list + { + char *string2 = string_list_concat_destroy (&$2.stringlist); + + check_obsolete ($1, $2); + check_obsolete ($1, $3); + check_obsolete ($1, $4); + if (!$1.obsolete || pass_obsolete_entries) + { + do_callback_message ($1.ctxt, string2, &$1.pos, $3.string, + $4.rhs.msgstr, $4.rhs.msgstr_len, &$4.pos, + $1.prev_ctxt, + $1.prev_id, $1.prev_id_plural, + $1.obsolete); + free ($3.string); + } + else + { + free_message_intro ($1); + free (string2); + free ($3.string); + free ($4.rhs.msgstr); + } + } + | message_intro string_list msgid_pluralform + { + check_obsolete ($1, $2); + check_obsolete ($1, $3); + po_gram_error_at_line (&$1.pos, _("missing 'msgstr[]' section")); + free_message_intro ($1); + string_list_destroy (&$2.stringlist); + free ($3.string); + } + | message_intro string_list pluralform_list + { + check_obsolete ($1, $2); + check_obsolete ($1, $3); + po_gram_error_at_line (&$1.pos, _("missing 'msgid_plural' section")); + free_message_intro ($1); + string_list_destroy (&$2.stringlist); + free ($3.rhs.msgstr); + } + | message_intro string_list + { + check_obsolete ($1, $2); + po_gram_error_at_line (&$1.pos, _("missing 'msgstr' section")); + free_message_intro ($1); + string_list_destroy (&$2.stringlist); + } + ; + + +message_intro + : msg_intro + { + $$.prev_ctxt = NULL; + $$.prev_id = NULL; + $$.prev_id_plural = NULL; + $$.ctxt = $1.string; + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + | prev msg_intro + { + check_obsolete ($1, $2); + $$.prev_ctxt = $1.ctxt; + $$.prev_id = $1.id; + $$.prev_id_plural = $1.id_plural; + $$.ctxt = $2.string; + $$.pos = $2.pos; + $$.obsolete = $2.obsolete; + } + ; + + +prev + : prev_msg_intro prev_string_list + { + check_obsolete ($1, $2); + $$.ctxt = $1.string; + $$.id = string_list_concat_destroy (&$2.stringlist); + $$.id_plural = NULL; + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + | prev_msg_intro prev_string_list prev_msgid_pluralform + { + check_obsolete ($1, $2); + check_obsolete ($1, $3); + $$.ctxt = $1.string; + $$.id = string_list_concat_destroy (&$2.stringlist); + $$.id_plural = $3.string; + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + ; + + +msg_intro + : MSGID + { + $$.string = NULL; + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + | MSGCTXT string_list MSGID + { + check_obsolete ($1, $2); + check_obsolete ($1, $3); + $$.string = string_list_concat_destroy (&$2.stringlist); + $$.pos = $3.pos; + $$.obsolete = $3.obsolete; + } + ; + +prev_msg_intro + : PREV_MSGID + { + $$.string = NULL; + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + | PREV_MSGCTXT prev_string_list PREV_MSGID + { + check_obsolete ($1, $2); + check_obsolete ($1, $3); + $$.string = string_list_concat_destroy (&$2.stringlist); + $$.pos = $3.pos; + $$.obsolete = $3.obsolete; + } + ; + + +msgid_pluralform + : MSGID_PLURAL string_list + { + check_obsolete ($1, $2); + plural_counter = 0; + $$.string = string_list_concat_destroy (&$2.stringlist); + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + ; + +prev_msgid_pluralform + : PREV_MSGID_PLURAL prev_string_list + { + check_obsolete ($1, $2); + $$.string = string_list_concat_destroy (&$2.stringlist); + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + ; + + +pluralform_list + : pluralform + { + $$ = $1; + } + | pluralform_list pluralform + { + check_obsolete ($1, $2); + $$.rhs.msgstr = XNMALLOC ($1.rhs.msgstr_len + $2.rhs.msgstr_len, char); + memcpy ($$.rhs.msgstr, $1.rhs.msgstr, $1.rhs.msgstr_len); + memcpy ($$.rhs.msgstr + $1.rhs.msgstr_len, $2.rhs.msgstr, $2.rhs.msgstr_len); + $$.rhs.msgstr_len = $1.rhs.msgstr_len + $2.rhs.msgstr_len; + free ($1.rhs.msgstr); + free ($2.rhs.msgstr); + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + ; + +pluralform + : MSGSTR '[' NUMBER ']' string_list + { + check_obsolete ($1, $2); + check_obsolete ($1, $3); + check_obsolete ($1, $4); + check_obsolete ($1, $5); + if ($3.number != plural_counter) + { + if (plural_counter == 0) + po_gram_error_at_line (&$1.pos, _("first plural form has nonzero index")); + else + po_gram_error_at_line (&$1.pos, _("plural form has wrong index")); + } + plural_counter++; + $$.rhs.msgstr = string_list_concat_destroy (&$5.stringlist); + $$.rhs.msgstr_len = strlen ($$.rhs.msgstr) + 1; + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + ; + + +string_list + : STRING + { + string_list_init (&$$.stringlist); + string_list_append (&$$.stringlist, $1.string); + free ($1.string); + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + | string_list STRING + { + check_obsolete ($1, $2); + $$.stringlist = $1.stringlist; + string_list_append (&$$.stringlist, $2.string); + free ($2.string); + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + ; + +prev_string_list + : PREV_STRING + { + string_list_init (&$$.stringlist); + string_list_append (&$$.stringlist, $1.string); + free ($1.string); + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + | prev_string_list PREV_STRING + { + check_obsolete ($1, $2); + $$.stringlist = $1.stringlist; + string_list_append (&$$.stringlist, $2.string); + free ($2.string); + $$.pos = $1.pos; + $$.obsolete = $1.obsolete; + } + ; diff --git a/gettext-tools/src/po-gram-gen2.h b/gettext-tools/src/po-gram-gen2.h new file mode 100644 index 0000000..36e9f4b --- /dev/null +++ b/gettext-tools/src/po-gram-gen2.h @@ -0,0 +1,106 @@ +/* A Bison parser, made by GNU Bison 3.0.2. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2013 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +#ifndef YY_YY_PO_GRAM_GEN_H_INCLUDED +# define YY_YY_PO_GRAM_GEN_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern DLL_VARIABLE int po_gram_debug; +#endif + +/* Token type. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum po_gram_tokentype + { + COMMENT = 258, + DOMAIN = 259, + JUNK = 260, + PREV_MSGCTXT = 261, + PREV_MSGID = 262, + PREV_MSGID_PLURAL = 263, + PREV_STRING = 264, + MSGCTXT = 265, + MSGID = 266, + MSGID_PLURAL = 267, + MSGSTR = 268, + NAME = 269, + NUMBER = 270, + STRING = 271 + }; +#endif +/* Tokens. */ +#define COMMENT 258 +#define DOMAIN 259 +#define JUNK 260 +#define PREV_MSGCTXT 261 +#define PREV_MSGID 262 +#define PREV_MSGID_PLURAL 263 +#define PREV_STRING 264 +#define MSGCTXT 265 +#define MSGID 266 +#define MSGID_PLURAL 267 +#define MSGSTR 268 +#define NAME 269 +#define NUMBER 270 +#define STRING 271 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE YYSTYPE; +union YYSTYPE +{ +#line 143 "po-gram-gen.y" /* yacc.c:1909 */ + + struct { char *string; lex_pos_ty pos; bool obsolete; } string; + struct { string_list_ty stringlist; lex_pos_ty pos; bool obsolete; } stringlist; + struct { long number; lex_pos_ty pos; bool obsolete; } number; + struct { lex_pos_ty pos; bool obsolete; } pos; + struct { char *ctxt; char *id; char *id_plural; lex_pos_ty pos; bool obsolete; } prev; + struct { char *prev_ctxt; char *prev_id; char *prev_id_plural; char *ctxt; lex_pos_ty pos; bool obsolete; } message_intro; + struct { struct msgstr_def rhs; lex_pos_ty pos; bool obsolete; } rhs; + +#line 96 "po-gram-gen.h" /* yacc.c:1909 */ +}; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + +extern DLL_VARIABLE YYSTYPE po_gram_lval; + +int po_gram_parse (void); + +#endif /* !YY_YY_PO_GRAM_GEN_H_INCLUDED */ diff --git a/gettext-tools/src/po-gram.h b/gettext-tools/src/po-gram.h new file mode 100644 index 0000000..0e2d079 --- /dev/null +++ b/gettext-tools/src/po-gram.h @@ -0,0 +1,32 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995, 2002-2003, 2006 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PO_GRAM_H +#define _PO_GRAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern int po_gram_parse (void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gettext-tools/src/po-lex.c b/gettext-tools/src/po-lex.c new file mode 100644 index 0000000..3bf5449 --- /dev/null +++ b/gettext-tools/src/po-lex.c @@ -0,0 +1,1151 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1999, 2000-2009, 2011 Free Software Foundation, Inc. + + This file was written by Peter Miller . + Multibyte character handling by Bruno Haible . + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "po-lex.h" + +#include +#include +#include +#include +#include +#include + +#if HAVE_ICONV +# include +#endif + +#include "c-ctype.h" +#include "uniwidth.h" +#include "gettext.h" +#include "po-charset.h" +#include "xalloc.h" +#include "error.h" +#include "error-progname.h" +#include "xvasprintf.h" +#include "po-error.h" +#include "po-xerror.h" +#include "pos.h" +#include "message.h" +#include "str-list.h" +#include "po-gram-gen2.h" + +#define _(str) gettext(str) + +#if HAVE_ICONV +# include "unistr.h" +#endif + +#if HAVE_DECL_GETC_UNLOCKED +# undef getc +# define getc getc_unlocked +#endif + + +/* Current position within the PO file. */ +lex_pos_ty gram_pos; +int gram_pos_column; + + +/* Error handling during the parsing of a PO file. + These functions can access gram_pos and gram_pos_column. */ + +/* VARARGS1 */ +void +po_gram_error (const char *fmt, ...) +{ + va_list ap; + char *buffer; + + va_start (ap, fmt); + if (vasprintf (&buffer, fmt, ap) < 0) + error (EXIT_FAILURE, 0, _("memory exhausted")); + va_end (ap); + po_xerror (PO_SEVERITY_ERROR, NULL, gram_pos.file_name, gram_pos.line_number, + gram_pos_column + 1, false, buffer); + free (buffer); + + if (error_message_count >= gram_max_allowed_errors) + po_error (EXIT_FAILURE, 0, _("too many errors, aborting")); +} + +/* VARARGS2 */ +void +po_gram_error_at_line (const lex_pos_ty *pp, const char *fmt, ...) +{ + va_list ap; + char *buffer; + + va_start (ap, fmt); + if (vasprintf (&buffer, fmt, ap) < 0) + error (EXIT_FAILURE, 0, _("memory exhausted")); + va_end (ap); + po_xerror (PO_SEVERITY_ERROR, NULL, pp->file_name, pp->line_number, + (size_t)(-1), false, buffer); + free (buffer); + + if (error_message_count >= gram_max_allowed_errors) + po_error (EXIT_FAILURE, 0, _("too many errors, aborting")); +} + + +/* The lowest level of PO file parsing converts bytes to multibyte characters. + This is needed + 1. for C compatibility: ISO C 99 section 5.1.1.2 says that the first + translation phase maps bytes to characters. + 2. to keep track of the current column, for the sake of precise error + location. Emacs compile.el interprets the column in error messages + by default as a screen column number, not as character number. + 3. to avoid skipping backslash-newline in the midst of a multibyte + character. If XY is a multibyte character, X \ newline Y is invalid. + */ + +/* Multibyte character data type. */ +/* Note this depends on po_lex_charset and po_lex_iconv, which get set + while the file is being parsed. */ + +#define MBCHAR_BUF_SIZE 24 + +struct mbchar +{ + size_t bytes; /* number of bytes of current character, > 0 */ +#if HAVE_ICONV + bool uc_valid; /* true if uc is a valid Unicode character */ + ucs4_t uc; /* if uc_valid: the current character */ +#endif + char buf[MBCHAR_BUF_SIZE]; /* room for the bytes */ +}; + +/* We want to pass multibyte characters by reference automatically, + therefore we use an array type. */ +typedef struct mbchar mbchar_t[1]; + +/* A version of memcpy optimized for the case n <= 1. */ +static inline void +memcpy_small (void *dst, const void *src, size_t n) +{ + if (n > 0) + { + char *q = (char *) dst; + const char *p = (const char *) src; + + *q = *p; + if (--n > 0) + do *++q = *++p; while (--n > 0); + } +} + +/* EOF (not a real character) is represented with bytes = 0 and + uc_valid = false. */ +static inline bool +mb_iseof (const mbchar_t mbc) +{ + return (mbc->bytes == 0); +} + +/* Access the current character. */ +static inline const char * +mb_ptr (const mbchar_t mbc) +{ + return mbc->buf; +} +static inline size_t +mb_len (const mbchar_t mbc) +{ + return mbc->bytes; +} + +/* Comparison of characters. */ + +static inline bool +mb_iseq (const mbchar_t mbc, char sc) +{ + /* Note: It is wrong to compare only mbc->uc, because when the encoding is + SHIFT_JIS, mbc->buf[0] == '\\' corresponds to mbc->uc == 0x00A5, but we + want to treat it as an escape character, although it looks like a Yen + sign. */ +#if HAVE_ICONV && 0 + if (mbc->uc_valid) + return (mbc->uc == sc); /* wrong! */ + else +#endif + return (mbc->bytes == 1 && mbc->buf[0] == sc); +} + +static inline bool +mb_isnul (const mbchar_t mbc) +{ +#if HAVE_ICONV + if (mbc->uc_valid) + return (mbc->uc == 0); + else +#endif + return (mbc->bytes == 1 && mbc->buf[0] == 0); +} + +static inline int +mb_cmp (const mbchar_t mbc1, const mbchar_t mbc2) +{ +#if HAVE_ICONV + if (mbc1->uc_valid && mbc2->uc_valid) + return (int) mbc1->uc - (int) mbc2->uc; + else +#endif + return (mbc1->bytes == mbc2->bytes + ? memcmp (mbc1->buf, mbc2->buf, mbc1->bytes) + : mbc1->bytes < mbc2->bytes + ? (memcmp (mbc1->buf, mbc2->buf, mbc1->bytes) > 0 ? 1 : -1) + : (memcmp (mbc1->buf, mbc2->buf, mbc2->bytes) >= 0 ? 1 : -1)); +} + +static inline bool +mb_equal (const mbchar_t mbc1, const mbchar_t mbc2) +{ +#if HAVE_ICONV + if (mbc1->uc_valid && mbc2->uc_valid) + return mbc1->uc == mbc2->uc; + else +#endif + return (mbc1->bytes == mbc2->bytes + && memcmp (mbc1->buf, mbc2->buf, mbc1->bytes) == 0); +} + +/* , classification. */ + +static inline bool +mb_isascii (const mbchar_t mbc) +{ +#if HAVE_ICONV + if (mbc->uc_valid) + return (mbc->uc >= 0x0000 && mbc->uc <= 0x007F); + else +#endif + return (mbc->bytes == 1 +#if CHAR_MIN < 0x00 /* to avoid gcc warning */ + && mbc->buf[0] >= 0x00 +#endif +#if CHAR_MAX > 0x7F /* to avoid gcc warning */ + && mbc->buf[0] <= 0x7F +#endif + ); +} + +/* Extra function. */ + +/* Unprintable characters appear as a small box of width 1. */ +#define MB_UNPRINTABLE_WIDTH 1 + +static int +mb_width (const mbchar_t mbc) +{ +#if HAVE_ICONV + if (mbc->uc_valid) + { + ucs4_t uc = mbc->uc; + const char *encoding = + (po_lex_iconv != (iconv_t)(-1) ? po_lex_charset : ""); + int w = uc_width (uc, encoding); + /* For unprintable characters, arbitrarily return 0 for control + characters (except tab) and MB_UNPRINTABLE_WIDTH otherwise. */ + if (w >= 0) + return w; + if (uc >= 0x0000 && uc <= 0x001F) + { + if (uc == 0x0009) + return 8 - (gram_pos_column & 7); + return 0; + } + if ((uc >= 0x007F && uc <= 0x009F) || (uc >= 0x2028 && uc <= 0x2029)) + return 0; + return MB_UNPRINTABLE_WIDTH; + } + else +#endif + { + if (mbc->bytes == 1) + { + if ( +#if CHAR_MIN < 0x00 /* to avoid gcc warning */ + mbc->buf[0] >= 0x00 && +#endif + mbc->buf[0] <= 0x1F) + { + if (mbc->buf[0] == 0x09) + return 8 - (gram_pos_column & 7); + return 0; + } + if (mbc->buf[0] == 0x7F) + return 0; + } + return MB_UNPRINTABLE_WIDTH; + } +} + +/* Output. */ +static inline void +mb_putc (const mbchar_t mbc, FILE *stream) +{ + fwrite (mbc->buf, 1, mbc->bytes, stream); +} + +/* Assignment. */ +static inline void +mb_setascii (mbchar_t mbc, char sc) +{ + mbc->bytes = 1; +#if HAVE_ICONV + mbc->uc_valid = 1; + mbc->uc = sc; +#endif + mbc->buf[0] = sc; +} + +/* Copying a character. */ +static inline void +mb_copy (mbchar_t new_mbc, const mbchar_t old_mbc) +{ + memcpy_small (&new_mbc->buf[0], &old_mbc->buf[0], old_mbc->bytes); + new_mbc->bytes = old_mbc->bytes; +#if HAVE_ICONV + if ((new_mbc->uc_valid = old_mbc->uc_valid)) + new_mbc->uc = old_mbc->uc; +#endif +} + + +/* Multibyte character input. */ + +/* Number of characters that can be pushed back. + We need 1 for lex_getc, plus 1 for lex_ungetc. */ +#define NPUSHBACK 2 + +/* Data type of a multibyte character input stream. */ +struct mbfile +{ + FILE *fp; + bool eof_seen; + int have_pushback; + unsigned int bufcount; + char buf[MBCHAR_BUF_SIZE]; + struct mbchar pushback[NPUSHBACK]; +}; + +/* We want to pass multibyte streams by reference automatically, + therefore we use an array type. */ +typedef struct mbfile mbfile_t[1]; + +/* Whether invalid multibyte sequences in the input shall be signalled + or silently tolerated. */ +static bool signal_eilseq; + +static inline void +mbfile_init (mbfile_t mbf, FILE *stream) +{ + mbf->fp = stream; + mbf->eof_seen = false; + mbf->have_pushback = 0; + mbf->bufcount = 0; +} + +/* Read the next multibyte character from mbf and put it into mbc. + If a read error occurs, errno is set and ferror (mbf->fp) becomes true. */ +static void +mbfile_getc (mbchar_t mbc, mbfile_t mbf) +{ + size_t bytes; + + /* If EOF has already been seen, don't use getc. This matters if + mbf->fp is connected to an interactive tty. */ + if (mbf->eof_seen) + goto eof; + + /* Return character pushed back, if there is one. */ + if (mbf->have_pushback > 0) + { + mbf->have_pushback--; + mb_copy (mbc, &mbf->pushback[mbf->have_pushback]); + return; + } + + /* Before using iconv, we need at least one byte. */ + if (mbf->bufcount == 0) + { + int c = getc (mbf->fp); + if (c == EOF) + { + mbf->eof_seen = true; + goto eof; + } + mbf->buf[0] = (unsigned char) c; + mbf->bufcount++; + } + +#if HAVE_ICONV + if (po_lex_iconv != (iconv_t)(-1)) + { + /* Use iconv on an increasing number of bytes. Read only as many + bytes from mbf->fp as needed. This is needed to give reasonable + interactive behaviour when mbf->fp is connected to an interactive + tty. */ + for (;;) + { + unsigned char scratchbuf[64]; + const char *inptr = &mbf->buf[0]; + size_t insize = mbf->bufcount; + char *outptr = (char *) &scratchbuf[0]; + size_t outsize = sizeof (scratchbuf); + + size_t res = iconv (po_lex_iconv, + (ICONV_CONST char **) &inptr, &insize, + &outptr, &outsize); + /* We expect that a character has been produced if and only if + some input bytes have been consumed. */ + if ((insize < mbf->bufcount) != (outsize < sizeof (scratchbuf))) + abort (); + if (outsize == sizeof (scratchbuf)) + { + /* No character has been produced. Must be an error. */ + if (res != (size_t)(-1)) + abort (); + + if (errno == EILSEQ) + { + /* An invalid multibyte sequence was encountered. */ + /* Return a single byte. */ + if (signal_eilseq) + po_gram_error (_("invalid multibyte sequence")); + bytes = 1; + mbc->uc_valid = false; + break; + } + else if (errno == EINVAL) + { + /* An incomplete multibyte character. */ + int c; + + if (mbf->bufcount == MBCHAR_BUF_SIZE) + { + /* An overlong incomplete multibyte sequence was + encountered. */ + /* Return a single byte. */ + bytes = 1; + mbc->uc_valid = false; + break; + } + + /* Read one more byte and retry iconv. */ + c = getc (mbf->fp); + if (c == EOF) + { + mbf->eof_seen = true; + if (ferror (mbf->fp)) + goto eof; + if (signal_eilseq) + po_gram_error (_("\ +incomplete multibyte sequence at end of file")); + bytes = mbf->bufcount; + mbc->uc_valid = false; + break; + } + mbf->buf[mbf->bufcount++] = (unsigned char) c; + if (c == '\n') + { + if (signal_eilseq) + po_gram_error (_("\ +incomplete multibyte sequence at end of line")); + bytes = mbf->bufcount - 1; + mbc->uc_valid = false; + break; + } + } + else + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + _("iconv failure"), + errno_description)); + } + } + else + { + size_t outbytes = sizeof (scratchbuf) - outsize; + bytes = mbf->bufcount - insize; + + /* We expect that one character has been produced. */ + if (bytes == 0) + abort (); + if (outbytes == 0) + abort (); + /* Convert it from UTF-8 to UCS-4. */ + if (u8_mbtoucr (&mbc->uc, scratchbuf, outbytes) < (int) outbytes) + { + /* scratchbuf contains an out-of-range Unicode character + (> 0x10ffff). */ + if (signal_eilseq) + po_gram_error (_("invalid multibyte sequence")); + mbc->uc_valid = false; + break; + } + mbc->uc_valid = true; + break; + } + } + } + else +#endif + { + if (po_lex_weird_cjk + /* Special handling of encodings with CJK structure. */ + && (unsigned char) mbf->buf[0] >= 0x80) + { + if (mbf->bufcount == 1) + { + /* Read one more byte. */ + int c = getc (mbf->fp); + if (c == EOF) + { + if (ferror (mbf->fp)) + { + mbf->eof_seen = true; + goto eof; + } + } + else + { + mbf->buf[1] = (unsigned char) c; + mbf->bufcount++; + } + } + if (mbf->bufcount >= 2 && (unsigned char) mbf->buf[1] >= 0x30) + /* Return a double byte. */ + bytes = 2; + else + /* Return a single byte. */ + bytes = 1; + } + else + { + /* Return a single byte. */ + bytes = 1; + } +#if HAVE_ICONV + mbc->uc_valid = false; +#endif + } + + /* Return the multibyte sequence mbf->buf[0..bytes-1]. */ + memcpy_small (&mbc->buf[0], &mbf->buf[0], bytes); + mbc->bytes = bytes; + + mbf->bufcount -= bytes; + if (mbf->bufcount > 0) + { + /* It's not worth calling memmove() for so few bytes. */ + unsigned int count = mbf->bufcount; + char *p = &mbf->buf[0]; + + do + { + *p = *(p + bytes); + p++; + } + while (--count > 0); + } + return; + +eof: + /* An mbchar_t with bytes == 0 is used to indicate EOF. */ + mbc->bytes = 0; +#if HAVE_ICONV + mbc->uc_valid = false; +#endif + return; +} + +static void +mbfile_ungetc (const mbchar_t mbc, mbfile_t mbf) +{ + if (mbf->have_pushback >= NPUSHBACK) + abort (); + mb_copy (&mbf->pushback[mbf->have_pushback], mbc); + mbf->have_pushback++; +} + + +/* Lexer variables. */ + +static mbfile_t mbf; +unsigned int gram_max_allowed_errors = 20; +static bool po_lex_obsolete; +static bool po_lex_previous; +static bool pass_comments = false; +bool pass_obsolete_entries = false; + + +/* Prepare lexical analysis. */ +void +lex_start (FILE *fp, const char *real_filename, const char *logical_filename) +{ + /* Ignore the logical_filename, because PO file entries already have + their file names attached. But use real_filename for error messages. */ + gram_pos.file_name = xstrdup (real_filename); + + mbfile_init (mbf, fp); + + gram_pos.line_number = 1; + gram_pos_column = 0; + signal_eilseq = true; + po_lex_obsolete = false; + po_lex_previous = false; + po_lex_charset_init (); +} + +/* Terminate lexical analysis. */ +void +lex_end () +{ + mbf->fp = NULL; + gram_pos.file_name = NULL; + gram_pos.line_number = 0; + gram_pos_column = 0; + signal_eilseq = false; + po_lex_obsolete = false; + po_lex_previous = false; + po_lex_charset_close (); +} + + +/* Read a single character, dealing with backslash-newline. + Also keep track of the current line number and column number. */ +static void +lex_getc (mbchar_t mbc) +{ + for (;;) + { + mbfile_getc (mbc, mbf); + + if (mb_iseof (mbc)) + { + if (ferror (mbf->fp)) + bomb: + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("error while reading \"%s\""), + gram_pos.file_name), + errno_description)); + } + break; + } + + if (mb_iseq (mbc, '\n')) + { + gram_pos.line_number++; + gram_pos_column = 0; + break; + } + + gram_pos_column += mb_width (mbc); + + if (mb_iseq (mbc, '\\')) + { + mbchar_t mbc2; + + mbfile_getc (mbc2, mbf); + + if (mb_iseof (mbc2)) + { + if (ferror (mbf->fp)) + goto bomb; + break; + } + + if (!mb_iseq (mbc2, '\n')) + { + mbfile_ungetc (mbc2, mbf); + break; + } + + gram_pos.line_number++; + gram_pos_column = 0; + } + else + break; + } +} + + +static void +lex_ungetc (const mbchar_t mbc) +{ + if (!mb_iseof (mbc)) + { + if (mb_iseq (mbc, '\n')) + /* Decrement the line number, but don't care about the column. */ + gram_pos.line_number--; + else + /* Decrement the column number. Also works well enough for tabs. */ + gram_pos_column -= mb_width (mbc); + + mbfile_ungetc (mbc, mbf); + } +} + + +static int +keyword_p (const char *s) +{ + if (!po_lex_previous) + { + if (!strcmp (s, "domain")) + return DOMAIN; + if (!strcmp (s, "msgid")) + return MSGID; + if (!strcmp (s, "msgid_plural")) + return MSGID_PLURAL; + if (!strcmp (s, "msgstr")) + return MSGSTR; + if (!strcmp (s, "msgctxt")) + return MSGCTXT; + } + else + { + /* Inside a "#|" context, the keywords have a different meaning. */ + if (!strcmp (s, "msgid")) + return PREV_MSGID; + if (!strcmp (s, "msgid_plural")) + return PREV_MSGID_PLURAL; + if (!strcmp (s, "msgctxt")) + return PREV_MSGCTXT; + } + po_gram_error_at_line (&gram_pos, _("keyword \"%s\" unknown"), s); + return NAME; +} + + +static int +control_sequence () +{ + mbchar_t mbc; + int val; + int max; + + lex_getc (mbc); + if (mb_len (mbc) == 1) + switch (mb_ptr (mbc) [0]) + { + case 'n': + return '\n'; + + case 't': + return '\t'; + + case 'b': + return '\b'; + + case 'r': + return '\r'; + + case 'f': + return '\f'; + + case 'v': + return '\v'; + + case 'a': + return '\a'; + + case '\\': + case '"': + return mb_ptr (mbc) [0]; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + val = 0; + max = 0; + for (;;) + { + char c = mb_ptr (mbc) [0]; + /* Warning: not portable, can't depend on '0'..'7' ordering. */ + val = val * 8 + (c - '0'); + if (++max == 3) + break; + lex_getc (mbc); + if (mb_len (mbc) == 1) + switch (mb_ptr (mbc) [0]) + { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + continue; + + default: + break; + } + lex_ungetc (mbc); + break; + } + return val; + + case 'x': + lex_getc (mbc); + if (mb_iseof (mbc) || mb_len (mbc) != 1 + || !c_isxdigit (mb_ptr (mbc) [0])) + break; + + val = 0; + for (;;) + { + char c = mb_ptr (mbc) [0]; + val *= 16; + if (c_isdigit (c)) + /* Warning: not portable, can't depend on '0'..'9' ordering */ + val += c - '0'; + else if (c_isupper (c)) + /* Warning: not portable, can't depend on 'A'..'F' ordering */ + val += c - 'A' + 10; + else + /* Warning: not portable, can't depend on 'a'..'f' ordering */ + val += c - 'a' + 10; + + lex_getc (mbc); + if (mb_len (mbc) == 1) + switch (mb_ptr (mbc) [0]) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + continue; + + default: + break; + } + lex_ungetc (mbc); + break; + } + return val; + + /* FIXME: \u and \U are not handled. */ + } + lex_ungetc (mbc); + po_gram_error (_("invalid control sequence")); + return ' '; +} + + +/* Return the next token in the PO file. The return codes are defined + in "po-gram-gen2.h". Associated data is put in 'po_gram_lval'. */ +int +po_gram_lex () +{ + static char *buf; + static size_t bufmax; + mbchar_t mbc; + size_t bufpos; + + for (;;) + { + lex_getc (mbc); + + if (mb_iseof (mbc)) + /* Yacc want this for end of file. */ + return 0; + + if (mb_len (mbc) == 1) + switch (mb_ptr (mbc) [0]) + { + case '\n': + po_lex_obsolete = false; + po_lex_previous = false; + /* Ignore whitespace, not relevant for the grammar. */ + break; + + case ' ': + case '\t': + case '\r': + case '\f': + case '\v': + /* Ignore whitespace, not relevant for the grammar. */ + break; + + case '#': + lex_getc (mbc); + if (mb_iseq (mbc, '~')) + /* A pseudo-comment beginning with #~ is found. This is + not a comment. It is the format for obsolete entries. + We simply discard the "#~" prefix. The following + characters are expected to be well formed. */ + { + po_lex_obsolete = true; + /* A pseudo-comment beginning with #~| denotes a previous + untranslated string in an obsolete entry. This does not + make much sense semantically, and is implemented here + for completeness only. */ + lex_getc (mbc); + if (mb_iseq (mbc, '|')) + po_lex_previous = true; + else + lex_ungetc (mbc); + break; + } + if (mb_iseq (mbc, '|')) + /* A pseudo-comment beginning with #| is found. This is + the previous untranslated string. We discard the "#|" + prefix, but change the keywords and string returns + accordingly. */ + { + po_lex_previous = true; + break; + } + + /* Accumulate comments into a buffer. If we have been asked + to pass comments, generate a COMMENT token, otherwise + discard it. */ + signal_eilseq = false; + if (pass_comments) + { + bufpos = 0; + for (;;) + { + while (bufpos + mb_len (mbc) >= bufmax) + { + bufmax += 100; + buf = xrealloc (buf, bufmax); + } + if (mb_iseof (mbc) || mb_iseq (mbc, '\n')) + break; + + memcpy_small (&buf[bufpos], mb_ptr (mbc), mb_len (mbc)); + bufpos += mb_len (mbc); + + lex_getc (mbc); + } + buf[bufpos] = '\0'; + + po_gram_lval.string.string = buf; + po_gram_lval.string.pos = gram_pos; + po_gram_lval.string.obsolete = po_lex_obsolete; + po_lex_obsolete = false; + signal_eilseq = true; + return COMMENT; + } + else + { + /* We do this in separate loop because collecting large + comments while they get not passed to the upper layers + is not very efficient. */ + while (!mb_iseof (mbc) && !mb_iseq (mbc, '\n')) + lex_getc (mbc); + po_lex_obsolete = false; + signal_eilseq = true; + } + break; + + case '"': + /* Accumulate a string. */ + bufpos = 0; + for (;;) + { + lex_getc (mbc); + while (bufpos + mb_len (mbc) >= bufmax) + { + bufmax += 100; + buf = xrealloc (buf, bufmax); + } + if (mb_iseof (mbc)) + { + po_gram_error_at_line (&gram_pos, + _("end-of-file within string")); + break; + } + if (mb_iseq (mbc, '\n')) + { + po_gram_error_at_line (&gram_pos, + _("end-of-line within string")); + break; + } + if (mb_iseq (mbc, '"')) + break; + if (mb_iseq (mbc, '\\')) + { + buf[bufpos++] = control_sequence (); + continue; + } + + /* Add mbc to the accumulator. */ + memcpy_small (&buf[bufpos], mb_ptr (mbc), mb_len (mbc)); + bufpos += mb_len (mbc); + } + buf[bufpos] = '\0'; + + /* Strings cannot contain the msgctxt separator, because it cannot + be faithfully represented in the msgid of a .mo file. */ + if (strchr (buf, MSGCTXT_SEPARATOR) != NULL) + po_gram_error_at_line (&gram_pos, + _("context separator within string")); + + /* FIXME: Treatment of embedded \000 chars is incorrect. */ + po_gram_lval.string.string = xstrdup (buf); + po_gram_lval.string.pos = gram_pos; + po_gram_lval.string.obsolete = po_lex_obsolete; + return (po_lex_previous ? PREV_STRING : STRING); + + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': case '$': + bufpos = 0; + for (;;) + { + char c = mb_ptr (mbc) [0]; + if (bufpos + 1 >= bufmax) + { + bufmax += 100; + buf = xrealloc (buf, bufmax); + } + buf[bufpos++] = c; + lex_getc (mbc); + if (mb_len (mbc) == 1) + switch (mb_ptr (mbc) [0]) + { + default: + break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': + case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': + case 'Z': + case '_': case '$': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + } + break; + } + lex_ungetc (mbc); + + buf[bufpos] = '\0'; + + { + int k = keyword_p (buf); + if (k == NAME) + { + po_gram_lval.string.string = xstrdup (buf); + po_gram_lval.string.pos = gram_pos; + po_gram_lval.string.obsolete = po_lex_obsolete; + } + else + { + po_gram_lval.pos.pos = gram_pos; + po_gram_lval.pos.obsolete = po_lex_obsolete; + } + return k; + } + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + bufpos = 0; + for (;;) + { + char c = mb_ptr (mbc) [0]; + if (bufpos + 1 >= bufmax) + { + bufmax += 100; + buf = xrealloc (buf, bufmax + 1); + } + buf[bufpos++] = c; + lex_getc (mbc); + if (mb_len (mbc) == 1) + switch (mb_ptr (mbc) [0]) + { + default: + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + } + break; + } + lex_ungetc (mbc); + + buf[bufpos] = '\0'; + + po_gram_lval.number.number = atol (buf); + po_gram_lval.number.pos = gram_pos; + po_gram_lval.number.obsolete = po_lex_obsolete; + return NUMBER; + + case '[': + po_gram_lval.pos.pos = gram_pos; + po_gram_lval.pos.obsolete = po_lex_obsolete; + return '['; + + case ']': + po_gram_lval.pos.pos = gram_pos; + po_gram_lval.pos.obsolete = po_lex_obsolete; + return ']'; + + default: + /* This will cause a syntax error. */ + return JUNK; + } + else + /* This will cause a syntax error. */ + return JUNK; + } +} + + +/* po_gram_lex() can return comments as COMMENT. Switch this on or off. */ +void +po_lex_pass_comments (bool flag) +{ + pass_comments = flag; +} + + +/* po_gram_lex() can return obsolete entries as if they were normal entries. + Switch this on or off. */ +void +po_lex_pass_obsolete_entries (bool flag) +{ + pass_obsolete_entries = flag; +} diff --git a/gettext-tools/src/po-lex.h b/gettext-tools/src/po-lex.h new file mode 100644 index 0000000..da05f38 --- /dev/null +++ b/gettext-tools/src/po-lex.h @@ -0,0 +1,103 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2006, 2012 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PO_LEX_H +#define _PO_LEX_H + +#include +#include +#include +#include "error.h" +#include "error-progname.h" +#include "xerror.h" +#include "pos.h" + +#ifndef __attribute__ +/* This feature is available in gcc versions 2.5 and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__ +# define __attribute__(Spec) /* empty */ +# endif +/* The __-protected variants of 'format' and 'printf' attributes + are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7) +# define __format__ format +# define __printf__ printf +# endif +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Lexical analyzer for reading PO files. */ + + +/* Global variables from po-lex.c. */ + +/* Current position within the PO file. */ +extern DLL_VARIABLE lex_pos_ty gram_pos; +extern DLL_VARIABLE int gram_pos_column; + +/* Number of parse errors within a PO file that cause the program to + terminate. Cf. error_message_count, declared in . */ +extern DLL_VARIABLE unsigned int gram_max_allowed_errors; + +/* True if obsolete entries shall be considered as valid. */ +extern DLL_VARIABLE bool pass_obsolete_entries; + + +/* Prepare lexical analysis. */ +extern void lex_start (FILE *fp, const char *real_filename, + const char *logical_filename); + +/* Terminate lexical analysis. */ +extern void lex_end (void); + +/* Return the next token in the PO file. The return codes are defined + in "po-gram-gen2.h". Associated data is put in 'po_gram_lval. */ +extern int po_gram_lex (void); + +/* po_gram_lex() can return comments as COMMENT. Switch this on or off. */ +extern void po_lex_pass_comments (bool flag); + +/* po_gram_lex() can return obsolete entries as if they were normal entries. + Switch this on or off. */ +extern void po_lex_pass_obsolete_entries (bool flag); + +extern void po_gram_error (const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 1, 2))); +extern void po_gram_error_at_line (const lex_pos_ty *pos, const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 2, 3))); + + +/* Contains information about the definition of one translation. */ +struct msgstr_def +{ + char *msgstr; + size_t msgstr_len; +}; + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/gettext-tools/src/po-time.c b/gettext-tools/src/po-time.c new file mode 100644 index 0000000..dd357f1 --- /dev/null +++ b/gettext-tools/src/po-time.c @@ -0,0 +1,75 @@ +/* PO/POT file timestamps. + Copyright (C) 1995-1998, 2000-2003, 2006 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "po-time.h" + +#include "xvasprintf.h" + + +#define TM_YEAR_ORIGIN 1900 + +/* Yield A - B, measured in seconds. */ +static long +difftm (const struct tm *a, const struct tm *b) +{ + int ay = a->tm_year + TM_YEAR_ORIGIN - 1; + int by = b->tm_year + TM_YEAR_ORIGIN - 1; + /* Some compilers cannot handle this as a single return statement. */ + long days = ( + /* difference in day of year */ + a->tm_yday - b->tm_yday + /* + intervening leap days */ + + ((ay >> 2) - (by >> 2)) + - (ay / 100 - by / 100) + + ((ay / 100 >> 2) - (by / 100 >> 2)) + /* + difference in years * 365 */ + + (long) (ay - by) * 365l); + + return 60l * (60l * (24l * days + (a->tm_hour - b->tm_hour)) + + (a->tm_min - b->tm_min)) + + (a->tm_sec - b->tm_sec); +} + + +char * +po_strftime (const time_t *tp) +{ + struct tm local_time; + char tz_sign; + long tz_min; + + local_time = *localtime (tp); + tz_sign = '+'; + tz_min = difftm (&local_time, gmtime (tp)) / 60; + if (tz_min < 0) + { + tz_min = -tz_min; + tz_sign = '-'; + } + return xasprintf ("%d-%02d-%02d %02d:%02d%c%02ld%02ld", + local_time.tm_year + TM_YEAR_ORIGIN, + local_time.tm_mon + 1, + local_time.tm_mday, + local_time.tm_hour, + local_time.tm_min, + tz_sign, tz_min / 60, tz_min % 60); +} diff --git a/gettext-tools/src/po-time.h b/gettext-tools/src/po-time.h new file mode 100644 index 0000000..846dea0 --- /dev/null +++ b/gettext-tools/src/po-time.h @@ -0,0 +1,38 @@ +/* PO/POT file timestamps. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PO_TIME_H +#define _PO_TIME_H + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Return a freshly allocated string containing the given time in the + format YYYY-MM-DD HH:MM+TZOFF. */ +extern char *po_strftime (const time_t *tp); + + +#ifdef __cplusplus +} +#endif + +#endif /* _PO_TIME_H */ diff --git a/gettext-tools/src/po-xerror.c b/gettext-tools/src/po-xerror.c new file mode 100644 index 0000000..8a889d8 --- /dev/null +++ b/gettext-tools/src/po-xerror.c @@ -0,0 +1,199 @@ +/* Error handling during reading and writing of PO files. + Copyright (C) 2005-2007 Free Software Foundation, Inc. + Written by Bruno Haible , 2005. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "po-xerror.h" + +#include +#include +#include + +#include "message.h" +#include "progname.h" +#include "error-progname.h" +#include "xalloc.h" +#include "xerror.h" +#include "error.h" +#include "xvasprintf.h" +#include "po-error.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +static void +xerror (int severity, const char *prefix_tail, + const char *filename, size_t lineno, size_t column, + int multiline_p, const char *message_text) +{ + if (multiline_p) + { + bool old_error_with_progname = error_with_progname; + char *prefix; + + if (filename != NULL) + { + if (lineno != (size_t)(-1)) + { + if (column != (size_t)(-1)) + prefix = + xasprintf ("%s:%ld:%ld: %s", filename, + (long) lineno, (long) column, prefix_tail); + else + prefix = + xasprintf ("%s:%ld: %s", filename, + (long) lineno, prefix_tail); + } + else + prefix = xasprintf ("%s: %s", filename, prefix_tail); + error_with_progname = false; + } + else + prefix = xasprintf ("%s: %s", program_name, prefix_tail); + + if (severity >= PO_SEVERITY_ERROR) + po_multiline_error (prefix, xstrdup (message_text)); + else + po_multiline_warning (prefix, xstrdup (message_text)); + error_with_progname = old_error_with_progname; + + if (severity == PO_SEVERITY_FATAL_ERROR) + exit (EXIT_FAILURE); + } + else + { + int exit_status = + (severity == PO_SEVERITY_FATAL_ERROR ? EXIT_FAILURE : 0); + + if (filename != NULL) + { + error_with_progname = false; + if (lineno != (size_t)(-1)) + { + if (column != (size_t)(-1)) + po_error (exit_status, 0, "%s:%ld:%ld: %s%s", + filename, (long) lineno, (long) column, + prefix_tail, message_text); + else + po_error_at_line (exit_status, 0, filename, lineno, "%s%s", + prefix_tail, message_text); + } + else + po_error (exit_status, 0, "%s: %s%s", + filename, prefix_tail, message_text); + error_with_progname = true; + } + else + po_error (exit_status, 0, "%s%s", prefix_tail, message_text); + if (severity < PO_SEVERITY_ERROR) + --error_message_count; + } +} + +/* The default error handler is based on the lower-level error handler + in po-error.h, so that gettext-po.h can offer to override one or the + other. */ +void +textmode_xerror (int severity, + const struct message_ty *message, + const char *filename, size_t lineno, size_t column, + int multiline_p, const char *message_text) +{ + const char *prefix_tail = + (severity == PO_SEVERITY_WARNING ? _("warning: ") : ""); + + if (message != NULL && (filename == NULL || lineno == (size_t)(-1))) + { + filename = message->pos.file_name; + lineno = message->pos.line_number; + column = (size_t)(-1); + } + + xerror (severity, prefix_tail, filename, lineno, column, + multiline_p, message_text); +} + +void +textmode_xerror2 (int severity, + const struct message_ty *message1, + const char *filename1, size_t lineno1, size_t column1, + int multiline_p1, const char *message_text1, + const struct message_ty *message2, + const char *filename2, size_t lineno2, size_t column2, + int multiline_p2, const char *message_text2) +{ + int severity1 = /* Don't exit before both texts have been output. */ + (severity == PO_SEVERITY_FATAL_ERROR ? PO_SEVERITY_ERROR : severity); + const char *prefix_tail = + (severity == PO_SEVERITY_WARNING ? _("warning: ") : ""); + + if (message1 != NULL && (filename1 == NULL || lineno1 == (size_t)(-1))) + { + filename1 = message1->pos.file_name; + lineno1 = message1->pos.line_number; + column1 = (size_t)(-1); + } + + if (message2 != NULL && (filename2 == NULL || lineno2 == (size_t)(-1))) + { + filename2 = message2->pos.file_name; + lineno2 = message2->pos.line_number; + column2 = (size_t)(-1); + } + + if (multiline_p1) + xerror (severity1, prefix_tail, filename1, lineno1, column1, multiline_p1, + message_text1); + else + { + char *message_text1_extended = xasprintf ("%s...", message_text1); + xerror (severity1, prefix_tail, filename1, lineno1, column1, + multiline_p1, message_text1_extended); + free (message_text1_extended); + } + + { + char *message_text2_extended = xasprintf ("...%s", message_text2); + xerror (severity, prefix_tail, filename2, lineno2, column2, + multiline_p2, message_text2_extended); + free (message_text2_extended); + } + + if (severity >= PO_SEVERITY_ERROR) + /* error_message_count needs to be incremented only by 1, not by 2. */ + --error_message_count; +} + +void (*po_xerror) (int severity, + const struct message_ty *message, + const char *filename, size_t lineno, size_t column, + int multiline_p, const char *message_text) + = textmode_xerror; + +void (*po_xerror2) (int severity, + const struct message_ty *message1, + const char *filename1, size_t lineno1, size_t column1, + int multiline_p1, const char *message_text1, + const struct message_ty *message2, + const char *filename2, size_t lineno2, size_t column2, + int multiline_p2, const char *message_text2) + = textmode_xerror2; diff --git a/gettext-tools/src/po-xerror.h b/gettext-tools/src/po-xerror.h new file mode 100644 index 0000000..521ec3f --- /dev/null +++ b/gettext-tools/src/po-xerror.h @@ -0,0 +1,82 @@ +/* Error handling during reading and writing of PO files. + Copyright (C) 2005 Free Software Foundation, Inc. + Written by Bruno Haible , 2005. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _PO_XERROR_H +#define _PO_XERROR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct message_ty; + + +/* A higher-level error handler than the one in po-error.h. */ + +/* These values must be the same as those in gettext-po.h. */ +#define PO_SEVERITY_WARNING 0 /* just a warning, tell the user */ +#define PO_SEVERITY_ERROR 1 /* an error, the operation cannot complete */ +#define PO_SEVERITY_FATAL_ERROR 2 /* an error, the operation must be aborted */ + +/* Signal a problem of the given severity. + MESSAGE and/or FILENAME + LINENO indicate where the problem occurred. + If FILENAME is NULL, FILENAME and LINENO and COLUMN should be ignored. + If LINENO is (size_t)(-1), LINENO and COLUMN should be ignored. + If COLUMN is (size_t)(-1), it should be ignored. + MESSAGE_TEXT is the problem description (if MULTILINE_P is true, + multiple lines of text, each terminated with a newline, otherwise + usually a single line). + Must not return if SEVERITY is PO_SEVERITY_FATAL_ERROR. */ +extern DLL_VARIABLE + void (*po_xerror) (int severity, + const struct message_ty *message, + const char *filename, size_t lineno, size_t column, + int multiline_p, const char *message_text); + +/* Signal a problem that refers to two messages. + Similar to two calls to po_xerror. + If possible, a "..." can be appended to MESSAGE_TEXT1 and prepended to + MESSAGE_TEXT2. */ +extern DLL_VARIABLE + void (*po_xerror2) (int severity, + const struct message_ty *message1, + const char *filename1, size_t lineno1, size_t column1, + int multiline_p1, const char *message_text1, + const struct message_ty *message2, + const char *filename2, size_t lineno2, size_t column2, + int multiline_p2, const char *message_text2); + +/* The default error handler. */ +extern void textmode_xerror (int severity, + const struct message_ty *message, + const char *filename, size_t lineno, size_t column, + int multiline_p, const char *message_text); +extern void textmode_xerror2 (int severity, + const struct message_ty *message1, + const char *filename1, size_t lineno1, size_t column1, + int multiline_p1, const char *message_text1, + const struct message_ty *message2, + const char *filename2, size_t lineno2, size_t column2, + int multiline_p2, const char *message_text2); + +#ifdef __cplusplus +} +#endif + +#endif /* _PO_XERROR_H */ diff --git a/gettext-tools/src/pos.h b/gettext-tools/src/pos.h new file mode 100644 index 0000000..684986e --- /dev/null +++ b/gettext-tools/src/pos.h @@ -0,0 +1,32 @@ +/* Source file positions. + Copyright (C) 1995-1998, 2000-2001 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _POS_H +#define _POS_H + +/* Get size_t. */ +#include + +/* Position of a message within a source file. + Used for error reporting purposes. */ +typedef struct lex_pos_ty lex_pos_ty; +struct lex_pos_ty +{ + char *file_name; + size_t line_number; +}; + +#endif /* _POS_H */ diff --git a/gettext-tools/src/project-id b/gettext-tools/src/project-id new file mode 100755 index 0000000..e60c8e3 --- /dev/null +++ b/gettext-tools/src/project-id @@ -0,0 +1,86 @@ +#!/bin/sh +# Prints a package's identification PACKAGE VERSION or PACKAGE. +# +# Copyright (C) 2001-2003, 2005 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +want_version="$1" + +# NLS nuisances: Letter ranges are different in the Estonian locale. +LC_ALL=C + +while true; do + if test -f configure; then + package=`(grep '^PACKAGE_NAME=' configure; grep '^ *PACKAGE=' configure) | grep -v '=[ ]*$' | sed -e '1q' | sed -e 's/^[^=]*=//' | sed -e "s/^'//" -e "s/'$//"` + case "$package" in + *[\"\$\`\{\}]*) + # Some packages (gcal) retrieve the package name dynamically. + package= + ;; + esac + if test -n "$package"; then + is_gnu=`LC_ALL=C grep "GNU $package" * 2>/dev/null | grep -v '^libtool:'` + if test -n "$is_gnu"; then + package="GNU $package" + fi + if test -n "$want_version"; then + version=`(grep '^PACKAGE_VERSION=' configure; grep '^ *VERSION=' configure) | grep -v '=[ ]*$' | sed -e '1q' | sed -e 's/^[^=]*=//' | sed -e "s/^'//" -e "s/'$//"` + case "$version" in + *[\"\$\`\{\}]*) + # Some packages (gcal, gcc, clisp) retrieve the version dynamically. + version= + ;; + esac + if test -n "$version"; then + echo "$package $version" + else + echo "$package" + fi + else + echo "$package" + fi + exit 0 + fi + fi + dir=`basename "\`pwd\`"` + case "$dir" in + i18n) + # This directory name, used in GNU make, is not the top level directory. + ;; + *[A-Za-z]*[0-9]*) + package=`echo "$dir" | sed -e 's/^\([^0-9]*\)[0-9].*$/\1/' -e 's/[-_]$//'` + if test -n "$want_version"; then + version=`echo "$dir" | sed -e 's/^[^0-9]*\([0-9].*\)$/\1/'` + echo "$package $version" + else + echo "$package" + fi + exit 0 + ;; + esac + # Go to parent directory + last=`/bin/pwd` + cd .. + curr=`/bin/pwd` + if test "$last" = "$curr"; then + # Oops, didn't find the package name. + if test -n "$want_version"; then + echo "PACKAGE VERSION" + else + echo "PACKAGE" + fi + exit 0 + fi +done diff --git a/gettext-tools/src/read-catalog-abstract.c b/gettext-tools/src/read-catalog-abstract.c new file mode 100644 index 0000000..d4e98ee --- /dev/null +++ b/gettext-tools/src/read-catalog-abstract.c @@ -0,0 +1,748 @@ +/* Reading PO files, abstract class. + Copyright (C) 1995-1996, 1998, 2000-2009 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "read-catalog-abstract.h" + +#include +#include +#include + +#include "xalloc.h" +#include "xvasprintf.h" +#include "po-xerror.h" +#include "error.h" +#include "gettext.h" + +/* Local variables. */ +static abstract_catalog_reader_ty *callback_arg; + + +/* ========================================================================= */ +/* Allocating and freeing instances of abstract_catalog_reader_ty. */ + + +abstract_catalog_reader_ty * +catalog_reader_alloc (abstract_catalog_reader_class_ty *method_table) +{ + abstract_catalog_reader_ty *pop; + + pop = (abstract_catalog_reader_ty *) xmalloc (method_table->size); + pop->methods = method_table; + if (method_table->constructor) + method_table->constructor (pop); + return pop; +} + + +void +catalog_reader_free (abstract_catalog_reader_ty *pop) +{ + if (pop->methods->destructor) + pop->methods->destructor (pop); + free (pop); +} + + +/* ========================================================================= */ +/* Inline functions to invoke the methods. */ + + +static inline void +call_parse_brief (abstract_catalog_reader_ty *pop) +{ + if (pop->methods->parse_brief) + pop->methods->parse_brief (pop); +} + +static inline void +call_parse_debrief (abstract_catalog_reader_ty *pop) +{ + if (pop->methods->parse_debrief) + pop->methods->parse_debrief (pop); +} + +static inline void +call_directive_domain (abstract_catalog_reader_ty *pop, char *name) +{ + if (pop->methods->directive_domain) + pop->methods->directive_domain (pop, name); +} + +static inline void +call_directive_message (abstract_catalog_reader_ty *pop, + char *msgctxt, + char *msgid, + lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete) +{ + if (pop->methods->directive_message) + pop->methods->directive_message (pop, msgctxt, + msgid, msgid_pos, msgid_plural, + msgstr, msgstr_len, msgstr_pos, + prev_msgctxt, + prev_msgid, + prev_msgid_plural, + force_fuzzy, obsolete); +} + +static inline void +call_comment (abstract_catalog_reader_ty *pop, const char *s) +{ + if (pop->methods->comment != NULL) + pop->methods->comment (pop, s); +} + +static inline void +call_comment_dot (abstract_catalog_reader_ty *pop, const char *s) +{ + if (pop->methods->comment_dot != NULL) + pop->methods->comment_dot (pop, s); +} + +static inline void +call_comment_filepos (abstract_catalog_reader_ty *pop, const char *name, + size_t line) +{ + if (pop->methods->comment_filepos) + pop->methods->comment_filepos (pop, name, line); +} + +static inline void +call_comment_special (abstract_catalog_reader_ty *pop, const char *s) +{ + if (pop->methods->comment_special != NULL) + pop->methods->comment_special (pop, s); +} + + +/* ========================================================================= */ +/* Exported functions. */ + + +static inline void +parse_start (abstract_catalog_reader_ty *pop) +{ + /* The parse will call the po_callback_... functions (see below) + when the various directive are recognised. The callback_arg + variable is used to tell these functions which instance is to + have the relevant method invoked. */ + callback_arg = pop; + + call_parse_brief (pop); +} + +static inline void +parse_end (abstract_catalog_reader_ty *pop) +{ + call_parse_debrief (pop); + callback_arg = NULL; +} + + +void +catalog_reader_parse (abstract_catalog_reader_ty *pop, FILE *fp, + const char *real_filename, const char *logical_filename, + catalog_input_format_ty input_syntax) +{ + error_message_count = 0; + + /* Parse the stream's content. */ + parse_start (pop); + input_syntax->parse (pop, fp, real_filename, logical_filename); + parse_end (pop); + + if (error_message_count > 0) + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, + /*real_filename*/ NULL, (size_t)(-1), (size_t)(-1), false, + xasprintf (ngettext ("found %d fatal error", + "found %d fatal errors", + error_message_count), + error_message_count)); +} + + +/* ========================================================================= */ +/* Callbacks used by po-gram.y or po-lex.c, indirectly from + catalog_reader_parse. */ + + +/* This function is called by po_gram_lex() whenever a domain directive + has been seen. */ +void +po_callback_domain (char *name) +{ + /* assert(callback_arg); */ + call_directive_domain (callback_arg, name); +} + + +/* This function is called by po_gram_lex() whenever a message has been + seen. */ +void +po_callback_message (char *msgctxt, + char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural, + char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete) +{ + /* assert(callback_arg); */ + call_directive_message (callback_arg, msgctxt, + msgid, msgid_pos, msgid_plural, + msgstr, msgstr_len, msgstr_pos, + prev_msgctxt, prev_msgid, prev_msgid_plural, + force_fuzzy, obsolete); +} + + +void +po_callback_comment (const char *s) +{ + /* assert(callback_arg); */ + call_comment (callback_arg, s); +} + + +void +po_callback_comment_dot (const char *s) +{ + /* assert(callback_arg); */ + call_comment_dot (callback_arg, s); +} + + +/* This function is called by po_parse_comment_filepos(), once for each + filename. */ +void +po_callback_comment_filepos (const char *name, size_t line) +{ + /* assert(callback_arg); */ + call_comment_filepos (callback_arg, name, line); +} + + +void +po_callback_comment_special (const char *s) +{ + /* assert(callback_arg); */ + call_comment_special (callback_arg, s); +} + + +/* Parse a special comment and put the result in *fuzzyp, formatp, *rangep, + *wrapp. */ +void +po_parse_comment_special (const char *s, + bool *fuzzyp, enum is_format formatp[NFORMATS], + struct argument_range *rangep, enum is_wrap *wrapp) +{ + size_t i; + + *fuzzyp = false; + for (i = 0; i < NFORMATS; i++) + formatp[i] = undecided; + rangep->min = -1; + rangep->max = -1; + *wrapp = undecided; + + while (*s != '\0') + { + const char *t; + + /* Skip whitespace. */ + while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) != NULL) + s++; + + /* Collect a token. */ + t = s; + while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) == NULL) + s++; + if (s != t) + { + size_t len = s - t; + + /* Accept fuzzy flag. */ + if (len == 5 && memcmp (t, "fuzzy", 5) == 0) + { + *fuzzyp = true; + continue; + } + + /* Accept format description. */ + if (len >= 7 && memcmp (t + len - 7, "-format", 7) == 0) + { + const char *p; + size_t n; + enum is_format value; + + p = t; + n = len - 7; + + if (n >= 3 && memcmp (p, "no-", 3) == 0) + { + p += 3; + n -= 3; + value = no; + } + else if (n >= 9 && memcmp (p, "possible-", 9) == 0) + { + p += 9; + n -= 9; + value = possible; + } + else if (n >= 11 && memcmp (p, "impossible-", 11) == 0) + { + p += 11; + n -= 11; + value = impossible; + } + else + value = yes; + + for (i = 0; i < NFORMATS; i++) + if (strlen (format_language[i]) == n + && memcmp (format_language[i], p, n) == 0) + { + formatp[i] = value; + break; + } + if (i < NFORMATS) + continue; + } + + /* Accept range description "range: ..". */ + if (len == 6 && memcmp (t, "range:", 6) == 0) + { + /* Skip whitespace. */ + while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) != NULL) + s++; + + /* Collect a token. */ + t = s; + while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) == NULL) + s++; + /* Parse it. */ + if (*t >= '0' && *t <= '9') + { + unsigned int min = 0; + + for (; *t >= '0' && *t <= '9'; t++) + { + if (min <= INT_MAX / 10) + { + min = 10 * min + (*t - '0'); + if (min > INT_MAX) + min = INT_MAX; + } + else + /* Avoid integer overflow. */ + min = INT_MAX; + } + if (*t++ == '.') + if (*t++ == '.') + if (*t >= '0' && *t <= '9') + { + unsigned int max = 0; + for (; *t >= '0' && *t <= '9'; t++) + { + if (max <= INT_MAX / 10) + { + max = 10 * max + (*t - '0'); + if (max > INT_MAX) + max = INT_MAX; + } + else + /* Avoid integer overflow. */ + max = INT_MAX; + } + if (min <= max) + { + rangep->min = min; + rangep->max = max; + continue; + } + } + } + } + + /* Accept wrap description. */ + if (len == 4 && memcmp (t, "wrap", 4) == 0) + { + *wrapp = yes; + continue; + } + if (len == 7 && memcmp (t, "no-wrap", 7) == 0) + { + *wrapp = no; + continue; + } + + /* Unknown special comment marker. It may have been generated + from a future xgettext version. Ignore it. */ + } + } +} + + +/* Parse a GNU style file comment. + Syntax: an arbitrary number of + STRING COLON NUMBER + or + STRING + The latter style, without line number, occurs in PO files converted e.g. + from Pascal .rst files or from OpenOffice resource files. + Call po_callback_comment_filepos for each of them. */ +static void +po_parse_comment_filepos (const char *s) +{ + while (*s != '\0') + { + while (*s == ' ' || *s == '\t' || *s == '\n') + s++; + if (*s != '\0') + { + const char *string_start = s; + + do + s++; + while (!(*s == '\0' || *s == ' ' || *s == '\t' || *s == '\n')); + + /* See if there is a COLON and NUMBER after the STRING, separated + through optional spaces. */ + { + const char *p = s; + + while (*p == ' ' || *p == '\t' || *p == '\n') + p++; + + if (*p == ':') + { + p++; + + while (*p == ' ' || *p == '\t' || *p == '\n') + p++; + + if (*p >= '0' && *p <= '9') + { + /* Accumulate a number. */ + size_t n = 0; + + do + { + n = n * 10 + (*p - '0'); + p++; + } + while (*p >= '0' && *p <= '9'); + + if (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n') + { + /* Parsed a GNU style file comment with spaces. */ + const char *string_end = s; + size_t string_length = string_end - string_start; + char *string = XNMALLOC (string_length + 1, char); + + memcpy (string, string_start, string_length); + string[string_length] = '\0'; + + po_callback_comment_filepos (string, n); + + free (string); + + s = p; + continue; + } + } + } + } + + /* See if there is a COLON at the end of STRING and a NUMBER after + it, separated through optional spaces. */ + if (s[-1] == ':') + { + const char *p = s; + + while (*p == ' ' || *p == '\t' || *p == '\n') + p++; + + if (*p >= '0' && *p <= '9') + { + /* Accumulate a number. */ + size_t n = 0; + + do + { + n = n * 10 + (*p - '0'); + p++; + } + while (*p >= '0' && *p <= '9'); + + if (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n') + { + /* Parsed a GNU style file comment with spaces. */ + const char *string_end = s - 1; + size_t string_length = string_end - string_start; + char *string = XNMALLOC (string_length + 1, char); + + memcpy (string, string_start, string_length); + string[string_length] = '\0'; + + po_callback_comment_filepos (string, n); + + free (string); + + s = p; + continue; + } + } + } + + /* See if there is a COLON and NUMBER at the end of the STRING, + without separating spaces. */ + { + const char *p = s; + + while (p > string_start) + { + p--; + if (!(*p >= '0' && *p <= '9')) + { + p++; + break; + } + } + + /* p now points to the beginning of the trailing digits segment + at the end of STRING. */ + + if (p < s + && p > string_start + 1 + && p[-1] == ':') + { + /* Parsed a GNU style file comment without spaces. */ + const char *string_end = p - 1; + + /* Accumulate a number. */ + { + size_t n = 0; + + do + { + n = n * 10 + (*p - '0'); + p++; + } + while (p < s); + + { + size_t string_length = string_end - string_start; + char *string = XNMALLOC (string_length + 1, char); + + memcpy (string, string_start, string_length); + string[string_length] = '\0'; + + po_callback_comment_filepos (string, n); + + free (string); + + continue; + } + } + } + } + + /* Parsed a file comment without line number. */ + { + const char *string_end = s; + size_t string_length = string_end - string_start; + char *string = XNMALLOC (string_length + 1, char); + + memcpy (string, string_start, string_length); + string[string_length] = '\0'; + + po_callback_comment_filepos (string, (size_t)(-1)); + + free (string); + } + } + } +} + + +/* Parse a SunOS or Solaris style file comment. + Syntax of SunOS style: + FILE_KEYWORD COLON STRING COMMA LINE_KEYWORD COLON NUMBER + Syntax of Solaris style: + FILE_KEYWORD COLON STRING COMMA LINE_KEYWORD NUMBER_KEYWORD COLON NUMBER + where + FILE_KEYWORD ::= "file" | "File" + COLON ::= ":" + COMMA ::= "," + LINE_KEYWORD ::= "line" + NUMBER_KEYWORD ::= "number" + NUMBER ::= [0-9]+ + Return true if parsed, false if not a comment of this form. */ +static bool +po_parse_comment_solaris_filepos (const char *s) +{ + if (s[0] == ' ' + && (s[1] == 'F' || s[1] == 'f') + && s[2] == 'i' && s[3] == 'l' && s[4] == 'e' + && s[5] == ':') + { + const char *string_start; + const char *string_end; + + { + const char *p = s + 6; + + while (*p == ' ' || *p == '\t') + p++; + string_start = p; + } + + for (string_end = string_start; *string_end != '\0'; string_end++) + { + const char *p = string_end; + + while (*p == ' ' || *p == '\t') + p++; + + if (*p == ',') + { + p++; + + while (*p == ' ' || *p == '\t') + p++; + + if (p[0] == 'l' && p[1] == 'i' && p[2] == 'n' && p[3] == 'e') + { + p += 4; + + while (*p == ' ' || *p == '\t') + p++; + + if (p[0] == 'n' && p[1] == 'u' && p[2] == 'm' + && p[3] == 'b' && p[4] == 'e' && p[5] == 'r') + { + p += 6; + while (*p == ' ' || *p == '\t') + p++; + } + + if (*p == ':') + { + p++; + + if (*p >= '0' && *p <= '9') + { + /* Accumulate a number. */ + size_t n = 0; + + do + { + n = n * 10 + (*p - '0'); + p++; + } + while (*p >= '0' && *p <= '9'); + + while (*p == ' ' || *p == '\t' || *p == '\n') + p++; + + if (*p == '\0') + { + /* Parsed a Sun style file comment. */ + size_t string_length = string_end - string_start; + char *string = + XNMALLOC (string_length + 1, char); + + memcpy (string, string_start, string_length); + string[string_length] = '\0'; + + po_callback_comment_filepos (string, n); + + free (string); + return true; + } + } + } + } + } + } + } + + return false; +} + + +/* This function is called by po_gram_lex() whenever a comment is + seen. It analyzes the comment to see what sort it is, and then + dispatches it to the appropriate method: call_comment, call_comment_dot, + call_comment_filepos (via po_parse_comment_filepos), or + call_comment_special. */ +void +po_callback_comment_dispatcher (const char *s) +{ + if (*s == '.') + { + s++; + /* There is usually a space before the comment. People don't + consider it part of the comment, therefore remove it here. */ + if (*s == ' ') + s++; + po_callback_comment_dot (s); + } + else if (*s == ':') + { + /* Parse the file location string. The appropriate callback will be + invoked. */ + po_parse_comment_filepos (s + 1); + } + else if (*s == ',' || *s == '!') + { + /* Get all entries in the special comment line. */ + po_callback_comment_special (s + 1); + } + else + { + /* It looks like a plain vanilla comment, but Solaris-style file + position lines do, too. Try to parse the lot. If the parse + succeeds, the appropriate callback will be invoked. */ + if (po_parse_comment_solaris_filepos (s)) + /* Do nothing, it is a Sun-style file pos line. */ ; + else + { + /* There is usually a space before the comment. People don't + consider it part of the comment, therefore remove it here. */ + if (*s == ' ') + s++; + po_callback_comment (s); + } + } +} diff --git a/gettext-tools/src/read-catalog-abstract.h b/gettext-tools/src/read-catalog-abstract.h new file mode 100644 index 0000000..c3fc84f --- /dev/null +++ b/gettext-tools/src/read-catalog-abstract.h @@ -0,0 +1,195 @@ +/* Reading PO files, abstract class. + Copyright (C) 1995-1996, 1998, 2000-2003, 2005-2006, 2008-2009, 2012 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_CATALOG_ABSTRACT_H +#define _READ_CATALOG_ABSTRACT_H + +#include "po-lex.h" +#include "message.h" + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Note: the _t suffix is reserved by ANSI C, so the _ty suffix is + used to indicate a type name. */ + +/* The following pair of structures cooperate to create an "Object" in + the OO sense. We are simply doing it manually, rather than with the + help of an OO compiler. This implementation allows polymorphism + and inheritance - more than enough for the immediate needs. */ + +/* Forward declaration. */ +struct abstract_catalog_reader_ty; + + +/* This first structure, playing the role of the "Class" in OO sense, + contains pointers to functions. Each function is a method for the + class (base or derived). Use a NULL pointer where no action is + required. */ + +typedef struct abstract_catalog_reader_class_ty + abstract_catalog_reader_class_ty; +struct abstract_catalog_reader_class_ty +{ + /* how many bytes to malloc for an instance of this class */ + size_t size; + + /* what to do immediately after the instance is malloc()ed */ + void (*constructor) (struct abstract_catalog_reader_ty *pop); + + /* what to do immediately before the instance is free()ed */ + void (*destructor) (struct abstract_catalog_reader_ty *pop); + + /* This method is invoked before the parse, but after the file is + opened by the lexer. */ + void (*parse_brief) (struct abstract_catalog_reader_ty *pop); + + /* This method is invoked after the parse, but before the file is + closed by the lexer. The intention is to make consistency checks + against the file here, and emit the errors through the lex_error* + functions. */ + void (*parse_debrief) (struct abstract_catalog_reader_ty *pop); + + /* what to do with a domain directive */ + void (*directive_domain) (struct abstract_catalog_reader_ty *pop, char *name); + + /* what to do with a message directive */ + void (*directive_message) (struct abstract_catalog_reader_ty *pop, + char *msgctxt, + char *msgid, lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, char *prev_msgid_plural, + bool force_fuzzy, bool obsolete); + + /* What to do with a plain-vanilla comment - the expectation is that + they will be accumulated, and added to the next message + definition seen. Or completely ignored. */ + void (*comment) (struct abstract_catalog_reader_ty *pop, const char *s); + + /* What to do with a comment that starts with a dot (i.e. extracted + by xgettext) - the expectation is that they will be accumulated, + and added to the next message definition seen. Or completely + ignored. */ + void (*comment_dot) (struct abstract_catalog_reader_ty *pop, const char *s); + + /* What to do with a file position seen in a comment (i.e. a message + location comment extracted by xgettext) - the expectation is that + they will be accumulated, and added to the next message + definition seen. Or completely ignored. */ + void (*comment_filepos) (struct abstract_catalog_reader_ty *pop, + const char *s, size_t line); + + /* What to do with a comment that starts with a ',' or '!' - this is a + special comment. One of the possible uses is to indicate a + inexact translation. */ + void (*comment_special) (struct abstract_catalog_reader_ty *pop, + const char *s); +}; + + +/* This next structure defines the base class passed to the methods. + Derived methods will often need to cast their first argument before + using it (this corresponds to the implicit 'this' argument in C++). + + When declaring derived classes, use the ABSTRACT_CATALOG_READER_TY define + at the start of the structure, to declare inherited instance variables, + etc. */ + +#define ABSTRACT_CATALOG_READER_TY \ + abstract_catalog_reader_class_ty *methods; + +typedef struct abstract_catalog_reader_ty abstract_catalog_reader_ty; +struct abstract_catalog_reader_ty +{ + ABSTRACT_CATALOG_READER_TY +}; + + +/* This structure describes a textual catalog input format. */ +struct catalog_input_format +{ + /* Parses the contents of FP, invoking the appropriate callbacks. */ + void (*parse) (abstract_catalog_reader_ty *pop, FILE *fp, + const char *real_filename, const char *logical_filename); + + /* Whether the parse function always produces messages encoded in UTF-8 + encoding. */ + bool produces_utf8; +}; + +typedef const struct catalog_input_format * catalog_input_format_ty; + + +/* Allocate a fresh abstract_catalog_reader_ty (or derived class) instance and + call its constructor. */ +extern abstract_catalog_reader_ty * + catalog_reader_alloc (abstract_catalog_reader_class_ty *method_table); + +/* Read a PO file from a stream, and dispatch to the various + abstract_catalog_reader_class_ty methods. */ +extern void + catalog_reader_parse (abstract_catalog_reader_ty *pop, FILE *fp, + const char *real_filename, + const char *logical_filename, + catalog_input_format_ty input_syntax); + +/* Call the destructor and deallocate a abstract_catalog_reader_ty (or derived + class) instance. */ +extern void + catalog_reader_free (abstract_catalog_reader_ty *pop); + + +/* Callbacks used by po-gram.y or po-lex.c, indirectly from + catalog_reader_parse. */ +extern void po_callback_domain (char *name); +extern void po_callback_message (char *msgctxt, + char *msgid, lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, char *prev_msgid_plural, + bool force_fuzzy, bool obsolete); +extern void po_callback_comment (const char *s); +extern void po_callback_comment_dot (const char *s); +extern void po_callback_comment_filepos (const char *s, size_t line); +extern void po_callback_comment_special (const char *s); +extern void po_callback_comment_dispatcher (const char *s); + +/* Parse a special comment and put the result in *fuzzyp, formatp, *rangep, + *wrapp. */ +extern void po_parse_comment_special (const char *s, bool *fuzzyp, + enum is_format formatp[NFORMATS], + struct argument_range *rangep, + enum is_wrap *wrapp); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _READ_CATALOG_ABSTRACT_H */ diff --git a/gettext-tools/src/read-catalog.c b/gettext-tools/src/read-catalog.c new file mode 100644 index 0000000..4642249 --- /dev/null +++ b/gettext-tools/src/read-catalog.c @@ -0,0 +1,494 @@ +/* Reading PO files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009 Free Software Foundation, Inc. + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-catalog.h" + +#include +#include +#include + +#include "open-catalog.h" +#include "po-charset.h" +#include "po-xerror.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* ========================================================================= */ +/* Inline functions to invoke the methods. */ + +static inline void +call_set_domain (struct default_catalog_reader_ty *this, char *name) +{ + default_catalog_reader_class_ty *methods = + (default_catalog_reader_class_ty *) this->methods; + + if (methods->set_domain) + methods->set_domain (this, name); +} + +static inline void +call_add_message (struct default_catalog_reader_ty *this, + char *msgctxt, + char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural, + char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos, + char *prev_msgctxt, char *prev_msgid, char *prev_msgid_plural, + bool force_fuzzy, bool obsolete) +{ + default_catalog_reader_class_ty *methods = + (default_catalog_reader_class_ty *) this->methods; + + if (methods->add_message) + methods->add_message (this, msgctxt, + msgid, msgid_pos, msgid_plural, + msgstr, msgstr_len, msgstr_pos, + prev_msgctxt, prev_msgid, prev_msgid_plural, + force_fuzzy, obsolete); +} + +static inline void +call_frob_new_message (struct default_catalog_reader_ty *this, message_ty *mp, + const lex_pos_ty *msgid_pos, + const lex_pos_ty *msgstr_pos) +{ + default_catalog_reader_class_ty *methods = + (default_catalog_reader_class_ty *) this->methods; + + if (methods->frob_new_message) + methods->frob_new_message (this, mp, msgid_pos, msgstr_pos); +} + + +/* ========================================================================= */ +/* Implementation of default_catalog_reader_ty's methods. */ + + +/* Implementation of methods declared in the superclass. */ + + +/* Prepare for first message. */ +void +default_constructor (abstract_catalog_reader_ty *that) +{ + default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; + size_t i; + + this->domain = MESSAGE_DOMAIN_DEFAULT; + this->comment = NULL; + this->comment_dot = NULL; + this->filepos_count = 0; + this->filepos = NULL; + this->is_fuzzy = false; + for (i = 0; i < NFORMATS; i++) + this->is_format[i] = undecided; + this->range.min = -1; + this->range.max = -1; + this->do_wrap = undecided; +} + + +void +default_destructor (abstract_catalog_reader_ty *that) +{ + default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; + size_t j; + + /* Do not free this->mdlp and this->mlp. */ + if (this->handle_comments) + { + if (this->comment != NULL) + string_list_free (this->comment); + if (this->comment_dot != NULL) + string_list_free (this->comment_dot); + } + + for (j = 0; j < this->filepos_count; ++j) + free (this->filepos[j].file_name); + if (this->filepos != NULL) + free (this->filepos); +} + + +void +default_parse_brief (abstract_catalog_reader_ty *that) +{ + /* We need to parse comments, because even if this->handle_comments + is false, we need to know which messages are fuzzy. */ + po_lex_pass_comments (true); +} + + +void +default_parse_debrief (abstract_catalog_reader_ty *that) +{ +} + + +/* Add the accumulated comments to the message. */ +static void +default_copy_comment_state (default_catalog_reader_ty *this, message_ty *mp) +{ + size_t j, i; + + if (this->handle_comments) + { + if (this->comment != NULL) + for (j = 0; j < this->comment->nitems; ++j) + message_comment_append (mp, this->comment->item[j]); + if (this->comment_dot != NULL) + for (j = 0; j < this->comment_dot->nitems; ++j) + message_comment_dot_append (mp, this->comment_dot->item[j]); + } + for (j = 0; j < this->filepos_count; ++j) + { + lex_pos_ty *pp; + + pp = &this->filepos[j]; + message_comment_filepos (mp, pp->file_name, pp->line_number); + } + mp->is_fuzzy = this->is_fuzzy; + for (i = 0; i < NFORMATS; i++) + mp->is_format[i] = this->is_format[i]; + mp->range = this->range; + mp->do_wrap = this->do_wrap; +} + + +static void +default_reset_comment_state (default_catalog_reader_ty *this) +{ + size_t j, i; + + if (this->handle_comments) + { + if (this->comment != NULL) + { + string_list_free (this->comment); + this->comment = NULL; + } + if (this->comment_dot != NULL) + { + string_list_free (this->comment_dot); + this->comment_dot = NULL; + } + } + for (j = 0; j < this->filepos_count; ++j) + free (this->filepos[j].file_name); + if (this->filepos != NULL) + free (this->filepos); + this->filepos_count = 0; + this->filepos = NULL; + this->is_fuzzy = false; + for (i = 0; i < NFORMATS; i++) + this->is_format[i] = undecided; + this->range.min = -1; + this->range.max = -1; + this->do_wrap = undecided; +} + + +/* Process 'domain' directive from .po file. */ +void +default_directive_domain (abstract_catalog_reader_ty *that, char *name) +{ + default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; + + call_set_domain (this, name); + + /* If there are accumulated comments, throw them away, they are + probably part of the file header, or about the domain directive, + and will be unrelated to the next message. */ + default_reset_comment_state (this); +} + + +/* Process ['msgctxt'/]'msgid'/'msgstr' pair from .po file. */ +void +default_directive_message (abstract_catalog_reader_ty *that, + char *msgctxt, + char *msgid, + lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, char *prev_msgid_plural, + bool force_fuzzy, bool obsolete) +{ + default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; + + call_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural, + msgstr, msgstr_len, msgstr_pos, + prev_msgctxt, prev_msgid, prev_msgid_plural, + force_fuzzy, obsolete); + + /* Prepare for next message. */ + default_reset_comment_state (this); +} + + +void +default_comment (abstract_catalog_reader_ty *that, const char *s) +{ + default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; + + if (this->handle_comments) + { + if (this->comment == NULL) + this->comment = string_list_alloc (); + string_list_append (this->comment, s); + } +} + + +void +default_comment_dot (abstract_catalog_reader_ty *that, const char *s) +{ + default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; + + if (this->handle_comments) + { + if (this->comment_dot == NULL) + this->comment_dot = string_list_alloc (); + string_list_append (this->comment_dot, s); + } +} + + +void +default_comment_filepos (abstract_catalog_reader_ty *that, + const char *name, size_t line) +{ + default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; + size_t nbytes; + lex_pos_ty *pp; + + nbytes = (this->filepos_count + 1) * sizeof (this->filepos[0]); + this->filepos = xrealloc (this->filepos, nbytes); + pp = &this->filepos[this->filepos_count++]; + pp->file_name = xstrdup (name); + pp->line_number = line; +} + + +/* Test for '#, fuzzy' comments and warn. */ +void +default_comment_special (abstract_catalog_reader_ty *that, const char *s) +{ + default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; + + po_parse_comment_special (s, &this->is_fuzzy, this->is_format, &this->range, + &this->do_wrap); +} + + +/* Default implementation of methods not inherited from the superclass. */ + + +void +default_set_domain (default_catalog_reader_ty *this, char *name) +{ + if (this->allow_domain_directives) + /* Override current domain name. Don't free memory. */ + this->domain = name; + else + { + po_gram_error_at_line (&gram_pos, + _("this file may not contain domain directives")); + + /* NAME was allocated in po-gram-gen.y but is not used anywhere. */ + free (name); + } +} + +void +default_add_message (default_catalog_reader_ty *this, + char *msgctxt, + char *msgid, + lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete) +{ + message_ty *mp; + + if (this->mdlp != NULL) + /* Select the appropriate sublist of this->mdlp. */ + this->mlp = msgdomain_list_sublist (this->mdlp, this->domain, true); + + if (this->allow_duplicates && msgid[0] != '\0') + /* Doesn't matter if this message ID has been seen before. */ + mp = NULL; + else + /* See if this message ID has been seen before. */ + mp = message_list_search (this->mlp, msgctxt, msgid); + + if (mp) + { + if (!(this->allow_duplicates_if_same_msgstr + && msgstr_len == mp->msgstr_len + && memcmp (msgstr, mp->msgstr, msgstr_len) == 0)) + { + /* We give a fatal error about this, regardless whether the + translations are equal or different. This is for consistency + with msgmerge, msgcat and others. The user can use the + msguniq program to get rid of duplicates. */ + po_xerror2 (PO_SEVERITY_ERROR, + NULL, msgid_pos->file_name, msgid_pos->line_number, + (size_t)(-1), false, _("duplicate message definition"), + mp, NULL, 0, 0, false, + _("this is the location of the first definition")); + } + /* We don't need the just constructed entries' parameter string + (allocated in po-gram-gen.y). */ + free (msgid); + if (msgid_plural != NULL) + free (msgid_plural); + free (msgstr); + if (msgctxt != NULL) + free (msgctxt); + if (prev_msgctxt != NULL) + free (prev_msgctxt); + if (prev_msgid != NULL) + free (prev_msgid); + if (prev_msgid_plural != NULL) + free (prev_msgid_plural); + + /* Add the accumulated comments to the message. */ + default_copy_comment_state (this, mp); + } + else + { + /* Construct message to add to the list. + Obsolete message go into the list at least for duplicate checking. + It's the caller's responsibility to ignore obsolete messages when + appropriate. */ + mp = message_alloc (msgctxt, msgid, msgid_plural, msgstr, msgstr_len, + msgstr_pos); + mp->prev_msgctxt = prev_msgctxt; + mp->prev_msgid = prev_msgid; + mp->prev_msgid_plural = prev_msgid_plural; + mp->obsolete = obsolete; + default_copy_comment_state (this, mp); + if (force_fuzzy) + mp->is_fuzzy = true; + + call_frob_new_message (this, mp, msgid_pos, msgstr_pos); + + message_list_append (this->mlp, mp); + } +} + + +/* So that the one parser can be used for multiple programs, and also + use good data hiding and encapsulation practices, an object + oriented approach has been taken. An object instance is allocated, + and all actions resulting from the parse will be through + invocations of method functions of that object. */ + +static default_catalog_reader_class_ty default_methods = +{ + { + sizeof (default_catalog_reader_ty), + default_constructor, + default_destructor, + default_parse_brief, + default_parse_debrief, + default_directive_domain, + default_directive_message, + default_comment, + default_comment_dot, + default_comment_filepos, + default_comment_special + }, + default_set_domain, /* set_domain */ + default_add_message, /* add_message */ + NULL /* frob_new_message */ +}; + + +default_catalog_reader_ty * +default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table) +{ + return + (default_catalog_reader_ty *) catalog_reader_alloc (&method_table->super); +} + + +/* ========================================================================= */ +/* Exported functions. */ + + +/* If false, duplicate msgids in the same domain and file generate an error. + If true, such msgids are allowed; the caller should treat them + appropriately. Defaults to false. */ +bool allow_duplicates = false; + + +msgdomain_list_ty * +read_catalog_stream (FILE *fp, const char *real_filename, + const char *logical_filename, + catalog_input_format_ty input_syntax) +{ + default_catalog_reader_ty *pop; + msgdomain_list_ty *mdlp; + + pop = default_catalog_reader_alloc (&default_methods); + pop->handle_comments = true; + pop->allow_domain_directives = true; + pop->allow_duplicates = allow_duplicates; + pop->allow_duplicates_if_same_msgstr = false; + pop->file_name = real_filename; + pop->mdlp = msgdomain_list_alloc (!pop->allow_duplicates); + pop->mlp = msgdomain_list_sublist (pop->mdlp, pop->domain, true); + if (input_syntax->produces_utf8) + /* We know a priori that input_syntax->parse convert strings to UTF-8. */ + pop->mdlp->encoding = po_charset_utf8; + po_lex_pass_obsolete_entries (true); + catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename, + logical_filename, input_syntax); + mdlp = pop->mdlp; + catalog_reader_free ((abstract_catalog_reader_ty *) pop); + return mdlp; +} + + +msgdomain_list_ty * +read_catalog_file (const char *filename, catalog_input_format_ty input_syntax) +{ + char *real_filename; + FILE *fp = open_catalog_file (filename, &real_filename, true); + msgdomain_list_ty *result; + + result = read_catalog_stream (fp, real_filename, filename, input_syntax); + + if (fp != stdin) + fclose (fp); + + return result; +} diff --git a/gettext-tools/src/read-catalog.h b/gettext-tools/src/read-catalog.h new file mode 100644 index 0000000..f567d78 --- /dev/null +++ b/gettext-tools/src/read-catalog.h @@ -0,0 +1,192 @@ +/* Reading PO files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009 Free Software Foundation, Inc. + This file was written by Bruno Haible . + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_CATALOG_H +#define _READ_CATALOG_H + +#include "message.h" +#include "read-catalog-abstract.h" + +#include +#include + + +/* For including this file in C++ mode. */ +#ifdef __cplusplus +# define this thiss +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* The following pair of structures cooperate to create a derived class from + class abstract_catalog_reader_ty. (See read-catalog-abstract.h for an + explanation.) It implements the default behaviour of reading a PO file + and converting it to an 'msgdomain_list_ty *'. */ + +/* Forward declaration. */ +struct default_catalog_reader_ty; + + +typedef struct default_catalog_reader_class_ty default_catalog_reader_class_ty; +struct default_catalog_reader_class_ty +{ + /* Methods inherited from superclass. */ + struct abstract_catalog_reader_class_ty super; + + /* How to change the current domain. */ + void (*set_domain) (struct default_catalog_reader_ty *pop, char *name); + + /* How to add a message to the list. */ + void (*add_message) (struct default_catalog_reader_ty *pop, + char *msgctxt, + char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural, + char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete); + + /* How to modify a new message before adding it to the list. */ + void (*frob_new_message) (struct default_catalog_reader_ty *pop, + message_ty *mp, + const lex_pos_ty *msgid_pos, + const lex_pos_ty *msgstr_pos); +}; + + +#define DEFAULT_CATALOG_READER_TY \ + ABSTRACT_CATALOG_READER_TY \ + \ + /* If true, pay attention to comments and filepos comments. */ \ + bool handle_comments; \ + \ + /* If false, domain directives lead to an error messsage. */ \ + bool allow_domain_directives; \ + \ + /* If false, duplicate msgids in the same domain and file generate an \ + error. If true, such msgids are allowed; the caller should treat \ + them appropriately. */ \ + bool allow_duplicates; \ + \ + /* If true, allow duplicates if they have the same translation. */ \ + bool allow_duplicates_if_same_msgstr; \ + \ + /* File name used in error messages. */ \ + const char *file_name; \ + \ + /* List of messages already appeared in the current file. */ \ + msgdomain_list_ty *mdlp; \ + \ + /* Name of domain we are currently examining. */ \ + const char *domain; \ + \ + /* List of messages belonging to the current domain. */ \ + message_list_ty *mlp; \ + \ + /* Accumulate comments for next message directive. */ \ + string_list_ty *comment; \ + string_list_ty *comment_dot; \ + \ + /* Accumulate filepos comments for the next message directive. */ \ + size_t filepos_count; \ + lex_pos_ty *filepos; \ + \ + /* Flags transported in special comments. */ \ + bool is_fuzzy; \ + enum is_format is_format[NFORMATS]; \ + struct argument_range range; \ + enum is_wrap do_wrap; \ + +typedef struct default_catalog_reader_ty default_catalog_reader_ty; +struct default_catalog_reader_ty +{ + DEFAULT_CATALOG_READER_TY +}; + +extern void default_constructor (abstract_catalog_reader_ty *that); +extern void default_destructor (abstract_catalog_reader_ty *that); +extern void default_parse_brief (abstract_catalog_reader_ty *that); +extern void default_parse_debrief (abstract_catalog_reader_ty *that); +extern void default_directive_domain (abstract_catalog_reader_ty *that, + char *name); +extern void default_directive_message (abstract_catalog_reader_ty *that, + char *msgctxt, + char *msgid, + lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete); +extern void default_comment (abstract_catalog_reader_ty *that, const char *s); +extern void default_comment_dot (abstract_catalog_reader_ty *that, + const char *s); +extern void default_comment_filepos (abstract_catalog_reader_ty *that, + const char *name, size_t line); +extern void default_comment_special (abstract_catalog_reader_ty *that, + const char *s); +extern void default_set_domain (default_catalog_reader_ty *this, char *name); +extern void default_add_message (default_catalog_reader_ty *this, + char *msgctxt, + char *msgid, + lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete); + +/* Allocate a fresh default_catalog_reader_ty (or derived class) instance and + call its constructor. */ +extern default_catalog_reader_ty * + default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table); + + +/* If false, duplicate msgids in the same domain and file generate an error. + If true, such msgids are allowed; the caller should treat them + appropriately. Defaults to false. */ +extern DLL_VARIABLE bool allow_duplicates; + +/* Read the input file from a stream. Returns a list of messages. */ +extern msgdomain_list_ty * + read_catalog_stream (FILE *fp, + const char *real_filename, + const char *logical_filename, + catalog_input_format_ty input_syntax); + +/* Read the input file with the name INPUT_NAME. The ending .po is added + if necessary. If INPUT_NAME is not an absolute file name and the file is + not found, the list of directories in "dir-list.h" is searched. Returns + a list of messages. */ +extern msgdomain_list_ty * + read_catalog_file (const char *input_name, + catalog_input_format_ty input_syntax); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _READ_CATALOG_H */ diff --git a/gettext-tools/src/read-csharp.c b/gettext-tools/src/read-csharp.c new file mode 100644 index 0000000..df5ca83 --- /dev/null +++ b/gettext-tools/src/read-csharp.c @@ -0,0 +1,168 @@ +/* Reading C# satellite assemblies. + Copyright (C) 2003-2004, 2006-2008, 2011 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-csharp.h" + +#include +#include +#include +#include + +#include "msgunfmt.h" +#include "relocatable.h" +#include "csharpexec.h" +#include "spawn-pipe.h" +#include "wait-process.h" +#include "read-catalog.h" +#include "read-po.h" +#include "xalloc.h" +#include "concat-filename.h" +#include "error.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* A C# satellite assembly can only be manipulated by a C# execution engine. + So we start a C# process to execute the DumpResource program, and read its + output, which is .po format without comments. */ + +struct locals +{ + /* OUT */ + msgdomain_list_ty *mdlp; +}; + +static bool +execute_and_read_po_output (const char *progname, + const char *prog_path, char **prog_argv, + void *private_data) +{ + struct locals *l = (struct locals *) private_data; + pid_t child; + int fd[1]; + FILE *fp; + int exitstatus; + + /* Open a pipe to the C# execution engine. */ + child = create_pipe_in (progname, prog_path, prog_argv, DEV_NULL, false, + true, true, fd); + + fp = fdopen (fd[0], "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + /* Read the message list. */ + l->mdlp = read_catalog_stream (fp, "(pipe)", "(pipe)", &input_format_po); + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = + wait_subprocess (child, progname, false, false, true, true, NULL); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + progname, exitstatus); + + return false; +} + + +msgdomain_list_ty * +msgdomain_read_csharp (const char *resource_name, const char *locale_name, + const char *directory) +{ + char *culture_name; + const char *args[4]; + const char *gettextexedir; + const char *gettextlibdir; + char *assembly_path; + const char *libdirs[1]; + struct locals locals; + + /* Assign a default value to the resource name. */ + if (resource_name == NULL) + resource_name = "Messages"; + + /* Convert the locale name to a .NET specific culture name. */ + culture_name = xstrdup (locale_name); + { + char *p; + for (p = culture_name; *p != '\0'; p++) + if (*p == '_') + *p = '-'; + if (strncmp (culture_name, "sr-CS", 5) == 0) + memcpy (culture_name, "sr-SP", 5); + p = strchr (culture_name, '@'); + if (p != NULL) + { + if (strcmp (p, "@latin") == 0) + strcpy (p, "-Latn"); + else if (strcmp (p, "@cyrillic") == 0) + strcpy (p, "-Cyrl"); + } + if (strcmp (culture_name, "sr-SP") == 0) + { + free (culture_name); + culture_name = xstrdup ("sr-SP-Latn"); + } + else if (strcmp (culture_name, "uz-UZ") == 0) + { + free (culture_name); + culture_name = xstrdup ("uz-UZ-Latn"); + } + } + + /* Prepare arguments. */ + args[0] = directory; + args[1] = resource_name; + args[2] = culture_name; + args[3] = NULL; + + /* Make it possible to override the .exe location. This is + necessary for running the testsuite before "make install". */ + gettextexedir = getenv ("GETTEXTCSHARPEXEDIR"); + if (gettextexedir == NULL || gettextexedir[0] == '\0') + gettextexedir = relocate (LIBDIR "/gettext"); + + /* Make it possible to override the .dll location. This is + necessary for running the testsuite before "make install". */ + gettextlibdir = getenv ("GETTEXTCSHARPLIBDIR"); + if (gettextlibdir == NULL || gettextlibdir[0] == '\0') + gettextlibdir = relocate (LIBDIR); + + /* Dump the resource and retrieve the resulting output. */ + assembly_path = + xconcatenated_filename (gettextexedir, "msgunfmt.net", ".exe"); + libdirs[0] = gettextlibdir; + if (execute_csharp_program (assembly_path, libdirs, 1, + args, + verbose, false, + execute_and_read_po_output, &locals)) + /* An error message should already have been provided. */ + exit (EXIT_FAILURE); + + free (assembly_path); + free (culture_name); + + return locals.mdlp; +} diff --git a/gettext-tools/src/read-csharp.h b/gettext-tools/src/read-csharp.h new file mode 100644 index 0000000..f8b66b3 --- /dev/null +++ b/gettext-tools/src/read-csharp.h @@ -0,0 +1,30 @@ +/* Reading C# satellite assemblies. + Copyright (C) 2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_CSHARP_H +#define _READ_CSHARP_H + +#include "message.h" + +/* Read the Java resource given by resource_name and locale_name. + Returns a list of messages. */ +extern msgdomain_list_ty * + msgdomain_read_csharp (const char *resource_name, + const char *locale_name, + const char *directory); + +#endif /* _READ_CSHARP_H */ diff --git a/gettext-tools/src/read-desktop.c b/gettext-tools/src/read-desktop.c new file mode 100644 index 0000000..8f9243b --- /dev/null +++ b/gettext-tools/src/read-desktop.c @@ -0,0 +1,645 @@ +/* Reading Desktop Entry files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014 Free Software Foundation, Inc. + This file was written by Daiki Ueno . + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-desktop.h" + +#include "xalloc.h" + +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "error-progname.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "c-ctype.h" +#include "po-lex.h" +#include "po-xerror.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + +/* The syntax of a Desktop Entry file is defined at + http://standards.freedesktop.org/desktop-entry-spec/latest/index.html. */ + +desktop_reader_ty * +desktop_reader_alloc (desktop_reader_class_ty *method_table) +{ + desktop_reader_ty *reader; + + reader = (desktop_reader_ty *) xmalloc (method_table->size); + reader->methods = method_table; + if (method_table->constructor) + method_table->constructor (reader); + return reader; +} + +void +desktop_reader_free (desktop_reader_ty *reader) +{ + if (reader->methods->destructor) + reader->methods->destructor (reader); + free (reader); +} + +void +desktop_reader_handle_group (desktop_reader_ty *reader, const char *group) +{ + if (reader->methods->handle_group) + reader->methods->handle_group (reader, group); +} + +void +desktop_reader_handle_pair (desktop_reader_ty *reader, + lex_pos_ty *key_pos, + const char *key, + const char *locale, + const char *value) +{ + if (reader->methods->handle_pair) + reader->methods->handle_pair (reader, key_pos, key, locale, value); +} + +void +desktop_reader_handle_comment (desktop_reader_ty *reader, const char *s) +{ + if (reader->methods->handle_comment) + reader->methods->handle_comment (reader, s); +} + +void +desktop_reader_handle_blank (desktop_reader_ty *reader, const char *s) +{ + if (reader->methods->handle_blank) + reader->methods->handle_blank (reader, s); +} + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* File name and line number. */ +extern lex_pos_ty gram_pos; + +/* The input file stream. */ +static FILE *fp; + + +static int +phase1_getc () +{ + int c; + + c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("error while reading \"%s\""), + real_file_name), + errno_description)); + } + return EOF; + } + + return c; +} + +static inline void +phase1_ungetc (int c) +{ + if (c != EOF) + ungetc (c, fp); +} + + +static unsigned char phase2_pushback[2]; +static int phase2_pushback_length; + +static int +phase2_getc () +{ + int c; + + if (phase2_pushback_length) + c = phase2_pushback[--phase2_pushback_length]; + else + { + c = phase1_getc (); + + if (c == '\r') + { + int c2 = phase1_getc (); + if (c2 == '\n') + c = c2; + else + phase1_ungetc (c2); + } + } + + if (c == '\n') + gram_pos.line_number++; + + return c; +} + +static void +phase2_ungetc (int c) +{ + if (c == '\n') + --gram_pos.line_number; + if (c != EOF) + phase2_pushback[phase2_pushback_length++] = c; +} + +enum token_type_ty +{ + token_type_eof, + token_type_group, + token_type_pair, + /* Unlike other scanners, preserve comments and blank lines for + merging translations back into a desktop file, with msgfmt. */ + token_type_comment, + token_type_blank, + token_type_other +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; + const char *value; + const char *locale; +}; + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_group || tp->type == token_type_pair + || tp->type == token_type_comment || tp->type == token_type_blank) + free (tp->string); +} + +static void +desktop_lex (token_ty *tp) +{ + static char *buffer; + static size_t bufmax; + size_t bufpos; + +#undef APPEND +#define APPEND(c) \ + do \ + { \ + if (bufpos >= bufmax) \ + { \ + bufmax += 100; \ + buffer = xrealloc (buffer, bufmax); \ + } \ + buffer[bufpos++] = c; \ + } \ + while (0) + + bufpos = 0; + for (;;) + { + int c; + + c = phase2_getc (); + + switch (c) + { + case EOF: + tp->type = token_type_eof; + return; + + case '[': + { + bool non_blank = false; + + for (;;) + { + c = phase2_getc (); + switch (c) + { + default: + /* Group names may contain all ASCII characters + except for '[' and ']' and control characters. */ + if (!(c_isascii (c) && c != '[') && !c_iscntrl (c)) + break; + APPEND (c); + continue; + case '\n': + po_xerror (PO_SEVERITY_WARNING, NULL, + real_file_name, gram_pos.line_number, 0, false, + _("unterminated group name")); + break; + case EOF: case ']': + break; + } + break; + } + /* Skip until newline. */ + if (c != '\n') + { + for (;;) + { + if (c == '\n' || c == EOF) + break; + if (!c_isspace (c)) + non_blank = true; + c = phase2_getc (); + } + } + if (non_blank) + po_xerror (PO_SEVERITY_WARNING, NULL, + real_file_name, gram_pos.line_number, 0, false, + _("invalid non-blank character")); + APPEND (0); + tp->type = token_type_group; + tp->string = xstrdup (buffer); + return; + } + + case '#': + { + /* Read until newline. */ + for (;;) + { + c = phase2_getc (); + switch (c) + { + default: + APPEND (c); + continue; + case EOF: case '\n': + break; + } + break; + } + APPEND (0); + tp->type = token_type_comment; + tp->string = xstrdup (buffer); + return; + } + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '-': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + const char *locale = NULL; + const char *value = NULL; + for (;;) + { + APPEND (c); + + c = phase2_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '-': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + + case '[': + /* Finish the key part and start the locale part. */ + APPEND (0); + locale = &buffer[bufpos]; + + for (;;) + { + int c2 = phase2_getc (); + switch (c2) + { + default: + APPEND (c2); + continue; + case EOF: case ']': + break; + } + break; + } + break; + + default: + phase2_ungetc (c); + break; + } + break; + } + APPEND (0); + + /* Skip any whitespace before '='. */ + for (;;) + { + c = phase2_getc (); + switch (c) + { + default: + if (c_isspace (c)) + continue; + phase2_ungetc (c); + break; + case EOF: case '\n': + break; + } + break; + } + + c = phase2_getc (); + if (c != '=') + { + po_xerror (PO_SEVERITY_WARNING, NULL, + real_file_name, gram_pos.line_number, 0, false, + xasprintf (_("missing '=' after \"%s\""), buffer)); + for (;;) + { + c = phase2_getc (); + if (c == EOF || c == '\n') + break; + } + tp->type = token_type_other; + return; + } + + /* Skip any whitespace after '='. */ + for (;;) + { + c = phase2_getc (); + switch (c) + { + default: + if (c_isspace (c)) + continue; + phase2_ungetc (c); + break; + case EOF: case '\n': + break; + } + break; + } + + value = &buffer[bufpos]; + for (;;) + { + c = phase2_getc (); + if (c == EOF || c == '\n') + break; + APPEND (c); + } + APPEND (0); + tp->type = token_type_pair; + tp->string = xmemdup (buffer, bufpos); + tp->locale = locale; + tp->value = value; + return; + } + default: + { + bool non_blank = false; + + for (;;) + { + if (c == '\n' || c == EOF) + break; + + if (!c_isspace (c)) + non_blank = true; + else + APPEND (c); + + c = phase2_getc (); + } + if (non_blank) + { + po_xerror (PO_SEVERITY_WARNING, NULL, + real_file_name, gram_pos.line_number, 0, false, + _("invalid non-blank line")); + tp->type = token_type_other; + return; + } + APPEND (0); + tp->type = token_type_blank; + tp->string = xstrdup (buffer); + return; + } + } + } +#undef APPEND +} + +void +desktop_parse (desktop_reader_ty *reader, FILE *file, + const char *real_filename, const char *logical_filename) +{ + fp = file; + real_file_name = real_filename; + gram_pos.file_name = xstrdup (logical_filename); + gram_pos.line_number = 1; + + for (;;) + { + struct token_ty token; + desktop_lex (&token); + switch (token.type) + { + case token_type_eof: + goto out; + case token_type_group: + desktop_reader_handle_group (reader, token.string); + break; + case token_type_comment: + desktop_reader_handle_comment (reader, token.string); + break; + case token_type_pair: + desktop_reader_handle_pair (reader, &gram_pos, + token.string, token.locale, token.value); + break; + case token_type_blank: + desktop_reader_handle_blank (reader, token.string); + break; + case token_type_other: + break; + } + free_token (&token); + } + + out: + fp = NULL; + real_file_name = NULL; + gram_pos.line_number = 0; +} + +char * +desktop_escape_string (const char *s, bool is_list) +{ + char *buffer, *p; + + p = buffer = XNMALLOC (strlen (s) * 2 + 1, char); + + /* The first character must not be a whitespace. */ + if (*s == ' ') + { + p = stpcpy (p, "\\s"); + s++; + } + else if (*s == '\t') + { + p = stpcpy (p, "\\t"); + s++; + } + + for (;; s++) + { + if (*s == '\0') + { + *p = '\0'; + break; + } + + switch (*s) + { + case '\n': + p = stpcpy (p, "\\n"); + break; + case '\r': + p = stpcpy (p, "\\r"); + break; + case '\\': + if (is_list && *(s + 1) == ';') + { + p = stpcpy (p, "\\;"); + s++; + } + else + p = stpcpy (p, "\\\\"); + break; + default: + *p++ = *s; + break; + } + } + + return buffer; +} + +char * +desktop_unescape_string (const char *s, bool is_list) +{ + char *buffer, *p; + + p = buffer = XNMALLOC (strlen (s) + 1, char); + for (;; s++) + { + if (*s == '\0') + { + *p = '\0'; + break; + } + + if (*s == '\\') + { + s++; + + if (*s == '\0') + { + *p = '\0'; + break; + } + + switch (*s) + { + case 's': + *p++ = ' '; + break; + case 'n': + *p++ = '\n'; + break; + case 't': + *p++ = '\t'; + break; + case 'r': + *p++ = '\r'; + break; + case ';': + p = stpcpy (p, "\\;"); + break; + default: + *p++ = *s; + break; + } + } + else + *p++ = *s; + } + return buffer; +} + +void +desktop_add_keyword (hash_table *keywords, const char *name, bool is_list) +{ + hash_insert_entry (keywords, name, strlen (name), (void *) is_list); +} + +void +desktop_add_default_keywords (hash_table *keywords) +{ + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + desktop_add_keyword (keywords, "Name", false); + desktop_add_keyword (keywords, "GenericName", false); + desktop_add_keyword (keywords, "Comment", false); + desktop_add_keyword (keywords, "Icon", false); + desktop_add_keyword (keywords, "Keywords", true); +} diff --git a/gettext-tools/src/read-desktop.h b/gettext-tools/src/read-desktop.h new file mode 100644 index 0000000..19ad8d8 --- /dev/null +++ b/gettext-tools/src/read-desktop.h @@ -0,0 +1,121 @@ +/* Reading Desktop Entry files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014 Free Software Foundation, Inc. + This file was written by Daiki Ueno . + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_DESKTOP_H +#define _READ_DESKTOP_H + +#include +#include +#include "hash.h" +#include "po-lex.h" +#include "str-list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration. */ +struct desktop_reader_ty; + + +/* This first structure, playing the role of the "Class" in OO sense, + contains pointers to functions. Each function is a method for the + class (base or derived). Use a NULL pointer where no action is + required. */ + +typedef struct desktop_reader_class_ty desktop_reader_class_ty; +struct desktop_reader_class_ty +{ + /* how many bytes to malloc for an instance of this class */ + size_t size; + + /* what to do immediately after the instance is malloc()ed */ + void (*constructor) (struct desktop_reader_ty *pop); + + /* what to do immediately before the instance is free()ed */ + void (*destructor) (struct desktop_reader_ty *pop); + + /* what to do with a group header */ + void (*handle_group) (struct desktop_reader_ty *pop, + const char *group); + + /* what to do with a key/value pair */ + void (*handle_pair) (struct desktop_reader_ty *pop, + lex_pos_ty *key_pos, + const char *key, + const char *locale, + const char *value); + + /* what to do with a comment */ + void (*handle_comment) (struct desktop_reader_ty *pop, const char *s); + + /* what to do with a blank line */ + void (*handle_blank) (struct desktop_reader_ty *pop, const char *s); +}; + +/* This next structure defines the base class passed to the methods. + Derived methods will often need to cast their first argument before + using it (this corresponds to the implicit 'this' argument in C++). + + When declaring derived classes, use the DESKTOP_READER_TY define + at the start of the structure, to declare inherited instance variables, + etc. */ + +#define DESKTOP_READER_TY \ + desktop_reader_class_ty *methods; + +typedef struct desktop_reader_ty desktop_reader_ty; +struct desktop_reader_ty +{ + DESKTOP_READER_TY +}; + +desktop_reader_ty *desktop_reader_alloc (desktop_reader_class_ty *methods); +void desktop_reader_free (desktop_reader_ty *reader); + +void desktop_reader_handle_group (desktop_reader_ty *reader, + const char *group); + +void desktop_reader_handle_pair (desktop_reader_ty *reader, + lex_pos_ty *key_pos, + const char *key, + const char *locale, + const char *value); + +void desktop_reader_handle_comment (desktop_reader_ty *reader, + const char *s); + +void desktop_reader_handle_blank (desktop_reader_ty *reader, + const char *s); + + +void desktop_parse (desktop_reader_ty *reader, FILE *file, + const char *real_filename, const char *logical_filename); + + +char *desktop_escape_string (const char *s, bool is_list); +char *desktop_unescape_string (const char *s, bool is_list); + +void desktop_add_keyword (hash_table *keywords, const char *name, bool is_list); +void desktop_add_default_keywords (hash_table *keywords); + +#ifdef __cplusplus +} +#endif + + +#endif /* _READ_DESKTOP_H */ diff --git a/gettext-tools/src/read-java.c b/gettext-tools/src/read-java.c new file mode 100644 index 0000000..bc6f2f4 --- /dev/null +++ b/gettext-tools/src/read-java.c @@ -0,0 +1,138 @@ +/* Reading Java ResourceBundles. + Copyright (C) 2001-2003, 2006-2008, 2011 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-java.h" + +#include +#include +#include +#include + +#include "msgunfmt.h" +#include "relocatable.h" +#include "javaexec.h" +#include "spawn-pipe.h" +#include "wait-process.h" +#include "read-catalog.h" +#include "read-po.h" +#include "error.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* A Java resource name can only be manipulated by a Java virtual machine. + So we start a JVM to execute the DumpResource program, and read its + output, which is .po format without comments. */ + +struct locals +{ + /* OUT */ + msgdomain_list_ty *mdlp; +}; + +static bool +execute_and_read_po_output (const char *progname, + const char *prog_path, char **prog_argv, + void *private_data) +{ + struct locals *l = (struct locals *) private_data; + pid_t child; + int fd[1]; + FILE *fp; + int exitstatus; + + /* Open a pipe to the JVM. */ + child = create_pipe_in (progname, prog_path, prog_argv, DEV_NULL, false, + true, true, fd); + + fp = fdopen (fd[0], "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + /* Read the message list. */ + l->mdlp = read_catalog_stream (fp, "(pipe)", "(pipe)", &input_format_po); + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = + wait_subprocess (child, progname, false, false, true, true, NULL); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + progname, exitstatus); + + return false; +} + + +msgdomain_list_ty * +msgdomain_read_java (const char *resource_name, const char *locale_name) +{ + const char *class_name = "gnu.gettext.DumpResource"; + const char *gettextjexedir; + const char *gettextjar; + const char *args[3]; + struct locals locals; + +#if USEJEXE + /* Make it possible to override the executable's location. This is + necessary for running the testsuite before "make install". */ + gettextjexedir = getenv ("GETTEXTJEXEDIR"); + if (gettextjexedir == NULL || gettextjexedir[0] == '\0') + gettextjexedir = relocate (GETTEXTJEXEDIR); +#else + gettextjexedir = NULL; +#endif + + /* Make it possible to override the gettext.jar location. This is + necessary for running the testsuite before "make install". */ + gettextjar = getenv ("GETTEXTJAR"); + if (gettextjar == NULL || gettextjar[0] == '\0') + gettextjar = relocate (GETTEXTJAR); + + /* Assign a default value to the resource name. */ + if (resource_name == NULL) + resource_name = "Messages"; + + /* Prepare arguments. */ + args[0] = resource_name; + if (locale_name != NULL) + { + args[1] = locale_name; + args[2] = NULL; + } + else + args[1] = NULL; + + /* Dump the resource and retrieve the resulting output. + Here we use the user's CLASSPATH, not a minimal one, so that the + resource can be found. */ + if (execute_java_class (class_name, &gettextjar, 1, false, gettextjexedir, + args, + verbose, false, + execute_and_read_po_output, &locals)) + /* An error message should already have been provided. */ + exit (EXIT_FAILURE); + + return locals.mdlp; +} diff --git a/gettext-tools/src/read-java.h b/gettext-tools/src/read-java.h new file mode 100644 index 0000000..6b4d037 --- /dev/null +++ b/gettext-tools/src/read-java.h @@ -0,0 +1,29 @@ +/* Reading Java ResourceBundles. + Copyright (C) 2001-2002 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_JAVA_H +#define _READ_JAVA_H + +#include "message.h" + +/* Read the Java resource given by resource_name and locale_name. + Returns a list of messages. */ +extern msgdomain_list_ty * + msgdomain_read_java (const char *resource_name, + const char *locale_name); + +#endif /* _READ_JAVA_H */ diff --git a/gettext-tools/src/read-mo.c b/gettext-tools/src/read-mo.c new file mode 100644 index 0000000..b97bbad --- /dev/null +++ b/gettext-tools/src/read-mo.c @@ -0,0 +1,467 @@ +/* Reading binary .mo files. + Copyright (C) 1995-1998, 2000-2007 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-mo.h" + +#include +#include +#include +#include +#include +#include + +/* This include file describes the main part of binary .mo format. */ +#include "gmo.h" + +#include "error.h" +#include "xalloc.h" +#include "binary-io.h" +#include "message.h" +#include "format.h" +#include "gettext.h" +#include "xsize.h" + +#define _(str) gettext (str) + + +enum mo_endianness +{ + MO_LITTLE_ENDIAN, + MO_BIG_ENDIAN +}; + +/* We read the file completely into memory. This is more efficient than + lots of lseek(). This struct represents the .mo file in memory. */ +struct binary_mo_file +{ + const char *filename; + char *data; + size_t size; + enum mo_endianness endian; +}; + + +/* Read the contents of the given input stream. */ +static void +read_binary_mo_file (struct binary_mo_file *bfp, + FILE *fp, const char *filename) +{ + char *buf = NULL; + size_t alloc = 0; + size_t size = 0; + size_t count; + + while (!feof (fp)) + { + const size_t increment = 4096; + if (size + increment > alloc) + { + alloc = alloc + alloc / 2; + if (alloc < size + increment) + alloc = size + increment; + buf = (char *) xrealloc (buf, alloc); + } + count = fread (buf + size, 1, increment, fp); + if (count == 0) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + filename); + } + else + size += count; + } + buf = (char *) xrealloc (buf, size); + bfp->filename = filename; + bfp->data = buf; + bfp->size = size; +} + +/* Get a 32-bit number from the file, at the given file position. */ +static nls_uint32 +get_uint32 (const struct binary_mo_file *bfp, size_t offset) +{ + nls_uint32 b0, b1, b2, b3; + size_t end = xsum (offset, 4); + + if (size_overflow_p (end) || end > bfp->size) + error (EXIT_FAILURE, 0, _("file \"%s\" is truncated"), bfp->filename); + + b0 = *(unsigned char *) (bfp->data + offset + 0); + b1 = *(unsigned char *) (bfp->data + offset + 1); + b2 = *(unsigned char *) (bfp->data + offset + 2); + b3 = *(unsigned char *) (bfp->data + offset + 3); + if (bfp->endian == MO_LITTLE_ENDIAN) + return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); + else + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; +} + +/* Get a static string from the file, at the given file position. */ +static char * +get_string (const struct binary_mo_file *bfp, size_t offset, size_t *lengthp) +{ + /* See 'struct string_desc'. */ + nls_uint32 s_length = get_uint32 (bfp, offset); + nls_uint32 s_offset = get_uint32 (bfp, offset + 4); + size_t s_end = xsum3 (s_offset, s_length, 1); + + if (size_overflow_p (s_end) || s_end > bfp->size) + error (EXIT_FAILURE, 0, _("file \"%s\" is truncated"), bfp->filename); + if (bfp->data[s_offset + s_length] != '\0') + error (EXIT_FAILURE, 0, + _("file \"%s\" contains a not NUL terminated string"), + bfp->filename); + + *lengthp = s_length + 1; + return bfp->data + s_offset; +} + +/* Get a system dependent string from the file, at the given file position. */ +static char * +get_sysdep_string (const struct binary_mo_file *bfp, size_t offset, + const struct mo_file_header *header, size_t *lengthp) +{ + /* See 'struct sysdep_string'. */ + size_t length; + char *string; + size_t i; + char *p; + nls_uint32 s_offset; + + /* Compute the length. */ + length = 0; + for (i = 4; ; i += 8) + { + nls_uint32 segsize = get_uint32 (bfp, offset + i); + nls_uint32 sysdepref = get_uint32 (bfp, offset + i + 4); + nls_uint32 sysdep_segment_offset; + nls_uint32 ss_length; + nls_uint32 ss_offset; + size_t ss_end; + size_t n; + + length += segsize; + + if (sysdepref == SEGMENTS_END) + break; + if (sysdepref >= header->n_sysdep_segments) + /* Invalid. */ + error (EXIT_FAILURE, 0, _("file \"%s\" is not in GNU .mo format"), + bfp->filename); + /* See 'struct sysdep_segment'. */ + sysdep_segment_offset = header->sysdep_segments_offset + sysdepref * 8; + ss_length = get_uint32 (bfp, sysdep_segment_offset); + ss_offset = get_uint32 (bfp, sysdep_segment_offset + 4); + ss_end = xsum (ss_offset, ss_length); + if (size_overflow_p (ss_end) || ss_end > bfp->size) + error (EXIT_FAILURE, 0, _("file \"%s\" is truncated"), bfp->filename); + if (!(ss_length > 0 && bfp->data[ss_offset + ss_length - 1] == '\0')) + { + char location[30]; + sprintf (location, "sysdep_segment[%u]", (unsigned int) sysdepref); + error (EXIT_FAILURE, 0, + _("file \"%s\" contains a not NUL terminated string, at %s"), + bfp->filename, location); + } + n = strlen (bfp->data + ss_offset); + length += (n > 1 ? 1 + n + 1 : n); + } + + /* Allocate and fill the string. */ + string = XNMALLOC (length, char); + p = string; + s_offset = get_uint32 (bfp, offset); + for (i = 4; ; i += 8) + { + nls_uint32 segsize = get_uint32 (bfp, offset + i); + nls_uint32 sysdepref = get_uint32 (bfp, offset + i + 4); + nls_uint32 sysdep_segment_offset; + nls_uint32 ss_length; + nls_uint32 ss_offset; + size_t s_end = xsum (s_offset, segsize); + size_t n; + + if (size_overflow_p (s_end) || s_end > bfp->size) + error (EXIT_FAILURE, 0, _("file \"%s\" is truncated"), bfp->filename); + memcpy (p, bfp->data + s_offset, segsize); + p += segsize; + s_offset += segsize; + + if (sysdepref == SEGMENTS_END) + break; + if (sysdepref >= header->n_sysdep_segments) + abort (); + /* See 'struct sysdep_segment'. */ + sysdep_segment_offset = header->sysdep_segments_offset + sysdepref * 8; + ss_length = get_uint32 (bfp, sysdep_segment_offset); + ss_offset = get_uint32 (bfp, sysdep_segment_offset + 4); + if (ss_offset + ss_length > bfp->size) + abort (); + if (!(ss_length > 0 && bfp->data[ss_offset + ss_length - 1] == '\0')) + abort (); + n = strlen (bfp->data + ss_offset); + if (n > 1) + *p++ = '<'; + memcpy (p, bfp->data + ss_offset, n); + p += n; + if (n > 1) + *p++ = '>'; + } + + if (p != string + length) + abort (); + + *lengthp = length; + return string; +} + +/* Reads an existing .mo file and adds the messages to mlp. */ +void +read_mo_file (message_list_ty *mlp, const char *filename) +{ + FILE *fp; + struct binary_mo_file bf; + struct mo_file_header header; + unsigned int i; + static lex_pos_ty pos = { __FILE__, __LINE__ }; + + if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0) + { + fp = stdin; + SET_BINARY (fileno (fp)); + } + else + { + fp = fopen (filename, "rb"); + if (fp == NULL) + error (EXIT_FAILURE, errno, + _("error while opening \"%s\" for reading"), filename); + } + + /* Read the file contents into memory. */ + read_binary_mo_file (&bf, fp, filename); + + /* Get a 32-bit number from the file header. */ +# define GET_HEADER_FIELD(field) \ + get_uint32 (&bf, offsetof (struct mo_file_header, field)) + + /* We must grope the file to determine which endian it is. + Perversity of the universe tends towards maximum, so it will + probably not match the currently executing architecture. */ + bf.endian = MO_BIG_ENDIAN; + header.magic = GET_HEADER_FIELD (magic); + if (header.magic != _MAGIC) + { + bf.endian = MO_LITTLE_ENDIAN; + header.magic = GET_HEADER_FIELD (magic); + if (header.magic != _MAGIC) + { + unrecognised: + error (EXIT_FAILURE, 0, _("file \"%s\" is not in GNU .mo format"), + filename); + } + } + + header.revision = GET_HEADER_FIELD (revision); + + /* We support only the major revisions 0 and 1. */ + switch (header.revision >> 16) + { + case 0: + case 1: + /* Fill the header parts that apply to major revisions 0 and 1. */ + header.nstrings = GET_HEADER_FIELD (nstrings); + header.orig_tab_offset = GET_HEADER_FIELD (orig_tab_offset); + header.trans_tab_offset = GET_HEADER_FIELD (trans_tab_offset); + header.hash_tab_size = GET_HEADER_FIELD (hash_tab_size); + header.hash_tab_offset = GET_HEADER_FIELD (hash_tab_offset); + + for (i = 0; i < header.nstrings; i++) + { + message_ty *mp; + char *msgctxt; + char *msgid; + size_t msgid_len; + char *separator; + char *msgstr; + size_t msgstr_len; + + /* Read the msgctxt and msgid. */ + msgid = get_string (&bf, header.orig_tab_offset + i * 8, + &msgid_len); + /* Split into msgctxt and msgid. */ + separator = strchr (msgid, MSGCTXT_SEPARATOR); + if (separator != NULL) + { + /* The part before the MSGCTXT_SEPARATOR is the msgctxt. */ + *separator = '\0'; + msgctxt = msgid; + msgid = separator + 1; + msgid_len -= msgid - msgctxt; + } + else + msgctxt = NULL; + + /* Read the msgstr. */ + msgstr = get_string (&bf, header.trans_tab_offset + i * 8, + &msgstr_len); + + mp = message_alloc (msgctxt, + msgid, + (strlen (msgid) + 1 < msgid_len + ? msgid + strlen (msgid) + 1 + : NULL), + msgstr, msgstr_len, + &pos); + message_list_append (mlp, mp); + } + + switch (header.revision & 0xffff) + { + case 0: + break; + case 1: + default: + /* Fill the header parts that apply to minor revision >= 1. */ + header.n_sysdep_segments = GET_HEADER_FIELD (n_sysdep_segments); + header.sysdep_segments_offset = + GET_HEADER_FIELD (sysdep_segments_offset); + header.n_sysdep_strings = GET_HEADER_FIELD (n_sysdep_strings); + header.orig_sysdep_tab_offset = + GET_HEADER_FIELD (orig_sysdep_tab_offset); + header.trans_sysdep_tab_offset = + GET_HEADER_FIELD (trans_sysdep_tab_offset); + + for (i = 0; i < header.n_sysdep_strings; i++) + { + message_ty *mp; + char *msgctxt; + char *msgid; + size_t msgid_len; + char *separator; + char *msgstr; + size_t msgstr_len; + nls_uint32 offset; + size_t f; + + /* Read the msgctxt and msgid. */ + offset = get_uint32 (&bf, header.orig_sysdep_tab_offset + i * 4); + msgid = get_sysdep_string (&bf, offset, &header, &msgid_len); + /* Split into msgctxt and msgid. */ + separator = strchr (msgid, MSGCTXT_SEPARATOR); + if (separator != NULL) + { + /* The part before the MSGCTXT_SEPARATOR is the msgctxt. */ + *separator = '\0'; + msgctxt = msgid; + msgid = separator + 1; + msgid_len -= msgid - msgctxt; + } + else + msgctxt = NULL; + + /* Read the msgstr. */ + offset = get_uint32 (&bf, header.trans_sysdep_tab_offset + i * 4); + msgstr = get_sysdep_string (&bf, offset, &header, &msgstr_len); + + mp = message_alloc (msgctxt, + msgid, + (strlen (msgid) + 1 < msgid_len + ? msgid + strlen (msgid) + 1 + : NULL), + msgstr, msgstr_len, + &pos); + + /* Only messages with c-format or objc-format annotation are + recognized as having system-dependent strings by msgfmt. + Which one of the two, we don't know. We have to guess, + assuming that c-format is more probable than objc-format and + that the .mo was likely produced by "msgfmt -c". */ + for (f = format_c; ; f = format_objc) + { + bool valid = true; + struct formatstring_parser *parser = formatstring_parsers[f]; + const char *str_end; + const char *str; + + str_end = msgid + msgid_len; + for (str = msgid; str < str_end; str += strlen (str) + 1) + { + char *invalid_reason = NULL; + void *descr = + parser->parse (str, false, NULL, &invalid_reason); + + if (descr != NULL) + parser->free (descr); + else + { + free (invalid_reason); + valid = false; + break; + } + } + if (valid) + { + str_end = msgstr + msgstr_len; + for (str = msgstr; str < str_end; str += strlen (str) + 1) + { + char *invalid_reason = NULL; + void *descr = + parser->parse (str, true, NULL, &invalid_reason); + + if (descr != NULL) + parser->free (descr); + else + { + free (invalid_reason); + valid = false; + break; + } + } + } + + if (valid) + { + /* Found the most likely among c-format, objc-format. */ + mp->is_format[f] = yes; + break; + } + + /* Try next f. */ + if (f == format_objc) + break; + } + + message_list_append (mlp, mp); + } + break; + } + break; + + default: + goto unrecognised; + } + + if (fp != stdin) + fclose (fp); +} diff --git a/gettext-tools/src/read-mo.h b/gettext-tools/src/read-mo.h new file mode 100644 index 0000000..c8e976c --- /dev/null +++ b/gettext-tools/src/read-mo.h @@ -0,0 +1,26 @@ +/* Reading binary .mo files. + Copyright (C) 1995-1998, 2000-2003 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_MO_H +#define _READ_MO_H + +#include "message.h" + +/* Reads an existing .mo file and adds the messages to mlp. */ +extern void read_mo_file (message_list_ty *mlp, const char *filename); + +#endif /* _READ_MO_H */ diff --git a/gettext-tools/src/read-po.c b/gettext-tools/src/read-po.c new file mode 100644 index 0000000..04f1b74 --- /dev/null +++ b/gettext-tools/src/read-po.c @@ -0,0 +1,48 @@ +/* Reading PO files. + Copyright (C) 1995-1996, 1998, 2000-2006 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-po.h" + +#include "po-lex.h" +#include "po-gram.h" + +/* For compiling this file in C++ mode. */ +#ifdef __cplusplus +# define this thiss +#endif + + +/* Read a .po / .pot file from a stream, and dispatch to the various + abstract_catalog_reader_class_ty methods. */ +static void +po_parse (abstract_catalog_reader_ty *this, FILE *fp, + const char *real_filename, const char *logical_filename) +{ + lex_start (fp, real_filename, logical_filename); + po_gram_parse (); + lex_end (); +} + +const struct catalog_input_format input_format_po = +{ + po_parse, /* parse */ + false /* produces_utf8 */ +}; diff --git a/gettext-tools/src/read-po.h b/gettext-tools/src/read-po.h new file mode 100644 index 0000000..e426eaa --- /dev/null +++ b/gettext-tools/src/read-po.h @@ -0,0 +1,26 @@ +/* Reading PO files. + Copyright (C) 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_PO_H +#define _READ_PO_H + +#include "read-catalog-abstract.h" + +/* Describes a .po / .pot file parser. */ +extern DLL_VARIABLE const struct catalog_input_format input_format_po; + +#endif /* _READ_PO_H */ diff --git a/gettext-tools/src/read-properties.c b/gettext-tools/src/read-properties.c new file mode 100644 index 0000000..0c64730 --- /dev/null +++ b/gettext-tools/src/read-properties.c @@ -0,0 +1,560 @@ +/* Reading Java .properties files. + Copyright (C) 2003, 2005-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-properties.h" + +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "error-progname.h" +#include "message.h" +#include "read-catalog-abstract.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "po-xerror.h" +#include "msgl-ascii.h" +#include "unistr.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* For compiling this file in C++ mode. */ +#ifdef __cplusplus +# define this thiss +#endif + + +/* The format of the Java .properties files is documented in the JDK + documentation for class java.util.Properties. In the case of .properties + files for PropertyResourceBundle, each non-comment line contains a + key/value pair in the form "key = value" or "key : value" or "key value", + where the key is the msgid and the value is the msgstr. Messages with + plurals are not supported in this format. */ + +/* Handling of comments: We copy all comments from the .properties file to + the PO file. This is not really needed; it's a service for translators + who don't like PO files and prefer to maintain the .properties file. */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* File name and line number. */ +extern lex_pos_ty gram_pos; + +/* The input file stream. */ +static FILE *fp; + + +/* Phase 1: Read an ISO-8859-1 character. + Max. 1 pushback character. */ + +static int +phase1_getc () +{ + int c; + + c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("error while reading \"%s\""), + real_file_name), + errno_description)); + } + return EOF; + } + + return c; +} + +static inline void +phase1_ungetc (int c) +{ + if (c != EOF) + ungetc (c, fp); +} + + +/* Phase 2: Read an ISO-8859-1 character, treating CR/LF like a single LF. + Max. 2 pushback characters. */ + +static unsigned char phase2_pushback[2]; +static int phase2_pushback_length; + +static int +phase2_getc () +{ + int c; + + if (phase2_pushback_length) + c = phase2_pushback[--phase2_pushback_length]; + else + { + c = phase1_getc (); + + if (c == '\r') + { + int c2 = phase1_getc (); + if (c2 == '\n') + c = c2; + else + phase1_ungetc (c2); + } + } + + if (c == '\n') + gram_pos.line_number++; + + return c; +} + +static void +phase2_ungetc (int c) +{ + if (c == '\n') + --gram_pos.line_number; + if (c != EOF) + phase2_pushback[phase2_pushback_length++] = c; +} + + +/* Phase 3: Read an ISO-8859-1 character, treating CR/LF like a single LF, + with handling of continuation lines. + Max. 1 pushback character. */ + +static int +phase3_getc () +{ + int c = phase2_getc (); + + for (;;) + { + if (c != '\\') + return c; + + c = phase2_getc (); + if (c != '\n') + { + phase2_ungetc (c); + return '\\'; + } + + /* Skip the backslash-newline and all whitespace that follows it. */ + do + c = phase2_getc (); + while (c == ' ' || c == '\t' || c == '\r' || c == '\f'); + } +} + +static inline void +phase3_ungetc (int c) +{ + phase2_ungetc (c); +} + + +/* Phase 4: Read an UTF-16 codepoint, treating CR/LF like a single LF, + with handling of continuation lines and of \uxxxx sequences. */ + +static int +phase4_getuc () +{ + int c = phase3_getc (); + + if (c == EOF) + return -1; + if (c == '\\') + { + int c2 = phase3_getc (); + + if (c2 == 't') + return '\t'; + if (c2 == 'n') + return '\n'; + if (c2 == 'r') + return '\r'; + if (c2 == 'f') + return '\f'; + if (c2 == 'u') + { + unsigned int n = 0; + int i; + + for (i = 0; i < 4; i++) + { + int c1 = phase3_getc (); + + if (c1 >= '0' && c1 <= '9') + n = (n << 4) + (c1 - '0'); + else if (c1 >= 'A' && c1 <= 'F') + n = (n << 4) + (c1 - 'A' + 10); + else if (c1 >= 'a' && c1 <= 'f') + n = (n << 4) + (c1 - 'a' + 10); + else + { + phase3_ungetc (c1); + po_xerror (PO_SEVERITY_ERROR, NULL, + real_file_name, gram_pos.line_number, (size_t)(-1), + false, _("warning: invalid \\uxxxx syntax for Unicode character")); + return 'u'; + } + } + return n; + } + + return c2; + } + else + return c; +} + + +/* Converts a string from ISO-8859-1 encoding to UTF-8 encoding. */ +static char * +conv_from_iso_8859_1 (char *string) +{ + if (is_ascii_string (string)) + return string; + else + { + size_t length = strlen (string); + /* Each ISO-8859-1 character needs 2 bytes at worst. */ + unsigned char *utf8_string = XNMALLOC (2 * length + 1, unsigned char); + unsigned char *q = utf8_string; + const char *str = string; + const char *str_limit = str + length; + + while (str < str_limit) + { + unsigned int uc = (unsigned char) *str++; + int n = u8_uctomb (q, uc, 6); + assert (n > 0); + q += n; + } + *q = '\0'; + assert (q - utf8_string <= 2 * length); + + return (char *) utf8_string; + } +} + + +/* Converts a string from JAVA encoding (with \uxxxx sequences) to UTF-8 + encoding. May destructively modify the argument string. */ +static char * +conv_from_java (char *string) +{ + /* This conversion can only shrink the string, never increase its size. + So there is no need to xmalloc the result freshly. */ + const char *p = string; + unsigned char *q = (unsigned char *) string; + + while (*p != '\0') + { + if (p[0] == '\\' && p[1] == 'u') + { + unsigned int n = 0; + int i; + + for (i = 0; i < 4; i++) + { + int c1 = (unsigned char) p[2 + i]; + + if (c1 >= '0' && c1 <= '9') + n = (n << 4) + (c1 - '0'); + else if (c1 >= 'A' && c1 <= 'F') + n = (n << 4) + (c1 - 'A' + 10); + else if (c1 >= 'a' && c1 <= 'f') + n = (n << 4) + (c1 - 'a' + 10); + else + goto just_one_byte; + } + + if (i == 4) + { + unsigned int uc; + + if (n >= 0xd800 && n < 0xdc00) + { + if (p[6] == '\\' && p[7] == 'u') + { + unsigned int m = 0; + + for (i = 0; i < 4; i++) + { + int c1 = (unsigned char) p[8 + i]; + + if (c1 >= '0' && c1 <= '9') + m = (m << 4) + (c1 - '0'); + else if (c1 >= 'A' && c1 <= 'F') + m = (m << 4) + (c1 - 'A' + 10); + else if (c1 >= 'a' && c1 <= 'f') + m = (m << 4) + (c1 - 'a' + 10); + else + goto just_one_byte; + } + + if (i == 4 && (m >= 0xdc00 && m < 0xe000)) + { + /* Combine two UTF-16 words to a character. */ + uc = 0x10000 + ((n - 0xd800) << 10) + (m - 0xdc00); + p += 12; + } + else + goto just_one_byte; + } + else + goto just_one_byte; + } + else + { + uc = n; + p += 6; + } + + q += u8_uctomb (q, uc, 6); + continue; + } + } + just_one_byte: + *q++ = (unsigned char) *p++; + } + *q = '\0'; + return string; +} + + +/* Reads a key or value string. + Returns the string in UTF-8 encoding, or NULL if the end of the logical + line is reached. + Parsing ends: + - when returning NULL, after the end of the logical line, + - otherwise, if in_key is true, after the whitespace and possibly the + separator that follows after the string, + - otherwise, if in_key is false, after the end of the logical line. */ + +static char * +read_escaped_string (bool in_key) +{ + static unsigned short *buffer; + static size_t bufmax; + static size_t buflen; + int c; + + /* Skip whitespace before the string. */ + do + c = phase3_getc (); + while (c == ' ' || c == '\t' || c == '\r' || c == '\f'); + + if (c == EOF || c == '\n') + /* Empty string. */ + return NULL; + + /* Start accumulating the string. We store the string in UTF-16 before + converting it to UTF-8. Why not converting every character directly to + UTF-8? Because a string can contain surrogates like \uD800\uDF00, and + we must combine them to a single UTF-8 character. */ + buflen = 0; + for (;;) + { + if (in_key && (c == '=' || c == ':' + || c == ' ' || c == '\t' || c == '\r' || c == '\f')) + { + /* Skip whitespace after the string. */ + while (c == ' ' || c == '\t' || c == '\r' || c == '\f') + c = phase3_getc (); + /* Skip '=' or ':' separator. */ + if (!(c == '=' || c == ':')) + phase3_ungetc (c); + break; + } + + phase3_ungetc (c); + + /* Read the next UTF-16 codepoint. */ + c = phase4_getuc (); + if (c < 0) + break; + /* Append it to the buffer. */ + if (buflen >= bufmax) + { + bufmax += 100; + buffer = xrealloc (buffer, bufmax * sizeof (unsigned short)); + } + buffer[buflen++] = c; + + c = phase3_getc (); + if (c == EOF || c == '\n') + { + if (in_key) + phase3_ungetc (c); + break; + } + } + + /* Now convert from UTF-16 to UTF-8. */ + { + size_t pos; + unsigned char *utf8_string; + unsigned char *q; + + /* Each UTF-16 word needs 3 bytes at worst. */ + utf8_string = XNMALLOC (3 * buflen + 1, unsigned char); + for (pos = 0, q = utf8_string; pos < buflen; ) + { + ucs4_t uc; + int n; + + pos += u16_mbtouc (&uc, buffer + pos, buflen - pos); + n = u8_uctomb (q, uc, 6); + assert (n > 0); + q += n; + } + *q = '\0'; + assert (q - utf8_string <= 3 * buflen); + + return (char *) utf8_string; + } +} + + +/* Read a .properties file from a stream, and dispatch to the various + abstract_catalog_reader_class_ty methods. */ +static void +properties_parse (abstract_catalog_reader_ty *this, FILE *file, + const char *real_filename, const char *logical_filename) +{ + fp = file; + real_file_name = real_filename; + gram_pos.file_name = xstrdup (real_file_name); + gram_pos.line_number = 1; + + for (;;) + { + int c; + bool comment; + bool hidden; + + c = phase2_getc (); + + if (c == EOF) + break; + + comment = false; + hidden = false; + if (c == '#') + comment = true; + else if (c == '!') + { + /* For compatibility with write-properties.c, we treat '!' not + followed by space as a fuzzy or untranslated message. */ + int c2 = phase2_getc (); + if (c2 == ' ' || c2 == '\n' || c2 == EOF) + comment = true; + else + hidden = true; + phase2_ungetc (c2); + } + else + phase2_ungetc (c); + + if (comment) + { + /* A comment line. */ + static char *buffer; + static size_t bufmax; + static size_t buflen; + + buflen = 0; + for (;;) + { + c = phase2_getc (); + + if (buflen >= bufmax) + { + bufmax += 100; + buffer = xrealloc (buffer, bufmax); + } + + if (c == EOF || c == '\n') + break; + + buffer[buflen++] = c; + } + buffer[buflen] = '\0'; + + po_callback_comment_dispatcher (conv_from_java (conv_from_iso_8859_1 (buffer))); + } + else + { + /* A key/value pair. */ + char *msgid; + lex_pos_ty msgid_pos; + + msgid_pos = gram_pos; + msgid = read_escaped_string (true); + if (msgid == NULL) + /* Skip blank line. */ + ; + else + { + char *msgstr; + lex_pos_ty msgstr_pos; + bool force_fuzzy; + + msgstr_pos = gram_pos; + msgstr = read_escaped_string (false); + if (msgstr == NULL) + msgstr = xstrdup (""); + + /* Be sure to make the message fuzzy if it was commented out + and if it is not already header/fuzzy/untranslated. */ + force_fuzzy = (hidden && msgid[0] != '\0' && msgstr[0] != '\0'); + + po_callback_message (NULL, msgid, &msgid_pos, NULL, + msgstr, strlen (msgstr) + 1, &msgstr_pos, + NULL, NULL, NULL, + force_fuzzy, false); + } + } + } + + fp = NULL; + real_file_name = NULL; + gram_pos.line_number = 0; +} + +const struct catalog_input_format input_format_properties = +{ + properties_parse, /* parse */ + true /* produces_utf8 */ +}; diff --git a/gettext-tools/src/read-properties.h b/gettext-tools/src/read-properties.h new file mode 100644 index 0000000..8ac7852 --- /dev/null +++ b/gettext-tools/src/read-properties.h @@ -0,0 +1,26 @@ +/* Reading Java .properties files. + Copyright (C) 2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_PROPERTIES_H +#define _READ_PROPERTIES_H + +#include "read-catalog-abstract.h" + +/* Describes a .properties file parser. */ +extern DLL_VARIABLE const struct catalog_input_format input_format_properties; + +#endif /* _READ_PROPERTIES_H */ diff --git a/gettext-tools/src/read-resources.c b/gettext-tools/src/read-resources.c new file mode 100644 index 0000000..a5eb8eb --- /dev/null +++ b/gettext-tools/src/read-resources.c @@ -0,0 +1,138 @@ +/* Reading C# .resources files. + Copyright (C) 2003, 2006-2008, 2011 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-resources.h" + +#include +#include +#include +#include + +#include "msgunfmt.h" +#include "relocatable.h" +#include "csharpexec.h" +#include "spawn-pipe.h" +#include "wait-process.h" +#include "read-catalog.h" +#include "read-po.h" +#include "message.h" +#include "concat-filename.h" +#include "error.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* A .resources file has such a complex format that it's most easily read + through the C# class ResourceReader. So we start a C# process to execute + the DumpResource program, and read its output, which is .po format without + comments. */ + +struct locals +{ + /* OUT */ + msgdomain_list_ty *mdlp; +}; + +static bool +execute_and_read_po_output (const char *progname, + const char *prog_path, char **prog_argv, + void *private_data) +{ + struct locals *l = (struct locals *) private_data; + pid_t child; + int fd[1]; + FILE *fp; + int exitstatus; + + /* Open a pipe to the C# execution engine. */ + child = create_pipe_in (progname, prog_path, prog_argv, NULL, false, + true, true, fd); + + fp = fdopen (fd[0], "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + /* Read the message list. */ + l->mdlp = read_catalog_stream (fp, "(pipe)", "(pipe)", &input_format_po); + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = + wait_subprocess (child, progname, false, false, true, true, NULL); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + progname, exitstatus); + + return false; +} + + +void +read_resources_file (message_list_ty *mlp, const char *filename) +{ + const char *args[2]; + const char *gettextexedir; + const char *gettextlibdir; + char *assembly_path; + const char *libdirs[1]; + struct locals locals; + + /* Prepare arguments. */ + args[0] = filename; + args[1] = NULL; + + /* Make it possible to override the .exe location. This is + necessary for running the testsuite before "make install". */ + gettextexedir = getenv ("GETTEXTCSHARPEXEDIR"); + if (gettextexedir == NULL || gettextexedir[0] == '\0') + gettextexedir = relocate (LIBDIR "/gettext"); + + /* Make it possible to override the .dll location. This is + necessary for running the testsuite before "make install". */ + gettextlibdir = getenv ("GETTEXTCSHARPLIBDIR"); + if (gettextlibdir == NULL || gettextlibdir[0] == '\0') + gettextlibdir = relocate (LIBDIR); + + /* Dump the resource and retrieve the resulting output. */ + assembly_path = + xconcatenated_filename (gettextexedir, "msgunfmt.net", ".exe"); + libdirs[0] = gettextlibdir; + if (execute_csharp_program (assembly_path, libdirs, 1, + args, + verbose, false, + execute_and_read_po_output, &locals)) + /* An error message should already have been provided. */ + exit (EXIT_FAILURE); + + /* Add the output to mlp. */ + { + message_list_ty *read_mlp = locals.mdlp->item[0]->messages; + size_t j; + + for (j = 0; j < read_mlp->nitems; j++) + message_list_append (mlp, read_mlp->item[j]); + } + + free (assembly_path); +} diff --git a/gettext-tools/src/read-resources.h b/gettext-tools/src/read-resources.h new file mode 100644 index 0000000..b3c6076 --- /dev/null +++ b/gettext-tools/src/read-resources.h @@ -0,0 +1,27 @@ +/* Reading C# .resources files. + Copyright (C) 2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_RESOURCES_H +#define _READ_RESOURCES_H + +#include "message.h" + +/* Reads an existing binary C# .resources file and adds the messages + to mlp. */ +extern void read_resources_file (message_list_ty *mlp, const char *filename); + +#endif /* _READ_RESOURCES_H */ diff --git a/gettext-tools/src/read-stringtable.c b/gettext-tools/src/read-stringtable.c new file mode 100644 index 0000000..27b8860 --- /dev/null +++ b/gettext-tools/src/read-stringtable.c @@ -0,0 +1,963 @@ +/* Reading NeXTstep/GNUstep .strings files. + Copyright (C) 2003, 2005-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "read-stringtable.h" + +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "error-progname.h" +#include "read-catalog-abstract.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "po-xerror.h" +#include "unistr.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* The format of NeXTstep/GNUstep .strings files is documented in + gnustep-base-1.8.0/Tools/make_strings/Using.txt + and in the comments of method propertyListFromStringsFileFormat in + gnustep-base-1.8.0/Source/NSString.m + In summary, it's a Objective-C like file with pseudo-assignments of the form + "key" = "value"; + where the key is the msgid and the value is the msgstr. + + The implementation of the parser of .strings files is in + gnustep-base-1.8.0/Source/NSString.m + function GSPropertyListFromStringsFormat + (indirectly called from NSBundle's method localizedStringForKey). + + A test case is in + gnustep-base-1.8.0/Testing/English.lproj/NXStringTable.example + */ + +/* Handling of comments: We copy all comments from the .strings file to + the PO file. This is not really needed; it's a service for translators + who don't like PO files and prefer to maintain the .strings file. */ + + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* File name and line number. */ +extern lex_pos_ty gram_pos; + +/* The input file stream. */ +static FILE *fp; + + +/* Phase 1: Read a byte. + Max. 4 pushback characters. */ + +static unsigned char phase1_pushback[4]; +static int phase1_pushback_length; + +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + return phase1_pushback[--phase1_pushback_length]; + + c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("error while reading \"%s\""), + real_file_name), + errno_description)); + } + return EOF; + } + + return c; +} + +static void +phase1_ungetc (int c) +{ + if (c != EOF) + phase1_pushback[phase1_pushback_length++] = c; +} + + +/* Phase 2: Read an UCS-4 character. + Max. 2 pushback characters. */ + +/* End-of-file indicator for functions returning an UCS-4 character. */ +#define UEOF -1 + +static int phase2_pushback[4]; +static int phase2_pushback_length; + +/* The input file can be in Unicode encoding (UCS-2BE, UCS-2LE, UTF-8, each + with a BOM!), or otherwise the locale-dependent default encoding is used. + Since we don't want to depend on the locale here, we use ISO-8859-1 + instead. */ +enum enc +{ + enc_undetermined, + enc_ucs2be, + enc_ucs2le, + enc_utf8, + enc_iso8859_1 +}; +static enum enc encoding; + +static int +phase2_getc () +{ + if (phase2_pushback_length) + return phase2_pushback[--phase2_pushback_length]; + + if (encoding == enc_undetermined) + { + /* Determine the input file's encoding. */ + int c0, c1; + + c0 = phase1_getc (); + if (c0 == EOF) + return UEOF; + c1 = phase1_getc (); + if (c1 == EOF) + { + phase1_ungetc (c0); + encoding = enc_iso8859_1; + } + else if (c0 == 0xfe && c1 == 0xff) + encoding = enc_ucs2be; + else if (c0 == 0xff && c1 == 0xfe) + encoding = enc_ucs2le; + else + { + int c2; + + c2 = phase1_getc (); + if (c2 == EOF) + { + phase1_ungetc (c1); + phase1_ungetc (c0); + encoding = enc_iso8859_1; + } + else if (c0 == 0xef && c1 == 0xbb && c2 == 0xbf) + encoding = enc_utf8; + else + { + phase1_ungetc (c2); + phase1_ungetc (c1); + phase1_ungetc (c0); + encoding = enc_iso8859_1; + } + } + } + + switch (encoding) + { + case enc_ucs2be: + /* Read an UCS-2BE encoded character. */ + { + int c0, c1; + + c0 = phase1_getc (); + if (c0 == EOF) + return UEOF; + c1 = phase1_getc (); + if (c1 == EOF) + return UEOF; + return (c0 << 8) + c1; + } + + case enc_ucs2le: + /* Read an UCS-2LE encoded character. */ + { + int c0, c1; + + c0 = phase1_getc (); + if (c0 == EOF) + return UEOF; + c1 = phase1_getc (); + if (c1 == EOF) + return UEOF; + return c0 + (c1 << 8); + } + + case enc_utf8: + /* Read an UTF-8 encoded character. */ + { + unsigned char buf[6]; + unsigned int count; + int c; + ucs4_t uc; + + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[0] = c; + count = 1; + + if (buf[0] >= 0xc0) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[1] = c; + count = 2; + + if (buf[0] >= 0xe0 + && ((buf[1] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[2] = c; + count = 3; + + if (buf[0] >= 0xf0 + && ((buf[2] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[3] = c; + count = 4; + + if (buf[0] >= 0xf8 + && ((buf[3] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[4] = c; + count = 5; + + if (buf[0] >= 0xfc + && ((buf[4] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[5] = c; + count = 6; + } + } + } + } + } + + u8_mbtouc (&uc, buf, count); + return uc; + } + + case enc_iso8859_1: + /* Read an ISO-8859-1 encoded character. */ + { + int c = phase1_getc (); + + if (c == EOF) + return UEOF; + return c; + } + + default: + abort (); + } +} + +static void +phase2_ungetc (int c) +{ + if (c != UEOF) + phase2_pushback[phase2_pushback_length++] = c; +} + + +/* Phase 3: Read an UCS-4 character, with line number handling. */ + +static int +phase3_getc () +{ + int c = phase2_getc (); + + if (c == '\n') + gram_pos.line_number++; + + return c; +} + +static void +phase3_ungetc (int c) +{ + if (c == '\n') + --gram_pos.line_number; + phase2_ungetc (c); +} + + +/* Convert from UCS-4 to UTF-8. */ +static char * +conv_from_ucs4 (const int *buffer, size_t buflen) +{ + unsigned char *utf8_string; + size_t pos; + unsigned char *q; + + /* Each UCS-4 word needs 6 bytes at worst. */ + utf8_string = XNMALLOC (6 * buflen + 1, unsigned char); + + for (pos = 0, q = utf8_string; pos < buflen; ) + { + unsigned int uc; + int n; + + uc = buffer[pos++]; + n = u8_uctomb (q, uc, 6); + assert (n > 0); + q += n; + } + *q = '\0'; + assert (q - utf8_string <= 6 * buflen); + + return (char *) utf8_string; +} + + +/* Parse a string enclosed in double-quotes. Input is UCS-4 encoded. + Return the string in UTF-8 encoding, or NULL if the input doesn't represent + a valid string enclosed in double-quotes. */ +static char * +parse_escaped_string (const int *string, size_t length) +{ + static int *buffer; + static size_t bufmax; + static size_t buflen; + const int *string_limit = string + length; + int c; + + if (string == string_limit) + return NULL; + c = *string++; + if (c != '"') + return NULL; + buflen = 0; + for (;;) + { + if (string == string_limit) + return NULL; + c = *string++; + if (c == '"') + break; + if (c == '\\') + { + if (string == string_limit) + return NULL; + c = *string++; + if (c >= '0' && c <= '7') + { + unsigned int n = 0; + int j = 0; + for (;;) + { + n = n * 8 + (c - '0'); + if (++j == 3) + break; + if (string == string_limit) + break; + c = *string; + if (!(c >= '0' && c <= '7')) + break; + string++; + } + c = n; + } + else if (c == 'u' || c == 'U') + { + unsigned int n = 0; + int j; + for (j = 0; j < 4; j++) + { + if (string == string_limit) + break; + c = *string; + if (c >= '0' && c <= '9') + n = n * 16 + (c - '0'); + else if (c >= 'A' && c <= 'F') + n = n * 16 + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + n = n * 16 + (c - 'a' + 10); + else + break; + string++; + } + c = n; + } + else + switch (c) + { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 't': c = '\t'; break; + case 'r': c = '\r'; break; + case 'n': c = '\n'; break; + case 'v': c = '\v'; break; + case 'f': c = '\f'; break; + } + } + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax * sizeof (int)); + } + buffer[buflen++] = c; + } + + return conv_from_ucs4 (buffer, buflen); +} + + +/* Accumulating flag comments. */ + +static char *special_comment; + +static inline void +special_comment_reset () +{ + if (special_comment != NULL) + free (special_comment); + special_comment = NULL; +} + +static void +special_comment_add (const char *flag) +{ + if (special_comment == NULL) + special_comment = xstrdup (flag); + else + { + size_t total_len = strlen (special_comment) + 2 + strlen (flag) + 1; + special_comment = xrealloc (special_comment, total_len); + strcat (special_comment, ", "); + strcat (special_comment, flag); + } +} + +static inline void +special_comment_finish () +{ + if (special_comment != NULL) + { + po_callback_comment_special (special_comment); + free (special_comment); + special_comment = NULL; + } +} + + +/* Accumulating comments. */ + +static int *buffer; +static size_t bufmax; +static size_t buflen; +static bool next_is_obsolete; +static bool next_is_fuzzy; +static char *fuzzy_msgstr; +static bool expect_fuzzy_msgstr_as_c_comment; +static bool expect_fuzzy_msgstr_as_cxx_comment; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax * sizeof (int)); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove, bool test_for_fuzzy_msgstr) +{ + char *line; + + buflen -= chars_to_remove; + /* Drop trailing white space, but not EOLs. */ + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + + /* At special positions we interpret a comment of the form + = "escaped string" + with an optional trailing semicolon as being the fuzzy msgstr, not a + regular comment. */ + if (test_for_fuzzy_msgstr + && buflen > 2 && buffer[0] == '=' && buffer[1] == ' ' + && (fuzzy_msgstr = + parse_escaped_string (buffer + 2, + buflen - (buffer[buflen - 1] == ';') - 2))) + return; + + line = conv_from_ucs4 (buffer, buflen); + + if (strcmp (line, "Flag: untranslated") == 0) + { + special_comment_add ("fuzzy"); + next_is_fuzzy = true; + } + else if (strcmp (line, "Flag: unmatched") == 0) + next_is_obsolete = true; + else if (strlen (line) >= 6 && memcmp (line, "Flag: ", 6) == 0) + special_comment_add (line + 6); + else if (strlen (line) >= 9 && memcmp (line, "Comment: ", 9) == 0) + /* A comment extracted from the source. */ + po_callback_comment_dot (line + 9); + else + { + char *last_colon; + unsigned long number; + char *endp; + + if (strlen (line) >= 6 && memcmp (line, "File: ", 6) == 0 + && (last_colon = strrchr (line + 6, ':')) != NULL + && *(last_colon + 1) != '\0' + && (number = strtoul (last_colon + 1, &endp, 10), *endp == '\0')) + { + /* A "File: :" type comment. */ + *last_colon = '\0'; + po_callback_comment_filepos (line + 6, number); + } + else + po_callback_comment (line); + } +} + + +/* Phase 4: Replace each comment that is not inside a string with a space + character. */ + +static int +phase4_getc () +{ + int c; + + c = phase3_getc (); + if (c != '/') + return c; + c = phase3_getc (); + switch (c) + { + default: + phase3_ungetc (c); + return '/'; + + case '*': + /* C style comment. */ + { + bool last_was_star; + size_t trailing_stars; + bool seen_newline; + + comment_start (); + last_was_star = false; + trailing_stars = 0; + seen_newline = false; + /* Drop additional stars at the beginning of the comment. */ + for (;;) + { + c = phase3_getc (); + if (c != '*') + break; + last_was_star = true; + } + phase3_ungetc (c); + for (;;) + { + c = phase3_getc (); + if (c == UEOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + switch (c) + { + case '\n': + seen_newline = true; + comment_line_end (1, false); + comment_start (); + last_was_star = false; + trailing_stars = 0; + continue; + + case '*': + last_was_star = true; + trailing_stars++; + continue; + + case '/': + if (last_was_star) + { + /* Drop additional stars at the end of the comment. */ + comment_line_end (trailing_stars + 1, + expect_fuzzy_msgstr_as_c_comment + && !seen_newline); + break; + } + /* FALLTHROUGH */ + + default: + last_was_star = false; + trailing_stars = 0; + continue; + } + break; + } + return ' '; + } + + case '/': + /* C++ style comment. */ + comment_start (); + for (;;) + { + c = phase3_getc (); + if (c == '\n' || c == UEOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + comment_line_end (0, expect_fuzzy_msgstr_as_cxx_comment); + return '\n'; + } +} + +static inline void +phase4_ungetc (int c) +{ + phase3_ungetc (c); +} + + +/* Return true if a character is considered as whitespace. */ +static bool +is_whitespace (int c) +{ + return (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f' + || c == '\b'); +} + +/* Return true if a character needs quoting, i.e. cannot be used in unquoted + tokens. */ +static bool +is_quotable (int c) +{ + if ((c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + return false; + switch (c) + { + case '!': case '#': case '$': case '%': case '&': case '*': + case '+': case '-': case '.': case '/': case ':': case '?': + case '@': case '|': case '~': case '_': case '^': + return false; + default: + return true; + } +} + + +/* Read a key or value string. + Return the string in UTF-8 encoding, or NULL if no string is seen. + Return the start position of the string in *pos. */ +static char * +read_string (lex_pos_ty *pos) +{ + static int *buffer; + static size_t bufmax; + static size_t buflen; + int c; + + /* Skip whitespace before the string. */ + do + c = phase4_getc (); + while (is_whitespace (c)); + + if (c == UEOF) + /* No more string. */ + return NULL; + + *pos = gram_pos; + buflen = 0; + if (c == '"') + { + /* Read a string enclosed in double-quotes. */ + for (;;) + { + c = phase3_getc (); + if (c == UEOF || c == '"') + break; + if (c == '\\') + { + c = phase3_getc (); + if (c == UEOF) + break; + if (c >= '0' && c <= '7') + { + unsigned int n = 0; + int j = 0; + for (;;) + { + n = n * 8 + (c - '0'); + if (++j == 3) + break; + c = phase3_getc (); + if (!(c >= '0' && c <= '7')) + { + phase3_ungetc (c); + break; + } + } + c = n; + } + else if (c == 'u' || c == 'U') + { + unsigned int n = 0; + int j; + for (j = 0; j < 4; j++) + { + c = phase3_getc (); + if (c >= '0' && c <= '9') + n = n * 16 + (c - '0'); + else if (c >= 'A' && c <= 'F') + n = n * 16 + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + n = n * 16 + (c - 'a' + 10); + else + { + phase3_ungetc (c); + break; + } + } + c = n; + } + else + switch (c) + { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 't': c = '\t'; break; + case 'r': c = '\r'; break; + case 'n': c = '\n'; break; + case 'v': c = '\v'; break; + case 'f': c = '\f'; break; + } + } + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax * sizeof (int)); + } + buffer[buflen++] = c; + } + if (c == UEOF) + po_xerror (PO_SEVERITY_ERROR, NULL, + real_file_name, gram_pos.line_number, (size_t)(-1), false, + _("warning: unterminated string")); + } + else + { + /* Read a token outside quotes. */ + if (is_quotable (c)) + po_xerror (PO_SEVERITY_ERROR, NULL, + real_file_name, gram_pos.line_number, (size_t)(-1), false, + _("warning: syntax error")); + for (; c != UEOF && !is_quotable (c); c = phase4_getc ()) + { + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax * sizeof (int)); + } + buffer[buflen++] = c; + } + } + + return conv_from_ucs4 (buffer, buflen); +} + + +/* Read a .strings file from a stream, and dispatch to the various + abstract_catalog_reader_class_ty methods. */ +static void +stringtable_parse (abstract_catalog_reader_ty *pop, FILE *file, + const char *real_filename, const char *logical_filename) +{ + fp = file; + real_file_name = real_filename; + gram_pos.file_name = xstrdup (real_file_name); + gram_pos.line_number = 1; + encoding = enc_undetermined; + expect_fuzzy_msgstr_as_c_comment = false; + expect_fuzzy_msgstr_as_cxx_comment = false; + + for (;;) + { + char *msgid; + lex_pos_ty msgid_pos; + char *msgstr; + lex_pos_ty msgstr_pos; + int c; + + /* Prepare for next msgid/msgstr pair. */ + special_comment_reset (); + next_is_obsolete = false; + next_is_fuzzy = false; + fuzzy_msgstr = NULL; + + /* Read the key and all the comments preceding it. */ + msgid = read_string (&msgid_pos); + if (msgid == NULL) + break; + + special_comment_finish (); + + /* Skip whitespace. */ + do + c = phase4_getc (); + while (is_whitespace (c)); + + /* Expect a '=' or ';'. */ + if (c == UEOF) + { + po_xerror (PO_SEVERITY_ERROR, NULL, + real_file_name, gram_pos.line_number, (size_t)(-1), false, + _("warning: unterminated key/value pair")); + break; + } + if (c == ';') + { + /* "key"; is an abbreviation for "key"=""; and does not + necessarily designate an untranslated entry. */ + msgstr = xstrdup (""); + msgstr_pos = msgid_pos; + po_callback_message (NULL, msgid, &msgid_pos, NULL, + msgstr, strlen (msgstr) + 1, &msgstr_pos, + NULL, NULL, NULL, + false, next_is_obsolete); + } + else if (c == '=') + { + /* Read the value. */ + msgstr = read_string (&msgstr_pos); + if (msgstr == NULL) + { + po_xerror (PO_SEVERITY_ERROR, NULL, + real_file_name, gram_pos.line_number, (size_t)(-1), + false, _("warning: unterminated key/value pair")); + break; + } + + /* Skip whitespace. But for fuzzy key/value pairs, look for the + tentative msgstr in the form of a C style comment. */ + expect_fuzzy_msgstr_as_c_comment = next_is_fuzzy; + do + { + c = phase4_getc (); + if (fuzzy_msgstr != NULL) + expect_fuzzy_msgstr_as_c_comment = false; + } + while (is_whitespace (c)); + expect_fuzzy_msgstr_as_c_comment = false; + + /* Expect a ';'. */ + if (c == ';') + { + /* But for fuzzy key/value pairs, look for the tentative msgstr + in the form of a C++ style comment. */ + if (fuzzy_msgstr == NULL && next_is_fuzzy) + { + do + c = phase3_getc (); + while (c == ' '); + phase3_ungetc (c); + + expect_fuzzy_msgstr_as_cxx_comment = true; + c = phase4_getc (); + phase4_ungetc (c); + expect_fuzzy_msgstr_as_cxx_comment = false; + } + if (fuzzy_msgstr != NULL && strcmp (msgstr, msgid) == 0) + msgstr = fuzzy_msgstr; + + /* A key/value pair. */ + po_callback_message (NULL, msgid, &msgid_pos, NULL, + msgstr, strlen (msgstr) + 1, &msgstr_pos, + NULL, NULL, NULL, + false, next_is_obsolete); + } + else + { + po_xerror (PO_SEVERITY_ERROR, NULL, + real_file_name, gram_pos.line_number, (size_t)(-1), + false, _("\ +warning: syntax error, expected ';' after string")); + break; + } + } + else + { + po_xerror (PO_SEVERITY_ERROR, NULL, + real_file_name, gram_pos.line_number, (size_t)(-1), false, + _("\ +warning: syntax error, expected '=' or ';' after string")); + break; + } + } + + fp = NULL; + real_file_name = NULL; + gram_pos.line_number = 0; +} + +const struct catalog_input_format input_format_stringtable = +{ + stringtable_parse, /* parse */ + true /* produces_utf8 */ +}; diff --git a/gettext-tools/src/read-stringtable.h b/gettext-tools/src/read-stringtable.h new file mode 100644 index 0000000..d254434 --- /dev/null +++ b/gettext-tools/src/read-stringtable.h @@ -0,0 +1,26 @@ +/* Reading NeXTstep/GNUstep .strings files. + Copyright (C) 2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_STRINGTABLE_H +#define _READ_STRINGTABLE_H + +#include "read-catalog-abstract.h" + +/* Describes a .strings file parser. */ +extern DLL_VARIABLE const struct catalog_input_format input_format_stringtable; + +#endif /* _READ_STRINGTABLE_H */ diff --git a/gettext-tools/src/read-tcl.c b/gettext-tools/src/read-tcl.c new file mode 100644 index 0000000..039dcfb --- /dev/null +++ b/gettext-tools/src/read-tcl.c @@ -0,0 +1,157 @@ +/* Reading tcl/msgcat .msg files. + Copyright (C) 2002-2003, 2005-2008, 2011 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +/* Specification. */ +#include "read-tcl.h" + +#include +#include +#include + +#include "msgunfmt.h" +#include "relocatable.h" +#include "concat-filename.h" +#include "sh-quote.h" +#include "spawn-pipe.h" +#include "wait-process.h" +#include "read-catalog.h" +#include "read-po.h" +#include "xmalloca.h" +#include "error.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* A Tcl .msg file contains Tcl commands. It is best interpreted by Tcl + itself. But we redirect the msgcat::mcset function so that it passes + the msgid/msgstr pair to us, instead of storing it in the hash table. */ + +msgdomain_list_ty * +msgdomain_read_tcl (const char *locale_name, const char *directory) +{ + const char *gettextdatadir; + char *tclscript; + size_t len; + char *frobbed_locale_name; + char *p; + char *file_name; + char *argv[4]; + pid_t child; + int fd[1]; + FILE *fp; + msgdomain_list_ty *mdlp; + int exitstatus; + size_t k; + + /* Make it possible to override the msgunfmt.tcl location. This is + necessary for running the testsuite before "make install". */ + gettextdatadir = getenv ("GETTEXTDATADIR"); + if (gettextdatadir == NULL || gettextdatadir[0] == '\0') + gettextdatadir = relocate (GETTEXTDATADIR); + + tclscript = xconcatenated_filename (gettextdatadir, "msgunfmt.tcl", NULL); + + /* Convert the locale name to lowercase and remove any encoding. */ + len = strlen (locale_name); + frobbed_locale_name = (char *) xmalloca (len + 1); + memcpy (frobbed_locale_name, locale_name, len + 1); + for (p = frobbed_locale_name; *p != '\0'; p++) + if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + else if (*p == '.') + { + *p = '\0'; + break; + } + + file_name = xconcatenated_filename (directory, frobbed_locale_name, ".msg"); + + freea (frobbed_locale_name); + + /* Prepare arguments. */ + argv[0] = "tclsh"; + argv[1] = tclscript; + argv[2] = file_name; + argv[3] = NULL; + + if (verbose) + { + char *command = shell_quote_argv (argv); + printf ("%s\n", command); + free (command); + } + + /* Open a pipe to the Tcl interpreter. */ + child = create_pipe_in ("tclsh", "tclsh", argv, DEV_NULL, false, true, true, + fd); + + fp = fdopen (fd[0], "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + /* Read the message list. */ + mdlp = read_catalog_stream (fp, "(pipe)", "(pipe)", &input_format_po); + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = + wait_subprocess (child, "tclsh", false, false, true, true, NULL); + if (exitstatus != 0) + { + if (exitstatus == 2) + /* Special exitcode provided by msgunfmt.tcl. */ + error (EXIT_FAILURE, ENOENT, + _("error while opening \"%s\" for reading"), file_name); + else + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + "tclsh", exitstatus); + } + + free (tclscript); + + /* Move the header entry to the beginning. */ + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + size_t j; + + for (j = 0; j < mlp->nitems; j++) + if (is_header (mlp->item[j])) + { + /* Found the header entry. */ + if (j > 0) + { + message_ty *header = mlp->item[j]; + size_t i; + + for (i = j; i > 0; i--) + mlp->item[i] = mlp->item[i - 1]; + mlp->item[0] = header; + } + break; + } + } + + return mdlp; +} diff --git a/gettext-tools/src/read-tcl.h b/gettext-tools/src/read-tcl.h new file mode 100644 index 0000000..a2c2d9e --- /dev/null +++ b/gettext-tools/src/read-tcl.h @@ -0,0 +1,28 @@ +/* Reading tcl/msgcat .msg files. + Copyright (C) 2002 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _READ_TCL_H +#define _READ_TCL_H + +#include "message.h" + +/* Read the Tcl msg file given by locale_name and directory. + Returns a list of messages. */ +extern msgdomain_list_ty * + msgdomain_read_tcl (const char *locale_name, const char *directory); + +#endif /* _READ_TCL_H */ diff --git a/gettext-tools/src/recode-sr-latin.c b/gettext-tools/src/recode-sr-latin.c new file mode 100644 index 0000000..25b88f6 --- /dev/null +++ b/gettext-tools/src/recode-sr-latin.c @@ -0,0 +1,395 @@ +/* Recode Serbian text from Cyrillic to Latin script. + Copyright (C) 2006-2007, 2010, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2006. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#if HAVE_ICONV +#include +#endif + +#include "closeout.h" +#include "error.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "xalloc.h" +#include "localcharset.h" +#include "c-strcase.h" +#include "xstriconv.h" +#include "filters.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Long options. */ +static const struct option long_options[] = +{ + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void process (FILE *stream); + +int +main (int argc, char *argv[]) +{ + /* Default values for command line options. */ + bool do_help = false; + bool do_version = false; + + int opt; + + /* Set program name for message texts. */ + set_program_name (argv[0]); + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Parse command line options. */ + while ((opt = getopt_long (argc, argv, "hV", long_options, NULL)) != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + case 'h': + do_help = true; + break; + case 'V': + do_version = true; + break; + default: + usage (EXIT_FAILURE); + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2006-2007"); + printf (_("Written by %s and %s.\n"), + /* TRANSLATORS: This is a proper name. The last name is + (with Unicode escapes) "\u0160egan" or (with HTML entities) + "Šegan". */ + proper_name_utf8 ("Danilo Segan", "Danilo \305\240egan"), + proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + if (argc - optind > 0) + error (EXIT_FAILURE, 0, _("too many arguments")); + + process (stdin); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + /* xgettext: no-wrap */ + printf (_("\ +Usage: %s [OPTION]\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Recode Serbian text from Cyrillic to Latin script.\n")); + /* xgettext: no-wrap */ + printf (_("\ +The input text is read from standard input. The converted text is output to\n\ +standard output.\n")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Informative output:\n")); + /* xgettext: no-wrap */ + printf (_("\ + -h, --help display this help and exit\n")); + /* xgettext: no-wrap */ + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), stdout); + } + + exit (status); +} + + +/* Routines for reading a line. + Don't use routines that drop NUL bytes. Don't use getline(), because it + doesn't provide a good error message in case of memory allocation failure. + The gnulib module 'linebuffer' is nearly the right thing, except that we + don't want an extra newline at the end of file. */ + +/* A 'struct linebuffer' holds a line of text. */ + +struct linebuffer +{ + size_t size; /* Allocated. */ + size_t length; /* Used. */ + char *buffer; +}; + +/* Initialize linebuffer LINEBUFFER for use. */ +static inline void +init_linebuffer (struct linebuffer *lb) +{ + lb->size = 0; + lb->length = 0; + lb->buffer = NULL; +} + +/* Read an arbitrarily long line of text from STREAM into linebuffer LB. + Keep the newline. Do not NUL terminate. + Return LINEBUFFER, except at end of file return NULL. */ +static struct linebuffer * +read_linebuffer (struct linebuffer *lb, FILE *stream) +{ + if (feof (stream)) + return NULL; + else + { + char *p = lb->buffer; + char *end = lb->buffer + lb->size; + + for (;;) + { + int c = getc (stream); + if (c == EOF) + { + if (p == lb->buffer || ferror (stream)) + return NULL; + break; + } + if (p == end) + { + size_t oldsize = lb->size; /* = p - lb->buffer */ + size_t newsize = 2 * oldsize + 40; + lb->buffer = (char *) xrealloc (lb->buffer, newsize); + lb->size = newsize; + p = lb->buffer + oldsize; + end = lb->buffer + newsize; + } + *p++ = c; + if (c == '\n') + break; + } + + lb->length = p - lb->buffer; + return lb; + } +} + +/* Free linebuffer LB and its data, all allocated with malloc. */ +static inline void +destroy_linebuffer (struct linebuffer *lb) +{ + if (lb->buffer != NULL) + free (lb->buffer); +} + + +/* Process the input and produce the output. */ +static void +process (FILE *stream) +{ + struct linebuffer lb; + const char *locale_code = locale_charset (); + bool need_code_conversion = (c_strcasecmp (locale_code, "UTF-8") != 0); +#if HAVE_ICONV + iconv_t conv_to_utf8 = (iconv_t)(-1); + iconv_t conv_from_utf8 = (iconv_t)(-1); + char *last_utf8_line; + size_t last_utf8_line_len; + char *last_backconv_line; + size_t last_backconv_line_len; +#endif + + init_linebuffer (&lb); + + /* Initialize the conversion descriptors. */ + if (need_code_conversion) + { +#if HAVE_ICONV + /* Avoid glibc-2.1 bug with EUC-KR. */ +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \ + && !defined _LIBICONV_VERSION + if (strcmp (locale_code, "EUC-KR") != 0) +# endif + { + conv_to_utf8 = iconv_open ("UTF-8", locale_code); + /* TODO: Maybe append //TRANSLIT here? */ + conv_from_utf8 = iconv_open (locale_code, "UTF-8"); + } + if (conv_to_utf8 == (iconv_t)(-1)) + error (EXIT_FAILURE, 0, _("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(), \ +and iconv() does not support this conversion."), + locale_code, "UTF-8", basename (program_name)); + if (conv_from_utf8 == (iconv_t)(-1)) + error (EXIT_FAILURE, 0, _("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(), \ +and iconv() does not support this conversion."), + "UTF-8", locale_code, basename (program_name)); + last_utf8_line = NULL; + last_utf8_line_len = 0; + last_backconv_line = NULL; + last_backconv_line_len = 0; +#else + error (EXIT_FAILURE, 0, _("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(). \ +This version was built without iconv()."), + locale_code, "UTF-8", basename (program_name)); +#endif + } + + /* Read the input line by line. + Processing it character by character is not possible, because some + filters need to look at adjacent characters. Processing the entire file + in a whole chunk would take an excessive amount of memory. */ + for (;;) + { + char *line; + size_t line_len; + char *filtered_line; + size_t filtered_line_len; + + /* Read a line. */ + if (read_linebuffer (&lb, stream) == NULL) + break; + line = lb.buffer; + line_len = lb.length; + /* read_linebuffer always returns a non-void result. */ + if (line_len == 0) + abort (); + +#if HAVE_ICONV + /* Convert it to UTF-8. */ + if (need_code_conversion) + { + char *utf8_line = last_utf8_line; + size_t utf8_line_len = last_utf8_line_len; + + if (xmem_cd_iconv (line, line_len, conv_to_utf8, + &utf8_line, &utf8_line_len) != 0) + error (EXIT_FAILURE, errno, + _("input is not valid in \"%s\" encoding"), + locale_code); + if (utf8_line != last_utf8_line) + { + if (last_utf8_line != NULL) + free (last_utf8_line); + last_utf8_line = utf8_line; + last_utf8_line_len = utf8_line_len; + } + + line = utf8_line; + line_len = utf8_line_len; + } +#endif + + /* Apply the filter. */ + serbian_to_latin (line, line_len, &filtered_line, &filtered_line_len); + +#if HAVE_ICONV + /* Convert it back to the original encoding. */ + if (need_code_conversion) + { + char *backconv_line = last_backconv_line; + size_t backconv_line_len = last_backconv_line_len; + + if (xmem_cd_iconv (filtered_line, filtered_line_len, conv_from_utf8, + &backconv_line, &backconv_line_len) != 0) + error (EXIT_FAILURE, errno, + _("error while converting from \"%s\" encoding to \"%s\" encoding"), + "UTF-8", locale_code); + if (backconv_line != last_backconv_line) + { + if (last_backconv_line != NULL) + free (last_backconv_line); + last_backconv_line = backconv_line; + last_backconv_line_len = backconv_line_len; + } + + fwrite (backconv_line, 1, backconv_line_len, stdout); + } + else +#endif + fwrite (filtered_line, 1, filtered_line_len, stdout); + + free (filtered_line); + } + +#if HAVE_ICONV + if (need_code_conversion) + { + iconv_close (conv_from_utf8); + iconv_close (conv_to_utf8); + } +#endif + + destroy_linebuffer (&lb); +} diff --git a/gettext-tools/src/str-list.c b/gettext-tools/src/str-list.c new file mode 100644 index 0000000..3f929c2 --- /dev/null +++ b/gettext-tools/src/str-list.c @@ -0,0 +1,236 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995, 1998, 2000-2004, 2006, 2009 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "str-list.h" + +#include +#include +#include + +#include "xalloc.h" + + +/* Initialize an empty list of strings. */ +void +string_list_init (string_list_ty *slp) +{ + slp->item = NULL; + slp->nitems = 0; + slp->nitems_max = 0; +} + + +/* Return a fresh, empty list of strings. */ +string_list_ty * +string_list_alloc () +{ + string_list_ty *slp; + + slp = XMALLOC (string_list_ty); + slp->item = NULL; + slp->nitems = 0; + slp->nitems_max = 0; + + return slp; +} + + +/* Append a single string to the end of a list of strings. */ +void +string_list_append (string_list_ty *slp, const char *s) +{ + /* Grow the list. */ + if (slp->nitems >= slp->nitems_max) + { + size_t nbytes; + + slp->nitems_max = slp->nitems_max * 2 + 4; + nbytes = slp->nitems_max * sizeof (slp->item[0]); + slp->item = (const char **) xrealloc (slp->item, nbytes); + } + + /* Add a copy of the string to the end of the list. */ + slp->item[slp->nitems++] = xstrdup (s); +} + + +/* Append a single string to the end of a list of strings, unless it is + already contained in the list. */ +void +string_list_append_unique (string_list_ty *slp, const char *s) +{ + size_t j; + + /* Do not if the string is already in the list. */ + for (j = 0; j < slp->nitems; ++j) + if (strcmp (slp->item[j], s) == 0) + return; + + /* Grow the list. */ + if (slp->nitems >= slp->nitems_max) + { + slp->nitems_max = slp->nitems_max * 2 + 4; + slp->item = (const char **) xrealloc (slp->item, + slp->nitems_max + * sizeof (slp->item[0])); + } + + /* Add a copy of the string to the end of the list. */ + slp->item[slp->nitems++] = xstrdup (s); +} + + +/* Destroy a list of strings. */ +void +string_list_destroy (string_list_ty *slp) +{ + size_t j; + + for (j = 0; j < slp->nitems; ++j) + free ((char *) slp->item[j]); + if (slp->item != NULL) + free (slp->item); +} + + +/* Free a list of strings. */ +void +string_list_free (string_list_ty *slp) +{ + size_t j; + + for (j = 0; j < slp->nitems; ++j) + free ((char *) slp->item[j]); + if (slp->item != NULL) + free (slp->item); + free (slp); +} + + +/* Return a freshly allocated string obtained by concatenating all the + strings in the list. */ +char * +string_list_concat (const string_list_ty *slp) +{ + size_t len; + size_t j; + char *result; + size_t pos; + + len = 1; + for (j = 0; j < slp->nitems; ++j) + len += strlen (slp->item[j]); + result = XNMALLOC (len, char); + pos = 0; + for (j = 0; j < slp->nitems; ++j) + { + len = strlen (slp->item[j]); + memcpy (result + pos, slp->item[j], len); + pos += len; + } + result[pos] = '\0'; + return result; +} + + +/* Return a freshly allocated string obtained by concatenating all the + strings in the list, and destroy the list. */ +char * +string_list_concat_destroy (string_list_ty *slp) +{ + char *result; + + /* Optimize the most frequent case. */ + if (slp->nitems == 1) + { + result = (char *) slp->item[0]; + free (slp->item); + } + else + { + result = string_list_concat (slp); + string_list_destroy (slp); + } + return result; +} + + +/* Return a freshly allocated string obtained by concatenating all the + strings in the list, separated by the separator string, terminated + by the terminator character. The terminator character is not added if + drop_redundant_terminator is true and the last string already ends with + the terminator. */ +char * +string_list_join (const string_list_ty *slp, const char *separator, + char terminator, bool drop_redundant_terminator) +{ + size_t separator_len = strlen (separator); + size_t len; + size_t j; + char *result; + size_t pos; + + len = 1; + for (j = 0; j < slp->nitems; ++j) + { + if (j > 0) + len += separator_len; + len += strlen (slp->item[j]); + } + if (terminator) + ++len; + result = XNMALLOC (len, char); + pos = 0; + for (j = 0; j < slp->nitems; ++j) + { + if (j > 0) + { + memcpy (result + pos, separator, separator_len); + pos += separator_len; + } + len = strlen (slp->item[j]); + memcpy (result + pos, slp->item[j], len); + pos += len; + } + if (terminator + && !(drop_redundant_terminator + && slp->nitems > 0 + && (len = strlen (slp->item[slp->nitems - 1])) > 0 + && slp->item[slp->nitems - 1][len - 1] == terminator)) + result[pos++] = terminator; + result[pos] = '\0'; + return result; +} + + +/* Return 1 if s is contained in the list of strings, 0 otherwise. */ +bool +string_list_member (const string_list_ty *slp, const char *s) +{ + size_t j; + + for (j = 0; j < slp->nitems; ++j) + if (strcmp (slp->item[j], s) == 0) + return true; + return false; +} diff --git a/gettext-tools/src/str-list.h b/gettext-tools/src/str-list.h new file mode 100644 index 0000000..5b7b75d --- /dev/null +++ b/gettext-tools/src/str-list.h @@ -0,0 +1,88 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1996, 1998, 2000-2004, 2009 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _STR_LIST_H +#define _STR_LIST_H 1 + +/* Get size_t and NULL. */ +#include + +/* Get bool. */ +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Type describing list of immutable strings, + implemented using a dynamic array. */ +typedef struct string_list_ty string_list_ty; +struct string_list_ty +{ + const char **item; + size_t nitems; + size_t nitems_max; +}; + +/* Initialize an empty list of strings. */ +extern void string_list_init (string_list_ty *slp); + +/* Return a fresh, empty list of strings. */ +extern string_list_ty *string_list_alloc (void); + +/* Append a single string to the end of a list of strings. */ +extern void string_list_append (string_list_ty *slp, const char *s); + +/* Append a single string to the end of a list of strings, unless it is + already contained in the list. */ +extern void string_list_append_unique (string_list_ty *slp, const char *s); + +/* Destroy a list of strings. */ +extern void string_list_destroy (string_list_ty *slp); + +/* Free a list of strings. */ +extern void string_list_free (string_list_ty *slp); + +/* Return a freshly allocated string obtained by concatenating all the + strings in the list. */ +extern char *string_list_concat (const string_list_ty *slp); + +/* Return a freshly allocated string obtained by concatenating all the + strings in the list, and destroy the list. */ +extern char *string_list_concat_destroy (string_list_ty *slp); + +/* Return a freshly allocated string obtained by concatenating all the + strings in the list, separated by the separator string, terminated + by the terminator character. The terminator character is not added if + drop_redundant_terminator is true and the last string already ends with + the terminator. */ +extern char *string_list_join (const string_list_ty *slp, const char *separator, + char terminator, bool drop_redundant_terminator); + +/* Return 1 if s is contained in the list of strings, 0 otherwise. */ +extern bool string_list_member (const string_list_ty *slp, const char *s); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _STR_LIST_H */ diff --git a/gettext-tools/src/urlget.c b/gettext-tools/src/urlget.c new file mode 100644 index 0000000..f3243e1 --- /dev/null +++ b/gettext-tools/src/urlget.c @@ -0,0 +1,448 @@ +/* Get the contents of an URL. + Copyright (C) 2001-2003, 2005-2010, 2012 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "full-write.h" +#include "execute.h" +#include "javaexec.h" +#include "binary-io.h" +#include "propername.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#ifndef STDOUT_FILENO +# define STDOUT_FILENO 1 +#endif + + +/* Only high-level toolkits, written in languages with exception handling, + have an URL datatype and operations to fetch an URL's contents. Such + toolkits are Java (class java.net.URL), Qt (classes QUrl and QUrlOperator). + We use the Java toolkit. + Note that this program doesn't handle redirection pages; programs which + wish to process HTML redirection tags need to include a HTML parser, + and only full-fledged browsers like w3m, lynx, links have have both + an URL fetcher (which covers at least the protocols "http", "ftp", "file") + and a HTML parser. [Well, this is not true: libxml2 and Java (see + ) also contain + HTML parsers.] */ + + +/* Whether to output something on standard error. + This is true by default, because the user should know why we are trying to + establish an internet connection. Also, users get confused if a program + produces no output for more than 10 seconds for no apparent reason. */ +static bool verbose = true; + +/* Long options. */ +static const struct option long_options[] = +{ + { "help", no_argument, NULL, 'h' }, + { "quiet", no_argument, NULL, 'q' }, + { "silent", no_argument, NULL, 'q' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void fetch (const char *url, const char *file); + + +int +main (int argc, char *argv[]) +{ + int optchar; + bool do_help; + bool do_version; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + + /* Parse command line options. */ + while ((optchar = getopt_long (argc, argv, "hqV", long_options, NULL)) != EOF) + switch (optchar) + { + case '\0': /* Long option. */ + break; + case 'h': /* --help */ + do_help = true; + break; + case 'q': /* --quiet / --silent */ + verbose = false; + break; + case 'V': /* --version */ + do_version = true; + break; + default: + usage (EXIT_FAILURE); + /* NOTREACHED */ + } + + /* Version information requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "2001-2003, 2005-2009"); + printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test argument count. */ + if (optind + 2 != argc) + error (EXIT_FAILURE, 0, _("expected two arguments")); + + /* Fetch the contents. */ + fetch (argv[optind], argv[optind + 1]); + + exit (EXIT_SUCCESS); +} + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] URL FILE\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Fetches and outputs the contents of an URL. If the URL cannot be accessed,\n\ +the locally accessible FILE is used instead.\n\ +")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf (_("\ + -q, --quiet, --silent suppress progress indicators\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + +/* Copy a file's contents to stdout. */ +static void +cat_file (const char *src_filename) +{ + int src_fd; + char buf[4096]; + const int buf_size = sizeof (buf); + + src_fd = open (src_filename, O_RDONLY | O_BINARY); + if (src_fd < 0) + error (EXIT_FAILURE, errno, _("error while opening \"%s\" for reading"), + src_filename); + + for (;;) + { + ssize_t n_read = read (src_fd, buf, buf_size); + if (n_read < 0) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + error (EXIT_FAILURE, errno, _("error reading \"%s\""), src_filename); + } + if (n_read == 0) + break; + + if (full_write (STDOUT_FILENO, buf, n_read) < n_read) + error (EXIT_FAILURE, errno, _("error writing stdout")); + } + + if (close (src_fd) < 0) + error (EXIT_FAILURE, errno, _("error after reading \"%s\""), src_filename); +} + +/* Exit code of the Java program. */ +static int java_exitcode; + +static bool +execute_it (const char *progname, + const char *prog_path, char **prog_argv, + void *private_data) +{ + (void) private_data; + + java_exitcode = + execute (progname, prog_path, prog_argv, true, true, false, false, true, + false, NULL); + /* Exit code 0 means success, 2 means timed out. */ + return !(java_exitcode == 0 || java_exitcode == 2); +} + +/* Fetch the URL. Upon error, use the FILE as fallback. */ +static void +fetch (const char *url, const char *file) +{ + if (verbose) + { + fprintf (stderr, _("Retrieving %s..."), url); + fflush (stderr); + } + +#if USEJAVA + /* First try: using Java. */ + { + const char *class_name = "gnu.gettext.GetURL"; + const char *gettextjexedir; + const char *gettextjar; + const char *args[2]; + +# if USEJEXE + /* Make it possible to override the executable's location. This is + necessary for running the testsuite before "make install". */ + gettextjexedir = getenv ("GETTEXTJEXEDIR"); + if (gettextjexedir == NULL || gettextjexedir[0] == '\0') + gettextjexedir = relocate (GETTEXTJEXEDIR); +# else + gettextjexedir = NULL; +# endif + + /* Make it possible to override the gettext.jar location. This is + necessary for running the testsuite before "make install". */ + gettextjar = getenv ("GETTEXTJAR"); + if (gettextjar == NULL || gettextjar[0] == '\0') + gettextjar = relocate (GETTEXTJAR); + + /* Prepare arguments. */ + args[0] = url; + args[1] = NULL; + + /* Fetch the URL's contents. */ + java_exitcode = 127; + if (!execute_java_class (class_name, &gettextjar, 1, true, gettextjexedir, + args, + false, true, + execute_it, NULL)) + { + if (verbose) + { + if (java_exitcode == 0) + fprintf (stderr, _(" done.\n")); + else if (java_exitcode == 2) + fprintf (stderr, _(" timed out.\n")); + } + return; + } + } +#endif + + /* Second try: using "wget -q -O - -T 30 url". */ + { + static bool wget_tested; + static bool wget_present; + + if (!wget_tested) + { + /* Test for presence of wget: "wget --version > /dev/null" */ + char *argv[3]; + int exitstatus; + + argv[0] = "wget"; + argv[1] = "--version"; + argv[2] = NULL; + exitstatus = execute ("wget", "wget", argv, false, false, true, true, + true, false, NULL); + wget_present = (exitstatus == 0); + wget_tested = true; + } + + if (wget_present) + { + char *argv[8]; + int exitstatus; + + argv[0] = "wget"; + argv[1] = "-q"; + argv[2] = "-O"; argv[3] = "-"; + argv[4] = "-T"; argv[5] = "30"; + argv[6] = (char *) url; + argv[7] = NULL; + exitstatus = execute ("wget", "wget", argv, true, false, false, false, + true, false, NULL); + if (exitstatus != 127) + { + if (exitstatus != 0) + goto failed; + if (verbose) + fprintf (stderr, _(" done.\n")); + return; + } + } + } + + /* Third try: using "lynx -source url". */ + { + static bool lynx_tested; + static bool lynx_present; + + if (!lynx_tested) + { + /* Test for presence of lynx: "lynx --version > /dev/null" */ + char *argv[3]; + int exitstatus; + + argv[0] = "lynx"; + argv[1] = "--version"; + argv[2] = NULL; + exitstatus = execute ("lynx", "lynx", argv, false, false, true, true, + true, false, NULL); + lynx_present = (exitstatus == 0); + lynx_tested = true; + } + + if (lynx_present) + { + char *argv[4]; + int exitstatus; + + argv[0] = "lynx"; + argv[1] = "-source"; + argv[2] = (char *) url; + argv[3] = NULL; + exitstatus = execute ("lynx", "lynx", argv, true, false, false, false, + true, false, NULL); + if (exitstatus != 127) + { + if (exitstatus != 0) + goto failed; + if (verbose) + fprintf (stderr, _(" done.\n")); + return; + } + } + } + + /* Fourth try: using "curl --silent url". */ + { + static bool curl_tested; + static bool curl_present; + + if (!curl_tested) + { + /* Test for presence of curl: "curl --version > /dev/null" */ + char *argv[3]; + int exitstatus; + + argv[0] = "curl"; + argv[1] = "--version"; + argv[2] = NULL; + exitstatus = execute ("curl", "curl", argv, false, false, true, true, + true, false, NULL); + curl_present = (exitstatus == 0 || exitstatus == 2); + curl_tested = true; + } + + if (curl_present) + { + char *argv[4]; + int exitstatus; + + argv[0] = "curl"; + argv[1] = "--silent"; + argv[2] = (char *) url; + argv[3] = NULL; + exitstatus = execute ("curl", "curl", argv, true, false, false, false, + true, false, NULL); + if (exitstatus != 127) + { + if (exitstatus != 0) + goto failed; + if (verbose) + fprintf (stderr, _(" done.\n")); + return; + } + } + } + + failed: + if (verbose) + fprintf (stderr, _(" failed.\n")); + /* Use the file as fallback. */ + cat_file (file); +} diff --git a/gettext-tools/src/user-email.sh.in b/gettext-tools/src/user-email.sh.in new file mode 100644 index 0000000..5a35405 --- /dev/null +++ b/gettext-tools/src/user-email.sh.in @@ -0,0 +1,435 @@ +#!/bin/sh +# Prints the user's email address, with confirmation from the user. +# +# Copyright (C) 2001-2003, 2005 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Prerequisites for using @libdir@ and @localedir@. +prefix="@prefix@" +exec_prefix="@exec_prefix@" +datarootdir="@datarootdir@" +datadir="@datadir@" +# Set variables libdir, localedir. +libdir="@libdir@" +localedir="@localedir@" + +# Support for relocatability. +if test "@RELOCATABLE@" = yes; then + orig_installdir="$libdir"/gettext # see Makefile.am's install rule + # Determine curr_installdir without caring for symlinked callers. + curr_installdir=`echo "$0" | sed -e 's,/[^/]*$,,'` + curr_installdir=`cd "$curr_installdir" && pwd` + # Compute the original/current installation prefixes by stripping the + # trailing directories off the original/current installation directories. + while true; do + orig_last=`echo "$orig_installdir" | sed -n -e 's,^.*/\([^/]*\)$,\1,p'` + curr_last=`echo "$curr_installdir" | sed -n -e 's,^.*/\([^/]*\)$,\1,p'` + if test -z "$orig_last" || test -z "$curr_last"; then + break + fi + if test "$orig_last" != "$curr_last"; then + break + fi + orig_installdir=`echo "$orig_installdir" | sed -e 's,/[^/]*$,,'` + curr_installdir=`echo "$curr_installdir" | sed -e 's,/[^/]*$,,'` + done + # Now relocate the directory variables that we use. + libdir=`echo "$libdir/" | sed -e "s%^${orig_installdir}/%${curr_installdir}/%" | sed -e 's,/$,,'` + localedir=`echo "$localedir/" | sed -e "s%^${orig_installdir}/%${curr_installdir}/%" | sed -e 's,/$,,'` +fi + +# Internationalization. +. gettext.sh +TEXTDOMAIN=gettext-tools +export TEXTDOMAIN +TEXTDOMAINDIR="$localedir" +export TEXTDOMAINDIR + +# Redirect fileno 3 to interactive I/O. +exec 3>/dev/tty + +# Output a prompt. +if test $# != 0; then + echo "$1" 1>&3 +fi + +# Find the user name on the local machine. +user=`id -u -n 2>/dev/null` +if test -z "$user"; then + user="$USER" + if test -z "$user"; then + user="$LOGNAME" + if test -z "$user"; then + user=unknown + fi + fi +fi + +# Find the hostname. +# hostname on some systems (SVR3.2, old Linux) returns a bogus exit status, +# so uname gets run too, so we keep only the first line of output. +#host=`(hostname || uname -n) 2>/dev/null | sed 1q` +host=`"$libdir"/gettext/hostname --short 2>/dev/null | sed 1q` + +# Find the hostname. +hostfqdn=`"$libdir"/gettext/hostname --fqdn 2>/dev/null | sed 1q` + +# Find a list of email addresses from various mailer configuration files. +# All mailers use configuration files under $HOME. We handle them in a +# last-modified - first-priority order. +cd $HOME +files="" + +# ----------------------- BEGIN MAILER SPECIFIC CODE ----------------------- + +# Mozilla Thunderbird addresses +files="$files .thunderbird/*/prefs.js" + +# Mozilla addresses +files="$files .mozilla/*/prefs.js" + +# Netscape 4 addresses +files="$files .netscape/liprefs.js .netscape/preferences.js" + +# Netscape 3 addresses +files="$files .netscape/preferences" + +# Emacs/XEmacs rmail, Emacs/XEmacs gnus, XEmacs vm addresses +# XEmacs mew addresses +files="$files .emacs .emacs.el" + +# KDE2 addresses +files="$files .kde2/share/config/emaildefaults" + +# KDE kmail addresses +files="$files .kde2/share/config/kmailrc" + +# GNOME evolution 2 addresses +files="$files .gconf/apps/evolution/mail/%gconf.xml" + +# GNOME evolution 1 addresses +files="$files evolution/config.xmldb" + +# GNOME balsa addresses +files="$files .gnome/balsa" + +# StarOffice and OpenOffice addresses +sed_dos2unix='s/\r$//' +sed_soffice51='s,StarOffice 5\.1=\(.*\)$,\1/sofficerc,p' +sed_soffice52='s,StarOffice 5\.2=\(.*\)$,\1/user/sofficerc,p' +sed_ooffice='s,^OpenOffice[^=]*=\(.*\)$,\1/user/config/registry/instance/org/openoffice/UserProfile.xml,p' +files="$files Office51/sofficerc Office52/user/sofficerc "`sed -n -e "$sed_dos2unix" -e "$sed_soffice51" -e "$sed_soffice52" -e "$sed_ooffice" .sversionrc 2>/dev/null | sed -e 's,^file://*,/,'` + +# mutt addresses +files="$files .muttrc" + +# pine addresses +files="$files .pinerc" + +# xfmail addresses +files="$files .xfmail/.xfmailrc" + +# tkrat addresses +files="$files .ratatosk/ratatoskrc" + +# ----------------------- END MAILER SPECIFIC CODE ----------------------- + +# Expand wildcards and remove nonexistent files from the list. +nfiles="" +for file in $files; do + if test -r "$file" && test ! -d "$file"; then + nfiles="$nfiles $file" + fi +done +files="$nfiles" + +addresses="" + +if test -n "$files"; then + + for file in `ls -t $files`; do + + case "$file" in + +# ----------------------- BEGIN MAILER SPECIFIC CODE ----------------------- + + # Mozilla and Mozilla Thunderbird addresses + .mozilla/*/prefs.js | .thunderbird/*/prefs.js) + addresses="$addresses "`grep -h '^user_pref("mail\.identity\..*\.useremail", ".*");$' $file 2>/dev/null | sed -e 's/^user_pref("mail\.identity\..*\.useremail", "\(.*\)");$/\1/'` + ;; + + # Netscape 4 addresses + .netscape/liprefs.js | .netscape/preferences.js) + addresses="$addresses "`grep -h '^user_pref("mail\.identity\.useremail", ".*");$' $file 2>/dev/null | sed -e 's/^user_pref("mail\.identity\.useremail", "\(.*\)");$/\1/'` + ;; + + # Netscape 3 addresses + .netscape/preferences) + addresses="$addresses "`grep -h '^EMAIL_ADDRESS:' $file 2>/dev/null | sed -e 's/^EMAIL_ADDRESS:[ ]*//'` + ;; + + .emacs | .emacs.el) + # Emacs/XEmacs rmail, Emacs/XEmacs gnus, XEmacs vm addresses + addresses="$addresses "`grep -h '[ (]user-mail-address "[^"]*"' $file 2>/dev/null | sed -e 's/^.*[ (]user-mail-address "\([^"]*\)".*$/\1/'` + # XEmacs mew addresses + domains=`grep -h '[ (]mew-mail-domain "[^"]*"' $file 2>/dev/null | sed -e 's/^.*[ (]mew-mail-domain "\([^"]*\)".*$/\1/'` + if test -n "$domains"; then + for domain in $domains; do + addresses="$addresses ${user}@$domain" + done + fi + ;; + + # KDE2 addresses + .kde2/share/config/emaildefaults) + addresses="$addresses "`grep -h '^EmailAddress=' $file 2>/dev/null | sed -e 's/^EmailAddress=//'` + ;; + + # KDE kmail addresses + .kde2/share/config/kmailrc) + addresses="$addresses "`grep -h '^Email Address=' $file 2>/dev/null | sed -e 's/^Email Address=//'` + ;; + + # GNOME evolution 2 addresses + .gconf/apps/evolution/mail/%gconf.xml) + sedexpr0='s,^.*<addr-spec>\(.*\)</addr-spec>.*$,\1,p' + addresses="$addresses "`sed -n -e "$sedexpr0" < $file` + ;; + + # GNOME evolution 1 addresses + evolution/config.xmldb) + sedexpr0='s/^.*\(.*\).*$,\1,p' $file 2>/dev/null` + ;; + + # StarOffice addresses + # Not a typo. They really write "Adress" with a single d. + # German orthography... + */sofficerc) + addresses="$addresses "`grep -h '^User-Adress=' $file 2>/dev/null | sed -e 's/#[^#]*$//' -e 's/^.*#//'` + ;; + + # mutt addresses + .muttrc) + mutt_addresses=`grep -h '^set from="[^"]*"[ ]*$' $file 2>/dev/null | sed -e 's/^set from="\([^"]*\)"[ ]*$/\1/'` + if test -n "$mutt_addresses"; then + addresses="$addresses $mutt_addresses" + else + # mutt uses $EMAIL as fallback. + if test -n "$EMAIL"; then + addresses="$addresses $EMAIL" + fi + fi + ;; + + # pine addresses + .pinerc) + domains=`grep -h '^user-domain=' $file 2>/dev/null | sed -e 's/^user-domain=//'` + if test -n "$domains"; then + for domain in $domains; do + addresses="$addresses ${user}@$domain" + done + else + # The use-only-domain-name option is only used if the user-domain option is not present. + domains=`grep -h '^use-only-domain-name=' $file 2>/dev/null | sed -e 's/^use-only-domain-name=//'` + if test "Yes" = "$domains"; then + addresses="$addresses ${user}@"`echo "$hostfqdn" | sed -e 's/^[^.]*\.//'` + fi + fi + ;; + + # xfmail addresses + .xfmail/.xfmailrc) + addresses="$addresses "`grep -h '^from=.*<.*>' $file 2>/dev/null | sed -e 's/^.*<\([^<>]*\)>.*$/\1/'` + ;; + + # tkrat addresses + .ratatosk/ratatoskrc) + domains=`grep -h '^set option(masquerade_as) ' $file 2>/dev/null | sed -e 's/^set option(masquerade_as) //'` + if test -n "$domains"; then + for domain in $domains; do + addresses="$addresses ${user}@$domain" + done + else + # The domain option is used only if the masquerade_as option is not present. + domains=`grep -h '^set option(domain) ' $file 2>/dev/null | sed -e 's/^set option(domain) //'` + if test -n "$domains"; then + for domain in $domains; do + addresses="$addresses ${user}@${host}.$domain" + done + fi + fi + ;; + +# ----------------------- END MAILER SPECIFIC CODE ----------------------- + + esac + + done + +fi + +# Some Debian systems have a file /etc/mailname. +if test -r /etc/mailname; then + hostmailname=`cat /etc/mailname` + if test -n "$hostmailname"; then + addresses="$addresses ${user}@$hostmailname" + fi +fi + +# SuSE Linux >= 8.0 systems have a file /etc/sysconfig/mail. +if test -r /etc/sysconfig/mail; then + hostmailname=`. /etc/sysconfig/mail && echo "$FROM_HEADER"` + if test -n "$hostmailname"; then + addresses="$addresses ${user}@$hostmailname" + fi +fi + +# elm has no user-defined addresses. +# mailx has no user-defined addresses. +# mh has no user-defined addresses. +# They use the system default. +addresses="$addresses ${user}@$hostfqdn" + +# Normalize addresses: remove addresses without @, lowercase the part after @, +# and remove duplicates. +lowercase_sed='{ +h +s/^[^@]*@\(.*\)$/\1/ +y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ +x +s/^\([^@]*\)@.*/\1@/ +G +s/\n// +p +}' +naddresses="" +for addr in $addresses; do + case "$addr" in + "<"*">") addr=`echo "$addr" | sed -e 's/^$//'` ;; + esac + case "$addr" in + *@*) + addr=`echo "$addr" | sed -n -e "$lowercase_sed"` + case " $naddresses " in + *" $addr "*) ;; + *) naddresses="$naddresses $addr" ;; + esac + ;; + esac +done +addresses="$naddresses" + +# Now it's time to ask the user. +case "$addresses" in + " "*" "*) + # At least two addresses. + lines="" + i=0 + for addr in $addresses; do + i=`expr $i + 1` + lines="${lines}${i} ${addr} +" + done + while true; do + { gettext "Which is your email address?"; echo; } 1>&3 + echo "$lines" 1>&3 + { gettext "Please choose the number, or enter your email address."; echo; } 1>&3 + read answer < /dev/tty + case "$answer" in + *@*) ;; + [0-9]*) + i=0 + for addr in $addresses; do + i=`expr $i + 1` + if test "$i" = "$answer"; then + break 2 + fi + done + ;; + esac + case "$answer" in + "<"*">") answer=`echo "$answer" | sed -e 's/^$//'` ;; + esac + case "$answer" in + *" "*) { gettext "Invalid email address: invalid character."; echo; echo; } 1>&3 ; continue ;; + *@*.*) ;; + *@*) { gettext "Invalid email address: need a fully qualified host name or domain name."; echo; echo; } 1>&3 ; continue ;; + *) { gettext "Invalid email address: missing @"; echo; echo; } 1>&3 ; continue ;; + esac + addr=`echo "$answer" | sed -n -e "$lowercase_sed"` + break + done + ;; + " "*) + # One address. + while true; do + { gettext "Is the following your email address?"; echo; } 1>&3 + echo " $addresses" 1>&3 + { gettext "Please confirm by pressing Return, or enter your email address."; echo; } 1>&3 + read answer < /dev/tty + if test -z "$answer"; then + addr=`echo "$addresses" | sed -e 's/^ //'` + break + fi + case "$answer" in + "<"*">") answer=`echo "$answer" | sed -e 's/^$//'` ;; + esac + case "$answer" in + *" "*) { gettext "Invalid email address: invalid character."; echo; echo; } 1>&3 ; continue ;; + *@*.*) ;; + *@*) { gettext "Invalid email address: need a fully qualified host name or domain name."; echo; echo; } 1>&3 ; continue ;; + *) { gettext "Invalid email address: missing @"; echo; echo; } 1>&3 ; continue ;; + esac + addr=`echo "$answer" | sed -n -e "$lowercase_sed"` + break + done + ;; + "") + # No address. + { gettext "Couldn't find out about your email address."; echo; } 1>&3 + while true; do + { gettext "Please enter your email address."; echo; } 1>&3 + read answer < /dev/tty + case "$answer" in + "<"*">") answer=`echo "$answer" | sed -e 's/^$//'` ;; + esac + case "$answer" in + *" "*) { gettext "Invalid email address: invalid character."; echo; echo; } 1>&3 ; continue ;; + *@*.*) ;; + *@*) { gettext "Invalid email address: need a fully qualified host name or domain name."; echo; echo; } 1>&3 ; continue ;; + *) { gettext "Invalid email address: missing @"; echo; echo; } 1>&3 ; continue ;; + esac + addr=`echo "$answer" | sed -n -e "$lowercase_sed"` + break + done + ;; + *) echo "internal error" 1>&3 ; exit 1 ;; +esac + +# Print to standard output. +echo "$addr" diff --git a/gettext-tools/src/write-catalog.c b/gettext-tools/src/write-catalog.c new file mode 100644 index 0000000..bab8c36 --- /dev/null +++ b/gettext-tools/src/write-catalog.c @@ -0,0 +1,469 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2008, 2012 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "write-catalog.h" + +#include +#include +#include +#include +#include +#include + +#include +#ifndef STDOUT_FILENO +# define STDOUT_FILENO 1 +#endif + +#include "ostream.h" +#include "file-ostream.h" +#include "fwriteerror.h" +#include "error-progname.h" +#include "xvasprintf.h" +#include "po-xerror.h" +#include "gettext.h" + +/* Our regular abbreviation. */ +#define _(str) gettext (str) + +/* When compiled in src, enable color support. + When compiled in libgettextpo, don't enable color support. */ +#ifdef GETTEXTDATADIR + +# define ENABLE_COLOR 1 + +# include "styled-ostream.h" +# include "term-styled-ostream.h" +# include "html-styled-ostream.h" +# include "fd-ostream.h" + +# include "color.h" +# include "po-charset.h" +# include "msgl-iconv.h" + +#endif + + +/* =========== Some parameters for use by 'msgdomain_list_print'. ========== */ + + +/* This variable controls the page width when printing messages. + Defaults to PAGE_WIDTH if not set. Zero (0) given to message_page_- + width_set will result in no wrapping being performed. */ +static size_t page_width = PAGE_WIDTH; + +void +message_page_width_set (size_t n) +{ + if (n == 0) + { + page_width = INT_MAX; + return; + } + + if (n < 20) + n = 20; + + page_width = n; +} + + +/* ======================== msgdomain_list_print() ======================== */ + + +void +msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename, + catalog_output_format_ty output_syntax, + bool force, bool debug) +{ + bool to_stdout; + + /* We will not write anything if, for every domain, we have no message + or only the header entry. */ + if (!force) + { + bool found_nonempty = false; + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + if (!(mlp->nitems == 0 + || (mlp->nitems == 1 && is_header (mlp->item[0])))) + { + found_nonempty = true; + break; + } + } + + if (!found_nonempty) + return; + } + + /* Check whether the output format can accommodate all messages. */ + if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1) + { + if (output_syntax->alternative_is_po) + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, _("\ +Cannot output multiple translation domains into a single file with the specified output format. Try using PO file syntax instead.")); + else + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, _("\ +Cannot output multiple translation domains into a single file with the specified output format.")); + } + else + { + if (!output_syntax->supports_contexts) + { + const lex_pos_ty *has_context; + size_t k; + + has_context = NULL; + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + size_t j; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (mp->msgctxt != NULL) + { + has_context = &mp->pos; + break; + } + } + } + + if (has_context != NULL) + { + error_with_progname = false; + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, + has_context->file_name, has_context->line_number, + (size_t)(-1), false, _("\ +message catalog has context dependent translations, but the output format does not support them.")); + error_with_progname = true; + } + } + + if (!output_syntax->supports_plurals) + { + const lex_pos_ty *has_plural; + size_t k; + + has_plural = NULL; + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + size_t j; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (mp->msgid_plural != NULL) + { + has_plural = &mp->pos; + break; + } + } + } + + if (has_plural != NULL) + { + error_with_progname = false; + if (output_syntax->alternative_is_java_class) + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, + has_plural->file_name, has_plural->line_number, + (size_t)(-1), false, _("\ +message catalog has plural form translations, but the output format does not support them. Try generating a Java class using \"msgfmt --java\", instead of a properties file.")); + else + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, + has_plural->file_name, has_plural->line_number, + (size_t)(-1), false, _("\ +message catalog has plural form translations, but the output format does not support them.")); + error_with_progname = true; + } + } + } + + to_stdout = (filename == NULL || strcmp (filename, "-") == 0 + || strcmp (filename, "/dev/stdout") == 0); + +#if ENABLE_COLOR + if (output_syntax->supports_color + && (color_mode == color_yes + || (color_mode == color_tty && to_stdout && isatty (STDOUT_FILENO)))) + { + int fd; + ostream_t stream; + + /* Open the output file. */ + if (!to_stdout) + { + fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, + /* 0666 in portable POSIX notation: */ + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd < 0) + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("cannot create output file \"%s\""), + filename), + errno_description)); + } + } + else + { + fd = STDOUT_FILENO; + filename = _("standard output"); + } + + style_file_prepare (); + stream = term_styled_ostream_create (fd, filename, style_file_name); + if (stream == NULL) + stream = fd_ostream_create (fd, filename, true); + output_syntax->print (mdlp, stream, page_width, debug); + ostream_free (stream); + + /* Make sure nothing went wrong. */ + if (close (fd) < 0) + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("error while writing \"%s\" file"), + filename), + errno_description)); + } + } + else +#endif + { + FILE *fp; + file_ostream_t stream; + + /* Open the output file. */ + if (!to_stdout) + { + fp = fopen (filename, "wb"); + if (fp == NULL) + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("cannot create output file \"%s\""), + filename), + errno_description)); + } + } + else + { + fp = stdout; + filename = _("standard output"); + } + + stream = file_ostream_create (fp); + +#if ENABLE_COLOR + if (output_syntax->supports_color && color_mode == color_html) + { + html_styled_ostream_t html_stream; + + /* Convert mdlp to UTF-8 encoding. */ + if (mdlp->encoding != po_charset_utf8) + { + mdlp = msgdomain_list_copy (mdlp, 0); + mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL); + } + + style_file_prepare (); + html_stream = html_styled_ostream_create (stream, style_file_name); + output_syntax->print (mdlp, html_stream, page_width, debug); + ostream_free (html_stream); + } + else +#endif + { + output_syntax->print (mdlp, stream, page_width, debug); + } + + ostream_free (stream); + + /* Make sure nothing went wrong. */ + if (fwriteerror (fp)) + { + const char *errno_description = strerror (errno); + po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, + xasprintf ("%s: %s", + xasprintf (_("error while writing \"%s\" file"), + filename), + errno_description)); + } + } +} + + +/* =============================== Sorting. ================================ */ + + +static int +cmp_by_msgid (const void *va, const void *vb) +{ + const message_ty *a = *(const message_ty **) va; + const message_ty *b = *(const message_ty **) vb; + + /* Because msgids normally contain only ASCII characters or are UTF-8 + encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And + strcoll() in a C.UTF-8 locale is the same as strcmp(). */ + int cmp = strcmp (a->msgid, b->msgid); + if (cmp != 0) + return cmp; + + /* If the msgids are equal, disambiguate by comparing the contexts. */ + if (a->msgctxt == b->msgctxt) + return 0; + if (a->msgctxt == NULL) + return -1; + if (b->msgctxt == NULL) + return 1; + return strcmp (a->msgctxt, b->msgctxt); +} + + +void +msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp) +{ + size_t k; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + if (mlp->nitems > 0) + qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid); + } +} + + +/* Sort the file positions of every message. */ + +static int +cmp_filepos (const void *va, const void *vb) +{ + const lex_pos_ty *a = (const lex_pos_ty *) va; + const lex_pos_ty *b = (const lex_pos_ty *) vb; + int cmp; + + cmp = strcmp (a->file_name, b->file_name); + if (cmp == 0) + cmp = (int) a->line_number - (int) b->line_number; + + return cmp; +} + +static void +msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp) +{ + size_t j, k; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (mp->filepos_count > 0) + qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]), + cmp_filepos); + } + } +} + + +/* Sort the messages according to the file position. */ + +static int +cmp_by_filepos (const void *va, const void *vb) +{ + const message_ty *a = *(const message_ty **) va; + const message_ty *b = *(const message_ty **) vb; + int cmp; + + /* No filepos is smaller than any other filepos. */ + if (a->filepos_count == 0) + { + if (b->filepos_count != 0) + return -1; + } + if (b->filepos_count == 0) + return 1; + + /* Compare on the file names... */ + cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name); + if (cmp != 0) + return cmp; + + /* If they are equal, compare on the line numbers... */ + cmp = a->filepos[0].line_number - b->filepos[0].line_number; + if (cmp != 0) + return cmp; + + /* If they are equal, compare on the msgid strings. */ + /* Because msgids normally contain only ASCII characters or are UTF-8 + encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And + strcoll() in a C.UTF-8 locale is the same as strcmp(). */ + cmp = strcmp (a->msgid, b->msgid); + if (cmp != 0) + return cmp; + + /* If the msgids are equal, disambiguate by comparing the contexts. */ + if (a->msgctxt == b->msgctxt) + return 0; + if (a->msgctxt == NULL) + return -1; + if (b->msgctxt == NULL) + return 1; + return strcmp (a->msgctxt, b->msgctxt); +} + + +void +msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp) +{ + size_t k; + + /* It makes sense to compare filepos[0] of different messages only after + the filepos[] array of each message has been sorted. Sort it now. */ + msgdomain_list_sort_filepos (mdlp); + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + if (mlp->nitems > 0) + qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos); + } +} diff --git a/gettext-tools/src/write-catalog.h b/gettext-tools/src/write-catalog.h new file mode 100644 index 0000000..804592c --- /dev/null +++ b/gettext-tools/src/write-catalog.h @@ -0,0 +1,91 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2003, 2006, 2008 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_CATALOG_H +#define _WRITE_CATALOG_H + +#include + +#include "ostream.h" +#include "message.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* This structure describes a textual catalog output format. */ +struct catalog_output_format +{ + /* Outputs a list of domains of messages to a stream. */ + void (*print) (msgdomain_list_ty *mdlp, ostream_t stream, size_t page_width, bool debug); + + /* Whether the print function requires the MDLP to be encoded in UTF-8 + encoding. */ + bool requires_utf8; + + /* Whether the print function supports styled output. */ + bool supports_color; + + /* Whether the format supports multiple domains in a single file. */ + bool supports_multiple_domains; + + /* Whether the format supports contexts. */ + bool supports_contexts; + + /* Whether the format supports plurals. */ + bool supports_plurals; + + /* Whether the formats sorts obsolete messages at the end. */ + bool sorts_obsoletes_to_end; + + /* Whether the PO file format is a suitable alternative output format for + this one. */ + bool alternative_is_po; + + /* Whether a Java class is a suitable alternative output format for this + one. */ + bool alternative_is_java_class; +}; + +typedef const struct catalog_output_format * catalog_output_format_ty; + +/* These functions set some parameters for use by 'msgdomain_list_print'. */ +extern void + message_page_width_set (size_t width); + +/* Output MDLP into a PO file with the given FILENAME, according to the + parameters set by the functions above. */ +extern void + msgdomain_list_print (msgdomain_list_ty *mdlp, + const char *filename, + catalog_output_format_ty output_syntax, + bool force, bool debug); + +/* Sort MDLP destructively according to the given criterion. */ +extern void + msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp); +extern void + msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _WRITE_CATALOG_H */ diff --git a/gettext-tools/src/write-csharp.c b/gettext-tools/src/write-csharp.c new file mode 100644 index 0000000..047361a --- /dev/null +++ b/gettext-tools/src/write-csharp.c @@ -0,0 +1,783 @@ +/* Writing C# satellite assemblies. + Copyright (C) 2003-2010 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +/* Specification. */ +#include "write-csharp.h" + +#include +#include +#include +#include +#include + +#include +#if !defined S_ISDIR && defined S_IFDIR +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !S_IRUSR && S_IREAD +# define S_IRUSR S_IREAD +#endif +#if !S_IRUSR +# define S_IRUSR 00400 +#endif +#if !S_IWUSR && S_IWRITE +# define S_IWUSR S_IWRITE +#endif +#if !S_IWUSR +# define S_IWUSR 00200 +#endif +#if !S_IXUSR && S_IEXEC +# define S_IXUSR S_IEXEC +#endif +#if !S_IXUSR +# define S_IXUSR 00100 +#endif +#if !S_IRGRP +# define S_IRGRP (S_IRUSR >> 3) +#endif +#if !S_IWGRP +# define S_IWGRP (S_IWUSR >> 3) +#endif +#if !S_IXGRP +# define S_IXGRP (S_IXUSR >> 3) +#endif +#if !S_IROTH +# define S_IROTH (S_IRUSR >> 6) +#endif +#if !S_IWOTH +# define S_IWOTH (S_IWUSR >> 6) +#endif +#if !S_IXOTH +# define S_IXOTH (S_IXUSR >> 6) +#endif + +#include "c-ctype.h" +#include "relocatable.h" +#include "error.h" +#include "xerror.h" +#include "csharpcomp.h" +#include "message.h" +#include "msgfmt.h" +#include "msgl-iconv.h" +#include "plural-exp.h" +#include "po-charset.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "concat-filename.h" +#include "fwriteerror.h" +#include "clean-temp.h" +#include "unistr.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Convert a resource name to a class name. + Return a nonempty string consisting of alphanumerics and underscores + and starting with a letter or underscore. */ +static char * +construct_class_name (const char *resource_name) +{ + /* This code must be kept consistent with intl.cs, function + GettextResourceManager.ConstructClassName. */ + /* We could just return an arbitrary fixed class name, like "Messages", + assuming that every assembly will only ever contain one + GettextResourceSet subclass, but this assumption would break the day + we want to support multi-domain PO files in the same format... */ + bool valid; + const char *p; + + /* Test for a valid ASCII identifier: + - nonempty, + - first character is A..Za..z_ - see x-csharp.c:is_identifier_start. + - next characters are A..Za..z_0..9 - see x-csharp.c:is_identifier_part. + */ + valid = (resource_name[0] != '\0'); + for (p = resource_name; valid && *p != '\0'; p++) + { + char c = *p; + if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') + || (p > resource_name && c >= '0' && c <= '9'))) + valid = false; + } + if (valid) + return xstrdup (resource_name); + else + { + static const char hexdigit[] = "0123456789abcdef"; + const char *str = resource_name; + const char *str_limit = str + strlen (str); + char *class_name = XNMALLOC (12 + 6 * (str_limit - str) + 1, char); + char *b; + + b = class_name; + memcpy (b, "__UESCAPED__", 12); b += 12; + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + if (uc >= 0x10000) + { + *b++ = '_'; + *b++ = 'U'; + *b++ = hexdigit[(uc >> 28) & 0x0f]; + *b++ = hexdigit[(uc >> 24) & 0x0f]; + *b++ = hexdigit[(uc >> 20) & 0x0f]; + *b++ = hexdigit[(uc >> 16) & 0x0f]; + *b++ = hexdigit[(uc >> 12) & 0x0f]; + *b++ = hexdigit[(uc >> 8) & 0x0f]; + *b++ = hexdigit[(uc >> 4) & 0x0f]; + *b++ = hexdigit[uc & 0x0f]; + } + else if (!((uc >= 'A' && uc <= 'Z') || (uc >= 'a' && uc <= 'z') + || (uc >= '0' && uc <= '9'))) + { + *b++ = '_'; + *b++ = 'u'; + *b++ = hexdigit[(uc >> 12) & 0x0f]; + *b++ = hexdigit[(uc >> 8) & 0x0f]; + *b++ = hexdigit[(uc >> 4) & 0x0f]; + *b++ = hexdigit[uc & 0x0f]; + } + else + *b++ = uc; + } + *b++ = '\0'; + return (char *) xrealloc (class_name, b - class_name); + } +} + + +/* Write a string in C# Unicode notation to the given stream. */ +static void +write_csharp_string (FILE *stream, const char *str) +{ + static const char hexdigit[] = "0123456789abcdef"; + const char *str_limit = str + strlen (str); + + fprintf (stream, "\""); + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + if (uc == 0x0000) + fprintf (stream, "\\0"); + else if (uc == 0x0007) + fprintf (stream, "\\a"); + else if (uc == 0x0008) + fprintf (stream, "\\b"); + else if (uc == 0x0009) + fprintf (stream, "\\t"); + else if (uc == 0x000a) + fprintf (stream, "\\n"); + else if (uc == 0x000b) + fprintf (stream, "\\v"); + else if (uc == 0x000c) + fprintf (stream, "\\f"); + else if (uc == 0x000d) + fprintf (stream, "\\r"); + else if (uc == 0x0022) + fprintf (stream, "\\\""); + else if (uc == 0x005c) + fprintf (stream, "\\\\"); + else if (uc >= 0x0020 && uc < 0x007f) + fprintf (stream, "%c", (int) uc); + else if (uc < 0x10000) + fprintf (stream, "\\u%c%c%c%c", + hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f], + hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]); + else + fprintf (stream, "\\U%c%c%c%c%c%c%c%c", + hexdigit[(uc >> 28) & 0x0f], hexdigit[(uc >> 24) & 0x0f], + hexdigit[(uc >> 20) & 0x0f], hexdigit[(uc >> 16) & 0x0f], + hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f], + hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]); + } + fprintf (stream, "\""); +} + + +/* Write a (msgctxt, msgid) pair as a string in C# Unicode notation to the + given stream. */ +static void +write_csharp_msgid (FILE *stream, message_ty *mp) +{ + const char *msgctxt = mp->msgctxt; + const char *msgid = mp->msgid; + + if (msgctxt == NULL) + write_csharp_string (stream, msgid); + else + { + size_t msgctxt_len = strlen (msgctxt); + size_t msgid_len = strlen (msgid); + size_t combined_len = msgctxt_len + 1 + msgid_len; + char *combined; + + combined = (char *) xmalloca (combined_len); + memcpy (combined, msgctxt, msgctxt_len); + combined[msgctxt_len] = MSGCTXT_SEPARATOR; + memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1); + + write_csharp_string (stream, combined); + + freea (combined); + } +} + + +/* Write C# code that returns the value for a message. If the message + has plural forms, it is an expression of type System.String[], otherwise it + is an expression of type System.String. */ +static void +write_csharp_msgstr (FILE *stream, message_ty *mp) +{ + if (mp->msgid_plural != NULL) + { + bool first; + const char *p; + + fprintf (stream, "new System.String[] { "); + for (p = mp->msgstr, first = true; + p < mp->msgstr + mp->msgstr_len; + p += strlen (p) + 1, first = false) + { + if (!first) + fprintf (stream, ", "); + write_csharp_string (stream, p); + } + fprintf (stream, " }"); + } + else + { + if (mp->msgstr_len != strlen (mp->msgstr) + 1) + abort (); + + write_csharp_string (stream, mp->msgstr); + } +} + + +/* Tests whether a plural expression, evaluated according to the C rules, + can only produce the values 0 and 1. */ +static bool +is_expression_boolean (struct expression *exp) +{ + switch (exp->operation) + { + case var: + case mult: + case divide: + case module: + case plus: + case minus: + return false; + case lnot: + case less_than: + case greater_than: + case less_or_equal: + case greater_or_equal: + case equal: + case not_equal: + case land: + case lor: + return true; + case num: + return (exp->val.num == 0 || exp->val.num == 1); + case qmop: + return is_expression_boolean (exp->val.args[1]) + && is_expression_boolean (exp->val.args[2]); + default: + abort (); + } +} + + +/* Write C# code that evaluates a plural expression according to the C rules. + The variable is called 'n'. */ +static void +write_csharp_expression (FILE *stream, const struct expression *exp, bool as_boolean) +{ + /* We use parentheses everywhere. This frees us from tracking the priority + of arithmetic operators. */ + if (as_boolean) + { + /* Emit a C# expression of type 'bool'. */ + switch (exp->operation) + { + case num: + fprintf (stream, "%s", exp->val.num ? "true" : "false"); + return; + case lnot: + fprintf (stream, "(!"); + write_csharp_expression (stream, exp->val.args[0], true); + fprintf (stream, ")"); + return; + case less_than: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " < "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case greater_than: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " > "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case less_or_equal: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " <= "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case greater_or_equal: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " >= "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case equal: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " == "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case not_equal: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " != "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case land: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], true); + fprintf (stream, " && "); + write_csharp_expression (stream, exp->val.args[1], true); + fprintf (stream, ")"); + return; + case lor: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], true); + fprintf (stream, " || "); + write_csharp_expression (stream, exp->val.args[1], true); + fprintf (stream, ")"); + return; + case qmop: + if (is_expression_boolean (exp->val.args[1]) + && is_expression_boolean (exp->val.args[2])) + { + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], true); + fprintf (stream, " ? "); + write_csharp_expression (stream, exp->val.args[1], true); + fprintf (stream, " : "); + write_csharp_expression (stream, exp->val.args[2], true); + fprintf (stream, ")"); + return; + } + /*FALLTHROUGH*/ + case var: + case mult: + case divide: + case module: + case plus: + case minus: + fprintf (stream, "("); + write_csharp_expression (stream, exp, false); + fprintf (stream, " != 0)"); + return; + default: + abort (); + } + } + else + { + /* Emit a C# expression of type 'long'. */ + switch (exp->operation) + { + case var: + fprintf (stream, "n"); + return; + case num: + fprintf (stream, "%lu", exp->val.num); + return; + case mult: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " * "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case divide: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " / "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case module: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " %% "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case plus: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " + "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case minus: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], false); + fprintf (stream, " - "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case qmop: + fprintf (stream, "("); + write_csharp_expression (stream, exp->val.args[0], true); + fprintf (stream, " ? "); + write_csharp_expression (stream, exp->val.args[1], false); + fprintf (stream, " : "); + write_csharp_expression (stream, exp->val.args[2], false); + fprintf (stream, ")"); + return; + case lnot: + case less_than: + case greater_than: + case less_or_equal: + case greater_or_equal: + case equal: + case not_equal: + case land: + case lor: + fprintf (stream, "("); + write_csharp_expression (stream, exp, true); + fprintf (stream, " ? 1 : 0)"); + return; + default: + abort (); + } + } +} + + +/* Write the C# code for the GettextResourceSet subclass to the given stream. + Note that we use fully qualified class names and no "using" statements, + because applications can have their own classes called X.Y.Hashtable or + X.Y.String. */ +static void +write_csharp_code (FILE *stream, const char *culture_name, const char *class_name, message_list_ty *mlp) +{ + const char *last_dot; + const char *class_name_last_part; + unsigned int plurals; + size_t j; + + fprintf (stream, + "/* Automatically generated by GNU msgfmt. Do not modify! */\n"); + + /* We have to use a "using" statement here, to avoid a bug in the pnet-0.6.0 + compiler. */ + fprintf (stream, "using GNU.Gettext;\n"); + + /* Assign a strong name to the assembly, so that two different localizations + of the same domain can be loaded one after the other. This strong name + tells the Global Assembly Cache that they are meant to be different. */ + fprintf (stream, "[assembly: System.Reflection.AssemblyCulture("); + write_csharp_string (stream, culture_name); + fprintf (stream, ")]\n"); + + last_dot = strrchr (class_name, '.'); + if (last_dot != NULL) + { + fprintf (stream, "namespace "); + fwrite (class_name, 1, last_dot - class_name, stream); + fprintf (stream, " {\n"); + class_name_last_part = last_dot + 1; + } + else + class_name_last_part = class_name; + fprintf (stream, "public class %s : GettextResourceSet {\n", + class_name_last_part); + + /* Determine whether there are plural messages. */ + plurals = 0; + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgid_plural != NULL) + plurals++; + + /* Emit the constructor. */ + fprintf (stream, " public %s ()\n", class_name_last_part); + fprintf (stream, " : base () {\n"); + fprintf (stream, " }\n"); + + /* Emit the TableInitialized field. */ + fprintf (stream, " private bool TableInitialized;\n"); + + /* Emit the ReadResources method. */ + fprintf (stream, " protected override void ReadResources () {\n"); + /* In some implementations, such as mono < 2009-02-27, the ReadResources + method is called just once, when Table == null. In other implementations, + such as mono >= 2009-02-27, it is called at every GetObject call, and it + is responsible for doing the initialization only once, even when called + simultaneously from multiple threads. */ + fprintf (stream, " if (!TableInitialized) {\n"); + fprintf (stream, " lock (this) {\n"); + fprintf (stream, " if (!TableInitialized) {\n"); + /* In some implementations, the ResourceSet constructor initializes Table + before calling ReadResources(). In other implementations, the + ReadResources() method is expected to initialize the Table. */ + fprintf (stream, " if (Table == null)\n"); + fprintf (stream, " Table = new System.Collections.Hashtable();\n"); + fprintf (stream, " System.Collections.Hashtable t = Table;\n"); + for (j = 0; j < mlp->nitems; j++) + { + fprintf (stream, " t.Add("); + write_csharp_msgid (stream, mlp->item[j]); + fprintf (stream, ","); + write_csharp_msgstr (stream, mlp->item[j]); + fprintf (stream, ");\n"); + } + fprintf (stream, " TableInitialized = true;\n"); + fprintf (stream, " }\n"); + fprintf (stream, " }\n"); + fprintf (stream, " }\n"); + fprintf (stream, " }\n"); + + /* Emit the msgid_plural strings. Only used by msgunfmt. */ + if (plurals) + { + fprintf (stream, " public static System.Collections.Hashtable GetMsgidPluralTable () {\n"); + fprintf (stream, " System.Collections.Hashtable t = new System.Collections.Hashtable();\n"); + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgid_plural != NULL) + { + fprintf (stream, " t.Add("); + write_csharp_msgid (stream, mlp->item[j]); + fprintf (stream, ","); + write_csharp_string (stream, mlp->item[j]->msgid_plural); + fprintf (stream, ");\n"); + } + fprintf (stream, " return t;\n"); + fprintf (stream, " }\n"); + } + + /* Emit the PluralEval function. It is a subroutine for GetPluralString. */ + if (plurals) + { + message_ty *header_entry; + const struct expression *plural; + unsigned long int nplurals; + + header_entry = message_list_search (mlp, NULL, ""); + extract_plural_expression (header_entry ? header_entry->msgstr : NULL, + &plural, &nplurals); + + fprintf (stream, " protected override long PluralEval (long n) {\n"); + fprintf (stream, " return "); + write_csharp_expression (stream, plural, false); + fprintf (stream, ";\n"); + fprintf (stream, " }\n"); + } + + /* Terminate the class. */ + fprintf (stream, "}\n"); + + if (last_dot != NULL) + /* Terminate the namespace. */ + fprintf (stream, "}\n"); +} + + +int +msgdomain_write_csharp (message_list_ty *mlp, const char *canon_encoding, + const char *resource_name, const char *locale_name, + const char *directory) +{ + int retval; + struct temp_dir *tmpdir; + char *culture_name; + char *output_file; + char *class_name; + char *csharp_file_name; + FILE *csharp_file; + const char *gettextlibdir; + const char *csharp_sources[1]; + const char *libdirs[1]; + const char *libraries[1]; + + /* If no entry for this resource/domain, don't even create the file. */ + if (mlp->nitems == 0) + return 0; + + retval = 1; + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + + /* Create a temporary directory where we can put the C# file. + A simple temporary file would also be possible but would require us to + define our own variant of mkstemp(): On one hand the functions mktemp(), + tmpnam(), tempnam() present a security risk, and on the other hand the + function mkstemp() doesn't allow to specify a fixed suffix of the file. + It is simpler to create a temporary directory. */ + tmpdir = create_temp_dir ("msg", NULL, false); + if (tmpdir == NULL) + goto quit1; + + /* Assign a default value to the resource name. */ + if (resource_name == NULL) + resource_name = "Messages"; + + /* Convert the locale name to a .NET specific culture name. */ + culture_name = xstrdup (locale_name); + { + char *p; + for (p = culture_name; *p != '\0'; p++) + if (*p == '_') + *p = '-'; + if (strncmp (culture_name, "sr-CS", 5) == 0) + memcpy (culture_name, "sr-SP", 5); + p = strchr (culture_name, '@'); + if (p != NULL) + { + if (strcmp (p, "@latin") == 0) + strcpy (p, "-Latn"); + else if (strcmp (p, "@cyrillic") == 0) + strcpy (p, "-Cyrl"); + } + if (strcmp (culture_name, "sr-SP") == 0) + { + free (culture_name); + culture_name = xstrdup ("sr-SP-Latn"); + } + else if (strcmp (culture_name, "uz-UZ") == 0) + { + free (culture_name); + culture_name = xstrdup ("uz-UZ-Latn"); + } + } + + /* Compute the output file name. This code must be kept consistent with + intl.cs, function GetSatelliteAssembly(). */ + { + char *output_dir = xconcatenated_filename (directory, culture_name, NULL); + struct stat statbuf; + + /* Try to create the output directory if it does not yet exist. */ + if (stat (output_dir, &statbuf) < 0 && errno == ENOENT) + if (mkdir (output_dir, S_IRUSR | S_IWUSR | S_IXUSR + | S_IRGRP | S_IWGRP | S_IXGRP + | S_IROTH | S_IWOTH | S_IXOTH) < 0) + { + error (0, errno, _("failed to create directory \"%s\""), output_dir); + free (output_dir); + goto quit2; + } + + output_file = + xconcatenated_filename (output_dir, resource_name, ".resources.dll"); + + free (output_dir); + } + + /* Compute the class name. This code must be kept consistent with intl.cs, + function InstantiateResourceSet(). */ + { + char *class_name_part1 = construct_class_name (resource_name); + char *p; + + class_name = + XNMALLOC (strlen (class_name_part1) + 1 + strlen (culture_name) + 1, char); + sprintf (class_name, "%s_%s", class_name_part1, culture_name); + for (p = class_name + strlen (class_name_part1) + 1; *p != '\0'; p++) + if (*p == '-') + *p = '_'; + free (class_name_part1); + } + + /* Compute the temporary C# file name. It must end in ".cs", so that + the C# compiler recognizes that it is C# source code. */ + csharp_file_name = + xconcatenated_filename (tmpdir->dir_name, "resset.cs", NULL); + + /* Create the C# file. */ + register_temp_file (tmpdir, csharp_file_name); + csharp_file = fopen_temp (csharp_file_name, "w"); + if (csharp_file == NULL) + { + error (0, errno, _("failed to create \"%s\""), csharp_file_name); + unregister_temp_file (tmpdir, csharp_file_name); + goto quit3; + } + + write_csharp_code (csharp_file, culture_name, class_name, mlp); + + if (fwriteerror_temp (csharp_file)) + { + error (0, errno, _("error while writing \"%s\" file"), csharp_file_name); + goto quit3; + } + + /* Make it possible to override the .dll location. This is + necessary for running the testsuite before "make install". */ + gettextlibdir = getenv ("GETTEXTCSHARPLIBDIR"); + if (gettextlibdir == NULL || gettextlibdir[0] == '\0') + gettextlibdir = relocate (LIBDIR); + + /* Compile the C# file to a .dll file. */ + csharp_sources[0] = csharp_file_name; + libdirs[0] = gettextlibdir; + libraries[0] = "GNU.Gettext"; + if (compile_csharp_class (csharp_sources, 1, libdirs, 1, libraries, 1, + output_file, true, false, verbose > 0)) + { + if (!verbose) + error (0, 0, _("compilation of C# class failed, please try --verbose")); + else + error (0, 0, _("compilation of C# class failed")); + goto quit3; + } + + retval = 0; + + quit3: + free (csharp_file_name); + free (class_name); + free (output_file); + quit2: + free (culture_name); + cleanup_temp_dir (tmpdir); + quit1: + return retval; +} diff --git a/gettext-tools/src/write-csharp.h b/gettext-tools/src/write-csharp.h new file mode 100644 index 0000000..55b71fb --- /dev/null +++ b/gettext-tools/src/write-csharp.h @@ -0,0 +1,35 @@ +/* Writing C# satellite assemblies. + Copyright (C) 2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_CSHARP_H +#define _WRITE_CSHARP_H + +#include "message.h" + +/* Write a C# satellite assembly DLL file. mlp is a list containing the + messages to be output. resource_name is the name of the resource + (with dot separators), locale_name is the locale name (with underscore + separators) or NULL, directory is the base directory. + Return 0 if ok, nonzero on error. */ +extern int + msgdomain_write_csharp (message_list_ty *mlp, + const char *canon_encoding, + const char *resource_name, + const char *locale_name, + const char *directory); + +#endif /* _WRITE_CSHARP_H */ diff --git a/gettext-tools/src/write-desktop.c b/gettext-tools/src/write-desktop.c new file mode 100644 index 0000000..dd3fb60 --- /dev/null +++ b/gettext-tools/src/write-desktop.c @@ -0,0 +1,225 @@ +/* Writing Desktop Entry files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014 Free Software Foundation, Inc. + This file was written by Daiki Ueno . + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "write-desktop.h" + +#include +#include +#include +#include +#include "error.h" +#include "msgl-iconv.h" +#include "po-charset.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-desktop.h" +#include "fwriteerror.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(str) gettext (str) + +typedef struct msgfmt_desktop_reader_ty msgfmt_desktop_reader_ty; +struct msgfmt_desktop_reader_ty +{ + DESKTOP_READER_TY + string_list_ty *languages; + message_list_ty **messages; + hash_table *keywords; + FILE *output_file; +}; + +static void +msgfmt_desktop_handle_group (struct desktop_reader_ty *reader, + const char *group) +{ + msgfmt_desktop_reader_ty *msgfmt_reader = (msgfmt_desktop_reader_ty *) reader; + + fprintf (msgfmt_reader->output_file, "[%s]\n", group); +} + +static void +msgfmt_desktop_handle_pair (desktop_reader_ty *reader, + lex_pos_ty *key_pos, + const char *key, + const char *locale, + const char *value) +{ + msgfmt_desktop_reader_ty *msgfmt_reader = (msgfmt_desktop_reader_ty *) reader; + void *keyword_value; + + if (!locale) + { + /* Write translated pair, if any. */ + if (hash_find_entry (msgfmt_reader->keywords, key, strlen (key), + &keyword_value) == 0) + { + bool is_list = (bool) keyword_value; + char *unescaped = desktop_unescape_string (value, is_list); + size_t i; + + for (i = 0; i < msgfmt_reader->languages->nitems; i++) + { + const char *language = msgfmt_reader->languages->item[i]; + message_list_ty *mlp = msgfmt_reader->messages[i]; + message_ty *mp; + + mp = message_list_search (mlp, NULL, unescaped); + if (mp && *mp->msgstr != '\0') + { + char *escaped; + + escaped = desktop_escape_string (mp->msgstr, is_list); + fprintf (msgfmt_reader->output_file, + "%s[%s]=%s\n", + key, language, escaped); + free (escaped); + } + } + free (unescaped); + } + + /* Write untranslated pair. */ + fprintf (msgfmt_reader->output_file, "%s=%s\n", key, value); + } + else + /* Preserve already translated pair. */ + fprintf (msgfmt_reader->output_file, "%s[%s]=%s\n", key, locale, value); +} + +static void +msgfmt_desktop_handle_comment (struct desktop_reader_ty *reader, const char *s) +{ + msgfmt_desktop_reader_ty *msgfmt_reader = (msgfmt_desktop_reader_ty *) reader; + + fputc ('#', msgfmt_reader->output_file); + fputs (s, msgfmt_reader->output_file); + fputc ('\n', msgfmt_reader->output_file); +} + +static void +msgfmt_desktop_handle_blank (struct desktop_reader_ty *reader, const char *s) +{ + msgfmt_desktop_reader_ty *msgfmt_reader = (msgfmt_desktop_reader_ty *) reader; + + fputs (s, msgfmt_reader->output_file); + fputc ('\n', msgfmt_reader->output_file); +} + +desktop_reader_class_ty msgfmt_methods = + { + sizeof (msgfmt_desktop_reader_ty), + NULL, + NULL, + msgfmt_desktop_handle_group, + msgfmt_desktop_handle_pair, + msgfmt_desktop_handle_comment, + msgfmt_desktop_handle_blank + }; + +int +msgdomain_write_desktop_bulk (string_list_ty *languages, + message_list_ty **messages, + const char *template_file_name, + hash_table *keywords, + const char *file_name) +{ + desktop_reader_ty *reader; + msgfmt_desktop_reader_ty *msgfmt_reader; + FILE *template_file; + + reader = desktop_reader_alloc (&msgfmt_methods); + msgfmt_reader = (msgfmt_desktop_reader_ty *) reader; + + msgfmt_reader->languages = languages; + msgfmt_reader->messages = messages; + msgfmt_reader->keywords = keywords; + + if (strcmp (file_name, "-") == 0) + msgfmt_reader->output_file = stdout; + else + { + msgfmt_reader->output_file = fopen (file_name, "w"); + if (msgfmt_reader->output_file == NULL) + { + desktop_reader_free (reader); + error (EXIT_SUCCESS, + errno, _("error while opening \"%s\" for writing"), + file_name); + return 1; + } + } + + template_file = fopen (template_file_name, "r"); + if (template_file == NULL) + { + desktop_reader_free (reader); + error (EXIT_SUCCESS, + errno, _("error while opening \"%s\" for reading"), + template_file_name); + return 1; + } + + desktop_parse (reader, template_file, template_file_name, template_file_name); + + /* Make sure nothing went wrong. */ + if (fwriteerror (msgfmt_reader->output_file)) + error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"), + file_name); + + desktop_reader_free (reader); + + return 0; +} + +int +msgdomain_write_desktop (message_list_ty *mlp, + const char *canon_encoding, + const char *locale_name, + const char *template_file_name, + hash_table *keywords, + const char *file_name) +{ + string_list_ty *languages; + message_list_ty **messages; + int retval; + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + + languages = string_list_alloc (); + string_list_append (languages, locale_name); + + messages = XNMALLOC (1, message_list_ty *); + messages[0] = mlp; + + retval = msgdomain_write_desktop_bulk (languages, + messages, + template_file_name, + keywords, + file_name); + + string_list_free (languages); + free (messages); + + return retval; +} diff --git a/gettext-tools/src/write-desktop.h b/gettext-tools/src/write-desktop.h new file mode 100644 index 0000000..028b441 --- /dev/null +++ b/gettext-tools/src/write-desktop.h @@ -0,0 +1,51 @@ +/* Reading Desktop Entry files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014 Free Software Foundation, Inc. + This file was written by Daiki Ueno . + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_DESKTOP_H +#define _WRITE_DESKTOP_H + +#include "message.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Write a Desktop Entry file. mlp is a list containing the messages + to be output. locale_name is the locale name. template_file_name + is the template file. file_name is the output file. Return 0 if + ok, nonzero on error. */ +extern int + msgdomain_write_desktop (message_list_ty *mlp, + const char *canon_encoding, + const char *locale_name, + const char *template_file_name, + hash_table *keywords, + const char *file_name); + +extern int + msgdomain_write_desktop_bulk (string_list_ty *languages, + message_list_ty **messages, + const char *template_file_name, + hash_table *keywords, + const char *file_name); + +#ifdef __cplusplus +} +#endif + + +#endif /* _WRITE_DESKTOP_H */ diff --git a/gettext-tools/src/write-java.c b/gettext-tools/src/write-java.c new file mode 100644 index 0000000..9cf4054 --- /dev/null +++ b/gettext-tools/src/write-java.c @@ -0,0 +1,1234 @@ +/* Writing Java ResourceBundles. + Copyright (C) 2001-2003, 2005-2010 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +/* Specification. */ +#include "write-java.h" + +#include +#include +#include +#include +#include +#include + +#include +#if !defined S_ISDIR && defined S_IFDIR +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !S_IRUSR && S_IREAD +# define S_IRUSR S_IREAD +#endif +#if !S_IRUSR +# define S_IRUSR 00400 +#endif +#if !S_IWUSR && S_IWRITE +# define S_IWUSR S_IWRITE +#endif +#if !S_IWUSR +# define S_IWUSR 00200 +#endif +#if !S_IXUSR && S_IEXEC +# define S_IXUSR S_IEXEC +#endif +#if !S_IXUSR +# define S_IXUSR 00100 +#endif + +#include "c-ctype.h" +#include "error.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "javacomp.h" +#include "message.h" +#include "msgfmt.h" +#include "msgl-iconv.h" +#include "plural-exp.h" +#include "po-charset.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "minmax.h" +#include "concat-filename.h" +#include "fwriteerror.h" +#include "clean-temp.h" +#include "unistr.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Check that the resource name is a valid Java class name. To simplify + things, we allow only ASCII characters in the class name. + Return the number of dots in the class name, or -1 if not OK. */ +static int +check_resource_name (const char *name) +{ + int ndots = 0; + const char *p = name; + + for (;;) + { + /* First character, see Character.isJavaIdentifierStart. */ + if (!(c_isalpha (*p) || (*p == '$') || (*p == '_'))) + return -1; + /* Following characters, see Character.isJavaIdentifierPart. */ + do + p++; + while (c_isalpha (*p) || (*p == '$') || (*p == '_') || c_isdigit (*p)); + if (*p == '\0') + break; + if (*p != '.') + return -1; + p++; + ndots++; + } + return ndots; +} + + +/* Return the Java hash code of a string mod 2^31. + The Java String.hashCode() function returns the same values across + Java implementations. + (See http://www.javasoft.com/docs/books/jls/clarify.html) + It returns a signed 32-bit integer. We add a mod 2^31 afterwards; + this removes one bit but greatly simplifies the following "mod hash_size" + and "mod (hash_size - 2)" operations. */ +static unsigned int +string_hashcode (const char *str) +{ + const char *str_limit = str + strlen (str); + int hash = 0; + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + if (uc < 0x10000) + /* Single UCS-2 'char'. */ + hash = 31 * hash + uc; + else + { + /* UTF-16 surrogate: two 'char's. */ + ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10); + ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff); + hash = 31 * hash + uc1; + hash = 31 * hash + uc2; + } + } + return hash & 0x7fffffff; +} + + +/* Return the Java hash code of a (msgctxt, msgid) pair mod 2^31. */ +static unsigned int +msgid_hashcode (const char *msgctxt, const char *msgid) +{ + if (msgctxt == NULL) + return string_hashcode (msgid); + else + { + size_t msgctxt_len = strlen (msgctxt); + size_t msgid_len = strlen (msgid); + size_t combined_len = msgctxt_len + 1 + msgid_len; + char *combined; + unsigned int result; + + combined = (char *) xmalloca (combined_len); + memcpy (combined, msgctxt, msgctxt_len); + combined[msgctxt_len] = MSGCTXT_SEPARATOR; + memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1); + + result = string_hashcode (combined); + + freea (combined); + + return result; + } +} + + +/* Compute a good hash table size for the given set of msgids. */ +static unsigned int +compute_hashsize (message_list_ty *mlp, bool *collisionp) +{ + /* This is an O(n^2) algorithm, but should be sufficient because few + programs have more than 1000 messages in a single domain. */ +#define XXN 3 /* can be tweaked */ +#define XXS 3 /* can be tweaked */ + unsigned int n = mlp->nitems; + unsigned int *hashcodes = + (unsigned int *) xmalloca (n * sizeof (unsigned int)); + unsigned int hashsize; + unsigned int best_hashsize; + unsigned int best_score; + size_t j; + + for (j = 0; j < n; j++) + hashcodes[j] = msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid); + + /* Try all numbers between n and 3*n. The score depends on the size of the + table -- the smaller the better -- and the number of collision lookups, + i.e. total number of times that 1 + (hashcode % (hashsize - 2)) + is added to the index during lookup. If there are collisions, only odd + hashsize values are allowed. */ + best_hashsize = 0; + best_score = UINT_MAX; + for (hashsize = n; hashsize <= XXN * n; hashsize++) + { + char *bitmap; + unsigned int score; + + /* Premature end of the loop if all future scores are known to be + larger than the already reached best_score. This relies on the + ascending loop and on the fact that score >= hashsize. */ + if (hashsize >= best_score) + break; + + bitmap = XNMALLOC (hashsize, char); + memset (bitmap, 0, hashsize); + + score = 0; + for (j = 0; j < n; j++) + { + unsigned int idx = hashcodes[j] % hashsize; + + if (bitmap[idx] != 0) + { + /* Collision. Cannot deal with it if hashsize is even. */ + if ((hashsize % 2) == 0) + /* Try next hashsize. */ + goto bad_hashsize; + else + { + unsigned int idx0 = idx; + unsigned int incr = 1 + (hashcodes[j] % (hashsize - 2)); + score += 2; /* Big penalty for the additional division */ + do + { + score++; /* Small penalty for each loop round */ + idx += incr; + if (idx >= hashsize) + idx -= hashsize; + if (idx == idx0) + /* Searching for a hole, we performed a whole round + across the table. This happens particularly + frequently if gcd(hashsize,incr) > 1. Try next + hashsize. */ + goto bad_hashsize; + } + while (bitmap[idx] != 0); + } + } + bitmap[idx] = 1; + } + + /* Big hashsize also gives a penalty. */ + score = XXS * score + hashsize; + + /* If for any incr between 1 and hashsize - 2, an whole round + (idx0, idx0 + incr, ...) is occupied, and the lookup function + must deal with collisions, then some inputs would lead to + an endless loop in the lookup function. */ + if (score > hashsize) + { + unsigned int incr; + + /* Since the set { idx0, idx0 + incr, ... } depends only on idx0 + and gcd(hashsize,incr), we only need to conside incr that + divides hashsize. */ + for (incr = 1; incr <= hashsize / 2; incr++) + if ((hashsize % incr) == 0) + { + unsigned int idx0; + + for (idx0 = 0; idx0 < incr; idx0++) + { + bool full = true; + unsigned int idx; + + for (idx = idx0; idx < hashsize; idx += incr) + if (bitmap[idx] == 0) + { + full = false; + break; + } + if (full) + /* A whole round is occupied. */ + goto bad_hashsize; + } + } + } + + if (false) + bad_hashsize: + score = UINT_MAX; + + free (bitmap); + + if (score < best_score) + { + best_score = score; + best_hashsize = hashsize; + } + } + if (best_hashsize == 0 || best_score < best_hashsize) + abort (); + + freea (hashcodes); + + /* There are collisions if and only if best_score > best_hashsize. */ + *collisionp = (best_score > best_hashsize); + return best_hashsize; +} + + +struct table_item { unsigned int index; message_ty *mp; }; + +static int +compare_index (const void *pval1, const void *pval2) +{ + return (int)((const struct table_item *) pval1)->index + - (int)((const struct table_item *) pval2)->index; +} + +/* Compute the list of messages and table indices, sorted according to the + indices. */ +static struct table_item * +compute_table_items (message_list_ty *mlp, unsigned int hashsize) +{ + unsigned int n = mlp->nitems; + struct table_item *arr = XNMALLOC (n, struct table_item); + char *bitmap; + size_t j; + + bitmap = XNMALLOC (hashsize, char); + memset (bitmap, 0, hashsize); + + for (j = 0; j < n; j++) + { + unsigned int hashcode = + msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid); + unsigned int idx = hashcode % hashsize; + + if (bitmap[idx] != 0) + { + unsigned int incr = 1 + (hashcode % (hashsize - 2)); + do + { + idx += incr; + if (idx >= hashsize) + idx -= hashsize; + } + while (bitmap[idx] != 0); + } + bitmap[idx] = 1; + + arr[j].index = idx; + arr[j].mp = mlp->item[j]; + } + + free (bitmap); + + qsort (arr, n, sizeof (arr[0]), compare_index); + + return arr; +} + + +/* Write a string in Java Unicode notation to the given stream. */ +static void +write_java_string (FILE *stream, const char *str) +{ + static const char hexdigit[] = "0123456789abcdef"; + const char *str_limit = str + strlen (str); + + fprintf (stream, "\""); + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + if (uc < 0x10000) + { + /* Single UCS-2 'char'. */ + if (uc == 0x000a) + fprintf (stream, "\\n"); + else if (uc == 0x000d) + fprintf (stream, "\\r"); + else if (uc == 0x0022) + fprintf (stream, "\\\""); + else if (uc == 0x005c) + fprintf (stream, "\\\\"); + else if (uc >= 0x0020 && uc < 0x007f) + fprintf (stream, "%c", (int) uc); + else + fprintf (stream, "\\u%c%c%c%c", + hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f], + hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]); + } + else + { + /* UTF-16 surrogate: two 'char's. */ + ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10); + ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff); + fprintf (stream, "\\u%c%c%c%c", + hexdigit[(uc1 >> 12) & 0x0f], hexdigit[(uc1 >> 8) & 0x0f], + hexdigit[(uc1 >> 4) & 0x0f], hexdigit[uc1 & 0x0f]); + fprintf (stream, "\\u%c%c%c%c", + hexdigit[(uc2 >> 12) & 0x0f], hexdigit[(uc2 >> 8) & 0x0f], + hexdigit[(uc2 >> 4) & 0x0f], hexdigit[uc2 & 0x0f]); + } + } + fprintf (stream, "\""); +} + + +/* Write a (msgctxt, msgid) pair as a string in Java Unicode notation to the + given stream. */ +static void +write_java_msgid (FILE *stream, message_ty *mp) +{ + const char *msgctxt = mp->msgctxt; + const char *msgid = mp->msgid; + + if (msgctxt == NULL) + write_java_string (stream, msgid); + else + { + size_t msgctxt_len = strlen (msgctxt); + size_t msgid_len = strlen (msgid); + size_t combined_len = msgctxt_len + 1 + msgid_len; + char *combined; + + combined = (char *) xmalloca (combined_len); + memcpy (combined, msgctxt, msgctxt_len); + combined[msgctxt_len] = MSGCTXT_SEPARATOR; + memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1); + + write_java_string (stream, combined); + + freea (combined); + } +} + + +/* Write Java code that returns the value for a message. If the message + has plural forms, it is an expression of type String[], otherwise it is + an expression of type String. */ +static void +write_java_msgstr (FILE *stream, message_ty *mp) +{ + if (mp->msgid_plural != NULL) + { + bool first; + const char *p; + + fprintf (stream, "new java.lang.String[] { "); + for (p = mp->msgstr, first = true; + p < mp->msgstr + mp->msgstr_len; + p += strlen (p) + 1, first = false) + { + if (!first) + fprintf (stream, ", "); + write_java_string (stream, p); + } + fprintf (stream, " }"); + } + else + { + if (mp->msgstr_len != strlen (mp->msgstr) + 1) + abort (); + + write_java_string (stream, mp->msgstr); + } +} + + +/* Writes the body of the function which returns the local value for a key + named 'msgid'. */ +static void +write_lookup_code (FILE *stream, unsigned int hashsize, bool collisions) +{ + fprintf (stream, " int hash_val = msgid.hashCode() & 0x7fffffff;\n"); + fprintf (stream, " int idx = (hash_val %% %d) << 1;\n", hashsize); + if (collisions) + { + fprintf (stream, " {\n"); + fprintf (stream, " java.lang.Object found = table[idx];\n"); + fprintf (stream, " if (found == null)\n"); + fprintf (stream, " return null;\n"); + fprintf (stream, " if (msgid.equals(found))\n"); + fprintf (stream, " return table[idx + 1];\n"); + fprintf (stream, " }\n"); + fprintf (stream, " int incr = ((hash_val %% %d) + 1) << 1;\n", + hashsize - 2); + fprintf (stream, " for (;;) {\n"); + fprintf (stream, " idx += incr;\n"); + fprintf (stream, " if (idx >= %d)\n", 2 * hashsize); + fprintf (stream, " idx -= %d;\n", 2 * hashsize); + fprintf (stream, " java.lang.Object found = table[idx];\n"); + fprintf (stream, " if (found == null)\n"); + fprintf (stream, " return null;\n"); + fprintf (stream, " if (msgid.equals(found))\n"); + fprintf (stream, " return table[idx + 1];\n"); + fprintf (stream, " }\n"); + } + else + { + fprintf (stream, " java.lang.Object found = table[idx];\n"); + fprintf (stream, " if (found != null && msgid.equals(found))\n"); + fprintf (stream, " return table[idx + 1];\n"); + fprintf (stream, " return null;\n"); + } +} + + +/* Tests whether a plural expression, evaluated according to the C rules, + can only produce the values 0 and 1. */ +static bool +is_expression_boolean (struct expression *exp) +{ + switch (exp->operation) + { + case var: + case mult: + case divide: + case module: + case plus: + case minus: + return false; + case lnot: + case less_than: + case greater_than: + case less_or_equal: + case greater_or_equal: + case equal: + case not_equal: + case land: + case lor: + return true; + case num: + return (exp->val.num == 0 || exp->val.num == 1); + case qmop: + return is_expression_boolean (exp->val.args[1]) + && is_expression_boolean (exp->val.args[2]); + default: + abort (); + } +} + + +/* Write Java code that evaluates a plural expression according to the C rules. + The variable is called 'n'. */ +static void +write_java_expression (FILE *stream, const struct expression *exp, bool as_boolean) +{ + /* We use parentheses everywhere. This frees us from tracking the priority + of arithmetic operators. */ + if (as_boolean) + { + /* Emit a Java expression of type 'boolean'. */ + switch (exp->operation) + { + case num: + fprintf (stream, "%s", exp->val.num ? "true" : "false"); + return; + case lnot: + fprintf (stream, "(!"); + write_java_expression (stream, exp->val.args[0], true); + fprintf (stream, ")"); + return; + case less_than: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " < "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case greater_than: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " > "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case less_or_equal: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " <= "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case greater_or_equal: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " >= "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case equal: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " == "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case not_equal: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " != "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case land: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], true); + fprintf (stream, " && "); + write_java_expression (stream, exp->val.args[1], true); + fprintf (stream, ")"); + return; + case lor: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], true); + fprintf (stream, " || "); + write_java_expression (stream, exp->val.args[1], true); + fprintf (stream, ")"); + return; + case qmop: + if (is_expression_boolean (exp->val.args[1]) + && is_expression_boolean (exp->val.args[2])) + { + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], true); + fprintf (stream, " ? "); + write_java_expression (stream, exp->val.args[1], true); + fprintf (stream, " : "); + write_java_expression (stream, exp->val.args[2], true); + fprintf (stream, ")"); + return; + } + /*FALLTHROUGH*/ + case var: + case mult: + case divide: + case module: + case plus: + case minus: + fprintf (stream, "("); + write_java_expression (stream, exp, false); + fprintf (stream, " != 0)"); + return; + default: + abort (); + } + } + else + { + /* Emit a Java expression of type 'long'. */ + switch (exp->operation) + { + case var: + fprintf (stream, "n"); + return; + case num: + fprintf (stream, "%lu", exp->val.num); + return; + case mult: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " * "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case divide: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " / "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case module: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " %% "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case plus: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " + "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case minus: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], false); + fprintf (stream, " - "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, ")"); + return; + case qmop: + fprintf (stream, "("); + write_java_expression (stream, exp->val.args[0], true); + fprintf (stream, " ? "); + write_java_expression (stream, exp->val.args[1], false); + fprintf (stream, " : "); + write_java_expression (stream, exp->val.args[2], false); + fprintf (stream, ")"); + return; + case lnot: + case less_than: + case greater_than: + case less_or_equal: + case greater_or_equal: + case equal: + case not_equal: + case land: + case lor: + fprintf (stream, "("); + write_java_expression (stream, exp, true); + fprintf (stream, " ? 1 : 0)"); + return; + default: + abort (); + } + } +} + + +/* Write the Java initialization statements for the Java 1.1.x case, + for items j, start_index <= j < end_index. */ +static void +write_java1_init_statements (FILE *stream, message_list_ty *mlp, + size_t start_index, size_t end_index) +{ + size_t j; + + for (j = start_index; j < end_index; j++) + { + fprintf (stream, " t.put("); + write_java_msgid (stream, mlp->item[j]); + fprintf (stream, ","); + write_java_msgstr (stream, mlp->item[j]); + fprintf (stream, ");\n"); + } +} + + +/* Write the Java initialization statements for the Java 2 case, + for items j, start_index <= j < end_index. */ +static void +write_java2_init_statements (FILE *stream, message_list_ty *mlp, + const struct table_item *table_items, + size_t start_index, size_t end_index) +{ + size_t j; + + for (j = start_index; j < end_index; j++) + { + const struct table_item *ti = &table_items[j]; + + fprintf (stream, " t[%d] = ", 2 * ti->index); + write_java_msgid (stream, ti->mp); + fprintf (stream, ";\n"); + fprintf (stream, " t[%d] = ", 2 * ti->index + 1); + write_java_msgstr (stream, ti->mp); + fprintf (stream, ";\n"); + } +} + + +/* Write the Java code for the ResourceBundle subclass to the given stream. + Note that we use fully qualified class names and no "import" statements, + because applications can have their own classes called X.Y.ResourceBundle + or X.Y.String. */ +static void +write_java_code (FILE *stream, const char *class_name, message_list_ty *mlp, + bool assume_java2) +{ + const char *last_dot; + unsigned int plurals; + size_t j; + + fprintf (stream, + "/* Automatically generated by GNU msgfmt. Do not modify! */\n"); + last_dot = strrchr (class_name, '.'); + if (last_dot != NULL) + { + fprintf (stream, "package "); + fwrite (class_name, 1, last_dot - class_name, stream); + fprintf (stream, ";\npublic class %s", last_dot + 1); + } + else + fprintf (stream, "public class %s", class_name); + fprintf (stream, " extends java.util.ResourceBundle {\n"); + + /* Determine whether there are plural messages. */ + plurals = 0; + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgid_plural != NULL) + plurals++; + + if (assume_java2) + { + unsigned int hashsize; + bool collisions; + struct table_item *table_items; + const char *table_eltype; + + /* Determine the hash table size and whether it leads to collisions. */ + hashsize = compute_hashsize (mlp, &collisions); + + /* Determines which indices in the table contain a message. The others + are null. */ + table_items = compute_table_items (mlp, hashsize); + + /* Emit the table of pairs (msgid, msgstr). If there are plurals, + it is of type Object[], otherwise of type String[]. We use a static + code block because that makes less code: The Java compilers also + generate code for the 'null' entries, which is dumb. */ + table_eltype = (plurals ? "java.lang.Object" : "java.lang.String"); + fprintf (stream, " private static final %s[] table;\n", table_eltype); + { + /* With the Sun javac compiler, each assignment takes 5 to 8 bytes + of bytecode, therefore for each message, up to 16 bytes are needed. + Since the bytecode of every method, including the method + that contains the static initializers, is limited to 64 KB, only ca, + 65536 / 16 = 4096 messages can be initialized in a single method. + Account for other Java compilers and for plurals by limiting it to + 1000. */ + const size_t max_items_per_method = 1000; + + if (mlp->nitems > max_items_per_method) + { + unsigned int k; + size_t start_j; + size_t end_j; + + for (k = 0, start_j = 0, end_j = start_j + max_items_per_method; + start_j < mlp->nitems; + k++, start_j = end_j, end_j = start_j + max_items_per_method) + { + fprintf (stream, " static void clinit_part_%u (%s[] t) {\n", + k, table_eltype); + write_java2_init_statements (stream, mlp, table_items, + start_j, MIN (end_j, mlp->nitems)); + fprintf (stream, " }\n"); + } + } + fprintf (stream, " static {\n"); + fprintf (stream, " %s[] t = new %s[%d];\n", table_eltype, + table_eltype, 2 * hashsize); + if (mlp->nitems > max_items_per_method) + { + unsigned int k; + size_t start_j; + + for (k = 0, start_j = 0; + start_j < mlp->nitems; + k++, start_j += max_items_per_method) + fprintf (stream, " clinit_part_%u(t);\n", k); + } + else + write_java2_init_statements (stream, mlp, table_items, + 0, mlp->nitems); + fprintf (stream, " table = t;\n"); + fprintf (stream, " }\n"); + } + + /* Emit the msgid_plural strings. Only used by msgunfmt. */ + if (plurals) + { + bool first; + fprintf (stream, " public static final java.lang.String[] get_msgid_plural_table () {\n"); + fprintf (stream, " return new java.lang.String[] { "); + first = true; + for (j = 0; j < mlp->nitems; j++) + { + struct table_item *ti = &table_items[j]; + if (ti->mp->msgid_plural != NULL) + { + if (!first) + fprintf (stream, ", "); + write_java_string (stream, ti->mp->msgid_plural); + first = false; + } + } + fprintf (stream, " };\n"); + fprintf (stream, " }\n"); + } + + if (plurals) + { + /* Emit the lookup function. It is a common subroutine for + handleGetObject and ngettext. */ + fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n"); + write_lookup_code (stream, hashsize, collisions); + fprintf (stream, " }\n"); + } + + /* Emit the handleGetObject function. It is declared abstract in + ResourceBundle. It implements a local version of gettext. */ + fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n"); + if (plurals) + { + fprintf (stream, " java.lang.Object value = lookup(msgid);\n"); + fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n"); + } + else + write_lookup_code (stream, hashsize, collisions); + fprintf (stream, " }\n"); + + /* Emit the getKeys function. It is declared abstract in ResourceBundle. + The inner class is not avoidable. */ + fprintf (stream, " public java.util.Enumeration getKeys () {\n"); + fprintf (stream, " return\n"); + fprintf (stream, " new java.util.Enumeration() {\n"); + fprintf (stream, " private int idx = 0;\n"); + fprintf (stream, " { while (idx < %d && table[idx] == null) idx += 2; }\n", + 2 * hashsize); + fprintf (stream, " public boolean hasMoreElements () {\n"); + fprintf (stream, " return (idx < %d);\n", 2 * hashsize); + fprintf (stream, " }\n"); + fprintf (stream, " public java.lang.Object nextElement () {\n"); + fprintf (stream, " java.lang.Object key = table[idx];\n"); + fprintf (stream, " do idx += 2; while (idx < %d && table[idx] == null);\n", + 2 * hashsize); + fprintf (stream, " return key;\n"); + fprintf (stream, " }\n"); + fprintf (stream, " };\n"); + fprintf (stream, " }\n"); + } + else + { + /* Java 1.1.x uses a different hash function. If compatibility with + this Java version is required, the hash table must be built at run time, + not at compile time. */ + fprintf (stream, " private static final java.util.Hashtable table;\n"); + { + /* With the Sun javac compiler, each 'put' call takes 9 to 11 bytes + of bytecode, therefore for each message, up to 11 bytes are needed. + Since the bytecode of every method, including the method + that contains the static initializers, is limited to 64 KB, only ca, + 65536 / 11 = 5958 messages can be initialized in a single method. + Account for other Java compilers and for plurals by limiting it to + 1500. */ + const size_t max_items_per_method = 1500; + + if (mlp->nitems > max_items_per_method) + { + unsigned int k; + size_t start_j; + size_t end_j; + + for (k = 0, start_j = 0, end_j = start_j + max_items_per_method; + start_j < mlp->nitems; + k++, start_j = end_j, end_j = start_j + max_items_per_method) + { + fprintf (stream, " static void clinit_part_%u (java.util.Hashtable t) {\n", + k); + write_java1_init_statements (stream, mlp, + start_j, MIN (end_j, mlp->nitems)); + fprintf (stream, " }\n"); + } + } + fprintf (stream, " static {\n"); + fprintf (stream, " java.util.Hashtable t = new java.util.Hashtable();\n"); + if (mlp->nitems > max_items_per_method) + { + unsigned int k; + size_t start_j; + + for (k = 0, start_j = 0; + start_j < mlp->nitems; + k++, start_j += max_items_per_method) + fprintf (stream, " clinit_part_%u(t);\n", k); + } + else + write_java1_init_statements (stream, mlp, 0, mlp->nitems); + fprintf (stream, " table = t;\n"); + fprintf (stream, " }\n"); + } + + /* Emit the msgid_plural strings. Only used by msgunfmt. */ + if (plurals) + { + fprintf (stream, " public static final java.util.Hashtable get_msgid_plural_table () {\n"); + fprintf (stream, " java.util.Hashtable p = new java.util.Hashtable();\n"); + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgid_plural != NULL) + { + fprintf (stream, " p.put("); + write_java_msgid (stream, mlp->item[j]); + fprintf (stream, ","); + write_java_string (stream, mlp->item[j]->msgid_plural); + fprintf (stream, ");\n"); + } + fprintf (stream, " return p;\n"); + fprintf (stream, " }\n"); + } + + if (plurals) + { + /* Emit the lookup function. It is a common subroutine for + handleGetObject and ngettext. */ + fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n"); + fprintf (stream, " return table.get(msgid);\n"); + fprintf (stream, " }\n"); + } + + /* Emit the handleGetObject function. It is declared abstract in + ResourceBundle. It implements a local version of gettext. */ + fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n"); + if (plurals) + { + fprintf (stream, " java.lang.Object value = table.get(msgid);\n"); + fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n"); + } + else + fprintf (stream, " return table.get(msgid);\n"); + fprintf (stream, " }\n"); + + /* Emit the getKeys function. It is declared abstract in + ResourceBundle. */ + fprintf (stream, " public java.util.Enumeration getKeys () {\n"); + fprintf (stream, " return table.keys();\n"); + fprintf (stream, " }\n"); + } + + /* Emit the pluralEval function. It is a subroutine for ngettext. */ + if (plurals) + { + message_ty *header_entry; + const struct expression *plural; + unsigned long int nplurals; + + header_entry = message_list_search (mlp, NULL, ""); + extract_plural_expression (header_entry ? header_entry->msgstr : NULL, + &plural, &nplurals); + + fprintf (stream, " public static long pluralEval (long n) {\n"); + fprintf (stream, " return "); + write_java_expression (stream, plural, false); + fprintf (stream, ";\n"); + fprintf (stream, " }\n"); + } + + /* Emit the getParent function. It is a subroutine for ngettext. */ + fprintf (stream, " public java.util.ResourceBundle getParent () {\n"); + fprintf (stream, " return parent;\n"); + fprintf (stream, " }\n"); + + fprintf (stream, "}\n"); +} + + +int +msgdomain_write_java (message_list_ty *mlp, const char *canon_encoding, + const char *resource_name, const char *locale_name, + const char *directory, + bool assume_java2, + bool output_source) +{ + int retval; + struct temp_dir *tmpdir; + int ndots; + char *class_name; + char **subdirs; + char *java_file_name; + FILE *java_file; + const char *java_sources[1]; + const char *source_dir_name; + + /* If no entry for this resource/domain, don't even create the file. */ + if (mlp->nitems == 0) + return 0; + + retval = 1; + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + + if (output_source) + { + tmpdir = NULL; + source_dir_name = directory; + } + else + { + /* Create a temporary directory where we can put the Java file. */ + tmpdir = create_temp_dir ("msg", NULL, false); + if (tmpdir == NULL) + goto quit1; + source_dir_name = tmpdir->dir_name; + } + + /* Assign a default value to the resource name. */ + if (resource_name == NULL) + resource_name = "Messages"; + + /* Prepare the list of subdirectories. */ + ndots = check_resource_name (resource_name); + if (ndots < 0) + { + error (0, 0, _("not a valid Java class name: %s"), resource_name); + goto quit2; + } + + if (locale_name != NULL) + class_name = xasprintf ("%s_%s", resource_name, locale_name); + else + class_name = xstrdup (resource_name); + + subdirs = (ndots > 0 ? (char **) xmalloca (ndots * sizeof (char *)) : NULL); + { + const char *p; + const char *last_dir; + int i; + + last_dir = source_dir_name; + p = resource_name; + for (i = 0; i < ndots; i++) + { + const char *q = strchr (p, '.'); + size_t n = q - p; + char *part = (char *) xmalloca (n + 1); + memcpy (part, p, n); + part[n] = '\0'; + subdirs[i] = xconcatenated_filename (last_dir, part, NULL); + freea (part); + last_dir = subdirs[i]; + p = q + 1; + } + + if (locale_name != NULL) + { + char *suffix = xasprintf ("_%s.java", locale_name); + java_file_name = xconcatenated_filename (last_dir, p, suffix); + free (suffix); + } + else + java_file_name = xconcatenated_filename (last_dir, p, ".java"); + } + + /* If OUTPUT_SOURCE, write the Java file in DIRECTORY and return. */ + if (output_source) + { + int i; + + for (i = 0; i < ndots; i++) + { + if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0) + { + error (0, errno, _("failed to create \"%s\""), subdirs[i]); + goto quit3; + } + } + + java_file = fopen (java_file_name, "w"); + if (java_file == NULL) + { + error (0, errno, _("failed to create \"%s\""), java_file_name); + goto quit3; + } + + write_java_code (java_file, class_name, mlp, assume_java2); + + if (fwriteerror (java_file)) + { + error (0, errno, _("error while writing \"%s\" file"), + java_file_name); + goto quit3; + } + + retval = 0; + goto quit3; + } + + /* Create the subdirectories. This is needed because some older Java + compilers verify that the source of class A.B.C really sits in a + directory whose name ends in /A/B. */ + { + int i; + + for (i = 0; i < ndots; i++) + { + register_temp_subdir (tmpdir, subdirs[i]); + if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0) + { + error (0, errno, _("failed to create \"%s\""), subdirs[i]); + unregister_temp_subdir (tmpdir, subdirs[i]); + goto quit3; + } + } + } + + /* Create the Java file. */ + register_temp_file (tmpdir, java_file_name); + java_file = fopen_temp (java_file_name, "w"); + if (java_file == NULL) + { + error (0, errno, _("failed to create \"%s\""), java_file_name); + unregister_temp_file (tmpdir, java_file_name); + goto quit3; + } + + write_java_code (java_file, class_name, mlp, assume_java2); + + if (fwriteerror_temp (java_file)) + { + error (0, errno, _("error while writing \"%s\" file"), java_file_name); + goto quit3; + } + + /* Compile the Java file to a .class file. + directory must be non-NULL, because when the -d option is omitted, the + Java compilers create the class files in the source file's directory - + which is in a temporary directory in our case. */ + java_sources[0] = java_file_name; + if (compile_java_class (java_sources, 1, NULL, 0, "1.3", "1.1", directory, + true, false, true, verbose > 0)) + { + if (!verbose) + error (0, 0, _("\ +compilation of Java class failed, please try --verbose or set $JAVAC")); + else + error (0, 0, _("\ +compilation of Java class failed, please try to set $JAVAC")); + goto quit3; + } + + retval = 0; + + quit3: + { + int i; + free (java_file_name); + for (i = 0; i < ndots; i++) + free (subdirs[i]); + } + freea (subdirs); + free (class_name); + quit2: + if (tmpdir != NULL) + cleanup_temp_dir (tmpdir); + quit1: + return retval; +} diff --git a/gettext-tools/src/write-java.h b/gettext-tools/src/write-java.h new file mode 100644 index 0000000..4249edb --- /dev/null +++ b/gettext-tools/src/write-java.h @@ -0,0 +1,39 @@ +/* Writing Java ResourceBundles. + Copyright (C) 2001-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_JAVA_H +#define _WRITE_JAVA_H + +#include + +#include "message.h" + +/* Write a Java ResourceBundle class file. mlp is a list containing the + messages to be output. resource_name is the name of the resource + (with dot separators), locale_name is the locale name (with underscore + separators) or NULL, directory is the base directory. + Return 0 if ok, nonzero on error. */ +extern int + msgdomain_write_java (message_list_ty *mlp, + const char *canon_encoding, + const char *resource_name, + const char *locale_name, + const char *directory, + bool assume_java2, + bool output_source); + +#endif /* _WRITE_JAVA_H */ diff --git a/gettext-tools/src/write-mo.c b/gettext-tools/src/write-mo.c new file mode 100644 index 0000000..ce27415 --- /dev/null +++ b/gettext-tools/src/write-mo.c @@ -0,0 +1,817 @@ +/* Writing binary .mo files. + Copyright (C) 1995-1998, 2000-2007 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +/* Specification. */ +#include "write-mo.h" + +#include +#include +#include +#include +#include + +#if HAVE_SYS_PARAM_H +# include +#endif + +/* These two include files describe the binary .mo format. */ +#include "gmo.h" +#include "hash-string.h" + +#include "byteswap.h" +#include "error.h" +#include "hash.h" +#include "message.h" +#include "format.h" +#include "xsize.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "binary-io.h" +#include "fwriteerror.h" +#include "gettext.h" + +#define _(str) gettext (str) + +#define freea(p) /* nothing */ + +/* Usually defined in . */ +#ifndef roundup +# if defined __GNUC__ && __GNUC__ >= 2 +# define roundup(x, y) ({typeof(x) _x = (x); typeof(y) _y = (y); \ + ((_x + _y - 1) / _y) * _y; }) +# else +# define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) +# endif /* GNU CC2 */ +#endif /* roundup */ + + +/* Alignment of strings in resulting .mo file. */ +size_t alignment; + +/* True if writing a .mo file in opposite endianness than the host. */ +bool byteswap; + +/* True if no hash table in .mo is wanted. */ +bool no_hash_table; + + +/* Destructively changes the byte order of a 32-bit value in memory. */ +#define BSWAP32(x) (x) = bswap_32 (x) + + +/* Indices into the strings contained in 'struct pre_message' and + 'struct pre_sysdep_message'. */ +enum +{ + M_ID = 0, /* msgid - the original string */ + M_STR = 1 /* msgstr - the translated string */ +}; + +/* An intermediate data structure representing a 'struct string_desc'. */ +struct pre_string +{ + size_t length; + const char *pointer; +}; + +/* An intermediate data structure representing a message. */ +struct pre_message +{ + struct pre_string str[2]; + const char *id_plural; + size_t id_plural_len; +}; + +static int +compare_id (const void *pval1, const void *pval2) +{ + return strcmp (((struct pre_message *) pval1)->str[M_ID].pointer, + ((struct pre_message *) pval2)->str[M_ID].pointer); +} + + +/* An intermediate data structure representing a 'struct sysdep_segment'. */ +struct pre_sysdep_segment +{ + size_t length; + const char *pointer; +}; + +/* An intermediate data structure representing a 'struct segment_pair'. */ +struct pre_segment_pair +{ + size_t segsize; + const char *segptr; + size_t sysdepref; +}; + +/* An intermediate data structure representing a 'struct sysdep_string'. */ +struct pre_sysdep_string +{ + unsigned int segmentcount; + struct pre_segment_pair segments[1]; +}; + +/* An intermediate data structure representing a message with system dependent + strings. */ +struct pre_sysdep_message +{ + struct pre_sysdep_string *str[2]; + const char *id_plural; + size_t id_plural_len; +}; + +/* Write the message list to the given open file. */ +static void +write_table (FILE *output_file, message_list_ty *mlp) +{ + char **msgctid_arr; + size_t nstrings; + struct pre_message *msg_arr; + size_t n_sysdep_strings; + struct pre_sysdep_message *sysdep_msg_arr; + size_t n_sysdep_segments; + struct pre_sysdep_segment *sysdep_segments; + bool have_outdigits; + int major_revision; + int minor_revision; + bool omit_hash_table; + nls_uint32 hash_tab_size; + struct mo_file_header header; /* Header of the .mo file to be written. */ + size_t header_size; + size_t offset; + struct string_desc *orig_tab; + struct string_desc *trans_tab; + size_t sysdep_tab_offset = 0; + size_t end_offset; + char *null; + size_t j, m; + + /* First pass: Move the static string pairs into an array, for sorting, + and at the same time, compute the segments of the system dependent + strings. */ + msgctid_arr = XNMALLOC (mlp->nitems, char *); + nstrings = 0; + msg_arr = XNMALLOC (mlp->nitems, struct pre_message); + n_sysdep_strings = 0; + sysdep_msg_arr = XNMALLOC (mlp->nitems, struct pre_sysdep_message); + n_sysdep_segments = 0; + sysdep_segments = NULL; + have_outdigits = false; + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + size_t msgctlen; + char *msgctid; + struct interval *intervals[2]; + size_t nintervals[2]; + + /* Concatenate mp->msgctxt and mp->msgid into msgctid. */ + msgctlen = (mp->msgctxt != NULL ? strlen (mp->msgctxt) + 1 : 0); + msgctid = XNMALLOC (msgctlen + strlen (mp->msgid) + 1, char); + if (mp->msgctxt != NULL) + { + memcpy (msgctid, mp->msgctxt, msgctlen - 1); + msgctid[msgctlen - 1] = MSGCTXT_SEPARATOR; + } + strcpy (msgctid + msgctlen, mp->msgid); + msgctid_arr[j] = msgctid; + + intervals[M_ID] = NULL; + nintervals[M_ID] = 0; + intervals[M_STR] = NULL; + nintervals[M_STR] = 0; + + /* Test if mp contains system dependent strings and thus + requires the use of the .mo file minor revision 1. */ + if (possible_format_p (mp->is_format[format_c]) + || possible_format_p (mp->is_format[format_objc])) + { + /* Check whether msgid or msgstr contain ISO C 99 + format string directives. No need to check msgid_plural, because + it is not accessed by the [n]gettext() function family. */ + const char *p_end; + const char *p; + + get_sysdep_c_format_directives (mp->msgid, false, + &intervals[M_ID], &nintervals[M_ID]); + if (msgctlen > 0) + { + struct interval *id_intervals = intervals[M_ID]; + size_t id_nintervals = nintervals[M_ID]; + + if (id_nintervals > 0) + { + unsigned int i; + + for (i = 0; i < id_nintervals; i++) + { + id_intervals[i].startpos += msgctlen; + id_intervals[i].endpos += msgctlen; + } + } + } + + p_end = mp->msgstr + mp->msgstr_len; + for (p = mp->msgstr; p < p_end; p += strlen (p) + 1) + { + struct interval *part_intervals; + size_t part_nintervals; + + get_sysdep_c_format_directives (p, true, + &part_intervals, + &part_nintervals); + if (part_nintervals > 0) + { + size_t d = p - mp->msgstr; + unsigned int i; + + intervals[M_STR] = + (struct interval *) + xrealloc (intervals[M_STR], + (nintervals[M_STR] + part_nintervals) + * sizeof (struct interval)); + for (i = 0; i < part_nintervals; i++) + { + intervals[M_STR][nintervals[M_STR] + i].startpos = + d + part_intervals[i].startpos; + intervals[M_STR][nintervals[M_STR] + i].endpos = + d + part_intervals[i].endpos; + } + nintervals[M_STR] += part_nintervals; + } + } + } + + if (nintervals[M_ID] > 0 || nintervals[M_STR] > 0) + { + /* System dependent string pair. */ + for (m = 0; m < 2; m++) + { + struct pre_sysdep_string *pre = + (struct pre_sysdep_string *) + xmalloc (xsum (sizeof (struct pre_sysdep_string), + xtimes (nintervals[m], + sizeof (struct pre_segment_pair)))); + const char *str; + size_t str_len; + size_t lastpos; + unsigned int i; + + if (m == M_ID) + { + str = msgctid; /* concatenation of mp->msgctxt + mp->msgid */ + str_len = strlen (msgctid) + 1; + } + else + { + str = mp->msgstr; + str_len = mp->msgstr_len; + } + + lastpos = 0; + pre->segmentcount = nintervals[m]; + for (i = 0; i < nintervals[m]; i++) + { + size_t length; + const char *pointer; + size_t r; + + pre->segments[i].segptr = str + lastpos; + pre->segments[i].segsize = intervals[m][i].startpos - lastpos; + + length = intervals[m][i].endpos - intervals[m][i].startpos; + pointer = str + intervals[m][i].startpos; + if (length >= 2 + && pointer[0] == '<' && pointer[length - 1] == '>') + { + /* Skip the '<' and '>' markers. */ + length -= 2; + pointer += 1; + } + + for (r = 0; r < n_sysdep_segments; r++) + if (sysdep_segments[r].length == length + && memcmp (sysdep_segments[r].pointer, pointer, length) + == 0) + break; + if (r == n_sysdep_segments) + { + n_sysdep_segments++; + sysdep_segments = + (struct pre_sysdep_segment *) + xrealloc (sysdep_segments, + n_sysdep_segments + * sizeof (struct pre_sysdep_segment)); + sysdep_segments[r].length = length; + sysdep_segments[r].pointer = pointer; + } + + pre->segments[i].sysdepref = r; + + if (length == 1 && *pointer == 'I') + have_outdigits = true; + + lastpos = intervals[m][i].endpos; + } + pre->segments[i].segptr = str + lastpos; + pre->segments[i].segsize = str_len - lastpos; + pre->segments[i].sysdepref = SEGMENTS_END; + + sysdep_msg_arr[n_sysdep_strings].str[m] = pre; + } + + sysdep_msg_arr[n_sysdep_strings].id_plural = mp->msgid_plural; + sysdep_msg_arr[n_sysdep_strings].id_plural_len = + (mp->msgid_plural != NULL ? strlen (mp->msgid_plural) + 1 : 0); + n_sysdep_strings++; + } + else + { + /* Static string pair. */ + msg_arr[nstrings].str[M_ID].pointer = msgctid; + msg_arr[nstrings].str[M_ID].length = strlen (msgctid) + 1; + msg_arr[nstrings].str[M_STR].pointer = mp->msgstr; + msg_arr[nstrings].str[M_STR].length = mp->msgstr_len; + msg_arr[nstrings].id_plural = mp->msgid_plural; + msg_arr[nstrings].id_plural_len = + (mp->msgid_plural != NULL ? strlen (mp->msgid_plural) + 1 : 0); + nstrings++; + } + + for (m = 0; m < 2; m++) + if (intervals[m] != NULL) + free (intervals[m]); + } + + /* Sort the table according to original string. */ + if (nstrings > 0) + qsort (msg_arr, nstrings, sizeof (struct pre_message), compare_id); + + /* We need major revision 1 if there are system dependent strings that use + "I" because older versions of gettext() crash when this occurs in a .mo + file. Otherwise use major revision 0. */ + major_revision = + (have_outdigits ? MO_REVISION_NUMBER_WITH_SYSDEP_I : MO_REVISION_NUMBER); + + /* We need minor revision 1 if there are system dependent strings. + Otherwise we choose minor revision 0 because it's supported by older + versions of libintl and revision 1 isn't. */ + minor_revision = (n_sysdep_strings > 0 ? 1 : 0); + + /* In minor revision >= 1, the hash table is obligatory. */ + omit_hash_table = (no_hash_table && minor_revision == 0); + + /* This should be explained: + Each string has an associate hashing value V, computed by a fixed + function. To locate the string we use open addressing with double + hashing. The first index will be V % M, where M is the size of the + hashing table. If no entry is found, iterating with a second, + independent hashing function takes place. This second value will + be 1 + V % (M - 2). + The approximate number of probes will be + + for unsuccessful search: (1 - N / M) ^ -1 + for successful search: - (N / M) ^ -1 * ln (1 - N / M) + + where N is the number of keys. + + If we now choose M to be the next prime bigger than 4 / 3 * N, + we get the values + 4 and 1.85 resp. + Because unsuccessful searches are unlikely this is a good value. + Formulas: [Knuth, The Art of Computer Programming, Volume 3, + Sorting and Searching, 1973, Addison Wesley] */ + if (!omit_hash_table) + { + hash_tab_size = next_prime ((mlp->nitems * 4) / 3); + /* Ensure M > 2. */ + if (hash_tab_size <= 2) + hash_tab_size = 3; + } + else + hash_tab_size = 0; + + + /* Second pass: Fill the structure describing the header. At the same time, + compute the sizes and offsets of the non-string parts of the file. */ + + /* Magic number. */ + header.magic = _MAGIC; + /* Revision number of file format. */ + header.revision = (major_revision << 16) + minor_revision; + + header_size = + (minor_revision == 0 + ? offsetof (struct mo_file_header, n_sysdep_segments) + : sizeof (struct mo_file_header)); + offset = header_size; + + /* Number of static string pairs. */ + header.nstrings = nstrings; + + /* Offset of table for original string offsets. */ + header.orig_tab_offset = offset; + offset += nstrings * sizeof (struct string_desc); + orig_tab = XNMALLOC (nstrings, struct string_desc); + + /* Offset of table for translated string offsets. */ + header.trans_tab_offset = offset; + offset += nstrings * sizeof (struct string_desc); + trans_tab = XNMALLOC (nstrings, struct string_desc); + + /* Size of hash table. */ + header.hash_tab_size = hash_tab_size; + /* Offset of hash table. */ + header.hash_tab_offset = offset; + offset += hash_tab_size * sizeof (nls_uint32); + + if (minor_revision >= 1) + { + /* Size of table describing system dependent segments. */ + header.n_sysdep_segments = n_sysdep_segments; + /* Offset of table describing system dependent segments. */ + header.sysdep_segments_offset = offset; + offset += n_sysdep_segments * sizeof (struct sysdep_segment); + + /* Number of system dependent string pairs. */ + header.n_sysdep_strings = n_sysdep_strings; + + /* Offset of table for original sysdep string offsets. */ + header.orig_sysdep_tab_offset = offset; + offset += n_sysdep_strings * sizeof (nls_uint32); + + /* Offset of table for translated sysdep string offsets. */ + header.trans_sysdep_tab_offset = offset; + offset += n_sysdep_strings * sizeof (nls_uint32); + + /* System dependent string descriptors. */ + sysdep_tab_offset = offset; + for (m = 0; m < 2; m++) + for (j = 0; j < n_sysdep_strings; j++) + offset += sizeof (struct sysdep_string) + + sysdep_msg_arr[j].str[m]->segmentcount + * sizeof (struct segment_pair); + } + + end_offset = offset; + + + /* Third pass: Write the non-string parts of the file. At the same time, + compute the offsets of each string, including the proper alignment. */ + + /* Write the header out. */ + if (byteswap) + { + BSWAP32 (header.magic); + BSWAP32 (header.revision); + BSWAP32 (header.nstrings); + BSWAP32 (header.orig_tab_offset); + BSWAP32 (header.trans_tab_offset); + BSWAP32 (header.hash_tab_size); + BSWAP32 (header.hash_tab_offset); + if (minor_revision >= 1) + { + BSWAP32 (header.n_sysdep_segments); + BSWAP32 (header.sysdep_segments_offset); + BSWAP32 (header.n_sysdep_strings); + BSWAP32 (header.orig_sysdep_tab_offset); + BSWAP32 (header.trans_sysdep_tab_offset); + } + } + fwrite (&header, header_size, 1, output_file); + + /* Table for original string offsets. */ + /* Here output_file is at position header.orig_tab_offset. */ + + for (j = 0; j < nstrings; j++) + { + offset = roundup (offset, alignment); + orig_tab[j].length = + msg_arr[j].str[M_ID].length + msg_arr[j].id_plural_len; + orig_tab[j].offset = offset; + offset += orig_tab[j].length; + /* Subtract 1 because of the terminating NUL. */ + orig_tab[j].length--; + } + if (byteswap) + for (j = 0; j < nstrings; j++) + { + BSWAP32 (orig_tab[j].length); + BSWAP32 (orig_tab[j].offset); + } + fwrite (orig_tab, nstrings * sizeof (struct string_desc), 1, output_file); + + /* Table for translated string offsets. */ + /* Here output_file is at position header.trans_tab_offset. */ + + for (j = 0; j < nstrings; j++) + { + offset = roundup (offset, alignment); + trans_tab[j].length = msg_arr[j].str[M_STR].length; + trans_tab[j].offset = offset; + offset += trans_tab[j].length; + /* Subtract 1 because of the terminating NUL. */ + trans_tab[j].length--; + } + if (byteswap) + for (j = 0; j < nstrings; j++) + { + BSWAP32 (trans_tab[j].length); + BSWAP32 (trans_tab[j].offset); + } + fwrite (trans_tab, nstrings * sizeof (struct string_desc), 1, output_file); + + /* Skip this part when no hash table is needed. */ + if (!omit_hash_table) + { + nls_uint32 *hash_tab; + unsigned int j; + + /* Here output_file is at position header.hash_tab_offset. */ + + /* Allocate room for the hashing table to be written out. */ + hash_tab = XNMALLOC (hash_tab_size, nls_uint32); + memset (hash_tab, '\0', hash_tab_size * sizeof (nls_uint32)); + + /* Insert all value in the hash table, following the algorithm described + above. */ + for (j = 0; j < nstrings; j++) + { + nls_uint32 hash_val = hash_string (msg_arr[j].str[M_ID].pointer); + nls_uint32 idx = hash_val % hash_tab_size; + + if (hash_tab[idx] != 0) + { + /* We need the second hashing function. */ + nls_uint32 incr = 1 + (hash_val % (hash_tab_size - 2)); + + do + if (idx >= hash_tab_size - incr) + idx -= hash_tab_size - incr; + else + idx += incr; + while (hash_tab[idx] != 0); + } + + hash_tab[idx] = j + 1; + } + + /* Write the hash table out. */ + if (byteswap) + for (j = 0; j < hash_tab_size; j++) + BSWAP32 (hash_tab[j]); + fwrite (hash_tab, hash_tab_size * sizeof (nls_uint32), 1, output_file); + + free (hash_tab); + } + + if (minor_revision >= 1) + { + struct sysdep_segment *sysdep_segments_tab; + nls_uint32 *sysdep_tab; + size_t stoffset; + unsigned int i; + + /* Here output_file is at position header.sysdep_segments_offset. */ + + sysdep_segments_tab = + XNMALLOC (n_sysdep_segments, struct sysdep_segment); + for (i = 0; i < n_sysdep_segments; i++) + { + offset = roundup (offset, alignment); + /* The "+ 1" accounts for the trailing NUL byte. */ + sysdep_segments_tab[i].length = sysdep_segments[i].length + 1; + sysdep_segments_tab[i].offset = offset; + offset += sysdep_segments_tab[i].length; + } + + if (byteswap) + for (i = 0; i < n_sysdep_segments; i++) + { + BSWAP32 (sysdep_segments_tab[i].length); + BSWAP32 (sysdep_segments_tab[i].offset); + } + fwrite (sysdep_segments_tab, + n_sysdep_segments * sizeof (struct sysdep_segment), 1, + output_file); + + free (sysdep_segments_tab); + + sysdep_tab = XNMALLOC (n_sysdep_strings, nls_uint32); + stoffset = sysdep_tab_offset; + + for (m = 0; m < 2; m++) + { + /* Here output_file is at position + m == M_ID -> header.orig_sysdep_tab_offset, + m == M_STR -> header.trans_sysdep_tab_offset. */ + + for (j = 0; j < n_sysdep_strings; j++) + { + sysdep_tab[j] = stoffset; + stoffset += sizeof (struct sysdep_string) + + sysdep_msg_arr[j].str[m]->segmentcount + * sizeof (struct segment_pair); + } + /* Write the table for original/translated sysdep string offsets. */ + if (byteswap) + for (j = 0; j < n_sysdep_strings; j++) + BSWAP32 (sysdep_tab[j]); + fwrite (sysdep_tab, n_sysdep_strings * sizeof (nls_uint32), 1, + output_file); + } + + free (sysdep_tab); + + /* Here output_file is at position sysdep_tab_offset. */ + + for (m = 0; m < 2; m++) + for (j = 0; j < n_sysdep_strings; j++) + { + struct pre_sysdep_message *msg = &sysdep_msg_arr[j]; + struct pre_sysdep_string *pre = msg->str[m]; + struct sysdep_string *str = + (struct sysdep_string *) + xmalloca (sizeof (struct sysdep_string) + + pre->segmentcount * sizeof (struct segment_pair)); + unsigned int i; + + offset = roundup (offset, alignment); + str->offset = offset; + for (i = 0; i <= pre->segmentcount; i++) + { + str->segments[i].segsize = pre->segments[i].segsize; + str->segments[i].sysdepref = pre->segments[i].sysdepref; + offset += str->segments[i].segsize; + } + if (m == M_ID && msg->id_plural_len > 0) + { + str->segments[pre->segmentcount].segsize += msg->id_plural_len; + offset += msg->id_plural_len; + } + if (byteswap) + { + BSWAP32 (str->offset); + for (i = 0; i <= pre->segmentcount; i++) + { + BSWAP32 (str->segments[i].segsize); + BSWAP32 (str->segments[i].sysdepref); + } + } + fwrite (str, + sizeof (struct sysdep_string) + + pre->segmentcount * sizeof (struct segment_pair), + 1, output_file); + + freea (str); + } + } + + /* Here output_file is at position end_offset. */ + + free (trans_tab); + free (orig_tab); + + + /* Fourth pass: Write the strings. */ + + offset = end_offset; + + /* A few zero bytes for padding. */ + null = (char *) alloca (alignment); + memset (null, '\0', alignment); + + /* Now write the original strings. */ + for (j = 0; j < nstrings; j++) + { + fwrite (null, roundup (offset, alignment) - offset, 1, output_file); + offset = roundup (offset, alignment); + + fwrite (msg_arr[j].str[M_ID].pointer, msg_arr[j].str[M_ID].length, 1, + output_file); + if (msg_arr[j].id_plural_len > 0) + fwrite (msg_arr[j].id_plural, msg_arr[j].id_plural_len, 1, + output_file); + offset += msg_arr[j].str[M_ID].length + msg_arr[j].id_plural_len; + } + + /* Now write the translated strings. */ + for (j = 0; j < nstrings; j++) + { + fwrite (null, roundup (offset, alignment) - offset, 1, output_file); + offset = roundup (offset, alignment); + + fwrite (msg_arr[j].str[M_STR].pointer, msg_arr[j].str[M_STR].length, 1, + output_file); + offset += msg_arr[j].str[M_STR].length; + } + + if (minor_revision >= 1) + { + unsigned int i; + + for (i = 0; i < n_sysdep_segments; i++) + { + fwrite (null, roundup (offset, alignment) - offset, 1, output_file); + offset = roundup (offset, alignment); + + fwrite (sysdep_segments[i].pointer, sysdep_segments[i].length, 1, + output_file); + fwrite (null, 1, 1, output_file); + offset += sysdep_segments[i].length + 1; + } + + for (m = 0; m < 2; m++) + for (j = 0; j < n_sysdep_strings; j++) + { + struct pre_sysdep_message *msg = &sysdep_msg_arr[j]; + struct pre_sysdep_string *pre = msg->str[m]; + + fwrite (null, roundup (offset, alignment) - offset, 1, + output_file); + offset = roundup (offset, alignment); + + for (i = 0; i <= pre->segmentcount; i++) + { + fwrite (pre->segments[i].segptr, pre->segments[i].segsize, 1, + output_file); + offset += pre->segments[i].segsize; + } + if (m == M_ID && msg->id_plural_len > 0) + { + fwrite (msg->id_plural, msg->id_plural_len, 1, output_file); + offset += msg->id_plural_len; + } + + free (pre); + } + } + + freea (null); + for (j = 0; j < mlp->nitems; j++) + free (msgctid_arr[j]); + free (sysdep_msg_arr); + free (msg_arr); + free (msgctid_arr); +} + + +int +msgdomain_write_mo (message_list_ty *mlp, + const char *domain_name, + const char *file_name) +{ + FILE *output_file; + + /* If no entry for this domain don't even create the file. */ + if (mlp->nitems != 0) + { + if (strcmp (domain_name, "-") == 0) + { + output_file = stdout; + SET_BINARY (fileno (output_file)); + } + else + { + output_file = fopen (file_name, "wb"); + if (output_file == NULL) + { + error (0, errno, _("error while opening \"%s\" for writing"), + file_name); + return 1; + } + } + + if (output_file != NULL) + { + write_table (output_file, mlp); + + /* Make sure nothing went wrong. */ + if (fwriteerror (output_file)) + error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"), + file_name); + } + } + + return 0; +} diff --git a/gettext-tools/src/write-mo.h b/gettext-tools/src/write-mo.h new file mode 100644 index 0000000..b1ea8ea --- /dev/null +++ b/gettext-tools/src/write-mo.h @@ -0,0 +1,43 @@ +/* Writing binary .mo files. + Copyright (C) 1995-1998, 2000-2003, 2005-2006 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_MO_H +#define _WRITE_MO_H + +#include +#include + +#include "message.h" + +/* Alignment of strings in resulting .mo file. */ +extern size_t alignment; + +/* True if writing a .mo file in opposite endianness than the host. */ +extern bool byteswap; + +/* True if no hash table in .mo is wanted. */ +extern bool no_hash_table; + +/* Write a GNU mo file. mlp is a list containing the messages to be output. + domain_name is the domain name, file_name is the desired file name. + Return 0 if ok, nonzero on error. */ +extern int + msgdomain_write_mo (message_list_ty *mlp, + const char *domain_name, + const char *file_name); + +#endif /* _WRITE_MO_H */ diff --git a/gettext-tools/src/write-po.c b/gettext-tools/src/write-po.c new file mode 100644 index 0000000..d49a95f --- /dev/null +++ b/gettext-tools/src/write-po.c @@ -0,0 +1,1620 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2010, 2012 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +/* Specification. */ +#include "write-po.h" + +#include +#include +#include +#include +#include + +#if HAVE_ICONV +# include +#endif + +#include "c-ctype.h" +#include "po-charset.h" +#include "format.h" +#include "unilbrk.h" +#include "msgl-ascii.h" +#include "write-catalog.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "c-strstr.h" +#include "ostream.h" +#ifdef GETTEXTDATADIR +# include "styled-ostream.h" +#endif +#include "xvasprintf.h" +#include "po-xerror.h" +#include "gettext.h" + +/* Our regular abbreviation. */ +#define _(str) gettext (str) + +#if HAVE_DECL_PUTC_UNLOCKED +# undef putc +# define putc putc_unlocked +#endif + + +/* =================== Putting together a #, flags line. =================== */ + + +/* Convert IS_FORMAT in the context of programming language LANG to a flag + string for use in #, flags. */ + +const char * +make_format_description_string (enum is_format is_format, const char *lang, + bool debug) +{ + static char result[100]; + + switch (is_format) + { + case possible: + if (debug) + { + sprintf (result, "possible-%s-format", lang); + break; + } + /* FALLTHROUGH */ + case yes_according_to_context: + case yes: + sprintf (result, "%s-format", lang); + break; + case no: + sprintf (result, "no-%s-format", lang); + break; + default: + /* The others have already been filtered out by significant_format_p. */ + abort (); + } + + return result; +} + + +/* Return true if IS_FORMAT is worth mentioning in a #, flags list. */ + +bool +significant_format_p (enum is_format is_format) +{ + return is_format != undecided && is_format != impossible; +} + + +/* Return true if one of IS_FORMAT is worth mentioning in a #, flags list. */ + +static bool +has_significant_format_p (const enum is_format is_format[NFORMATS]) +{ + size_t i; + + for (i = 0; i < NFORMATS; i++) + if (significant_format_p (is_format[i])) + return true; + return false; +} + + +/* Convert a RANGE to a freshly allocated string for use in #, flags. */ + +char * +make_range_description_string (struct argument_range range) +{ + return xasprintf ("range: %d..%d", range.min, range.max); +} + + +/* Convert a wrapping flag DO_WRAP to a string for use in #, flags. */ + +static const char * +make_c_width_description_string (enum is_wrap do_wrap) +{ + const char *result = NULL; + + switch (do_wrap) + { + case yes: + result = "wrap"; + break; + case no: + result = "no-wrap"; + break; + default: + abort (); + } + + return result; +} + + +/* ========================== Styling primitives. ========================== */ + + +/* When compiled in src, enable styling support. + When compiled in libgettextpo, don't enable styling support. */ +#ifdef GETTEXTDATADIR + +/* Return true if the stream is an instance of styled_ostream_t. */ +static inline bool +is_stylable (ostream_t stream) +{ + return IS_INSTANCE (stream, ostream, styled_ostream); +} + +/* Start a run of text belonging to a given CSS class. */ +static void +begin_css_class (ostream_t stream, const char *classname) +{ + if (is_stylable (stream)) + styled_ostream_begin_use_class ((styled_ostream_t) stream, classname); +} + +/* End a run of text belonging to a given CSS class. */ +static void +end_css_class (ostream_t stream, const char *classname) +{ + if (is_stylable (stream)) + styled_ostream_end_use_class ((styled_ostream_t) stream, classname); +} + +#else + +#define is_stylable(stream) false +#define begin_css_class(stream,classname) /* empty */ +#define end_css_class(stream,classname) /* empty */ + +#endif + +/* CSS classes at message level. */ +static const char class_header[] = "header"; +static const char class_translated[] = "translated"; +static const char class_untranslated[] = "untranslated"; +static const char class_fuzzy[] = "fuzzy"; +static const char class_obsolete[] = "obsolete"; + +/* CSS classes describing the parts of a message. */ +static const char class_comment[] = "comment"; +static const char class_translator_comment[] = "translator-comment"; +static const char class_extracted_comment[] = "extracted-comment"; +static const char class_reference_comment[] = "reference-comment"; +static const char class_reference[] = "reference"; +static const char class_flag_comment[] = "flag-comment"; +static const char class_flag[] = "flag"; +static const char class_fuzzy_flag[] = "fuzzy-flag"; +static const char class_previous_comment[] = "previous-comment"; +static const char class_previous[] = "previous"; +static const char class_msgid[] = "msgid"; +static const char class_msgstr[] = "msgstr"; +static const char class_keyword[] = "keyword"; +static const char class_string[] = "string"; + +/* CSS classes for the contents of strings. */ +static const char class_text[] = "text"; +static const char class_escape_sequence[] = "escape-sequence"; +static const char class_format_directive[] = "format-directive"; +static const char class_invalid_format_directive[] = "invalid-format-directive"; +#if 0 +static const char class_added[] = "added"; +static const char class_changed[] = "changed"; +static const char class_removed[] = "removed"; +#endif + +/* Per-character attributes. */ +enum +{ + ATTR_ESCAPE_SEQUENCE = 1 << 0, + /* The following two are exclusive. */ + ATTR_FORMAT_DIRECTIVE = 1 << 1, + ATTR_INVALID_FORMAT_DIRECTIVE = 1 << 2 +}; + + +/* ================ Output parts of a message, as comments. ================ */ + + +/* Output mp->comment as a set of comment lines. */ + +void +message_print_comment (const message_ty *mp, ostream_t stream) +{ + if (mp->comment != NULL) + { + size_t j; + + begin_css_class (stream, class_translator_comment); + + for (j = 0; j < mp->comment->nitems; ++j) + { + const char *s = mp->comment->item[j]; + do + { + const char *e; + ostream_write_str (stream, "#"); + if (*s != '\0') + ostream_write_str (stream, " "); + e = strchr (s, '\n'); + if (e == NULL) + { + ostream_write_str (stream, s); + s = NULL; + } + else + { + ostream_write_mem (stream, s, e - s); + s = e + 1; + } + ostream_write_str (stream, "\n"); + } + while (s != NULL); + } + + end_css_class (stream, class_translator_comment); + } +} + + +/* Output mp->comment_dot as a set of comment lines. */ + +void +message_print_comment_dot (const message_ty *mp, ostream_t stream) +{ + if (mp->comment_dot != NULL) + { + size_t j; + + begin_css_class (stream, class_extracted_comment); + + for (j = 0; j < mp->comment_dot->nitems; ++j) + { + const char *s = mp->comment_dot->item[j]; + ostream_write_str (stream, "#."); + if (*s != '\0') + ostream_write_str (stream, " "); + ostream_write_str (stream, s); + ostream_write_str (stream, "\n"); + } + + end_css_class (stream, class_extracted_comment); + } +} + + +/* Output mp->filepos as a set of comment lines. */ + +static enum filepos_comment_type filepos_comment_type = filepos_comment_full; + +void +message_print_comment_filepos (const message_ty *mp, ostream_t stream, + bool uniforum, size_t page_width) +{ + if (filepos_comment_type != filepos_comment_none + && mp->filepos_count != 0) + { + size_t filepos_count; + lex_pos_ty *filepos; + + begin_css_class (stream, class_reference_comment); + + if (filepos_comment_type == filepos_comment_file) + { + size_t i; + + filepos_count = 0; + filepos = XNMALLOC (mp->filepos_count, lex_pos_ty); + + for (i = 0; i < mp->filepos_count; ++i) + { + lex_pos_ty *pp = &mp->filepos[i]; + size_t j; + + for (j = 0; j < filepos_count; j++) + if (strcmp (filepos[j].file_name, pp->file_name) == 0) + break; + + if (j == filepos_count) + { + filepos[filepos_count].file_name = pp->file_name; + filepos[filepos_count].line_number = (size_t)-1; + filepos_count++; + } + } + } + else + { + filepos = mp->filepos; + filepos_count = mp->filepos_count; + } + + if (uniforum) + { + size_t j; + + for (j = 0; j < filepos_count; ++j) + { + lex_pos_ty *pp = &filepos[j]; + const char *cp = pp->file_name; + char *str; + + while (cp[0] == '.' && cp[1] == '/') + cp += 2; + ostream_write_str (stream, "# "); + begin_css_class (stream, class_reference); + /* There are two Sun formats to choose from: SunOS and + Solaris. Use the Solaris form here. */ + str = xasprintf ("File: %s, line: %ld", + cp, (long) pp->line_number); + ostream_write_str (stream, str); + end_css_class (stream, class_reference); + ostream_write_str (stream, "\n"); + free (str); + } + } + else + { + size_t column; + size_t j; + + ostream_write_str (stream, "#:"); + column = 2; + for (j = 0; j < filepos_count; ++j) + { + lex_pos_ty *pp; + char buffer[21]; + const char *cp; + size_t len; + + pp = &filepos[j]; + cp = pp->file_name; + while (cp[0] == '.' && cp[1] == '/') + cp += 2; + if (filepos_comment_type == filepos_comment_file + /* Some xgettext input formats, like RST, lack line + numbers. */ + || pp->line_number == (size_t)(-1)) + buffer[0] = '\0'; + else + sprintf (buffer, ":%ld", (long) pp->line_number); + len = strlen (cp) + strlen (buffer) + 1; + if (column > 2 && column + len >= page_width) + { + ostream_write_str (stream, "\n#:"); + column = 2; + } + ostream_write_str (stream, " "); + begin_css_class (stream, class_reference); + ostream_write_str (stream, cp); + ostream_write_str (stream, buffer); + end_css_class (stream, class_reference); + column += len; + } + ostream_write_str (stream, "\n"); + } + + if (filepos != mp->filepos) + free (filepos); + + end_css_class (stream, class_reference_comment); + } +} + + +/* Output mp->is_fuzzy, mp->is_format, mp->range, mp->do_wrap as a comment + line. */ + +void +message_print_comment_flags (const message_ty *mp, ostream_t stream, bool debug) +{ + if ((mp->is_fuzzy && mp->msgstr[0] != '\0') + || has_significant_format_p (mp->is_format) + || has_range_p (mp->range) + || mp->do_wrap == no) + { + bool first_flag = true; + size_t i; + + begin_css_class (stream, class_flag_comment); + + ostream_write_str (stream, "#,"); + + /* We don't print the fuzzy flag if the msgstr is empty. This + might be introduced by the user but we want to normalize the + output. */ + if (mp->is_fuzzy && mp->msgstr[0] != '\0') + { + ostream_write_str (stream, " "); + begin_css_class (stream, class_flag); + begin_css_class (stream, class_fuzzy_flag); + ostream_write_str (stream, "fuzzy"); + end_css_class (stream, class_fuzzy_flag); + end_css_class (stream, class_flag); + first_flag = false; + } + + for (i = 0; i < NFORMATS; i++) + if (significant_format_p (mp->is_format[i])) + { + if (!first_flag) + ostream_write_str (stream, ","); + + ostream_write_str (stream, " "); + begin_css_class (stream, class_flag); + ostream_write_str (stream, + make_format_description_string (mp->is_format[i], + format_language[i], + debug)); + end_css_class (stream, class_flag); + first_flag = false; + } + + if (has_range_p (mp->range)) + { + char *string; + + if (!first_flag) + ostream_write_str (stream, ","); + + ostream_write_str (stream, " "); + begin_css_class (stream, class_flag); + string = make_range_description_string (mp->range); + ostream_write_str (stream, string); + free (string); + end_css_class (stream, class_flag); + first_flag = false; + } + + if (mp->do_wrap == no) + { + if (!first_flag) + ostream_write_str (stream, ","); + + ostream_write_str (stream, " "); + begin_css_class (stream, class_flag); + ostream_write_str (stream, + make_c_width_description_string (mp->do_wrap)); + end_css_class (stream, class_flag); + first_flag = false; + } + + ostream_write_str (stream, "\n"); + + end_css_class (stream, class_flag_comment); + } +} + + +/* ========= Some parameters for use by 'msgdomain_list_print_po'. ========= */ + + +/* This variable controls the extent to which the page width applies. + True means it applies to message strings and file reference lines. + False means it applies to file reference lines only. */ +static bool wrap_strings = true; + +void +message_page_width_ignore () +{ + wrap_strings = false; +} + + +/* These three variables control the output style of the message_print + function. Interface functions for them are to be used. */ +static bool indent = false; +static bool uniforum = false; +static bool escape = false; + +void +message_print_style_indent () +{ + indent = true; +} + +void +message_print_style_uniforum () +{ + uniforum = true; +} + +void +message_print_style_escape (bool flag) +{ + escape = flag; +} + +void +message_print_style_filepos (enum filepos_comment_type type) +{ + filepos_comment_type = type; +} + + +/* --add-location argument handling. Return an error indicator. */ +bool +handle_filepos_comment_option (const char *option) +{ + if (option != NULL) + { + if (strcmp (option, "never") == 0 || strcmp (option, "no") == 0) + message_print_style_filepos (filepos_comment_none); + else if (strcmp (option, "full") == 0 || strcmp (option, "yes") == 0) + message_print_style_filepos (filepos_comment_full); + else if (strcmp (option, "file") == 0) + message_print_style_filepos (filepos_comment_file); + else + { + fprintf (stderr, "invalid --add-location argument: %s\n", option); + return true; + } + } + else + /* --add-location is equivalent to --add-location=full. */ + message_print_style_filepos (filepos_comment_full); + return false; +} + + +/* =============== msgdomain_list_print_po() and subroutines. =============== */ + + +/* A version of memcpy optimized for the case n <= 1. */ +static inline void +memcpy_small (void *dst, const void *src, size_t n) +{ + if (n > 0) + { + char *q = (char *) dst; + const char *p = (const char *) src; + + *q = *p; + if (--n > 0) + do *++q = *++p; while (--n > 0); + } +} + + +/* A version of memset optimized for the case n <= 1. */ +static inline void +memset_small (void *dst, char c, size_t n) +{ + if (n > 0) + { + char *p = (char *) dst; + + *p = c; + if (--n > 0) + do *++p = c; while (--n > 0); + } +} + + +static void +wrap (const message_ty *mp, ostream_t stream, + const char *line_prefix, int extra_indent, const char *css_class, + const char *name, const char *value, + enum is_wrap do_wrap, size_t page_width, + const char *charset) +{ + const char *canon_charset; + char *fmtdir; + char *fmtdirattr; + const char *s; + bool first_line; +#if HAVE_ICONV + const char *envval; + iconv_t conv; +#endif + bool weird_cjk; + + canon_charset = po_charset_canonicalize (charset); + +#if HAVE_ICONV + /* The old Solaris/openwin msgfmt and GNU msgfmt <= 0.10.35 don't know + about multibyte encodings, and require a spurious backslash after + every multibyte character whose last byte is 0x5C. Some programs, + like vim, distribute PO files in this broken format. It is important + for such programs that GNU msgmerge continues to support this old + PO file format when the Makefile requests it. */ + envval = getenv ("OLD_PO_FILE_OUTPUT"); + if (envval != NULL && *envval != '\0') + /* Write a PO file in old format, with extraneous backslashes. */ + conv = (iconv_t)(-1); + else + if (canon_charset == NULL) + /* Invalid PO file encoding. */ + conv = (iconv_t)(-1); + else + /* Avoid glibc-2.1 bug with EUC-KR. */ +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \ + && !defined _LIBICONV_VERSION + if (strcmp (canon_charset, "EUC-KR") == 0) + conv = (iconv_t)(-1); + else +# endif + /* Avoid Solaris 2.9 bug with GB2312, EUC-TW, BIG5, BIG5-HKSCS, GBK, + GB18030. */ +# if defined __sun && !defined _LIBICONV_VERSION + if ( strcmp (canon_charset, "GB2312") == 0 + || strcmp (canon_charset, "EUC-TW") == 0 + || strcmp (canon_charset, "BIG5") == 0 + || strcmp (canon_charset, "BIG5-HKSCS") == 0 + || strcmp (canon_charset, "GBK") == 0 + || strcmp (canon_charset, "GB18030") == 0) + conv = (iconv_t)(-1); + else +# endif + /* Use iconv() to parse multibyte characters. */ + conv = iconv_open ("UTF-8", canon_charset); + + if (conv != (iconv_t)(-1)) + weird_cjk = false; + else +#endif + if (canon_charset == NULL) + weird_cjk = false; + else + weird_cjk = po_is_charset_weird_cjk (canon_charset); + + if (canon_charset == NULL) + canon_charset = po_charset_ascii; + + /* Determine the extent of format string directives. */ + fmtdir = NULL; + fmtdirattr = NULL; + if (value[0] != '\0') + { + bool is_msgstr = + (strlen (name) >= 6 && memcmp (name, "msgstr", 6) == 0); + /* or equivalent: = (css_class == class_msgstr) */ + size_t i; + + for (i = 0; i < NFORMATS; i++) + if (possible_format_p (mp->is_format[i])) + { + size_t len = strlen (value); + struct formatstring_parser *parser = formatstring_parsers[i]; + char *invalid_reason = NULL; + void *descr; + const char *fdp; + const char *fd_end; + char *fdap; + + fmtdir = XCALLOC (len, char); + descr = parser->parse (value, is_msgstr, fmtdir, &invalid_reason); + if (descr != NULL) + parser->free (descr); + + /* Locate the FMTDIR_* bits and transform the array to an array + of attributes. */ + fmtdirattr = XCALLOC (len, char); + fd_end = fmtdir + len; + for (fdp = fmtdir, fdap = fmtdirattr; fdp < fd_end; fdp++, fdap++) + if (*fdp & FMTDIR_START) + { + const char *fdq; + for (fdq = fdp; fdq < fd_end; fdq++) + if (*fdq & (FMTDIR_END | FMTDIR_ERROR)) + break; + if (!(fdq < fd_end)) + /* The ->parse method has determined the start of a + formatstring directive but not stored a bit indicating + its end. It is a bug in the ->parse method. */ + abort (); + if (*fdq & FMTDIR_ERROR) + memset (fdap, ATTR_INVALID_FORMAT_DIRECTIVE, fdq - fdp + 1); + else + memset (fdap, ATTR_FORMAT_DIRECTIVE, fdq - fdp + 1); + fdap += fdq - fdp; + fdp = fdq; + } + else + *fdap = 0; + + break; + } + } + + /* Loop over the '\n' delimited portions of value. */ + s = value; + first_line = true; + do + { + /* The usual escapes, as defined by the ANSI C Standard. */ +# define is_escape(c) \ + ((c) == '\a' || (c) == '\b' || (c) == '\f' || (c) == '\n' \ + || (c) == '\r' || (c) == '\t' || (c) == '\v') + + const char *es; + const char *ep; + size_t portion_len; + char *portion; + char *overrides; + char *attributes; + char *linebreaks; + char *pp; + char *op; + char *ap; + int startcol, startcol_after_break, width; + size_t i; + + for (es = s; *es != '\0'; ) + if (*es++ == '\n') + break; + + /* Expand escape sequences in each portion. */ + for (ep = s, portion_len = 0; ep < es; ep++) + { + char c = *ep; + if (is_escape (c)) + portion_len += 2; + else if (escape && !c_isprint ((unsigned char) c)) + portion_len += 4; + else if (c == '\\' || c == '"') + portion_len += 2; + else + { +#if HAVE_ICONV + if (conv != (iconv_t)(-1)) + { + /* Skip over a complete multi-byte character. Don't + interpret the second byte of a multi-byte character as + ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK, + GB18030, SHIFT_JIS, JOHAB encodings. */ + char scratchbuf[64]; + const char *inptr = ep; + size_t insize; + char *outptr = &scratchbuf[0]; + size_t outsize = sizeof (scratchbuf); + size_t res; + + res = (size_t)(-1); + for (insize = 1; inptr + insize <= es; insize++) + { + res = iconv (conv, + (ICONV_CONST char **) &inptr, &insize, + &outptr, &outsize); + if (!(res == (size_t)(-1) && errno == EINVAL)) + break; + /* We expect that no input bytes have been consumed + so far. */ + if (inptr != ep) + abort (); + } + if (res == (size_t)(-1)) + { + if (errno == EILSEQ) + { + po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, + _("invalid multibyte sequence")); + continue; + } + else if (errno == EINVAL) + { + /* This could happen if an incomplete + multibyte sequence at the end of input + bytes. */ + po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, + _("incomplete multibyte sequence")); + continue; + } + else + abort (); + } + insize = inptr - ep; + portion_len += insize; + ep += insize - 1; + } + else +#endif + { + if (weird_cjk + /* Special handling of encodings with CJK structure. */ + && ep + 2 <= es + && (unsigned char) ep[0] >= 0x80 + && (unsigned char) ep[1] >= 0x30) + { + portion_len += 2; + ep += 1; + } + else + portion_len += 1; + } + } + } + portion = XNMALLOC (portion_len, char); + overrides = XNMALLOC (portion_len, char); + attributes = XNMALLOC (portion_len, char); + for (ep = s, pp = portion, op = overrides, ap = attributes; ep < es; ep++) + { + char c = *ep; + char attr = (fmtdirattr != NULL ? fmtdirattr[ep - value] : 0); + char brk = UC_BREAK_UNDEFINED; + /* Don't break inside format directives. */ + if (attr == ATTR_FORMAT_DIRECTIVE + && (fmtdir[ep - value] & FMTDIR_START) == 0) + brk = UC_BREAK_PROHIBITED; + if (is_escape (c)) + { + switch (c) + { + case '\a': c = 'a'; break; + case '\b': c = 'b'; break; + case '\f': c = 'f'; break; + case '\n': c = 'n'; break; + case '\r': c = 'r'; break; + case '\t': c = 't'; break; + case '\v': c = 'v'; break; + default: abort (); + } + *pp++ = '\\'; + *pp++ = c; + *op++ = brk; + *op++ = UC_BREAK_PROHIBITED; + *ap++ = attr | ATTR_ESCAPE_SEQUENCE; + *ap++ = attr | ATTR_ESCAPE_SEQUENCE; + /* We warn about any use of escape sequences beside + '\n' and '\t'. */ + if (c != 'n' && c != 't') + { + char *error_message = + xasprintf (_("\ +internationalized messages should not contain the '\\%c' escape sequence"), + c); + po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, false, + error_message); + free (error_message); + } + } + else if (escape && !c_isprint ((unsigned char) c)) + { + *pp++ = '\\'; + *pp++ = '0' + (((unsigned char) c >> 6) & 7); + *pp++ = '0' + (((unsigned char) c >> 3) & 7); + *pp++ = '0' + ((unsigned char) c & 7); + *op++ = brk; + *op++ = UC_BREAK_PROHIBITED; + *op++ = UC_BREAK_PROHIBITED; + *op++ = UC_BREAK_PROHIBITED; + *ap++ = attr | ATTR_ESCAPE_SEQUENCE; + *ap++ = attr | ATTR_ESCAPE_SEQUENCE; + *ap++ = attr | ATTR_ESCAPE_SEQUENCE; + *ap++ = attr | ATTR_ESCAPE_SEQUENCE; + } + else if (c == '\\' || c == '"') + { + *pp++ = '\\'; + *pp++ = c; + *op++ = brk; + *op++ = UC_BREAK_PROHIBITED; + *ap++ = attr | ATTR_ESCAPE_SEQUENCE; + *ap++ = attr | ATTR_ESCAPE_SEQUENCE; + } + else + { +#if HAVE_ICONV + if (conv != (iconv_t)(-1)) + { + /* Copy a complete multi-byte character. Don't + interpret the second byte of a multi-byte character as + ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK, + GB18030, SHIFT_JIS, JOHAB encodings. */ + char scratchbuf[64]; + const char *inptr = ep; + size_t insize; + char *outptr = &scratchbuf[0]; + size_t outsize = sizeof (scratchbuf); + size_t res; + + res = (size_t)(-1); + for (insize = 1; inptr + insize <= es; insize++) + { + res = iconv (conv, + (ICONV_CONST char **) &inptr, &insize, + &outptr, &outsize); + if (!(res == (size_t)(-1) && errno == EINVAL)) + break; + /* We expect that no input bytes have been consumed + so far. */ + if (inptr != ep) + abort (); + } + if (res == (size_t)(-1)) + { + if (errno == EILSEQ) + { + po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, + false, _("invalid multibyte sequence")); + continue; + } + else + abort (); + } + insize = inptr - ep; + memcpy_small (pp, ep, insize); + pp += insize; + *op = brk; + memset_small (op + 1, UC_BREAK_PROHIBITED, insize - 1); + op += insize; + memset_small (ap, attr, insize); + ap += insize; + ep += insize - 1; + } + else +#endif + { + if (weird_cjk + /* Special handling of encodings with CJK structure. */ + && ep + 2 <= es + && (unsigned char) c >= 0x80 + && (unsigned char) ep[1] >= 0x30) + { + *pp++ = c; + ep += 1; + *pp++ = *ep; + *op++ = brk; + *op++ = UC_BREAK_PROHIBITED; + *ap++ = attr; + *ap++ = attr; + } + else + { + *pp++ = c; + *op++ = brk; + *ap++ = attr; + } + } + } + } + + /* Don't break immediately before the "\n" at the end. */ + if (es > s && es[-1] == '\n') + overrides[portion_len - 2] = UC_BREAK_PROHIBITED; + + linebreaks = XNMALLOC (portion_len, char); + + /* Subsequent lines after a break are all indented. + See INDENT-S. */ + startcol_after_break = (line_prefix ? strlen (line_prefix) : 0); + if (indent) + startcol_after_break = (startcol_after_break + extra_indent + 8) & ~7; + startcol_after_break++; + + /* The line width. Allow room for the closing quote character. */ + width = (wrap_strings && do_wrap != no ? page_width : INT_MAX) - 1; + /* Adjust for indentation of subsequent lines. */ + width -= startcol_after_break; + + recompute: + /* The line starts with different things depending on whether it + is the first line, and if we are using the indented style. + See INDENT-F. */ + startcol = (line_prefix ? strlen (line_prefix) : 0); + if (first_line) + { + startcol += strlen (name); + if (indent) + startcol = (startcol + extra_indent + 8) & ~7; + else + startcol++; + } + else + { + if (indent) + startcol = (startcol + extra_indent + 8) & ~7; + } + /* Allow room for the opening quote character. */ + startcol++; + /* Adjust for indentation of subsequent lines. */ + startcol -= startcol_after_break; + + /* Do line breaking on the portion. */ + ulc_width_linebreaks (portion, portion_len, width, startcol, 0, + overrides, canon_charset, linebreaks); + + /* If this is the first line, and we are not using the indented + style, and the line would wrap, then use an empty first line + and restart. */ + if (first_line && !indent + && portion_len > 0 + && (*es != '\0' + || startcol > width + || memchr (linebreaks, UC_BREAK_POSSIBLE, portion_len) != NULL)) + { + if (line_prefix != NULL) + ostream_write_str (stream, line_prefix); + begin_css_class (stream, css_class); + begin_css_class (stream, class_keyword); + ostream_write_str (stream, name); + end_css_class (stream, class_keyword); + ostream_write_str (stream, " "); + begin_css_class (stream, class_string); + ostream_write_str (stream, "\"\""); + end_css_class (stream, class_string); + end_css_class (stream, css_class); + ostream_write_str (stream, "\n"); + first_line = false; + /* Recompute startcol and linebreaks. */ + goto recompute; + } + + /* Print the beginning of the line. This will depend on whether + this is the first line, and if the indented style is being + used. INDENT-F. */ + { + int currcol = 0; + + if (line_prefix != NULL) + { + ostream_write_str (stream, line_prefix); + currcol = strlen (line_prefix); + } + begin_css_class (stream, css_class); + if (first_line) + { + begin_css_class (stream, class_keyword); + ostream_write_str (stream, name); + currcol += strlen (name); + end_css_class (stream, class_keyword); + if (indent) + { + if (extra_indent > 0) + ostream_write_mem (stream, " ", extra_indent); + currcol += extra_indent; + ostream_write_mem (stream, " ", 8 - (currcol & 7)); + currcol = (currcol + 8) & ~7; + } + else + { + ostream_write_str (stream, " "); + currcol++; + } + first_line = false; + } + else + { + if (indent) + { + if (extra_indent > 0) + ostream_write_mem (stream, " ", extra_indent); + currcol += extra_indent; + ostream_write_mem (stream, " ", 8 - (currcol & 7)); + currcol = (currcol + 8) & ~7; + } + } + } + + /* Print the portion itself, with linebreaks where necessary. */ + { + char currattr = 0; + + begin_css_class (stream, class_string); + ostream_write_str (stream, "\""); + begin_css_class (stream, class_text); + + for (i = 0; i < portion_len; i++) + { + if (linebreaks[i] == UC_BREAK_POSSIBLE) + { + int currcol; + + /* Change currattr so that it becomes 0. */ + if (currattr & ATTR_ESCAPE_SEQUENCE) + { + end_css_class (stream, class_escape_sequence); + currattr &= ~ATTR_ESCAPE_SEQUENCE; + } + if (currattr & ATTR_FORMAT_DIRECTIVE) + { + end_css_class (stream, class_format_directive); + currattr &= ~ATTR_FORMAT_DIRECTIVE; + } + else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE) + { + end_css_class (stream, class_invalid_format_directive); + currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; + } + if (!(currattr == 0)) + abort (); + + end_css_class (stream, class_text); + ostream_write_str (stream, "\""); + end_css_class (stream, class_string); + end_css_class (stream, css_class); + ostream_write_str (stream, "\n"); + currcol = 0; + /* INDENT-S. */ + if (line_prefix != NULL) + { + ostream_write_str (stream, line_prefix); + currcol = strlen (line_prefix); + } + begin_css_class (stream, css_class); + if (indent) + { + ostream_write_mem (stream, " ", 8 - (currcol & 7)); + currcol = (currcol + 8) & ~7; + } + begin_css_class (stream, class_string); + ostream_write_str (stream, "\""); + begin_css_class (stream, class_text); + } + /* Change currattr so that it matches attributes[i]. */ + if (attributes[i] != currattr) + { + /* class_escape_sequence occurs inside class_format_directive + and class_invalid_format_directive, so clear it first. */ + if (currattr & ATTR_ESCAPE_SEQUENCE) + { + end_css_class (stream, class_escape_sequence); + currattr &= ~ATTR_ESCAPE_SEQUENCE; + } + if (~attributes[i] & currattr & ATTR_FORMAT_DIRECTIVE) + { + end_css_class (stream, class_format_directive); + currattr &= ~ATTR_FORMAT_DIRECTIVE; + } + else if (~attributes[i] & currattr & ATTR_INVALID_FORMAT_DIRECTIVE) + { + end_css_class (stream, class_invalid_format_directive); + currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; + } + if (attributes[i] & ~currattr & ATTR_FORMAT_DIRECTIVE) + { + begin_css_class (stream, class_format_directive); + currattr |= ATTR_FORMAT_DIRECTIVE; + } + else if (attributes[i] & ~currattr & ATTR_INVALID_FORMAT_DIRECTIVE) + { + begin_css_class (stream, class_invalid_format_directive); + currattr |= ATTR_INVALID_FORMAT_DIRECTIVE; + } + /* class_escape_sequence occurs inside class_format_directive + and class_invalid_format_directive, so set it last. */ + if (attributes[i] & ~currattr & ATTR_ESCAPE_SEQUENCE) + { + begin_css_class (stream, class_escape_sequence); + currattr |= ATTR_ESCAPE_SEQUENCE; + } + } + ostream_write_mem (stream, &portion[i], 1); + } + + /* Change currattr so that it becomes 0. */ + if (currattr & ATTR_ESCAPE_SEQUENCE) + { + end_css_class (stream, class_escape_sequence); + currattr &= ~ATTR_ESCAPE_SEQUENCE; + } + if (currattr & ATTR_FORMAT_DIRECTIVE) + { + end_css_class (stream, class_format_directive); + currattr &= ~ATTR_FORMAT_DIRECTIVE; + } + else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE) + { + end_css_class (stream, class_invalid_format_directive); + currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; + } + if (!(currattr == 0)) + abort (); + + end_css_class (stream, class_text); + ostream_write_str (stream, "\""); + end_css_class (stream, class_string); + end_css_class (stream, css_class); + ostream_write_str (stream, "\n"); + } + + free (linebreaks); + free (attributes); + free (overrides); + free (portion); + + s = es; +# undef is_escape + } + while (*s); + + if (fmtdirattr != NULL) + free (fmtdirattr); + if (fmtdir != NULL) + free (fmtdir); + +#if HAVE_ICONV + if (conv != (iconv_t)(-1)) + iconv_close (conv); +#endif +} + + +static void +print_blank_line (ostream_t stream) +{ + if (uniforum) + { + begin_css_class (stream, class_comment); + ostream_write_str (stream, "#\n"); + end_css_class (stream, class_comment); + } + else + ostream_write_str (stream, "\n"); +} + + +static void +message_print (const message_ty *mp, ostream_t stream, + const char *charset, size_t page_width, bool blank_line, + bool debug) +{ + int extra_indent; + + /* Separate messages with a blank line. Uniforum doesn't like blank + lines, so use an empty comment (unless there already is one). */ + if (blank_line && (!uniforum + || mp->comment == NULL + || mp->comment->nitems == 0 + || mp->comment->item[0][0] != '\0')) + print_blank_line (stream); + + if (is_header (mp)) + begin_css_class (stream, class_header); + else if (mp->msgstr[0] == '\0') + begin_css_class (stream, class_untranslated); + else if (mp->is_fuzzy) + begin_css_class (stream, class_fuzzy); + else + begin_css_class (stream, class_translated); + + begin_css_class (stream, class_comment); + + /* Print translator comment if available. */ + message_print_comment (mp, stream); + + /* Print xgettext extracted comments. */ + message_print_comment_dot (mp, stream); + + /* Print the file position comments. This will help a human who is + trying to navigate the sources. There is no problem of getting + repeated positions, because duplicates are checked for. */ + message_print_comment_filepos (mp, stream, uniforum, page_width); + + /* Print flag information in special comment. */ + message_print_comment_flags (mp, stream, debug); + + /* Print the previous msgid. This helps the translator when the msgid has + only slightly changed. */ + begin_css_class (stream, class_previous_comment); + if (mp->prev_msgctxt != NULL) + wrap (mp, stream, "#| ", 0, class_previous, "msgctxt", mp->prev_msgctxt, + mp->do_wrap, page_width, charset); + if (mp->prev_msgid != NULL) + wrap (mp, stream, "#| ", 0, class_previous, "msgid", mp->prev_msgid, + mp->do_wrap, page_width, charset); + if (mp->prev_msgid_plural != NULL) + wrap (mp, stream, "#| ", 0, class_previous, "msgid_plural", + mp->prev_msgid_plural, mp->do_wrap, page_width, charset); + end_css_class (stream, class_previous_comment); + extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL + || mp->prev_msgid_plural != NULL + ? 3 + : 0); + + end_css_class (stream, class_comment); + + /* Print each of the message components. Wrap them nicely so they + are as readable as possible. If there is no recorded msgstr for + this domain, emit an empty string. */ + if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt) + && po_charset_canonicalize (charset) != po_charset_utf8) + { + char *warning_message = + xasprintf (_("\ +The following msgctxt contains non-ASCII characters.\n\ +This will cause problems to translators who use a character encoding\n\ +different from yours. Consider using a pure ASCII msgctxt instead.\n\ +%s\n"), mp->msgctxt); + po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); + free (warning_message); + } + if (!is_ascii_string (mp->msgid) + && po_charset_canonicalize (charset) != po_charset_utf8) + { + char *warning_message = + xasprintf (_("\ +The following msgid contains non-ASCII characters.\n\ +This will cause problems to translators who use a character encoding\n\ +different from yours. Consider using a pure ASCII msgid instead.\n\ +%s\n"), mp->msgid); + po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); + free (warning_message); + } + if (mp->msgctxt != NULL) + wrap (mp, stream, NULL, extra_indent, class_msgid, "msgctxt", mp->msgctxt, + mp->do_wrap, page_width, charset); + wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid", mp->msgid, + mp->do_wrap, page_width, charset); + if (mp->msgid_plural != NULL) + wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid_plural", + mp->msgid_plural, mp->do_wrap, page_width, charset); + + if (mp->msgid_plural == NULL) + wrap (mp, stream, NULL, extra_indent, class_msgstr, "msgstr", mp->msgstr, + mp->do_wrap, page_width, charset); + else + { + char prefix_buf[20]; + unsigned int i; + const char *p; + + for (p = mp->msgstr, i = 0; + p < mp->msgstr + mp->msgstr_len; + p += strlen (p) + 1, i++) + { + sprintf (prefix_buf, "msgstr[%u]", i); + wrap (mp, stream, NULL, extra_indent, class_msgstr, prefix_buf, p, + mp->do_wrap, page_width, charset); + } + } + + if (is_header (mp)) + end_css_class (stream, class_header); + else if (mp->msgstr[0] == '\0') + end_css_class (stream, class_untranslated); + else if (mp->is_fuzzy) + end_css_class (stream, class_fuzzy); + else + end_css_class (stream, class_translated); +} + + +static void +message_print_obsolete (const message_ty *mp, ostream_t stream, + const char *charset, size_t page_width, bool blank_line) +{ + int extra_indent; + + /* If msgstr is the empty string we print nothing. */ + if (mp->msgstr[0] == '\0') + return; + + /* Separate messages with a blank line. Uniforum doesn't like blank + lines, so use an empty comment (unless there already is one). */ + if (blank_line) + print_blank_line (stream); + + begin_css_class (stream, class_obsolete); + + begin_css_class (stream, class_comment); + + /* Print translator comment if available. */ + message_print_comment (mp, stream); + + /* Print xgettext extracted comments (normally empty). */ + message_print_comment_dot (mp, stream); + + /* Print the file position comments (normally empty). */ + message_print_comment_filepos (mp, stream, uniforum, page_width); + + /* Print flag information in special comment. */ + if (mp->is_fuzzy) + { + ostream_write_str (stream, "#,"); + + if (mp->is_fuzzy) + ostream_write_str (stream, " fuzzy"); + + ostream_write_str (stream, "\n"); + } + + /* Print the previous msgid. This helps the translator when the msgid has + only slightly changed. */ + begin_css_class (stream, class_previous_comment); + if (mp->prev_msgctxt != NULL) + wrap (mp, stream, "#~| ", 0, class_previous, "msgctxt", mp->prev_msgctxt, + mp->do_wrap, page_width, charset); + if (mp->prev_msgid != NULL) + wrap (mp, stream, "#~| ", 0, class_previous, "msgid", mp->prev_msgid, + mp->do_wrap, page_width, charset); + if (mp->prev_msgid_plural != NULL) + wrap (mp, stream, "#~| ", 0, class_previous, "msgid_plural", + mp->prev_msgid_plural, mp->do_wrap, page_width, charset); + end_css_class (stream, class_previous_comment); + extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL + || mp->prev_msgid_plural != NULL + ? 1 + : 0); + + end_css_class (stream, class_comment); + + /* Print each of the message components. Wrap them nicely so they + are as readable as possible. */ + if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt) + && po_charset_canonicalize (charset) != po_charset_utf8) + { + char *warning_message = + xasprintf (_("\ +The following msgctxt contains non-ASCII characters.\n\ +This will cause problems to translators who use a character encoding\n\ +different from yours. Consider using a pure ASCII msgctxt instead.\n\ +%s\n"), mp->msgctxt); + po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); + free (warning_message); + } + if (!is_ascii_string (mp->msgid) + && po_charset_canonicalize (charset) != po_charset_utf8) + { + char *warning_message = + xasprintf (_("\ +The following msgid contains non-ASCII characters.\n\ +This will cause problems to translators who use a character encoding\n\ +different from yours. Consider using a pure ASCII msgid instead.\n\ +%s\n"), mp->msgid); + po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); + free (warning_message); + } + if (mp->msgctxt != NULL) + wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgctxt", mp->msgctxt, + mp->do_wrap, page_width, charset); + wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid", mp->msgid, + mp->do_wrap, page_width, charset); + if (mp->msgid_plural != NULL) + wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid_plural", + mp->msgid_plural, mp->do_wrap, page_width, charset); + + if (mp->msgid_plural == NULL) + wrap (mp, stream, "#~ ", extra_indent, class_msgstr, "msgstr", mp->msgstr, + mp->do_wrap, page_width, charset); + else + { + char prefix_buf[20]; + unsigned int i; + const char *p; + + for (p = mp->msgstr, i = 0; + p < mp->msgstr + mp->msgstr_len; + p += strlen (p) + 1, i++) + { + sprintf (prefix_buf, "msgstr[%u]", i); + wrap (mp, stream, "#~ ", extra_indent, class_msgstr, prefix_buf, p, + mp->do_wrap, page_width, charset); + } + } + + end_css_class (stream, class_obsolete); +} + + +static void +msgdomain_list_print_po (msgdomain_list_ty *mdlp, ostream_t stream, + size_t page_width, bool debug) +{ + size_t j, k; + bool blank_line; + + /* Write out the messages for each domain. */ + blank_line = false; + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp; + const char *header; + const char *charset; + char *allocated_charset; + + /* If the first domain is the default, don't bother emitting + the domain name, because it is the default. */ + if (!(k == 0 + && strcmp (mdlp->item[k]->domain, MESSAGE_DOMAIN_DEFAULT) == 0)) + { + if (blank_line) + print_blank_line (stream); + begin_css_class (stream, class_keyword); + ostream_write_str (stream, "domain"); + end_css_class (stream, class_keyword); + ostream_write_str (stream, " "); + begin_css_class (stream, class_string); + ostream_write_str (stream, "\""); + begin_css_class (stream, class_text); + ostream_write_str (stream, mdlp->item[k]->domain); + end_css_class (stream, class_text); + ostream_write_str (stream, "\""); + end_css_class (stream, class_string); + ostream_write_str (stream, "\n"); + blank_line = true; + } + + mlp = mdlp->item[k]->messages; + + /* Search the header entry. */ + header = NULL; + for (j = 0; j < mlp->nitems; ++j) + if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) + { + header = mlp->item[j]->msgstr; + break; + } + + /* Extract the charset name. */ + charset = "ASCII"; + allocated_charset = NULL; + if (header != NULL) + { + const char *charsetstr = c_strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + allocated_charset = (char *) xmalloca (len + 1); + memcpy (allocated_charset, charsetstr, len); + allocated_charset[len] = '\0'; + charset = allocated_charset; + + /* Treat the dummy default value as if it were absent. */ + if (strcmp (charset, "CHARSET") == 0) + charset = "ASCII"; + } + } + + /* Write out each of the messages for this domain. */ + for (j = 0; j < mlp->nitems; ++j) + if (!mlp->item[j]->obsolete) + { + message_print (mlp->item[j], stream, charset, page_width, + blank_line, debug); + blank_line = true; + } + + /* Write out each of the obsolete messages for this domain. */ + for (j = 0; j < mlp->nitems; ++j) + if (mlp->item[j]->obsolete) + { + message_print_obsolete (mlp->item[j], stream, charset, page_width, + blank_line); + blank_line = true; + } + + if (allocated_charset != NULL) + freea (allocated_charset); + } +} + + +/* Describes a PO file in .po syntax. */ +const struct catalog_output_format output_format_po = +{ + msgdomain_list_print_po, /* print */ + false, /* requires_utf8 */ + true, /* supports_color */ + true, /* supports_multiple_domains */ + true, /* supports_contexts */ + true, /* supports_plurals */ + true, /* sorts_obsoletes_to_end */ + false, /* alternative_is_po */ + false /* alternative_is_java_class */ +}; diff --git a/gettext-tools/src/write-po.h b/gettext-tools/src/write-po.h new file mode 100644 index 0000000..9a243bf --- /dev/null +++ b/gettext-tools/src/write-po.h @@ -0,0 +1,87 @@ +/* GNU gettext - internationalization aids + Copyright (C) 1995-1998, 2000-2003, 2006, 2008 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_PO_H +#define _WRITE_PO_H + +#include "ostream.h" +#include "message.h" + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +enum filepos_comment_type + { + filepos_comment_none, + filepos_comment_full, + filepos_comment_file + }; + +/* These functions are used to output a #, flags line. */ +extern const char * + make_format_description_string (enum is_format is_format, + const char *lang, bool debug); +extern bool + significant_format_p (enum is_format is_format); + +extern char * + make_range_description_string (struct argument_range range); + +/* These functions output parts of a message, as comments. */ +extern void + message_print_comment (const message_ty *mp, ostream_t stream); +extern void + message_print_comment_dot (const message_ty *mp, ostream_t stream); +extern void + message_print_comment_filepos (const message_ty *mp, ostream_t stream, + bool uniforum, size_t page_width); +extern void + message_print_comment_flags (const message_ty *mp, ostream_t stream, + bool debug); + +/* These functions set some parameters for use by 'output_format_po.print'. */ +extern void + message_page_width_ignore (void); +extern void + message_print_style_indent (void); +extern void + message_print_style_uniforum (void); +extern void + message_print_style_escape (bool flag); +extern void + message_print_style_filepos (enum filepos_comment_type type); + +/* --add-location argument handling. Return an error indicator. */ +extern bool handle_filepos_comment_option (const char *option); + + +/* Describes a PO file in .po syntax. */ +extern DLL_VARIABLE const struct catalog_output_format output_format_po; + + +#ifdef __cplusplus +} +#endif + + +#endif /* _WRITE_PO_H */ diff --git a/gettext-tools/src/write-properties.c b/gettext-tools/src/write-properties.c new file mode 100644 index 0000000..8b6dcee --- /dev/null +++ b/gettext-tools/src/write-properties.c @@ -0,0 +1,305 @@ +/* Writing Java .properties files. + Copyright (C) 2003, 2005-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "write-properties.h" + +#include +#include +#include +#include +#include + +#include "error.h" +#include "message.h" +#include "msgl-ascii.h" +#include "msgl-iconv.h" +#include "po-charset.h" +#include "unistr.h" +#include "ostream.h" +#include "write-po.h" +#include "xalloc.h" + +/* The format of the Java .properties files is documented in the JDK + documentation for class java.util.Properties. In the case of .properties + files for PropertyResourceBundle, for each message, the msgid becomes the + key (left-hand side) and the msgstr becomes the value (right-hand side) + of a "key=value" line. Messages with plurals are not supported in this + format. */ + +/* Handling of comments: We copy all comments from the PO file to the + .properties file. This is not really needed; it's a service for translators + who don't like PO files and prefer to maintain the .properties file. */ + +/* Converts a string to JAVA encoding (with \uxxxx sequences for non-ASCII + characters). */ +static const char * +conv_to_java (const char *string) +{ + /* We cannot use iconv to "JAVA" because not all iconv() implementations + know about the "JAVA" encoding. */ + static const char hexdigit[] = "0123456789abcdef"; + size_t length; + char *result; + + if (is_ascii_string (string)) + return string; + + length = 0; + { + const char *str = string; + const char *str_limit = str + strlen (str); + + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + length += (uc <= 0x007f ? 1 : uc < 0x10000 ? 6 : 12); + } + } + + result = XNMALLOC (length + 1, char); + + { + char *newstr = result; + const char *str = string; + const char *str_limit = str + strlen (str); + + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + if (uc <= 0x007f) + /* ASCII characters can be output literally. + We could treat non-ASCII ISO-8859-1 characters (0x0080..0x00FF) + the same way, but there is no point in doing this; Sun's + nativetoascii doesn't do it either. */ + *newstr++ = uc; + else if (uc < 0x10000) + { + /* Single UCS-2 'char' */ + sprintf (newstr, "\\u%c%c%c%c", + hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f], + hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]); + newstr += 6; + } + else + { + /* UTF-16 surrogate: two 'char's. */ + ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10); + ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff); + sprintf (newstr, "\\u%c%c%c%c", + hexdigit[(uc1 >> 12) & 0x0f], hexdigit[(uc1 >> 8) & 0x0f], + hexdigit[(uc1 >> 4) & 0x0f], hexdigit[uc1 & 0x0f]); + newstr += 6; + sprintf (newstr, "\\u%c%c%c%c", + hexdigit[(uc2 >> 12) & 0x0f], hexdigit[(uc2 >> 8) & 0x0f], + hexdigit[(uc2 >> 4) & 0x0f], hexdigit[uc2 & 0x0f]); + newstr += 6; + } + } + *newstr = '\0'; + } + + return result; +} + +/* Writes a key or value to the stream, without newline. */ +static void +write_escaped_string (ostream_t stream, const char *str, bool in_key) +{ + static const char hexdigit[] = "0123456789abcdef"; + const char *str_limit = str + strlen (str); + bool first = true; + + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + /* Whitespace must be escaped. */ + if (uc == 0x0020 && (first || in_key)) + ostream_write_str (stream, "\\ "); + else if (uc == 0x0009) + ostream_write_str (stream, "\\t"); + else if (uc == 0x000a) + ostream_write_str (stream, "\\n"); + else if (uc == 0x000d) + ostream_write_str (stream, "\\r"); + else if (uc == 0x000c) + ostream_write_str (stream, "\\f"); + else if (/* Backslash must be escaped. */ + uc == '\\' + /* Possible comment introducers must be escaped. */ + || uc == '#' || uc == '!' + /* Key terminators must be escaped. */ + || uc == '=' || uc == ':') + { + char seq[2]; + seq[0] = '\\'; + seq[1] = uc; + ostream_write_mem (stream, seq, 2); + } + else if (uc >= 0x0020 && uc <= 0x007e) + { + /* ASCII characters can be output literally. + We could treat non-ASCII ISO-8859-1 characters (0x0080..0x00FF) + the same way, but there is no point in doing this; Sun's + nativetoascii doesn't do it either. */ + char seq[1]; + seq[0] = uc; + ostream_write_mem (stream, seq, 1); + } + else if (uc < 0x10000) + { + /* Single UCS-2 'char' */ + char seq[6]; + seq[0] = '\\'; + seq[1] = 'u'; + seq[2] = hexdigit[(uc >> 12) & 0x0f]; + seq[3] = hexdigit[(uc >> 8) & 0x0f]; + seq[4] = hexdigit[(uc >> 4) & 0x0f]; + seq[5] = hexdigit[uc & 0x0f]; + ostream_write_mem (stream, seq, 6); + } + else + { + /* UTF-16 surrogate: two 'char's. */ + ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10); + ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff); + char seq[6]; + seq[0] = '\\'; + seq[1] = 'u'; + seq[2] = hexdigit[(uc1 >> 12) & 0x0f]; + seq[3] = hexdigit[(uc1 >> 8) & 0x0f]; + seq[4] = hexdigit[(uc1 >> 4) & 0x0f]; + seq[5] = hexdigit[uc1 & 0x0f]; + ostream_write_mem (stream, seq, 6); + seq[0] = '\\'; + seq[1] = 'u'; + seq[2] = hexdigit[(uc2 >> 12) & 0x0f]; + seq[3] = hexdigit[(uc2 >> 8) & 0x0f]; + seq[4] = hexdigit[(uc2 >> 4) & 0x0f]; + seq[5] = hexdigit[uc2 & 0x0f]; + ostream_write_mem (stream, seq, 6); + } + first = false; + } +} + +/* Writes a message to the stream. */ +static void +write_message (ostream_t stream, const message_ty *mp, + size_t page_width, bool debug) +{ + /* Print translator comment if available. */ + message_print_comment (mp, stream); + + /* Print xgettext extracted comments. */ + message_print_comment_dot (mp, stream); + + /* Print the file position comments. */ + message_print_comment_filepos (mp, stream, false, page_width); + + /* Print flag information in special comment. */ + message_print_comment_flags (mp, stream, debug); + + /* Put a comment mark if the message is the header or untranslated or + fuzzy. */ + if (is_header (mp) + || mp->msgstr[0] == '\0' + || (mp->is_fuzzy && !is_header (mp))) + ostream_write_str (stream, "!"); + + /* Now write the untranslated string and the translated string. */ + write_escaped_string (stream, mp->msgid, true); + ostream_write_str (stream, "="); + write_escaped_string (stream, mp->msgstr, false); + + ostream_write_str (stream, "\n"); +} + +/* Writes an entire message list to the stream. */ +static void +write_properties (ostream_t stream, message_list_ty *mlp, + const char *canon_encoding, size_t page_width, bool debug) +{ + bool blank_line; + size_t j, i; + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + for (j = 0; j < mlp->nitems; ++j) + { + message_ty *mp = mlp->item[j]; + + if (mp->comment != NULL) + for (i = 0; i < mp->comment->nitems; ++i) + mp->comment->item[i] = conv_to_java (mp->comment->item[i]); + if (mp->comment_dot != NULL) + for (i = 0; i < mp->comment_dot->nitems; ++i) + mp->comment_dot->item[i] = conv_to_java (mp->comment_dot->item[i]); + } + + /* Loop through the messages. */ + blank_line = false; + for (j = 0; j < mlp->nitems; ++j) + { + const message_ty *mp = mlp->item[j]; + + if (mp->msgid_plural == NULL && !mp->obsolete) + { + if (blank_line) + ostream_write_str (stream, "\n"); + + write_message (stream, mp, page_width, debug); + + blank_line = true; + } + } +} + +/* Output the contents of a PO file in Java .properties syntax. */ +static void +msgdomain_list_print_properties (msgdomain_list_ty *mdlp, ostream_t stream, + size_t page_width, bool debug) +{ + message_list_ty *mlp; + + if (mdlp->nitems == 1) + mlp = mdlp->item[0]->messages; + else + mlp = message_list_alloc (false); + write_properties (stream, mlp, mdlp->encoding, page_width, debug); +} + +/* Describes a PO file in Java .properties syntax. */ +const struct catalog_output_format output_format_properties = +{ + msgdomain_list_print_properties, /* print */ + true, /* requires_utf8 */ + false, /* supports_color */ + false, /* supports_multiple_domains */ + false, /* supports_contexts */ + false, /* supports_plurals */ + false, /* sorts_obsoletes_to_end */ + true, /* alternative_is_po */ + true /* alternative_is_java_class */ +}; diff --git a/gettext-tools/src/write-properties.h b/gettext-tools/src/write-properties.h new file mode 100644 index 0000000..9f614b5 --- /dev/null +++ b/gettext-tools/src/write-properties.h @@ -0,0 +1,26 @@ +/* Writing Java .properties files. + Copyright (C) 2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_PROPERTIES_H +#define _WRITE_PROPERTIES_H + +#include "write-catalog.h" + +/* Describes a PO file in Java .properties syntax. */ +extern DLL_VARIABLE const struct catalog_output_format output_format_properties; + +#endif /* _WRITE_PROPERTIES_H */ diff --git a/gettext-tools/src/write-qt.c b/gettext-tools/src/write-qt.c new file mode 100644 index 0000000..50b96b7 --- /dev/null +++ b/gettext-tools/src/write-qt.c @@ -0,0 +1,754 @@ +/* Writing Qt .qm files. + Copyright (C) 2003, 2005-2007, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "write-qt.h" + +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "xerror.h" +#include "message.h" +#include "po-charset.h" +#include "msgl-iconv.h" +#include "hash-string.h" +#include "unistr.h" +#include "xalloc.h" +#include "obstack.h" +#include "hash.h" +#include "binary-io.h" +#include "fwriteerror.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Qt .qm files are read by the QTranslator::load() function and written + by the Qt QTranslator::save() function. + + The Qt tool 'msg2qm' uses the latter function and can convert PO files + to .qm files. But since 'msg2qm' is marked as an "old" tool in Qt 3.0.5's + i18n.html documentation and therefore likely to disappear, we provide the + same functionality here. + + The format of .qm files, as reverse engineered from the functions + QTranslator::save(const QString& filename, SaveMode mode) + QTranslator::squeeze(SaveMode mode) + QTranslatorMessage::write(QDataStream& stream, bool strip, Prefix prefix) + elfHash(const char* name) + in qt-3.0.5, is as follows: + + It's a binary data format. Elements are u8 (byte), u16, u32. They are + written in big-endian order. + + The file starts with a magic string of 16 bytes: + 3C B8 64 18 CA EF 9C 95 CD 21 1C BF 60 A1 BD DD + + Then come three sections. Each of the three sections is optional. Each + has this structure: + struct { + u8 section_type; // 0x42 = hashes, 0x69 = messages, 0x2f = contexts + u32 length; // number of bytes of the data + u8 data[length]; + }; + + In the first section, the hashes section, the data has the following + structure: + It's a sorted array of + struct { + u32 hashcode; // elfHash of the concatenation of msgid and + // disambiguating-comment + u32 offset; // offset within the data[] of the messages section + }; + It's sorted in ascending order by hashcode as primary sorting criteria + and - when the hashcodes are the same - by offset as secondary criteria. + + In the second section, the messages section, the data has the following + structure: + It's a sequence of records, each representing a message, in no + particular order. Each record is a sequence of subsections, each + introduced by a particular subsection tag. The possible subsection tags + are (and they usually occur in this order): + - 03: Translation. Followed by the msgstr in UCS-2 or UTF-16 format: + struct { + u32 length; + u16 chars[length/2]; + }; + - 08: Disambiguating-comment. Followed by the NUL-terminated, + ISO-8859-1 encoded, disambiguating-comment string: + struct { + u32 length; // number of bytes including the NUL at the end + u8 chars[length]; + }; + - 06: SourceText, i.e. msgid. Followed by the NUL-terminated, + ISO-8859-1 encoded, msgid: + struct { + u32 length; // number of bytes including the NUL at the end + u8 chars[length]; + }; + - 02: SourceText16, i.e. msgid. Encoded as UCS-2, but must actually + be ISO-8859-1. + struct { + u32 length; + u16 chars[length/2]; + }; + This subsection tag is obsoleted by SourceText. + - 07: Context. Followed by the NUL-terminated, ISO-8859-1 encoded, + context string (usually a C++ class name or empty): + struct { + u32 length; // number of bytes including the NUL at the end + u8 chars[length]; + }; + - 04: Context16. Encoded as UCS-2, but must actually be ISO-8859-1. + struct { + u32 length; + u16 chars[length/2]; + }; + This subsection tag is obsoleted by Context. + - 05: Hash. Followed by + struct { + u32 hashcode; // elfHash of the concatenation of msgid and + // disambiguating-comment + }; + - 01: End. Designates the end of the record. No further data. + Usually the following subsections are written, but some of them are + optional: + - 03: Translation. + - 08: Disambiguating-comment (optional). + - 06: SourceText (optional). + - 07: Context (optional). + - 05: Hash. + - 01: End. + A subsection can be omitted if the value to be output is the same as + for the previous record. + + The third section, the contexts section, contains the set of all occurring + context strings. This section is optional; it is used to speed up the + search. The data is a hash table with the following structure: + struct { + u16 table_size; + u16 buckets[table_size]; + u8 pool[...]; + }; + pool[...] contains: + u16 zero; + for i = 0, ..., table_size: + if there are context strings with elfHash(context)%table_size == i: + for all context strings with elfHash(context)%table_size == i: + len := min(length(context),255); // truncated to length 255 + struct { + u8 len; + u8 chars[len]; + }; + struct { + u8 zero[1]; // signals the end of this bucket + u8 padding[0 or 1]; // padding for even number of bytes + }; + buckets[i] is 0 for an empty bucket, or the offset in pool[] where + the context strings for this bucket start, divided by 2. + This context section must not be used + - if the empty context is used, or + - if a context of length > 255 is used, or + - if the context pool's size would be > 2^17. + + The elfHash function is the same as our hash_string function, except that + at the end it maps a hash code of 0x00000000 to 0x00000001. + + When we convert from PO file format, all disambiguating-comments and + contexts are empty, and therefore the contexts section can be omitted. */ + + +/* Write a u8 (a single byte) to the output stream. */ +static inline void +write_u8 (FILE *output_file, unsigned char value) +{ + putc (value, output_file); +} + +/* Write a u16 (two bytes) to the output stream. */ +static inline void +write_u16 (FILE *output_file, unsigned short value) +{ + unsigned char data[2]; + + data[0] = (value >> 8) & 0xff; + data[1] = value & 0xff; + + fwrite (data, 2, 1, output_file); +} + +/* Write a u32 (four bytes) to the output stream. */ +static inline void +write_u32 (FILE *output_file, unsigned int value) +{ + unsigned char data[4]; + + data[0] = (value >> 24) & 0xff; + data[1] = (value >> 16) & 0xff; + data[2] = (value >> 8) & 0xff; + data[3] = value & 0xff; + + fwrite (data, 4, 1, output_file); +} + + +#define obstack_chunk_alloc xmalloc +#define obstack_chunk_free free + +/* Add a u8 (a single byte) to an obstack. */ +static void +append_u8 (struct obstack *mempool, unsigned char value) +{ + unsigned char data[1]; + + data[0] = value; + + obstack_grow (mempool, data, 1); +} + +/* Add a u16 (two bytes) to an obstack. */ +static void +append_u16 (struct obstack *mempool, unsigned short value) +{ + unsigned char data[2]; + + data[0] = (value >> 8) & 0xff; + data[1] = value & 0xff; + + obstack_grow (mempool, data, 2); +} + +/* Add a u32 (four bytes) to an obstack. */ +static void +append_u32 (struct obstack *mempool, unsigned int value) +{ + unsigned char data[4]; + + data[0] = (value >> 24) & 0xff; + data[1] = (value >> 16) & 0xff; + data[2] = (value >> 8) & 0xff; + data[3] = value & 0xff; + + obstack_grow (mempool, data, 4); +} + +/* Add an ISO-8859-1 encoded string to an obstack. */ +static void +append_base_string (struct obstack *mempool, const char *string) +{ + size_t length = strlen (string) + 1; + append_u32 (mempool, length); + obstack_grow (mempool, string, length); +} + +/* Add an UTF-16 encoded string to an obstack. */ +static void +append_unicode_string (struct obstack *mempool, const unsigned short *string, + size_t length) +{ + append_u32 (mempool, length * 2); + for (; length > 0; string++, length--) + append_u16 (mempool, *string); +} + +/* Retrieve a 4-byte integer from memory. */ +static inline unsigned int +peek_u32 (const unsigned char *p) +{ + return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; +} + +/* Convert an UTF-8 string to ISO-8859-1, without error checking. */ +static char * +conv_to_iso_8859_1 (const char *string) +{ + size_t length = strlen (string); + const char *str = string; + const char *str_limit = string + length; + /* Conversion to ISO-8859-1 can only reduce the number of bytes. */ + char *result = XNMALLOC (length + 1, char); + char *q = result; + + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + /* It has already been verified that the string fits in ISO-8859-1. */ + if (!(uc < 0x100)) + abort (); + /* Store as ISO-8859-1. */ + *q++ = (unsigned char) uc; + } + *q = '\0'; + assert (q - result <= length); + + return result; +} + +/* Convert an UTF-8 string to UTF-16, returning its size (number of UTF-16 + codepoints) in *SIZEP. */ +static unsigned short * +conv_to_utf16 (const char *string, size_t *sizep) +{ + size_t length = strlen (string); + const char *str = string; + const char *str_limit = string + length; + /* Conversion to UTF-16 can at most double the number of bytes. */ + unsigned short *result = XNMALLOC (length, unsigned short); + unsigned short *q = result; + + while (str < str_limit) + { + ucs4_t uc; + str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + if (uc < 0x10000) + /* UCS-2 character. */ + *q++ = (unsigned short) uc; + else + { + /* UTF-16 surrogate. */ + *q++ = 0xd800 + ((uc - 0x10000) >> 10); + *q++ = 0xdc00 + ((uc - 0x10000) & 0x3ff); + } + } + assert (q - result <= 2 * length); + + *sizep = q - result; + return result; +} + +/* Return the Qt hash code of a string. */ +static unsigned int +string_hashcode (const char *str) +{ + unsigned int h; + + h = hash_string (str); + if (h == 0) + h = 1; + return h; +} + +/* Compare two entries of the hashes section. */ +static int +cmp_hashes (const void *va, const void *vb) +{ + const unsigned char *a = (const unsigned char *) va; + const unsigned char *b = (const unsigned char *) vb; + unsigned int a_hashcode = peek_u32 (a); + unsigned int b_hashcode = peek_u32 (b); + + if (a_hashcode != b_hashcode) + return (a_hashcode >= b_hashcode ? 1 : -1); + else + { + unsigned int a_offset = peek_u32 (a + 4); + unsigned int b_offset = peek_u32 (b + 4); + + if (a_offset != b_offset) + return (a_offset >= b_offset ? 1 : -1); + else + return 0; + } +} + + +/* Write a section to the output stream. */ +static void +write_section (FILE *output_file, unsigned char tag, void *data, size_t size) +{ + /* A section can be omitted if it is empty. */ + if (size > 0) + { + write_u8 (output_file, tag); + write_u32 (output_file, size); + fwrite (data, size, 1, output_file); + } +} + + +/* Write an entire .qm file. */ +static void +write_qm (FILE *output_file, message_list_ty *mlp) +{ + static unsigned char magic[16] = + { + 0x3C, 0xB8, 0x64, 0x18, 0xCA, 0xEF, 0x9C, 0x95, + 0xCD, 0x21, 0x1C, 0xBF, 0x60, 0xA1, 0xBD, 0xDD + }; + struct obstack hashes_pool; + struct obstack messages_pool; + size_t j; + + obstack_init (&hashes_pool); + obstack_init (&messages_pool); + + /* Prepare the hashes section and the messages section. */ + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + /* No need to emit the header entry, it's not needed at runtime. */ + if (!is_header (mp)) + { + char *msgctxt_as_iso_8859_1 = + conv_to_iso_8859_1 (mp->msgctxt != NULL ? mp->msgctxt : ""); + char *msgid_as_iso_8859_1 = conv_to_iso_8859_1 (mp->msgid); + size_t msgstr_len; + unsigned short *msgstr_as_utf16 = + conv_to_utf16 (mp->msgstr, &msgstr_len); + unsigned int hashcode = string_hashcode (msgid_as_iso_8859_1); + unsigned int offset = obstack_object_size (&messages_pool); + + /* Add a record to the hashes section. */ + append_u32 (&hashes_pool, hashcode); + append_u32 (&hashes_pool, offset); + + /* Add a record to the messages section. */ + + append_u8 (&messages_pool, 0x03); + append_unicode_string (&messages_pool, msgstr_as_utf16, msgstr_len); + + append_u8 (&messages_pool, 0x08); + append_base_string (&messages_pool, ""); + + append_u8 (&messages_pool, 0x06); + append_base_string (&messages_pool, msgid_as_iso_8859_1); + + append_u8 (&messages_pool, 0x07); + append_base_string (&messages_pool, msgctxt_as_iso_8859_1); + + append_u8 (&messages_pool, 0x05); + append_u32 (&messages_pool, hashcode); + + append_u8 (&messages_pool, 0x01); + + free (msgstr_as_utf16); + free (msgid_as_iso_8859_1); + free (msgctxt_as_iso_8859_1); + } + } + + /* Sort the hashes section. */ + { + size_t nstrings = obstack_object_size (&hashes_pool) / 8; + if (nstrings > 0) + qsort (obstack_base (&hashes_pool), nstrings, 8, cmp_hashes); + } + + /* Write the magic number. */ + fwrite (magic, sizeof (magic), 1, output_file); + + /* Write the hashes section. */ + write_section (output_file, 0x42, obstack_base (&hashes_pool), + obstack_object_size (&hashes_pool)); + + /* Write the messages section. */ + write_section (output_file, 0x69, obstack_base (&messages_pool), + obstack_object_size (&messages_pool)); + + /* Decide whether to write a contexts section. */ + { + bool can_write_contexts = true; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (!is_header (mp)) + if (mp->msgctxt == NULL || mp->msgctxt[0] == '\0' + || strlen (mp->msgctxt) > 255) + { + can_write_contexts = false; + break; + } + } + + if (can_write_contexts) + { + hash_table all_contexts; + size_t num_contexts; + unsigned long table_size; + + /* Collect the contexts, removing duplicates. */ + hash_init (&all_contexts, 10); + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (!is_header (mp)) + hash_insert_entry (&all_contexts, + mp->msgctxt, strlen (mp->msgctxt) + 1, + NULL); + } + + /* Compute the number of different contexts. */ + num_contexts = all_contexts.size; + + /* Compute a suitable hash table size. */ + table_size = next_prime (num_contexts * 1.7); + if (table_size >= 0x10000) + table_size = 65521; + + /* Put the contexts into a hash table of size table_size. */ + { + struct list_cell { const char *context; struct list_cell *next; }; + struct list_cell *list_memory = + XNMALLOC (table_size, struct list_cell); + struct list_cell *freelist; + struct bucket { struct list_cell *head; struct list_cell **tail; }; + struct bucket *buckets = XNMALLOC (table_size, struct bucket); + size_t i; + + freelist = list_memory; + + for (i = 0; i < table_size; i++) + { + buckets[i].head = NULL; + buckets[i].tail = &buckets[i].head; + } + + { + void *iter; + const void *key; + size_t keylen; + void *null; + + iter = NULL; + while (hash_iterate (&all_contexts, &iter, &key, &keylen, &null) + == 0) + { + const char *context = (const char *)key; + i = string_hashcode (context) % table_size; + freelist->context = context; + freelist->next = NULL; + *buckets[i].tail = freelist; + buckets[i].tail = &freelist->next; + freelist++; + } + } + + /* Determine the total context pool size. */ + { + size_t pool_size; + + pool_size = 2; + for (i = 0; i < table_size; i++) + if (buckets[i].head != NULL) + { + const struct list_cell *p; + + for (p = buckets[i].head; p != NULL; p = p->next) + pool_size += 1 + strlen (p->context); + pool_size++; + if ((pool_size % 2) != 0) + pool_size++; + } + if (pool_size <= 0x20000) + { + /* Prepare the contexts section. */ + struct obstack contexts_pool; + size_t pool_offset; + + obstack_init (&contexts_pool); + + append_u16 (&contexts_pool, table_size); + pool_offset = 2; + for (i = 0; i < table_size; i++) + if (buckets[i].head != NULL) + { + const struct list_cell *p; + + append_u16 (&contexts_pool, pool_offset / 2); + for (p = buckets[i].head; p != NULL; p = p->next) + pool_offset += 1 + strlen (p->context); + pool_offset++; + if ((pool_offset % 2) != 0) + pool_offset++; + } + else + append_u16 (&contexts_pool, 0); + if (!(pool_offset == pool_size)) + abort (); + + append_u16 (&contexts_pool, 0); + pool_offset = 2; + for (i = 0; i < table_size; i++) + if (buckets[i].head != NULL) + { + const struct list_cell *p; + + for (p = buckets[i].head; p != NULL; p = p->next) + { + append_u8 (&contexts_pool, strlen (p->context)); + obstack_grow (&contexts_pool, + p->context, strlen (p->context)); + pool_offset += 1 + strlen (p->context); + } + append_u8 (&contexts_pool, 0); + pool_offset++; + if ((pool_offset % 2) != 0) + { + append_u8 (&contexts_pool, 0); + pool_offset++; + } + } + if (!(pool_offset == pool_size)) + abort (); + + if (!(obstack_object_size (&contexts_pool) + == 2 + 2 * table_size + pool_size)) + abort (); + + /* Write the contexts section. */ + write_section (output_file, 0x2f, obstack_base (&contexts_pool), + obstack_object_size (&contexts_pool)); + + obstack_free (&contexts_pool, NULL); + } + } + + free (buckets); + free (list_memory); + } + + hash_destroy (&all_contexts); + } + } + + obstack_free (&messages_pool, NULL); + obstack_free (&hashes_pool, NULL); +} + + +int +msgdomain_write_qt (message_list_ty *mlp, const char *canon_encoding, + const char *domain_name, const char *file_name) +{ + FILE *output_file; + + /* If no entry for this domain don't even create the file. */ + if (mlp->nitems != 0) + { + /* Determine whether mlp has plural entries. */ + { + bool has_plural; + size_t j; + + has_plural = false; + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgid_plural != NULL) + has_plural = true; + if (has_plural) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +message catalog has plural form translations\n\ +but the Qt message catalog format doesn't support plural handling\n"))); + return 1; + } + } + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + + /* Determine whether mlp has non-ISO-8859-1 msgctxt entries. */ + { + size_t j; + + for (j = 0; j < mlp->nitems; j++) + { + const char *string = mlp->item[j]->msgctxt; + + if (string != NULL) + { + /* An UTF-8 encoded string fits in ISO-8859-1 if and only if + all its bytes are < 0xc4. */ + for (; *string; string++) + if ((unsigned char) *string >= 0xc4) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +message catalog has msgctxt strings containing characters outside ISO-8859-1\n\ +but the Qt message catalog format supports Unicode only in the translated\n\ +strings, not in the context strings\n"))); + return 1; + } + } + } + } + + /* Determine whether mlp has non-ISO-8859-1 msgid entries. */ + { + size_t j; + + for (j = 0; j < mlp->nitems; j++) + { + const char *string = mlp->item[j]->msgid; + + /* An UTF-8 encoded string fits in ISO-8859-1 if and only if all + its bytes are < 0xc4. */ + for (; *string; string++) + if ((unsigned char) *string >= 0xc4) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +message catalog has msgid strings containing characters outside ISO-8859-1\n\ +but the Qt message catalog format supports Unicode only in the translated\n\ +strings, not in the untranslated strings\n"))); + return 1; + } + } + } + + if (strcmp (domain_name, "-") == 0) + { + output_file = stdout; + SET_BINARY (fileno (output_file)); + } + else + { + output_file = fopen (file_name, "wb"); + if (output_file == NULL) + { + error (0, errno, _("error while opening \"%s\" for writing"), + file_name); + return 1; + } + } + + if (output_file != NULL) + { + write_qm (output_file, mlp); + + /* Make sure nothing went wrong. */ + if (fwriteerror (output_file)) + error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"), + file_name); + } + } + + return 0; +} diff --git a/gettext-tools/src/write-qt.h b/gettext-tools/src/write-qt.h new file mode 100644 index 0000000..2b36da2 --- /dev/null +++ b/gettext-tools/src/write-qt.h @@ -0,0 +1,30 @@ +/* Writing Qt .qm files. + Copyright (C) 2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_QT_H +#define _WRITE_QT_H + +#include "message.h" + +/* Write a Qt .qm file. mlp is a list containing the messages to be output. + domain_name is the domain name, file_name is the desired file name. + Return 0 if ok, nonzero on error. */ +extern int + msgdomain_write_qt (message_list_ty *mlp, const char *canon_encoding, + const char *domain_name, const char *file_name); + +#endif /* _WRITE_QT_H */ diff --git a/gettext-tools/src/write-resources.c b/gettext-tools/src/write-resources.c new file mode 100644 index 0000000..6759c1b --- /dev/null +++ b/gettext-tools/src/write-resources.c @@ -0,0 +1,194 @@ +/* Writing C# .resources files. + Copyright (C) 2003, 2005, 2007-2009, 2011 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "write-resources.h" + +#include +#include +#include +#include +#include + +#include "error.h" +#include "xerror.h" +#include "relocatable.h" +#include "csharpexec.h" +#include "spawn-pipe.h" +#include "wait-process.h" +#include "message.h" +#include "msgfmt.h" +#include "msgl-iconv.h" +#include "po-charset.h" +#include "xalloc.h" +#include "concat-filename.h" +#include "fwriteerror.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* A .resources file has such a complex format that it's most easily generated + through the C# class ResourceWriter. So we start a C# process to execute + the WriteResource program, sending it the msgid/msgstr pairs as + NUL-terminated UTF-8 encoded strings. */ + +struct locals +{ + /* IN */ + message_list_ty *mlp; +}; + +static bool +execute_writing_input (const char *progname, + const char *prog_path, char **prog_argv, + void *private_data) +{ + struct locals *l = (struct locals *) private_data; + pid_t child; + int fd[1]; + FILE *fp; + int exitstatus; + + /* Open a pipe to the C# execution engine. */ + child = create_pipe_out (progname, prog_path, prog_argv, NULL, false, + true, true, fd); + + fp = fdopen (fd[0], "wb"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + /* Write the message list. */ + { + message_list_ty *mlp = l->mlp; + size_t j; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + fwrite (mp->msgid, 1, strlen (mp->msgid) + 1, fp); + fwrite (mp->msgstr, 1, strlen (mp->msgstr) + 1, fp); + } + } + + if (fwriteerror (fp)) + error (EXIT_FAILURE, 0, _("error while writing to %s subprocess"), + progname); + + /* Remove zombie process from process list, and retrieve exit status. */ + /* He we can ignore SIGPIPE because WriteResource either writes to a file + - then it never gets SIGPIPE - or to standard output, and in the latter + case it has no side effects other than writing to standard output. */ + exitstatus = + wait_subprocess (child, progname, true, false, true, true, NULL); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + progname, exitstatus); + + return false; +} + +int +msgdomain_write_csharp_resources (message_list_ty *mlp, + const char *canon_encoding, + const char *domain_name, + const char *file_name) +{ + /* If no entry for this domain don't even create the file. */ + if (mlp->nitems != 0) + { + /* Determine whether mlp has entries with context. */ + { + bool has_context; + size_t j; + + has_context = false; + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgctxt != NULL) + has_context = true; + if (has_context) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +message catalog has context dependent translations\n\ +but the C# .resources format doesn't support contexts\n"))); + return 1; + } + } + + /* Determine whether mlp has plural entries. */ + { + bool has_plural; + size_t j; + + has_plural = false; + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgid_plural != NULL) + has_plural = true; + if (has_plural) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +message catalog has plural form translations\n\ +but the C# .resources format doesn't support plural handling\n"))); + return 1; + } + } + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + + /* Execute the WriteResource program. */ + { + const char *args[2]; + const char *gettextexedir; + char *assembly_path; + struct locals locals; + + /* Prepare arguments. */ + args[0] = file_name; + args[1] = NULL; + + /* Make it possible to override the .exe location. This is + necessary for running the testsuite before "make install". */ + gettextexedir = getenv ("GETTEXTCSHARPEXEDIR"); + if (gettextexedir == NULL || gettextexedir[0] == '\0') + gettextexedir = relocate (LIBDIR "/gettext"); + + assembly_path = + xconcatenated_filename (gettextexedir, "msgfmt.net", ".exe"); + + locals.mlp = mlp; + + if (execute_csharp_program (assembly_path, NULL, 0, + args, + verbose > 0, false, + execute_writing_input, &locals)) + /* An error message should already have been provided. */ + exit (EXIT_FAILURE); + + free (assembly_path); + } + } + + return 0; +} diff --git a/gettext-tools/src/write-resources.h b/gettext-tools/src/write-resources.h new file mode 100644 index 0000000..7512f0f --- /dev/null +++ b/gettext-tools/src/write-resources.h @@ -0,0 +1,31 @@ +/* Writing C# .resources files. + Copyright (C) 2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_RESOURCES_H +#define _WRITE_RESOURCES_H + +#include "message.h" + +/* Output the contents of a PO file as a binary C# .resources file. + Return 0 if ok, nonzero on error. */ +extern int + msgdomain_write_csharp_resources (message_list_ty *mlp, + const char *canon_encoding, + const char *domain_name, + const char *file_name); + +#endif /* _WRITE_RESOURCES_H */ diff --git a/gettext-tools/src/write-stringtable.c b/gettext-tools/src/write-stringtable.c new file mode 100644 index 0000000..34e171f --- /dev/null +++ b/gettext-tools/src/write-stringtable.c @@ -0,0 +1,335 @@ +/* Writing NeXTstep/GNUstep .strings files. + Copyright (C) 2003, 2006-2008 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "write-stringtable.h" + +#include +#include +#include + +#include "message.h" +#include "msgl-ascii.h" +#include "msgl-iconv.h" +#include "po-charset.h" +#include "c-strstr.h" +#include "ostream.h" +#include "xvasprintf.h" +#include "write-po.h" + +/* The format of NeXTstep/GNUstep .strings files is documented in + gnustep-base-1.8.0/Tools/make_strings/Using.txt + and in the comments of method propertyListFromStringsFileFormat in + gnustep-base-1.8.0/Source/NSString.m + In summary, it's a Objective-C like file with pseudo-assignments of the form + "key" = "value"; + where the key is the msgid and the value is the msgstr. + */ + +/* Handling of comments: We copy all comments from the PO file to the + .strings file. This is not really needed; it's a service for translators + who don't like PO files and prefer to maintain the .strings file. */ + +/* Since the interpretation of text files in GNUstep depends on the locale's + encoding if they don't have a BOM, we choose one of three encodings with + a BOM: UCS-2BE, UCS-2LE, UTF-8. Since the first two of these don't cope + with all of Unicode and we don't know whether GNUstep will switch to + UTF-16 instead of UCS-2, we use UTF-8 with BOM. BOMs are bad because they + get in the way when concatenating files, but here we have no choice. */ + +/* Writes a key or value to the stream, without newline. */ +static void +write_escaped_string (ostream_t stream, const char *str) +{ + const char *str_limit = str + strlen (str); + + ostream_write_str (stream, "\""); + while (str < str_limit) + { + unsigned char c = (unsigned char) *str++; + + if (c == '\t') + ostream_write_str (stream, "\\t"); + else if (c == '\n') + ostream_write_str (stream, "\\n"); + else if (c == '\r') + ostream_write_str (stream, "\\r"); + else if (c == '\f') + ostream_write_str (stream, "\\f"); + else if (c == '\\' || c == '"') + { + char seq[2]; + seq[0] = '\\'; + seq[1] = c; + ostream_write_mem (stream, seq, 2); + } + else + { + char seq[1]; + seq[0] = c; + ostream_write_mem (stream, seq, 1); + } + } + ostream_write_str (stream, "\""); +} + +/* Writes a message to the stream. */ +static void +write_message (ostream_t stream, const message_ty *mp, + size_t page_width, bool debug) +{ + /* Print translator comment if available. */ + if (mp->comment != NULL) + { + size_t j; + + for (j = 0; j < mp->comment->nitems; ++j) + { + const char *s = mp->comment->item[j]; + + /* Test whether it is safe to output the comment in C style, or + whether we need C++ style for it. */ + if (c_strstr (s, "*/") == NULL) + { + ostream_write_str (stream, "/*"); + if (*s != '\0' && *s != '\n') + ostream_write_str (stream, " "); + ostream_write_str (stream, s); + ostream_write_str (stream, " */\n"); + } + else + do + { + const char *e; + ostream_write_str (stream, "//"); + if (*s != '\0' && *s != '\n') + ostream_write_str (stream, " "); + e = strchr (s, '\n'); + if (e == NULL) + { + ostream_write_str (stream, s); + s = NULL; + } + else + { + ostream_write_mem (stream, s, e - s); + s = e + 1; + } + ostream_write_str (stream, "\n"); + } + while (s != NULL); + } + } + + /* Print xgettext extracted comments. */ + if (mp->comment_dot != NULL) + { + size_t j; + + for (j = 0; j < mp->comment_dot->nitems; ++j) + { + const char *s = mp->comment_dot->item[j]; + + /* Test whether it is safe to output the comment in C style, or + whether we need C++ style for it. */ + if (c_strstr (s, "*/") == NULL) + { + ostream_write_str (stream, "/* Comment: "); + ostream_write_str (stream, s); + ostream_write_str (stream, " */\n"); + } + else + { + bool first = true; + do + { + const char *e; + ostream_write_str (stream, "//"); + if (first || (*s != '\0' && *s != '\n')) + ostream_write_str (stream, " "); + if (first) + ostream_write_str (stream, "Comment: "); + e = strchr (s, '\n'); + if (e == NULL) + { + ostream_write_str (stream, s); + s = NULL; + } + else + { + ostream_write_mem (stream, s, e - s); + s = e + 1; + } + ostream_write_str (stream, "\n"); + first = false; + } + while (s != NULL); + } + } + } + + /* Print the file position comments. */ + if (mp->filepos_count != 0) + { + size_t j; + + for (j = 0; j < mp->filepos_count; ++j) + { + lex_pos_ty *pp = &mp->filepos[j]; + const char *cp = pp->file_name; + char *str; + + while (cp[0] == '.' && cp[1] == '/') + cp += 2; + str = xasprintf ("/* File: %s:%ld */\n", cp, (long) pp->line_number); + ostream_write_str (stream, str); + free (str); + } + } + + /* Print flag information in special comment. */ + if (mp->is_fuzzy || mp->msgstr[0] == '\0') + ostream_write_str (stream, "/* Flag: untranslated */\n"); + if (mp->obsolete) + ostream_write_str (stream, "/* Flag: unmatched */\n"); + { + size_t i; + for (i = 0; i < NFORMATS; i++) + if (significant_format_p (mp->is_format[i])) + { + ostream_write_str (stream, "/* Flag: "); + ostream_write_str (stream, + make_format_description_string (mp->is_format[i], + format_language[i], + debug)); + ostream_write_str (stream, " */\n"); + } + } + if (has_range_p (mp->range)) + { + char *string; + + ostream_write_str (stream, "/* Flag: "); + string = make_range_description_string (mp->range); + ostream_write_str (stream, string); + free (string); + ostream_write_str (stream, " */\n"); + } + + /* Now write the untranslated string and the translated string. */ + write_escaped_string (stream, mp->msgid); + ostream_write_str (stream, " = "); + if (mp->msgstr[0] != '\0') + { + if (mp->is_fuzzy) + { + /* Output the msgid as value, so that at runtime the untranslated + string is returned. */ + write_escaped_string (stream, mp->msgid); + + /* Output the msgstr as a comment, so that at runtime + propertyListFromStringsFileFormat ignores it. */ + if (c_strstr (mp->msgstr, "*/") == NULL) + { + ostream_write_str (stream, " /* = "); + write_escaped_string (stream, mp->msgstr); + ostream_write_str (stream, " */"); + } + else + { + ostream_write_str (stream, "; // = "); + write_escaped_string (stream, mp->msgstr); + } + } + else + write_escaped_string (stream, mp->msgstr); + } + else + { + /* Output the msgid as value, so that at runtime the untranslated + string is returned. */ + write_escaped_string (stream, mp->msgid); + } + ostream_write_str (stream, ";"); + + ostream_write_str (stream, "\n"); +} + +/* Writes an entire message list to the stream. */ +static void +write_stringtable (ostream_t stream, message_list_ty *mlp, + const char *canon_encoding, size_t page_width, bool debug) +{ + bool blank_line; + size_t j; + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + + /* Output the BOM. */ + if (!is_ascii_message_list (mlp)) + ostream_write_str (stream, "\xef\xbb\xbf"); + + /* Loop through the messages. */ + blank_line = false; + for (j = 0; j < mlp->nitems; ++j) + { + const message_ty *mp = mlp->item[j]; + + if (mp->msgid_plural == NULL) + { + if (blank_line) + ostream_write_str (stream, "\n"); + + write_message (stream, mp, page_width, debug); + + blank_line = true; + } + } +} + +/* Output the contents of a PO file in .strings syntax. */ +static void +msgdomain_list_print_stringtable (msgdomain_list_ty *mdlp, ostream_t stream, + size_t page_width, bool debug) +{ + message_list_ty *mlp; + + if (mdlp->nitems == 1) + mlp = mdlp->item[0]->messages; + else + mlp = message_list_alloc (false); + write_stringtable (stream, mlp, mdlp->encoding, page_width, debug); +} + +/* Describes a PO file in .strings syntax. */ +const struct catalog_output_format output_format_stringtable = +{ + msgdomain_list_print_stringtable, /* print */ + true, /* requires_utf8 */ + false, /* supports_color */ + false, /* supports_multiple_domains */ + false, /* supports_contexts */ + false, /* supports_plurals */ + false, /* sorts_obsoletes_to_end */ + false, /* alternative_is_po */ + false /* alternative_is_java_class */ +}; diff --git a/gettext-tools/src/write-stringtable.h b/gettext-tools/src/write-stringtable.h new file mode 100644 index 0000000..3130467 --- /dev/null +++ b/gettext-tools/src/write-stringtable.h @@ -0,0 +1,26 @@ +/* Writing NeXTstep/GNUstep .strings files. + Copyright (C) 2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_STRINGTABLE_H +#define _WRITE_STRINGTABLE_H + +#include "write-catalog.h" + +/* Describes a PO file in .strings syntax. */ +extern DLL_VARIABLE const struct catalog_output_format output_format_stringtable; + +#endif /* _WRITE_STRINGTABLE_H */ diff --git a/gettext-tools/src/write-tcl.c b/gettext-tools/src/write-tcl.c new file mode 100644 index 0000000..db582c6 --- /dev/null +++ b/gettext-tools/src/write-tcl.c @@ -0,0 +1,229 @@ +/* Writing tcl/msgcat .msg files. + Copyright (C) 2002-2003, 2005, 2007-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +/* Specification. */ +#include "write-tcl.h" + +#include +#include +#include +#include +#include + +#include "error.h" +#include "xerror.h" +#include "message.h" +#include "msgl-iconv.h" +#include "po-charset.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "concat-filename.h" +#include "fwriteerror.h" +#include "unistr.h" +#include "gettext.h" + +#define _(str) gettext (str) + + +/* Write a string in Tcl Unicode notation to the given stream. + Tcl 8 uses Unicode for its internal string representation. + In tcl-8.3.3, the .msg files are read in using the locale dependent + encoding. The only way to specify strings in an encoding independent + form is the \unnnn notation. Newer tcl versions have this fixed: + they read the .msg files in UTF-8 encoding. */ +static void +write_tcl_string (FILE *stream, const char *str) +{ + static const char hexdigit[] = "0123456789abcdef"; + const char *str_limit = str + strlen (str); + + fprintf (stream, "\""); + while (str < str_limit) + { + ucs4_t uc; + unsigned int count; + count = u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); + if (uc < 0x10000) + { + /* Single UCS-2 'char'. */ + if (uc == 0x000a) + fprintf (stream, "\\n"); + else if (uc == 0x000d) + fprintf (stream, "\\r"); + else if (uc == 0x0022) + fprintf (stream, "\\\""); + else if (uc == 0x0024) + fprintf (stream, "\\$"); + else if (uc == 0x005b) + fprintf (stream, "\\["); + else if (uc == 0x005c) + fprintf (stream, "\\\\"); + else if (uc == 0x005d) + fprintf (stream, "\\]"); + /* No need to escape '{' and '}' because we don't have opening + braces outside the strings. */ +#if 0 + else if (uc == 0x007b) + fprintf (stream, "\\{"); + else if (uc == 0x007d) + fprintf (stream, "\\}"); +#endif + else if (uc >= 0x0020 && uc < 0x007f) + fprintf (stream, "%c", (int) uc); + else + fprintf (stream, "\\u%c%c%c%c", + hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f], + hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]); + } + else + /* The \unnnn notation doesn't support characters >= 0x10000. + We output them as UTF-8 byte sequences and hope that either + the Tcl version reading them will be new enough or that the + user is using an UTF-8 locale. */ + fwrite (str, 1, count, stream); + str += count; + } + fprintf (stream, "\""); +} + + +static void +write_msg (FILE *output_file, message_list_ty *mlp, const char *locale_name) +{ + size_t j; + + /* We don't care about esthetic formattic of the output (like respecting + a maximum line width, or including the translator comments) because + the \unnnn notation is unesthetic anyway. Translators shall edit + the PO file. */ + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (is_header (mp)) + /* Tcl's msgcat unit ignores this, but msgunfmt needs it. */ + fprintf (output_file, "set ::msgcat::header "); + else + { + fprintf (output_file, "::msgcat::mcset %s ", locale_name); + write_tcl_string (output_file, mp->msgid); + fprintf (output_file, " "); + } + write_tcl_string (output_file, mp->msgstr); + fprintf (output_file, "\n"); + } +} + +int +msgdomain_write_tcl (message_list_ty *mlp, const char *canon_encoding, + const char *locale_name, + const char *directory) +{ + /* If no entry for this domain don't even create the file. */ + if (mlp->nitems == 0) + return 0; + + /* Determine whether mlp has entries with context. */ + { + bool has_context; + size_t j; + + has_context = false; + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgctxt != NULL) + has_context = true; + if (has_context) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +message catalog has context dependent translations\n\ +but the Tcl message catalog format doesn't support contexts\n"))); + return 1; + } + } + + /* Determine whether mlp has plural entries. */ + { + bool has_plural; + size_t j; + + has_plural = false; + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgid_plural != NULL) + has_plural = true; + if (has_plural) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +message catalog has plural form translations\n\ +but the Tcl message catalog format doesn't support plural handling\n"))); + return 1; + } + } + + /* Convert the messages to Unicode. */ + iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); + + /* Now create the file. */ + { + size_t len; + char *frobbed_locale_name; + char *p; + char *file_name; + FILE *output_file; + + /* Convert the locale name to lowercase and remove any encoding. */ + len = strlen (locale_name); + frobbed_locale_name = (char *) xmalloca (len + 1); + memcpy (frobbed_locale_name, locale_name, len + 1); + for (p = frobbed_locale_name; *p != '\0'; p++) + if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + else if (*p == '.') + { + *p = '\0'; + break; + } + + file_name = xconcatenated_filename (directory, frobbed_locale_name, ".msg"); + + output_file = fopen (file_name, "w"); + if (output_file == NULL) + { + error (0, errno, _("error while opening \"%s\" for writing"), + file_name); + freea (frobbed_locale_name); + return 1; + } + + write_msg (output_file, mlp, frobbed_locale_name); + + /* Make sure nothing went wrong. */ + if (fwriteerror (output_file)) + error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"), + file_name); + + freea (frobbed_locale_name); + } + + return 0; +} diff --git a/gettext-tools/src/write-tcl.h b/gettext-tools/src/write-tcl.h new file mode 100644 index 0000000..e6a3dce --- /dev/null +++ b/gettext-tools/src/write-tcl.h @@ -0,0 +1,32 @@ +/* Writing tcl/msgcat .msg files. + Copyright (C) 2002-2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _WRITE_TCL_H +#define _WRITE_TCL_H + +#include "message.h" + +/* Write a Tcl msg file. mlp is a list containing the messages to be output. + locale_name is the locale name (with underscore separators), directory is + the base directory. + Return 0 if ok, nonzero on error. */ +extern int + msgdomain_write_tcl (message_list_ty *mlp, const char *canon_encoding, + const char *locale_name, + const char *directory); + +#endif /* _WRITE_TCL_H */ diff --git a/gettext-tools/src/x-awk.c b/gettext-tools/src/x-awk.c new file mode 100644 index 0000000..6a6a9dc --- /dev/null +++ b/gettext-tools/src/x-awk.c @@ -0,0 +1,887 @@ +/* xgettext awk backend. + Copyright (C) 2002-2003, 2005-2009 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-awk.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "error-progname.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(s) gettext(s) + + +/* The awk syntax is defined in the gawk manual page and documentation. + See also gawk/awkgram.y. */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_awk_extract_all () +{ + extract_all = true; +} + + +void +x_awk_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_awk_keyword ("dcgettext"); + x_awk_keyword ("dcngettext:1,2"); + default_keywords = false; + } +} + +void +init_flag_table_awk () +{ + xgettext_record_flag ("dcgettext:1:pass-awk-format"); + xgettext_record_flag ("dcngettext:1:pass-awk-format"); + xgettext_record_flag ("dcngettext:2:pass-awk-format"); + xgettext_record_flag ("printf:1:awk-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* 1. line_number handling. */ + +static int +phase1_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + + if (c == '\n') + line_number++; + + return c; +} + +/* Supports only one pushback character. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + --line_number; + + ungetc (c, fp); + } +} + + +/* 2. Replace each comment that is not inside a string literal or regular + expression with a newline character. We need to remember the comment + for later, because it may be attached to a keyword string. */ + +static int +phase2_getc () +{ + static char *buffer; + static size_t bufmax; + size_t buflen; + int lineno; + int c; + + c = phase1_getc (); + if (c == '#') + { + buflen = 0; + lineno = line_number; + for (;;) + { + c = phase1_getc (); + if (c == '\n' || c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + { + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; + } + } + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); + last_comment_line = lineno; + } + return c; +} + +/* Supports only one pushback character. */ +static void +phase2_ungetc (int c) +{ + if (c != EOF) + phase1_ungetc (c); +} + + +/* ========================== Reading of tokens. ========================== */ + + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_comma, /* , */ + token_type_string, /* "abc" */ + token_type_i18nstring, /* _"abc" */ + token_type_symbol, /* symbol, number */ + token_type_semicolon, /* ; */ + token_type_other /* regexp, misc. operator */ +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_{symbol,string,i18nstring} */ + int line_number; +}; + + +/* 7. Replace escape sequences within character strings with their + single character equivalents. */ + +#define P7_QUOTES (1000 + '"') + +static int +phase7_getc () +{ + int c; + + for (;;) + { + /* Use phase 1, because phase 2 elides comments. */ + c = phase1_getc (); + + if (c == EOF || c == '\n') + break; + if (c == '"') + return P7_QUOTES; + if (c != '\\') + return c; + c = phase1_getc (); + if (c == EOF) + break; + if (c != '\n') + switch (c) + { + case 'a': + return '\a'; + case 'b': + return '\b'; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'v': + return '\v'; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + { + int n = c - '0'; + + c = phase1_getc (); + if (c != EOF) + { + if (c >= '0' && c <= '7') + { + n = (n << 3) + (c - '0'); + c = phase1_getc (); + if (c != EOF) + { + if (c >= '0' && c <= '7') + n = (n << 3) + (c - '0'); + else + phase1_ungetc (c); + } + } + else + phase1_ungetc (c); + } + return (unsigned char) n; + } + case 'x': + { + int n = 0; + + for (;;) + { + c = phase1_getc (); + if (c == EOF) + break; + else if (c >= '0' && c <= '9') + n = (n << 4) + (c - '0'); + else if (c >= 'A' && c <= 'F') + n = (n << 4) + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + n = (n << 4) + (c - 'a' + 10); + else + { + phase1_ungetc (c); + break; + } + } + return (unsigned char) n; + } + default: + return c; + } + } + + phase1_ungetc (c); + error_with_progname = false; + error (0, 0, _("%s:%d: warning: unterminated string"), logical_file_name, + line_number); + error_with_progname = true; + return P7_QUOTES; +} + + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + switch (tp->type) + { + case token_type_string: + case token_type_i18nstring: + case token_type_symbol: + free (tp->string); + break; + default: + break; + } +} + + +/* Combine characters into tokens. Discard whitespace. */ + +/* There is an ambiguity about '/': It can start a division operator ('/' or + '/=') or it can start a regular expression. The distinction is important + because inside regular expressions, '#' and '"' lose its special meanings. + If you look at the awk grammar, you see that the operator is only allowed + right after a 'variable' or 'simp_exp' nonterminal, and these nonterminals + can only end in the NAME, LENGTH, YSTRING, YNUMBER, ')', ']' terminals. + So we prefer the division operator interpretation only right after + symbol, string, number, ')', ']', with whitespace but no newline allowed + in between. */ +static bool prefer_division_over_regexp; + +static void +x_awk_lex (token_ty *tp) +{ + static char *buffer; + static int bufmax; + int bufpos; + int c; + + for (;;) + { + tp->line_number = line_number; + c = phase2_getc (); + + switch (c) + { + case EOF: + tp->type = token_type_eof; + return; + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* Newline is not allowed inside expressions. It usually + introduces a fresh statement. + FIXME: Newlines after any of ',' '{' '?' ':' '||' '&&' 'do' 'else' + does *not* introduce a fresh statement. */ + prefer_division_over_regexp = false; + /* FALLTHROUGH */ + case '\t': + case ' ': + /* Ignore whitespace and comments. */ + continue; + + case '\\': + /* Backslash ought to be immediately followed by a newline. */ + continue; + } + + last_non_comment_line = tp->line_number; + + switch (c) + { + case '.': + { + int c2 = phase2_getc (); + phase2_ungetc (c2); + if (!(c2 >= '0' && c2 <= '9')) + { + + tp->type = token_type_other; + prefer_division_over_regexp = false; + return; + } + } + /* FALLTHROUGH */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* Symbol, or part of a number. */ + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase2_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + default: + if (bufpos == 1 && buffer[0] == '_' && c == '"') + { + tp->type = token_type_i18nstring; + goto case_string; + } + phase2_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + tp->string = xstrdup (buffer); + tp->type = token_type_symbol; + /* Most identifiers can be variable names; after them we must + interpret '/' as division operator. But for awk's builtin + keywords we have three cases: + (a) Must interpret '/' as division operator. "length". + (b) Must interpret '/' as start of a regular expression. + "do", "exit", "print", "printf", "return". + (c) '/' after this keyword in invalid anyway. All others. + I used the following script for the distinction. + for k in $awk_keywords; do + echo; echo $k; awk "function foo () { $k / 10 }" < /dev/null + done + */ + if (strcmp (buffer, "do") == 0 + || strcmp (buffer, "exit") == 0 + || strcmp (buffer, "print") == 0 + || strcmp (buffer, "printf") == 0 + || strcmp (buffer, "return") == 0) + prefer_division_over_regexp = false; + else + prefer_division_over_regexp = true; + return; + + case '"': + tp->type = token_type_string; + case_string: + bufpos = 0; + for (;;) + { + c = phase7_getc (); + if (c == EOF || c == P7_QUOTES) + break; + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + tp->string = xstrdup (buffer); + prefer_division_over_regexp = true; + return; + + case '(': + tp->type = token_type_lparen; + prefer_division_over_regexp = false; + return; + + case ')': + tp->type = token_type_rparen; + prefer_division_over_regexp = true; + return; + + case ',': + tp->type = token_type_comma; + prefer_division_over_regexp = false; + return; + + case ';': + tp->type = token_type_semicolon; + prefer_division_over_regexp = false; + return; + + case ']': + tp->type = token_type_other; + prefer_division_over_regexp = true; + return; + + case '/': + if (!prefer_division_over_regexp) + { + /* Regular expression. + Counting brackets is non-trivial. [[] is balanced, and so is + [\]]. Also, /[/]/ is balanced and ends at the third slash. + Do not count [ or ] if either one is preceded by a \. + A '[' should be counted if + a) it is the first one so far (brackets == 0), or + b) it is the '[' in '[:'. + A ']' should be counted if not preceded by a \. + According to POSIX, []] is how you put a ] into a set. + Try to handle that too. + */ + int brackets = 0; + bool pos0 = true; /* true at start of regexp */ + bool pos1_open = false; /* true after [ at start of regexp */ + bool pos2_open_not = false; /* true after [^ at start of regexp */ + + for (;;) + { + c = phase1_getc (); + + if (c == EOF || c == '\n') + { + phase1_ungetc (c); + error_with_progname = false; + error (0, 0, _("%s:%d: warning: unterminated regular expression"), + logical_file_name, line_number); + error_with_progname = true; + break; + } + else if (c == '[') + { + if (brackets == 0) + brackets++; + else + { + c = phase1_getc (); + if (c == ':') + brackets++; + phase1_ungetc (c); + } + if (pos0) + { + pos0 = false; + pos1_open = true; + continue; + } + } + else if (c == ']') + { + if (!(pos1_open || pos2_open_not)) + brackets--; + } + else if (c == '^') + { + if (pos1_open) + { + pos1_open = false; + pos2_open_not = true; + continue; + } + } + else if (c == '\\') + { + c = phase1_getc (); + /* Backslash-newline is valid and ignored. */ + } + else if (c == '/') + { + if (brackets <= 0) + break; + } + + pos0 = false; + pos1_open = false; + pos2_open_not = false; + } + + tp->type = token_type_other; + prefer_division_over_regexp = false; + return; + } + /* FALLTHROUGH */ + + default: + /* We could carefully recognize each of the 2 and 3 character + operators, but it is not necessary, as we only need to recognize + gettext invocations. Don't bother. */ + tp->type = token_type_other; + prefer_division_over_regexp = false; + return; + } + } +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid C or C++ program, and leave the complaints about + the grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + + +/* Extract messages until the next balanced closing parenthesis. + Extracted messages are added to MLP. + Return true upon eof, false upon closing parenthesis. */ +static bool +extract_parenthesized (message_list_ty *mlp, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Whether to implicitly assume the next tokens are arguments even without + a '('. */ + bool next_is_argument = false; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_awk_lex (&token); + + if (next_is_argument && token.type != token_type_lparen) + { + /* An argument list starts, even though there is no '('. */ + context_iter = next_context_iter; + outer_context = inner_context; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + } + + switch (token.type) + { + case token_type_symbol: + { + void *keyword_value; + + if (hash_find_entry (&keywords, token.string, strlen (token.string), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + } + else + state = 0; + } + next_is_argument = + (strcmp (token.string, "print") == 0 + || strcmp (token.string, "printf") == 0); + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, strlen (token.string))); + free (token.string); + continue; + + case token_type_lparen: + if (extract_parenthesized (mlp, inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + arglist_parser_done (argparser, arg); + return true; + } + next_is_argument = false; + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rparen: + arglist_parser_done (argparser, arg); + return false; + + case token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_is_argument = false; + next_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case token_type_string: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + if (extract_all) + remember_a_message (mlp, NULL, token.string, inner_context, &pos, + NULL, savable_comment); + else + arglist_parser_remember (argparser, arg, token.string, + inner_context, + pos.file_name, pos.line_number, + savable_comment); + } + next_is_argument = false; + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_i18nstring: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + remember_a_message (mlp, NULL, token.string, inner_context, &pos, + NULL, savable_comment); + } + next_is_argument = false; + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_semicolon: + /* An argument list ends, and a new statement begins. */ + /* FIXME: Should handle newline that acts as statement separator + in the same way. */ + /* FIXME: Instead of resetting outer_context here, it may be better + to recurse in the next_is_argument handling above, waiting for + the next semicolon or other statement terminator. */ + outer_context = null_context; + context_iter = null_context_list_iterator; + next_is_argument = false; + next_context_iter = passthrough_context_list_iterator; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + state = 0; + continue; + + case token_type_eof: + arglist_parser_done (argparser, arg); + return true; + + case token_type_other: + next_is_argument = false; + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + default: + abort (); + } + } +} + + +void +extract_awk (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + prefer_division_over_regexp = false; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When extract_parenthesized returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_parenthesized (mlp, null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-awk.h b/gettext-tools/src/x-awk.h new file mode 100644 index 0000000..642c0e2 --- /dev/null +++ b/gettext-tools/src/x-awk.h @@ -0,0 +1,51 @@ +/* xgettext awk backend. + Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_AWK \ + { "awk", "awk" }, \ + +#define SCANNERS_AWK \ + { "awk", extract_awk, \ + &flag_table_awk, &formatstring_awk, NULL, NULL }, \ + +/* Scan an awk file and add its translatable strings to mdlp. */ +extern void extract_awk (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_awk_keyword (const char *keyword); +extern void x_awk_extract_all (void); + +extern void init_flag_table_awk (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-c.c b/gettext-tools/src/x-c.c new file mode 100644 index 0000000..001cfd9 --- /dev/null +++ b/gettext-tools/src/x-c.c @@ -0,0 +1,2174 @@ +/* xgettext C/C++/ObjectiveC backend. + Copyright (C) 1995-1998, 2000-2009, 2012 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-c.h" + +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "error-progname.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "hash.h" +#include "po-charset.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The ANSI C standard defines several phases of translation: + + 1. Terminate line by \n, regardless of the external representation + of a text line. Stdio does this for us. + + 2. Convert trigraphs to their single character equivalents. + + 3. Concatenate each line ending in backslash (\) with the following + line. + + 4. Replace each comment with a space character. + + 5. Parse each resulting logical line as preprocessing tokens a + white space. + + 6. Recognize and carry out directives (it also expands macros on + non-directive lines, which we do not do here). + + 7. Replaces escape sequences within character strings with their + single character equivalents (we do this in step 5, because we + don't have to worry about the #include argument). + + 8. Concatenates adjacent string literals to form single string + literals (because we don't expand macros, there are a few things + we will miss). + + 9. Converts the remaining preprocessing tokens to C tokens and + discards any white space from the translation unit. + + This lexer implements the above, and presents the scanner (in + xgettext.c) with a stream of C tokens. The comments are + accumulated in a buffer, and given to xgettext when asked for. */ + + +/* ========================= Lexer customization. ========================= */ + +static bool trigraphs = false; + +void +x_c_trigraphs () +{ + trigraphs = true; +} + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table c_keywords; +static hash_table objc_keywords; +static bool default_keywords = true; + + +void +x_c_extract_all () +{ + extract_all = true; +} + + +static void +add_keyword (const char *name, hash_table *keywords) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords->table == NULL) + hash_init (keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (keywords, name, end - name, &shape); + } +} + +void +x_c_keyword (const char *name) +{ + add_keyword (name, &c_keywords); +} + +void +x_objc_keyword (const char *name) +{ + add_keyword (name, &objc_keywords); +} + +/* Finish initializing the keywords hash tables. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_c_keyword ("gettext"); + x_c_keyword ("dgettext:2"); + x_c_keyword ("dcgettext:2"); + x_c_keyword ("ngettext:1,2"); + x_c_keyword ("dngettext:2,3"); + x_c_keyword ("dcngettext:2,3"); + x_c_keyword ("gettext_noop"); + x_c_keyword ("pgettext:1c,2"); + x_c_keyword ("dpgettext:2c,3"); + x_c_keyword ("dcpgettext:2c,3"); + x_c_keyword ("npgettext:1c,2,3"); + x_c_keyword ("dnpgettext:2c,3,4"); + x_c_keyword ("dcnpgettext:2c,3,4"); + + x_objc_keyword ("gettext"); + x_objc_keyword ("dgettext:2"); + x_objc_keyword ("dcgettext:2"); + x_objc_keyword ("ngettext:1,2"); + x_objc_keyword ("dngettext:2,3"); + x_objc_keyword ("dcngettext:2,3"); + x_objc_keyword ("gettext_noop"); + x_objc_keyword ("pgettext:1c,2"); + x_objc_keyword ("dpgettext:2c,3"); + x_objc_keyword ("dcpgettext:2c,3"); + x_objc_keyword ("npgettext:1c,2,3"); + x_objc_keyword ("dnpgettext:2c,3,4"); + x_objc_keyword ("dcnpgettext:2c,3,4"); + x_objc_keyword ("NSLocalizedString"); /* similar to gettext */ + x_objc_keyword ("_"); /* similar to gettext */ + x_objc_keyword ("NSLocalizedStaticString"); /* similar to gettext_noop */ + x_objc_keyword ("__"); /* similar to gettext_noop */ + + default_keywords = false; + } +} + +void +init_flag_table_c () +{ + xgettext_record_flag ("gettext:1:pass-c-format"); + xgettext_record_flag ("dgettext:2:pass-c-format"); + xgettext_record_flag ("dcgettext:2:pass-c-format"); + xgettext_record_flag ("ngettext:1:pass-c-format"); + xgettext_record_flag ("ngettext:2:pass-c-format"); + xgettext_record_flag ("dngettext:2:pass-c-format"); + xgettext_record_flag ("dngettext:3:pass-c-format"); + xgettext_record_flag ("dcngettext:2:pass-c-format"); + xgettext_record_flag ("dcngettext:3:pass-c-format"); + xgettext_record_flag ("gettext_noop:1:pass-c-format"); + xgettext_record_flag ("pgettext:2:pass-c-format"); + xgettext_record_flag ("dpgettext:3:pass-c-format"); + xgettext_record_flag ("dcpgettext:3:pass-c-format"); + xgettext_record_flag ("npgettext:2:pass-c-format"); + xgettext_record_flag ("npgettext:3:pass-c-format"); + xgettext_record_flag ("dnpgettext:3:pass-c-format"); + xgettext_record_flag ("dnpgettext:4:pass-c-format"); + xgettext_record_flag ("dcnpgettext:3:pass-c-format"); + xgettext_record_flag ("dcnpgettext:4:pass-c-format"); + + /* */ + xgettext_record_flag ("fprintf:2:c-format"); + xgettext_record_flag ("vfprintf:2:c-format"); + xgettext_record_flag ("printf:1:c-format"); + xgettext_record_flag ("vprintf:1:c-format"); + xgettext_record_flag ("sprintf:2:c-format"); + xgettext_record_flag ("vsprintf:2:c-format"); + xgettext_record_flag ("snprintf:3:c-format"); + xgettext_record_flag ("vsnprintf:3:c-format"); +#if 0 /* These functions are not standard. */ + /* */ + xgettext_record_flag ("asprintf:2:c-format"); + xgettext_record_flag ("vasprintf:2:c-format"); + xgettext_record_flag ("dprintf:2:c-format"); + xgettext_record_flag ("vdprintf:2:c-format"); + xgettext_record_flag ("obstack_printf:2:c-format"); + xgettext_record_flag ("obstack_vprintf:2:c-format"); + /* */ + xgettext_record_flag ("error:3:c-format"); + xgettext_record_flag ("error_at_line:5:c-format"); + /* */ + xgettext_record_flag ("argp_error:2:c-format"); + xgettext_record_flag ("argp_failure:2:c-format"); +#endif + + xgettext_record_flag ("gettext:1:pass-qt-format"); + xgettext_record_flag ("dgettext:2:pass-qt-format"); + xgettext_record_flag ("dcgettext:2:pass-qt-format"); + xgettext_record_flag ("ngettext:1:pass-qt-format"); + xgettext_record_flag ("ngettext:2:pass-qt-format"); + xgettext_record_flag ("dngettext:2:pass-qt-format"); + xgettext_record_flag ("dngettext:3:pass-qt-format"); + xgettext_record_flag ("dcngettext:2:pass-qt-format"); + xgettext_record_flag ("dcngettext:3:pass-qt-format"); + xgettext_record_flag ("gettext_noop:1:pass-qt-format"); + xgettext_record_flag ("pgettext:2:pass-qt-format"); + xgettext_record_flag ("dpgettext:3:pass-qt-format"); + xgettext_record_flag ("dcpgettext:3:pass-qt-format"); + xgettext_record_flag ("npgettext:2:pass-qt-format"); + xgettext_record_flag ("npgettext:3:pass-qt-format"); + xgettext_record_flag ("dnpgettext:3:pass-qt-format"); + xgettext_record_flag ("dnpgettext:4:pass-qt-format"); + xgettext_record_flag ("dcnpgettext:3:pass-qt-format"); + xgettext_record_flag ("dcnpgettext:4:pass-qt-format"); + + xgettext_record_flag ("gettext:1:pass-kde-format"); + xgettext_record_flag ("dgettext:2:pass-kde-format"); + xgettext_record_flag ("dcgettext:2:pass-kde-format"); + xgettext_record_flag ("ngettext:1:pass-kde-format"); + xgettext_record_flag ("ngettext:2:pass-kde-format"); + xgettext_record_flag ("dngettext:2:pass-kde-format"); + xgettext_record_flag ("dngettext:3:pass-kde-format"); + xgettext_record_flag ("dcngettext:2:pass-kde-format"); + xgettext_record_flag ("dcngettext:3:pass-kde-format"); + xgettext_record_flag ("gettext_noop:1:pass-kde-format"); + xgettext_record_flag ("pgettext:2:pass-kde-format"); + xgettext_record_flag ("dpgettext:3:pass-kde-format"); + xgettext_record_flag ("dcpgettext:3:pass-kde-format"); + xgettext_record_flag ("npgettext:2:pass-kde-format"); + xgettext_record_flag ("npgettext:3:pass-kde-format"); + xgettext_record_flag ("dnpgettext:3:pass-kde-format"); + xgettext_record_flag ("dnpgettext:4:pass-kde-format"); + xgettext_record_flag ("dcnpgettext:3:pass-kde-format"); + xgettext_record_flag ("dcnpgettext:4:pass-kde-format"); + + xgettext_record_flag ("gettext:1:pass-boost-format"); + xgettext_record_flag ("dgettext:2:pass-boost-format"); + xgettext_record_flag ("dcgettext:2:pass-boost-format"); + xgettext_record_flag ("ngettext:1:pass-boost-format"); + xgettext_record_flag ("ngettext:2:pass-boost-format"); + xgettext_record_flag ("dngettext:2:pass-boost-format"); + xgettext_record_flag ("dngettext:3:pass-boost-format"); + xgettext_record_flag ("dcngettext:2:pass-boost-format"); + xgettext_record_flag ("dcngettext:3:pass-boost-format"); + xgettext_record_flag ("gettext_noop:1:pass-boost-format"); + xgettext_record_flag ("pgettext:2:pass-boost-format"); + xgettext_record_flag ("dpgettext:3:pass-boost-format"); + xgettext_record_flag ("dcpgettext:3:pass-boost-format"); + xgettext_record_flag ("npgettext:2:pass-boost-format"); + xgettext_record_flag ("npgettext:3:pass-boost-format"); + xgettext_record_flag ("dnpgettext:3:pass-boost-format"); + xgettext_record_flag ("dnpgettext:4:pass-boost-format"); + xgettext_record_flag ("dcnpgettext:3:pass-boost-format"); + xgettext_record_flag ("dcnpgettext:4:pass-boost-format"); + + /* */ + xgettext_record_flag ("format:1:boost-format"); +} + +void +init_flag_table_objc () +{ + /* Since the settings done in init_flag_table_c() also have an effect for + the ObjectiveC parser, we don't have to repeat them here. */ + xgettext_record_flag ("gettext:1:pass-objc-format"); + xgettext_record_flag ("dgettext:2:pass-objc-format"); + xgettext_record_flag ("dcgettext:2:pass-objc-format"); + xgettext_record_flag ("ngettext:1:pass-objc-format"); + xgettext_record_flag ("ngettext:2:pass-objc-format"); + xgettext_record_flag ("dngettext:2:pass-objc-format"); + xgettext_record_flag ("dngettext:3:pass-objc-format"); + xgettext_record_flag ("dcngettext:2:pass-objc-format"); + xgettext_record_flag ("dcngettext:3:pass-objc-format"); + xgettext_record_flag ("gettext_noop:1:pass-objc-format"); + xgettext_record_flag ("pgettext:2:pass-objc-format"); + xgettext_record_flag ("dpgettext:3:pass-objc-format"); + xgettext_record_flag ("dcpgettext:3:pass-objc-format"); + xgettext_record_flag ("npgettext:2:pass-objc-format"); + xgettext_record_flag ("npgettext:3:pass-objc-format"); + xgettext_record_flag ("dnpgettext:3:pass-objc-format"); + xgettext_record_flag ("dnpgettext:4:pass-objc-format"); + xgettext_record_flag ("dcnpgettext:3:pass-objc-format"); + xgettext_record_flag ("dcnpgettext:4:pass-objc-format"); + xgettext_record_flag ("NSLocalizedString:1:pass-c-format"); + xgettext_record_flag ("NSLocalizedString:1:pass-objc-format"); + xgettext_record_flag ("_:1:pass-c-format"); + xgettext_record_flag ("_:1:pass-objc-format"); + xgettext_record_flag ("stringWithFormat::1:objc-format"); + xgettext_record_flag ("initWithFormat::1:objc-format"); + xgettext_record_flag ("stringByAppendingFormat::1:objc-format"); + xgettext_record_flag ("localizedStringWithFormat::1:objc-format"); + xgettext_record_flag ("appendFormat::1:objc-format"); +} + +void +init_flag_table_gcc_internal () +{ + xgettext_record_flag ("gettext:1:pass-gcc-internal-format"); + xgettext_record_flag ("dgettext:2:pass-gcc-internal-format"); + xgettext_record_flag ("dcgettext:2:pass-gcc-internal-format"); + xgettext_record_flag ("ngettext:1:pass-gcc-internal-format"); + xgettext_record_flag ("ngettext:2:pass-gcc-internal-format"); + xgettext_record_flag ("dngettext:2:pass-gcc-internal-format"); + xgettext_record_flag ("dngettext:3:pass-gcc-internal-format"); + xgettext_record_flag ("dcngettext:2:pass-gcc-internal-format"); + xgettext_record_flag ("dcngettext:3:pass-gcc-internal-format"); + xgettext_record_flag ("gettext_noop:1:pass-gcc-internal-format"); + xgettext_record_flag ("pgettext:2:pass-gcc-internal-format"); + xgettext_record_flag ("dpgettext:3:pass-gcc-internal-format"); + xgettext_record_flag ("dcpgettext:3:pass-gcc-internal-format"); + xgettext_record_flag ("npgettext:2:pass-gcc-internal-format"); + xgettext_record_flag ("npgettext:3:pass-gcc-internal-format"); + xgettext_record_flag ("dnpgettext:3:pass-gcc-internal-format"); + xgettext_record_flag ("dnpgettext:4:pass-gcc-internal-format"); + xgettext_record_flag ("dcnpgettext:3:pass-gcc-internal-format"); + xgettext_record_flag ("dcnpgettext:4:pass-gcc-internal-format"); +#if 0 /* This should better be done inside GCC. */ + /* grepping for ATTRIBUTE_PRINTF in gcc-3.3/gcc/?*.h */ + /* c-format.c */ + xgettext_record_flag ("status_warning:2:gcc-internal-format"); + /* c-tree.h */ + xgettext_record_flag ("pedwarn_c99:1:pass-gcc-internal-format"); + /* collect2.h */ + //xgettext_record_flag ("error:1:c-format"); // 3 different versions + xgettext_record_flag ("notice:1:c-format"); + //xgettext_record_flag ("fatal:1:c-format"); // 2 different versions + xgettext_record_flag ("fatal_perror:1:c-format"); + /* cpplib.h */ + xgettext_record_flag ("cpp_error:3:c-format"); + xgettext_record_flag ("cpp_error_with_line:5:c-format"); + /* diagnostic.h */ + xgettext_record_flag ("diagnostic_set_info:2:pass-gcc-internal-format"); + xgettext_record_flag ("output_printf:2:gcc-internal-format"); + xgettext_record_flag ("output_verbatim:2:pass-gcc-internal-format"); + xgettext_record_flag ("verbatim:1:gcc-internal-format"); + xgettext_record_flag ("inform:1:pass-gcc-internal-format"); + /* gcc.h */ + //xgettext_record_flag ("fatal:1:c-format"); // 2 different versions + //xgettext_record_flag ("error:1:c-format"); // 3 different versions + /* genattrtab.h */ + xgettext_record_flag ("attr_printf:2:pass-c-format"); + /* gengtype.h */ + xgettext_record_flag ("error_at_line:2:pass-c-format"); + xgettext_record_flag ("xvasprintf:2:pass-c-format"); + xgettext_record_flag ("xasprintf:1:pass-c-format"); + xgettext_record_flag ("oprintf:2:pass-c-format"); + /* gensupport.h */ + xgettext_record_flag ("message_with_line:2:pass-c-format"); + /* output.h */ + xgettext_record_flag ("output_operand_lossage:1:c-format"); + /* ra.h */ + xgettext_record_flag ("ra_debug_msg:2:pass-c-format"); + /* toplev.h */ + xgettext_record_flag ("fnotice:2:c-format"); + xgettext_record_flag ("fatal_io_error:2:gcc-internal-format"); + xgettext_record_flag ("error_for_asm:2:pass-gcc-internal-format"); + xgettext_record_flag ("warning_for_asm:2:pass-gcc-internal-format"); + xgettext_record_flag ("error_with_file_and_line:3:pass-gcc-internal-format"); + xgettext_record_flag ("error_with_decl:2:pass-gcc-internal-format"); + xgettext_record_flag ("pedwarn:1:gcc-internal-format"); + xgettext_record_flag ("pedwarn_with_file_and_line:3:gcc-internal-format"); + xgettext_record_flag ("pedwarn_with_decl:2:gcc-internal-format"); + xgettext_record_flag ("sorry:1:gcc-internal-format"); + xgettext_record_flag ("error:1:pass-gcc-internal-format"); + xgettext_record_flag ("fatal_error:1:pass-gcc-internal-format"); + xgettext_record_flag ("internal_error:1:pass-gcc-internal-format"); + xgettext_record_flag ("warning:1:pass-gcc-internal-format"); + xgettext_record_flag ("warning_with_file_and_line:3:pass-gcc-internal-format"); + xgettext_record_flag ("warning_with_decl:2:pass-gcc-internal-format"); + /* f/com.h */ + xgettext_record_flag ("ffecom_get_invented_identifier:1:pass-c-format"); + /* f/sts.h */ + xgettext_record_flag ("ffests_printf:2:pass-c-format"); + /* java/java-tree.h */ + xgettext_record_flag ("parse_error_context:2:pass-c-format"); +#endif + + xgettext_record_flag ("gettext:1:pass-gfc-internal-format"); + xgettext_record_flag ("dgettext:2:pass-gfc-internal-format"); + xgettext_record_flag ("dcgettext:2:pass-gfc-internal-format"); + xgettext_record_flag ("ngettext:1:pass-gfc-internal-format"); + xgettext_record_flag ("ngettext:2:pass-gfc-internal-format"); + xgettext_record_flag ("dngettext:2:pass-gfc-internal-format"); + xgettext_record_flag ("dngettext:3:pass-gfc-internal-format"); + xgettext_record_flag ("dcngettext:2:pass-gfc-internal-format"); + xgettext_record_flag ("dcngettext:3:pass-gfc-internal-format"); + xgettext_record_flag ("gettext_noop:1:pass-gfc-internal-format"); + xgettext_record_flag ("pgettext:2:pass-gfc-internal-format"); + xgettext_record_flag ("dpgettext:3:pass-gfc-internal-format"); + xgettext_record_flag ("dcpgettext:3:pass-gfc-internal-format"); + xgettext_record_flag ("npgettext:2:pass-gfc-internal-format"); + xgettext_record_flag ("npgettext:3:pass-gfc-internal-format"); + xgettext_record_flag ("dnpgettext:3:pass-gfc-internal-format"); + xgettext_record_flag ("dnpgettext:4:pass-gfc-internal-format"); + xgettext_record_flag ("dcnpgettext:3:pass-gfc-internal-format"); + xgettext_record_flag ("dcnpgettext:4:pass-gfc-internal-format"); +#if 0 /* This should better be done inside GCC. */ + /* fortran/error.c */ + xgettext_record_flag ("gfc_error:1:gfc-internal-format"); + xgettext_record_flag ("gfc_error_now:1:gfc-internal-format"); + xgettext_record_flag ("gfc_fatal_error:1:gfc-internal-format"); + xgettext_record_flag ("gfc_internal_error:1:gfc-internal-format"); + xgettext_record_flag ("gfc_notify_std:2:gfc-internal-format"); + xgettext_record_flag ("gfc_warning:1:gfc-internal-format"); + xgettext_record_flag ("gfc_warning_now:1:gfc-internal-format"); +#endif +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* 0. Terminate line by \n, regardless whether the external representation of + a line terminator is LF (Unix), CR (Mac) or CR/LF (DOS/Windows). + It is debatable whether supporting CR/LF line terminators in C sources + on Unix is ISO C or POSIX compliant, but since GCC 3.3 now supports it + unconditionally, it must be OK. + The so-called "text mode" in stdio on DOS/Windows translates CR/LF to \n + automatically, but here we also need this conversion on Unix. As a side + effect, on DOS/Windows we also parse CR/CR/LF into a single \n, but this + is not a problem. */ + + +static int +phase0_getc () +{ + int c; + + c = getc (fp); + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + + if (c == '\r') + { + int c1 = getc (fp); + + if (c1 != EOF && c1 != '\n') + ungetc (c1, fp); + + /* Seen line terminator CR or CR/LF. */ + return '\n'; + } + + return c; +} + + +/* Supports only one pushback character, and not '\n'. */ +static inline void +phase0_ungetc (int c) +{ + if (c != EOF) + ungetc (c, fp); +} + + +/* 1. line_number handling. Combine backslash-newline to nothing. */ + +static unsigned char phase1_pushback[2]; +static int phase1_pushback_length; + + +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + { + c = phase1_pushback[--phase1_pushback_length]; + if (c == '\n') + ++line_number; + return c; + } + for (;;) + { + c = phase0_getc (); + switch (c) + { + case '\n': + ++line_number; + return '\n'; + + case '\\': + c = phase0_getc (); + if (c != '\n') + { + phase0_ungetc (c); + return '\\'; + } + ++line_number; + break; + + default: + return c; + } + } +} + + +/* Supports 2 characters of pushback. */ +static void +phase1_ungetc (int c) +{ + switch (c) + { + case EOF: + break; + + case '\n': + --line_number; + /* FALLTHROUGH */ + + default: + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + break; + } +} + + +/* 2. Convert trigraphs to their single character equivalents. Most + sane human beings vomit copiously at the mention of trigraphs, which + is why they are an option. */ + +static unsigned char phase2_pushback[1]; +static int phase2_pushback_length; + + +static int +phase2_getc () +{ + int c; + + if (phase2_pushback_length) + return phase2_pushback[--phase2_pushback_length]; + if (!trigraphs) + return phase1_getc (); + + c = phase1_getc (); + if (c != '?') + return c; + c = phase1_getc (); + if (c != '?') + { + phase1_ungetc (c); + return '?'; + } + c = phase1_getc (); + switch (c) + { + case '(': + return '['; + case '/': + return '\\'; + case ')': + return ']'; + case '\'': + return '^'; + case '<': + return '{'; + case '!': + return '|'; + case '>': + return '}'; + case '-': + return '~'; + case '#': + return '='; + } + phase1_ungetc (c); + phase1_ungetc ('?'); + return '?'; +} + + +/* Supports only one pushback character. */ +static void +phase2_ungetc (int c) +{ + if (c != EOF) + { + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + } +} + + +/* 3. Concatenate each line ending in backslash (\) with the following + line. Basically, all you need to do is elide "\\\n" sequences from + the input. */ + +static unsigned char phase3_pushback[2]; +static int phase3_pushback_length; + + +static int +phase3_getc () +{ + if (phase3_pushback_length) + return phase3_pushback[--phase3_pushback_length]; + for (;;) + { + int c = phase2_getc (); + if (c != '\\') + return c; + c = phase2_getc (); + if (c != '\n') + { + phase2_ungetc (c); + return '\\'; + } + } +} + + +/* Supports 2 characters of pushback. */ +static void +phase3_ungetc (int c) +{ + if (c != EOF) + { + if (phase3_pushback_length == SIZEOF (phase3_pushback)) + abort (); + phase3_pushback[phase3_pushback_length++] = c; + } +} + + +/* Accumulating comments. */ + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (chars_to_remove == 0 && buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; +static int newline_count; + + +/* 4. Replace each comment that is not inside a character constant or + string literal with a space character. We need to remember the + comment for later, because it may be attached to a keyword string. + We also optionally understand C++ comments. */ + +static int +phase4_getc () +{ + int c; + bool last_was_star; + + c = phase3_getc (); + if (c != '/') + return c; + c = phase3_getc (); + switch (c) + { + default: + phase3_ungetc (c); + return '/'; + + case '*': + /* C comment. */ + comment_start (); + last_was_star = false; + for (;;) + { + c = phase3_getc (); + if (c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + switch (c) + { + case '\n': + comment_line_end (1); + comment_start (); + last_was_star = false; + continue; + + case '*': + last_was_star = true; + continue; + + case '/': + if (last_was_star) + { + comment_line_end (2); + break; + } + /* FALLTHROUGH */ + + default: + last_was_star = false; + continue; + } + break; + } + last_comment_line = newline_count; + return ' '; + + case '/': + /* C++ or ISO C 99 comment. */ + comment_start (); + for (;;) + { + c = phase3_getc (); + if (c == '\n' || c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + comment_line_end (0); + last_comment_line = newline_count; + return '\n'; + } +} + + +/* Supports only one pushback character. */ +static void +phase4_ungetc (int c) +{ + phase3_ungetc (c); +} + + +/* ========================== Reading of tokens. ========================== */ + + +/* True if ObjectiveC extensions are recognized. */ +static bool objc_extensions; + +enum token_type_ty +{ + token_type_character_constant, /* 'x' */ + token_type_eof, + token_type_eoln, + token_type_hash, /* # */ + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_comma, /* , */ + token_type_colon, /* : */ + token_type_name, /* abc */ + token_type_number, /* 2.7 */ + token_type_string_literal, /* "abc" */ + token_type_symbol, /* < > = etc. */ + token_type_objc_special, /* @ */ + token_type_white_space +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_name, token_type_string_literal */ + refcounted_string_list_ty *comment; /* for token_type_string_literal, + token_type_objc_special */ + long number; + int line_number; +}; + + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_name || tp->type == token_type_string_literal) + free (tp->string); + if (tp->type == token_type_string_literal + || tp->type == token_type_objc_special) + drop_reference (tp->comment); +} + + +static char * +literalstring_parse (const char *string, lex_pos_ty *pos, + enum literalstring_escape_type type) +{ + struct mixed_string_buffer *bp; + const char *p; + + /* Start accumulating the string. */ + bp = mixed_string_buffer_alloc (lc_string, + logical_file_name, + line_number); + + for (p = string; *p != '\0'; p++) + { + int c; + + if (*p != '\\') + { + mixed_string_buffer_append_char (bp, *p); + continue; + } + + if (!(type & LET_ANSI_C) && !(type & LET_UNICODE)) + { + mixed_string_buffer_append_char (bp, '\\'); + continue; + } + + c = *++p; + + if (type & LET_ANSI_C) + switch (c) + { + case '"': + case '\'': + case '?': + case '\\': + mixed_string_buffer_append_char (bp, c); + continue; + + case 'a': + mixed_string_buffer_append_char (bp, '\a'); + continue; + case 'b': + mixed_string_buffer_append_char (bp, '\b'); + continue; + + /* The \e escape is preculiar to gcc, and assumes an ASCII + character set (or superset). We don't provide support for it + here. */ + + case 'f': + mixed_string_buffer_append_char (bp, '\f'); + continue; + case 'n': + mixed_string_buffer_append_char (bp, '\n'); + continue; + case 'r': + mixed_string_buffer_append_char (bp, '\r'); + continue; + case 't': + mixed_string_buffer_append_char (bp, '\t'); + continue; + case 'v': + mixed_string_buffer_append_char (bp, '\v'); + continue; + + case 'x': + c = *++p; + switch (c) + { + default: + mixed_string_buffer_append_char (bp, '\\'); + mixed_string_buffer_append_char (bp, 'x'); + mixed_string_buffer_append_char (bp, c); + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + { + int n; + + for (n = 0; ; ++p) + { + switch (*p) + { + default: + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = n * 16 + *p - '0'; + continue; + + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + n = n * 16 + 10 + *p - 'A'; + continue; + + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + n = n * 16 + 10 + *p - 'a'; + continue; + } + break; + } + + mixed_string_buffer_append_char (bp, n); + --p; + } + break; + } + continue; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + int n, j; + + for (n = 0, j = 0; j < 3; ++j) + { + n = n * 8 + c - '0'; + c = *++p; + switch (c) + { + default: + break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + continue; + } + break; + } + + mixed_string_buffer_append_char (bp, n); + --p; + } + continue; + } + + if (type & LET_UNICODE) + switch (c) + { + case 'U': case 'u': + { + unsigned char buf[8]; + int length = c == 'u' ? 4 : 8; + int n, j; + + for (n = 0, j = 0; j < length; j++) + { + int c1 = *++p; + + if (c1 >= '0' && c1 <= '9') + n = (n << 4) + (c1 - '0'); + else if (c1 >= 'A' && c1 <= 'F') + n = (n << 4) + (c1 - 'A' + 10); + else if (c1 >= 'a' && c1 <= 'f') + n = (n << 4) + (c1 - 'a' + 10); + else + break; + + buf[j] = c1; + } + + if (j == length) + { + if (n < 0x110000) + mixed_string_buffer_append_unicode (bp, n); + else + { + error_with_progname = false; + error_at_line (0, 0, + pos->file_name, pos->line_number, + _("\ +warning: invalid Unicode character")); + error_with_progname = true; + } + } + else + { + int i; + + mixed_string_buffer_append_char (bp, '\\'); + mixed_string_buffer_append_char (bp, c); + + for (i = 0; i < j; i++) + mixed_string_buffer_append_char (bp, buf[i]); + + --p; + } + } + continue; + } + + mixed_string_buffer_append_char (bp, c); + } + + return mixed_string_buffer_done (bp); +} + +struct literalstring_parser literalstring_c = + { + literalstring_parse + }; + + +/* 5. Parse each resulting logical line as preprocessing tokens and + white space. Preprocessing tokens and C tokens don't always match. */ + +static token_ty phase5_pushback[1]; +static int phase5_pushback_length; + + +static void +phase5_get (token_ty *tp) +{ + static char *buffer; + static int bufmax; + int bufpos; + int c; + int last_was_backslash; + bool raw_expected = false; + + if (phase5_pushback_length) + { + *tp = phase5_pushback[--phase5_pushback_length]; + return; + } + tp->string = NULL; + tp->number = 0; + tp->line_number = line_number; + c = phase4_getc (); + switch (c) + { + case EOF: + tp->type = token_type_eof; + return; + + case '\n': + tp->type = token_type_eoln; + return; + + case ' ': + case '\f': + case '\t': + for (;;) + { + c = phase4_getc (); + switch (c) + { + case ' ': + case '\f': + case '\t': + continue; + + default: + phase4_ungetc (c); + break; + } + break; + } + tp->type = token_type_white_space; + return; + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase4_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + + default: + /* Recognize C++ string literals prefixed by R, u8, u8R, + u, uR, U, UR, L, or LR. It is defined in ISO/IEC + 9899:2011 2.14.5. Since gettext's argument is a byte + sequence, we are only interested in u8, R, and u8R. */ + if (c == '"') + { + bool is_prefix = false; + + switch (buffer[0]) + { + case 'R': + if (bufpos == 1) + is_prefix = true; + break; + case 'u': + if (bufpos == 1) + is_prefix = true; + else + switch (buffer[1]) + { + case 'R': + if (bufpos == 2) + is_prefix = true; + break; + case '8': + if (bufpos == 2 + || (bufpos == 3 && buffer[2] == 'R')) + is_prefix = true; + break; + } + break; + case 'U': + case 'L': + if (bufpos == 1 + || (bufpos == 2 && buffer[1] == 'R')) + is_prefix = true; + break; + } + + if (is_prefix) + { + raw_expected = buffer[bufpos - 1] == 'R'; + bufpos = 0; + goto string; + } + } + phase4_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = 0; + tp->string = xstrdup (buffer); + tp->type = token_type_name; + return; + + case '.': + c = phase4_getc (); + phase4_ungetc (c); + switch (c) + { + default: + tp->type = token_type_symbol; + return; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c = '.'; + break; + } + /* FALLTHROUGH */ + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* The preprocessing number token is more "generous" than the C + number tokens. This is mostly due to token pasting (another + thing we can ignore here). */ + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase4_getc (); + switch (c) + { + case 'e': + case 'E': + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase4_getc (); + if (c != '+' && c != '-') + { + phase4_ungetc (c); + break; + } + continue; + + case 'A': case 'B': case 'C': case 'D': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '.': + continue; + + default: + phase4_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = 0; + tp->type = token_type_number; + tp->number = atol (buffer); + return; + + case '\'': + /* We could worry about the 'L' before wide character constants, + but ignoring it has no effect unless one of the keywords is + "L". Just pretend it won't happen. Also, we don't need to + remember the character constant. */ + last_was_backslash = false; + for (;;) + { + c = phase3_getc (); + if (last_was_backslash) + { + last_was_backslash = false; + continue; + } + switch (c) + { + case '\\': + last_was_backslash = true; + /* FALLTHROUGH */ + default: + continue; + case '\n': + error_with_progname = false; + error (0, 0, _("%s:%d: warning: unterminated character constant"), + logical_file_name, line_number - 1); + error_with_progname = true; + phase3_ungetc ('\n'); + break; + case EOF: case '\'': + break; + } + break; + } + tp->type = token_type_character_constant; + return; + + case '"': + { + string: + /* We could worry about the 'L' before wide string constants, + but since gettext's argument is not a wide character string, + let the compiler complain about the argument not matching the + prototype. Just pretend it won't happen. */ + last_was_backslash = false; + bufpos = 0; + for (;;) + { + c = phase3_getc (); + if (last_was_backslash) + { + last_was_backslash = false; + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + continue; + } + switch (c) + { + case '\\': + last_was_backslash = true; + /* FALLTHROUGH */ + default: + if (c == '\n' && !raw_expected) + { + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: unterminated string literal"), + logical_file_name, line_number - 1); + error_with_progname = true; + phase3_ungetc ('\n'); + break; + } + else + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + continue; + } + + case EOF: case '"': + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = 0; + + if (raw_expected) + { + char *delimiter_left_end; + char *delimiter_right_start; + + if (!(delimiter_left_end = strchr (buffer, '(')) + || !(delimiter_right_start = strrchr (buffer, ')')) + || strncmp (buffer, delimiter_right_start + 1, + (delimiter_left_end - buffer)) != 0) + { + error_with_progname = false; + error (0, 0, _("%s:%d: warning: unterminated string literal"), + logical_file_name, line_number - 1); + error_with_progname = true; + } + else + { + *delimiter_right_start = '\0'; + tp->type = token_type_string_literal; + tp->string = xstrdup (delimiter_left_end + 1); + tp->comment = add_reference (savable_comment); + return; + } + } + tp->type = token_type_string_literal; + tp->string = xstrdup (buffer); + tp->comment = add_reference (savable_comment); + return; + } + + case '(': + tp->type = token_type_lparen; + return; + + case ')': + tp->type = token_type_rparen; + return; + + case ',': + tp->type = token_type_comma; + return; + + case '#': + tp->type = token_type_hash; + return; + + case ':': + tp->type = token_type_colon; + return; + + case '@': + if (objc_extensions) + { + tp->type = token_type_objc_special; + tp->comment = add_reference (savable_comment); + return; + } + /* FALLTHROUGH */ + + default: + /* We could carefully recognize each of the 2 and 3 character + operators, but it is not necessary, as we only need to recognize + gettext invocations. Don't bother. */ + tp->type = token_type_symbol; + return; + } +} + + +/* Supports only one pushback token. */ +static void +phase5_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase5_pushback_length == SIZEOF (phase5_pushback)) + abort (); + phase5_pushback[phase5_pushback_length++] = *tp; + } +} + + +/* X. Recognize a leading # symbol. Leave leading hash as a hash, but + turn hash in the middle of a line into a plain symbol token. This + makes the phase 6 easier. */ + +static void +phaseX_get (token_ty *tp) +{ + static bool middle; /* false at the beginning of a line, true otherwise. */ + + phase5_get (tp); + + if (tp->type == token_type_eoln || tp->type == token_type_eof) + middle = false; + else + { + if (middle) + { + /* Turn hash in the middle of a line into a plain symbol token. */ + if (tp->type == token_type_hash) + tp->type = token_type_symbol; + } + else + { + /* When we see leading whitespace followed by a hash sign, + discard the leading white space token. The hash is all + phase 6 is interested in. */ + if (tp->type == token_type_white_space) + { + token_ty next; + + phase5_get (&next); + if (next.type == token_type_hash) + *tp = next; + else + phase5_unget (&next); + } + middle = true; + } + } +} + + +/* 6. Recognize and carry out directives (it also expands macros on + non-directive lines, which we do not do here). The only directive + we care about are the #line and #define directive. We throw all the + others away. */ + +static token_ty phase6_pushback[2]; +static int phase6_pushback_length; + + +static void +phase6_get (token_ty *tp) +{ + static token_ty *buf; + static int bufmax; + int bufpos; + int j; + + if (phase6_pushback_length) + { + *tp = phase6_pushback[--phase6_pushback_length]; + return; + } + for (;;) + { + /* Get the next token. If it is not a '#' at the beginning of a + line (ignoring whitespace), return immediately. */ + phaseX_get (tp); + if (tp->type != token_type_hash) + return; + + /* Accumulate the rest of the directive in a buffer, until the + "define" keyword is seen or until end of line. */ + bufpos = 0; + for (;;) + { + phaseX_get (tp); + if (tp->type == token_type_eoln || tp->type == token_type_eof) + break; + + /* Before the "define" keyword and inside other directives + white space is irrelevant. So just throw it away. */ + if (tp->type != token_type_white_space) + { + /* If it is a #define directive, return immediately, + thus treating the body of the #define directive like + normal input. */ + if (bufpos == 0 + && tp->type == token_type_name + && strcmp (tp->string, "define") == 0) + return; + + /* Accumulate. */ + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buf = xrealloc (buf, bufmax * sizeof (buf[0])); + } + buf[bufpos++] = *tp; + } + } + + /* If it is a #line directive, with no macros to expand, act on + it. Ignore all other directives. */ + if (bufpos >= 3 && buf[0].type == token_type_name + && strcmp (buf[0].string, "line") == 0 + && buf[1].type == token_type_number + && buf[2].type == token_type_string_literal) + { + logical_file_name = xstrdup (buf[2].string); + line_number = buf[1].number; + } + if (bufpos >= 2 && buf[0].type == token_type_number + && buf[1].type == token_type_string_literal) + { + logical_file_name = xstrdup (buf[1].string); + line_number = buf[0].number; + } + + /* Release the storage held by the directive. */ + for (j = 0; j < bufpos; ++j) + free_token (&buf[j]); + + /* We must reset the selected comments. */ + savable_comment_reset (); + } +} + + +/* Supports 2 tokens of pushback. */ +static void +phase6_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase6_pushback_length == SIZEOF (phase6_pushback)) + abort (); + phase6_pushback[phase6_pushback_length++] = *tp; + } +} + + +/* 8a. Convert ISO C 99 section 7.8.1 format string directives to string + literal placeholders. */ + +/* Test for an ISO C 99 section 7.8.1 format string directive. */ +static bool +is_inttypes_macro (const char *name) +{ + /* Syntax: + P R I { d | i | o | u | x | X } + { { | LEAST | FAST } { 8 | 16 | 32 | 64 } | MAX | PTR } */ + if (name[0] == 'P' && name[1] == 'R' && name[2] == 'I') + { + name += 3; + if (name[0] == 'd' || name[0] == 'i' || name[0] == 'o' || name[0] == 'u' + || name[0] == 'x' || name[0] == 'X') + { + name += 1; + if (name[0] == 'M' && name[1] == 'A' && name[2] == 'X' + && name[3] == '\0') + return true; + if (name[0] == 'P' && name[1] == 'T' && name[2] == 'R' + && name[3] == '\0') + return true; + if (name[0] == 'L' && name[1] == 'E' && name[2] == 'A' + && name[3] == 'S' && name[4] == 'T') + name += 5; + else if (name[0] == 'F' && name[1] == 'A' && name[2] == 'S' + && name[3] == 'T') + name += 4; + if (name[0] == '8' && name[1] == '\0') + return true; + if (name[0] == '1' && name[1] == '6' && name[2] == '\0') + return true; + if (name[0] == '3' && name[1] == '2' && name[2] == '\0') + return true; + if (name[0] == '6' && name[1] == '4' && name[2] == '\0') + return true; + } + } + return false; +} + +static void +phase8a_get (token_ty *tp) +{ + phase6_get (tp); + if (tp->type == token_type_name && is_inttypes_macro (tp->string)) + { + /* Turn PRIdXXX into "". */ + char *new_string = xasprintf ("<%s>", tp->string); + free (tp->string); + tp->string = new_string; + tp->comment = add_reference (savable_comment); + tp->type = token_type_string_literal; + } +} + +/* Supports 2 tokens of pushback. */ +static inline void +phase8a_unget (token_ty *tp) +{ + phase6_unget (tp); +} + + +/* 8b. Drop whitespace. */ +static void +phase8b_get (token_ty *tp) +{ + for (;;) + { + phase8a_get (tp); + + if (tp->type == token_type_white_space) + continue; + if (tp->type == token_type_eoln) + { + /* We have to track the last occurrence of a string. One + mode of xgettext allows to group an extracted message + with a comment for documentation. The rule which states + which comment is assumed to be grouped with the message + says it should immediately precede it. Our + interpretation: between the last line of the comment and + the line in which the keyword is found must be no line + with non-white space tokens. */ + ++newline_count; + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + continue; + } + break; + } +} + +/* Supports 2 tokens of pushback. */ +static inline void +phase8b_unget (token_ty *tp) +{ + phase8a_unget (tp); +} + + +/* 8c. In ObjectiveC mode, drop '@' before a literal string. We need to + do this before performing concatenation of adjacent string literals. */ +static void +phase8c_get (token_ty *tp) +{ + token_ty tmp; + + phase8b_get (tp); + if (tp->type != token_type_objc_special) + return; + phase8b_get (&tmp); + if (tmp.type != token_type_string_literal) + { + phase8b_unget (&tmp); + return; + } + /* Drop the '@' token and return immediately the following string. */ + drop_reference (tmp.comment); + tmp.comment = tp->comment; + *tp = tmp; +} + +/* Supports only one pushback token. */ +static inline void +phase8c_unget (token_ty *tp) +{ + phase8b_unget (tp); +} + + +/* 8. Concatenate adjacent string literals to form single string + literals (because we don't expand macros, there are a few things we + will miss). */ + +static void +phase8_get (token_ty *tp) +{ + phase8c_get (tp); + if (tp->type != token_type_string_literal) + return; + for (;;) + { + token_ty tmp; + size_t len; + + phase8c_get (&tmp); + if (tmp.type != token_type_string_literal) + { + phase8c_unget (&tmp); + return; + } + len = strlen (tp->string); + tp->string = xrealloc (tp->string, len + strlen (tmp.string) + 1); + strcpy (tp->string + len, tmp.string); + free_token (&tmp); + } +} + + +/* ===================== Reading of high-level tokens. ==================== */ + + +enum xgettext_token_type_ty +{ + xgettext_token_type_eof, + xgettext_token_type_keyword, + xgettext_token_type_symbol, + xgettext_token_type_lparen, + xgettext_token_type_rparen, + xgettext_token_type_comma, + xgettext_token_type_colon, + xgettext_token_type_string_literal, + xgettext_token_type_other +}; +typedef enum xgettext_token_type_ty xgettext_token_type_ty; + +typedef struct xgettext_token_ty xgettext_token_ty; +struct xgettext_token_ty +{ + xgettext_token_type_ty type; + + /* This field is used only for xgettext_token_type_keyword. */ + const struct callshapes *shapes; + + /* This field is used only for xgettext_token_type_string_literal, + xgettext_token_type_keyword, xgettext_token_type_symbol. */ + char *string; + + /* This field is used only for xgettext_token_type_string_literal. */ + refcounted_string_list_ty *comment; + + /* These fields are only for + xgettext_token_type_keyword, + xgettext_token_type_string_literal. */ + lex_pos_ty pos; +}; + + +/* 9. Convert the remaining preprocessing tokens to C tokens and + discards any white space from the translation unit. */ + +static void +x_c_lex (xgettext_token_ty *tp) +{ + for (;;) + { + token_ty token; + void *keyword_value; + + phase8_get (&token); + switch (token.type) + { + case token_type_eof: + tp->type = xgettext_token_type_eof; + return; + + case token_type_name: + last_non_comment_line = newline_count; + + if (hash_find_entry (objc_extensions ? &objc_keywords : &c_keywords, + token.string, strlen (token.string), + &keyword_value) + == 0) + { + tp->type = xgettext_token_type_keyword; + tp->shapes = (const struct callshapes *) keyword_value; + tp->pos.file_name = logical_file_name; + tp->pos.line_number = token.line_number; + } + else + tp->type = xgettext_token_type_symbol; + tp->string = token.string; + return; + + case token_type_lparen: + last_non_comment_line = newline_count; + + tp->type = xgettext_token_type_lparen; + return; + + case token_type_rparen: + last_non_comment_line = newline_count; + + tp->type = xgettext_token_type_rparen; + return; + + case token_type_comma: + last_non_comment_line = newline_count; + + tp->type = xgettext_token_type_comma; + return; + + case token_type_colon: + last_non_comment_line = newline_count; + + tp->type = xgettext_token_type_colon; + return; + + case token_type_string_literal: + last_non_comment_line = newline_count; + + tp->type = xgettext_token_type_string_literal; + tp->string = token.string; + tp->comment = token.comment; + tp->pos.file_name = logical_file_name; + tp->pos.line_number = token.line_number; + return; + + case token_type_objc_special: + drop_reference (token.comment); + /* FALLTHROUGH */ + + default: + last_non_comment_line = newline_count; + + tp->type = xgettext_token_type_other; + return; + } + } +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid C or C++ program, and leave the complaints about + the grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + + +/* Extract messages until the next balanced closing parenthesis. + Extracted messages are added to MLP. + Return true upon eof, false upon closing parenthesis. */ +static bool +extract_parenthesized (message_list_ty *mlp, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Context iterator that will be used if the next token is a ':'. + (Objective C selector syntax.) */ + flag_context_list_iterator_ty selectorcall_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + xgettext_token_ty token; + + x_c_lex (&token); + switch (token.type) + { + case xgettext_token_type_keyword: + next_shapes = token.shapes; + state = 1; + goto keyword_or_symbol; + + case xgettext_token_type_symbol: + state = 0; + keyword_or_symbol: + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, strlen (token.string))); + if (objc_extensions) + { + size_t token_string_len = strlen (token.string); + token.string = xrealloc (token.string, token_string_len + 2); + token.string[token_string_len] = ':'; + token.string[token_string_len + 1] = '\0'; + selectorcall_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, token_string_len + 1)); + } + free (token.string); + continue; + + case xgettext_token_type_lparen: + if (extract_parenthesized (mlp, inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + arglist_parser_done (argparser, arg); + return true; + } + next_context_iter = null_context_list_iterator; + selectorcall_context_iter = null_context_list_iterator; + state = 0; + continue; + + case xgettext_token_type_rparen: + arglist_parser_done (argparser, arg); + return false; + + case xgettext_token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + selectorcall_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case xgettext_token_type_colon: + if (objc_extensions) + { + context_iter = selectorcall_context_iter; + inner_context = + inherited_context (inner_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + selectorcall_context_iter = passthrough_context_list_iterator; + } + else + { + next_context_iter = null_context_list_iterator; + selectorcall_context_iter = null_context_list_iterator; + } + state = 0; + continue; + + case xgettext_token_type_string_literal: + if (extract_all) + { + char *string; + refcounted_string_list_ty *comment; + const char *encoding; + + string = literalstring_parse (token.string, &token.pos, + LET_ANSI_C | LET_UNICODE); + free (token.string); + token.string = string; + + if (token.comment != NULL) + { + comment = savable_comment_convert_encoding (token.comment, + &token.pos); + drop_reference (token.comment); + token.comment = comment; + } + + /* token.string and token.comment are already converted + to UTF-8. Prevent further conversion in + remember_a_message. */ + encoding = xgettext_current_source_encoding; + xgettext_current_source_encoding = po_charset_utf8; + remember_a_message (mlp, NULL, token.string, inner_context, + &token.pos, NULL, token.comment); + xgettext_current_source_encoding = encoding; + } + else + arglist_parser_remember_literal (argparser, arg, token.string, + inner_context, + token.pos.file_name, + token.pos.line_number, + token.comment, + LET_ANSI_C | LET_UNICODE); + drop_reference (token.comment); + next_context_iter = null_context_list_iterator; + selectorcall_context_iter = null_context_list_iterator; + state = 0; + continue; + + case xgettext_token_type_other: + next_context_iter = null_context_list_iterator; + selectorcall_context_iter = null_context_list_iterator; + state = 0; + continue; + + case xgettext_token_type_eof: + arglist_parser_done (argparser, arg); + return true; + + default: + abort (); + } + } +} + + +static void +extract_whole_file (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + newline_count = 0; + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When extract_parenthesized returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_parenthesized (mlp, null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + /* Close scanner. */ + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} + + +void +extract_c (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + objc_extensions = false; + extract_whole_file (f, real_filename, logical_filename, flag_table, mdlp); +} + +void +extract_objc (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + objc_extensions = true; + extract_whole_file (f, real_filename, logical_filename, flag_table, mdlp); +} diff --git a/gettext-tools/src/x-c.h b/gettext-tools/src/x-c.h new file mode 100644 index 0000000..64e4953 --- /dev/null +++ b/gettext-tools/src/x-c.h @@ -0,0 +1,92 @@ +/* xgettext C/C++/ObjectiveC backend. + Copyright (C) 2001-2003, 2006, 2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_C \ + { "c", "C" }, \ + { "h", "C" }, \ + { "C", "C++" }, \ + { "c++", "C++" }, \ + { "cc", "C++" }, \ + { "cxx", "C++" }, \ + { "cpp", "C++" }, \ + { "hh", "C++" }, \ + { "hxx", "C++" }, \ + { "hpp", "C++" }, \ + { "m", "ObjectiveC" }, \ + +#define SCANNERS_C \ + { "C", extract_c, \ + &flag_table_c, \ + &formatstring_c, NULL, \ + &literalstring_c }, \ + { "C++", extract_c, \ + &flag_table_c, \ + &formatstring_c, NULL, \ + &literalstring_c }, \ + { "ObjectiveC", extract_objc, \ + &flag_table_objc, \ + &formatstring_c, &formatstring_objc, \ + &literalstring_c }, \ + { "GCC-source", extract_c, \ + &flag_table_gcc_internal, \ + &formatstring_gcc_internal, &formatstring_gfc_internal, \ + &literalstring_c }, \ + +/* Scan a C/C++ file and add its translatable strings to mdlp. */ +extern void extract_c (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); +/* Scan an ObjectiveC file and add its translatable strings to mdlp. */ +extern void extract_objc (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +/* Handling of options specific to this language. */ + +extern void x_c_extract_all (void); + +extern void x_c_keyword (const char *name); +extern void x_objc_keyword (const char *name); + +extern void x_c_trigraphs (void); + +extern void init_flag_table_c (void); +extern void init_flag_table_objc (void); +extern void init_flag_table_gcc_internal (void); + + +extern struct literalstring_parser literalstring_c; + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-csharp.c b/gettext-tools/src/x-csharp.c new file mode 100644 index 0000000..5091c3b --- /dev/null +++ b/gettext-tools/src/x-csharp.c @@ -0,0 +1,2147 @@ +/* xgettext C# backend. + Copyright (C) 2003, 2005-2009, 2011 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-csharp.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "c-ctype.h" +#include "error.h" +#include "error-progname.h" +#include "xalloc.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "hash.h" +#include "po-charset.h" +#include "unistr.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The C# syntax is defined in ECMA-334, second edition. */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_csharp_extract_all () +{ + extract_all = true; +} + + +/* Processes a --keyword option. + Non-ASCII function names can be used if given in UTF-8 encoding. */ +void +x_csharp_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C# + identifier sequence with dots. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_csharp_keyword ("GetString"); /* Resource{Manager,Set}.GetString */ + x_csharp_keyword ("GetPluralString:1,2"); /* GettextResource{Manager,Set}.GetPluralString */ + x_csharp_keyword ("GetParticularString:1c,2"); /* Resource{Manager,Set}.GetParticularString */ + x_csharp_keyword ("GetParticularPluralString:1c,2,3"); /* Resource{Manager,Set}.GetParticularPluralString */ + default_keywords = false; + } +} + +void +init_flag_table_csharp () +{ + xgettext_record_flag ("GetString:1:pass-csharp-format"); + xgettext_record_flag ("GetPluralString:1:pass-csharp-format"); + xgettext_record_flag ("GetPluralString:2:pass-csharp-format"); + xgettext_record_flag ("GetParticularString:2:pass-csharp-format"); + xgettext_record_flag ("GetParticularPluralString:2:pass-csharp-format"); + xgettext_record_flag ("GetParticularPluralString:3:pass-csharp-format"); + xgettext_record_flag ("String.Format:1:csharp-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* Phase 1: line_number handling. */ + +/* Maximum used, roughly a safer MB_LEN_MAX. */ +#define MAX_PHASE1_PUSHBACK 16 +static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK]; +static int phase1_pushback_length; + +/* Read the next single byte from the input file. */ +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + { + c = phase1_pushback[--phase1_pushback_length]; + if (c == '\n') + ++line_number; + return c; + } + + c = getc (fp); + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + + if (c == '\n') + ++line_number; + return c; +} + +/* Supports MAX_PHASE1_PUSHBACK characters of pushback. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + --line_number; + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + } +} + + +/* Phase 2: Conversion to Unicode. + This is done early because ECMA-334 section 9.1. says that the source is + "an ordered sequence of Unicode characters", and because the recognition + of the line terminators (ECMA-334 section 9.3.1) is hardly possible without + prior conversion to Unicode. */ + +/* End-of-file indicator for functions returning an UCS-4 character. */ +#define UEOF -1 + +/* Newline Unicode character. */ +#define UNL 0x000a + +static lexical_context_ty lexical_context; + +static int phase2_pushback[1]; +static int phase2_pushback_length; + +/* Read the next Unicode UCS-4 character from the input file. */ +static int +phase2_getc () +{ + if (phase2_pushback_length) + return phase2_pushback[--phase2_pushback_length]; + + if (xgettext_current_source_encoding == po_charset_ascii) + { + int c = phase1_getc (); + if (c == EOF) + return UEOF; + if (!c_isascii (c)) + { + multiline_error (xstrdup (""), + xasprintf ("%s\n%s\n", + non_ascii_error_message (lexical_context, + real_file_name, + line_number), + _("\ +Please specify the source encoding through --from-code."))); + exit (EXIT_FAILURE); + } + return c; + } + else if (xgettext_current_source_encoding != po_charset_utf8) + { +#if HAVE_ICONV + /* Use iconv on an increasing number of bytes. Read only as many bytes + through phase1_getc as needed. This is needed to give reasonable + interactive behaviour when fp is connected to an interactive tty. */ + unsigned char buf[MAX_PHASE1_PUSHBACK]; + size_t bufcount; + int c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[0] = (unsigned char) c; + bufcount = 1; + + for (;;) + { + unsigned char scratchbuf[6]; + const char *inptr = (const char *) &buf[0]; + size_t insize = bufcount; + char *outptr = (char *) &scratchbuf[0]; + size_t outsize = sizeof (scratchbuf); + + size_t res = iconv (xgettext_current_source_iconv, + (ICONV_CONST char **) &inptr, &insize, + &outptr, &outsize); + /* We expect that a character has been produced if and only if + some input bytes have been consumed. */ + if ((insize < bufcount) != (outsize < sizeof (scratchbuf))) + abort (); + if (outsize == sizeof (scratchbuf)) + { + /* No character has been produced. Must be an error. */ + if (res != (size_t)(-1)) + abort (); + + if (errno == EILSEQ) + { + /* An invalid multibyte sequence was encountered. */ + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Invalid multibyte sequence.\n\ +Please specify the correct source encoding through --from-code.\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + else if (errno == EINVAL) + { + /* An incomplete multibyte character. */ + int c; + + if (bufcount == MAX_PHASE1_PUSHBACK) + { + /* An overlong incomplete multibyte sequence was + encountered. */ + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Long incomplete multibyte sequence.\n\ +Please specify the correct source encoding through --from-code.\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + + /* Read one more byte and retry iconv. */ + c = phase1_getc (); + if (c == EOF) + { + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Incomplete multibyte sequence at end of file.\n\ +Please specify the correct source encoding through --from-code.\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + if (c == '\n') + { + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Incomplete multibyte sequence at end of line.\n\ +Please specify the correct source encoding through --from-code.\n"), + real_file_name, line_number - 1)); + exit (EXIT_FAILURE); + } + buf[bufcount++] = (unsigned char) c; + } + else + error (EXIT_FAILURE, errno, _("%s:%d: iconv failure"), + real_file_name, line_number); + } + else + { + size_t outbytes = sizeof (scratchbuf) - outsize; + size_t bytes = bufcount - insize; + ucs4_t uc; + + /* We expect that one character has been produced. */ + if (bytes == 0) + abort (); + if (outbytes == 0) + abort (); + /* Push back the unused bytes. */ + while (insize > 0) + phase1_ungetc (buf[--insize]); + /* Convert the character from UTF-8 to UCS-4. */ + if (u8_mbtoucr (&uc, scratchbuf, outbytes) < (int) outbytes) + { + /* scratchbuf contains an out-of-range Unicode character + (> 0x10ffff). */ + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Invalid multibyte sequence.\n\ +Please specify the source encoding through --from-code.\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + return uc; + } + } +#else + /* If we don't have iconv(), the only supported values for + xgettext_global_source_encoding and thus also for + xgettext_current_source_encoding are ASCII and UTF-8. */ + abort (); +#endif + } + else + { + /* Read an UTF-8 encoded character. */ + unsigned char buf[6]; + unsigned int count; + int c; + ucs4_t uc; + + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[0] = c; + count = 1; + + if (buf[0] >= 0xc0) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[1] = c; + count = 2; + } + + if (buf[0] >= 0xe0 + && ((buf[1] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[2] = c; + count = 3; + } + + if (buf[0] >= 0xf0 + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[3] = c; + count = 4; + } + + if (buf[0] >= 0xf8 + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40) + && ((buf[3] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[4] = c; + count = 5; + } + + if (buf[0] >= 0xfc + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40) + && ((buf[3] ^ 0x80) < 0x40) + && ((buf[4] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[5] = c; + count = 6; + } + + u8_mbtouc (&uc, buf, count); + return uc; + } +} + +/* Supports only one pushback character. */ +static void +phase2_ungetc (int c) +{ + if (c != UEOF) + { + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + } +} + + +/* Phase 3: Convert all line terminators to LF. + See ECMA-334 section 9.3.1. */ + +/* Line number defined in terms of phase3. */ +static int logical_line_number; + +static int phase3_pushback[9]; +static int phase3_pushback_length; + +/* Read the next Unicode UCS-4 character from the input file, mapping + all line terminators to U+000A, and dropping U+001A at the end of file. */ +static int +phase3_getc () +{ + int c; + + if (phase3_pushback_length) + { + c = phase3_pushback[--phase3_pushback_length]; + if (c == UNL) + ++logical_line_number; + return c; + } + + c = phase2_getc (); + + if (c == 0x000d) + { + int c1 = phase2_getc (); + + if (c1 != UEOF && c1 != 0x000a) + phase2_ungetc (c1); + + /* Seen line terminator CR or CR/LF. */ + ++logical_line_number; + return UNL; + } + + if (c == 0x0085 || c == 0x2028 || c == 0x2029) + { + /* Seen Unicode word processor newline. */ + ++logical_line_number; + return UNL; + } + + if (c == 0x001a) + { + int c1 = phase2_getc (); + + if (c1 == UEOF) + /* Seen U+001A right before the end of file. */ + return UEOF; + + phase2_ungetc (c1); + } + + if (c == UNL) + ++logical_line_number; + return c; +} + +/* Supports 9 characters of pushback. */ +static void +phase3_ungetc (int c) +{ + if (c != UEOF) + { + if (c == UNL) + --logical_line_number; + if (phase3_pushback_length == SIZEOF (phase3_pushback)) + abort (); + phase3_pushback[phase3_pushback_length++] = c; + } +} + + +/* ========================= Accumulating strings. ======================== */ + +/* A string buffer type that allows appending Unicode characters. + Returns the entire string in UTF-8 encoding. */ + +struct string_buffer +{ + /* The part of the string that has already been converted to UTF-8. */ + char *utf8_buffer; + size_t utf8_buflen; + size_t utf8_allocated; +}; + +/* Initialize a 'struct string_buffer' to empty. */ +static inline void +init_string_buffer (struct string_buffer *bp) +{ + bp->utf8_buffer = NULL; + bp->utf8_buflen = 0; + bp->utf8_allocated = 0; +} + +/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ +static inline void +string_buffer_append_unicode_grow (struct string_buffer *bp, size_t count) +{ + if (bp->utf8_buflen + count > bp->utf8_allocated) + { + size_t new_allocated = 2 * bp->utf8_allocated + 10; + if (new_allocated < bp->utf8_buflen + count) + new_allocated = bp->utf8_buflen + count; + bp->utf8_allocated = new_allocated; + bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); + } +} + +/* Auxiliary function: Append a Unicode character to bp->utf8. + uc must be < 0x110000. */ +static inline void +string_buffer_append_unicode (struct string_buffer *bp, unsigned int uc) +{ + unsigned char utf8buf[6]; + int count = u8_uctomb (utf8buf, uc, 6); + + if (count < 0) + /* The caller should have ensured that uc is not out-of-range. */ + abort (); + + string_buffer_append_unicode_grow (bp, count); + memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); + bp->utf8_buflen += count; +} + +/* Return the string buffer's contents. */ +static char * +string_buffer_result (struct string_buffer *bp) +{ + /* NUL-terminate it. */ + string_buffer_append_unicode_grow (bp, 1); + bp->utf8_buffer[bp->utf8_buflen] = '\0'; + /* Return it. */ + return bp->utf8_buffer; +} + +/* Free the memory pointed to by a 'struct string_buffer'. */ +static inline void +free_string_buffer (struct string_buffer *bp) +{ + free (bp->utf8_buffer); +} + + +/* ======================== Accumulating comments. ======================== */ + + +/* Accumulating a single comment line. */ + +static struct string_buffer comment_buffer; + +static inline void +comment_start () +{ + lexical_context = lc_comment; + comment_buffer.utf8_buflen = 0; +} + +static inline bool +comment_at_start () +{ + return (comment_buffer.utf8_buflen == 0); +} + +static inline void +comment_add (int c) +{ + string_buffer_append_unicode (&comment_buffer, c); +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + char *buffer = string_buffer_result (&comment_buffer); + size_t buflen = strlen (buffer); + + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + buffer[buflen] = '\0'; + savable_comment_add (buffer); + lexical_context = lc_outside; +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* Phase 4: Replace each comment that is not inside a character constant or + string literal with a space or newline character. + See ECMA-334 section 9.3.2. */ + +static int +phase4_getc () +{ + int c0; + int c; + bool last_was_star; + + c0 = phase3_getc (); + if (c0 != '/') + return c0; + c = phase3_getc (); + switch (c) + { + default: + phase3_ungetc (c); + return c0; + + case '*': + /* C style comment. */ + comment_start (); + last_was_star = false; + for (;;) + { + c = phase3_getc (); + if (c == UEOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(comment_at_start () && (c == ' ' || c == '\t'))) + comment_add (c); + switch (c) + { + case UNL: + comment_line_end (1); + comment_start (); + last_was_star = false; + continue; + + case '*': + last_was_star = true; + continue; + + case '/': + if (last_was_star) + { + comment_line_end (2); + break; + } + /* FALLTHROUGH */ + + default: + last_was_star = false; + continue; + } + break; + } + last_comment_line = logical_line_number; + return ' '; + + case '/': + /* C++ style comment. */ + last_comment_line = logical_line_number; + comment_start (); + for (;;) + { + c = phase3_getc (); + if (c == UNL || c == UEOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(comment_at_start () && (c == ' ' || c == '\t'))) + comment_add (c); + } + phase3_ungetc (c); /* push back the newline, to decrement logical_line_number */ + comment_line_end (0); + phase3_getc (); /* read the newline again */ + return UNL; + } +} + +/* Supports only one pushback character. */ +static void +phase4_ungetc (int c) +{ + phase3_ungetc (c); +} + + +/* ======================= Character classification. ====================== */ + + +/* Return true if a given character is white space. + See ECMA-334 section 9.3.3. */ +static bool +is_whitespace (int c) +{ + /* Unicode character class Zs, as of Unicode 4.0. */ + /* grep '^[^;]*;[^;]*;Zs;' UnicodeData-4.0.0.txt */ + switch (c >> 8) + { + case 0x00: + return (c == 0x0020 || c == 0x00a0); + case 0x16: + return (c == 0x1680); + case 0x18: + return (c == 0x180e); + case 0x20: + return ((c >= 0x2000 && c <= 0x200b) || c == 0x202f || c == 0x205f); + case 0x30: + return (c == 0x3000); + default: + return false; + } +} + + +/* C# allows identifiers containing many Unicode characters. We recognize + them; to use an identifier with Unicode characters in a --keyword option, + it must be specified in UTF-8. */ + +static inline int +bitmap_lookup (const void *table, unsigned int uc) +{ + unsigned int index1 = uc >> 16; + if (index1 < ((const int *) table)[0]) + { + int lookup1 = ((const int *) table)[1 + index1]; + if (lookup1 >= 0) + { + unsigned int index2 = (uc >> 9) & 0x7f; + int lookup2 = ((const int *) table)[lookup1 + index2]; + if (lookup2 >= 0) + { + unsigned int index3 = (uc >> 5) & 0xf; + unsigned int lookup3 = ((const int *) table)[lookup2 + index3]; + + return (lookup3 >> (uc & 0x1f)) & 1; + } + } + } + return 0; +} + +/* Unicode character classes Lu, Ll, Lt, Lm, Lo, Nl, as of Unicode 4.0, + plus the underscore. */ +static const +struct + { + int header[1]; + int level1[3]; + int level2[3 << 7]; + /*unsigned*/ int level3[34 << 4]; + } +table_identifier_start = +{ + { 3 }, + { 4, 132, 260 }, + { + 388, 404, 420, 436, 452, 468, 484, 500, + 516, 532, 548, 564, 580, -1, 596, 612, + 628, -1, -1, -1, -1, -1, -1, -1, + 644, -1, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 676, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 692, + 660, 660, 708, -1, -1, -1, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 724, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 740, 756, 772, 788, + 804, 820, 836, -1, 852, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 868, 884, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 660, 660, 660, 660, 660, + 660, 660, 660, 900, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 660, 916, -1, -1 + }, + { + 0x00000000, 0x00000000, 0x87FFFFFE, 0x07FFFFFE, + 0x00000000, 0x04200400, 0xFF7FFFFF, 0xFF7FFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x007FFFFF, 0xFFFF0000, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0x0003FFC3, 0x0000401F, + 0x00000000, 0x00000000, 0x00000000, 0x04000000, + 0xFFFFD740, 0xFFFFFFFB, 0xFFFF7FFF, 0x0FBFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFC03, 0xFFFFFFFF, 0xFFFF7FFF, 0x033FFFFF, + 0x0000FFFF, 0xFFFE0000, 0x027FFFFF, 0xFFFFFFFE, + 0x000000FF, 0x00000000, 0xFFFF0000, 0x000707FF, + 0x00000000, 0x07FFFFFE, 0x000007FF, 0xFFFEC000, + 0xFFFFFFFF, 0xFFFFFFFF, 0x002FFFFF, 0x9C00C060, + 0xFFFD0000, 0x0000FFFF, 0x0000E000, 0x00000000, + 0xFFFFFFFF, 0x0002003F, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFF0, 0x23FFFFFF, 0xFF010000, 0x00000003, + 0xFFF99FE0, 0x23C5FDFF, 0xB0000000, 0x00030003, + 0xFFF987E0, 0x036DFDFF, 0x5E000000, 0x001C0000, + 0xFFFBBFE0, 0x23EDFDFF, 0x00010000, 0x00000003, + 0xFFF99FE0, 0x23EDFDFF, 0xB0000000, 0x00020003, + 0xD63DC7E8, 0x03BFC718, 0x00000000, 0x00000000, + 0xFFFDDFE0, 0x03EFFDFF, 0x00000000, 0x00000003, + 0xFFFDDFE0, 0x23EFFDFF, 0x40000000, 0x00000003, + 0xFFFDDFE0, 0x03FFFDFF, 0x00000000, 0x00000003, + 0xFC7FFFE0, 0x2FFBFFFF, 0x0000007F, 0x00000000, + 0xFFFFFFFE, 0x000DFFFF, 0x0000007F, 0x00000000, + 0xFEF02596, 0x200DECAE, 0x3000005F, 0x00000000, + 0x00000001, 0x00000000, 0xFFFFFEFF, 0x000007FF, + 0x00000F00, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0x000006FB, 0x003F0000, 0x00000000, + 0x00000000, 0xFFFFFFFF, 0xFFFF003F, 0x01FFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0x83FFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFF07, 0xFFFFFFFF, 0x03FFFFFF, + 0xFFFFFF7F, 0xFFFFFFFF, 0x3D7F3D7F, 0xFFFFFFFF, + 0xFFFF3D7F, 0x7F3D7FFF, 0xFF7F7F3D, 0xFFFF7FFF, + 0x7F3D7FFF, 0xFFFFFFFF, 0x07FFFF7F, 0x00000000, + 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0x001FFFFF, + 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x007F9FFF, + 0x07FFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0x0001C7FF, + 0x0003DFFF, 0x0003FFFF, 0x0003FFFF, 0x0001DFFF, + 0xFFFFFFFF, 0x000FFFFF, 0x10800000, 0x00000000, + 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00FFFFFF, + 0xFFFFFFFF, 0x000001FF, 0x00000000, 0x00000000, + 0x1FFFFFFF, 0x00000000, 0xFFFF0000, 0x001F3FFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x0FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x03FFFFFF, + 0x3F3FFFFF, 0xFFFFFFFF, 0xAAFF3F3F, 0x3FFFFFFF, + 0xFFFFFFFF, 0x5FDFFFFF, 0x0FCF1FDC, 0x1FDC1FFF, + 0x00000000, 0x00000000, 0x00000000, 0x80020000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3E2FFC84, 0xE3FBBD50, 0x000003E0, 0xFFFFFFFF, + 0x0000000F, 0x00000000, 0x00000000, 0x00000000, + 0x000000E0, 0x1F3E03FE, 0xFFFFFFFE, 0xFFFFFFFF, + 0xE07FFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xF7FFFFFF, + 0xFFFFFFE0, 0xFFFE1FFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00007FFF, 0x00FFFFFF, 0x00000000, 0xFFFF0000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x003FFFFF, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x0000003F, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00001FFF, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x0000000F, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFF3FFF, 0xFFFFFFFF, 0x000007FF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xA0F8007F, 0x5F7FFDFF, 0xFFFFFFDB, 0xFFFFFFFF, + 0xFFFFFFFF, 0x0003FFFF, 0xFFF80000, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x3FFFFFFF, 0xFFFF0000, 0xFFFFFFFF, + 0xFFFCFFFF, 0xFFFFFFFF, 0x000000FF, 0x0FFF0000, + 0x00000000, 0x00000000, 0x00000000, 0xFFDF0000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x1FFFFFFF, + 0x00000000, 0x07FFFFFE, 0x07FFFFFE, 0xFFFFFFC0, + 0xFFFFFFFF, 0x7FFFFFFF, 0x1CFCFCFC, 0x00000000, + 0xFFFFEFFF, 0xB7FFFF7F, 0x3FFF3FFF, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x07FFFFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x7FFFFFFF, 0xFFFF0000, 0x000007FF, 0x00000000, + 0x3FFFFFFF, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x3FFFFFFF, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFD3F, 0x91BFFFFF, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFFFFF, 0xFFFFFFFF, + 0xDFFFFFFF, 0xEBFFDE64, 0xFFFFFFEF, 0xFFFFFFFF, + 0xDFDFE7BF, 0x7BFFFFFF, 0xFFFDFC5F, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFF0F, 0xF7FFFFFD, 0xF7FFFFFF, + 0xFFDFFFFF, 0xFFDFFFFF, 0xFFFF7FFF, 0xFFFF7FFF, + 0xFFFFFDFF, 0xFFFFFDFF, 0x000003F7, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0x007FFFFF, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3FFFFFFF, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 + } +}; + +/* Unicode character classes Lu, Ll, Lt, Lm, Lo, Nl, Nd, Pc, Mn, Mc, Cf, + as of Unicode 4.0. */ +static const +struct + { + int header[1]; + int level1[15]; + int level2[4 << 7]; + /*unsigned*/ int level3[36 << 4]; + } +table_identifier_part = +{ + { 15 }, + { + 16, 144, 272, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 400 + }, + { + 528, 544, 560, 576, 592, 608, 624, 640, + 656, 672, 688, 704, 720, -1, 736, 752, + 768, -1, -1, -1, -1, -1, -1, -1, + 784, -1, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 816, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 832, + 800, 800, 848, -1, -1, -1, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 864, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 880, 896, 912, 928, + 944, 960, 976, -1, 992, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + 1008, -1, 1024, 1040, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 800, 800, 800, 800, 800, + 800, 800, 800, 1056, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 800, 1072, -1, -1, + 1088, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1 + }, + { + 0x00000000, 0x03FF0000, 0x87FFFFFE, 0x07FFFFFE, + 0x00000000, 0x04202400, 0xFF7FFFFF, 0xFF7FFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x007FFFFF, 0xFFFF0000, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0x0003FFC3, 0x0000401F, + 0xFFFFFFFF, 0xFFFFFFFF, 0xE0FFFFFF, 0x0400FFFF, + 0xFFFFD740, 0xFFFFFFFB, 0xFFFF7FFF, 0x0FBFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFC7B, 0xFFFFFFFF, 0xFFFF7FFF, 0x033FFFFF, + 0x0000FFFF, 0xFFFE0000, 0x027FFFFF, 0xFFFFFFFE, + 0xFFFE00FF, 0xBBFFFFFB, 0xFFFF0016, 0x000707FF, + 0x003F000F, 0x07FFFFFE, 0x01FFFFFF, 0xFFFFC3FF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xBFEFFFFF, 0x9FFFFDFF, + 0xFFFF8000, 0xFFFFFFFF, 0x0000E7FF, 0x00000000, + 0xFFFFFFFF, 0x0003FFFF, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFE, 0xF3FFFFFF, 0xFF1F3FFF, 0x0000FFCF, + 0xFFF99FEE, 0xF3C5FDFF, 0xB080399F, 0x0003FFCF, + 0xFFF987EE, 0xD36DFDFF, 0x5E003987, 0x001FFFC0, + 0xFFFBBFEE, 0xF3EDFDFF, 0x00013BBF, 0x0000FFCF, + 0xFFF99FEE, 0xF3EDFDFF, 0xB0C0398F, 0x0002FFC3, + 0xD63DC7EC, 0xC3BFC718, 0x00803DC7, 0x0000FF80, + 0xFFFDDFEE, 0xC3EFFDFF, 0x00603DDF, 0x0000FFC3, + 0xFFFDDFEC, 0xF3EFFDFF, 0x40603DDF, 0x0000FFC3, + 0xFFFDDFEC, 0xC3FFFDFF, 0x00803DCF, 0x0000FFC3, + 0xFC7FFFEC, 0x2FFBFFFF, 0xFF5F847F, 0x000C0000, + 0xFFFFFFFE, 0x07FFFFFF, 0x03FF7FFF, 0x00000000, + 0xFEF02596, 0x3BFFECAE, 0x33FF3F5F, 0x00000000, + 0x03000001, 0xC2A003FF, 0xFFFFFEFF, 0xFFFE07FF, + 0xFEFF0FDF, 0x1FFFFFFF, 0x00000040, 0x00000000, + 0xFFFFFFFF, 0x03C7F6FB, 0x03FF03FF, 0x00000000, + 0x00000000, 0xFFFFFFFF, 0xFFFF003F, 0x01FFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0x83FFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFF07, 0xFFFFFFFF, 0x03FFFFFF, + 0xFFFFFF7F, 0xFFFFFFFF, 0x3D7F3D7F, 0xFFFFFFFF, + 0xFFFF3D7F, 0x7F3D7FFF, 0xFF7F7F3D, 0xFFFF7FFF, + 0x7F3D7FFF, 0xFFFFFFFF, 0x07FFFF7F, 0x0003FE00, + 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0x001FFFFF, + 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x007F9FFF, + 0x07FFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0x0001C7FF, + 0x001FDFFF, 0x001FFFFF, 0x000FFFFF, 0x000DDFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0x308FFFFF, 0x000003FF, + 0x03FF3800, 0xFFFFFFFF, 0xFFFFFFFF, 0x00FFFFFF, + 0xFFFFFFFF, 0x000003FF, 0x00000000, 0x00000000, + 0x1FFFFFFF, 0x0FFF0FFF, 0xFFFFFFC0, 0x001F3FFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000FFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x0FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x03FFFFFF, + 0x3F3FFFFF, 0xFFFFFFFF, 0xAAFF3F3F, 0x3FFFFFFF, + 0xFFFFFFFF, 0x5FDFFFFF, 0x0FCF1FDC, 0x1FDC1FFF, + 0x0000F000, 0x80007C00, 0x00100001, 0x8002FC0F, + 0x00000000, 0x00000000, 0x1FFF0000, 0x000007E2, + 0x3E2FFC84, 0xE3FBBD50, 0x000003E0, 0xFFFFFFFF, + 0x0000000F, 0x00000000, 0x00000000, 0x00000000, + 0x000000E0, 0x1F3EFFFE, 0xFFFFFFFE, 0xFFFFFFFF, + 0xE67FFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFE0, 0xFFFE1FFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00007FFF, 0x00FFFFFF, 0x00000000, 0xFFFF0000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x003FFFFF, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x0000003F, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00001FFF, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x0000000F, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFF3FFF, 0xFFFFFFFF, 0x000007FF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xE0F8007F, 0x5F7FFDFF, 0xFFFFFFDB, 0xFFFFFFFF, + 0xFFFFFFFF, 0x0003FFFF, 0xFFF80000, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0x3FFFFFFF, 0xFFFF0000, 0xFFFFFFFF, + 0xFFFCFFFF, 0xFFFFFFFF, 0x000000FF, 0x0FFF0000, + 0x0000FFFF, 0x0018000F, 0x0000E000, 0xFFDF0000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9FFFFFFF, + 0x03FF0000, 0x87FFFFFE, 0x07FFFFFE, 0xFFFFFFE0, + 0xFFFFFFFF, 0x7FFFFFFF, 0x1CFCFCFC, 0x0E000000, + 0xFFFFEFFF, 0xB7FFFF7F, 0x3FFF3FFF, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x07FFFFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x7FFFFFFF, 0xFFFF0000, 0x000007FF, 0x00000000, + 0x3FFFFFFF, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x3FFFFFFF, 0x000003FF, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFD3F, 0x91BFFFFF, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xFFFFE3E0, + 0x00000FE7, 0x00003C00, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFFFFF, 0xFFFFFFFF, + 0xDFFFFFFF, 0xEBFFDE64, 0xFFFFFFEF, 0xFFFFFFFF, + 0xDFDFE7BF, 0x7BFFFFFF, 0xFFFDFC5F, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFF0F, 0xF7FFFFFD, 0xF7FFFFFF, + 0xFFDFFFFF, 0xFFDFFFFF, 0xFFFF7FFF, 0xFFFF7FFF, + 0xFFFFFDFF, 0xFFFFFDFF, 0xFFFFC3F7, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0x007FFFFF, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x3FFFFFFF, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000002, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF + } +}; + +/* Return true if a given character can occur as first character of an + identifier. See ECMA-334 section 9.4.2. */ +static bool +is_identifier_start (int c) +{ + return bitmap_lookup (&table_identifier_start, c); + /* In ASCII only this would be: + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'); + */ +} + +/* Return true if a given character can occur as character of an identifier. + See ECMA-334 section 9.4.2. */ +static bool +is_identifier_part (int c) +{ + return bitmap_lookup (&table_identifier_part, c); + /* In ASCII only this would be: + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') || c == '_'); + */ +} + +static bool +is_any_character (int c) +{ + return true; +} + + +/* ======================= Preprocessor directives. ======================= */ + + +/* Phase 5: Remove preprocessor lines. See ECMA-334 section 9.5. + As a side effect, this also removes initial whitespace on every line; + this whitespace doesn't matter. */ + +static int phase5_pushback[10]; +static int phase5_pushback_length; + +static int +phase5_getc () +{ + int c; + + if (phase5_pushback_length) + return phase5_pushback[--phase5_pushback_length]; + + c = phase4_getc (); + if (c != UNL) + return c; + + do + c = phase3_getc (); + while (c != UEOF && is_whitespace (c)); + + if (c == '#') + { + /* Ignore the entire line containing the preprocessor directive + (including the // comment if it contains one). */ + do + c = phase3_getc (); + while (c != UEOF && c != UNL); + return c; + } + else + { + phase3_ungetc (c); + return UNL; + } +} + +#ifdef unused +static void +phase5_ungetc (int c) +{ + if (c != UEOF) + { + if (phase5_pushback_length == SIZEOF (phase5_pushback)) + abort (); + phase5_pushback[phase5_pushback_length++] = c; + } +} +#endif + + +/* ========================== Reading of tokens. ========================== */ + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_lbrace, /* { */ + token_type_rbrace, /* } */ + token_type_comma, /* , */ + token_type_dot, /* . */ + token_type_string_literal, /* "abc", @"abc" */ + token_type_number, /* 1.23 */ + token_type_symbol, /* identifier, keyword, null */ + token_type_plus, /* + */ + token_type_other /* character literal, misc. operator */ +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_string_literal, token_type_symbol */ + refcounted_string_list_ty *comment; /* for token_type_string_literal */ + int line_number; + int logical_line_number; +}; + + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_string_literal || tp->type == token_type_symbol) + free (tp->string); + if (tp->type == token_type_string_literal) + drop_reference (tp->comment); +} + + +/* Read a Unicode escape sequence outside string/character literals. + Reject Unicode escapes that don't fulfill the given predicate. + See ECMA-334 section 9.4.2. */ +static int +do_getc_unicode_escaped (bool (*predicate) (int)) +{ + int c; + + /* Use phase 3, because phase 4 elides comments. */ + c = phase3_getc (); + if (c == UEOF) + return '\\'; + if (c == 'u' || c == 'U') + { + unsigned char buf[8]; + int expect; + unsigned int n; + int i; + + expect = (c == 'U' ? 8 : 4); + n = 0; + for (i = 0; i < expect; i++) + { + int c1 = phase3_getc (); + + if (c1 >= '0' && c1 <= '9') + n = (n << 4) + (c1 - '0'); + else if (c1 >= 'A' && c1 <= 'F') + n = (n << 4) + (c1 - 'A' + 10); + else if (c1 >= 'a' && c1 <= 'f') + n = (n << 4) + (c1 - 'a' + 10); + else + { + phase3_ungetc (c1); + while (--i >= 0) + phase3_ungetc (buf[i]); + phase3_ungetc (c); + return '\\'; + } + + buf[i] = c1; + } + + if (n >= 0x110000) + { + error_with_progname = false; + error (0, 0, _("%s:%d: warning: invalid Unicode character"), + logical_file_name, line_number); + error_with_progname = true; + } + else if (predicate (n)) + return n; + + while (--i >= 0) + phase3_ungetc (buf[i]); + } + phase3_ungetc (c); + return '\\'; +} + + +/* Read an escape sequence inside a string literal or character literal. + See ECMA-334 sections 9.4.4.4., 9.4.4.5. */ +static int +do_getc_escaped () +{ + int c; + int n; + int i; + + /* Use phase 3, because phase 4 elides comments. */ + c = phase3_getc (); + if (c == UEOF) + return '\\'; + switch (c) + { + case 'a': + return 0x0007; + case 'b': + return 0x0008; + case 't': + return 0x0009; + case 'n': + return 0x000a; + case 'v': + return 0x000b; + case 'f': + return 0x000c; + case 'r': + return 0x000d; + case '"': + return '"'; + case '\'': + return '\''; + case '\\': + return '\\'; + case '0': + return 0x0000; + case 'x': + c = phase3_getc (); + switch (c) + { + default: + phase3_ungetc (c); + phase3_ungetc ('x'); + return '\\'; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + break; + } + n = 0; + for (i = 0;; i++) + { + switch (c) + { + default: + phase3_ungetc (c); + return n; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = n * 16 + c - '0'; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + n = n * 16 + 10 + c - 'A'; + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + n = n * 16 + 10 + c - 'a'; + break; + } + if (i == 3) + break; + c = phase3_getc (); + } + return n; + case 'u': case 'U': + phase3_ungetc (c); + return do_getc_unicode_escaped (is_any_character); + default: + /* Invalid escape sequence. */ + phase3_ungetc (c); + return '\\'; + } +} + +/* Read a regular string literal or character literal. + See ECMA-334 sections 9.4.4.4., 9.4.4.5. */ +static void +accumulate_escaped (struct mixed_string_buffer *literal, int delimiter) +{ + int c; + + for (;;) + { + /* Use phase 3, because phase 4 elides comments. */ + c = phase3_getc (); + if (c == UEOF || c == delimiter) + break; + if (c == UNL) + { + phase3_ungetc (c); + error_with_progname = false; + if (delimiter == '\'') + error (0, 0, _("%s:%d: warning: unterminated character constant"), + logical_file_name, line_number); + else + error (0, 0, _("%s:%d: warning: unterminated string constant"), + logical_file_name, line_number); + error_with_progname = true; + break; + } + if (c == '\\') + c = do_getc_escaped (); + if (literal) + mixed_string_buffer_append_unicode (literal, c); + } +} + + +/* Combine characters into tokens. Discard whitespace. */ + +/* Maximum used guaranteed to be < 4. */ +static token_ty phase6_pushback[4]; +static int phase6_pushback_length; + +static void +phase6_get (token_ty *tp) +{ + int c; + + if (phase6_pushback_length) + { + *tp = phase6_pushback[--phase6_pushback_length]; + return; + } + tp->string = NULL; + + for (;;) + { + tp->line_number = line_number; + tp->logical_line_number = logical_line_number; + c = phase5_getc (); + + if (c == UEOF) + { + tp->type = token_type_eof; + return; + } + + switch (c) + { + case UNL: + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* FALLTHROUGH */ + case ' ': + case '\t': + case '\f': + /* Ignore whitespace and comments. */ + continue; + } + + last_non_comment_line = tp->logical_line_number; + + switch (c) + { + case '(': + tp->type = token_type_lparen; + return; + + case ')': + tp->type = token_type_rparen; + return; + + case '{': + tp->type = token_type_lbrace; + return; + + case '}': + tp->type = token_type_rbrace; + return; + + case ',': + tp->type = token_type_comma; + return; + + case '.': + c = phase4_getc (); + if (!(c >= '0' && c <= '9')) + { + phase4_ungetc (c); + tp->type = token_type_dot; + return; + } + /* FALLTHROUGH */ + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + /* Don't need to verify the complicated syntax of integers and + floating-point numbers. We assume a valid C# input. + The simplified syntax that we recognize as number is: any + sequence of alphanumeric characters, additionally '+' and '-' + immediately after 'e' or 'E' except in hexadecimal numbers. */ + bool hexadecimal = false; + + for (;;) + { + c = phase4_getc (); + if (c >= '0' && c <= '9') + continue; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' &&c <= 'z')) + { + if (c == 'X' || c == 'x') + hexadecimal = true; + if ((c == 'E' || c == 'e') && !hexadecimal) + { + c = phase4_getc (); + if (!(c == '+' || c == '-')) + phase4_ungetc (c); + } + continue; + } + if (c == '.') + continue; + break; + } + phase4_ungetc (c); + tp->type = token_type_number; + return; + } + + case '"': + /* Regular string literal. */ + { + struct mixed_string_buffer *literal; + + lexical_context = lc_string; + literal = mixed_string_buffer_alloc (lexical_context, + logical_file_name, + logical_line_number); + accumulate_escaped (literal, '"'); + tp->string = mixed_string_buffer_done (literal); + tp->comment = add_reference (savable_comment); + lexical_context = lc_outside; + tp->type = token_type_string_literal; + return; + } + + case '\'': + /* Character literal. */ + { + accumulate_escaped (NULL, '\''); + tp->type = token_type_other; + return; + } + + case '+': + c = phase4_getc (); + if (c == '+') + /* Operator ++ */ + tp->type = token_type_other; + else if (c == '=') + /* Operator += */ + tp->type = token_type_other; + else + { + /* Operator + */ + phase4_ungetc (c); + tp->type = token_type_plus; + } + return; + + case '@': + c = phase4_getc (); + if (c == '"') + { + /* Verbatim string literal. */ + struct string_buffer literal; + + lexical_context = lc_string; + init_string_buffer (&literal); + for (;;) + { + /* Use phase 2, because phase 4 elides comments and phase 3 + mixes up the newline characters. */ + c = phase2_getc (); + if (c == UEOF) + break; + if (c == '"') + { + c = phase2_getc (); + if (c != '"') + { + phase2_ungetc (c); + break; + } + } + /* No special treatment of newline and backslash here. */ + string_buffer_append_unicode (&literal, c); + } + tp->string = xstrdup (string_buffer_result (&literal)); + free_string_buffer (&literal); + tp->comment = add_reference (savable_comment); + lexical_context = lc_outside; + tp->type = token_type_string_literal; + return; + } + /* FALLTHROUGH, so that @identifier is recognized. */ + + default: + if (c == '\\') + c = do_getc_unicode_escaped (is_identifier_start); + if (is_identifier_start (c)) + { + static struct string_buffer buffer; + buffer.utf8_buflen = 0; + for (;;) + { + string_buffer_append_unicode (&buffer, c); + c = phase4_getc (); + if (c == '\\') + c = do_getc_unicode_escaped (is_identifier_part); + if (!is_identifier_part (c)) + break; + } + phase4_ungetc (c); + tp->string = xstrdup (string_buffer_result (&buffer)); + tp->type = token_type_symbol; + return; + } + else + { + /* Misc. operator. */ + tp->type = token_type_other; + return; + } + } + } +} + +/* Supports 3 tokens of pushback. */ +static void +phase6_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase6_pushback_length == SIZEOF (phase6_pushback)) + abort (); + phase6_pushback[phase6_pushback_length++] = *tp; + } +} + + +/* Compile-time optimization of string literal concatenation. + Combine "string1" + ... + "stringN" to the concatenated string if + - the token after this expression is not '.' (because then the last + string could be part of a method call expression). */ + +static token_ty phase7_pushback[2]; +static int phase7_pushback_length; + +static void +phase7_get (token_ty *tp) +{ + if (phase7_pushback_length) + { + *tp = phase7_pushback[--phase7_pushback_length]; + return; + } + + phase6_get (tp); + if (tp->type == token_type_string_literal) + { + char *sum = tp->string; + size_t sum_len = strlen (sum); + + for (;;) + { + token_ty token2; + + phase6_get (&token2); + if (token2.type == token_type_plus) + { + token_ty token3; + + phase6_get (&token3); + if (token3.type == token_type_string_literal) + { + token_ty token_after; + + phase6_get (&token_after); + if (token_after.type != token_type_dot) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = (char *) xrealloc (sum, sum_len + addend_len + 1); + memcpy (sum + sum_len, addend, addend_len + 1); + sum_len += addend_len; + + phase6_unget (&token_after); + free_token (&token3); + free_token (&token2); + continue; + } + phase6_unget (&token_after); + } + phase6_unget (&token3); + } + phase6_unget (&token2); + break; + } + tp->string = sum; + } +} + +/* Supports 2 tokens of pushback. */ +static void +phase7_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase7_pushback_length == SIZEOF (phase7_pushback)) + abort (); + phase7_pushback[phase7_pushback_length++] = *tp; + } +} + + +static void +x_csharp_lex (token_ty *tp) +{ + phase7_get (tp); +} + +/* Supports 2 tokens of pushback. */ +static void +x_csharp_unlex (token_ty *tp) +{ + phase7_unget (tp); +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid C or C++ program, and leave the complaints about + the grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + + +/* Extract messages until the next balanced closing parenthesis or brace, + depending on TERMINATOR. + Extracted messages are added to MLP. + Return true upon eof, false upon closing parenthesis or brace. */ +static bool +extract_parenthesized (message_list_ty *mlp, token_type_ty terminator, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_csharp_lex (&token); + switch (token.type) + { + case token_type_symbol: + { + /* Combine symbol1 . ... . symbolN to a single strings, so that + we can recognize static function calls like + GettextResource.gettext. The information present for + symbolI.....symbolN has precedence over the information for + symbolJ.....symbolN with J > I. */ + char *sum = token.string; + size_t sum_len = strlen (sum); + const char *dottedname; + flag_context_list_ty *context_list; + + for (;;) + { + token_ty token2; + + x_csharp_lex (&token2); + if (token2.type == token_type_dot) + { + token_ty token3; + + x_csharp_lex (&token3); + if (token3.type == token_type_symbol) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = + (char *) xrealloc (sum, sum_len + 1 + addend_len + 1); + sum[sum_len] = '.'; + memcpy (sum + sum_len + 1, addend, addend_len + 1); + sum_len += 1 + addend_len; + + free_token (&token3); + free_token (&token2); + continue; + } + x_csharp_unlex (&token3); + } + x_csharp_unlex (&token2); + break; + } + + for (dottedname = sum;;) + { + void *keyword_value; + + if (hash_find_entry (&keywords, dottedname, strlen (dottedname), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + break; + } + + dottedname = strchr (dottedname, '.'); + if (dottedname == NULL) + { + state = 0; + break; + } + dottedname++; + } + + for (dottedname = sum;;) + { + context_list = + flag_context_list_table_lookup ( + flag_context_list_table, + dottedname, strlen (dottedname)); + if (context_list != NULL) + break; + + dottedname = strchr (dottedname, '.'); + if (dottedname == NULL) + break; + dottedname++; + } + next_context_iter = flag_context_list_iterator (context_list); + + free (sum); + continue; + } + + case token_type_lparen: + if (extract_parenthesized (mlp, token_type_rparen, + inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rparen: + if (terminator == token_type_rparen) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return false; + } + if (terminator == token_type_rbrace) + { + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: ')' found where '}' was expected"), + logical_file_name, token.line_number); + error_with_progname = true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_lbrace: + if (extract_parenthesized (mlp, token_type_rbrace, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rbrace: + if (terminator == token_type_rbrace) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return false; + } + if (terminator == token_type_rparen) + { + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: '}' found where ')' was expected"), + logical_file_name, token.line_number); + error_with_progname = true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case token_type_string_literal: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + xgettext_current_source_encoding = po_charset_utf8; + if (extract_all) + remember_a_message (mlp, NULL, token.string, inner_context, + &pos, NULL, token.comment); + else + arglist_parser_remember (argparser, arg, token.string, + inner_context, + pos.file_name, pos.line_number, + token.comment); + xgettext_current_source_encoding = xgettext_global_source_encoding; + } + drop_reference (token.comment); + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_eof: + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return true; + + case token_type_dot: + case token_type_number: + case token_type_plus: + case token_type_other: + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + default: + abort (); + } + } +} + + +void +extract_csharp (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + lexical_context = lc_outside; + + logical_line_number = 1; + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When extract_parenthesized returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_parenthesized (mlp, token_type_eof, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-csharp.h b/gettext-tools/src/x-csharp.h new file mode 100644 index 0000000..de37789 --- /dev/null +++ b/gettext-tools/src/x-csharp.h @@ -0,0 +1,50 @@ +/* xgettext C# backend. + Copyright (C) 2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_CSHARP \ + { "cs", "C#" }, \ + +#define SCANNERS_CSHARP \ + { "C#", extract_csharp, \ + &flag_table_csharp, &formatstring_csharp, NULL, NULL }, \ + +extern void extract_csharp (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_csharp_keyword (const char *keyword); +extern void x_csharp_extract_all (void); + +extern void init_flag_table_csharp (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-desktop.c b/gettext-tools/src/x-desktop.c new file mode 100644 index 0000000..320266e --- /dev/null +++ b/gettext-tools/src/x-desktop.c @@ -0,0 +1,193 @@ +/* xgettext Desktop Entry backend. + Copyright (C) 2014 Free Software Foundation, Inc. + + This file was written by Daiki Ueno , 2014. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-desktop.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "error-progname.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "hash.h" +#include "gettext.h" +#include "read-desktop.h" +#include "po-charset.h" +#include "c-ctype.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + +/* ====================== Keyword set customization. ====================== */ + +/* The syntax of a Desktop Entry file is defined at + http://standards.freedesktop.org/desktop-entry-spec/latest/index.html + + Basically, values with 'localestring' type can be translated. + + The type of a value is determined by looking at the key associated + with it. The list of available keys are listed on: + http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s05.html */ + +static hash_table keywords; +static bool default_keywords = true; + +static void +add_keyword (const char *name, hash_table *keywords, bool is_list) +{ + if (name == NULL) + default_keywords = false; + else + { + if (keywords->table == NULL) + hash_init (keywords, 100); + + desktop_add_keyword (keywords, name, is_list); + } +} + +void +x_desktop_keyword (const char *name) +{ + add_keyword (name, &keywords, false); +} + +static void +init_keywords (void) +{ + if (default_keywords) + { + if (keywords.table == NULL) + hash_init (&keywords, 100); + + desktop_add_default_keywords (&keywords); + default_keywords = false; + } +} + +typedef struct extract_desktop_reader_ty extract_desktop_reader_ty; +struct extract_desktop_reader_ty +{ + DESKTOP_READER_TY + + message_list_ty *mlp; +}; + +static void +extract_desktop_handle_group (struct desktop_reader_ty *reader, + const char *group) +{ + savable_comment_reset (); +} + +static void +extract_desktop_handle_pair (struct desktop_reader_ty *reader, + lex_pos_ty *key_pos, + const char *key, + const char *locale, + const char *value) +{ + extract_desktop_reader_ty *extract_reader = + (extract_desktop_reader_ty *) reader; + void *keyword_value; + + if (!locale /* Skip already translated entry. */ + && hash_find_entry (&keywords, key, strlen (key), &keyword_value) == 0) + { + bool is_list = (bool) keyword_value; + + remember_a_message (extract_reader->mlp, NULL, + desktop_unescape_string (value, is_list), + null_context, key_pos, + NULL, savable_comment); + } + savable_comment_reset (); +} + +static void +extract_desktop_handle_comment (struct desktop_reader_ty *reader, + const char *buffer) +{ + size_t buflen = strlen (buffer); + size_t bufpos = 0; + + while (bufpos < buflen + && c_isspace (buffer[bufpos])) + ++bufpos; + while (buflen >= bufpos + && c_isspace (buffer[buflen - 1])) + --buflen; + if (bufpos < buflen) + { + char *comment = xstrdup (buffer); + comment[buflen] = 0; + savable_comment_add (&comment[bufpos]); + free (comment); + } +} + +static void +extract_desktop_handle_blank (struct desktop_reader_ty *reader, + const char *s) +{ + savable_comment_reset (); +} + +desktop_reader_class_ty extract_methods = + { + sizeof (extract_desktop_reader_ty), + NULL, + NULL, + extract_desktop_handle_group, + extract_desktop_handle_pair, + extract_desktop_handle_comment, + extract_desktop_handle_blank + }; + +void +extract_desktop (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + desktop_reader_ty *reader = desktop_reader_alloc (&extract_methods); + extract_desktop_reader_ty *extract_reader = + (extract_desktop_reader_ty *) reader; + + init_keywords (); + xgettext_current_source_encoding = po_charset_utf8; + + extract_reader->mlp = mdlp->item[0]->messages; + + desktop_parse (reader, f, real_filename, logical_filename); + desktop_reader_free (reader); + + reader = NULL; +} diff --git a/gettext-tools/src/x-desktop.h b/gettext-tools/src/x-desktop.h new file mode 100644 index 0000000..d24f174 --- /dev/null +++ b/gettext-tools/src/x-desktop.h @@ -0,0 +1,47 @@ +/* xgettext Desktop Entry backend. + Copyright (C) 2014 Free Software Foundation, Inc. + Written by Daiki Ueno , 2014. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_DESKTOP \ + { "desktop", "Desktop" }, \ + +#define SCANNERS_DESKTOP \ + { "Desktop", extract_desktop, NULL, NULL, NULL, NULL }, \ + +/* Scan a Desktop Entry file and add its translatable strings to mdlp. */ +extern void extract_desktop (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_desktop_keyword (const char *keyword); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-elisp.c b/gettext-tools/src/x-elisp.c new file mode 100644 index 0000000..50c3d55 --- /dev/null +++ b/gettext-tools/src/x-elisp.c @@ -0,0 +1,1255 @@ +/* xgettext Emacs Lisp backend. + Copyright (C) 2001-2003, 2005-2009 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2001-2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-elisp.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "hash.h" +#include "c-ctype.h" +#include "gettext.h" + +#define _(s) gettext(s) + + +/* Summary of Emacs Lisp syntax: + - ';' starts a comment until end of line. + - '#@nn' starts a comment of nn bytes. + - Integers are constituted of an optional prefix (#b, #B for binary, + #o, #O for octal, #x, #X for hexadecimal, #nnr, #nnR for any radix), + an optional sign (+ or -), the digits, and an optional trailing dot. + - Characters are written as '?' followed by the character, possibly + with an escape sequence, for examples '?a', '?\n', '?\177'. + - Strings are delimited by double quotes. Backslash introduces an escape + sequence. The following are understood: '\n', '\r', '\f', '\t', '\a', + '\\', '\^C', '\012' (octal), '\x12' (hexadecimal). + - Symbols: can contain meta-characters if preceded by backslash. + - Uninterned symbols: written as #:SYMBOL. + - () delimit lists. + - [] delimit vectors. + The reader is implemented in emacs-21.1/src/lread.c. */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_elisp_extract_all () +{ + extract_all = true; +} + + +void +x_elisp_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid Lisp + symbol. */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_elisp_keyword ("_"); + default_keywords = false; + } +} + +void +init_flag_table_elisp () +{ + xgettext_record_flag ("_:1:pass-elisp-format"); + xgettext_record_flag ("format:1:elisp-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* Fetch the next character from the input file. */ +static int +do_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_file_name); + } + else if (c == '\n') + line_number++; + + return c; +} + +/* Put back the last fetched character, not EOF. */ +static void +do_ungetc (int c) +{ + if (c == '\n') + line_number--; + ungetc (c, fp); +} + + +/* ========================== Reading of tokens. ========================== */ + + +/* A token consists of a sequence of characters. */ +struct token +{ + int allocated; /* number of allocated 'token_char's */ + int charcount; /* number of used 'token_char's */ + char *chars; /* the token's constituents */ +}; + +/* Initialize a 'struct token'. */ +static inline void +init_token (struct token *tp) +{ + tp->allocated = 10; + tp->chars = XNMALLOC (tp->allocated, char); + tp->charcount = 0; +} + +/* Free the memory pointed to by a 'struct token'. */ +static inline void +free_token (struct token *tp) +{ + free (tp->chars); +} + +/* Ensure there is enough room in the token for one more character. */ +static inline void +grow_token (struct token *tp) +{ + if (tp->charcount == tp->allocated) + { + tp->allocated *= 2; + tp->chars = (char *) xrealloc (tp->chars, tp->allocated * sizeof (char)); + } +} + +/* Test whether a token has integer syntax. */ +static inline bool +is_integer (const char *p) +{ + /* NB: Yes, '+.' and '-.' both designate the integer 0. */ + const char *p_start = p; + + if (*p == '+' || *p == '-') + p++; + if (*p == '\0') + return false; + while (*p >= '0' && *p <= '9') + p++; + if (p > p_start && *p == '.') + p++; + return (*p == '\0'); +} + +/* Test whether a token has float syntax. */ +static inline bool +is_float (const char *p) +{ + enum { LEAD_INT = 1, DOT_CHAR = 2, TRAIL_INT = 4, E_CHAR = 8, EXP_INT = 16 }; + int state; + + state = 0; + if (*p == '+' || *p == '-') + p++; + if (*p >= '0' && *p <= '9') + { + state |= LEAD_INT; + do + p++; + while (*p >= '0' && *p <= '9'); + } + if (*p == '.') + { + state |= DOT_CHAR; + p++; + } + if (*p >= '0' && *p <= '9') + { + state |= TRAIL_INT; + do + p++; + while (*p >= '0' && *p <= '9'); + } + if (*p == 'e' || *p == 'E') + { + state |= E_CHAR; + p++; + if (*p == '+' || *p == '-') + p++; + if (*p >= '0' && *p <= '9') + { + state |= EXP_INT; + do + p++; + while (*p >= '0' && *p <= '9'); + } + else if (p[-1] == '+' + && ((p[0] == 'I' && p[1] == 'N' && p[2] == 'F') + || (p[0] == 'N' && p[1] == 'a' && p[2] == 'N'))) + { + state |= EXP_INT; + p += 3; + } + } + return (*p == '\0') + && (state == (LEAD_INT | DOT_CHAR | TRAIL_INT) + || state == (DOT_CHAR | TRAIL_INT) + || state == (LEAD_INT | E_CHAR | EXP_INT) + || state == (LEAD_INT | DOT_CHAR | TRAIL_INT | E_CHAR | EXP_INT) + || state == (DOT_CHAR | TRAIL_INT | E_CHAR | EXP_INT)); +} + +/* Read the next token. 'first' is the first character, which has already + been read. Returns true for a symbol, false for a number. */ +static bool +read_token (struct token *tp, int first) +{ + int c; + bool quoted = false; + + init_token (tp); + + c = first; + + for (;; c = do_getc ()) + { + if (c == EOF) + break; + if (c <= ' ') /* FIXME: Assumes ASCII compatible encoding */ + break; + if (c == '\"' || c == '\'' || c == ';' || c == '(' || c == ')' + || c == '[' || c == ']' || c == '#') + break; + if (c == '\\') + { + quoted = true; + c = do_getc (); + if (c == EOF) + /* Invalid, but be tolerant. */ + break; + } + grow_token (tp); + tp->chars[tp->charcount++] = c; + } + if (c != EOF) + do_ungetc (c); + + if (quoted) + return true; /* symbol */ + + /* Add a NUL byte at the end, for is_integer and is_float. */ + grow_token (tp); + tp->chars[tp->charcount] = '\0'; + + if (is_integer (tp->chars) || is_float (tp->chars)) + return false; /* number */ + else + return true; /* symbol */ +} + + +/* ========================= Accumulating comments ========================= */ + + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (chars_to_remove == 0 && buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ========================= Accumulating messages ========================= */ + + +static message_list_ty *mlp; + + +/* ============== Reading of objects. See CLHS 2 "Syntax". ============== */ + + +/* We are only interested in symbols (e.g. GETTEXT or NGETTEXT) and strings. + Other objects need not to be represented precisely. */ +enum object_type +{ + t_symbol, /* symbol */ + t_string, /* string */ + t_other, /* other kind of real object */ + t_dot, /* '.' pseudo object */ + t_listclose, /* ')' pseudo object */ + t_vectorclose,/* ']' pseudo object */ + t_eof /* EOF marker */ +}; + +struct object +{ + enum object_type type; + struct token *token; /* for t_symbol and t_string */ + int line_number_at_start; /* for t_string */ +}; + +/* Free the memory pointed to by a 'struct object'. */ +static inline void +free_object (struct object *op) +{ + if (op->type == t_symbol || op->type == t_string) + { + free_token (op->token); + free (op->token); + } +} + +/* Convert a t_symbol/t_string token to a char*. */ +static char * +string_of_object (const struct object *op) +{ + char *str; + int n; + + if (!(op->type == t_symbol || op->type == t_string)) + abort (); + n = op->token->charcount; + str = XNMALLOC (n + 1, char); + memcpy (str, op->token->chars, n); + str[n] = '\0'; + return str; +} + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + +/* Returns the character represented by an escape sequence. */ +#define IGNORABLE_ESCAPE (EOF - 1) +static int +do_getc_escaped (int c, bool in_string) +{ + switch (c) + { + case 'a': + return '\a'; + case 'b': + return '\b'; + case 'd': + return 0x7F; + case 'e': + return 0x1B; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'v': + return '\v'; + + case '\n': + return IGNORABLE_ESCAPE; + + case ' ': + return (in_string ? IGNORABLE_ESCAPE : ' '); + + case 'M': /* meta */ + c = do_getc (); + if (c == EOF) + return EOF; + if (c != '-') + /* Invalid input. But be tolerant. */ + return c; + c = do_getc (); + if (c == EOF) + return EOF; + if (c == '\\') + { + c = do_getc (); + if (c == EOF) + return EOF; + c = do_getc_escaped (c, false); + } + return c | 0x80; + + case 'S': /* shift */ + c = do_getc (); + if (c == EOF) + return EOF; + if (c != '-') + /* Invalid input. But be tolerant. */ + return c; + c = do_getc (); + if (c == EOF) + return EOF; + if (c == '\\') + { + c = do_getc (); + if (c == EOF) + return EOF; + c = do_getc_escaped (c, false); + } + return (c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c); + + case 'H': /* hyper */ + case 'A': /* alt */ + case 's': /* super */ + c = do_getc (); + if (c == EOF) + return EOF; + if (c != '-') + /* Invalid input. But be tolerant. */ + return c; + c = do_getc (); + if (c == EOF) + return EOF; + if (c == '\\') + { + c = do_getc (); + if (c == EOF) + return EOF; + c = do_getc_escaped (c, false); + } + return c; + + case 'C': /* ctrl */ + c = do_getc (); + if (c == EOF) + return EOF; + if (c != '-') + /* Invalid input. But be tolerant. */ + return c; + /*FALLTHROUGH*/ + case '^': + c = do_getc (); + if (c == EOF) + return EOF; + if (c == '\\') + { + c = do_getc (); + if (c == EOF) + return EOF; + c = do_getc_escaped (c, false); + } + if (c == '?') + return 0x7F; + if ((c & 0x5F) >= 0x41 && (c & 0x5F) <= 0x5A) + return c & 0x9F; + if ((c & 0x7F) >= 0x40 && (c & 0x7F) <= 0x5F) + return c & 0x9F; +#if 0 /* We cannot handle NUL bytes in strings. */ + if (c == ' ') + return 0x00; +#endif + return c; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + /* An octal escape, as in ANSI C. */ + { + int n = c - '0'; + + c = do_getc (); + if (c != EOF) + { + if (c >= '0' && c <= '7') + { + n = (n << 3) + (c - '0'); + c = do_getc (); + if (c != EOF) + { + if (c >= '0' && c <= '7') + n = (n << 3) + (c - '0'); + else + do_ungetc (c); + } + } + else + do_ungetc (c); + } + return (unsigned char) n; + } + + case 'x': + /* A hexadecimal escape, as in ANSI C. */ + { + int n = 0; + + for (;;) + { + c = do_getc (); + if (c == EOF) + break; + else if (c >= '0' && c <= '9') + n = (n << 4) + (c - '0'); + else if (c >= 'A' && c <= 'F') + n = (n << 4) + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + n = (n << 4) + (c - 'a' + 10); + else + { + do_ungetc (c); + break; + } + } + return (unsigned char) n; + } + + default: + /* Ignore Emacs multibyte character stuff. All the strings we are + interested in are ASCII strings. */ + return c; + } +} + +/* Read the next object. + 'first_in_list' and 'new_backquote_flag' are used for reading old + backquote syntax and new backquote syntax. */ +static void +read_object (struct object *op, bool first_in_list, bool new_backquote_flag, + flag_context_ty outer_context) +{ + for (;;) + { + int c; + + c = do_getc (); + + switch (c) + { + case EOF: + op->type = t_eof; + return; + + case '\n': + /* Comments assumed to be grouped with a message must immediately + precede it, with no non-whitespace token on a line between + both. */ + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + continue; + + case '(': + { + int arg = 0; /* Current argument number. */ + flag_context_list_iterator_ty context_iter; + const struct callshapes *shapes = NULL; + struct arglist_parser *argparser = NULL; + + for (;; arg++) + { + struct object inner; + flag_context_ty inner_context; + + if (arg == 0) + inner_context = null_context; + else + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + + read_object (&inner, arg == 0, new_backquote_flag, + inner_context); + + /* Recognize end of list. */ + if (inner.type == t_listclose) + { + op->type = t_other; + /* Don't bother converting "()" to "NIL". */ + last_non_comment_line = line_number; + if (argparser != NULL) + arglist_parser_done (argparser, arg); + return; + } + + /* Dots are not allowed in every position. ']' is not allowed. + But be tolerant. */ + + /* EOF inside list is illegal. But be tolerant. */ + if (inner.type == t_eof) + break; + + if (arg == 0) + { + /* This is the function position. */ + if (inner.type == t_symbol) + { + char *symbol_name = string_of_object (&inner); + void *keyword_value; + + if (hash_find_entry (&keywords, + symbol_name, strlen (symbol_name), + &keyword_value) + == 0) + shapes = (const struct callshapes *) keyword_value; + + argparser = arglist_parser_alloc (mlp, shapes); + + context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + symbol_name, strlen (symbol_name))); + + free (symbol_name); + } + else + context_iter = null_context_list_iterator; + } + else + { + /* These are the argument positions. */ + if (argparser != NULL && inner.type == t_string) + arglist_parser_remember (argparser, arg, + string_of_object (&inner), + inner_context, + logical_file_name, + inner.line_number_at_start, + savable_comment); + } + + free_object (&inner); + } + + if (argparser != NULL) + arglist_parser_done (argparser, arg); + } + op->type = t_other; + last_non_comment_line = line_number; + return; + + case ')': + /* Tell the caller about the end of list. + Unmatched closing parenthesis is illegal. But be tolerant. */ + op->type = t_listclose; + last_non_comment_line = line_number; + return; + + case '[': + { + for (;;) + { + struct object inner; + + read_object (&inner, false, new_backquote_flag, null_context); + + /* Recognize end of vector. */ + if (inner.type == t_vectorclose) + { + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + /* Dots and ')' are not allowed. But be tolerant. */ + + /* EOF inside vector is illegal. But be tolerant. */ + if (inner.type == t_eof) + break; + + free_object (&inner); + } + } + op->type = t_other; + last_non_comment_line = line_number; + return; + + case ']': + /* Tell the caller about the end of vector. + Unmatched closing bracket is illegal. But be tolerant. */ + op->type = t_vectorclose; + last_non_comment_line = line_number; + return; + + case '\'': + { + struct object inner; + + read_object (&inner, false, new_backquote_flag, null_context); + + /* Dots and EOF are not allowed here. But be tolerant. */ + + free_object (&inner); + + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '`': + if (first_in_list) + goto default_label; + { + struct object inner; + + read_object (&inner, false, true, null_context); + + /* Dots and EOF are not allowed here. But be tolerant. */ + + free_object (&inner); + + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case ',': + if (!new_backquote_flag) + goto default_label; + { + int c = do_getc (); + /* The ,@ handling inside lists is wrong anyway, because + ,@form expands to an unknown number of elements. */ + if (c != EOF && c != '@' && c != '.') + do_ungetc (c); + } + { + struct object inner; + + read_object (&inner, false, false, null_context); + + /* Dots and EOF are not allowed here. But be tolerant. */ + + free_object (&inner); + + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case ';': + { + bool all_semicolons = true; + + last_comment_line = line_number; + comment_start (); + for (;;) + { + int c = do_getc (); + if (c == EOF || c == '\n') + break; + if (c != ';') + all_semicolons = false; + if (!all_semicolons) + { + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + } + comment_line_end (0); + continue; + } + + case '"': + { + op->token = XMALLOC (struct token); + init_token (op->token); + op->line_number_at_start = line_number; + for (;;) + { + int c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + if (c == '"') + break; + if (c == '\\') + { + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + c = do_getc_escaped (c, true); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + if (c == IGNORABLE_ESCAPE) + /* Ignore escaped newline and escaped space. */ + ; + else + { + grow_token (op->token); + op->token->chars[op->token->charcount++] = c; + } + } + else + { + grow_token (op->token); + op->token->chars[op->token->charcount++] = c; + } + } + op->type = t_string; + + if (extract_all) + { + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = op->line_number_at_start; + remember_a_message (mlp, NULL, string_of_object (op), + null_context, &pos, NULL, savable_comment); + } + last_non_comment_line = line_number; + return; + } + + case '?': + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + ; + else if (c == '\\') + { + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + ; + else + { + c = do_getc_escaped (c, false); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + ; + } + } + /* Impossible to deal with Emacs multibyte character stuff here. */ + op->type = t_other; + last_non_comment_line = line_number; + return; + + case '#': + /* Dispatch macro handling. */ + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + { + op->type = t_other; + return; + } + + switch (c) + { + case '^': + c = do_getc (); + if (c == '^') + c = do_getc (); + if (c == '[') + { + /* Read a char table, same syntax as a vector. */ + for (;;) + { + struct object inner; + + read_object (&inner, false, new_backquote_flag, + null_context); + + /* Recognize end of vector. */ + if (inner.type == t_vectorclose) + { + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + /* Dots and ')' are not allowed. But be tolerant. */ + + /* EOF inside vector is illegal. But be tolerant. */ + if (inner.type == t_eof) + break; + + free_object (&inner); + } + op->type = t_other; + last_non_comment_line = line_number; + return; + } + else + /* Invalid input. Be tolerant, no error message. */ + { + op->type = t_other; + if (c != EOF) + last_non_comment_line = line_number; + return; + } + + case '&': + /* Read a bit vector. */ + { + struct object length; + read_object (&length, first_in_list, new_backquote_flag, + null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&length); + } + c = do_getc (); + if (c == '"') + { + struct object string; + read_object (&string, first_in_list, new_backquote_flag, + null_context); + free_object (&string); + } + else + /* Invalid input. Be tolerant, no error message. */ + do_ungetc (c); + op->type = t_other; + last_non_comment_line = line_number; + return; + + case '[': + /* Read a compiled function, same syntax as a vector. */ + case '(': + /* Read a string with properties, same syntax as a list. */ + { + struct object inner; + do_ungetc (c); + read_object (&inner, false, new_backquote_flag, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '@': + /* Read a comment consisting of a given number of bytes. */ + { + unsigned int nskip = 0; + + for (;;) + { + c = do_getc (); + if (!(c >= '0' && c <= '9')) + break; + nskip = 10 * nskip + (c - '0'); + } + if (c != EOF) + { + do_ungetc (c); + for (; nskip > 0; nskip--) + if (do_getc () == EOF) + break; + } + continue; + } + + case '$': + op->type = t_other; + last_non_comment_line = line_number; + return; + + case '\'': + case ':': + case 'S': case 's': /* XEmacs only */ + { + struct object inner; + read_object (&inner, false, new_backquote_flag, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* Read Common Lisp style #n# or #n=. */ + for (;;) + { + c = do_getc (); + if (!(c >= '0' && c <= '9')) + break; + } + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + { + op->type = t_other; + return; + } + if (c == '=') + { + read_object (op, false, new_backquote_flag, outer_context); + last_non_comment_line = line_number; + return; + } + if (c == '#') + { + op->type = t_other; + last_non_comment_line = line_number; + return; + } + if (c == 'R' || c == 'r') + { + /* Read an integer. */ + c = do_getc (); + if (c == '+' || c == '-') + c = do_getc (); + for (; c != EOF; c = do_getc ()) + if (!c_isalnum (c)) + { + do_ungetc (c); + break; + } + op->type = t_other; + last_non_comment_line = line_number; + return; + } + /* Invalid input. Be tolerant, no error message. */ + op->type = t_other; + last_non_comment_line = line_number; + return; + + case 'X': case 'x': + case 'O': case 'o': + case 'B': case 'b': + { + /* Read an integer. */ + c = do_getc (); + if (c == '+' || c == '-') + c = do_getc (); + for (; c != EOF; c = do_getc ()) + if (!c_isalnum (c)) + { + do_ungetc (c); + break; + } + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '*': /* XEmacs only */ + { + /* Read a bit-vector. */ + do + c = do_getc (); + while (c == '0' || c == '1'); + if (c != EOF) + do_ungetc (c); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '+': /* XEmacs only */ + case '-': /* XEmacs only */ + /* Simply assume every feature expression is true. */ + { + struct object inner; + read_object (&inner, false, new_backquote_flag, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + continue; + } + + default: + /* Invalid input. Be tolerant, no error message. */ + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + /*NOTREACHED*/ + abort (); + + case '.': + c = do_getc (); + if (c != EOF) + { + do_ungetc (c); + if (c <= ' ' /* FIXME: Assumes ASCII compatible encoding */ + || strchr ("\"'`,(", c) != NULL) + { + op->type = t_dot; + last_non_comment_line = line_number; + return; + } + } + c = '.'; + /*FALLTHROUGH*/ + default: + default_label: + if (c <= ' ') /* FIXME: Assumes ASCII compatible encoding */ + continue; + /* Read a token. */ + { + bool symbol; + + op->token = XMALLOC (struct token); + symbol = read_token (op->token, c); + if (symbol) + { + op->type = t_symbol; + last_non_comment_line = line_number; + return; + } + else + { + free_token (op->token); + free (op->token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + } + } + } +} + + +void +extract_elisp (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When read_object returns + due to an unbalanced closing parenthesis, just restart it. */ + do + { + struct object toplevel_object; + + read_object (&toplevel_object, false, false, null_context); + + if (toplevel_object.type == t_eof) + break; + + free_object (&toplevel_object); + } + while (!feof (fp)); + + /* Close scanner. */ + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-elisp.h b/gettext-tools/src/x-elisp.h new file mode 100644 index 0000000..f939957 --- /dev/null +++ b/gettext-tools/src/x-elisp.h @@ -0,0 +1,54 @@ +/* xgettext Emacs Lisp backend. + Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_ELISP \ + { "el", "EmacsLisp" }, \ + +#define SCANNERS_ELISP \ + { "EmacsLisp", extract_elisp, \ + &flag_table_elisp, &formatstring_elisp, NULL, NULL }, \ + +/* Scan an Emacs Lisp file and add its translatable strings to mdlp. */ +extern void extract_elisp (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +/* Handling of options specific to this language. */ + +extern void x_elisp_extract_all (void); +extern void x_elisp_keyword (const char *name); + +extern void init_flag_table_elisp (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-glade.c b/gettext-tools/src/x-glade.c new file mode 100644 index 0000000..eebce00 --- /dev/null +++ b/gettext-tools/src/x-glade.c @@ -0,0 +1,612 @@ +/* xgettext glade backend. + Copyright (C) 2002-2003, 2005-2009, 2013 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-glade.h" + +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "basename.h" +#include "progname.h" +#include "xalloc.h" +#include "hash.h" +#include "po-charset.h" +#include "gettext.h" +#include "libexpat-compat.h" + +#define _(s) gettext(s) + + +/* Glade is an XML based format with three variants. The syntax for + each format is defined as follows. + + - Glade 1 + Some example files are contained in libglade-0.16. + + - Glade 2 + See http://library.gnome.org/devel/libglade/unstable/libglade-dtd.html + + - GtkBuilder + See https://developer.gnome.org/gtk3/stable/GtkBuilder.html#BUILDER-UI */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +/* The keywords correspond to the translatable elements in Glade 1. + For Glade 2 and GtkBuilder, translatable content is determined by + the translatable="..." attribute, thus those keywords are not used. */ +static hash_table keywords; +static bool default_keywords = true; + + +void +x_glade_extract_all () +{ + extract_all = true; +} + + +void +x_glade_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + if (keywords.table == NULL) + hash_init (&keywords, 100); + + hash_insert_entry (&keywords, name, strlen (name), NULL); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_glade_keyword ("label"); + x_glade_keyword ("title"); + x_glade_keyword ("text"); + x_glade_keyword ("format"); + x_glade_keyword ("copyright"); + x_glade_keyword ("comments"); + x_glade_keyword ("preview_text"); + x_glade_keyword ("tooltip"); + default_keywords = false; + } +} + + + +/* ============================= XML parsing. ============================= */ + +#if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT + +/* Accumulator for the extracted messages. */ +static message_list_ty *mlp; + +/* Logical filename, used to label the extracted messages. */ +static char *logical_file_name; + +/* XML parser. */ +static XML_Parser parser; + +struct element_state +{ + bool extract_string; + bool extract_context; /* used by Glade 2 */ + char *extracted_comment; /* used by Glade 2 or GtkBuilder */ + char *extracted_context; /* used by GtkBuilder */ + int lineno; + char *buffer; + size_t bufmax; + size_t buflen; +}; +static struct element_state *stack; +static size_t stack_size; + +/* Ensures stack_size >= size. */ +static void +ensure_stack_size (size_t size) +{ + if (size > stack_size) + { + stack_size = 2 * stack_size; + if (stack_size < size) + stack_size = size; + stack = + (struct element_state *) + xrealloc (stack, stack_size * sizeof (struct element_state)); + } +} + +static size_t stack_depth; + +/* Parser logic for each Glade compatible file format. */ +struct element_parser +{ + void (*start_element) (struct element_state *p, const char *name, + const char **attributes); + void (*end_element) (struct element_state *p, const char *name); +}; +static struct element_parser *element_parser; + +static void +start_element_null (struct element_state *p, const char *name, + const char **attributes) +{ +} + +static void +end_element_null (struct element_state *p, const char *name) +{ +} + +static void +start_element_glade1 (struct element_state *p, const char *name, + const char **attributes) +{ + void *hash_result; + + /* In Glade 1, a few specific elements are translatable without + --extract-all option. */ + p->extract_string = extract_all; + if (!p->extract_string) + p->extract_string = + (hash_find_entry (&keywords, name, strlen (name), &hash_result) == 0); +} + +static void +end_element_glade1 (struct element_state *p, const char *name) +{ + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = p->lineno; + + if (p->buffer != NULL) + { + remember_a_message (mlp, NULL, p->buffer, + null_context, &pos, + p->extracted_comment, savable_comment); + p->buffer = NULL; + } +} + +static void +start_element_glade2 (struct element_state *p, const char *name, + const char **attributes) +{ + /* In Glade 2, all and elements are translatable + that have the attribute translatable="yes". + See . + The translator comment is found in the attribute comments="...". + See . + If the element has the attribute context="yes", the content of + the element is in the form "msgctxt|msgid". */ + if (strcmp (name, "property") == 0 || strcmp (name, "atkproperty") == 0) + { + bool has_translatable = false; + bool has_context = false; + const char *extracted_comment = NULL; + const char **attp = attributes; + while (*attp != NULL) + { + if (strcmp (attp[0], "translatable") == 0) + has_translatable = (strcmp (attp[1], "yes") == 0); + else if (strcmp (attp[0], "comments") == 0) + extracted_comment = attp[1]; + else if (strcmp (attp[0], "context") == 0) + has_context = (strcmp (attp[1], "yes") == 0); + attp += 2; + } + p->extract_string = has_translatable; + p->extract_context = has_context; + p->extracted_comment = + (has_translatable && extracted_comment != NULL + ? xstrdup (extracted_comment) + : NULL); + } + + /* In Glade 2, the attribute description="..." of + element is also translatable. */ + if (strcmp (name, "atkaction") == 0) + { + const char **attp = attributes; + while (*attp != NULL) + { + if (strcmp (attp[0], "description") == 0) + { + if (strcmp (attp[1], "") != 0) + { + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = XML_GetCurrentLineNumber (parser); + + remember_a_message (mlp, NULL, xstrdup (attp[1]), + null_context, &pos, + NULL, savable_comment); + } + break; + } + attp += 2; + } + } +} + +static void +end_element_glade2 (struct element_state *p, const char *name) +{ + lex_pos_ty pos; + char *msgid = NULL; + char *msgctxt = NULL; + + pos.file_name = logical_file_name; + pos.line_number = p->lineno; + + if (p->extract_context) + { + char *separator = strchr (p->buffer, '|'); + + if (separator == NULL) + { + error_with_progname = false; + error_at_line (0, 0, + pos.file_name, + pos.line_number, + _("\ +Missing context for the string extracted from '%s' element"), + name); + error_with_progname = true; + } + else + { + *separator = '\0'; + msgid = xstrdup (separator + 1); + msgctxt = xstrdup (p->buffer); + } + } + else + { + msgid = p->buffer; + p->buffer = NULL; + } + + if (msgid != NULL) + remember_a_message (mlp, msgctxt, msgid, + null_context, &pos, + p->extracted_comment, savable_comment); +} + +static void +start_element_gtkbuilder (struct element_state *p, const char *name, + const char **attributes) +{ + /* In GtkBuilder (used by Glade 3), all elements are translatable + that have the attribute translatable="yes". + See . + The translator comment is found in the attribute comments="..." + and context is found in the attribute context="...". */ + bool has_translatable = false; + const char *extracted_comment = NULL; + const char *extracted_context = NULL; + const char **attp = attributes; + while (*attp != NULL) + { + if (strcmp (attp[0], "translatable") == 0) + has_translatable = (strcmp (attp[1], "yes") == 0); + else if (strcmp (attp[0], "comments") == 0) + extracted_comment = attp[1]; + else if (strcmp (attp[0], "context") == 0) + extracted_context = attp[1]; + attp += 2; + } + p->extract_string = has_translatable; + p->extracted_comment = + (has_translatable && extracted_comment != NULL + ? xstrdup (extracted_comment) + : NULL); + p->extracted_context = + (has_translatable && extracted_context != NULL + ? xstrdup (extracted_context) + : NULL); +} + +static void +end_element_gtkbuilder (struct element_state *p, const char *name) +{ + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = p->lineno; + + if (p->buffer != NULL) + { + remember_a_message (mlp, p->extracted_context, p->buffer, + null_context, &pos, + p->extracted_comment, savable_comment); + p->buffer = NULL; + p->extracted_context = NULL; + } +} + +static struct element_parser element_parser_null = +{ + start_element_null, + end_element_null +}; + +static struct element_parser element_parser_glade1 = +{ + start_element_glade1, + end_element_glade1 +}; + +static struct element_parser element_parser_glade2 = +{ + start_element_glade2, + end_element_glade2 +}; + +static struct element_parser element_parser_gtkbuilder = +{ + start_element_gtkbuilder, + end_element_gtkbuilder +}; + +/* Callback called when is seen. */ +static void +start_element_handler (void *userData, const char *name, + const char **attributes) +{ + struct element_state *p; + + if (!stack_depth) + { + if (strcmp (name, "GTK-Interface") == 0) + element_parser = &element_parser_glade1; + else if (strcmp (name, "glade-interface") == 0) + element_parser = &element_parser_glade2; + else if (strcmp (name, "interface") == 0) + element_parser = &element_parser_gtkbuilder; + else + { + element_parser = &element_parser_null; + error_with_progname = false; + error_at_line (0, 0, + logical_file_name, + XML_GetCurrentLineNumber (parser), + _("\ +The root element <%s> is not allowed in a valid Glade file"), + name); + error_with_progname = true; + } + } + + /* Increase stack depth. */ + stack_depth++; + ensure_stack_size (stack_depth + 1); + + /* Don't extract a string for the containing element. */ + stack[stack_depth - 1].extract_string = false; + + p = &stack[stack_depth]; + p->extract_string = false; + p->extract_context = false; + p->extracted_comment = NULL; + p->extracted_context = NULL; + + element_parser->start_element (p, name, attributes); + + p->lineno = XML_GetCurrentLineNumber (parser); + p->buffer = NULL; + p->bufmax = 0; + p->buflen = 0; + if (!p->extract_string) + savable_comment_reset (); +} + +/* Callback called when is seen. */ +static void +end_element_handler (void *userData, const char *name) +{ + struct element_state *p = &stack[stack_depth]; + + /* Actually extract string. */ + if (p->extract_string) + { + /* Don't extract the empty string. */ + if (p->buflen > 0) + { + if (p->buflen == p->bufmax) + p->buffer = (char *) xrealloc (p->buffer, p->buflen + 1); + p->buffer[p->buflen] = '\0'; + + element_parser->end_element (p, name); + } + } + + /* Free memory for this stack level. */ + if (p->extracted_comment != NULL) + free (p->extracted_comment); + if (p->extracted_context != NULL) + free (p->extracted_context); + if (p->buffer != NULL) + free (p->buffer); + + /* Decrease stack depth. */ + stack_depth--; + + savable_comment_reset (); +} + +/* Callback called when some text is seen. */ +static void +character_data_handler (void *userData, const char *s, int len) +{ + struct element_state *p = &stack[stack_depth]; + + /* Accumulate character data. */ + if (len > 0) + { + if (p->buflen + len > p->bufmax) + { + p->bufmax = 2 * p->bufmax; + if (p->bufmax < p->buflen + len) + p->bufmax = p->buflen + len; + p->buffer = (char *) xrealloc (p->buffer, p->bufmax); + } + memcpy (p->buffer + p->buflen, s, len); + p->buflen += len; + } +} + +/* Callback called when some comment text is seen. */ +static void +comment_handler (void *userData, const char *data) +{ + /* Split multiline comment into lines, and remove leading and trailing + whitespace. */ + char *copy = xstrdup (data); + char *p; + char *q; + + for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1) + { + while (p[0] == ' ' || p[0] == '\t') + p++; + while (q > p && (q[-1] == ' ' || q[-1] == '\t')) + q--; + *q = '\0'; + savable_comment_add (p); + } + q = p + strlen (p); + while (p[0] == ' ' || p[0] == '\t') + p++; + while (q > p && (q[-1] == ' ' || q[-1] == '\t')) + q--; + *q = '\0'; + savable_comment_add (p); + free (copy); +} + + +static void +do_extract_glade (FILE *fp, + const char *real_filename, const char *logical_filename, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + /* expat feeds us strings in UTF-8 encoding. */ + xgettext_current_source_encoding = po_charset_utf8; + + logical_file_name = xstrdup (logical_filename); + + init_keywords (); + + parser = XML_ParserCreate (NULL); + if (parser == NULL) + error (EXIT_FAILURE, 0, _("memory exhausted")); + + XML_SetElementHandler (parser, start_element_handler, end_element_handler); + XML_SetCharacterDataHandler (parser, character_data_handler); + XML_SetCommentHandler (parser, comment_handler); + + stack_depth = 0; + element_parser = &element_parser_null; + + while (!feof (fp)) + { + char buf[4096]; + int count = fread (buf, 1, sizeof buf, fp); + + if (count == 0) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_filename); + /* EOF reached. */ + break; + } + + if (XML_Parse (parser, buf, count, 0) == 0) + error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, + (unsigned long) XML_GetCurrentLineNumber (parser), + (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, + XML_ErrorString (XML_GetErrorCode (parser))); + } + + if (XML_Parse (parser, NULL, 0, 1) == 0) + error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, + (unsigned long) XML_GetCurrentLineNumber (parser), + (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, + XML_ErrorString (XML_GetErrorCode (parser))); + + XML_ParserFree (parser); + + /* Close scanner. */ + logical_file_name = NULL; + parser = NULL; +} + +#endif + +void +extract_glade (FILE *fp, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ +#if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT + if (LIBEXPAT_AVAILABLE ()) + do_extract_glade (fp, real_filename, logical_filename, mdlp); + else +#endif + { + multiline_error (xstrdup (""), + xasprintf (_("\ +Language \"glade\" is not supported. %s relies on expat.\n\ +This version was built without expat.\n"), + basename (program_name))); + exit (EXIT_FAILURE); + } +} diff --git a/gettext-tools/src/x-glade.h b/gettext-tools/src/x-glade.h new file mode 100644 index 0000000..f212d2b --- /dev/null +++ b/gettext-tools/src/x-glade.h @@ -0,0 +1,53 @@ +/* xgettext glade backend. + Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_GLADE \ + { "glade", "glade" }, \ + { "glade2", "glade" }, \ + { "ui", "glade" }, \ + +#define SCANNERS_GLADE \ + { "glade", extract_glade, NULL, NULL, NULL, NULL }, \ + +/* Scan a glade XML file and add its translatable strings to mdlp. */ +extern void extract_glade (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +/* Handling of options specific to this language. */ + +extern void x_glade_extract_all (void); +extern void x_glade_keyword (const char *name); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-gsettings.c b/gettext-tools/src/x-gsettings.c new file mode 100644 index 0000000..14a2584 --- /dev/null +++ b/gettext-tools/src/x-gsettings.c @@ -0,0 +1,386 @@ +/* xgettext GSettings schema file backend. + Copyright (C) 2002-2003, 2005-2009, 2013 Free Software Foundation, Inc. + + This file was written by Daiki Ueno , 2013. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-gsettings.h" + +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "basename.h" +#include "progname.h" +#include "xalloc.h" +#include "hash.h" +#include "po-charset.h" +#include "gettext.h" +#include "libexpat-compat.h" + +#define _(s) gettext(s) + + +/* GSettings schema file is an XML based format. + The syntax is defined in glib/gio/gschema.dtd and: + https://developer.gnome.org/gio/unstable/GSettings.html */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + + +/* ============================= XML parsing. ============================= */ + +#if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT + +/* Accumulator for the extracted messages. */ +static message_list_ty *mlp; + +/* Logical filename, used to label the extracted messages. */ +static char *logical_file_name; + +/* XML parser. */ +static XML_Parser parser; + +enum whitespace_type_ty +{ + none, + normalize, + strip +}; +typedef enum whitespace_type_ty whitespace_type_ty; + +struct element_state +{ + bool extract_string; + whitespace_type_ty whitespace; + char *extracted_context; + int lineno; + char *buffer; + size_t bufmax; + size_t buflen; +}; +static struct element_state *stack; +static size_t stack_size; + +static char * +normalize_whitespace (const char *text, whitespace_type_ty whitespace) +{ + if (whitespace == none) + return xstrdup (text); + else + { + char *result, *p; + + /* Strip whitespaces at the beginning/end of the text. */ + result = xstrdup (text + strspn (text, " \t\n")); + for (p = result + strlen (result); + p > result && (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n'); + p--) + ; + if (p > result) + *++p = '\0'; + + /* Normalize whitespaces within the text. */ + if (whitespace == normalize) + { + char *end = result + strlen (result); + for (p = result; *p != '\0';) + { + size_t len = strspn (p, " \t\n"); + if (len > 0) + { + *p = ' '; + memmove (p + 1, p + len, end - (p + len)); + end -= len - 1; + *end = '\0'; + p++; + } + p += strcspn (p, " \t\n"); + } + } + return result; + } +} + +/* Ensures stack_size >= size. */ +static void +ensure_stack_size (size_t size) +{ + if (size > stack_size) + { + stack_size = 2 * stack_size; + if (stack_size < size) + stack_size = size; + stack = + (struct element_state *) + xrealloc (stack, stack_size * sizeof (struct element_state)); + } +} + +static size_t stack_depth; + +/* Callback called when is seen. */ +static void +start_element_handler (void *userData, const char *name, + const char **attributes) +{ + struct element_state *p; + + /* Increase stack depth. */ + stack_depth++; + ensure_stack_size (stack_depth + 1); + + /* Don't extract a string for the containing element. */ + stack[stack_depth - 1].extract_string = false; + + p = &stack[stack_depth]; + p->extract_string = extract_all; + p->extracted_context = NULL; + + if (!p->extract_string) + { + bool has_translatable = false; + whitespace_type_ty whitespace = none; + const char *extracted_context = NULL; + if (strcmp (name, "summary") == 0 || strcmp (name, "description") == 0) + { + has_translatable = true; + whitespace = normalize; + } + else if (strcmp (name, "default") == 0) + { + const char *extracted_l10n = NULL; + const char **attp = attributes; + while (*attp != NULL) + { + if (strcmp (attp[0], "context") == 0) + extracted_context = attp[1]; + else if (strcmp (attp[0], "l10n") == 0) + extracted_l10n = attp[1]; + attp += 2; + } + if (extracted_l10n != NULL) + { + has_translatable = true; + whitespace = strip; + } + } + p->extract_string = has_translatable; + p->whitespace = whitespace; + p->extracted_context = + (has_translatable && extracted_context != NULL + ? xstrdup (extracted_context) + : NULL); + } + + p->lineno = XML_GetCurrentLineNumber (parser); + p->buffer = NULL; + p->bufmax = 0; + p->buflen = 0; + if (!p->extract_string) + savable_comment_reset (); +} + +/* Callback called when is seen. */ +static void +end_element_handler (void *userData, const char *name) +{ + struct element_state *p = &stack[stack_depth]; + + /* Actually extract string. */ + if (p->extract_string) + { + /* Don't extract the empty string. */ + if (p->buflen > 0) + { + lex_pos_ty pos; + + if (p->buflen == p->bufmax) + p->buffer = (char *) xrealloc (p->buffer, p->buflen + 1); + p->buffer[p->buflen] = '\0'; + + pos.file_name = logical_file_name; + pos.line_number = p->lineno; + + if (p->buffer != NULL) + { + remember_a_message (mlp, p->extracted_context, + normalize_whitespace (p->buffer, + p->whitespace), + null_context, &pos, + NULL, savable_comment); + p->extracted_context = NULL; + } + } + } + + /* Free memory for this stack level. */ + if (p->extracted_context != NULL) + free (p->extracted_context); + if (p->buffer != NULL) + free (p->buffer); + + /* Decrease stack depth. */ + stack_depth--; + + savable_comment_reset (); +} + +/* Callback called when some text is seen. */ +static void +character_data_handler (void *userData, const char *s, int len) +{ + struct element_state *p = &stack[stack_depth]; + + /* Accumulate character data. */ + if (len > 0) + { + if (p->buflen + len > p->bufmax) + { + p->bufmax = 2 * p->bufmax; + if (p->bufmax < p->buflen + len) + p->bufmax = p->buflen + len; + p->buffer = (char *) xrealloc (p->buffer, p->bufmax); + } + memcpy (p->buffer + p->buflen, s, len); + p->buflen += len; + } +} + +/* Callback called when some comment text is seen. */ +static void +comment_handler (void *userData, const char *data) +{ + /* Split multiline comment into lines, and remove leading and trailing + whitespace. */ + char *copy = xstrdup (data); + char *p; + char *q; + + for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1) + { + while (p[0] == ' ' || p[0] == '\t') + p++; + while (q > p && (q[-1] == ' ' || q[-1] == '\t')) + q--; + *q = '\0'; + savable_comment_add (p); + } + q = p + strlen (p); + while (p[0] == ' ' || p[0] == '\t') + p++; + while (q > p && (q[-1] == ' ' || q[-1] == '\t')) + q--; + *q = '\0'; + savable_comment_add (p); + free (copy); +} + + +static void +do_extract_gsettings (FILE *fp, + const char *real_filename, const char *logical_filename, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + /* expat feeds us strings in UTF-8 encoding. */ + xgettext_current_source_encoding = po_charset_utf8; + + logical_file_name = xstrdup (logical_filename); + + parser = XML_ParserCreate (NULL); + if (parser == NULL) + error (EXIT_FAILURE, 0, _("memory exhausted")); + + XML_SetElementHandler (parser, start_element_handler, end_element_handler); + XML_SetCharacterDataHandler (parser, character_data_handler); + XML_SetCommentHandler (parser, comment_handler); + + stack_depth = 0; + + while (!feof (fp)) + { + char buf[4096]; + int count = fread (buf, 1, sizeof buf, fp); + + if (count == 0) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_filename); + /* EOF reached. */ + break; + } + + if (XML_Parse (parser, buf, count, 0) == 0) + error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, + (unsigned long) XML_GetCurrentLineNumber (parser), + (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, + XML_ErrorString (XML_GetErrorCode (parser))); + } + + if (XML_Parse (parser, NULL, 0, 1) == 0) + error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, + (unsigned long) XML_GetCurrentLineNumber (parser), + (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, + XML_ErrorString (XML_GetErrorCode (parser))); + + XML_ParserFree (parser); + + /* Close scanner. */ + logical_file_name = NULL; + parser = NULL; +} + +#endif + +void +extract_gsettings (FILE *fp, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ +#if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT + if (LIBEXPAT_AVAILABLE ()) + do_extract_gsettings (fp, real_filename, logical_filename, mdlp); + else +#endif + { + multiline_error (xstrdup (""), + xasprintf (_("\ +Language \"gsettings\" is not supported. %s relies on expat.\n\ +This version was built without expat.\n"), + basename (program_name))); + exit (EXIT_FAILURE); + } +} diff --git a/gettext-tools/src/x-gsettings.h b/gettext-tools/src/x-gsettings.h new file mode 100644 index 0000000..b06016d --- /dev/null +++ b/gettext-tools/src/x-gsettings.h @@ -0,0 +1,45 @@ +/* xgettext GSettings schema file backend. + Copyright (C) 2002-2003, 2006, 2013 Free Software Foundation, Inc. + Written by Daiki Ueno , 2013. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_GSETTINGS \ + { "gschema.xml", "gsettings" }, \ + +#define SCANNERS_GSETTINGS \ + { "gsettings", extract_gsettings, NULL, NULL, NULL, NULL }, \ + +/* Scan a gsettings XML file and add its translatable strings to mdlp. */ +extern void extract_gsettings (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-java.c b/gettext-tools/src/x-java.c new file mode 100644 index 0000000..58c1ad7 --- /dev/null +++ b/gettext-tools/src/x-java.c @@ -0,0 +1,1497 @@ +/* xgettext Java backend. + Copyright (C) 2003, 2005-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-java.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "hash.h" +#include "po-charset.h" +#include "unistr.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The Java syntax is defined in the + Java Language Specification, Second Edition, + (available from http://java.sun.com/), + chapter 3 "Lexical Structure". */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_java_extract_all () +{ + extract_all = true; +} + + +void +x_java_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid Java + identifier sequence with dots. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_java_keyword ("GettextResource.gettext:2"); /* static method */ + x_java_keyword ("GettextResource.ngettext:2,3"); /* static method */ + x_java_keyword ("GettextResource.pgettext:2c,3"); /* static method */ + x_java_keyword ("GettextResource.npgettext:2c,3,4"); /* static method */ + x_java_keyword ("gettext"); + x_java_keyword ("ngettext:1,2"); + x_java_keyword ("pgettext:1c,2"); + x_java_keyword ("npgettext:1c,2,3"); + x_java_keyword ("getString"); /* ResourceBundle.getString */ + default_keywords = false; + } +} + +void +init_flag_table_java () +{ + xgettext_record_flag ("GettextResource.gettext:2:pass-java-format"); + xgettext_record_flag ("GettextResource.ngettext:2:pass-java-format"); + xgettext_record_flag ("GettextResource.ngettext:3:pass-java-format"); + xgettext_record_flag ("GettextResource.pgettext:3:pass-java-format"); + xgettext_record_flag ("GettextResource.npgettext:3:pass-java-format"); + xgettext_record_flag ("GettextResource.npgettext:4:pass-java-format"); + xgettext_record_flag ("gettext:1:pass-java-format"); + xgettext_record_flag ("ngettext:1:pass-java-format"); + xgettext_record_flag ("ngettext:2:pass-java-format"); + xgettext_record_flag ("pgettext:2:pass-java-format"); + xgettext_record_flag ("npgettext:2:pass-java-format"); + xgettext_record_flag ("npgettext:3:pass-java-format"); + xgettext_record_flag ("getString:1:pass-java-format"); + xgettext_record_flag ("MessageFormat:1:java-format"); + xgettext_record_flag ("MessageFormat.format:1:java-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* Fetch the next single-byte character from the input file. + Pushback can consist of an unlimited number of 'u' followed by up to 4 + other characters. */ + +/* Special coding of multiple 'u's in the pushback buffer. */ +#define MULTIPLE_U(count) (0x1000 + (count)) + +static int phase1_pushback[5]; +static unsigned int phase1_pushback_length; + +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + { + c = phase1_pushback[--phase1_pushback_length]; + if (c >= MULTIPLE_U (0)) + { + if (c > MULTIPLE_U (1)) + phase1_pushback[phase1_pushback_length++] = c - 1; + return 'u'; + } + else + return c; + } + + c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_file_name); + } + + return c; +} + +/* Supports any number of 'u' and up to 4 arbitrary characters of pushback. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == 'u') + { + if (phase1_pushback_length > 0 + && phase1_pushback[phase1_pushback_length - 1] >= MULTIPLE_U (0)) + phase1_pushback[phase1_pushback_length - 1]++; + else + { + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = MULTIPLE_U (1); + } + } + else + { + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + } + } +} + + +/* Fetch the next single-byte character or Unicode character from the file. + (Here, as in the Java Language Specification, when we say "Unicode + character", we actually mean "UTF-16 encoding unit".) */ + +/* Return value of phase 2, 3, 4 when EOF is reached. */ +#define P2_EOF 0xffff + +/* Convert an UTF-16 code point to a return value that can be distinguished + from a single-byte return value. */ +#define UNICODE(code) (0x10000 + (code)) + +/* Test a return value of phase 2, 3, 4 whether it designates an UTF-16 code + point. */ +#define IS_UNICODE(p2_result) ((p2_result) >= 0x10000) + +/* Extract the UTF-16 code of a return value that satisfies IS_UNICODE. */ +#define UTF16_VALUE(p2_result) ((p2_result) - 0x10000) + +/* Reduces a return value of phase 2, 3, 4 by unmasking the UNICODE bit, + so that it can be more easily compared against an ASCII character. + (RED (c) == 'x') is equivalent to (c == 'x' || c == UNICODE ('x')). */ +#define RED(p2_result) ((p2_result) & 0xffff) + +static int phase2_pushback[1]; +static int phase2_pushback_length; + +static int +phase2_getc () +{ + int c; + + if (phase2_pushback_length) + return phase2_pushback[--phase2_pushback_length]; + + c = phase1_getc (); + if (c == EOF) + return P2_EOF; + if (c == '\\') + { + c = phase1_getc (); + if (c == 'u') + { + unsigned int u_count = 1; + unsigned char buf[4]; + unsigned int n; + int i; + + for (;;) + { + c = phase1_getc (); + if (c != 'u') + break; + u_count++; + } + phase1_ungetc (c); + + n = 0; + for (i = 0; i < 4; i++) + { + c = phase1_getc (); + + if (c >= '0' && c <= '9') + n = (n << 4) + (c - '0'); + else if (c >= 'A' && c <= 'F') + n = (n << 4) + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + n = (n << 4) + (c - 'a' + 10); + else + { + phase1_ungetc (c); + while (--i >= 0) + phase1_ungetc (buf[i]); + for (; u_count > 0; u_count--) + phase1_ungetc ('u'); + return '\\'; + } + + buf[i] = c; + } + return UNICODE (n); + } + phase1_ungetc (c); + return '\\'; + } + return c; +} + +/* Supports only one pushback character. */ +static void +phase2_ungetc (int c) +{ + if (c != P2_EOF) + { + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + } +} + + +/* Fetch the next single-byte character or Unicode character from the file. + With line number handling. + Convert line terminators to '\n' or UNICODE ('\n'). */ + +static int phase3_pushback[2]; +static int phase3_pushback_length; + +static int +phase3_getc () +{ + int c; + + if (phase3_pushback_length) + { + c = phase3_pushback[--phase3_pushback_length]; + if (c == '\n') + ++line_number; + return c; + } + + c = phase2_getc (); + + /* Handle line terminators. */ + if (RED (c) == '\r') + { + int c1 = phase2_getc (); + + if (RED (c1) != '\n') + phase2_ungetc (c1); + + /* Seen line terminator CR or CR/LF. */ + if (c == '\r' || c1 == '\n') + { + ++line_number; + return '\n'; + } + else + return UNICODE ('\n'); + } + else if (RED (c) == '\n') + { + /* Seen line terminator LF. */ + if (c == '\n') + { + ++line_number; + return '\n'; + } + else + return UNICODE ('\n'); + } + + return c; +} + +/* Supports 2 characters of pushback. */ +static void +phase3_ungetc (int c) +{ + if (c != P2_EOF) + { + if (c == '\n') + --line_number; + if (phase3_pushback_length == SIZEOF (phase3_pushback)) + abort (); + phase3_pushback[phase3_pushback_length++] = c; + } +} + + +/* ========================= Accumulating strings. ======================== */ + +/* A string buffer type that allows appending bytes (in the + xgettext_current_source_encoding) or Unicode characters. + Returns the entire string in UTF-8 encoding. */ + +struct string_buffer +{ + /* The part of the string that has already been converted to UTF-8. */ + char *utf8_buffer; + size_t utf8_buflen; + size_t utf8_allocated; + /* The first half of an UTF-16 surrogate character. */ + unsigned short utf16_surr; + /* The part of the string that is still in the source encoding. */ + char *curr_buffer; + size_t curr_buflen; + size_t curr_allocated; + /* The lexical context. Used only for error message purposes. */ + lexical_context_ty lcontext; +}; + +/* Initialize a 'struct string_buffer' to empty. */ +static inline void +init_string_buffer (struct string_buffer *bp, lexical_context_ty lcontext) +{ + bp->utf8_buffer = NULL; + bp->utf8_buflen = 0; + bp->utf8_allocated = 0; + bp->utf16_surr = 0; + bp->curr_buffer = NULL; + bp->curr_buflen = 0; + bp->curr_allocated = 0; + bp->lcontext = lcontext; +} + +/* Auxiliary function: Append a byte to bp->curr. */ +static inline void +string_buffer_append_byte (struct string_buffer *bp, unsigned char c) +{ + if (bp->curr_buflen == bp->curr_allocated) + { + bp->curr_allocated = 2 * bp->curr_allocated + 10; + bp->curr_buffer = xrealloc (bp->curr_buffer, bp->curr_allocated); + } + bp->curr_buffer[bp->curr_buflen++] = c; +} + +/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ +static inline void +string_buffer_append_unicode_grow (struct string_buffer *bp, size_t count) +{ + if (bp->utf8_buflen + count > bp->utf8_allocated) + { + size_t new_allocated = 2 * bp->utf8_allocated + 10; + if (new_allocated < bp->utf8_buflen + count) + new_allocated = bp->utf8_buflen + count; + bp->utf8_allocated = new_allocated; + bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); + } +} + +/* Auxiliary function: Append a Unicode character to bp->utf8. + uc must be < 0x110000. */ +static inline void +string_buffer_append_unicode (struct string_buffer *bp, ucs4_t uc) +{ + unsigned char utf8buf[6]; + int count = u8_uctomb (utf8buf, uc, 6); + + if (count < 0) + /* The caller should have ensured that uc is not out-of-range. */ + abort (); + + string_buffer_append_unicode_grow (bp, count); + memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); + bp->utf8_buflen += count; +} + +/* Auxiliary function: Handle the attempt to append a lone surrogate to + bp->utf8. */ +static void +string_buffer_append_lone_surrogate (struct string_buffer *bp, unsigned int uc) +{ + /* A half surrogate is invalid, therefore use U+FFFD instead. + It appears to be valid Java: The Java Language Specification, + 3rd ed., says "The Java programming language represents text + in sequences of 16-bit code units, using the UTF-16 encoding." + but does not impose constraints on the use of \uxxxx escape + sequences for surrogates. And the JDK's javac happily groks + half surrogates. + But a half surrogate is invalid in UTF-8: + - RFC 3629 says + "The definition of UTF-8 prohibits encoding character + numbers between U+D800 and U+DFFF". + - Unicode 4.0 chapter 3 + + section 3.9, p.77, says + "Because surrogate code points are not Unicode scalar + values, any UTF-8 byte sequence that would otherwise + map to code points D800..DFFF is ill-formed." + and in table 3-6, p. 78, does not mention D800..DFFF. + - The unicode.org FAQ question "How do I convert an unpaired + UTF-16 surrogate to UTF-8?" has the answer + "By representing such an unpaired surrogate on its own + as a 3-byte sequence, the resulting UTF-8 data stream + would become ill-formed." + So use U+FFFD instead. */ + error_with_progname = false; + error (0, 0, _("%s:%d: warning: lone surrogate U+%04X"), + logical_file_name, line_number, uc); + error_with_progname = true; + string_buffer_append_unicode (bp, 0xfffd); +} + +/* Auxiliary function: Flush bp->utf16_surr into bp->utf8_buffer. */ +static inline void +string_buffer_flush_utf16_surr (struct string_buffer *bp) +{ + if (bp->utf16_surr != 0) + { + string_buffer_append_lone_surrogate (bp, bp->utf16_surr); + bp->utf16_surr = 0; + } +} + +/* Auxiliary function: Flush bp->curr_buffer into bp->utf8_buffer. */ +static inline void +string_buffer_flush_curr_buffer (struct string_buffer *bp, int lineno) +{ + if (bp->curr_buflen > 0) + { + char *curr; + size_t count; + + string_buffer_append_byte (bp, '\0'); + + /* Convert from the source encoding to UTF-8. */ + curr = from_current_source_encoding (bp->curr_buffer, bp->lcontext, + logical_file_name, lineno); + + /* Append it to bp->utf8_buffer. */ + count = strlen (curr); + string_buffer_append_unicode_grow (bp, count); + memcpy (bp->utf8_buffer + bp->utf8_buflen, curr, count); + bp->utf8_buflen += count; + + if (curr != bp->curr_buffer) + free (curr); + bp->curr_buflen = 0; + } +} + +/* Append a character or Unicode character to a 'struct string_buffer'. */ +static void +string_buffer_append (struct string_buffer *bp, int c) +{ + if (IS_UNICODE (c)) + { + /* Append a Unicode character. */ + + /* Switch from multibyte character mode to Unicode character mode. */ + string_buffer_flush_curr_buffer (bp, line_number); + + /* Test whether this character and the previous one form a Unicode + surrogate character pair. */ + if (bp->utf16_surr != 0 + && (c >= UNICODE (0xdc00) && c < UNICODE (0xe000))) + { + unsigned short utf16buf[2]; + ucs4_t uc; + + utf16buf[0] = bp->utf16_surr; + utf16buf[1] = UTF16_VALUE (c); + if (u16_mbtouc (&uc, utf16buf, 2) != 2) + abort (); + + string_buffer_append_unicode (bp, uc); + bp->utf16_surr = 0; + } + else + { + string_buffer_flush_utf16_surr (bp); + + if (c >= UNICODE (0xd800) && c < UNICODE (0xdc00)) + bp->utf16_surr = UTF16_VALUE (c); + else if (c >= UNICODE (0xdc00) && c < UNICODE (0xe000)) + string_buffer_append_lone_surrogate (bp, UTF16_VALUE (c)); + else + string_buffer_append_unicode (bp, UTF16_VALUE (c)); + } + } + else + { + /* Append a single byte. */ + + /* Switch from Unicode character mode to multibyte character mode. */ + string_buffer_flush_utf16_surr (bp); + + /* When a newline is seen, convert the accumulated multibyte sequence. + This ensures a correct line number in the error message in case of + a conversion error. The "- 1" is to account for the newline. */ + if (c == '\n') + string_buffer_flush_curr_buffer (bp, line_number - 1); + + string_buffer_append_byte (bp, (unsigned char) c); + } +} + +/* Return the string buffer's contents. */ +static char * +string_buffer_result (struct string_buffer *bp) +{ + /* Flush all into bp->utf8_buffer. */ + string_buffer_flush_utf16_surr (bp); + string_buffer_flush_curr_buffer (bp, line_number); + /* NUL-terminate it. */ + string_buffer_append_unicode_grow (bp, 1); + bp->utf8_buffer[bp->utf8_buflen] = '\0'; + /* Return it. */ + return bp->utf8_buffer; +} + +/* Free the memory pointed to by a 'struct string_buffer'. */ +static inline void +free_string_buffer (struct string_buffer *bp) +{ + free (bp->utf8_buffer); + free (bp->curr_buffer); +} + + +/* ======================== Accumulating comments. ======================== */ + + +/* Accumulating a single comment line. */ + +static struct string_buffer comment_buffer; + +static inline void +comment_start () +{ + comment_buffer.utf8_buflen = 0; + comment_buffer.utf16_surr = 0; + comment_buffer.curr_buflen = 0; + comment_buffer.lcontext = lc_comment; +} + +static inline bool +comment_at_start () +{ + return (comment_buffer.utf8_buflen == 0 && comment_buffer.utf16_surr == 0 + && comment_buffer.curr_buflen == 0); +} + +static inline void +comment_add (int c) +{ + string_buffer_append (&comment_buffer, c); +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + char *buffer = string_buffer_result (&comment_buffer); + size_t buflen = strlen (buffer); + + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* Replace each comment that is not inside a character constant or string + literal with a space or newline character. */ + +static int +phase4_getc () +{ + int c0; + int c; + bool last_was_star; + + c0 = phase3_getc (); + if (RED (c0) != '/') + return c0; + c = phase3_getc (); + switch (RED (c)) + { + default: + phase3_ungetc (c); + return c0; + + case '*': + /* C style comment. */ + comment_start (); + last_was_star = false; + for (;;) + { + c = phase3_getc (); + if (c == P2_EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(comment_at_start () && (RED (c) == ' ' || RED (c) == '\t'))) + comment_add (c); + switch (RED (c)) + { + case '\n': + comment_line_end (1); + comment_start (); + last_was_star = false; + continue; + + case '*': + last_was_star = true; + continue; + + case '/': + if (last_was_star) + { + comment_line_end (2); + break; + } + /* FALLTHROUGH */ + + default: + last_was_star = false; + continue; + } + break; + } + last_comment_line = line_number; + return ' '; + + case '/': + /* C++ style comment. */ + last_comment_line = line_number; + comment_start (); + for (;;) + { + c = phase3_getc (); + if (RED (c) == '\n' || c == P2_EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(comment_at_start () && (RED (c) == ' ' || RED (c) == '\t'))) + comment_add (c); + } + phase3_ungetc (c); /* push back the newline, to decrement line_number */ + comment_line_end (0); + phase3_getc (); /* read the newline again */ + return '\n'; + } +} + +/* Supports only one pushback character. */ +static void +phase4_ungetc (int c) +{ + phase3_ungetc (c); +} + + +/* ========================== Reading of tokens. ========================== */ + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_lbrace, /* { */ + token_type_rbrace, /* } */ + token_type_comma, /* , */ + token_type_dot, /* . */ + token_type_string_literal, /* "abc" */ + token_type_number, /* 1.23 */ + token_type_symbol, /* identifier, keyword, null */ + token_type_plus, /* + */ + token_type_other /* character literal, misc. operator */ +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_string_literal, token_type_symbol */ + refcounted_string_list_ty *comment; /* for token_type_string_literal */ + int line_number; +}; + + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_string_literal || tp->type == token_type_symbol) + free (tp->string); + if (tp->type == token_type_string_literal) + drop_reference (tp->comment); +} + + +/* Read an escape sequence inside a string literal or character literal. */ +static inline int +do_getc_escaped () +{ + int c; + + /* Use phase 3, because phase 4 elides comments. */ + c = phase3_getc (); + if (c == P2_EOF) + return UNICODE ('\\'); + switch (RED (c)) + { + case 'b': + return UNICODE (0x08); + case 't': + return UNICODE (0x09); + case 'n': + return UNICODE (0x0a); + case 'f': + return UNICODE (0x0c); + case 'r': + return UNICODE (0x0d); + case '"': + return UNICODE ('"'); + case '\'': + return UNICODE ('\''); + case '\\': + return UNICODE ('\\'); + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + int n = RED (c) - '0'; + bool maybe3digits = (n < 4); + + c = phase3_getc (); + if (RED (c) >= '0' && RED (c) <= '7') + { + n = (n << 3) + (RED (c) - '0'); + if (maybe3digits) + { + c = phase3_getc (); + if (RED (c) >= '0' && RED (c) <= '7') + n = (n << 3) + (RED (c) - '0'); + else + phase3_ungetc (c); + } + } + else + phase3_ungetc (c); + + return UNICODE (n); + } + default: + /* Invalid escape sequence. */ + phase3_ungetc (c); + return UNICODE ('\\'); + } +} + +/* Read a string literal or character literal. */ +static void +accumulate_escaped (struct string_buffer *literal, int delimiter) +{ + int c; + + for (;;) + { + /* Use phase 3, because phase 4 elides comments. */ + c = phase3_getc (); + if (c == P2_EOF || RED (c) == delimiter) + break; + if (RED (c) == '\n') + { + phase3_ungetc (c); + error_with_progname = false; + if (delimiter == '\'') + error (0, 0, _("%s:%d: warning: unterminated character constant"), + logical_file_name, line_number); + else + error (0, 0, _("%s:%d: warning: unterminated string constant"), + logical_file_name, line_number); + error_with_progname = true; + break; + } + if (RED (c) == '\\') + c = do_getc_escaped (); + string_buffer_append (literal, c); + } +} + + +/* Combine characters into tokens. Discard whitespace. */ + +static token_ty phase5_pushback[3]; +static int phase5_pushback_length; + +static void +phase5_get (token_ty *tp) +{ + int c; + + if (phase5_pushback_length) + { + *tp = phase5_pushback[--phase5_pushback_length]; + return; + } + tp->string = NULL; + + for (;;) + { + tp->line_number = line_number; + c = phase4_getc (); + + if (c == P2_EOF) + { + tp->type = token_type_eof; + return; + } + + switch (RED (c)) + { + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* FALLTHROUGH */ + case ' ': + case '\t': + case '\f': + /* Ignore whitespace and comments. */ + continue; + } + + last_non_comment_line = tp->line_number; + + switch (RED (c)) + { + case '(': + tp->type = token_type_lparen; + return; + + case ')': + tp->type = token_type_rparen; + return; + + case '{': + tp->type = token_type_lbrace; + return; + + case '}': + tp->type = token_type_rbrace; + return; + + case ',': + tp->type = token_type_comma; + return; + + case '.': + c = phase4_getc (); + if (!(RED (c) >= '0' && RED (c) <= '9')) + { + phase4_ungetc (c); + tp->type = token_type_dot; + return; + } + /* FALLTHROUGH */ + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + /* Don't need to verify the complicated syntax of integers and + floating-point numbers. We assume a valid Java input. + The simplified syntax that we recognize as number is: any + sequence of alphanumeric characters, additionally '+' and '-' + immediately after 'e' or 'E' except in hexadecimal numbers. */ + bool hexadecimal = false; + + for (;;) + { + c = phase4_getc (); + if (RED (c) >= '0' && RED (c) <= '9') + continue; + if ((RED (c) >= 'A' && RED (c) <= 'Z') + || (RED (c) >= 'a' && RED (c) <= 'z')) + { + if (RED (c) == 'X' || RED (c) == 'x') + hexadecimal = true; + if ((RED (c) == 'E' || RED (c) == 'e') && !hexadecimal) + { + c = phase4_getc (); + if (!(RED (c) == '+' || RED (c) == '-')) + phase4_ungetc (c); + } + continue; + } + if (RED (c) == '.') + continue; + break; + } + phase4_ungetc (c); + tp->type = token_type_number; + return; + } + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + /* Although Java allows identifiers containing many Unicode + characters, we recognize only identifiers consisting of ASCII + characters. This avoids conversion hassles w.r.t. the --keyword + arguments, and shouldn't be a big problem in practice. */ + { + static char *buffer; + static int bufmax; + int bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = RED (c); + c = phase4_getc (); + if (!((RED (c) >= 'A' && RED (c) <= 'Z') + || (RED (c) >= 'a' && RED (c) <= 'z') + || (RED (c) >= '0' && RED (c) <= '9') + || RED (c) == '_')) + break; + } + phase4_ungetc (c); + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + tp->string = xstrdup (buffer); + tp->type = token_type_symbol; + return; + } + + case '"': + /* String literal. */ + { + struct string_buffer literal; + + init_string_buffer (&literal, lc_string); + accumulate_escaped (&literal, '"'); + tp->string = xstrdup (string_buffer_result (&literal)); + free_string_buffer (&literal); + tp->comment = add_reference (savable_comment); + tp->type = token_type_string_literal; + return; + } + + case '\'': + /* Character literal. */ + { + struct string_buffer literal; + + init_string_buffer (&literal, lc_outside); + accumulate_escaped (&literal, '\''); + free_string_buffer (&literal); + tp->type = token_type_other; + return; + } + + case '+': + c = phase4_getc (); + if (RED (c) == '+') + /* Operator ++ */ + tp->type = token_type_other; + else if (RED (c) == '=') + /* Operator += */ + tp->type = token_type_other; + else + { + /* Operator + */ + phase4_ungetc (c); + tp->type = token_type_plus; + } + return; + + default: + /* Misc. operator. */ + tp->type = token_type_other; + return; + } + } +} + +/* Supports 3 tokens of pushback. */ +static void +phase5_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase5_pushback_length == SIZEOF (phase5_pushback)) + abort (); + phase5_pushback[phase5_pushback_length++] = *tp; + } +} + + +/* Compile-time optimization of string literal concatenation. + Combine "string1" + ... + "stringN" to the concatenated string if + - the token before this expression is not ')' (because then the first + string could be part of a cast expression), + - the token after this expression is not '.' (because then the last + string could be part of a method call expression). */ + +static token_ty phase6_pushback[2]; +static int phase6_pushback_length; + +static token_type_ty phase6_last; + +static void +phase6_get (token_ty *tp) +{ + if (phase6_pushback_length) + { + *tp = phase6_pushback[--phase6_pushback_length]; + return; + } + + phase5_get (tp); + if (tp->type == token_type_string_literal && phase6_last != token_type_rparen) + { + char *sum = tp->string; + size_t sum_len = strlen (sum); + + for (;;) + { + token_ty token2; + + phase5_get (&token2); + if (token2.type == token_type_plus) + { + token_ty token3; + + phase5_get (&token3); + if (token3.type == token_type_string_literal) + { + token_ty token_after; + + phase5_get (&token_after); + if (token_after.type != token_type_dot) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = (char *) xrealloc (sum, sum_len + addend_len + 1); + memcpy (sum + sum_len, addend, addend_len + 1); + sum_len += addend_len; + + phase5_unget (&token_after); + free_token (&token3); + free_token (&token2); + continue; + } + phase5_unget (&token_after); + } + phase5_unget (&token3); + } + phase5_unget (&token2); + break; + } + tp->string = sum; + } + phase6_last = tp->type; +} + +/* Supports 2 tokens of pushback. */ +static void +phase6_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase6_pushback_length == SIZEOF (phase6_pushback)) + abort (); + phase6_pushback[phase6_pushback_length++] = *tp; + } +} + + +static void +x_java_lex (token_ty *tp) +{ + phase6_get (tp); +} + +/* Supports 2 tokens of pushback. */ +static void +x_java_unlex (token_ty *tp) +{ + phase6_unget (tp); +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid C or C++ program, and leave the complaints about + the grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + + +/* Extract messages until the next balanced closing parenthesis or brace, + depending on TERMINATOR. + Extracted messages are added to MLP. + Return true upon eof, false upon closing parenthesis or brace. */ +static bool +extract_parenthesized (message_list_ty *mlp, token_type_ty terminator, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_java_lex (&token); + switch (token.type) + { + case token_type_symbol: + { + /* Combine symbol1 . ... . symbolN to a single strings, so that + we can recognize static function calls like + GettextResource.gettext. The information present for + symbolI.....symbolN has precedence over the information for + symbolJ.....symbolN with J > I. */ + char *sum = token.string; + size_t sum_len = strlen (sum); + const char *dottedname; + flag_context_list_ty *context_list; + + for (;;) + { + token_ty token2; + + x_java_lex (&token2); + if (token2.type == token_type_dot) + { + token_ty token3; + + x_java_lex (&token3); + if (token3.type == token_type_symbol) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = + (char *) xrealloc (sum, sum_len + 1 + addend_len + 1); + sum[sum_len] = '.'; + memcpy (sum + sum_len + 1, addend, addend_len + 1); + sum_len += 1 + addend_len; + + free_token (&token3); + free_token (&token2); + continue; + } + x_java_unlex (&token3); + } + x_java_unlex (&token2); + break; + } + + for (dottedname = sum;;) + { + void *keyword_value; + + if (hash_find_entry (&keywords, dottedname, strlen (dottedname), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + break; + } + + dottedname = strchr (dottedname, '.'); + if (dottedname == NULL) + { + state = 0; + break; + } + dottedname++; + } + + for (dottedname = sum;;) + { + context_list = + flag_context_list_table_lookup ( + flag_context_list_table, + dottedname, strlen (dottedname)); + if (context_list != NULL) + break; + + dottedname = strchr (dottedname, '.'); + if (dottedname == NULL) + break; + dottedname++; + } + next_context_iter = flag_context_list_iterator (context_list); + + free (sum); + continue; + } + + case token_type_lparen: + if (extract_parenthesized (mlp, token_type_rparen, + inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rparen: + if (terminator == token_type_rparen) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return false; + } + if (terminator == token_type_rbrace) + { + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: ')' found where '}' was expected"), + logical_file_name, token.line_number); + error_with_progname = true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_lbrace: + if (extract_parenthesized (mlp, token_type_rbrace, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rbrace: + if (terminator == token_type_rbrace) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return false; + } + if (terminator == token_type_rparen) + { + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: '}' found where ')' was expected"), + logical_file_name, token.line_number); + error_with_progname = true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case token_type_string_literal: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + xgettext_current_source_encoding = po_charset_utf8; + if (extract_all) + remember_a_message (mlp, NULL, token.string, inner_context, + &pos, NULL, token.comment); + else + arglist_parser_remember (argparser, arg, token.string, + inner_context, + pos.file_name, pos.line_number, + token.comment); + xgettext_current_source_encoding = xgettext_global_source_encoding; + } + drop_reference (token.comment); + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_eof: + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return true; + + case token_type_dot: + case token_type_number: + case token_type_plus: + case token_type_other: + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + default: + abort (); + } + } +} + + +void +extract_java (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + phase6_last = token_type_eof; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When extract_parenthesized returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_parenthesized (mlp, token_type_eof, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-java.h b/gettext-tools/src/x-java.h new file mode 100644 index 0000000..49f2af9 --- /dev/null +++ b/gettext-tools/src/x-java.h @@ -0,0 +1,50 @@ +/* xgettext Java backend. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Tommy Johansson , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_JAVA \ + { "java", "Java" }, \ + +#define SCANNERS_JAVA \ + { "Java", extract_java, \ + &flag_table_java, &formatstring_java, NULL, NULL }, \ + +extern void extract_java (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_java_keyword (const char *keyword); +extern void x_java_extract_all (void); + +extern void init_flag_table_java (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-javascript.c b/gettext-tools/src/x-javascript.c new file mode 100644 index 0000000..35c9a9e --- /dev/null +++ b/gettext-tools/src/x-javascript.c @@ -0,0 +1,1673 @@ +/* xgettext JavaScript backend. + Copyright (C) 2002-2003, 2005-2009, 2013 Free Software Foundation, Inc. + + This file was written by Andreas Stricker , 2010 + It's based on x-python from Bruno Haible. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-javascript.h" + +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "basename.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "xalloc.h" +#include "c-strstr.h" +#include "c-ctype.h" +#include "po-charset.h" +#include "unistr.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define max(a,b) ((a) > (b) ? (a) : (b)) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + +/* The JavaScript aka ECMA-Script syntax is defined in ECMA-262 + specification: + http://www.ecma-international.org/publications/standards/Ecma-262.htm */ + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_javascript_extract_all () +{ + extract_all = true; +} + + +void +x_javascript_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_javascript_keyword ("gettext"); + x_javascript_keyword ("dgettext:2"); + x_javascript_keyword ("dcgettext:2"); + x_javascript_keyword ("ngettext:1,2"); + x_javascript_keyword ("dngettext:2,3"); + x_javascript_keyword ("pgettext:1c,2"); + x_javascript_keyword ("dpgettext:2c,3"); + x_javascript_keyword ("_"); + default_keywords = false; + } +} + +void +init_flag_table_javascript () +{ + xgettext_record_flag ("gettext:1:pass-javascript-format"); + xgettext_record_flag ("dgettext:2:pass-javascript-format"); + xgettext_record_flag ("dcgettext:2:pass-javascript-format"); + xgettext_record_flag ("ngettext:1:pass-javascript-format"); + xgettext_record_flag ("ngettext:2:pass-javascript-format"); + xgettext_record_flag ("dngettext:2:pass-javascript-format"); + xgettext_record_flag ("dngettext:3:pass-javascript-format"); + xgettext_record_flag ("pgettext:2:pass-javascript-format"); + xgettext_record_flag ("dpgettext:3:pass-javascript-format"); + xgettext_record_flag ("_:1:pass-javascript-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* 1. line_number handling. */ + +/* Maximum used, roughly a safer MB_LEN_MAX. */ +#define MAX_PHASE1_PUSHBACK 16 +static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK]; +static int phase1_pushback_length; + +/* Read the next single byte from the input file. */ +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + c = phase1_pushback[--phase1_pushback_length]; + else + { + c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + } + + if (c == '\n') + ++line_number; + + return c; +} + +/* Supports MAX_PHASE1_PUSHBACK characters of pushback. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + --line_number; + + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + } +} + + +/* Phase 2: Conversion to Unicode. + For now, we expect JavaScript files to be encoded as UTF-8. */ + +/* End-of-file indicator for functions returning an UCS-4 character. */ +#define UEOF -1 + +static lexical_context_ty lexical_context; + +/* Maximum used, length of " 0) + phase1_ungetc (buf[--insize]); + /* Convert the character from UTF-8 to UCS-4. */ + if (u8_mbtoucr (&uc, scratchbuf, outbytes) < (int) outbytes) + { + /* scratchbuf contains an out-of-range Unicode character + (> 0x10ffff). */ + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Invalid multibyte sequence.\n\ +Please specify the source encoding through --from-code\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + return uc; + } + } +#else + /* If we don't have iconv(), the only supported values for + xgettext_global_source_encoding and thus also for + xgettext_current_source_encoding are ASCII and UTF-8. */ + abort (); +#endif + } + else + { + /* Read an UTF-8 encoded character. */ + unsigned char buf[6]; + unsigned int count; + int c; + ucs4_t uc; + + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[0] = c; + count = 1; + + if (buf[0] >= 0xc0) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[1] = c; + count = 2; + } + + if (buf[0] >= 0xe0 + && ((buf[1] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[2] = c; + count = 3; + } + + if (buf[0] >= 0xf0 + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[3] = c; + count = 4; + } + + if (buf[0] >= 0xf8 + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40) + && ((buf[3] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[4] = c; + count = 5; + } + + if (buf[0] >= 0xfc + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40) + && ((buf[3] ^ 0x80) < 0x40) + && ((buf[4] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[5] = c; + count = 6; + } + + u8_mbtouc (&uc, buf, count); + return uc; + } +} + +/* Supports max (9, UNINAME_MAX + 3) pushback characters. */ +static void +phase2_ungetc (int c) +{ + if (c != UEOF) + { + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + } +} + + +/* ========================= Accumulating strings. ======================== */ + +/* A string buffer type that allows appending Unicode characters. + Returns the entire string in UTF-8 encoding. */ + +struct unicode_string_buffer +{ + /* The part of the string that has already been converted to UTF-8. */ + char *utf8_buffer; + size_t utf8_buflen; + size_t utf8_allocated; +}; + +/* Initialize a 'struct unicode_string_buffer' to empty. */ +static inline void +init_unicode_string_buffer (struct unicode_string_buffer *bp) +{ + bp->utf8_buffer = NULL; + bp->utf8_buflen = 0; + bp->utf8_allocated = 0; +} + +/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ +static inline void +unicode_string_buffer_append_unicode_grow (struct unicode_string_buffer *bp, + size_t count) +{ + if (bp->utf8_buflen + count > bp->utf8_allocated) + { + size_t new_allocated = 2 * bp->utf8_allocated + 10; + if (new_allocated < bp->utf8_buflen + count) + new_allocated = bp->utf8_buflen + count; + bp->utf8_allocated = new_allocated; + bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); + } +} + +/* Auxiliary function: Append a Unicode character to bp->utf8. + uc must be < 0x110000. */ +static inline void +unicode_string_buffer_append_unicode (struct unicode_string_buffer *bp, + unsigned int uc) +{ + unsigned char utf8buf[6]; + int count = u8_uctomb (utf8buf, uc, 6); + + if (count < 0) + /* The caller should have ensured that uc is not out-of-range. */ + abort (); + + unicode_string_buffer_append_unicode_grow (bp, count); + memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); + bp->utf8_buflen += count; +} + +/* Return the string buffer's contents. */ +static char * +unicode_string_buffer_result (struct unicode_string_buffer *bp) +{ + /* NUL-terminate it. */ + unicode_string_buffer_append_unicode_grow (bp, 1); + bp->utf8_buffer[bp->utf8_buflen] = '\0'; + /* Return it. */ + return bp->utf8_buffer; +} + +/* Free the memory pointed to by a 'struct unicode_string_buffer'. */ +static inline void +free_unicode_string_buffer (struct unicode_string_buffer *bp) +{ + free (bp->utf8_buffer); +} + + +/* ======================== Accumulating comments. ======================== */ + + +/* Accumulating a single comment line. */ + +static struct unicode_string_buffer comment_buffer; + +static inline void +comment_start () +{ + lexical_context = lc_comment; + comment_buffer.utf8_buflen = 0; +} + +static inline bool +comment_at_start () +{ + return (comment_buffer.utf8_buflen == 0); +} + +static inline void +comment_add (int c) +{ + unicode_string_buffer_append_unicode (&comment_buffer, c); +} + +static inline const char * +comment_line_end (size_t chars_to_remove) +{ + char *buffer = unicode_string_buffer_result (&comment_buffer); + size_t buflen = strlen (buffer) - chars_to_remove; + + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + buffer[buflen] = '\0'; + savable_comment_add (buffer); + lexical_context = lc_outside; + return buffer; +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ======================== Recognizing comments. ======================== */ + + +/* Canonicalized encoding name for the current input file. */ +static const char *xgettext_current_file_source_encoding; + +#if HAVE_ICONV +/* Converter from xgettext_current_file_source_encoding to UTF-8 (except from + ASCII or UTF-8, when this conversion is a no-op). */ +static iconv_t xgettext_current_file_source_iconv; +#endif + +/* Tracking whether the current line is a continuation line or contains a + non-blank character. */ +static bool continuation_or_nonblank_line = false; + + +/* Phase 3: Outside strings, replace backslash-newline with nothing and a + comment with nothing. */ + +static int +phase3_getc () +{ + int c; + + for (;;) + { + c = phase2_getc (); + if (c == '\\') + { + c = phase2_getc (); + if (c != '\n') + { + phase2_ungetc (c); + /* This shouldn't happen usually, because "A backslash is + illegal elsewhere on a line outside a string literal." */ + return '\\'; + } + /* Eat backslash-newline. */ + continuation_or_nonblank_line = true; + } + else if (c == '/') + { + c = phase2_getc (); + if (c == '/') + { + /* C++ style comment. */ + last_comment_line = line_number; + comment_start (); + for (;;) + { + c = phase2_getc (); + if (c == UEOF || c == '\n') + { + comment_line_end (0); + break; + } + /* We skip all leading white space, but not EOLs. */ + if (!(comment_at_start () && (c == ' ' || c == '\t'))) + comment_add (c); + } + continuation_or_nonblank_line = false; + return c; + } + else if (c == '*') + { + /* C style comment. */ + bool last_was_star = false; + last_comment_line = line_number; + comment_start (); + for (;;) + { + c = phase2_getc (); + if (c == UEOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(comment_at_start () && (c == ' ' || c == '\t'))) + comment_add (c); + switch (c) + { + case '\n': + comment_line_end (1); + comment_start (); + last_was_star = false; + continue; + + case '*': + last_was_star = true; + continue; + case '/': + if (last_was_star) + { + comment_line_end (2); + break; + } + /* FALLTHROUGH */ + + default: + last_was_star = false; + continue; + } + break; + } + continuation_or_nonblank_line = false; + } + else + { + phase2_ungetc (c); + return '/'; + } + } + else + { + if (c == '\n') + continuation_or_nonblank_line = false; + else if (!(c == ' ' || c == '\t' || c == '\f')) + continuation_or_nonblank_line = true; + return c; + } + } +} + +/* Supports only one pushback character. */ +static void +phase3_ungetc (int c) +{ + phase2_ungetc (c); +} + + +/* ========================= Accumulating strings. ======================== */ + +/* Return value of phase7_getuc when EOF is reached. */ +#define P7_EOF (-1) +#define P7_STRING_END (-2) + +/* Convert an UTF-16 or UTF-32 code point to a return value that can be + distinguished from a single-byte return value. */ +#define UNICODE(code) (0x100 + (code)) + +/* Test a return value of phase7_getuc whether it designates an UTF-16 or + UTF-32 code point. */ +#define IS_UNICODE(p7_result) ((p7_result) >= 0x100) + +/* Extract the UTF-16 or UTF-32 code of a return value that satisfies + IS_UNICODE. */ +#define UNICODE_VALUE(p7_result) ((p7_result) - 0x100) + + +/* ========================== Reading of tokens. ========================== */ + + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_comma, /* , */ + token_type_lbracket, /* [ */ + token_type_rbracket, /* ] */ + token_type_plus, /* + */ + token_type_regexp, /* /.../ */ + token_type_operator, /* - * / % . < > = ~ ! | & ? : ^ */ + token_type_equal, /* = */ + token_type_string, /* "abc", 'abc' */ + token_type_keyword, /* return, else */ + token_type_symbol, /* symbol, number */ + token_type_other /* misc. operator */ +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_string, token_type_symbol, + token_type_keyword */ + refcounted_string_list_ty *comment; /* for token_type_string */ + int line_number; +}; + + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_string || tp->type == token_type_symbol) + free (tp->string); + if (tp->type == token_type_string) + drop_reference (tp->comment); +} + + +/* JavaScript provides strings with either double or single quotes: + "abc" or 'abc' + Both may contain special sequences after a backslash: + \', \", \\, \b, \f, \n, \r, \t, \v + Special characters can be entered using hexadecimal escape + sequences or deprecated octal escape sequences: + \xXX, \OOO + Any unicode point can be entered using Unicode escape sequences: + \uNNNN + If a sequence after a backslash is not a legitimate character + escape sequence, the character value is the sequence itself without + a backslash. For example, \xxx is treated as xxx. */ + +static int +phase7_getuc (int quote_char) +{ + int c; + + for (;;) + { + /* Use phase 2, because phase 3 elides comments. */ + c = phase2_getc (); + + if (c == UEOF) + return P7_EOF; + + if (c == quote_char) + return P7_STRING_END; + + if (c == '\n') + { + phase2_ungetc (c); + error_with_progname = false; + error (0, 0, _("%s:%d: warning: unterminated string"), + logical_file_name, line_number); + error_with_progname = true; + return P7_STRING_END; + } + + if (c != '\\') + return UNICODE (c); + + /* Dispatch according to the character following the backslash. */ + c = phase2_getc (); + if (c == UEOF) + return P7_EOF; + + switch (c) + { + case '\n': + continue; + case 'b': + return UNICODE ('\b'); + case 'f': + return UNICODE ('\f'); + case 'n': + return UNICODE ('\n'); + case 'r': + return UNICODE ('\r'); + case 't': + return UNICODE ('\t'); + case 'v': + return UNICODE ('\v'); + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + { + int n = c - '0'; + + c = phase2_getc (); + if (c != UEOF) + { + if (c >= '0' && c <= '7') + { + n = (n << 3) + (c - '0'); + c = phase2_getc (); + if (c != UEOF) + { + if (c >= '0' && c <= '7') + n = (n << 3) + (c - '0'); + else + phase2_ungetc (c); + } + } + else + phase2_ungetc (c); + } + return UNICODE (n); + } + case 'x': + { + int c1 = phase2_getc (); + int n1; + + if (c1 >= '0' && c1 <= '9') + n1 = c1 - '0'; + else if (c1 >= 'A' && c1 <= 'F') + n1 = c1 - 'A' + 10; + else if (c1 >= 'a' && c1 <= 'f') + n1 = c1 - 'a' + 10; + else + n1 = -1; + + if (n1 >= 0) + { + int c2 = phase2_getc (); + int n2; + + if (c2 >= '0' && c2 <= '9') + n2 = c2 - '0'; + else if (c2 >= 'A' && c2 <= 'F') + n2 = c2 - 'A' + 10; + else if (c2 >= 'a' && c2 <= 'f') + n2 = c2 - 'a' + 10; + else + n2 = -1; + + if (n2 >= 0) + { + int n = (n1 << 4) + n2; + return UNICODE (n); + } + + phase2_ungetc (c2); + } + phase2_ungetc (c1); + return UNICODE (c); + } + case 'u': + { + unsigned char buf[4]; + unsigned int n = 0; + int i; + + for (i = 0; i < 4; i++) + { + int c1 = phase2_getc (); + + if (c1 >= '0' && c1 <= '9') + n = (n << 4) + (c1 - '0'); + else if (c1 >= 'A' && c1 <= 'F') + n = (n << 4) + (c1 - 'A' + 10); + else if (c1 >= 'a' && c1 <= 'f') + n = (n << 4) + (c1 - 'a' + 10); + else + { + phase2_ungetc (c1); + while (--i >= 0) + phase2_ungetc (buf[i]); + return UNICODE (c); + } + + buf[i] = c1; + } + return UNICODE (n); + } + default: + return UNICODE (c); + } + } +} + + +/* Combine characters into tokens. Discard whitespace except newlines at + the end of logical lines. */ + +static token_ty phase5_pushback[2]; +static int phase5_pushback_length; + +static token_type_ty last_token_type = token_type_other; + +static void +phase5_scan_regexp () +{ + int c; + + /* Scan for end of RegExp literal ('/'). */ + for (;;) + { + /* Must use phase2 as there can't be comments. */ + c = phase2_getc (); + if (c == '/') + break; + if (c == '\\') + { + c = phase2_getc (); + if (c != UEOF) + continue; + } + if (c == UEOF) + { + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: RegExp literal terminated too early"), + logical_file_name, line_number); + error_with_progname = true; + return; + } + } + + /* Scan for modifier flags (ECMA-262 5th section 15.10.4.1). */ + c = phase2_getc (); + if (!(c == 'g' || c == 'i' || c == 'm')) + phase2_ungetc (c); +} + +static int xml_element_depth = 0; +static bool inside_embedded_js_in_xml = false; + +static bool +phase5_scan_xml_markup (token_ty *tp) +{ + struct + { + const char *start; + const char *end; + } markers[] = + { + { "!--", "--" }, + { "![CDATA[", "]]" }, + { "?", "?" } + }; + int i; + + for (i = 0; i < SIZEOF (markers); i++) + { + const char *start = markers[i].start; + const char *end = markers[i].end; + int j; + + /* Look for a start marker. */ + for (j = 0; start[j] != '\0'; j++) + { + int c; + + assert (phase2_pushback_length + j < SIZEOF (phase2_pushback)); + c = phase2_getc (); + if (c == UEOF) + goto eof; + if (c != start[j]) + { + int k = j; + + phase2_ungetc (c); + k--; + + for (; k >= 0; k--) + phase2_ungetc (start[k]); + break; + } + } + + if (start[j] != '\0') + continue; + + /* Skip until the end marker. */ + for (;;) + { + int c; + + for (j = 0; end[j] != '\0'; j++) + { + assert (phase2_pushback_length + 1 < SIZEOF (phase2_pushback)); + c = phase2_getc (); + if (c == UEOF) + goto eof; + if (c != end[j]) + { + /* Don't push the first character back so the next + iteration start from the second character. */ + if (j > 0) + { + int k = j; + + phase2_ungetc (c); + k--; + + for (; k > 0; k--) + phase2_ungetc (end[k]); + } + break; + } + } + + if (end[j] != '\0') + continue; + + c = phase2_getc (); + if (c == UEOF) + goto eof; + if (c != '>') + { + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: %s is not allowed"), + logical_file_name, line_number, + end); + error_with_progname = true; + return false; + } + return true; + } + } + return false; + + eof: + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: unterminated XML markup"), + logical_file_name, line_number); + error_with_progname = true; + return false; +} + +static void +phase5_get (token_ty *tp) +{ + int c; + + if (phase5_pushback_length) + { + *tp = phase5_pushback[--phase5_pushback_length]; + last_token_type = tp->type; + return; + } + + for (;;) + { + tp->line_number = line_number; + c = phase3_getc (); + + switch (c) + { + case UEOF: + tp->type = last_token_type = token_type_eof; + return; + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* FALLTHROUGH */ + case ' ': + case '\t': + case '\f': + /* Ignore whitespace and comments. */ + continue; + } + + last_non_comment_line = tp->line_number; + + switch (c) + { + case '.': + { + int c1 = phase3_getc (); + phase3_ungetc (c1); + if (!(c1 >= '0' && c1 <= '9')) + { + + tp->type = last_token_type = token_type_other; + return; + } + } + /* FALLTHROUGH */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* Symbol, or part of a number. */ + { + static char *buffer; + static int bufmax; + int bufpos; + + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase3_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + default: + phase3_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + tp->string = xstrdup (buffer); + if (strcmp (buffer, "return") == 0 + || strcmp (buffer, "else") == 0) + tp->type = last_token_type = token_type_keyword; + else + tp->type = last_token_type = token_type_symbol; + return; + } + + /* Strings. */ + { + struct mixed_string_buffer *bp; + int quote_char; + + case '"': case '\'': + quote_char = c; + lexical_context = lc_string; + /* Start accumulating the string. */ + bp = mixed_string_buffer_alloc (lexical_context, + logical_file_name, + line_number); + for (;;) + { + int uc = phase7_getuc (quote_char); + + /* Keep line_number in sync. */ + bp->line_number = line_number; + + if (uc == P7_EOF || uc == P7_STRING_END) + break; + + if (IS_UNICODE (uc)) + { + assert (UNICODE_VALUE (uc) >= 0 + && UNICODE_VALUE (uc) < 0x110000); + mixed_string_buffer_append_unicode (bp, + UNICODE_VALUE (uc)); + } + else + mixed_string_buffer_append_char (bp, uc); + } + tp->string = mixed_string_buffer_done (bp); + tp->comment = add_reference (savable_comment); + lexical_context = lc_outside; + tp->type = last_token_type = token_type_string; + return; + } + + case '+': + tp->type = last_token_type = token_type_plus; + return; + + /* Identify operators. The multiple character ones are simply ignored + * as they are recognized here and are otherwise not relevant. */ + case '-': case '*': /* '+' and '/' are not listed here! */ + case '%': + case '~': case '!': case '|': case '&': case '^': + case '?': case ':': + tp->type = last_token_type = token_type_operator; + return; + + case '=': + tp->type = last_token_type = token_type_equal; + return; + + case '<': + { + /* We assume: + - XMLMarkup and XMLElement are only allowed after '=' or '(' + - embedded JavaScript expressions in XML do not recurse + */ + if (xml_element_depth > 0 + || (!inside_embedded_js_in_xml + && (last_token_type == token_type_equal + || last_token_type == token_type_lparen))) + { + /* Comments, PI, or CDATA. */ + if (phase5_scan_xml_markup (tp)) + return; + c = phase2_getc (); + + /* Closing tag. */ + if (c == '/') + lexical_context = lc_xml_close_tag; + + /* Opening element. */ + else + { + phase2_ungetc (c); + lexical_context = lc_xml_open_tag; + xml_element_depth++; + } + + tp->type = last_token_type = token_type_other; + } + else + tp->type = last_token_type = token_type_operator; + } + return; + + case '>': + if (xml_element_depth > 0 && !inside_embedded_js_in_xml) + { + switch (lexical_context) + { + case lc_xml_open_tag: + lexical_context = lc_xml_content; + break; + + case lc_xml_close_tag: + if (xml_element_depth-- > 0) + lexical_context = lc_xml_content; + else + lexical_context = lc_outside; + break; + + default: + break; + } + tp->type = last_token_type = token_type_other; + } + else + tp->type = last_token_type = token_type_operator; + return; + + case '/': + if (xml_element_depth > 0 && !inside_embedded_js_in_xml) + { + /* If it appears in an opening tag of an XML element, it's + part of '/>'. */ + if (lexical_context == lc_xml_open_tag) + { + c = phase2_getc (); + if (c == '>') + lexical_context = lc_outside; + else + phase2_ungetc (c); + } + tp->type = last_token_type = token_type_other; + return; + } + + /* Either a division operator or the start of a regular + expression literal. If the '/' token is spotted after a + symbol it's a division, otherwise it's a regular + expression. */ + if (last_token_type == token_type_symbol + || last_token_type == token_type_rparen + || last_token_type == token_type_rbracket) + tp->type = last_token_type = token_type_operator; + else + { + phase5_scan_regexp (tp); + tp->type = last_token_type = token_type_regexp; + } + return; + + case '{': + if (xml_element_depth > 0 && !inside_embedded_js_in_xml) + inside_embedded_js_in_xml = true; + tp->type = last_token_type = token_type_other; + return; + + case '}': + if (xml_element_depth > 0 && inside_embedded_js_in_xml) + inside_embedded_js_in_xml = false; + tp->type = last_token_type = token_type_other; + return; + + case '(': + tp->type = last_token_type = token_type_lparen; + return; + + case ')': + tp->type = last_token_type = token_type_rparen; + return; + + case ',': + tp->type = last_token_type = token_type_comma; + return; + + case '[': + tp->type = last_token_type = token_type_lbracket; + return; + + case ']': + tp->type = last_token_type = token_type_rbracket; + return; + + default: + /* We could carefully recognize each of the 2 and 3 character + operators, but it is not necessary, as we only need to recognize + gettext invocations. Don't bother. */ + tp->type = last_token_type = token_type_other; + return; + } + } +} + +/* Supports only one pushback token. */ +static void +phase5_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase5_pushback_length == SIZEOF (phase5_pushback)) + abort (); + phase5_pushback[phase5_pushback_length++] = *tp; + } +} + + +/* String concatenation with '+'. */ + +static void +x_javascript_lex (token_ty *tp) +{ + phase5_get (tp); + if (tp->type == token_type_string) + { + char *sum = tp->string; + size_t sum_len = strlen (sum); + + for (;;) + { + token_ty token2; + + phase5_get (&token2); + if (token2.type == token_type_plus) + { + token_ty token3; + + phase5_get (&token3); + if (token3.type == token_type_string) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = (char *) xrealloc (sum, sum_len + addend_len + 1); + memcpy (sum + sum_len, addend, addend_len + 1); + sum_len += addend_len; + + free_token (&token3); + free_token (&token2); + continue; + } + phase5_unget (&token3); + } + phase5_unget (&token2); + break; + } + tp->string = sum; + } +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid JavaScript program, and leave the complaints about + the grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + + +/* Extract messages until the next balanced closing parenthesis or bracket. + Extracted messages are added to MLP. + DELIM can be either token_type_rparen or token_type_rbracket, or + token_type_eof to accept both. + Return true upon eof, false upon closing parenthesis or bracket. */ +static bool +extract_balanced (message_list_ty *mlp, + token_type_ty delim, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_javascript_lex (&token); + switch (token.type) + { + case token_type_symbol: + { + void *keyword_value; + + if (hash_find_entry (&keywords, token.string, strlen (token.string), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + } + else + state = 0; + } + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, strlen (token.string))); + free (token.string); + continue; + + case token_type_lparen: + if (extract_balanced (mlp, token_type_rparen, + inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rparen: + if (delim == token_type_rparen || delim == token_type_eof) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return false; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case token_type_lbracket: + if (extract_balanced (mlp, token_type_rbracket, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rbracket: + if (delim == token_type_rbracket || delim == token_type_eof) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return false; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_string: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + xgettext_current_source_encoding = po_charset_utf8; + if (extract_all) + remember_a_message (mlp, NULL, token.string, inner_context, + &pos, NULL, token.comment); + else + arglist_parser_remember (argparser, arg, token.string, + inner_context, + pos.file_name, pos.line_number, + token.comment); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + } + drop_reference (token.comment); + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_eof: + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return true; + + case token_type_keyword: + case token_type_plus: + case token_type_regexp: + case token_type_operator: + case token_type_equal: + case token_type_other: + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + default: + abort (); + } + } +} + + +void +extract_javascript (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + lexical_context = lc_outside; + + last_comment_line = -1; + last_non_comment_line = -1; + + xml_element_depth = 0; + + xgettext_current_file_source_encoding = xgettext_global_source_encoding; +#if HAVE_ICONV + xgettext_current_file_source_iconv = xgettext_global_source_iconv; +#endif + + xgettext_current_source_encoding = xgettext_current_file_source_encoding; +#if HAVE_ICONV + xgettext_current_source_iconv = xgettext_current_file_source_iconv; +#endif + + continuation_or_nonblank_line = false; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When extract_balanced returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_balanced (mlp, token_type_eof, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-javascript.h b/gettext-tools/src/x-javascript.h new file mode 100644 index 0000000..a2f6418 --- /dev/null +++ b/gettext-tools/src/x-javascript.h @@ -0,0 +1,52 @@ +/* xgettext JavaScript backend. + Copyright (C) 2002-2003, 2006, 2013 Free Software Foundation, Inc. + This file was written by Andreas Stricker , 2010. + It's based on x-python from Bruno Haible. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_JAVASCRIPT \ + { "js", "JavaScript" }, \ + +#define SCANNERS_JAVASCRIPT \ + { "JavaScript", extract_javascript, \ + &flag_table_javascript, &formatstring_javascript, NULL, NULL }, \ + +/* Scan a Python file and add its translatable strings to mdlp. */ +extern void extract_javascript (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_javascript_keyword (const char *keyword); +extern void x_javascript_extract_all (void); + +extern void init_flag_table_javascript (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-librep.c b/gettext-tools/src/x-librep.c new file mode 100644 index 0000000..c91d22a --- /dev/null +++ b/gettext-tools/src/x-librep.c @@ -0,0 +1,1133 @@ +/* xgettext librep backend. + Copyright (C) 2001-2003, 2005-2009 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-librep.h" + +#include +#include +#include +#include +#include + +#include "c-ctype.h" +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "hash.h" +#include "gettext.h" + +#define _(s) gettext(s) + + +/* Summary of librep syntax: + - ';' starts a comment until end of line. + - Block comments start with '#|' and end with '|#'. + - Numbers are constituted of an optional prefix (#b, #B for binary, + #o, #O for octal, #d, #D for decimal, #x, #X for hexadecimal, + #e, #E for exact, #i, #I for inexact), an optional sign (+ or -), and + the digits. + - Characters are written as '?' followed by the character, possibly + with an escape sequence, for examples '?a', '?\n', '?\177'. + - Strings are delimited by double quotes. Backslash introduces an escape + sequence. The following are understood: '\n', '\r', '\f', '\t', '\a', + '\\', '\^C', '\012' (octal), '\x12' (hexadecimal). + - Symbols: can contain meta-characters - whitespace or any from ()[]'";|\' - + if preceded by backslash or enclosed in |...|. + - Keywords: written as #:SYMBOL. + - () delimit lists. + - [] delimit vectors. + The reader is implemented in librep-0.14/src/lisp.c. */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_librep_extract_all () +{ + extract_all = true; +} + + +void +x_librep_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid Lisp + symbol. */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_librep_keyword ("_"); + default_keywords = false; + } +} + +void +init_flag_table_librep () +{ + xgettext_record_flag ("_:1:pass-librep-format"); + xgettext_record_flag ("format:2:librep-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* Fetch the next character from the input file. */ +static int +do_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_file_name); + } + else if (c == '\n') + line_number++; + + return c; +} + +/* Put back the last fetched character, not EOF. */ +static void +do_ungetc (int c) +{ + if (c == '\n') + line_number--; + ungetc (c, fp); +} + + +/* ========================== Reading of tokens. ========================== */ + + +/* A token consists of a sequence of characters. */ +struct token +{ + int allocated; /* number of allocated 'token_char's */ + int charcount; /* number of used 'token_char's */ + char *chars; /* the token's constituents */ +}; + +/* Initialize a 'struct token'. */ +static inline void +init_token (struct token *tp) +{ + tp->allocated = 10; + tp->chars = XNMALLOC (tp->allocated, char); + tp->charcount = 0; +} + +/* Free the memory pointed to by a 'struct token'. */ +static inline void +free_token (struct token *tp) +{ + free (tp->chars); +} + +/* Ensure there is enough room in the token for one more character. */ +static inline void +grow_token (struct token *tp) +{ + if (tp->charcount == tp->allocated) + { + tp->allocated *= 2; + tp->chars = (char *) xrealloc (tp->chars, tp->allocated * sizeof (char)); + } +} + +/* Read the next token. If 'first' is given, it points to the first + character, which has already been read. Returns true for a symbol, + false for a number. */ +static bool +read_token (struct token *tp, const int *first) +{ + int c; + /* Variables for speculative number parsing: */ + int radix = -1; + int nfirst = 0; + bool exact = true; + bool rational = false; + bool exponent = false; + bool had_sign = false; + bool expecting_prefix = false; + + init_token (tp); + + if (first) + c = *first; + else + c = do_getc (); + + for (;; c = do_getc ()) + { + switch (c) + { + case EOF: + goto done; + + case ' ': case '\t': case '\n': case '\f': case '\r': + case '(': case ')': case '[': case ']': + case '\'': case '"': case ';': case ',': case '`': + goto done; + + case '\\': + radix = 0; + c = do_getc (); + if (c == EOF) + /* Invalid, but be tolerant. */ + break; + grow_token (tp); + tp->chars[tp->charcount++] = c; + break; + + case '|': + radix = 0; + for (;;) + { + c = do_getc (); + if (c == EOF || c == '|') + break; + grow_token (tp); + tp->chars[tp->charcount++] = c; + } + break; + + default: + if (radix != 0) + { + if (expecting_prefix) + { + switch (c) + { + case 'B': case 'b': + radix = 2; + break; + case 'O': case 'o': + radix = 8; + break; + case 'D': case 'd': + radix = 10; + break; + case 'X': case 'x': + radix = 16; + break; + case 'E': case 'e': + case 'I': case 'i': + break; + default: + radix = 0; + break; + } + expecting_prefix = false; + nfirst = tp->charcount + 1; + } + else if (tp->charcount == nfirst + && (c == '+' || c == '-' || c == '#')) + { + if (c == '#') + { + if (had_sign) + radix = 0; + else + expecting_prefix = true; + } + else + had_sign = true; + nfirst = tp->charcount + 1; + } + else + { + switch (radix) + { + case -1: + if (c == '.') + { + radix = 10; + exact = false; + } + else if (!(c >= '0' && c <= '9')) + radix = 0; + else if (c == '0') + radix = 1; + else + radix = 10; + break; + + case 1: + switch (c) + { + case 'X': case 'x': + radix = 16; + nfirst = tp->charcount + 1; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + radix = 8; + nfirst = tp->charcount; + break; + case '.': case 'E': case 'e': + radix = 10; + exact = false; + break; + case '/': + radix = 10; + rational = true; + break; + default: + radix = 0; + break; + } + break; + + default: + switch (c) + { + case '.': + if (exact && radix == 10 && !rational) + exact = false; + else + radix = 0; + break; + case '/': + if (exact && !rational) + rational = true; + else + radix = 0; + break; + case 'E': case 'e': + if (radix == 10) + { + if (!rational && !exponent) + { + exponent = true; + exact = false; + } + else + radix = 0; + break; + } + /*FALLTHROUGH*/ + default: + if (exponent && (c == '+' || c == '-')) + break; + if ((radix <= 10 + && !(c >= '0' && c <= '0' + radix - 1)) + || (radix == 16 && !c_isxdigit (c))) + radix = 0; + break; + } + break; + } + } + } + else + { + if (c == '#') + goto done; + } + grow_token (tp); + tp->chars[tp->charcount++] = c; + } + } + done: + if (c != EOF) + do_ungetc (c); + if (radix > 0 && nfirst < tp->charcount) + return false; /* number */ + else + return true; /* symbol */ +} + + +/* ========================= Accumulating comments ========================= */ + + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (chars_to_remove == 0 && buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ========================= Accumulating messages ========================= */ + + +static message_list_ty *mlp; + + +/* ============== Reading of objects. See CLHS 2 "Syntax". ============== */ + + +/* We are only interested in symbols (e.g. GETTEXT or NGETTEXT) and strings. + Other objects need not to be represented precisely. */ +enum object_type +{ + t_symbol, /* symbol */ + t_string, /* string */ + t_other, /* other kind of real object */ + t_dot, /* '.' pseudo object */ + t_close, /* ')' or ']' pseudo object */ + t_eof /* EOF marker */ +}; + +struct object +{ + enum object_type type; + struct token *token; /* for t_symbol and t_string */ + int line_number_at_start; /* for t_string */ +}; + +/* Free the memory pointed to by a 'struct object'. */ +static inline void +free_object (struct object *op) +{ + if (op->type == t_symbol || op->type == t_string) + { + free_token (op->token); + free (op->token); + } +} + +/* Convert a t_symbol/t_string token to a char*. */ +static char * +string_of_object (const struct object *op) +{ + char *str; + int n; + + if (!(op->type == t_symbol || op->type == t_string)) + abort (); + n = op->token->charcount; + str = XNMALLOC (n + 1, char); + memcpy (str, op->token->chars, n); + str[n] = '\0'; + return str; +} + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + +/* Returns the character represented by an escape sequence. */ +static int +do_getc_escaped (int c) +{ + switch (c) + { + case 'n': + return '\n'; + case 'r': + return '\r'; + case 'f': + return '\f'; + case 't': + return '\t'; + case 'v': + return '\v'; + case 'a': + return '\a'; + case '^': + c = do_getc (); + if (c == EOF) + return EOF; + return c & 0x1f; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + { + int n = c - '0'; + + c = do_getc (); + if (c != EOF) + { + if (c >= '0' && c <= '7') + { + n = (n << 3) + (c - '0'); + c = do_getc (); + if (c != EOF) + { + if (c >= '0' && c <= '7') + n = (n << 3) + (c - '0'); + else + do_ungetc (c); + } + } + else + do_ungetc (c); + } + return (unsigned char) n; + } + case 'x': + { + int n = 0; + + for (;;) + { + c = do_getc (); + if (c == EOF) + break; + else if (c >= '0' && c <= '9') + n = (n << 4) + (c - '0'); + else if (c >= 'A' && c <= 'F') + n = (n << 4) + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + n = (n << 4) + (c - 'a' + 10); + else + { + do_ungetc (c); + break; + } + } + return (unsigned char) n; + } + default: + return c; + } +} + +/* Read the next object. */ +static void +read_object (struct object *op, flag_context_ty outer_context) +{ + for (;;) + { + int c; + + c = do_getc (); + + switch (c) + { + case EOF: + op->type = t_eof; + return; + + case '\n': + /* Comments assumed to be grouped with a message must immediately + precede it, with no non-whitespace token on a line between + both. */ + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + continue; + + case ' ': case '\t': case '\f': case '\r': + continue; + + case '(': + { + int arg = 0; /* Current argument number. */ + flag_context_list_iterator_ty context_iter; + const struct callshapes *shapes = NULL; + struct arglist_parser *argparser = NULL; + + for (;; arg++) + { + struct object inner; + flag_context_ty inner_context; + + if (arg == 0) + inner_context = null_context; + else + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + + read_object (&inner, inner_context); + + /* Recognize end of list. */ + if (inner.type == t_close) + { + op->type = t_other; + /* Don't bother converting "()" to "NIL". */ + last_non_comment_line = line_number; + if (argparser != NULL) + arglist_parser_done (argparser, arg); + return; + } + + /* Dots are not allowed in every position. + But be tolerant. */ + + /* EOF inside list is illegal. But be tolerant. */ + if (inner.type == t_eof) + break; + + if (arg == 0) + { + /* This is the function position. */ + if (inner.type == t_symbol) + { + char *symbol_name = string_of_object (&inner); + void *keyword_value; + + if (hash_find_entry (&keywords, + symbol_name, strlen (symbol_name), + &keyword_value) + == 0) + shapes = (const struct callshapes *) keyword_value; + + argparser = arglist_parser_alloc (mlp, shapes); + + context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + symbol_name, strlen (symbol_name))); + + free (symbol_name); + } + else + context_iter = null_context_list_iterator; + } + else + { + /* These are the argument positions. */ + if (argparser != NULL && inner.type == t_string) + arglist_parser_remember (argparser, arg, + string_of_object (&inner), + inner_context, + logical_file_name, + inner.line_number_at_start, + savable_comment); + } + + free_object (&inner); + } + + if (argparser != NULL) + arglist_parser_done (argparser, arg); + } + op->type = t_other; + last_non_comment_line = line_number; + return; + + case '[': + { + for (;;) + { + struct object inner; + + read_object (&inner, null_context); + + /* Recognize end of vector. */ + if (inner.type == t_close) + { + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + /* Dots are not allowed. But be tolerant. */ + + /* EOF inside vector is illegal. But be tolerant. */ + if (inner.type == t_eof) + break; + + free_object (&inner); + } + } + op->type = t_other; + last_non_comment_line = line_number; + return; + + case ')': case ']': + /* Tell the caller about the end of list or vector. + Unmatched closing parenthesis is illegal. But be tolerant. */ + op->type = t_close; + last_non_comment_line = line_number; + return; + + case ',': + { + int c = do_getc (); + /* The ,@ handling inside lists is wrong anyway, because + ,@form expands to an unknown number of elements. */ + if (c != EOF && c != '@') + do_ungetc (c); + } + /*FALLTHROUGH*/ + case '\'': + case '`': + { + struct object inner; + + read_object (&inner, null_context); + + /* Dots and EOF are not allowed here. But be tolerant. */ + + free_object (&inner); + + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case ';': + { + bool all_semicolons = true; + + last_comment_line = line_number; + comment_start (); + for (;;) + { + int c = do_getc (); + if (c == EOF || c == '\n' || c == '\f' || c == '\r') + break; + if (c != ';') + all_semicolons = false; + if (!all_semicolons) + { + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + } + comment_line_end (0); + continue; + } + + case '"': + { + op->token = XMALLOC (struct token); + init_token (op->token); + op->line_number_at_start = line_number; + for (;;) + { + int c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + if (c == '"') + break; + if (c == '\\') + { + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + if (c == '\n') + /* Ignore escaped newline. */ + ; + else + { + c = do_getc_escaped (c); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + grow_token (op->token); + op->token->chars[op->token->charcount++] = c; + } + } + else + { + grow_token (op->token); + op->token->chars[op->token->charcount++] = c; + } + } + op->type = t_string; + + if (extract_all) + { + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = op->line_number_at_start; + remember_a_message (mlp, NULL, string_of_object (op), + null_context, &pos, NULL, savable_comment); + } + last_non_comment_line = line_number; + return; + } + + case '?': + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + ; + else if (c == '\\') + { + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + ; + else + { + c = do_getc_escaped (c); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + ; + } + } + op->type = t_other; + last_non_comment_line = line_number; + return; + + case '#': + /* Dispatch macro handling. */ + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + { + op->type = t_other; + return; + } + + switch (c) + { + case '!': + if (ftell (fp) == 2) + /* Skip comment until !# */ + { + c = do_getc (); + for (;;) + { + if (c == EOF) + break; + if (c == '!') + { + c = do_getc (); + if (c == EOF || c == '#') + break; + } + else + c = do_getc (); + } + if (c == EOF) + { + /* EOF not allowed here. But be tolerant. */ + op->type = t_eof; + return; + } + continue; + } + /*FALLTHROUGH*/ + case '\'': + case ':': + { + struct object inner; + read_object (&inner, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '[': + case '(': + { + struct object inner; + do_ungetc (c); + read_object (&inner, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '|': + { + int depth = 0; + + comment_start (); + c = do_getc (); + for (;;) + { + if (c == EOF) + break; + if (c == '|') + { + c = do_getc (); + if (c == EOF) + break; + if (c == '#') + { + if (depth == 0) + { + comment_line_end (0); + break; + } + depth--; + comment_add ('|'); + comment_add ('#'); + c = do_getc (); + } + else + comment_add ('|'); + } + else if (c == '#') + { + c = do_getc (); + if (c == EOF) + break; + comment_add ('#'); + if (c == '|') + { + depth++; + comment_add ('|'); + c = do_getc (); + } + } + else + { + /* We skip all leading white space. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + if (c == '\n') + { + comment_line_end (1); + comment_start (); + } + c = do_getc (); + } + } + if (c == EOF) + { + /* EOF not allowed here. But be tolerant. */ + op->type = t_eof; + return; + } + last_comment_line = line_number; + continue; + } + + case '\\': + { + struct token token; + int first = '\\'; + read_token (&token, &first); + free_token (&token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case 'T': case 't': + case 'F': case 'f': + op->type = t_other; + last_non_comment_line = line_number; + return; + + case 'B': case 'b': + case 'O': case 'o': + case 'D': case 'd': + case 'X': case 'x': + case 'E': case 'e': + case 'I': case 'i': + { + struct token token; + do_ungetc (c); + c = '#'; + read_token (&token, &c); + free_token (&token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + default: + /* Invalid input. Be tolerant, no error message. */ + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + /*NOTREACHED*/ + abort (); + + default: + /* Read a token. */ + { + bool symbol; + + op->token = XMALLOC (struct token); + symbol = read_token (op->token, &c); + if (op->token->charcount == 1 && op->token->chars[0] == '.') + { + free_token (op->token); + free (op->token); + op->type = t_dot; + last_non_comment_line = line_number; + return; + } + if (!symbol) + { + free_token (op->token); + free (op->token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + /* Distinguish between "foo" and "foo#bar". */ + c = do_getc (); + if (c == '#') + { + struct token second_token; + + free_token (op->token); + free (op->token); + read_token (&second_token, NULL); + free_token (&second_token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + else + { + if (c != EOF) + do_ungetc (c); + op->type = t_symbol; + last_non_comment_line = line_number; + return; + } + } + } + } +} + + +void +extract_librep (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When read_object returns + due to an unbalanced closing parenthesis, just restart it. */ + do + { + struct object toplevel_object; + + read_object (&toplevel_object, null_context); + + if (toplevel_object.type == t_eof) + break; + + free_object (&toplevel_object); + } + while (!feof (fp)); + + /* Close scanner. */ + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-librep.h b/gettext-tools/src/x-librep.h new file mode 100644 index 0000000..5747db1 --- /dev/null +++ b/gettext-tools/src/x-librep.h @@ -0,0 +1,54 @@ +/* xgettext librep backend. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_LIBREP \ + { "jl", "librep" }, \ + +#define SCANNERS_LIBREP \ + { "librep", extract_librep, \ + &flag_table_librep, &formatstring_librep, NULL, NULL }, \ + +/* Scan a librep file and add its translatable strings to mdlp. */ +extern void extract_librep (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +/* Handling of options specific to this language. */ + +extern void x_librep_extract_all (void); +extern void x_librep_keyword (const char *name); + +extern void init_flag_table_librep (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-lisp.c b/gettext-tools/src/x-lisp.c new file mode 100644 index 0000000..20d88a2 --- /dev/null +++ b/gettext-tools/src/x-lisp.c @@ -0,0 +1,1425 @@ +/* xgettext Lisp backend. + Copyright (C) 2001-2003, 2005-2009 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-lisp.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "hash.h" +#include "gettext.h" + +#define _(s) gettext(s) + + +/* The Common Lisp syntax is described in the Common Lisp HyperSpec, chapter 2. + Since we are interested only in strings and in forms similar to + (gettext msgid ...) + or (ngettext msgid msgid_plural ...) + we make the following simplifications: + + - Assume the keywords and strings are in an ASCII compatible encoding. + This means we can read the input file one byte at a time, instead of + one character at a time. No need to worry about multibyte characters: + If they occur as part of identifiers, they most probably act as + constituent characters, and the byte based approach will do the same. + + - Assume the read table is the standard Common Lisp read table. + Non-standard read tables are mostly used to read data, not programs. + + - Assume the read table case is :UPCASE, and *READ-BASE* is 10. + + - Don't interpret #n= and #n#, they usually don't appear in programs. + + - Don't interpret #+, #-, they are unlikely to appear in a gettext form. + + The remaining syntax rules are: + + - The syntax code assigned to each character, and how tokens are built + up from characters (single escape, multiple escape etc.). + + - Comment syntax: ';' and '#| ... |#'. + + - String syntax: "..." with single escapes. + + - Read macros and dispatch macro character '#'. Needed to be able to + tell which is the n-th argument of a function call. + + */ + + +/* ========================= Lexer customization. ========================= */ + +/* 'readtable_case' is the case conversion that is applied to non-escaped + parts of symbol tokens. In Common Lisp: (readtable-case *readtable*). */ + +enum rtcase +{ + case_upcase, + case_downcase, + case_preserve, + case_invert +}; + +static enum rtcase readtable_case = case_upcase; + +/* 'read_base' is the assumed radix of integers and rational numbers. + In Common Lisp: *read-base*. */ +static int read_base = 10; + +/* 'read_preserve_whitespace' specifies whether a whitespace character + that terminates a token must be pushed back on the input stream. + We set it to true, because the special newline side effect in read_object() + requires that read_object() sees every newline not inside a token. */ +static bool read_preserve_whitespace = true; + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_lisp_extract_all () +{ + extract_all = true; +} + + +void +x_lisp_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + size_t len; + char *symname; + size_t i; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid Lisp symbol. + Extract the symbol name part. */ + colon = strchr (name, ':'); + if (colon != NULL && colon < end) + { + name = colon + 1; + if (name < end && *name == ':') + name++; + colon = strchr (name, ':'); + if (colon != NULL && colon < end) + return; + } + + /* Uppercase it. */ + len = end - name; + symname = XNMALLOC (len, char); + for (i = 0; i < len; i++) + symname[i] = + (name[i] >= 'a' && name[i] <= 'z' ? name[i] - 'a' + 'A' : name[i]); + + insert_keyword_callshape (&keywords, symname, len, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_lisp_keyword ("gettext"); /* I18N:GETTEXT */ + x_lisp_keyword ("ngettext:1,2"); /* I18N:NGETTEXT */ + x_lisp_keyword ("gettext-noop"); + default_keywords = false; + } +} + +void +init_flag_table_lisp () +{ + xgettext_record_flag ("gettext:1:pass-lisp-format"); + xgettext_record_flag ("ngettext:1:pass-lisp-format"); + xgettext_record_flag ("ngettext:2:pass-lisp-format"); + xgettext_record_flag ("gettext-noop:1:pass-lisp-format"); + xgettext_record_flag ("format:2:lisp-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* Fetch the next character from the input file. */ +static int +do_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_file_name); + } + else if (c == '\n') + line_number++; + + return c; +} + +/* Put back the last fetched character, not EOF. */ +static void +do_ungetc (int c) +{ + if (c == '\n') + line_number--; + ungetc (c, fp); +} + + +/* ========= Reading of tokens. See CLHS 2.2 "Reader Algorithm". ========= */ + + +/* Syntax code. See CLHS 2.1.4 "Character Syntax Types". */ + +enum syntax_code +{ + syntax_illegal, /* non-printable, except whitespace */ + syntax_single_esc, /* '\' (single escape) */ + syntax_multi_esc, /* '|' (multiple escape) */ + syntax_constituent, /* everything else (constituent) */ + syntax_whitespace, /* TAB,LF,FF,CR,' ' (whitespace) */ + syntax_eof, /* EOF */ + syntax_t_macro, /* '()'"' (terminating macro) */ + syntax_nt_macro /* '#' (non-terminating macro) */ +}; + +/* Returns the syntax code of a character. */ +static enum syntax_code +syntax_code_of (unsigned char c) +{ + switch (c) + { + case '\\': + return syntax_single_esc; + case '|': + return syntax_multi_esc; + case '\t': case '\n': case '\f': case '\r': case ' ': + return syntax_whitespace; + case '(': case ')': case '\'': case '"': case ',': case ';': case '`': + return syntax_t_macro; + case '#': + return syntax_nt_macro; + default: + if (c < ' ' && c != '\b') + return syntax_illegal; + else + return syntax_constituent; + } +} + +struct char_syntax +{ + int ch; /* character */ + enum syntax_code scode; /* syntax code */ +}; + +/* Returns the next character and its syntax code. */ +static void +read_char_syntax (struct char_syntax *p) +{ + int c = do_getc (); + + p->ch = c; + p->scode = (c == EOF ? syntax_eof : syntax_code_of (c)); +} + +/* Every character in a token has an attribute assigned. The attributes + help during interpretation of the token. See + CLHS 2.3 "Interpretation of Tokens" for the possible interpretations, + and CLHS 2.1.4.2 "Constituent Traits". */ + +enum attribute +{ + a_illg, /* invalid constituent */ + a_pack_m, /* ':' package marker */ + a_alpha, /* normal alphabetic */ + a_escaped, /* alphabetic but not subject to case conversion */ + a_ratio, /* '/' */ + a_dot, /* '.' */ + a_sign, /* '+-' */ + a_extens, /* '_^' extension characters */ + a_digit, /* '0123456789' */ + a_letterdigit,/* 'A'-'Z','a'-'z' below base, except 'esfdlESFDL' */ + a_expodigit, /* 'esfdlESFDL' below base */ + a_letter, /* 'A'-'Z','a'-'z', except 'esfdlESFDL' */ + a_expo /* 'esfdlESFDL' */ +}; + +#define is_letter_attribute(a) ((a) >= a_letter) +#define is_number_attribute(a) ((a) >= a_ratio) + +/* Returns the attribute of a character, assuming base 10. */ +static enum attribute +attribute_of (unsigned char c) +{ + switch (c) + { + case ':': + return a_pack_m; + case '/': + return a_ratio; + case '.': + return a_dot; + case '+': case '-': + return a_sign; + case '_': case '^': + return a_extens; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return a_digit; + case 'a': case 'b': case 'c': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + return a_letter; + case 'e': case 's': case 'd': case 'f': case 'l': + case 'E': case 'S': case 'D': case 'F': case 'L': + return a_expo; + default: + /* Treat everything as valid. Never return a_illg. */ + return a_alpha; + } +} + +struct token_char +{ + unsigned char ch; /* character */ + unsigned char attribute; /* attribute */ +}; + +/* A token consists of a sequence of characters with associated attribute. */ +struct token +{ + int allocated; /* number of allocated 'token_char's */ + int charcount; /* number of used 'token_char's */ + struct token_char *chars; /* the token's constituents */ + bool with_escape; /* whether single-escape or multiple escape occurs */ +}; + +/* Initialize a 'struct token'. */ +static inline void +init_token (struct token *tp) +{ + tp->allocated = 10; + tp->chars = XNMALLOC (tp->allocated, struct token_char); + tp->charcount = 0; +} + +/* Free the memory pointed to by a 'struct token'. */ +static inline void +free_token (struct token *tp) +{ + free (tp->chars); +} + +/* Ensure there is enough room in the token for one more character. */ +static inline void +grow_token (struct token *tp) +{ + if (tp->charcount == tp->allocated) + { + tp->allocated *= 2; + tp->chars = (struct token_char *) xrealloc (tp->chars, tp->allocated * sizeof (struct token_char)); + } +} + +/* Read the next token. If 'first' is given, it points to the first + character, which has already been read. + The algorithm follows CLHS 2.2 "Reader Algorithm". */ +static void +read_token (struct token *tp, const struct char_syntax *first) +{ + bool multiple_escape_flag; + struct char_syntax curr; + + init_token (tp); + tp->with_escape = false; + + multiple_escape_flag = false; + if (first) + curr = *first; + else + read_char_syntax (&curr); + + for (;; read_char_syntax (&curr)) + { + switch (curr.scode) + { + case syntax_illegal: + /* Invalid input. Be tolerant, no error message. */ + do_ungetc (curr.ch); + return; + + case syntax_single_esc: + tp->with_escape = true; + read_char_syntax (&curr); + if (curr.scode == syntax_eof) + /* Invalid input. Be tolerant, no error message. */ + return; + grow_token (tp); + tp->chars[tp->charcount].ch = curr.ch; + tp->chars[tp->charcount].attribute = a_escaped; + tp->charcount++; + break; + + case syntax_multi_esc: + multiple_escape_flag = !multiple_escape_flag; + tp->with_escape = true; + break; + + case syntax_constituent: + case syntax_nt_macro: + grow_token (tp); + if (multiple_escape_flag) + { + tp->chars[tp->charcount].ch = curr.ch; + tp->chars[tp->charcount].attribute = a_escaped; + tp->charcount++; + } + else + { + tp->chars[tp->charcount].ch = curr.ch; + tp->chars[tp->charcount].attribute = attribute_of (curr.ch); + tp->charcount++; + } + break; + + case syntax_whitespace: + case syntax_t_macro: + if (multiple_escape_flag) + { + grow_token (tp); + tp->chars[tp->charcount].ch = curr.ch; + tp->chars[tp->charcount].attribute = a_escaped; + tp->charcount++; + } + else + { + if (curr.scode != syntax_whitespace || read_preserve_whitespace) + do_ungetc (curr.ch); + return; + } + break; + + case syntax_eof: + if (multiple_escape_flag) + /* Invalid input. Be tolerant, no error message. */ + ; + return; + } + } +} + +/* A potential number is a token which + 1. consists only of digits, '+','-','/','^','_','.' and number markers. + The base for digits is context dependent, but always 10 if a dot '.' + occurs. A number marker is a non-digit letter which is not adjacent + to a non-digit letter. + 2. has at least one digit. + 3. starts with a digit, '+','-','.','^' or '_'. + 4. does not end with '+' or '-'. + See CLHS 2.3.1.1 "Potential Numbers as Tokens". + */ + +static inline bool +has_a_dot (const struct token *tp) +{ + int n = tp->charcount; + int i; + + for (i = 0; i < n; i++) + if (tp->chars[i].attribute == a_dot) + return true; + return false; +} + +static inline bool +all_a_number (const struct token *tp) +{ + int n = tp->charcount; + int i; + + for (i = 0; i < n; i++) + if (!is_number_attribute (tp->chars[i].attribute)) + return false; + return true; +} + +static inline void +a_letter_to_digit (const struct token *tp, int base) +{ + int n = tp->charcount; + int i; + + for (i = 0; i < n; i++) + if (is_letter_attribute (tp->chars[i].attribute)) + { + int c = tp->chars[i].ch; + + if (c >= 'a') + c -= 'a' - 'A'; + if (c - 'A' + 10 < base) + tp->chars[i].attribute -= 2; /* a_letter -> a_letterdigit, + a_expo -> a_expodigit */ + } +} + +static inline bool +has_a_digit (const struct token *tp) +{ + int n = tp->charcount; + int i; + + for (i = 0; i < n; i++) + if (tp->chars[i].attribute == a_digit + || tp->chars[i].attribute == a_letterdigit + || tp->chars[i].attribute == a_expodigit) + return true; + return false; +} + +static inline bool +has_adjacent_letters (const struct token *tp) +{ + int n = tp->charcount; + int i; + + for (i = 1; i < n; i++) + if (is_letter_attribute (tp->chars[i-1].attribute) + && is_letter_attribute (tp->chars[i].attribute)) + return true; + return false; +} + +static bool +is_potential_number (const struct token *tp, int *basep) +{ + /* CLHS 2.3.1.1.1: + "A potential number cannot contain any escape characters." */ + if (tp->with_escape) + return false; + + if (has_a_dot (tp)) + *basep = 10; + + if (!all_a_number (tp)) + return false; + + a_letter_to_digit (tp, *basep); + + if (!has_a_digit (tp)) + return false; + + if (has_adjacent_letters (tp)) + return false; + + if (!(tp->chars[0].attribute >= a_dot + && tp->chars[0].attribute <= a_expodigit)) + return false; + + if (tp->chars[tp->charcount - 1].attribute == a_sign) + return false; + + return true; +} + +/* A number is one of integer, ratio, float. Each has a particular syntax. + See CLHS 2.3.1 "Numbers as Tokens". + But note a mistake: The exponent rule should read: + exponent ::= exponent-marker [sign] {decimal-digit}+ + (see 22.1.3.1.3 "Printing Floats"). */ + +enum number_type +{ + n_none, + n_integer, + n_ratio, + n_float +}; + +static enum number_type +is_number (const struct token *tp, int *basep) +{ + struct token_char *ptr_limit; + struct token_char *ptr1; + + if (!is_potential_number (tp, basep)) + return n_none; + + /* is_potential_number guarantees + - all attributes are >= a_ratio, + - there is at least one a_digit or a_letterdigit or a_expodigit, and + - if there is an a_dot, then *basep = 10. */ + + ptr1 = &tp->chars[0]; + ptr_limit = &tp->chars[tp->charcount]; + + if (ptr1->attribute == a_sign) + ptr1++; + + /* Test for syntax + * { a_sign | } + * { a_digit < base }+ { a_ratio { a_digit < base }+ | } + */ + { + bool seen_a_ratio = false; + bool seen_a_digit = false; /* seen a digit in last digit block? */ + struct token_char *ptr; + + for (ptr = ptr1;; ptr++) + { + if (ptr >= ptr_limit) + { + if (!seen_a_digit) + break; + if (seen_a_ratio) + return n_ratio; + else + return n_integer; + } + if (ptr->attribute == a_digit + || ptr->attribute == a_letterdigit + || ptr->attribute == a_expodigit) + { + int c = ptr->ch; + + c = (c < 'A' ? c - '0' : c < 'a' ? c - 'A' + 10 : c - 'a' + 10); + if (c >= *basep) + break; + seen_a_digit = true; + } + else if (ptr->attribute == a_ratio) + { + if (seen_a_ratio || !seen_a_digit) + break; + seen_a_ratio = true; + seen_a_digit = false; + } + else + break; + } + } + + /* Test for syntax + * { a_sign | } + * { a_digit }* { a_dot { a_digit }* | } + * { a_expo { a_sign | } { a_digit }+ | } + * + * If there is an exponent part, there must be digits before the dot or + * after the dot. The result is a float. + * If there is no exponen: + * If there is no dot, it would an integer in base 10, but is has already + * been verified to not be an integer in the current base. + * If there is a dot: + * If there are digits after the dot, it's a float. + * Otherwise, if there are digits before the dot, it's an integer. + */ + *basep = 10; + { + bool seen_a_dot = false; + bool seen_a_dot_with_leading_digits = false; + bool seen_a_digit = false; /* seen a digit in last digit block? */ + struct token_char *ptr; + + for (ptr = ptr1;; ptr++) + { + if (ptr >= ptr_limit) + { + /* no exponent */ + if (!seen_a_dot) + return n_none; + if (seen_a_digit) + return n_float; + if (seen_a_dot_with_leading_digits) + return n_integer; + else + return n_none; + } + if (ptr->attribute == a_digit) + { + seen_a_digit = true; + } + else if (ptr->attribute == a_dot) + { + if (seen_a_dot) + return n_none; + seen_a_dot = true; + if (seen_a_digit) + seen_a_dot_with_leading_digits = true; + seen_a_digit = false; + } + else if (ptr->attribute == a_expo || ptr->attribute == a_expodigit) + break; + else + return n_none; + } + ptr++; + if (!seen_a_dot_with_leading_digits || !seen_a_digit) + return n_none; + if (ptr >= ptr_limit) + return n_none; + if (ptr->attribute == a_sign) + ptr++; + seen_a_digit = false; + for (;; ptr++) + { + if (ptr >= ptr_limit) + break; + if (ptr->attribute != a_digit) + return n_none; + seen_a_digit = true; + } + if (!seen_a_digit) + return n_none; + return n_float; + } +} + +/* A token representing a symbol must be case converted. + For portability, we convert only ASCII characters here. */ + +static void +upcase_token (struct token *tp) +{ + int n = tp->charcount; + int i; + + for (i = 0; i < n; i++) + if (tp->chars[i].attribute != a_escaped) + { + unsigned char c = tp->chars[i].ch; + if (c >= 'a' && c <= 'z') + tp->chars[i].ch = c - 'a' + 'A'; + } +} + +static void +downcase_token (struct token *tp) +{ + int n = tp->charcount; + int i; + + for (i = 0; i < n; i++) + if (tp->chars[i].attribute != a_escaped) + { + unsigned char c = tp->chars[i].ch; + if (c >= 'A' && c <= 'Z') + tp->chars[i].ch = c - 'A' + 'a'; + } +} + +static void +case_convert_token (struct token *tp) +{ + int n = tp->charcount; + int i; + + switch (readtable_case) + { + case case_upcase: + upcase_token (tp); + break; + + case case_downcase: + downcase_token (tp); + break; + + case case_preserve: + break; + + case case_invert: + { + bool seen_uppercase = false; + bool seen_lowercase = false; + for (i = 0; i < n; i++) + if (tp->chars[i].attribute != a_escaped) + { + unsigned char c = tp->chars[i].ch; + if (c >= 'a' && c <= 'z') + seen_lowercase = true; + if (c >= 'A' && c <= 'Z') + seen_uppercase = true; + } + if (seen_uppercase) + { + if (!seen_lowercase) + downcase_token (tp); + } + else + { + if (seen_lowercase) + upcase_token (tp); + } + } + break; + } +} + + +/* ========================= Accumulating comments ========================= */ + + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (chars_to_remove == 0 && buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ========================= Accumulating messages ========================= */ + + +static message_list_ty *mlp; + + +/* ============== Reading of objects. See CLHS 2 "Syntax". ============== */ + + +/* We are only interested in symbols (e.g. GETTEXT or NGETTEXT) and strings. + Other objects need not to be represented precisely. */ +enum object_type +{ + t_symbol, /* symbol */ + t_string, /* string */ + t_other, /* other kind of real object */ + t_dot, /* '.' pseudo object */ + t_close, /* ')' pseudo object */ + t_eof /* EOF marker */ +}; + +struct object +{ + enum object_type type; + struct token *token; /* for t_symbol and t_string */ + int line_number_at_start; /* for t_string */ +}; + +/* Free the memory pointed to by a 'struct object'. */ +static inline void +free_object (struct object *op) +{ + if (op->type == t_symbol || op->type == t_string) + { + free_token (op->token); + free (op->token); + } +} + +/* Convert a t_symbol/t_string token to a char*. */ +static char * +string_of_object (const struct object *op) +{ + char *str; + const struct token_char *p; + char *q; + int n; + + if (!(op->type == t_symbol || op->type == t_string)) + abort (); + n = op->token->charcount; + str = XNMALLOC (n + 1, char); + q = str; + for (p = op->token->chars; n > 0; p++, n--) + *q++ = p->ch; + *q = '\0'; + return str; +} + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + +/* Read the next object. */ +static void +read_object (struct object *op, flag_context_ty outer_context) +{ + for (;;) + { + struct char_syntax curr; + + read_char_syntax (&curr); + + switch (curr.scode) + { + case syntax_eof: + op->type = t_eof; + return; + + case syntax_whitespace: + if (curr.ch == '\n') + /* Comments assumed to be grouped with a message must immediately + precede it, with no non-whitespace token on a line between + both. */ + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + continue; + + case syntax_illegal: + op->type = t_other; + return; + + case syntax_single_esc: + case syntax_multi_esc: + case syntax_constituent: + /* Start reading a token. */ + op->token = XMALLOC (struct token); + read_token (op->token, &curr); + last_non_comment_line = line_number; + + /* Interpret the token. */ + + /* Dots. */ + if (!op->token->with_escape + && op->token->charcount == 1 + && op->token->chars[0].attribute == a_dot) + { + free_token (op->token); + free (op->token); + op->type = t_dot; + return; + } + /* Tokens consisting entirely of dots are illegal, but be tolerant + here. */ + + /* Number. */ + { + int base = read_base; + + if (is_number (op->token, &base) != n_none) + { + free_token (op->token); + free (op->token); + op->type = t_other; + return; + } + } + + /* We interpret all other tokens as symbols (including 'reserved + tokens', i.e. potential numbers which are not numbers). */ + case_convert_token (op->token); + op->type = t_symbol; + return; + + case syntax_t_macro: + case syntax_nt_macro: + /* Read a macro. */ + switch (curr.ch) + { + case '(': + { + int arg = 0; /* Current argument number. */ + flag_context_list_iterator_ty context_iter; + const struct callshapes *shapes = NULL; + struct arglist_parser *argparser = NULL; + + for (;; arg++) + { + struct object inner; + flag_context_ty inner_context; + + if (arg == 0) + inner_context = null_context; + else + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + + read_object (&inner, inner_context); + + /* Recognize end of list. */ + if (inner.type == t_close) + { + op->type = t_other; + /* Don't bother converting "()" to "NIL". */ + last_non_comment_line = line_number; + if (argparser != NULL) + arglist_parser_done (argparser, arg); + return; + } + + /* Dots are not allowed in every position. + But be tolerant. */ + + /* EOF inside list is illegal. + But be tolerant. */ + if (inner.type == t_eof) + break; + + if (arg == 0) + { + /* This is the function position. */ + if (inner.type == t_symbol) + { + char *symbol_name = string_of_object (&inner); + int i; + int prefix_len; + void *keyword_value; + + /* Omit any package name. */ + i = inner.token->charcount; + while (i > 0 + && inner.token->chars[i-1].attribute != a_pack_m) + i--; + prefix_len = i; + + if (hash_find_entry (&keywords, + symbol_name + prefix_len, + strlen (symbol_name + prefix_len), + &keyword_value) + == 0) + shapes = (const struct callshapes *) keyword_value; + + argparser = arglist_parser_alloc (mlp, shapes); + + context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + symbol_name, strlen (symbol_name))); + + free (symbol_name); + } + else + context_iter = null_context_list_iterator; + } + else + { + /* These are the argument positions. */ + if (argparser != NULL && inner.type == t_string) + arglist_parser_remember (argparser, arg, + string_of_object (&inner), + inner_context, + logical_file_name, + inner.line_number_at_start, + savable_comment); + } + + free_object (&inner); + } + + if (argparser != NULL) + arglist_parser_done (argparser, arg); + } + op->type = t_other; + last_non_comment_line = line_number; + return; + + case ')': + /* Tell the caller about the end of list. + Unmatched closing parenthesis is illegal. + But be tolerant. */ + op->type = t_close; + last_non_comment_line = line_number; + return; + + case ',': + { + int c = do_getc (); + /* The ,@ handling inside lists is wrong anyway, because + ,@form expands to an unknown number of elements. */ + if (c != EOF && c != '@' && c != '.') + do_ungetc (c); + } + /*FALLTHROUGH*/ + case '\'': + case '`': + { + struct object inner; + + read_object (&inner, null_context); + + /* Dots and EOF are not allowed here. But be tolerant. */ + + free_object (&inner); + + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case ';': + { + bool all_semicolons = true; + + last_comment_line = line_number; + comment_start (); + for (;;) + { + int c = do_getc (); + if (c == EOF || c == '\n') + break; + if (c != ';') + all_semicolons = false; + if (!all_semicolons) + { + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + } + comment_line_end (0); + continue; + } + + case '"': + { + op->token = XMALLOC (struct token); + init_token (op->token); + op->line_number_at_start = line_number; + for (;;) + { + int c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + if (c == '"') + break; + if (c == '\\') /* syntax_single_esc */ + { + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + } + grow_token (op->token); + op->token->chars[op->token->charcount++].ch = c; + } + op->type = t_string; + + if (extract_all) + { + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = op->line_number_at_start; + remember_a_message (mlp, NULL, string_of_object (op), + null_context, &pos, + NULL, savable_comment); + } + last_non_comment_line = line_number; + return; + } + + case '#': + /* Dispatch macro handling. */ + { + int c; + + for (;;) + { + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + { + op->type = t_other; + return; + } + if (!(c >= '0' && c <= '9')) + break; + } + + switch (c) + { + case '(': + case '"': + do_ungetc (c); + /*FALLTHROUGH*/ + case '\'': + case ':': + case '.': + case ',': + case 'A': case 'a': + case 'C': case 'c': + case 'P': case 'p': + case 'S': case 's': + { + struct object inner; + read_object (&inner, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '|': + { + int depth = 0; + int c; + + comment_start (); + c = do_getc (); + for (;;) + { + if (c == EOF) + break; + if (c == '|') + { + c = do_getc (); + if (c == EOF) + break; + if (c == '#') + { + if (depth == 0) + { + comment_line_end (0); + break; + } + depth--; + comment_add ('|'); + comment_add ('#'); + c = do_getc (); + } + else + comment_add ('|'); + } + else if (c == '#') + { + c = do_getc (); + if (c == EOF) + break; + comment_add ('#'); + if (c == '|') + { + depth++; + comment_add ('|'); + c = do_getc (); + } + } + else + { + /* We skip all leading white space. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + if (c == '\n') + { + comment_line_end (1); + comment_start (); + } + c = do_getc (); + } + } + if (c == EOF) + { + /* EOF not allowed here. But be tolerant. */ + op->type = t_eof; + return; + } + last_comment_line = line_number; + continue; + } + + case '\\': + { + struct token token; + struct char_syntax first; + first.ch = '\\'; + first.scode = syntax_single_esc; + read_token (&token, &first); + free_token (&token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case 'B': case 'b': + case 'O': case 'o': + case 'X': case 'x': + case 'R': case 'r': + case '*': + { + struct token token; + read_token (&token, NULL); + free_token (&token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '=': + /* Ignore read labels. */ + continue; + + case '#': + /* Don't bother looking up the corresponding object. */ + op->type = t_other; + last_non_comment_line = line_number; + return; + + case '+': + case '-': + /* Simply assume every feature expression is true. */ + { + struct object inner; + read_object (&inner, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + continue; + } + + default: + op->type = t_other; + last_non_comment_line = line_number; + return; + } + /*NOTREACHED*/ + abort (); + } + + default: + /*NOTREACHED*/ + abort (); + } + + default: + /*NOTREACHED*/ + abort (); + } + } +} + + +void +extract_lisp (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When read_object returns + due to an unbalanced closing parenthesis, just restart it. */ + do + { + struct object toplevel_object; + + read_object (&toplevel_object, null_context); + + if (toplevel_object.type == t_eof) + break; + + free_object (&toplevel_object); + } + while (!feof (fp)); + + /* Close scanner. */ + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-lisp.h b/gettext-tools/src/x-lisp.h new file mode 100644 index 0000000..97b593d --- /dev/null +++ b/gettext-tools/src/x-lisp.h @@ -0,0 +1,54 @@ +/* xgettext Lisp backend. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_LISP \ + { "lisp", "Lisp" }, \ + +#define SCANNERS_LISP \ + { "Lisp", extract_lisp, \ + &flag_table_lisp, &formatstring_lisp, NULL, NULL }, \ + +/* Scan a Lisp file and add its translatable strings to mdlp. */ +extern void extract_lisp (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +/* Handling of options specific to this language. */ + +extern void x_lisp_extract_all (void); +extern void x_lisp_keyword (const char *name); + +extern void init_flag_table_lisp (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-lua.c b/gettext-tools/src/x-lua.c new file mode 100644 index 0000000..9fcc20d --- /dev/null +++ b/gettext-tools/src/x-lua.c @@ -0,0 +1,1215 @@ +/* xgettext Lua backend. + Copyright (C) 2012-2013 Free Software Foundation, Inc. + + This file was written by Ľubomír Remák , 2012. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Specification. */ +#include "x-lua.h" + +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "gettext.h" +#include "po-charset.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + +/* The Lua syntax is defined in the Lua manual section 9, + which can be found at + http://www.lua.org/manual/5.2/manual.html#9 */ + +/* If true extract all strings. */ +static bool extract_all = false; + +/* A hash table for keywords. */ +static hash_table keywords; +static bool default_keywords = true; + +/* Set extract_all flag (gettext will extract all strings). */ +void +x_lua_extract_all () +{ + extract_all = true; +} + +/* Adds a keyword. Copied from other lexers. */ +void +x_lua_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_lua_keyword ("_"); + x_lua_keyword ("gettext.gettext"); + x_lua_keyword ("gettext.dgettext:2"); + x_lua_keyword ("gettext.dcgettext:2"); + x_lua_keyword ("gettext.ngettext:1,2"); + x_lua_keyword ("gettext.dngettext:2,3"); + x_lua_keyword ("gettext.dcngettext:2,3"); + default_keywords = false; + } +} + +void +init_flag_table_lua () +{ + xgettext_record_flag ("_:1:pass-lua-format"); + xgettext_record_flag ("gettext.gettext:1:pass-lua-format"); + xgettext_record_flag ("gettext.dgettext:2:pass-lua-format"); + xgettext_record_flag ("gettext.dcgettext:2:pass-lua-format"); + xgettext_record_flag ("gettext.ngettext:1:pass-lua-format"); + xgettext_record_flag ("gettext.ngettext:2:pass-lua-format"); + xgettext_record_flag ("gettext.dngettext:2:pass-lua-format"); + xgettext_record_flag ("gettext.dngettext:3:pass-lua-format"); + xgettext_record_flag ("gettext.dcngettext:2:pass-lua-format"); + xgettext_record_flag ("gettext.dcngettext:3:pass-lua-format"); + xgettext_record_flag ("string.format:1:lua-format"); +} + +/* ======================== Reading of characters. ======================== */ + + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* 1. line_number handling. */ + +static unsigned char phase1_pushback[2]; +static int phase1_pushback_length; + +static int first_character = 1; + +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + c = phase1_pushback[--phase1_pushback_length]; + else + { + c = getc (fp); + + if (first_character) + { + first_character = 0; + + /* Ignore shebang line. No pushback required in this case. */ + if (c == '#') + { + while (c != '\n' && c != EOF) + c = getc (fp); + if (c == '\n') + { + line_number++; + c = getc (fp); + } + } + } + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + } + + if (c == '\n') + line_number++; + + return c; +} + +/* Supports 2 characters of pushback. */ + +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + --line_number; + + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + } +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + +/* Accumulating comments. */ + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (chars_to_remove == 0 && buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + +/* Eats characters until '\n' and adds them to the comment. */ +static void +eat_comment_line () +{ + for (;;) + { + int c = phase1_getc (); + if (c == '\n' || c == EOF) + { + comment_line_end (0); + break; + } + + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } +} + +static int +phase2_getc () +{ + int c; + int lineno; + + c = phase1_getc (); + + if (c == '-') + { + c = phase1_getc (); + + if (c == '-') + { + /* It starts with '--', so it must be either a short or a long + comment. */ + c = phase1_getc (); + + if (c == '[') + { + c = phase1_getc (); + + int esigns = 0; + while (c == '=') + { + esigns++; + c = phase1_getc (); + } + + if (c == '[') + { + /* Long comment. */ + bool right_bracket = false; + bool end = false; + int esigns2 = 0; + + lineno = line_number; + comment_start (); + while (!end) + { + c = phase1_getc (); + + if (c == EOF) + break; + + /* Ignore leading spaces and tabs. */ + if (buflen == 0 && (c == ' ' || c == '\t')) + continue; + + comment_add (c); + + switch (c) + { + case ']': + if (!right_bracket) + { + right_bracket = true; + esigns2 = 0; + } + else + { + if (esigns2 == esigns) + { + comment_line_end (2 + esigns); + end = true; + } + } + break; + + case '=': + if (right_bracket) + esigns2++; + break; + + case '\n': + comment_line_end (1); + comment_start (); + lineno = line_number; + /* Intentionally not breaking. */ + + default: + right_bracket = false; + } + } + last_comment_line = lineno; + return ' '; + } + else + { + /* One line (short) comment, starting with '--[=...='. */ + lineno = last_comment_line; + comment_start (); + comment_add ('['); + while (esigns--) + comment_add ('='); + phase1_ungetc (c); + eat_comment_line (); + last_comment_line = lineno; + return '\n'; + } + } + else + { + /* One line (short) comment. */ + lineno = line_number; + comment_start (); + phase1_ungetc (c); + eat_comment_line (); + last_comment_line = lineno; + return '\n'; + } + } + else + { + /* Minus sign. */ + phase1_ungetc (c); + return '-'; + } + } + else + return c; +} + +/* ========================== Reading of tokens. ========================== */ + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_lbracket, /* [ */ + token_type_rbracket, /* ] */ + token_type_comma, /* , */ + token_type_dot, /* . */ + token_type_doubledot, /* .. */ + token_type_operator1, /* + - * / % not # - ^ */ + token_type_operator2, /* < > <= >= ~= == and or */ + token_type_string, + token_type_number, + token_type_symbol, + token_type_other +}; + +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_string_literal, token_type_symbol */ + refcounted_string_list_ty *comment; /* for token_type_string_literal */ + int line_number; +}; + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_string || tp->type == token_type_symbol) + free (tp->string); + if (tp->type == token_type_string) + drop_reference (tp->comment); +} + +/* Our current string. */ +static int string_buf_length; +static int string_buf_alloc; +static char *string_buf; + +static void +string_start () +{ + string_buf_length = 0; +} + +static void +string_add (int c) +{ + if (string_buf_length >= string_buf_alloc) + { + string_buf_alloc = 2 * string_buf_alloc + 10; + string_buf = xrealloc (string_buf, string_buf_alloc); + } + + string_buf[string_buf_length++] = c; +} + +static void +string_end () +{ + string_buf[string_buf_length] = '\0'; +} + + +/* We need 3 pushback tokens for string optimization. */ +static int phase3_pushback_length; +static token_ty phase3_pushback[3]; + + +static void +phase3_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase3_pushback_length == SIZEOF (phase3_pushback)) + abort (); + phase3_pushback[phase3_pushback_length++] = *tp; + } +} + +static void +phase3_get (token_ty *tp) +{ + int c; + int c2; + int c_start; + + if (phase3_pushback_length) + { + *tp = phase3_pushback[--phase3_pushback_length]; + return; + } + + tp->string = NULL; + + for (;;) + { + tp->line_number = line_number; + c = phase2_getc (); + + switch (c) + { + case EOF: + tp->type = token_type_eof; + return; + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* Intentionally not breaking. */ + case ' ': + case '\t': + case '\f': + continue; + + case '+': + case '-': + case '*': + case '/': + case '^': + case '%': + case '#': + tp->type = token_type_operator1; + return; + case '<': + case '>': + case '=': + c2 = phase1_getc (); + if (c2 != '=') + phase1_ungetc (c2); + tp->type = token_type_operator2; + return; + case '~': + c2 = phase1_getc (); + if (c2 == '=') + { + tp->type = token_type_operator2; + return; + } + else + phase1_ungetc (c2); + continue; + case '(': + tp->type = token_type_lparen; + return; + case ')': + tp->type = token_type_rparen; + return; + case ',': + tp->type = token_type_comma; + return; + + case ';': + tp->type = token_type_other; + return; + + /* There are three operators beginning with a dot. '.', + '..' and '...'. The most useful for us is the string + concatenation operator ('..'). */ + case '.': + c = phase1_getc (); + if (c == '.') + { + c = phase1_getc (); + if (c == '.') + { + tp->type = token_type_other; + return; + } + else + { + phase1_ungetc (c); + tp->type = token_type_doubledot; + return; + } + } + else if (c >= '0' && c <= '9') + { + /* It's a number. We aren't interested in the actual + numeric value, so ignore the dot and let next + iteration eat the number. */ + phase1_ungetc (c); + continue; + } + else + { + phase1_ungetc (c); + tp->type = token_type_dot; + return; + } + + case '"': + case '\'': + c_start = c; + string_start (); + + for (;;) + { + /* We need unprocessed characters from phase 1. */ + c = phase1_getc (); + + /* We got '\', this is probably an escape sequence. */ + if (c == '\\') + { + c = phase1_getc (); + switch (c) + { + case 'a': + string_add ('\a'); + break; + case 'b': + string_add ('\b'); + break; + case 'f': + string_add ('\f'); + break; + case 'n': + string_add ('\n'); + break; + case 'r': + string_add ('\r'); + break; + case 't': + string_add ('\t'); + break; + case 'v': + string_add ('\v'); + break; + case 'x': + { + int num = 0; + int i = 0; + + for (i = 0; i < 2; i++) + { + c = phase1_getc (); + if (c >= '0' && c <= '9') + num += c - '0'; + else if (c >= 'a' && c <= 'f') + num += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + num += c - 'A' + 10; + else + { + phase1_ungetc (c); + break; + } + + if (i == 0) + num *= 16; + } + + if (i == 2) + string_add (num); + } + + break; + case 'z': + /* Ignore the following whitespace. */ + do + { + c = phase1_getc (); + } + while (c == ' ' || c == '\n' || c == '\t' || c == '\r' + || c == '\f' || c == '\v'); + + phase1_ungetc (c); + + break; + default: + /* Check if it's a '\ddd' sequence. */ + if (c >= '0' && c <= '9') + { + int num = 0; + int i = 0; + + while (c >= '0' && c <= '9' && i < 3) + { + num *= 10; + num += (c - '0'); + c = phase1_getc (); + i++; + } + + /* The last read character is either a + non-number or another number after our + '\ddd' sequence. We need to ungetc it. */ + phase1_ungetc (c); + + /* The sequence number is too big, this + causes a lexical error. Ignore it. */ + if (num < 256) + string_add (num); + } + else + string_add (c); + } + } + else if (c == c_start || c == EOF || c == '\n') + { + /* End of string. */ + string_end (); + tp->string = xstrdup (string_buf); + tp->comment = add_reference (savable_comment); + tp->type = token_type_string; + return; + } + else + string_add (c); + } + break; + + case '[': + c = phase1_getc (); + + /* Count the number of equal signs. */ + int esigns = 0; + while (c == '=') + { + esigns++; + c = phase1_getc (); + } + + if (c != '[') + { + /* We did not find what we were looking for, ungetc it. */ + phase1_ungetc (c); + if (esigns == 0) + { + /* Our current character isn't '[' and we got 0 equal + signs, so the first '[' must have been a left + bracket. */ + tp->type = token_type_lbracket; + return; + } + else + /* Lexical error, ignore it. */ + continue; + } + + string_start (); + + for (;;) + { + c = phase1_getc (); + + if (c == ']') + { + c = phase1_getc (); + + /* Count the number of equal signs. */ + int esigns2 = 0; + while (c == '=') + { + esigns2++; + c = phase1_getc (); + } + + if (c == ']' && esigns == esigns2) + { + /* We got ']==...==]', where the number of equal + signs matches the number of equal signs in + the opening bracket. */ + string_end (); + tp->string = xstrdup (string_buf); + tp->comment = add_reference (savable_comment); + tp->type = token_type_string; + return; + } + else + { + /* Otherwise we got either ']==' garbage or + ']==...==]' with a different number of equal + signs. + + Add ']' and equal signs to the string, and + ungetc the current character, because the + second ']' might be a part of another closing + long bracket, e.g. '==]===]'. */ + phase1_ungetc (c); + + string_add (']'); + while (esigns2--) + string_add ('='); + } + } + else + { + if (c == EOF) + { + string_end (); + tp->string = xstrdup (string_buf); + tp->comment = add_reference (savable_comment); + tp->type = token_type_string; + return; + } + else + string_add (c); + } + } + break; + + case ']': + tp->type = token_type_rbracket; + return; + + default: + if (c >= '0' && c <= '9') + { + while (c >= '0' && c <= '9') + c = phase1_getc (); + + if (c == '.') + { + c = phase1_getc (); + while (c >= '0' && c <= '9') + c = phase1_getc (); + } + + if (c == 'e' || c == 'E') + { + if (c == '+' || c == '-') + c = phase1_getc (); + while (c >= '0' && c <= '9') + c = phase1_getc (); + } + + phase1_ungetc (c); + + tp->type = token_type_number; + return; + } + else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || c == '_') + { + string_start (); + while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || c == '_' || (c >= '0' && c <= '9')) + { + string_add (c); + c = phase1_getc (); + } + string_end (); + phase1_ungetc (c); + + if (strcmp (string_buf, "not") == 0) + tp->type = token_type_operator1; + else if (strcmp (string_buf, "and") == 0) + tp->type = token_type_operator2; + else if (strcmp (string_buf, "or") == 0) + tp->type = token_type_operator2; + else + { + tp->string = xstrdup (string_buf); + tp->type = token_type_symbol; + } + return; + } + else + tp->type = token_type_other; + } + } +} + +/* String and symbol concatenation. */ + +static token_type_ty phase4_last; + +/* We need 3 pushback tokens for string and symbol concatenation. */ +static int phase4_pushback_length; +static token_ty phase4_pushback[3]; + +static void +phase4_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase4_pushback_length == SIZEOF (phase4_pushback)) + abort (); + phase4_pushback[phase4_pushback_length++] = *tp; + } +} + +static void +phase4_get (token_ty *tp) +{ + if (phase4_pushback_length) + { + *tp = phase4_pushback[--phase4_pushback_length]; + phase4_last = tp->type; + return; + } + + phase3_get (tp); + if (tp->type == token_type_string + && !(phase4_last == token_type_operator1 + || phase4_last == token_type_dot + || phase4_last == token_type_symbol + || phase4_last == token_type_doubledot + || phase4_last == token_type_rparen)) + { + char *sum = tp->string; + size_t sum_len = strlen (sum); + + for (;;) + { + token_ty token2; + + phase3_get (&token2); + if (token2.type == token_type_doubledot) + { + token_ty token3; + + phase3_get (&token3); + if (token3.type == token_type_string) + { + token_ty token_after; + + phase3_get (&token_after); + if (token_after.type != token_type_operator1) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = (char *) xrealloc (sum, sum_len + addend_len + 1); + memcpy (sum + sum_len, addend, addend_len + 1); + sum_len += addend_len; + + phase3_unget (&token_after); + free_token (&token3); + free_token (&token2); + continue; + } + phase3_unget (&token_after); + } + phase3_unget (&token3); + } + phase3_unget (&token2); + break; + } + tp->string = sum; + } + phase4_last = tp->type; +} + +static void +phase5_get (token_ty *tp) +{ + phase4_get (tp); + + /* Combine symbol1 . ... . symbolN to a single strings, so that + we can recognize function calls like + gettext.gettext. The information present for + symbolI.....symbolN has precedence over the information for + symbolJ.....symbolN with J > I. */ + if (tp->type == token_type_symbol) + { + char *sum = tp->string; + size_t sum_len = strlen (sum); + + for (;;) + { + token_ty token2; + + phase4_get (&token2); + if (token2.type == token_type_dot) + { + token_ty token3; + + phase4_get (&token3); + if (token3.type == token_type_symbol) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = (char *) xrealloc (sum, sum_len + 1 + addend_len + 1); + sum[sum_len] = '.'; + memcpy (sum + sum_len + 1, addend, addend_len + 1); + sum_len += 1 + addend_len; + + free_token (&token2); + free_token (&token3); + continue; + } + phase4_unget (&token3); + } + phase4_unget (&token2); + break; + } + tp->string = sum; + } +} + +static void +x_lua_lex (token_ty *tok) +{ + phase5_get (tok); +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid Lua program, and leave the complaints about the + grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + keyword msgid + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + +/* Extract messages until the next balanced closing parenthesis or bracket. + Extracted messages are added to MLP. + DELIM can be either token_type_rparen or token_type_rbracket, or + token_type_eof to accept both. + Return true upon eof, false upon closing parenthesis or bracket. */ +static bool +extract_balanced (message_list_ty *mlp, token_type_ty delim, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_lua_lex (&token); + + switch (token.type) + { + case token_type_symbol: + { + void *keyword_value; + + if (hash_find_entry (&keywords, token.string, strlen (token.string), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + } + else + state = 0; + } + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, strlen (token.string))); + free (token.string); + continue; + + case token_type_lparen: + if (extract_balanced (mlp, token_type_rparen, + inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + arglist_parser_done (argparser, arg); + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + break; + + case token_type_rparen: + if (delim == token_type_rparen || delim == token_type_eof) + { + arglist_parser_done (argparser, arg); + return false; + } + + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_lbracket: + if (extract_balanced (mlp, token_type_rbracket, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + { + arglist_parser_done (argparser, arg); + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + break; + + case token_type_rbracket: + if (delim == token_type_rbracket || delim == token_type_eof) + { + arglist_parser_done (argparser, arg); + return false; + } + + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case token_type_eof: + arglist_parser_done (argparser, arg); + return true; + + case token_type_string: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + if (extract_all) + remember_a_message (mlp, NULL, token.string, inner_context, + &pos, NULL, token.comment); + else + { + /* A string immediately after a symbol means a function call. */ + if (state) + { + struct arglist_parser *tmp_argparser; + tmp_argparser = arglist_parser_alloc (mlp, next_shapes); + + arglist_parser_remember (tmp_argparser, 1, token.string, + inner_context, pos.file_name, + pos.line_number, token.comment); + arglist_parser_done (tmp_argparser, 1); + } + else + arglist_parser_remember (argparser, arg, token.string, + inner_context, pos.file_name, + pos.line_number, token.comment); + } + } + drop_reference (token.comment); + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_dot: + case token_type_doubledot: + case token_type_operator1: + case token_type_operator2: + case token_type_number: + case token_type_other: + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + default: + abort (); + } + } +} + +void +extract_lua (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When extract_parenthesized returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_balanced (mlp, token_type_eof, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-lua.h b/gettext-tools/src/x-lua.h new file mode 100644 index 0000000..3161638 --- /dev/null +++ b/gettext-tools/src/x-lua.h @@ -0,0 +1,48 @@ +/* xgettext Lua backend. + Copyright (C) 2011 Free Software Foundation, Inc. + Written by Ľubomír Remák , 2011 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "message.h" +#include "xgettext.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define EXTENSIONS_LUA \ + { "lua", "Lua" }, \ + +#define SCANNERS_LUA \ + { "Lua", extract_lua, \ + &flag_table_lua, &formatstring_lua, NULL, NULL }, \ + + /* Scan a Lua file and add its translatable strings to mdlp. */ + extern void extract_lua (FILE * fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty * flag_table, + msgdomain_list_ty * mdlp); + + extern void x_lua_keyword (const char *keyword); + extern void x_lua_extract_all (void); + + extern void init_flag_table_lua (void); + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-perl.c b/gettext-tools/src/x-perl.c new file mode 100644 index 0000000..571f6de --- /dev/null +++ b/gettext-tools/src/x-perl.c @@ -0,0 +1,3592 @@ +/* xgettext Perl backend. + Copyright (C) 2002-2010 Free Software Foundation, Inc. + + This file was written by Guido Flohr , 2002-2010. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-perl.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "error-progname.h" +#include "xalloc.h" +#include "po-charset.h" +#include "unistr.h" +#include "uniname.h" +#include "gettext.h" + +#define _(s) gettext(s) + +/* The Perl syntax is defined in perlsyn.pod. Try the command + "man perlsyn" or "perldoc perlsyn". + Also, the syntax after the 'sub' keyword is specified in perlsub.pod. + Try the command "man perlsub" or "perldoc perlsub". + Perl 5.10 has new operators '//' and '//=', see + . */ + +#define DEBUG_PERL 0 + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_perl_extract_all () +{ + extract_all = true; +} + + +void +x_perl_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_perl_keyword ("gettext"); + x_perl_keyword ("%gettext"); + x_perl_keyword ("$gettext"); + x_perl_keyword ("dgettext:2"); + x_perl_keyword ("dcgettext:2"); + x_perl_keyword ("ngettext:1,2"); + x_perl_keyword ("dngettext:2,3"); + x_perl_keyword ("dcngettext:2,3"); + x_perl_keyword ("gettext_noop"); +#if 0 + x_perl_keyword ("__"); + x_perl_keyword ("$__"); + x_perl_keyword ("%__"); + x_perl_keyword ("__x"); + x_perl_keyword ("__n:1,2"); + x_perl_keyword ("__nx:1,2"); + x_perl_keyword ("__xn:1,2"); + x_perl_keyword ("N__"); +#endif + default_keywords = false; + } +} + +void +init_flag_table_perl () +{ + xgettext_record_flag ("gettext:1:pass-perl-format"); + xgettext_record_flag ("gettext:1:pass-perl-brace-format"); + xgettext_record_flag ("%gettext:1:pass-perl-format"); + xgettext_record_flag ("%gettext:1:pass-perl-brace-format"); + xgettext_record_flag ("$gettext:1:pass-perl-format"); + xgettext_record_flag ("$gettext:1:pass-perl-brace-format"); + xgettext_record_flag ("dgettext:2:pass-perl-format"); + xgettext_record_flag ("dgettext:2:pass-perl-brace-format"); + xgettext_record_flag ("dcgettext:2:pass-perl-format"); + xgettext_record_flag ("dcgettext:2:pass-perl-brace-format"); + xgettext_record_flag ("ngettext:1:pass-perl-format"); + xgettext_record_flag ("ngettext:2:pass-perl-format"); + xgettext_record_flag ("ngettext:1:pass-perl-brace-format"); + xgettext_record_flag ("ngettext:2:pass-perl-brace-format"); + xgettext_record_flag ("dngettext:2:pass-perl-format"); + xgettext_record_flag ("dngettext:3:pass-perl-format"); + xgettext_record_flag ("dngettext:2:pass-perl-brace-format"); + xgettext_record_flag ("dngettext:3:pass-perl-brace-format"); + xgettext_record_flag ("dcngettext:2:pass-perl-format"); + xgettext_record_flag ("dcngettext:3:pass-perl-format"); + xgettext_record_flag ("dcngettext:2:pass-perl-brace-format"); + xgettext_record_flag ("dcngettext:3:pass-perl-brace-format"); + xgettext_record_flag ("gettext_noop:1:pass-perl-format"); + xgettext_record_flag ("gettext_noop:1:pass-perl-brace-format"); + xgettext_record_flag ("printf:1:perl-format"); /* argument 1 or 2 ?? */ + xgettext_record_flag ("sprintf:1:perl-format"); +#if 0 + xgettext_record_flag ("__:1:pass-perl-format"); + xgettext_record_flag ("__:1:pass-perl-brace-format"); + xgettext_record_flag ("%__:1:pass-perl-format"); + xgettext_record_flag ("%__:1:pass-perl-brace-format"); + xgettext_record_flag ("$__:1:pass-perl-format"); + xgettext_record_flag ("$__:1:pass-perl-brace-format"); + xgettext_record_flag ("__x:1:perl-brace-format"); + xgettext_record_flag ("__n:1:pass-perl-format"); + xgettext_record_flag ("__n:2:pass-perl-format"); + xgettext_record_flag ("__n:1:pass-perl-brace-format"); + xgettext_record_flag ("__n:2:pass-perl-brace-format"); + xgettext_record_flag ("__nx:1:perl-brace-format"); + xgettext_record_flag ("__nx:2:perl-brace-format"); + xgettext_record_flag ("__xn:1:perl-brace-format"); + xgettext_record_flag ("__xn:2:perl-brace-format"); + xgettext_record_flag ("N__:1:pass-perl-format"); + xgettext_record_flag ("N__:1:pass-perl-brace-format"); +#endif +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + +/* The current line buffer. */ +static char *linebuf; + +/* The size of the current line. */ +static int linesize; + +/* The position in the current line. */ +static int linepos; + +/* The size of the input buffer. */ +static size_t linebuf_size; + +/* Number of lines eaten for here documents. */ +static int eaten_here; + +/* Paranoia: EOF marker for __END__ or __DATA__. */ +static bool end_of_file; + + +/* 1. line_number handling. */ + +/* Returns the next character from the input stream or EOF. */ +static int +phase1_getc () +{ + line_number += eaten_here; + eaten_here = 0; + + if (end_of_file) + return EOF; + + if (linepos >= linesize) + { + linesize = getline (&linebuf, &linebuf_size, fp); + + if (linesize < 0) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + end_of_file = true; + return EOF; + } + + linepos = 0; + ++line_number; + + /* Undosify. This is important for catching the end of <= 2 && linebuf[linesize - 1] == '\n' + && linebuf[linesize - 2] == '\r') + { + linebuf[linesize - 2] = '\n'; + linebuf[linesize - 1] = '\0'; + --linesize; + } + } + + return linebuf[linepos++]; +} + +/* Supports only one pushback character. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (linepos == 0) + /* Attempt to ungetc across line boundary. Shouldn't happen. + No two phase1_ungetc calls are permitted in a row. */ + abort (); + + --linepos; + } +} + +/* Read a here document and return its contents. + The delimiter is an UTF-8 encoded string; the resulting string is UTF-8 + encoded as well. */ + +static char * +get_here_document (const char *delimiter) +{ + /* Accumulator for the entire here document, including a NUL byte + at the end. */ + static char *buffer; + static size_t bufmax = 0; + size_t bufpos = 0; + /* Current line being appended. */ + static char *my_linebuf = NULL; + static size_t my_linebuf_size = 0; + + /* Allocate the initial buffer. Later on, bufmax > 0. */ + if (bufmax == 0) + { + buffer = XNMALLOC (1, char); + buffer[0] = '\0'; + bufmax = 1; + } + + for (;;) + { + int read_bytes = getline (&my_linebuf, &my_linebuf_size, fp); + char *my_line_utf8; + bool chomp; + + if (read_bytes < 0) + { + if (ferror (fp)) + { + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + } + else + { + error_with_progname = false; + error (EXIT_SUCCESS, 0, _("\ +%s:%d: can't find string terminator \"%s\" anywhere before EOF"), + real_file_name, line_number, delimiter); + error_with_progname = true; + + break; + } + } + + ++eaten_here; + + /* Convert to UTF-8. */ + my_line_utf8 = + from_current_source_encoding (my_linebuf, lc_string, logical_file_name, + line_number + eaten_here); + if (my_line_utf8 != my_linebuf) + { + if (strlen (my_line_utf8) >= my_linebuf_size) + { + my_linebuf_size = strlen (my_line_utf8) + 1; + my_linebuf = xrealloc (my_linebuf, my_linebuf_size); + } + strcpy (my_linebuf, my_line_utf8); + free (my_line_utf8); + } + + /* Undosify. This is important for catching the end of <= 2 && my_linebuf[read_bytes - 1] == '\n' + && my_linebuf[read_bytes - 2] == '\r') + { + my_linebuf[read_bytes - 2] = '\n'; + my_linebuf[read_bytes - 1] = '\0'; + --read_bytes; + } + + /* Temporarily remove the trailing newline from my_linebuf. */ + chomp = false; + if (read_bytes >= 1 && my_linebuf[read_bytes - 1] == '\n') + { + chomp = true; + my_linebuf[read_bytes - 1] = '\0'; + } + + /* See whether this line terminates the here document. */ + if (strcmp (my_linebuf, delimiter) == 0) + break; + + /* Add back the trailing newline to my_linebuf. */ + if (chomp) + my_linebuf[read_bytes - 1] = '\n'; + + /* Ensure room for read_bytes + 1 bytes. */ + if (bufpos + read_bytes >= bufmax) + { + do + bufmax = 2 * bufmax + 10; + while (bufpos + read_bytes >= bufmax); + buffer = xrealloc (buffer, bufmax); + } + /* Append this line to the accumulator. */ + strcpy (buffer + bufpos, my_linebuf); + bufpos += read_bytes; + } + + /* Done accumulating the here document. */ + return xstrdup (buffer); +} + +/* Skips pod sections. */ +static void +skip_pod () +{ + line_number += eaten_here; + eaten_here = 0; + linepos = 0; + + for (;;) + { + linesize = getline (&linebuf, &linebuf_size, fp); + + if (linesize < 0) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return; + } + + ++line_number; + + if (strncmp ("=cut", linebuf, 4) == 0) + { + /* Force reading of a new line on next call to phase1_getc(). */ + linepos = linesize; + return; + } + } +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* 2. Replace each comment that is not inside a string literal or regular + expression with a newline character. We need to remember the comment + for later, because it may be attached to a keyword string. */ + +static int +phase2_getc () +{ + static char *buffer; + static size_t bufmax; + size_t buflen; + int lineno; + int c; + char *utf8_string; + + c = phase1_getc (); + if (c == '#') + { + buflen = 0; + lineno = line_number; + /* Skip leading whitespace. */ + for (;;) + { + c = phase1_getc (); + if (c == EOF) + break; + if (c != ' ' && c != '\t' && c != '\r' && c != '\f') + { + phase1_ungetc (c); + break; + } + } + /* Accumulate the comment. */ + for (;;) + { + c = phase1_getc (); + if (c == '\n' || c == EOF) + break; + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; + } + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + /* Convert it to UTF-8. */ + utf8_string = + from_current_source_encoding (buffer, lc_comment, logical_file_name, + lineno); + /* Save it until we encounter the corresponding string. */ + savable_comment_add (utf8_string); + last_comment_line = lineno; + } + return c; +} + +/* Supports only one pushback character. */ +static void +phase2_ungetc (int c) +{ + if (c != EOF) + phase1_ungetc (c); +} + +/* Whitespace recognition. */ + +#define case_whitespace \ + case ' ': case '\t': case '\r': case '\n': case '\f' + +static inline bool +is_whitespace (int c) +{ + return (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f'); +} + + +/* ========================== Reading of tokens. ========================== */ + + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_comma, /* , */ + token_type_fat_comma, /* => */ + token_type_dereference, /* -> */ + token_type_semicolon, /* ; */ + token_type_lbrace, /* { */ + token_type_rbrace, /* } */ + token_type_lbracket, /* [ */ + token_type_rbracket, /* ] */ + token_type_string, /* quote-like */ + token_type_number, /* starting with a digit o dot */ + token_type_named_op, /* if, unless, while, ... */ + token_type_variable, /* $... */ + token_type_object, /* A dereferenced variable, maybe a blessed + object. */ + token_type_symbol, /* symbol, number */ + token_type_regex_op, /* s, tr, y, m. */ + token_type_dot, /* . */ + token_type_other, /* regexp, misc. operator */ + /* The following are not really token types, but variants used by + the parser. */ + token_type_keyword_symbol /* keyword symbol */ +}; +typedef enum token_type_ty token_type_ty; + +/* Subtypes for strings, important for interpolation. */ +enum string_type_ty +{ + string_type_verbatim, /* "<<'EOF'", "m'...'", "s'...''...'", + "tr/.../.../", "y/.../.../". */ + string_type_q, /* "'..'", "q/.../". */ + string_type_qq, /* '"..."', "`...`", "qq/.../", "qx/.../", + "". */ + string_type_qr /* Not supported. */ +}; + +/* Subtypes for symbols, important for dollar interpretation. */ +enum symbol_type_ty +{ + symbol_type_none, /* Nothing special. */ + symbol_type_sub, /* 'sub'. */ + symbol_type_function /* Function name after 'sub'. */ +}; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + token_type_ty last_type; + int sub_type; /* for token_type_string, token_type_symbol */ + char *string; /* for: in encoding: + token_type_named_op ASCII + token_type_string UTF-8 + token_type_symbol ASCII + token_type_variable global_source_encoding + token_type_object global_source_encoding + */ + refcounted_string_list_ty *comment; /* for token_type_string */ + int line_number; +}; + +#if DEBUG_PERL +static const char * +token2string (const token_ty *token) +{ + switch (token->type) + { + case token_type_eof: + return "token_type_eof"; + case token_type_lparen: + return "token_type_lparen"; + case token_type_rparen: + return "token_type_rparen"; + case token_type_comma: + return "token_type_comma"; + case token_type_fat_comma: + return "token_type_fat_comma"; + case token_type_dereference: + return "token_type_dereference"; + case token_type_semicolon: + return "token_type_semicolon"; + case token_type_lbrace: + return "token_type_lbrace"; + case token_type_rbrace: + return "token_type_rbrace"; + case token_type_lbracket: + return "token_type_lbracket"; + case token_type_rbracket: + return "token_type_rbracket"; + case token_type_string: + return "token_type_string"; + case token_type_number: + return "token type number"; + case token_type_named_op: + return "token_type_named_op"; + case token_type_variable: + return "token_type_variable"; + case token_type_object: + return "token_type_object"; + case token_type_symbol: + return "token_type_symbol"; + case token_type_regex_op: + return "token_type_regex_op"; + case token_type_dot: + return "token_type_dot"; + case token_type_other: + return "token_type_other"; + default: + return "unknown"; + } +} +#endif + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + switch (tp->type) + { + case token_type_named_op: + case token_type_string: + case token_type_symbol: + case token_type_variable: + case token_type_object: + free (tp->string); + break; + default: + break; + } + if (tp->type == token_type_string) + drop_reference (tp->comment); + free (tp); +} + +/* Pass 1 of extracting quotes: Find the end of the string, regardless + of the semantics of the construct. Return the complete string, + including the starting and the trailing delimiter, with backslashes + removed where appropriate. */ +static char * +extract_quotelike_pass1 (int delim) +{ + /* This function is called recursively. No way to allocate stuff + statically. Also alloca() is inappropriate due to limited stack + size on some platforms. So we use malloc(). */ + int bufmax = 10; + char *buffer = XNMALLOC (bufmax, char); + int bufpos = 0; + bool nested = true; + int counter_delim; + + buffer[bufpos++] = delim; + + /* Find the closing delimiter. */ + switch (delim) + { + case '(': + counter_delim = ')'; + break; + case '{': + counter_delim = '}'; + break; + case '[': + counter_delim = ']'; + break; + case '<': + counter_delim = '>'; + break; + default: /* "..." or '...' or |...| etc. */ + nested = false; + counter_delim = delim; + break; + } + + for (;;) + { + int c = phase1_getc (); + + /* This round can produce 1 or 2 bytes. Ensure room for 2 bytes. */ + if (bufpos + 2 > bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + + if (c == counter_delim || c == EOF) + { + buffer[bufpos++] = counter_delim; /* will be stripped off later */ + buffer[bufpos++] = '\0'; +#if DEBUG_PERL + fprintf (stderr, "PASS1: %s\n", buffer); +#endif + return buffer; + } + + if (nested && c == delim) + { + char *inner = extract_quotelike_pass1 (delim); + size_t len = strlen (inner); + + /* Ensure room for len + 1 bytes. */ + if (bufpos + len >= bufmax) + { + do + bufmax = 2 * bufmax + 10; + while (bufpos + len >= bufmax); + buffer = xrealloc (buffer, bufmax); + } + strcpy (buffer + bufpos, inner); + free (inner); + bufpos += len; + } + else if (c == '\\') + { + c = phase1_getc (); + if (c == '\\') + { + buffer[bufpos++] = '\\'; + buffer[bufpos++] = '\\'; + } + else if (c == delim || c == counter_delim) + { + /* This is pass2 in Perl. */ + buffer[bufpos++] = c; + } + else + { + buffer[bufpos++] = '\\'; + phase1_ungetc (c); + } + } + else + { + buffer[bufpos++] = c; + } + } +} + +/* Like extract_quotelike_pass1, but return the complete string in UTF-8 + encoding. */ +static char * +extract_quotelike_pass1_utf8 (int delim) +{ + char *string = extract_quotelike_pass1 (delim); + char *utf8_string = + from_current_source_encoding (string, lc_string, logical_file_name, + line_number); + if (utf8_string != string) + free (string); + return utf8_string; +} + + +/* ========= Reading of tokens and commands. Extracting strings. ========= */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* Forward declaration of local functions. */ +static void interpolate_keywords (message_list_ty *mlp, const char *string, + int lineno); +static token_ty *x_perl_lex (message_list_ty *mlp); +static void x_perl_unlex (token_ty *tp); +static bool extract_balanced (message_list_ty *mlp, + token_type_ty delim, bool eat_delim, + bool comma_delim, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + int arg, struct arglist_parser *argparser); + + +/* Extract an unsigned hexadecimal number from STRING, considering at + most LEN bytes and place the result in *RESULT. Returns a pointer + to the first character past the hexadecimal number. */ +static const char * +extract_hex (const char *string, size_t len, unsigned int *result) +{ + size_t i; + + *result = 0; + + for (i = 0; i < len; i++) + { + char c = string[i]; + int number; + + if (c >= 'A' && c <= 'F') + number = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + number = c - 'a' + 10; + else if (c >= '0' && c <= '9') + number = c - '0'; + else + break; + + *result <<= 4; + *result |= number; + } + + return string + i; +} + +/* Extract an unsigned octal number from STRING, considering at + most LEN bytes and place the result in *RESULT. Returns a pointer + to the first character past the octal number. */ +static const char * +extract_oct (const char *string, size_t len, unsigned int *result) +{ + size_t i; + + *result = 0; + + for (i = 0; i < len; i++) + { + char c = string[i]; + int number; + + if (c >= '0' && c <= '7') + number = c - '0'; + else + break; + + *result <<= 3; + *result |= number; + } + + return string + i; +} + +/* Extract the various quotelike constructs except for <type == token_type_string. */ +static void +extract_quotelike (token_ty *tp, int delim) +{ + char *string = extract_quotelike_pass1_utf8 (delim); + size_t len = strlen (string); + + tp->type = token_type_string; + /* Take the string without the delimiters at the start and at the end. */ + if (!(len >= 2)) + abort (); + string[len - 1] = '\0'; + tp->string = xstrdup (string + 1); + free (string); + tp->comment = add_reference (savable_comment); +} + +/* Extract the quotelike constructs with double delimiters, like + s/[SEARCH]/[REPLACE]/. This function does not eat up trailing + modifiers (left to the caller). + Return the resulting token in *tp; tp->type == token_type_regex_op. */ +static void +extract_triple_quotelike (message_list_ty *mlp, token_ty *tp, int delim, + bool interpolate) +{ + char *string; + + tp->type = token_type_regex_op; + + string = extract_quotelike_pass1_utf8 (delim); + if (interpolate) + interpolate_keywords (mlp, string, line_number); + free (string); + + if (delim == '(' || delim == '<' || delim == '{' || delim == '[') + { + /* The delimiter for the second string can be different, e.g. + s{SEARCH}{REPLACE} or s{SEARCH}/REPLACE/. See "man perlrequick". */ + delim = phase1_getc (); + while (is_whitespace (delim)) + { + /* The hash-sign is not a valid delimiter after whitespace, ergo + use phase2_getc() and not phase1_getc() now. */ + delim = phase2_getc (); + } + } + string = extract_quotelike_pass1_utf8 (delim); + if (interpolate) + interpolate_keywords (mlp, string, line_number); + free (string); +} + +/* Perform pass 3 of quotelike extraction (interpolation). + *tp is a token of type token_type_string. + This function replaces tp->string. + This function does not access tp->comment. */ +/* FIXME: Currently may writes null-bytes into the string. */ +static void +extract_quotelike_pass3 (token_ty *tp, int error_level) +{ + static char *buffer; + static int bufmax = 0; + int bufpos = 0; + const char *crs; + bool uppercase; + bool lowercase; + bool quotemeta; + +#if DEBUG_PERL + switch (tp->sub_type) + { + case string_type_verbatim: + fprintf (stderr, "Interpolating string_type_verbatim:\n"); + break; + case string_type_q: + fprintf (stderr, "Interpolating string_type_q:\n"); + break; + case string_type_qq: + fprintf (stderr, "Interpolating string_type_qq:\n"); + break; + case string_type_qr: + fprintf (stderr, "Interpolating string_type_qr:\n"); + break; + } + fprintf (stderr, "%s\n", tp->string); + if (tp->sub_type == string_type_verbatim) + fprintf (stderr, "---> %s\n", tp->string); +#endif + + if (tp->sub_type == string_type_verbatim) + return; + + /* Loop over tp->string, accumulating the expansion in buffer. */ + crs = tp->string; + uppercase = false; + lowercase = false; + quotemeta = false; + while (*crs) + { + bool backslashed; + + /* Ensure room for 7 bytes, 6 (multi-)bytes plus a leading backslash + if \Q modifier is present. */ + if (bufpos + 7 > bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + + if (tp->sub_type == string_type_q) + { + switch (*crs) + { + case '\\': + if (crs[1] == '\\') + { + crs += 2; + buffer[bufpos++] = '\\'; + break; + } + /* FALLTHROUGH */ + default: + buffer[bufpos++] = *crs++; + break; + } + continue; + } + + /* We only get here for double-quoted strings or regular expressions. + Unescape escape sequences. */ + if (*crs == '\\') + { + switch (crs[1]) + { + case 't': + crs += 2; + buffer[bufpos++] = '\t'; + continue; + case 'n': + crs += 2; + buffer[bufpos++] = '\n'; + continue; + case 'r': + crs += 2; + buffer[bufpos++] = '\r'; + continue; + case 'f': + crs += 2; + buffer[bufpos++] = '\f'; + continue; + case 'b': + crs += 2; + buffer[bufpos++] = '\b'; + continue; + case 'a': + crs += 2; + buffer[bufpos++] = '\a'; + continue; + case 'e': + crs += 2; + buffer[bufpos++] = 0x1b; + continue; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + unsigned int oct_number; + int length; + + crs = extract_oct (crs + 1, 3, &oct_number); + + /* FIXME: If one of the variables UPPERCASE or LOWERCASE is + true, the character should be converted to its uppercase + resp. lowercase equivalent. I don't know if the necessary + facilities are already included in gettext. For US-Ascii + the conversion can be already be done, however. */ + if (uppercase && oct_number >= 'a' && oct_number <= 'z') + { + oct_number = oct_number - 'a' + 'A'; + } + else if (lowercase && oct_number >= 'A' && oct_number <= 'Z') + { + oct_number = oct_number - 'A' + 'a'; + } + + + /* Yes, octal escape sequences in the range 0x100..0x1ff are + valid. */ + length = u8_uctomb ((unsigned char *) (buffer + bufpos), + oct_number, 2); + if (length > 0) + bufpos += length; + } + continue; + case 'x': + { + unsigned int hex_number = 0; + int length; + + crs += 2; + if (*crs == '{') + { + const char *end = strchr (crs, '}'); + if (end == NULL) + { + error_with_progname = false; + error (error_level, 0, _("\ +%s:%d: missing right brace on \\x{HEXNUMBER}"), real_file_name, line_number); + error_with_progname = true; + ++crs; + continue; + } + else + { + ++crs; + (void) extract_hex (crs, end - crs, &hex_number); + crs = end + 1; + } + } + else + { + crs = extract_hex (crs, 2, &hex_number); + } + + /* FIXME: If one of the variables UPPERCASE or LOWERCASE is + true, the character should be converted to its uppercase + resp. lowercase equivalent. I don't know if the necessary + facilities are already included in gettext. For US-Ascii + the conversion can be already be done, however. */ + if (uppercase && hex_number >= 'a' && hex_number <= 'z') + { + hex_number = hex_number - 'a' + 'A'; + } + else if (lowercase && hex_number >= 'A' && hex_number <= 'Z') + { + hex_number = hex_number - 'A' + 'a'; + } + + length = u8_uctomb ((unsigned char *) (buffer + bufpos), + hex_number, 6); + + if (length > 0) + bufpos += length; + } + continue; + case 'c': + /* Perl's notion of control characters. */ + crs += 2; + if (*crs) + { + int the_char = (unsigned char) *crs; + if (the_char >= 'a' && the_char <= 'z') + the_char = the_char - 'a' + 'A'; + buffer[bufpos++] = the_char ^ 0x40; + } + continue; + case 'N': + crs += 2; + if (*crs == '{') + { + const char *end = strchr (crs + 1, '}'); + if (end != NULL) + { + char *name; + unsigned int unicode; + + name = XNMALLOC (end - (crs + 1) + 1, char); + memcpy (name, crs + 1, end - (crs + 1)); + name[end - (crs + 1)] = '\0'; + + unicode = unicode_name_character (name); + if (unicode != UNINAME_INVALID) + { + /* FIXME: Convert to upper/lowercase if the + corresponding flag is set to true. */ + int length = + u8_uctomb ((unsigned char *) (buffer + bufpos), + unicode, 6); + if (length > 0) + bufpos += length; + } + + free (name); + + crs = end + 1; + } + } + continue; + } + } + + /* No escape sequence, go on. */ + if (*crs == '\\') + { + ++crs; + switch (*crs) + { + case 'E': + uppercase = false; + lowercase = false; + quotemeta = false; + ++crs; + continue; + case 'L': + uppercase = false; + lowercase = true; + ++crs; + continue; + case 'U': + uppercase = true; + lowercase = false; + ++crs; + continue; + case 'Q': + quotemeta = true; + ++crs; + continue; + case 'l': + ++crs; + if (*crs >= 'A' && *crs <= 'Z') + { + buffer[bufpos++] = *crs - 'A' + 'a'; + } + else if ((unsigned char) *crs >= 0x80) + { + error_with_progname = false; + error (error_level, 0, _("\ +%s:%d: invalid interpolation (\"\\l\") of 8bit character \"%c\""), + real_file_name, line_number, *crs); + error_with_progname = true; + } + else + { + buffer[bufpos++] = *crs; + } + ++crs; + continue; + case 'u': + ++crs; + if (*crs >= 'a' && *crs <= 'z') + { + buffer[bufpos++] = *crs - 'a' + 'A'; + } + else if ((unsigned char) *crs >= 0x80) + { + error_with_progname = false; + error (error_level, 0, _("\ +%s:%d: invalid interpolation (\"\\u\") of 8bit character \"%c\""), + real_file_name, line_number, *crs); + error_with_progname = true; + } + else + { + buffer[bufpos++] = *crs; + } + ++crs; + continue; + case '\\': + buffer[bufpos++] = *crs; + ++crs; + continue; + default: + backslashed = true; + break; + } + } + else + backslashed = false; + + if (quotemeta + && !((*crs >= 'A' && *crs <= 'Z') || (*crs >= 'A' && *crs <= 'z') + || (*crs >= '0' && *crs <= '9') || *crs == '_')) + { + buffer[bufpos++] = '\\'; + backslashed = true; + } + + if (!backslashed && !extract_all && (*crs == '$' || *crs == '@')) + { + error_with_progname = false; + error (error_level, 0, _("\ +%s:%d: invalid variable interpolation at \"%c\""), + real_file_name, line_number, *crs); + error_with_progname = true; + ++crs; + } + else if (lowercase) + { + if (*crs >= 'A' && *crs <= 'Z') + buffer[bufpos++] = *crs - 'A' + 'a'; + else if ((unsigned char) *crs >= 0x80) + { + error_with_progname = false; + error (error_level, 0, _("\ +%s:%d: invalid interpolation (\"\\L\") of 8bit character \"%c\""), + real_file_name, line_number, *crs); + error_with_progname = true; + buffer[bufpos++] = *crs; + } + else + buffer[bufpos++] = *crs; + ++crs; + } + else if (uppercase) + { + if (*crs >= 'a' && *crs <= 'z') + buffer[bufpos++] = *crs - 'a' + 'A'; + else if ((unsigned char) *crs >= 0x80) + { + error_with_progname = false; + error (error_level, 0, _("\ +%s:%d: invalid interpolation (\"\\U\") of 8bit character \"%c\""), + real_file_name, line_number, *crs); + error_with_progname = true; + buffer[bufpos++] = *crs; + } + else + buffer[bufpos++] = *crs; + ++crs; + } + else + { + buffer[bufpos++] = *crs++; + } + } + + /* Ensure room for 1 more byte. */ + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + + buffer[bufpos++] = '\0'; + +#if DEBUG_PERL + fprintf (stderr, "---> %s\n", buffer); +#endif + + /* Replace tp->string. */ + free (tp->string); + tp->string = xstrdup (buffer); +} + +/* Parse a variable. This is done in several steps: + 1) Consume all leading occurencies of '$', '@', '%', and '*'. + 2) Determine the name of the variable from the following input. + 3) Parse possible following hash keys or array indexes. + */ +static void +extract_variable (message_list_ty *mlp, token_ty *tp, int first) +{ + static char *buffer; + static int bufmax = 0; + int bufpos = 0; + int c = first; + size_t varbody_length = 0; + bool maybe_hash_deref = false; + bool maybe_hash_value = false; + + tp->type = token_type_variable; + +#if DEBUG_PERL + fprintf (stderr, "%s:%d: extracting variable type '%c'\n", + real_file_name, line_number, first); +#endif + + /* + * 1) Consume dollars and so on (not euros ...). Unconditionally + * accepting the hash sign (#) will maybe lead to inaccurate + * results. FIXME! + */ + while (c == '$' || c == '*' || c == '#' || c == '@' || c == '%') + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase1_getc (); + } + + if (c == EOF) + { + tp->type = token_type_eof; + return; + } + + /* Hash references are treated in a special way, when looking for + our keywords. */ + if (buffer[0] == '$') + { + if (bufpos == 1) + maybe_hash_value = true; + else if (bufpos == 2 && buffer[1] == '$') + { + if (!(c == '{' + || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '_' || c == ':' || c == '\'' || c >= 0x80)) + { + /* Special variable $$ for pid. */ + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = '\0'; + tp->string = xstrdup (buffer); +#if DEBUG_PERL + fprintf (stderr, "%s:%d: is PID ($$)\n", + real_file_name, line_number); +#endif + + phase1_ungetc (c); + return; + } + + maybe_hash_deref = true; + bufpos = 1; + } + } + + /* + * 2) Get the name of the variable. The first character is practically + * arbitrary. Punctuation and numbers automagically put a variable + * in the global namespace but that subtle difference is not interesting + * for us. + */ + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + if (c == '{') + { + /* Yuck, we cannot accept ${gettext} as a keyword... Except for + * debugging purposes it is also harmless, that we suppress the + * real name of the variable. + */ +#if DEBUG_PERL + fprintf (stderr, "%s:%d: braced {variable_name}\n", + real_file_name, line_number); +#endif + + if (extract_balanced (mlp, token_type_rbrace, true, false, + null_context, null_context_list_iterator, + 1, arglist_parser_alloc (mlp, NULL))) + { + tp->type = token_type_eof; + return; + } + buffer[bufpos++] = c; + } + else + { + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '_' || c == ':' || c == '\'' || c >= 0x80) + { + ++varbody_length; + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase1_getc (); + } + phase1_ungetc (c); + } + + /* Probably some strange Perl variable like $`. */ + if (varbody_length == 0) + { + c = phase1_getc (); + if (c == EOF || is_whitespace (c)) + phase1_ungetc (c); /* Loser. */ + else + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + } + } + + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = '\0'; + + tp->string = xstrdup (buffer); + +#if DEBUG_PERL + fprintf (stderr, "%s:%d: complete variable name: %s\n", + real_file_name, line_number, tp->string); +#endif + + /* + * 3) If the following looks strange to you, this is valid Perl syntax: + * + * $var = $$hashref # We can place a + * # comment here and then ... + * {key_into_hashref}; + * + * POD sections are not allowed but we leave complaints about + * that to the compiler/interpreter. + */ + /* We only extract strings from the first hash key (if present). */ + + if (maybe_hash_deref || maybe_hash_value) + { + bool is_dereference = false; + int c; + + do + c = phase2_getc (); + while (is_whitespace (c)); + + if (c == '-') + { + int c2 = phase1_getc (); + + if (c2 == '>') + { + is_dereference = true; + + do + c = phase2_getc (); + while (is_whitespace (c)); + } + else if (c2 != '\n') + { + /* Discarding the newline is harmless here. The only + special character recognized after a minus is greater-than + for dereference. However, the sequence "-\n>" that we + treat incorrectly here, is a syntax error. */ + phase1_ungetc (c2); + } + } + + if (maybe_hash_value && is_dereference) + { + tp->type = token_type_object; +#if DEBUG_PERL + fprintf (stderr, "%s:%d: first keys preceded by \"->\"\n", + real_file_name, line_number); +#endif + } + else if (maybe_hash_value) + { + /* Fake it into a hash. */ + tp->string[0] = '%'; + } + + /* Do NOT change that into else if (see above). */ + if ((maybe_hash_value || maybe_hash_deref) && c == '{') + { + void *keyword_value; + +#if DEBUG_PERL + fprintf (stderr, "%s:%d: first keys preceded by '{'\n", + real_file_name, line_number); +#endif + + if (hash_find_entry (&keywords, tp->string, strlen (tp->string), + &keyword_value) == 0) + { + /* TODO: Shouldn't we use the shapes of the keyword, instead + of hardwiring argnum1 = 1 ? + const struct callshapes *shapes = + (const struct callshapes *) keyword_value; + */ + struct callshapes shapes; + shapes.keyword = tp->string; /* XXX storage duration? */ + shapes.keyword_len = strlen (tp->string); + shapes.nshapes = 1; + shapes.shapes[0].argnum1 = 1; + shapes.shapes[0].argnum2 = 0; + shapes.shapes[0].argnumc = 0; + shapes.shapes[0].argnum1_glib_context = false; + shapes.shapes[0].argnum2_glib_context = false; + shapes.shapes[0].argtotal = 0; + string_list_init (&shapes.shapes[0].xcomments); + + { + /* Extract a possible string from the key. Before proceeding + we check whether the open curly is followed by a symbol and + then by a right curly. */ + flag_context_list_iterator_ty context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + tp->string, strlen (tp->string))); + token_ty *t1 = x_perl_lex (mlp); + +#if DEBUG_PERL + fprintf (stderr, "%s:%d: extracting string key\n", + real_file_name, line_number); +#endif + + if (t1->type == token_type_symbol + || t1->type == token_type_named_op) + { + token_ty *t2 = x_perl_lex (mlp); + if (t2->type == token_type_rbrace) + { + flag_context_ty context; + lex_pos_ty pos; + + context = + inherited_context (null_context, + flag_context_list_iterator_advance ( + &context_iter)); + + pos.line_number = line_number; + pos.file_name = logical_file_name; + + xgettext_current_source_encoding = po_charset_utf8; + remember_a_message (mlp, NULL, xstrdup (t1->string), + context, &pos, NULL, savable_comment); + xgettext_current_source_encoding = xgettext_global_source_encoding; + free_token (t2); + free_token (t1); + } + else + { + x_perl_unlex (t2); + } + } + else + { + x_perl_unlex (t1); + if (extract_balanced (mlp, token_type_rbrace, true, false, + null_context, context_iter, + 1, arglist_parser_alloc (mlp, &shapes))) + return; + } + } + } + else + { + phase2_ungetc (c); + } + } + else + { + phase2_ungetc (c); + } + } + + /* Now consume "->", "[...]", and "{...}". */ + for (;;) + { + int c = phase2_getc (); + int c2; + + switch (c) + { + case '{': +#if DEBUG_PERL + fprintf (stderr, "%s:%d: extracting balanced '{' after varname\n", + real_file_name, line_number); +#endif + extract_balanced (mlp, token_type_rbrace, true, false, + null_context, null_context_list_iterator, + 1, arglist_parser_alloc (mlp, NULL)); + break; + + case '[': +#if DEBUG_PERL + fprintf (stderr, "%s:%d: extracting balanced '[' after varname\n", + real_file_name, line_number); +#endif + extract_balanced (mlp, token_type_rbracket, true, false, + null_context, null_context_list_iterator, + 1, arglist_parser_alloc (mlp, NULL)); + break; + + case '-': + c2 = phase1_getc (); + if (c2 == '>') + { +#if DEBUG_PERL + fprintf (stderr, "%s:%d: another \"->\" after varname\n", + real_file_name, line_number); +#endif + break; + } + else if (c2 != '\n') + { + /* Discarding the newline is harmless here. The only + special character recognized after a minus is greater-than + for dereference. However, the sequence "-\n>" that we + treat incorrectly here, is a syntax error. */ + phase1_ungetc (c2); + } + /* FALLTHROUGH */ + + default: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: variable finished\n", + real_file_name, line_number); +#endif + phase2_ungetc (c); + return; + } + } +} + +/* Actually a simplified version of extract_variable(). It searches for + variables inside a double-quoted string that may interpolate to + some keyword hash (reference). The string is UTF-8 encoded. */ +static void +interpolate_keywords (message_list_ty *mlp, const char *string, int lineno) +{ + static char *buffer; + static int bufmax = 0; + int bufpos = 0; + flag_context_ty context; + int c; + bool maybe_hash_deref = false; + enum parser_state + { + initial, + one_dollar, + two_dollars, + identifier, + minus, + wait_lbrace, + wait_quote, + dquote, + squote, + barekey, + wait_rbrace + } state; + token_ty token; + + lex_pos_ty pos; + + /* States are: + * + * initial: initial + * one_dollar: dollar sign seen in state INITIAL + * two_dollars: another dollar-sign has been seen in state ONE_DOLLAR + * identifier: a valid identifier character has been seen in state + * ONE_DOLLAR or TWO_DOLLARS + * minus: a minus-sign has been seen in state IDENTIFIER + * wait_lbrace: a greater-than has been seen in state MINUS + * wait_quote: a left brace has been seen in state IDENTIFIER or in + * state WAIT_LBRACE + * dquote: a double-quote has been seen in state WAIT_QUOTE + * squote: a single-quote has been seen in state WAIT_QUOTE + * barekey: an bareword character has been seen in state WAIT_QUOTE + * wait_rbrace: closing quote has been seen in state DQUOTE or SQUOTE + * + * In the states initial...identifier the context is null_context; in the + * states minus...wait_rbrace the context is the one suitable for the first + * argument of the last seen identifier. + */ + state = initial; + context = null_context; + + token.type = token_type_string; + token.sub_type = string_type_qq; + token.line_number = line_number; + /* No need for token.comment = add_reference (savable_comment); here. + We can let token.comment uninitialized here, and use savable_comment + directly, because this function only parses the given string and does + not call phase2_getc. */ + pos.file_name = logical_file_name; + pos.line_number = lineno; + + while ((c = (unsigned char) *string++) != '\0') + { + void *keyword_value; + + if (state == initial) + bufpos = 0; + + if (c == '\n') + lineno++; + + if (bufpos + 1 >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + + switch (state) + { + case initial: + switch (c) + { + case '\\': + c = (unsigned char) *string++; + if (c == '\0') + return; + break; + case '$': + buffer[bufpos++] = '$'; + maybe_hash_deref = false; + state = one_dollar; + break; + default: + break; + } + break; + case one_dollar: + switch (c) + { + case '$': + /* + * This is enough to make us believe later that we dereference + * a hash reference. + */ + maybe_hash_deref = true; + state = two_dollars; + break; + default: + if (c == '_' || c == ':' || c == '\'' || c >= 0x80 + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9')) + { + buffer[bufpos++] = c; + state = identifier; + } + else + state = initial; + break; + } + break; + case two_dollars: + if (c == '_' || c == ':' || c == '\'' || c >= 0x80 + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9')) + { + buffer[bufpos++] = c; + state = identifier; + } + else + state = initial; + break; + case identifier: + switch (c) + { + case '-': + if (hash_find_entry (&keywords, buffer, bufpos, &keyword_value) + == 0) + { + flag_context_list_iterator_ty context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + buffer, bufpos)); + context = + inherited_context (null_context, + flag_context_list_iterator_advance ( + &context_iter)); + state = minus; + } + else + state = initial; + break; + case '{': + if (!maybe_hash_deref) + buffer[0] = '%'; + if (hash_find_entry (&keywords, buffer, bufpos, &keyword_value) + == 0) + { + flag_context_list_iterator_ty context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + buffer, bufpos)); + context = + inherited_context (null_context, + flag_context_list_iterator_advance ( + &context_iter)); + state = wait_quote; + } + else + state = initial; + break; + default: + if (c == '_' || c == ':' || c == '\'' || c >= 0x80 + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9')) + { + buffer[bufpos++] = c; + } + else + state = initial; + break; + } + break; + case minus: + switch (c) + { + case '>': + state = wait_lbrace; + break; + default: + context = null_context; + state = initial; + break; + } + break; + case wait_lbrace: + switch (c) + { + case '{': + state = wait_quote; + break; + default: + context = null_context; + state = initial; + break; + } + break; + case wait_quote: + switch (c) + { + case_whitespace: + break; + case '\'': + pos.line_number = lineno; + bufpos = 0; + state = squote; + break; + case '"': + pos.line_number = lineno; + bufpos = 0; + state = dquote; + break; + default: + if (c == '_' || (c >= '0' && c <= '9') || c >= 0x80 + || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + { + pos.line_number = lineno; + bufpos = 0; + buffer[bufpos++] = c; + state = barekey; + } + else + { + context = null_context; + state = initial; + } + break; + } + break; + case dquote: + switch (c) + { + case '"': + /* The resulting string has to be interpolated twice. */ + buffer[bufpos] = '\0'; + token.string = xstrdup (buffer); + extract_quotelike_pass3 (&token, EXIT_FAILURE); + /* The string can only shrink with interpolation (because + we ignore \Q). */ + if (!(strlen (token.string) <= bufpos)) + abort (); + strcpy (buffer, token.string); + free (token.string); + state = wait_rbrace; + break; + case '\\': + if (string[0] == '\"') + { + buffer[bufpos++] = string++[0]; + } + else if (string[0]) + { + buffer[bufpos++] = '\\'; + buffer[bufpos++] = string++[0]; + } + else + { + context = null_context; + state = initial; + } + break; + default: + buffer[bufpos++] = c; + break; + } + break; + case squote: + switch (c) + { + case '\'': + state = wait_rbrace; + break; + case '\\': + if (string[0] == '\'') + { + buffer[bufpos++] = string++[0]; + } + else if (string[0]) + { + buffer[bufpos++] = '\\'; + buffer[bufpos++] = string++[0]; + } + else + { + context = null_context; + state = initial; + } + break; + default: + buffer[bufpos++] = c; + break; + } + break; + case barekey: + if (c == '_' || (c >= '0' && c <= '9') || c >= 0x80 + || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + { + buffer[bufpos++] = c; + break; + } + else if (is_whitespace (c)) + { + state = wait_rbrace; + break; + } + else if (c != '}') + { + context = null_context; + state = initial; + break; + } + /* Must be right brace. */ + /* FALLTHROUGH */ + case wait_rbrace: + switch (c) + { + case_whitespace: + break; + case '}': + buffer[bufpos] = '\0'; + token.string = xstrdup (buffer); + extract_quotelike_pass3 (&token, EXIT_FAILURE); + xgettext_current_source_encoding = po_charset_utf8; + remember_a_message (mlp, NULL, token.string, context, &pos, + NULL, savable_comment); + xgettext_current_source_encoding = xgettext_global_source_encoding; + /* FALLTHROUGH */ + default: + context = null_context; + state = initial; + break; + } + break; + } + } +} + +/* There is an ambiguity about '/' and '?': They can start an operator + (division operator '/' or '/=' or the conditional operator '?'), or they can + start a regular expression. The distinction is important because inside + regular expressions, '#' loses its special meaning. This function helps + making the decision (a heuristic). See the documentation for details. */ +static bool +prefer_regexp_over_division (token_type_ty type) +{ + bool retval = true; + + switch (type) + { + case token_type_eof: + retval = true; + break; + case token_type_lparen: + retval = true; + break; + case token_type_rparen: + retval = false; + break; + case token_type_comma: + retval = true; + break; + case token_type_fat_comma: + retval = true; + break; + case token_type_dereference: + retval = true; + break; + case token_type_semicolon: + retval = true; + break; + case token_type_lbrace: + retval = true; + break; + case token_type_rbrace: + retval = false; + break; + case token_type_lbracket: + retval = true; + break; + case token_type_rbracket: + retval = false; + break; + case token_type_string: + retval = false; + break; + case token_type_number: + retval = false; + break; + case token_type_named_op: + retval = true; + break; + case token_type_variable: + retval = false; + break; + case token_type_object: + retval = false; + break; + case token_type_symbol: + case token_type_keyword_symbol: + retval = true; + break; + case token_type_regex_op: + retval = false; + break; + case token_type_dot: + retval = true; + break; + case token_type_other: + retval = true; + break; + } + +#if DEBUG_PERL + token_ty ty; + ty.type = type; + fprintf (stderr, "Prefer regexp over division after %s: %s\n", + token2string (&ty), retval ? "true" : "false"); +#endif + + return retval; +} + +/* Last token type seen in the stream. Important for the interpretation + of slash and question mark. */ +static token_type_ty last_token_type; + +/* Combine characters into tokens. Discard whitespace. */ + +static void +x_perl_prelex (message_list_ty *mlp, token_ty *tp) +{ + static char *buffer; + static int bufmax; + int bufpos; + int c; + + for (;;) + { + c = phase2_getc (); + tp->line_number = line_number; + tp->last_type = last_token_type; + + switch (c) + { + case EOF: + tp->type = token_type_eof; + return; + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* FALLTHROUGH */ + case '\t': + case ' ': + /* Ignore whitespace. */ + continue; + + case '%': + case '@': + case '*': + case '$': + if (!extract_all) + { + extract_variable (mlp, tp, c); + return; + } + break; + } + + last_non_comment_line = tp->line_number; + + switch (c) + { + case '.': + { + int c2 = phase1_getc (); + phase1_ungetc (c2); + if (c2 == '.') + { + tp->type = token_type_other; + return; + } + else if (!(c2 >= '0' && c2 <= '9')) + { + tp->type = token_type_dot; + return; + } + } + /* FALLTHROUGH */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* Symbol, or part of a number. */ + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase1_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + + default: + phase1_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + + if (strcmp (buffer, "__END__") == 0 + || strcmp (buffer, "__DATA__") == 0) + { + end_of_file = true; + tp->type = token_type_eof; + return; + } + else if (strcmp (buffer, "and") == 0 + || strcmp (buffer, "cmp") == 0 + || strcmp (buffer, "eq") == 0 + || strcmp (buffer, "if") == 0 + || strcmp (buffer, "ge") == 0 + || strcmp (buffer, "gt") == 0 + || strcmp (buffer, "le") == 0 + || strcmp (buffer, "lt") == 0 + || strcmp (buffer, "ne") == 0 + || strcmp (buffer, "not") == 0 + || strcmp (buffer, "or") == 0 + || strcmp (buffer, "unless") == 0 + || strcmp (buffer, "while") == 0 + || strcmp (buffer, "xor") == 0) + { + tp->type = token_type_named_op; + tp->string = xstrdup (buffer); + return; + } + else if (strcmp (buffer, "s") == 0 + || strcmp (buffer, "y") == 0 + || strcmp (buffer, "tr") == 0) + { + int delim = phase1_getc (); + + while (is_whitespace (delim)) + delim = phase2_getc (); + + if (delim == EOF) + { + tp->type = token_type_eof; + return; + } + if ((delim >= '0' && delim <= '9') + || (delim >= 'A' && delim <= 'Z') + || (delim >= 'a' && delim <= 'z')) + { + /* False positive. */ + phase2_ungetc (delim); + tp->type = token_type_symbol; + tp->sub_type = symbol_type_none; + tp->string = xstrdup (buffer); + return; + } + extract_triple_quotelike (mlp, tp, delim, + buffer[0] == 's' && delim != '\''); + + /* Eat the following modifiers. */ + do + c = phase1_getc (); + while (c >= 'a' && c <= 'z'); + phase1_ungetc (c); + return; + } + else if (strcmp (buffer, "m") == 0) + { + int delim = phase1_getc (); + + while (is_whitespace (delim)) + delim = phase2_getc (); + + if (delim == EOF) + { + tp->type = token_type_eof; + return; + } + if ((delim >= '0' && delim <= '9') + || (delim >= 'A' && delim <= 'Z') + || (delim >= 'a' && delim <= 'z')) + { + /* False positive. */ + phase2_ungetc (delim); + tp->type = token_type_symbol; + tp->sub_type = symbol_type_none; + tp->string = xstrdup (buffer); + return; + } + extract_quotelike (tp, delim); + if (delim != '\'') + interpolate_keywords (mlp, tp->string, line_number); + free (tp->string); + drop_reference (tp->comment); + tp->type = token_type_regex_op; + + /* Eat the following modifiers. */ + do + c = phase1_getc (); + while (c >= 'a' && c <= 'z'); + phase1_ungetc (c); + return; + } + else if (strcmp (buffer, "qq") == 0 + || strcmp (buffer, "q") == 0 + || strcmp (buffer, "qx") == 0 + || strcmp (buffer, "qw") == 0 + || strcmp (buffer, "qr") == 0) + { + /* The qw (...) construct is not really a string but we + can treat in the same manner and then pretend it is + a symbol. Rationale: Saying "qw (foo bar)" is the + same as "my @list = ('foo', 'bar'); @list;". */ + + int delim = phase1_getc (); + + while (is_whitespace (delim)) + delim = phase2_getc (); + + if (delim == EOF) + { + tp->type = token_type_eof; + return; + } + + if ((delim >= '0' && delim <= '9') + || (delim >= 'A' && delim <= 'Z') + || (delim >= 'a' && delim <= 'z')) + { + /* False positive. */ + phase2_ungetc (delim); + tp->type = token_type_symbol; + tp->sub_type = symbol_type_none; + tp->string = xstrdup (buffer); + return; + } + + extract_quotelike (tp, delim); + + switch (buffer[1]) + { + case 'q': + case 'x': + tp->type = token_type_string; + tp->sub_type = string_type_qq; + interpolate_keywords (mlp, tp->string, line_number); + break; + case 'r': + drop_reference (tp->comment); + tp->type = token_type_regex_op; + break; + case 'w': + drop_reference (tp->comment); + tp->type = token_type_symbol; + tp->sub_type = symbol_type_none; + break; + case '\0': + tp->type = token_type_string; + tp->sub_type = string_type_q; + break; + default: + abort (); + } + return; + } + else if ((buffer[0] >= '0' && buffer[0] <= '9') || buffer[0] == '.') + { + tp->type = token_type_number; + return; + } + tp->type = token_type_symbol; + tp->sub_type = (strcmp (buffer, "sub") == 0 + ? symbol_type_sub + : symbol_type_none); + tp->string = xstrdup (buffer); + return; + + case '"': + extract_quotelike (tp, c); + tp->sub_type = string_type_qq; + interpolate_keywords (mlp, tp->string, line_number); + return; + + case '`': + extract_quotelike (tp, c); + tp->sub_type = string_type_qq; + interpolate_keywords (mlp, tp->string, line_number); + return; + + case '\'': + extract_quotelike (tp, c); + tp->sub_type = string_type_q; + return; + + case '(': + c = phase2_getc (); + if (c == ')') + /* Ignore empty list. */ + continue; + else + phase2_ungetc (c); + tp->type = token_type_lparen; + return; + + case ')': + tp->type = token_type_rparen; + return; + + case '{': + tp->type = token_type_lbrace; + return; + + case '}': + tp->type = token_type_rbrace; + return; + + case '[': + tp->type = token_type_lbracket; + return; + + case ']': + tp->type = token_type_rbracket; + return; + + case ';': + tp->type = token_type_semicolon; + return; + + case ',': + tp->type = token_type_comma; + return; + + case '=': + /* Check for fat comma. */ + c = phase1_getc (); + if (c == '>') + { + tp->type = token_type_fat_comma; + return; + } + else if (linepos == 2 + && (last_token_type == token_type_semicolon + || last_token_type == token_type_rbrace) + && ((c >= 'A' && c <='Z') + || (c >= 'a' && c <= 'z'))) + { +#if DEBUG_PERL + fprintf (stderr, "%s:%d: start pod section\n", + real_file_name, line_number); +#endif + skip_pod (); +#if DEBUG_PERL + fprintf (stderr, "%s:%d: end pod section\n", + real_file_name, line_number); +#endif + continue; + } + phase1_ungetc (c); + tp->type = token_type_other; + return; + + case '<': + /* Check for <string); + free (tp->string); + tp->string = string; + tp->type = token_type_string; + tp->sub_type = string_type_verbatim; + tp->line_number = line_number + 1; + return; + } + else if (c == '"') + { + char *string; + extract_quotelike (tp, c); + string = get_here_document (tp->string); + free (tp->string); + tp->string = string; + tp->type = token_type_string; + tp->sub_type = string_type_qq; + tp->line_number = line_number + 1; + interpolate_keywords (mlp, tp->string, tp->line_number); + return; + } + else if ((c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || c == '_') + { + bufpos = 0; + while ((c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '_' || c >= 0x80) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase1_getc (); + } + if (c == EOF) + { + tp->type = token_type_eof; + return; + } + else + { + char *string; + phase1_ungetc (c); + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = '\0'; + string = get_here_document (buffer); + tp->string = string; + tp->type = token_type_string; + tp->sub_type = string_type_qq; + tp->comment = add_reference (savable_comment); + tp->line_number = line_number + 1; + interpolate_keywords (mlp, tp->string, tp->line_number); + return; + } + } + else + { + tp->type = token_type_other; + return; + } + } + else + { + phase1_ungetc (c); + tp->type = token_type_other; + } + return; /* End of case '>'. */ + + case '-': + /* Check for dereferencing operator. */ + c = phase1_getc (); + if (c == '>') + { + tp->type = token_type_dereference; + return; + } + else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) + { + /* One of the -X (filetest) functions. We play safe + and accept all alphabetical characters here. */ + tp->type = token_type_other; + return; + } + phase1_ungetc (c); + tp->type = token_type_other; + return; + + case '/': + case '?': + if (prefer_regexp_over_division (tp->last_type)) + { + extract_quotelike (tp, c); + interpolate_keywords (mlp, tp->string, line_number); + free (tp->string); + drop_reference (tp->comment); + tp->type = token_type_regex_op; + /* Eat the following modifiers. */ + do + c = phase1_getc (); + while (c >= 'a' && c <= 'z'); + phase1_ungetc (c); + return; + } + /* Recognize operator '//'. */ + if (c == '/') + { + c = phase1_getc (); + if (c != '/') + phase1_ungetc (c); + } + /* FALLTHROUGH */ + + default: + /* We could carefully recognize each of the 2 and 3 character + operators, but it is not necessary, except for the '//' operator, + as we only need to recognize gettext invocations. Don't + bother. */ + tp->type = token_type_other; + return; + } + } +} + + +/* A token stack used as a lookahead buffer. */ + +typedef struct token_stack_ty token_stack_ty; +struct token_stack_ty +{ + token_ty **items; + size_t nitems; + size_t nitems_max; +}; + +static struct token_stack_ty token_stack; + +#if DEBUG_PERL +/* Dumps all resources allocated by stack STACK. */ +static int +token_stack_dump (token_stack_ty *stack) +{ + size_t i; + + fprintf (stderr, "BEGIN STACK DUMP\n"); + for (i = 0; i < stack->nitems; i++) + { + token_ty *token = stack->items[i]; + fprintf (stderr, " [%s]\n", token2string (token)); + switch (token->type) + { + case token_type_named_op: + case token_type_string: + case token_type_symbol: + case token_type_variable: + fprintf (stderr, " string: %s\n", token->string); + break; + case token_type_object: + fprintf (stderr, " string: %s->\n", token->string); + default: + break; + } + } + fprintf (stderr, "END STACK DUMP\n"); + return 0; +} +#endif + +/* Pushes the token TOKEN onto the stack STACK. */ +static inline void +token_stack_push (token_stack_ty *stack, token_ty *token) +{ + if (stack->nitems >= stack->nitems_max) + { + size_t nbytes; + + stack->nitems_max = 2 * stack->nitems_max + 4; + nbytes = stack->nitems_max * sizeof (token_ty *); + stack->items = xrealloc (stack->items, nbytes); + } + stack->items[stack->nitems++] = token; +} + +/* Pops the most recently pushed token from the stack STACK and returns it. + Returns NULL if the stack is empty. */ +static inline token_ty * +token_stack_pop (token_stack_ty *stack) +{ + if (stack->nitems > 0) + return stack->items[--(stack->nitems)]; + else + return NULL; +} + +/* Return the top of the stack without removing it from the stack, or + NULL if the stack is empty. */ +static inline token_ty * +token_stack_peek (const token_stack_ty *stack) +{ + if (stack->nitems > 0) + return stack->items[stack->nitems - 1]; + else + return NULL; +} + +/* Frees all resources allocated by stack STACK. */ +static inline void +token_stack_free (token_stack_ty *stack) +{ + size_t i; + + for (i = 0; i < stack->nitems; i++) + free_token (stack->items[i]); + free (stack->items); +} + + +static token_ty * +x_perl_lex (message_list_ty *mlp) +{ +#if DEBUG_PERL + int dummy = token_stack_dump (&token_stack); +#endif + token_ty *tp = token_stack_pop (&token_stack); + + if (!tp) + { + tp = XMALLOC (token_ty); + x_perl_prelex (mlp, tp); + tp->last_type = last_token_type; + last_token_type = tp->type; + +#if DEBUG_PERL + fprintf (stderr, "%s:%d: x_perl_prelex returned %s\n", + real_file_name, line_number, token2string (tp)); +#endif + + /* The interpretation of a slash or question mark after a function call + depends on the prototype of that function. If the function expects + at least one argument, a regular expression is preferred, otherwise + an operator. With our limited means, we can only guess here. If + the function is a builtin that takes no arguments, we prefer an + operator by silently turning the last symbol into a variable instead + of a symbol. + + Method calls without parentheses are not ambiguous. After them, an + operator must follow. Due to some ideosyncrasies in this parser + they are treated in two different manners. If the call is + chained ($foo->bar->baz) the token left of the symbol is a + dereference operator. If it is not chained ($foo->bar) the + dereference operator is consumed with the extracted variable. The + latter case is handled below. */ + if (tp->type == token_type_symbol) + { + if (tp->last_type == token_type_dereference) + { + /* Class method call or chained method call (with at least + two arrow operators). */ + last_token_type = token_type_variable; + } + else if (tp->last_type == token_type_object) + { + /* Instance method, not chained. */ + last_token_type = token_type_variable; + } + else if (strcmp (tp->string, "wantarray") == 0 + || strcmp (tp->string, "fork") == 0 + || strcmp (tp->string, "getlogin") == 0 + || strcmp (tp->string, "getppid") == 0 + || strcmp (tp->string, "getpwent") == 0 + || strcmp (tp->string, "getgrent") == 0 + || strcmp (tp->string, "gethostent") == 0 + || strcmp (tp->string, "getnetent") == 0 + || strcmp (tp->string, "getprotoent") == 0 + || strcmp (tp->string, "getservent") == 0 + || strcmp (tp->string, "setpwent") == 0 + || strcmp (tp->string, "setgrent") == 0 + || strcmp (tp->string, "endpwent") == 0 + || strcmp (tp->string, "endgrent") == 0 + || strcmp (tp->string, "endhostent") == 0 + || strcmp (tp->string, "endnetent") == 0 + || strcmp (tp->string, "endprotoent") == 0 + || strcmp (tp->string, "endservent") == 0 + || strcmp (tp->string, "time") == 0 + || strcmp (tp->string, "times") == 0 + || strcmp (tp->string, "wait") == 0 + || strcmp (tp->string, "wantarray") == 0) + { + /* A Perl built-in function that does not accept arguments. */ + last_token_type = token_type_variable; + } + } + } +#if DEBUG_PERL + else + { + fprintf (stderr, "%s:%d: %s recycled from stack\n", + real_file_name, line_number, token2string (tp)); + } +#endif + + /* A symbol followed by a fat comma is really a single-quoted string. + Function definitions or forward declarations also need a special + handling because the dollars and at signs inside the parentheses + must not be interpreted as the beginning of a variable ')'. */ + if (tp->type == token_type_symbol || tp->type == token_type_named_op) + { + token_ty *next = token_stack_peek (&token_stack); + + if (!next) + { +#if DEBUG_PERL + fprintf (stderr, "%s:%d: pre-fetching next token\n", + real_file_name, line_number); +#endif + next = x_perl_lex (mlp); + x_perl_unlex (next); +#if DEBUG_PERL + fprintf (stderr, "%s:%d: unshifted next token\n", + real_file_name, line_number); +#endif + } + +#if DEBUG_PERL + fprintf (stderr, "%s:%d: next token is %s\n", + real_file_name, line_number, token2string (next)); +#endif + + if (next->type == token_type_fat_comma) + { + tp->type = token_type_string; + tp->sub_type = string_type_q; + tp->comment = add_reference (savable_comment); +#if DEBUG_PERL + fprintf (stderr, + "%s:%d: token %s mutated to token_type_string\n", + real_file_name, line_number, token2string (tp)); +#endif + } + else if (tp->type == token_type_symbol && tp->sub_type == symbol_type_sub + && next->type == token_type_symbol) + { + /* Start of a function declaration or definition. Mark this + symbol as a function name, so that we can later eat up + possible prototype information. */ +#if DEBUG_PERL + fprintf (stderr, "%s:%d: subroutine declaration/definition '%s'\n", + real_file_name, line_number, next->string); +#endif + next->sub_type = symbol_type_function; + } + else if (tp->type == token_type_symbol + && (tp->sub_type == symbol_type_sub + || tp->sub_type == symbol_type_function) + && next->type == token_type_lparen) + { + /* For simplicity we simply consume everything up to the + closing parenthesis. Actually only a limited set of + characters is allowed inside parentheses but we leave + complaints to the interpreter and are prepared for + future extensions to the Perl syntax. */ + int c; + +#if DEBUG_PERL + fprintf (stderr, "%s:%d: consuming prototype information\n", + real_file_name, line_number); +#endif + + do + { + c = phase1_getc (); +#if DEBUG_PERL + fprintf (stderr, " consuming character '%c'\n", c); +#endif + } + while (c != EOF && c != ')'); + phase1_ungetc (c); + } + } + + return tp; +} + +static void +x_perl_unlex (token_ty *tp) +{ + token_stack_push (&token_stack, tp); +} + + +/* ========================= Extracting strings. ========================== */ + +/* Assuming TP is a string token, this function accumulates all subsequent + . string2 . string3 ... to the string. (String concatenation.) */ + +static char * +collect_message (message_list_ty *mlp, token_ty *tp, int error_level) +{ + char *string; + size_t len; + + extract_quotelike_pass3 (tp, error_level); + string = xstrdup (tp->string); + len = strlen (tp->string) + 1; + + for (;;) + { + int c; + + do + c = phase2_getc (); + while (is_whitespace (c)); + + if (c != '.') + { + phase2_ungetc (c); + return string; + } + + do + c = phase2_getc (); + while (is_whitespace (c)); + + phase2_ungetc (c); + + if (c == '"' || c == '\'' || c == '`' + || ((c == '/' || c == '?') + && prefer_regexp_over_division (tp->last_type)) + || c == 'q') + { + token_ty *qstring = x_perl_lex (mlp); + if (qstring->type != token_type_string) + { + /* assert (qstring->type == token_type_symbol) */ + x_perl_unlex (qstring); + return string; + } + + extract_quotelike_pass3 (qstring, error_level); + len += strlen (qstring->string); + string = xrealloc (string, len); + strcat (string, qstring->string); + free_token (qstring); + } + } +} + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid Perl program, and leave the complaints about + the grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. + + In Perl, parentheses around function arguments can be omitted. + + The general rules are: + 1) Functions declared with a prototype take exactly the specified number + of arguments. + sub one_arg ($) { ... } + sub two_args ($$) { ... } + 2) When a function name is immediately followed by an opening parenthesis, + the argument list ends at the corresponding closing parenthesis. + + If rule 1 and rule 2 are contradictory, i.e. when the program calls a + function with an explicit argument list and the wrong number of arguments, + the program is invalid: + sub two_args ($$) { ... } + foo two_args (x), y - invalid due to rules 1 and 2 + + Ambiguities are resolved as follows: + 3) Some built-ins, such as 'abs', 'sqrt', 'sin', 'cos', ..., and functions + declared with a prototype of exactly one argument take exactly one + argument: + foo sin x, y ==> foo (sin (x), y) + sub one_arg ($) { ... } + foo one_arg x, y, z ==> foo (one_arg (x), y, z) + 4) Other identifiers, if not immediately followed by an opening + parenthesis, consume the entire remaining argument list: + foo bar x, y ==> foo (bar (x, y)) + sub two_args ($$) { ... } + foo two_args x, y ==> foo (two_args (x, y)) + + Other series of comma separated expressions without a function name at + the beginning are comma expressions: + sub two_args ($$) { ... } + foo two_args x, (y, z) ==> foo (two_args (x, (y, z))) + Note that the evaluation of comma expressions returns a list of values + when in list context (e.g. inside the argument list of a function without + prototype) but only one value when inside the argument list of a function + with a prototype: + sub print3 ($$$) { print @_ } + print3 5, (6, 7), 8 ==> 578 + print 5, (6, 7), 8 ==> 5678 + + Where rule 3 or 4 contradict rule 1 or 2, the program is invalid: + sin (x, y) - invalid due to rules 2 and 3 + sub one_arg ($) { ... } + one_arg (x, y) - invalid due to rules 2 and 3 + sub two_args ($$) { ... } + foo two_args x, y, z - invalid due to rules 1 and 4 + */ + +/* Extract messages until the next balanced closing parenthesis. + Extracted messages are added to MLP. + + DELIM can be either token_type_rbrace, token_type_rbracket, + token_type_rparen. Additionally, if COMMA_DELIM is true, parsing + stops at the next comma outside parentheses. + + ARG is the current argument list position, starts with 1. + ARGPARSER is the corresponding argument list parser. + + Returns true for EOF, false otherwise. */ + +static bool +extract_balanced (message_list_ty *mlp, + token_type_ty delim, bool eat_delim, bool comma_delim, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + int arg, struct arglist_parser *argparser) +{ + /* Whether to implicitly assume the next tokens are arguments even without + a '('. */ + bool next_is_argument = false; + /* Parameters of the keyword just seen. Defined only when next_is_argument + is true. */ + const struct callshapes *next_shapes = NULL; + struct arglist_parser *next_argparser = NULL; + + /* Whether to not consider strings until the next comma. */ + bool skip_until_comma = false; + + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + +#if DEBUG_PERL + static int nesting_level = 0; + + ++nesting_level; +#endif + + for (;;) + { + /* The current token. */ + token_ty *tp; + + tp = x_perl_lex (mlp); + + if (delim == tp->type) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + if (next_argparser != NULL) + free (next_argparser); +#if DEBUG_PERL + fprintf (stderr, "%s:%d: extract_balanced finished (%d)\n", + logical_file_name, tp->line_number, --nesting_level); +#endif + if (eat_delim) + free_token (tp); + else + /* Preserve the delimiter for the caller. */ + x_perl_unlex (tp); + return false; + } + + if (comma_delim && tp->type == token_type_comma) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + if (next_argparser != NULL) + free (next_argparser); +#if DEBUG_PERL + fprintf (stderr, "%s:%d: extract_balanced finished at comma (%d)\n", + logical_file_name, tp->line_number, --nesting_level); +#endif + x_perl_unlex (tp); + return false; + } + + if (next_is_argument && tp->type != token_type_lparen) + { + /* An argument list starts, even though there is no '('. */ + bool next_comma_delim; + + x_perl_unlex (tp); + + if (next_shapes != NULL) + /* We know something about the function being called. Assume + that it consumes only one argument if no argument number or + total > 1 is specified. */ + { + size_t i; + + next_comma_delim = true; + for (i = 0; i < next_shapes->nshapes; i++) + { + const struct callshape *shape = &next_shapes->shapes[i]; + + if (shape->argnum1 > 1 + || shape->argnum2 > 1 + || shape->argnumc > 1 + || shape->argtotal > 1) + next_comma_delim = false; + } + } + else + /* We know nothing about the function being called. It could be + a function prototyped to take only one argument, or on the other + hand it could be prototyped to take more than one argument or an + arbitrary argument list or it could be unprototyped. Due to + the way the parser works, assuming the first case gives the + best results. */ + next_comma_delim = true; + + if (extract_balanced (mlp, delim, false, next_comma_delim, + inner_context, next_context_iter, + 1, next_argparser)) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return true; + } + + next_is_argument = false; + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + continue; + } + + switch (tp->type) + { + case token_type_symbol: + case token_type_keyword_symbol: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type symbol (%d) \"%s\"\n", + logical_file_name, tp->line_number, nesting_level, + tp->string); +#endif + + { + void *keyword_value; + + if (hash_find_entry (&keywords, tp->string, strlen (tp->string), + &keyword_value) == 0) + { + const struct callshapes *shapes = + (const struct callshapes *) keyword_value; + + next_shapes = shapes; + next_argparser = arglist_parser_alloc (mlp, shapes); + } + else + { + next_shapes = NULL; + next_argparser = arglist_parser_alloc (mlp, NULL); + } + } + next_is_argument = true; + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + tp->string, strlen (tp->string))); + break; + + case token_type_variable: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type variable (%d) \"%s\"\n", + logical_file_name, tp->line_number, nesting_level, + tp->string); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_object: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type object (%d) \"%s->\"\n", + logical_file_name, tp->line_number, nesting_level, + tp->string); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_lparen: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type left parenthesis (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + if (next_is_argument) + { + /* Parse the argument list of a function call. */ + if (extract_balanced (mlp, token_type_rparen, true, false, + inner_context, next_context_iter, + 1, next_argparser)) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + return true; + } + next_is_argument = false; + next_argparser = NULL; + } + else + { + /* Parse a parenthesized expression or comma expression. */ + if (extract_balanced (mlp, token_type_rparen, true, false, + inner_context, next_context_iter, + arg, arglist_parser_clone (argparser))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + if (next_argparser != NULL) + free (next_argparser); + free_token (tp); + return true; + } + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + } + skip_until_comma = true; + next_context_iter = null_context_list_iterator; + break; + + case token_type_rparen: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type right parenthesis (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + skip_until_comma = true; + next_context_iter = null_context_list_iterator; + break; + + case token_type_comma: + case token_type_fat_comma: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type comma (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + if (arglist_parser_decidedp (argparser, arg)) + { + /* We have missed the argument. */ + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + argparser = arglist_parser_alloc (mlp, NULL); + arg = 0; + } + arg++; +#if DEBUG_PERL + fprintf (stderr, "%s:%d: arg: %d\n", + real_file_name, tp->line_number, arg); +#endif + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + skip_until_comma = false; + next_context_iter = passthrough_context_list_iterator; + break; + + case token_type_string: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type string (%d): \"%s\"\n", + logical_file_name, tp->line_number, nesting_level, + tp->string); +#endif + + if (extract_all) + { + char *string = collect_message (mlp, tp, EXIT_SUCCESS); + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = tp->line_number; + xgettext_current_source_encoding = po_charset_utf8; + remember_a_message (mlp, NULL, string, inner_context, &pos, + NULL, tp->comment); + xgettext_current_source_encoding = xgettext_global_source_encoding; + } + else if (!skip_until_comma) + { + /* Need to collect the complete string, with error checking, + only if the argument ARG is used in ARGPARSER. */ + bool must_collect = false; + { + size_t nalternatives = argparser->nalternatives; + size_t i; + + for (i = 0; i < nalternatives; i++) + { + struct partial_call *cp = &argparser->alternative[i]; + + if (arg == cp->argnumc + || arg == cp->argnum1 || arg == cp->argnum2) + must_collect = true; + } + } + + if (must_collect) + { + char *string = collect_message (mlp, tp, EXIT_FAILURE); + + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_remember (argparser, arg, + string, inner_context, + logical_file_name, tp->line_number, + tp->comment); + xgettext_current_source_encoding = xgettext_global_source_encoding; + } + } + + if (arglist_parser_decidedp (argparser, arg)) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + argparser = arglist_parser_alloc (mlp, NULL); + } + + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_number: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type number (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_eof: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type EOF (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + free_token (tp); + return true; + + case token_type_lbrace: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type lbrace (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + if (extract_balanced (mlp, token_type_rbrace, true, false, + null_context, null_context_list_iterator, + 1, arglist_parser_alloc (mlp, NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + if (next_argparser != NULL) + free (next_argparser); + free_token (tp); + return true; + } + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_rbrace: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type rbrace (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_lbracket: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type lbracket (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + if (extract_balanced (mlp, token_type_rbracket, true, false, + null_context, null_context_list_iterator, + 1, arglist_parser_alloc (mlp, NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + if (next_argparser != NULL) + free (next_argparser); + free_token (tp); + return true; + } + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_rbracket: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type rbracket (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_semicolon: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type semicolon (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + + /* The ultimate sign. */ + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_global_source_encoding; + argparser = arglist_parser_alloc (mlp, NULL); + + /* FIXME: Instead of resetting outer_context here, it may be better + to recurse in the next_is_argument handling above, waiting for + the next semicolon or other statement terminator. */ + outer_context = null_context; + context_iter = null_context_list_iterator; + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = passthrough_context_list_iterator; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + break; + + case token_type_dereference: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type dereference (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_dot: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type dot (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_named_op: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type named operator (%d): %s\n", + logical_file_name, tp->line_number, nesting_level, + tp->string); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_regex_op: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type regex operator (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + case token_type_other: +#if DEBUG_PERL + fprintf (stderr, "%s:%d: type other (%d)\n", + logical_file_name, tp->line_number, nesting_level); +#endif + next_is_argument = false; + if (next_argparser != NULL) + free (next_argparser); + next_argparser = NULL; + next_context_iter = null_context_list_iterator; + break; + + default: + fprintf (stderr, "%s:%d: unknown token type %d\n", + real_file_name, tp->line_number, tp->type); + abort (); + } + + free_token (tp); + } +} + +void +extract_perl (FILE *f, const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 0; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + token_stack.items = NULL; + token_stack.nitems = 0; + token_stack.nitems_max = 0; + linesize = 0; + linepos = 0; + eaten_here = 0; + end_of_file = false; + + /* Safe assumption. */ + last_token_type = token_type_semicolon; + + /* Eat tokens until eof is seen. When extract_balanced returns + due to an unbalanced closing brace, just restart it. */ + while (!extract_balanced (mlp, token_type_rbrace, true, false, + null_context, null_context_list_iterator, + 1, arglist_parser_alloc (mlp, NULL))) + ; + + fp = NULL; + real_file_name = NULL; + free (logical_file_name); + logical_file_name = NULL; + line_number = 0; + last_token_type = token_type_semicolon; + token_stack_free (&token_stack); + eaten_here = 0; + end_of_file = true; +} diff --git a/gettext-tools/src/x-perl.h b/gettext-tools/src/x-perl.h new file mode 100644 index 0000000..8e140e9 --- /dev/null +++ b/gettext-tools/src/x-perl.h @@ -0,0 +1,55 @@ +/* xgettext Perl backend. + Copyright (C) 2002-2003, 2006, 2010 Free Software Foundation, Inc. + Written by Guido Flohr , 2002-2003 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_PERL \ + { "pl", "perl" }, \ + { "PL", "perl" }, \ + { "pm", "perl" }, \ + { "perl", "perl" }, \ + { "cgi", "perl" }, \ + +#define SCANNERS_PERL \ + { "perl", extract_perl, \ + &flag_table_perl, &formatstring_perl, &formatstring_perl_brace, NULL }, \ + +/* Scan a Perl file and add its translatable strings to mdlp. */ +extern void extract_perl (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_perl_keyword (const char *keyword); +extern void x_perl_extract_all (void); + +extern void init_flag_table_perl (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-php.c b/gettext-tools/src/x-php.c new file mode 100644 index 0000000..74b8ece --- /dev/null +++ b/gettext-tools/src/x-php.c @@ -0,0 +1,1605 @@ +/* xgettext PHP backend. + Copyright (C) 2001-2003, 2005-2010 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-php.h" + +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The PHP syntax is defined in phpdoc/manual/langref.html. + See also php-4.1.0/Zend/zend_language_scanner.l + and php-4.1.0/Zend/zend_language_parser.y. + Note that variable and function names can contain bytes in the range + 0x7f..0xff; see + http://www.php.net/manual/en/language.variables.php + http://www.php.net/manual/en/language.functions.php */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_php_extract_all () +{ + extract_all = true; +} + + +void +x_php_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_php_keyword ("_"); + x_php_keyword ("gettext"); + x_php_keyword ("dgettext:2"); + x_php_keyword ("dcgettext:2"); + /* The following were added in PHP 4.2.0. */ + x_php_keyword ("ngettext:1,2"); + x_php_keyword ("dngettext:2,3"); + x_php_keyword ("dcngettext:2,3"); + default_keywords = false; + } +} + +void +init_flag_table_php () +{ + xgettext_record_flag ("_:1:pass-php-format"); + xgettext_record_flag ("gettext:1:pass-php-format"); + xgettext_record_flag ("dgettext:2:pass-php-format"); + xgettext_record_flag ("dcgettext:2:pass-php-format"); + xgettext_record_flag ("ngettext:1:pass-php-format"); + xgettext_record_flag ("ngettext:2:pass-php-format"); + xgettext_record_flag ("dngettext:2:pass-php-format"); + xgettext_record_flag ("dngettext:3:pass-php-format"); + xgettext_record_flag ("dcngettext:2:pass-php-format"); + xgettext_record_flag ("dcngettext:3:pass-php-format"); + xgettext_record_flag ("sprintf:1:php-format"); + xgettext_record_flag ("printf:1:php-format"); +} + + +/* ======================== Reading of characters. ======================== */ + + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* 1. line_number handling. */ + +static unsigned char phase1_pushback[2]; +static int phase1_pushback_length; + +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + c = phase1_pushback[--phase1_pushback_length]; + else + { + c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + } + + if (c == '\n') + line_number++; + + return c; +} + +/* Supports 2 characters of pushback. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + --line_number; + + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + } +} + + +/* 2. Ignore HTML sections. They are equivalent to PHP echo commands and + therefore don't contain translatable strings. */ + +static void +skip_html () +{ + for (;;) + { + int c = phase1_getc (); + + if (c == EOF) + return; + + if (c == '<') + { + int c2 = phase1_getc (); + + if (c2 == EOF) + break; + + if (c2 == '?') + { + /* + < script language = "php" > + < script language = 'php' > + are always recognized. */ + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r') + c2 = phase1_getc (); + if (c2 != 's' && c2 != 'S') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'c' && c2 != 'C') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'r' && c2 != 'R') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'i' && c2 != 'I') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'p' && c2 != 'P') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 't' && c2 != 'T') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (!(c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r')) + { + phase1_ungetc (c2); + continue; + } + do + c2 = phase1_getc (); + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r'); + if (c2 != 'l' && c2 != 'L') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'a' && c2 != 'A') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'n' && c2 != 'N') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'g' && c2 != 'G') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'u' && c2 != 'U') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'a' && c2 != 'A') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'g' && c2 != 'G') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'e' && c2 != 'E') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r') + c2 = phase1_getc (); + if (c2 != '=') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r') + c2 = phase1_getc (); + if (c2 == '"') + { + c2 = phase1_getc (); + if (c2 != 'p') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'h') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'p') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != '"') + { + phase1_ungetc (c2); + continue; + } + } + else if (c2 == '\'') + { + c2 = phase1_getc (); + if (c2 != 'p') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'h') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'p') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != '\'') + { + phase1_ungetc (c2); + continue; + } + } + else + { + if (c2 != 'p') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'h') + { + phase1_ungetc (c2); + continue; + } + c2 = phase1_getc (); + if (c2 != 'p') + { + phase1_ungetc (c2); + continue; + } + } + c2 = phase1_getc (); + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r') + c2 = phase1_getc (); + if (c2 != '>') + { + phase1_ungetc (c2); + continue; + } + return; + } + } +} + +#if 0 + +static unsigned char phase2_pushback[1]; +static int phase2_pushback_length; + +static int +phase2_getc () +{ + int c; + + if (phase2_pushback_length) + return phase2_pushback[--phase2_pushback_length]; + + c = phase1_getc (); + switch (c) + { + case '?': + case '%': + { + int c2 = phase1_getc (); + if (c2 == '>') + { + /* ?> and %> terminate PHP mode and switch back to HTML mode. */ + skip_html (); + return ' '; + } + phase1_ungetc (c2); + } + break; + + case '<': + { + int c2 = phase1_getc (); + + /* < / script > terminates PHP mode and switches back to HTML mode. */ + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r') + c2 = phase1_getc (); + if (c2 == '/') + { + do + c2 = phase1_getc (); + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r'); + if (c2 == 's' || c2 == 'S') + { + c2 = phase1_getc (); + if (c2 == 'c' || c2 == 'C') + { + c2 = phase1_getc (); + if (c2 == 'r' || c2 == 'R') + { + c2 = phase1_getc (); + if (c2 == 'i' || c2 == 'I') + { + c2 = phase1_getc (); + if (c2 == 'p' || c2 == 'P') + { + c2 = phase1_getc (); + if (c2 == 't' || c2 == 'T') + { + do + c2 = phase1_getc (); + while (c2 == ' ' || c2 == '\t' + || c2 == '\n' || c2 == '\r'); + if (c2 == '>') + { + skip_html (); + return ' '; + } + } + } + } + } + } + } + } + phase1_ungetc (c2); + } + break; + } + + return c; +} + +static void +phase2_ungetc (int c) +{ + if (c != EOF) + { + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + } +} + +#endif + + +/* Accumulating comments. */ + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (chars_to_remove == 0 && buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* 3. Replace each comment that is not inside a string literal with a + space character. We need to remember the comment for later, because + it may be attached to a keyword string. */ + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + +static unsigned char phase3_pushback[1]; +static int phase3_pushback_length; + +static int +phase3_getc () +{ + int lineno; + int c; + + if (phase3_pushback_length) + return phase3_pushback[--phase3_pushback_length]; + + c = phase1_getc (); + + if (c == '#') + { + /* sh comment. */ + bool last_was_qmark = false; + + comment_start (); + lineno = line_number; + for (;;) + { + c = phase1_getc (); + if (c == '\n' || c == EOF) + { + comment_line_end (0); + break; + } + if (last_was_qmark && c == '>') + { + comment_line_end (1); + skip_html (); + break; + } + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + last_was_qmark = (c == '?' || c == '%'); + } + last_comment_line = lineno; + return '\n'; + } + else if (c == '/') + { + c = phase1_getc (); + + switch (c) + { + default: + phase1_ungetc (c); + return '/'; + + case '*': + { + /* C comment. */ + bool last_was_star; + + comment_start (); + lineno = line_number; + last_was_star = false; + for (;;) + { + c = phase1_getc (); + if (c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (buflen == 0 && (c == ' ' || c == '\t')) + continue; + comment_add (c); + switch (c) + { + case '\n': + comment_line_end (1); + comment_start (); + lineno = line_number; + last_was_star = false; + continue; + + case '*': + last_was_star = true; + continue; + + case '/': + if (last_was_star) + { + comment_line_end (2); + break; + } + /* FALLTHROUGH */ + + default: + last_was_star = false; + continue; + } + break; + } + last_comment_line = lineno; + return ' '; + } + + case '/': + { + /* C++ comment. */ + bool last_was_qmark = false; + + comment_start (); + lineno = line_number; + for (;;) + { + c = phase1_getc (); + if (c == '\n' || c == EOF) + { + comment_line_end (0); + break; + } + if (last_was_qmark && c == '>') + { + comment_line_end (1); + skip_html (); + break; + } + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + last_was_qmark = (c == '?' || c == '%'); + } + last_comment_line = lineno; + return '\n'; + } + } + } + else + return c; +} + +#ifdef unused +static void +phase3_ungetc (int c) +{ + if (c != EOF) + { + if (phase3_pushback_length == SIZEOF (phase3_pushback)) + abort (); + phase3_pushback[phase3_pushback_length++] = c; + } +} +#endif + + +/* ========================== Reading of tokens. ========================== */ + + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_comma, /* , */ + token_type_lbracket, /* [ */ + token_type_rbracket, /* ] */ + token_type_dot, /* . */ + token_type_operator1, /* * / % ++ -- */ + token_type_operator2, /* + - ! ~ @ */ + token_type_string_literal, /* "abc" */ + token_type_symbol, /* symbol, number */ + token_type_other /* misc. operator */ +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_string_literal, token_type_symbol */ + refcounted_string_list_ty *comment; /* for token_type_string_literal */ + int line_number; +}; + + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_string_literal || tp->type == token_type_symbol) + free (tp->string); + if (tp->type == token_type_string_literal) + drop_reference (tp->comment); +} + + +/* 4. Combine characters into tokens. Discard whitespace. */ + +static token_ty phase4_pushback[3]; +static int phase4_pushback_length; + +static void +phase4_get (token_ty *tp) +{ + static char *buffer; + static int bufmax; + int bufpos; + int c; + + if (phase4_pushback_length) + { + *tp = phase4_pushback[--phase4_pushback_length]; + return; + } + tp->string = NULL; + + for (;;) + { + tp->line_number = line_number; + c = phase3_getc (); + switch (c) + { + case EOF: + tp->type = token_type_eof; + return; + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* FALLTHROUGH */ + case ' ': + case '\t': + case '\r': + /* Ignore whitespace. */ + continue; + } + + last_non_comment_line = tp->line_number; + + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case 127: case 128: case 129: case 130: case 131: case 132: case 133: + case 134: case 135: case 136: case 137: case 138: case 139: case 140: + case 141: case 142: case 143: case 144: case 145: case 146: case 147: + case 148: case 149: case 150: case 151: case 152: case 153: case 154: + case 155: case 156: case 157: case 158: case 159: case 160: case 161: + case 162: case 163: case 164: case 165: case 166: case 167: case 168: + case 169: case 170: case 171: case 172: case 173: case 174: case 175: + case 176: case 177: case 178: case 179: case 180: case 181: case 182: + case 183: case 184: case 185: case 186: case 187: case 188: case 189: + case 190: case 191: case 192: case 193: case 194: case 195: case 196: + case 197: case 198: case 199: case 200: case 201: case 202: case 203: + case 204: case 205: case 206: case 207: case 208: case 209: case 210: + case 211: case 212: case 213: case 214: case 215: case 216: case 217: + case 218: case 219: case 220: case 221: case 222: case 223: case 224: + case 225: case 226: case 227: case 228: case 229: case 230: case 231: + case 232: case 233: case 234: case 235: case 236: case 237: case 238: + case 239: case 240: case 241: case 242: case 243: case 244: case 245: + case 246: case 247: case 248: case 249: case 250: case 251: case 252: + case 253: case 254: case 255: + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase1_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 127: case 128: case 129: case 130: case 131: case 132: + case 133: case 134: case 135: case 136: case 137: case 138: + case 139: case 140: case 141: case 142: case 143: case 144: + case 145: case 146: case 147: case 148: case 149: case 150: + case 151: case 152: case 153: case 154: case 155: case 156: + case 157: case 158: case 159: case 160: case 161: case 162: + case 163: case 164: case 165: case 166: case 167: case 168: + case 169: case 170: case 171: case 172: case 173: case 174: + case 175: case 176: case 177: case 178: case 179: case 180: + case 181: case 182: case 183: case 184: case 185: case 186: + case 187: case 188: case 189: case 190: case 191: case 192: + case 193: case 194: case 195: case 196: case 197: case 198: + case 199: case 200: case 201: case 202: case 203: case 204: + case 205: case 206: case 207: case 208: case 209: case 210: + case 211: case 212: case 213: case 214: case 215: case 216: + case 217: case 218: case 219: case 220: case 221: case 222: + case 223: case 224: case 225: case 226: case 227: case 228: + case 229: case 230: case 231: case 232: case 233: case 234: + case 235: case 236: case 237: case 238: case 239: case 240: + case 241: case 242: case 243: case 244: case 245: case 246: + case 247: case 248: case 249: case 250: case 251: case 252: + case 253: case 254: case 255: + continue; + + default: + phase1_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = 0; + tp->string = xstrdup (buffer); + tp->type = token_type_symbol; + return; + + case '\'': + /* Single-quoted string literal. */ + bufpos = 0; + for (;;) + { + c = phase1_getc (); + if (c == EOF || c == '\'') + break; + if (c == '\\') + { + c = phase1_getc (); + if (c != '\\' && c != '\'') + { + phase1_ungetc (c); + c = '\\'; + } + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = 0; + tp->type = token_type_string_literal; + tp->string = xstrdup (buffer); + tp->comment = add_reference (savable_comment); + return; + + case '"': + /* Double-quoted string literal. */ + tp->type = token_type_string_literal; + bufpos = 0; + for (;;) + { + c = phase1_getc (); + if (c == EOF || c == '"') + break; + if (c == '$') + { + c = phase1_getc (); + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || c == '_' || c == '{' || c >= 0x7f) + { + /* String with variables. */ + tp->type = token_type_other; + continue; + } + phase1_ungetc (c); + c = '$'; + } + if (c == '{') + { + c = phase1_getc (); + if (c == '$') + { + /* String with expressions. */ + tp->type = token_type_other; + continue; + } + phase1_ungetc (c); + c = '{'; + } + if (c == '\\') + { + int n, j; + + c = phase1_getc (); + switch (c) + { + case '"': + case '\\': + case '$': + break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + n = 0; + for (j = 0; j < 3; ++j) + { + n = n * 8 + c - '0'; + c = phase1_getc (); + switch (c) + { + default: + break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + continue; + } + break; + } + phase1_ungetc (c); + c = n; + break; + + case 'x': + n = 0; + for (j = 0; j < 2; ++j) + { + c = phase1_getc (); + switch (c) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = n * 16 + c - '0'; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + n = n * 16 + 10 + c - 'A'; + break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + n = n * 16 + 10 + c - 'a'; + break; + default: + phase1_ungetc (c); + c = 0; + break; + } + if (c == 0) + break; + } + if (j == 0) + { + phase1_ungetc ('x'); + c = '\\'; + } + else + c = n; + break; + + case 'n': + c = '\n'; + break; + case 't': + c = '\t'; + break; + case 'r': + c = '\r'; + break; + + default: + phase1_ungetc (c); + c = '\\'; + break; + } + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = 0; + if (tp->type == token_type_string_literal) + { + tp->string = xstrdup (buffer); + tp->comment = add_reference (savable_comment); + } + return; + + case '?': + case '%': + { + int c2 = phase1_getc (); + if (c2 == '>') + { + /* ?> and %> terminate PHP mode and switch back to HTML + mode. */ + skip_html (); + tp->type = token_type_other; + } + else + { + phase1_ungetc (c2); + tp->type = (c == '%' ? token_type_operator1 : token_type_other); + } + return; + } + + case '(': + tp->type = token_type_lparen; + return; + + case ')': + tp->type = token_type_rparen; + return; + + case ',': + tp->type = token_type_comma; + return; + + case '[': + tp->type = token_type_lbracket; + return; + + case ']': + tp->type = token_type_rbracket; + return; + + case '.': + tp->type = token_type_dot; + return; + + case '*': + case '/': + tp->type = token_type_operator1; + return; + + case '+': + case '-': + { + int c2 = phase1_getc (); + if (c2 == c) + /* ++ or -- */ + tp->type = token_type_operator1; + else + /* + or - */ + { + phase1_ungetc (c2); + tp->type = token_type_operator2; + } + return; + } + + case '!': + case '~': + case '@': + tp->type = token_type_operator2; + return; + + case '<': + { + int c2 = phase1_getc (); + if (c2 == '<') + { + int c3 = phase1_getc (); + if (c3 == '<') + { + int label_start = 0; + + /* Start of here and now document. + Parse whitespace, then label, then newline. */ + do + c = phase3_getc (); + while (c == ' ' || c == '\t' || c == '\n' || c == '\r'); + + bufpos = 0; + do + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase3_getc (); + } + while (c != EOF && c != '\n' && c != '\r'); + /* buffer[0..bufpos-1] now contains the label + (including single or double quotes). */ + + if (*buffer == '\'' || *buffer == '"') + { + label_start++; + bufpos--; + } + + /* Now skip the here document. */ + for (;;) + { + c = phase1_getc (); + if (c == EOF) + break; + if (c == '\n' || c == '\r') + { + int bufidx = label_start; + + while (bufidx < bufpos) + { + c = phase1_getc (); + if (c == EOF) + break; + if (c != buffer[bufidx]) + { + phase1_ungetc (c); + break; + } + bufidx++; + } + if (bufidx == bufpos) + { + c = phase1_getc (); + if (c != ';') + phase1_ungetc (c); + c = phase1_getc (); + if (c == '\n' || c == '\r') + break; + } + } + } + + /* FIXME: Ideally we should turn the here document into a + string literal if it didn't contain $ substitution. And + we should also respect backslash escape sequences like + in double-quoted strings. */ + tp->type = token_type_other; + return; + } + phase1_ungetc (c3); + } + + /* < / script > terminates PHP mode and switches back to HTML + mode. */ + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r') + c2 = phase1_getc (); + if (c2 == '/') + { + do + c2 = phase1_getc (); + while (c2 == ' ' || c2 == '\t' || c2 == '\n' || c2 == '\r'); + if (c2 == 's' || c2 == 'S') + { + c2 = phase1_getc (); + if (c2 == 'c' || c2 == 'C') + { + c2 = phase1_getc (); + if (c2 == 'r' || c2 == 'R') + { + c2 = phase1_getc (); + if (c2 == 'i' || c2 == 'I') + { + c2 = phase1_getc (); + if (c2 == 'p' || c2 == 'P') + { + c2 = phase1_getc (); + if (c2 == 't' || c2 == 'T') + { + do + c2 = phase1_getc (); + while (c2 == ' ' || c2 == '\t' + || c2 == '\n' || c2 == '\r'); + if (c2 == '>') + { + skip_html (); + } + else + phase1_ungetc (c2); + } + else + phase1_ungetc (c2); + } + else + phase1_ungetc (c2); + } + else + phase1_ungetc (c2); + } + else + phase1_ungetc (c2); + } + else + phase1_ungetc (c2); + } + else + phase1_ungetc (c2); + } + else + phase1_ungetc (c2); + + tp->type = token_type_other; + return; + } + + case '`': + /* Execution operator. */ + default: + /* We could carefully recognize each of the 2 and 3 character + operators, but it is not necessary, as we only need to recognize + gettext invocations. Don't bother. */ + tp->type = token_type_other; + return; + } + } +} + +/* Supports 3 tokens of pushback. */ +static void +phase4_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase4_pushback_length == SIZEOF (phase4_pushback)) + abort (); + phase4_pushback[phase4_pushback_length++] = *tp; + } +} + + +/* 5. Compile-time optimization of string literal concatenation. + Combine "string1" . ... . "stringN" to the concatenated string if + - the token before this expression is none of + '+' '-' '.' '*' '/' '%' '!' '~' '++' '--' ')' '@' + (because then the first string could be part of an expression with + the same or higher precedence as '.', such as an additive, + multiplicative, negation, preincrement, or cast expression), + - the token after this expression is none of + '*' '/' '%' '++' '--' + (because then the last string could be part of an expression with + higher precedence as '.', such as a multiplicative or postincrement + expression). */ + +static token_type_ty phase5_last; + +static void +x_php_lex (token_ty *tp) +{ + phase4_get (tp); + if (tp->type == token_type_string_literal + && !(phase5_last == token_type_dot + || phase5_last == token_type_operator1 + || phase5_last == token_type_operator2 + || phase5_last == token_type_rparen)) + { + char *sum = tp->string; + size_t sum_len = strlen (sum); + + for (;;) + { + token_ty token2; + + phase4_get (&token2); + if (token2.type == token_type_dot) + { + token_ty token3; + + phase4_get (&token3); + if (token3.type == token_type_string_literal) + { + token_ty token_after; + + phase4_get (&token_after); + if (token_after.type != token_type_operator1) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = (char *) xrealloc (sum, sum_len + addend_len + 1); + memcpy (sum + sum_len, addend, addend_len + 1); + sum_len += addend_len; + + phase4_unget (&token_after); + free_token (&token3); + free_token (&token2); + continue; + } + phase4_unget (&token_after); + } + phase4_unget (&token3); + } + phase4_unget (&token2); + break; + } + tp->string = sum; + } + phase5_last = tp->type; +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid C or C++ program, and leave the complaints about + the grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + + +/* Extract messages until the next balanced closing parenthesis or bracket. + Extracted messages are added to MLP. + DELIM can be either token_type_rparen or token_type_rbracket, or + token_type_eof to accept both. + Return true upon eof, false upon closing parenthesis or bracket. */ +static bool +extract_balanced (message_list_ty *mlp, + token_type_ty delim, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_php_lex (&token); + switch (token.type) + { + case token_type_symbol: + { + void *keyword_value; + + if (hash_find_entry (&keywords, token.string, strlen (token.string), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + } + else + state = 0; + } + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, strlen (token.string))); + free (token.string); + continue; + + case token_type_lparen: + if (extract_balanced (mlp, token_type_rparen, + inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + arglist_parser_done (argparser, arg); + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rparen: + if (delim == token_type_rparen || delim == token_type_eof) + { + arglist_parser_done (argparser, arg); + return false; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case token_type_lbracket: + if (extract_balanced (mlp, token_type_rbracket, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + { + arglist_parser_done (argparser, arg); + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rbracket: + if (delim == token_type_rbracket || delim == token_type_eof) + { + arglist_parser_done (argparser, arg); + return false; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_string_literal: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + if (extract_all) + remember_a_message (mlp, NULL, token.string, inner_context, + &pos, NULL, token.comment); + else + arglist_parser_remember (argparser, arg, token.string, + inner_context, + pos.file_name, pos.line_number, + token.comment); + drop_reference (token.comment); + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_dot: + case token_type_operator1: + case token_type_operator2: + case token_type_other: + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_eof: + arglist_parser_done (argparser, arg); + return true; + + default: + abort (); + } + } +} + + +void +extract_php (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + phase5_last = token_type_eof; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Initial mode is HTML mode, not PHP mode. */ + skip_html (); + + /* Eat tokens until eof is seen. When extract_balanced returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_balanced (mlp, token_type_eof, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + /* Close scanner. */ + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-php.h b/gettext-tools/src/x-php.h new file mode 100644 index 0000000..79442b4 --- /dev/null +++ b/gettext-tools/src/x-php.h @@ -0,0 +1,53 @@ +/* xgettext PHP backend. + Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_PHP \ + { "php", "PHP" }, \ + { "php3", "PHP" }, \ + { "php4", "PHP" }, \ + +#define SCANNERS_PHP \ + { "PHP", extract_php, \ + &flag_table_php, &formatstring_php, NULL, NULL }, \ + +/* Scan a PHP file and add its translatable strings to mdlp. */ +extern void extract_php (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_php_keyword (const char *keyword); +extern void x_php_extract_all (void); + +extern void init_flag_table_php (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-po.c b/gettext-tools/src/x-po.c new file mode 100644 index 0000000..89824e2 --- /dev/null +++ b/gettext-tools/src/x-po.c @@ -0,0 +1,240 @@ +/* xgettext PO and JavaProperties backends. + Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009 Free Software Foundation, Inc. + + This file was written by Peter Miller + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* Specification. */ +#include "x-po.h" +#include "x-properties.h" +#include "x-stringtable.h" + +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "xalloc.h" +#include "read-catalog.h" +#include "read-po.h" +#include "read-properties.h" +#include "read-stringtable.h" +#include "po-lex.h" +#include "gettext.h" + +/* A convenience macro. I don't like writing gettext() every time. */ +#define _(str) gettext (str) + + +/* The charset found in the header entry. */ +static char *header_charset; + +/* Define a subclass extract_catalog_reader_ty of default_catalog_reader_ty. */ + +static void +extract_add_message (default_catalog_reader_ty *this, + char *msgctxt, + char *msgid, + lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete) +{ + /* See whether we shall exclude this message. */ + if (exclude != NULL && message_list_search (exclude, msgctxt, msgid) != NULL) + goto discard; + + /* If the msgid is the empty string, it is the old header. Throw it + away, we have constructed a new one. Only remember its charset. + But if no new one was constructed, keep the old header. This is useful + because the old header may contain a charset= directive. */ + if (msgctxt == NULL && *msgid == '\0' && !xgettext_omit_header) + { + { + const char *charsetstr = strstr (msgstr, "charset="); + + if (charsetstr != NULL) + { + size_t len; + char *charset; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + charset = XNMALLOC (len + 1, char); + memcpy (charset, charsetstr, len); + charset[len] = '\0'; + + if (header_charset != NULL) + free (header_charset); + header_charset = charset; + } + } + + discard: + if (msgctxt != NULL) + free (msgctxt); + free (msgid); + if (msgid_plural != NULL) + free (msgid_plural); + free (msgstr); + if (prev_msgctxt != NULL) + free (prev_msgctxt); + if (prev_msgid != NULL) + free (prev_msgid); + if (prev_msgid_plural != NULL) + free (prev_msgid_plural); + return; + } + + /* Invoke superclass method. */ + default_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural, + msgstr, msgstr_len, msgstr_pos, + prev_msgctxt, prev_msgid, prev_msgid_plural, + force_fuzzy, obsolete); +} + + +/* So that the one parser can be used for multiple programs, and also + use good data hiding and encapsulation practices, an object + oriented approach has been taken. An object instance is allocated, + and all actions resulting from the parse will be through + invocations of method functions of that object. */ + +static default_catalog_reader_class_ty extract_methods = +{ + { + sizeof (default_catalog_reader_ty), + default_constructor, + default_destructor, + default_parse_brief, + default_parse_debrief, + default_directive_domain, + default_directive_message, + default_comment, + default_comment_dot, + default_comment_filepos, + default_comment_special + }, + default_set_domain, /* set_domain */ + extract_add_message, /* add_message */ + NULL /* frob_new_message */ +}; + + +static void +extract (FILE *fp, + const char *real_filename, const char *logical_filename, + catalog_input_format_ty input_syntax, + msgdomain_list_ty *mdlp) +{ + default_catalog_reader_ty *pop; + + header_charset = NULL; + + pop = default_catalog_reader_alloc (&extract_methods); + pop->handle_comments = true; + pop->allow_domain_directives = false; + pop->allow_duplicates = false; + pop->allow_duplicates_if_same_msgstr = true; + pop->file_name = real_filename; + pop->mdlp = NULL; + pop->mlp = mdlp->item[0]->messages; + catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename, + logical_filename, input_syntax); + catalog_reader_free ((abstract_catalog_reader_ty *) pop); + + if (header_charset != NULL) + { + if (!xgettext_omit_header) + { + /* Put the old charset into the freshly constructed header entry. */ + message_ty *mp = + message_list_search (mdlp->item[0]->messages, NULL, ""); + + if (mp != NULL && !mp->obsolete) + { + const char *header = mp->msgstr; + + if (header != NULL) + { + const char *charsetstr = strstr (header, "charset="); + + if (charsetstr != NULL) + { + size_t len, len1, len2, len3; + char *new_header; + + charsetstr += strlen ("charset="); + len = strcspn (charsetstr, " \t\n"); + + len1 = charsetstr - header; + len2 = strlen (header_charset); + len3 = (header + strlen (header)) - (charsetstr + len); + new_header = XNMALLOC (len1 + len2 + len3 + 1, char); + memcpy (new_header, header, len1); + memcpy (new_header + len1, header_charset, len2); + memcpy (new_header + len1 + len2, charsetstr + len, len3 + 1); + mp->msgstr = new_header; + mp->msgstr_len = len1 + len2 + len3 + 1; + } + } + } + } + + free (header_charset); + } +} + + +void +extract_po (FILE *fp, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + extract (fp, real_filename, logical_filename, &input_format_po, mdlp); +} + + +void +extract_properties (FILE *fp, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + extract (fp, real_filename, logical_filename, &input_format_properties, + mdlp); +} + + +void +extract_stringtable (FILE *fp, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + extract (fp, real_filename, logical_filename, &input_format_stringtable, + mdlp); +} diff --git a/gettext-tools/src/x-po.h b/gettext-tools/src/x-po.h new file mode 100644 index 0000000..0d350d8 --- /dev/null +++ b/gettext-tools/src/x-po.h @@ -0,0 +1,46 @@ +/* xgettext PO backend. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_PO \ + { "po", "PO" }, \ + { "pot", "PO" }, \ + +#define SCANNERS_PO \ + { "PO", extract_po, NULL, NULL, NULL, NULL }, \ + +/* Scan a PO file and add its translatable strings to mdlp. */ +extern void extract_po (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-properties.h b/gettext-tools/src/x-properties.h new file mode 100644 index 0000000..fd16ab3 --- /dev/null +++ b/gettext-tools/src/x-properties.h @@ -0,0 +1,45 @@ +/* xgettext JavaProperties backend. + Copyright (C) 2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_PROPERTIES \ + { "properties", "JavaProperties" }, \ + +#define SCANNERS_PROPERTIES \ + { "JavaProperties", extract_properties, NULL, NULL, NULL, NULL }, \ + +/* Scan a JavaProperties file and add its translatable strings to mdlp. */ +extern void extract_properties (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-python.c b/gettext-tools/src/x-python.c new file mode 100644 index 0000000..d781ef2 --- /dev/null +++ b/gettext-tools/src/x-python.c @@ -0,0 +1,1779 @@ +/* xgettext Python backend. + Copyright (C) 2002-2003, 2005-2013 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-python.h" + +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "basename.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "xalloc.h" +#include "c-strstr.h" +#include "c-ctype.h" +#include "po-charset.h" +#include "uniname.h" +#include "unistr.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define max(a,b) ((a) > (b) ? (a) : (b)) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The Python syntax is defined in the Python Reference Manual + /usr/share/doc/packages/python/html/ref/index.html. + See also Python-2.0/Parser/tokenizer.c, Python-2.0/Python/compile.c, + Python-2.0/Objects/unicodeobject.c. */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_python_extract_all () +{ + extract_all = true; +} + + +void +x_python_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_python_keyword ("gettext"); + x_python_keyword ("ugettext"); + x_python_keyword ("dgettext:2"); + x_python_keyword ("ngettext:1,2"); + x_python_keyword ("ungettext:1,2"); + x_python_keyword ("dngettext:2,3"); + x_python_keyword ("_"); + default_keywords = false; + } +} + +void +init_flag_table_python () +{ + xgettext_record_flag ("gettext:1:pass-python-format"); + xgettext_record_flag ("ugettext:1:pass-python-format"); + xgettext_record_flag ("dgettext:2:pass-python-format"); + xgettext_record_flag ("ngettext:1:pass-python-format"); + xgettext_record_flag ("ngettext:2:pass-python-format"); + xgettext_record_flag ("ungettext:1:pass-python-format"); + xgettext_record_flag ("ungettext:2:pass-python-format"); + xgettext_record_flag ("dngettext:2:pass-python-format"); + xgettext_record_flag ("dngettext:3:pass-python-format"); + xgettext_record_flag ("_:1:pass-python-format"); + /* xgettext_record_flag ("%:1:python-format"); // % is an infix operator! */ + + xgettext_record_flag ("gettext:1:pass-python-brace-format"); + xgettext_record_flag ("ugettext:1:pass-python-brace-format"); + xgettext_record_flag ("dgettext:2:pass-python-brace-format"); + xgettext_record_flag ("ngettext:1:pass-python-brace-format"); + xgettext_record_flag ("ngettext:2:pass-python-brace-format"); + xgettext_record_flag ("ungettext:1:pass-python-brace-format"); + xgettext_record_flag ("ungettext:2:pass-python-brace-format"); + xgettext_record_flag ("dngettext:2:pass-python-brace-format"); + xgettext_record_flag ("dngettext:3:pass-python-brace-format"); + xgettext_record_flag ("_:1:pass-python-brace-format"); + /* xgettext_record_flag ("format:1:python-brace-format"); */ +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* 0. Terminate line by \n, regardless whether the external + representation of a line terminator is CR (Mac), and CR/LF + (DOS/Windows), as Python treats them equally. */ +static int +phase0_getc () +{ + int c; + + c = getc (fp); + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + + if (c == '\r') + { + int c1 = getc (fp); + + if (c1 != EOF && c1 != '\n') + ungetc (c1, fp); + + /* Seen line terminator CR or CR/LF. */ + return '\n'; + } + + return c; +} + +/* Supports only one pushback character, and not '\n'. */ +static inline void +phase0_ungetc (int c) +{ + if (c != EOF) + ungetc (c, fp); +} + + +/* 1. line_number handling. */ + +/* Maximum used, roughly a safer MB_LEN_MAX. */ +#define MAX_PHASE1_PUSHBACK 16 +static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK]; +static int phase1_pushback_length; + +/* Read the next single byte from the input file. */ +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + c = phase1_pushback[--phase1_pushback_length]; + else + c = phase0_getc (); + + if (c == '\n') + ++line_number; + + return c; +} + +/* Supports MAX_PHASE1_PUSHBACK characters of pushback. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + --line_number; + + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + } +} + + +/* Phase 2: Conversion to Unicode. + This is done early because PEP 0263 specifies that conversion to Unicode + conceptually occurs before tokenization. A test case where it matters + is with encodings like BIG5: when a double-byte character ending in 0x5C + is followed by '\' or 'u0021', the tokenizer must not treat the second + half of the double-byte character as a backslash. */ + +/* End-of-file indicator for functions returning an UCS-4 character. */ +#define UEOF -1 + +static lexical_context_ty lexical_context; + +static int phase2_pushback[max (9, UNINAME_MAX + 3)]; +static int phase2_pushback_length; + +/* Read the next Unicode UCS-4 character from the input file. */ +static int +phase2_getc () +{ + if (phase2_pushback_length) + return phase2_pushback[--phase2_pushback_length]; + + if (xgettext_current_source_encoding == po_charset_ascii) + { + int c = phase1_getc (); + if (c == EOF) + return UEOF; + if (!c_isascii (c)) + { + multiline_error (xstrdup (""), + xasprintf ("%s\n%s\n", + non_ascii_error_message (lexical_context, + real_file_name, + line_number), + _("\ +Please specify the source encoding through --from-code or through a comment\n\ +as specified in http://www.python.org/peps/pep-0263.html.\n"))); + exit (EXIT_FAILURE); + } + return c; + } + else if (xgettext_current_source_encoding != po_charset_utf8) + { +#if HAVE_ICONV + /* Use iconv on an increasing number of bytes. Read only as many bytes + through phase1_getc as needed. This is needed to give reasonable + interactive behaviour when fp is connected to an interactive tty. */ + unsigned char buf[MAX_PHASE1_PUSHBACK]; + size_t bufcount; + int c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[0] = (unsigned char) c; + bufcount = 1; + + for (;;) + { + unsigned char scratchbuf[6]; + const char *inptr = (const char *) &buf[0]; + size_t insize = bufcount; + char *outptr = (char *) &scratchbuf[0]; + size_t outsize = sizeof (scratchbuf); + + size_t res = iconv (xgettext_current_source_iconv, + (ICONV_CONST char **) &inptr, &insize, + &outptr, &outsize); + /* We expect that a character has been produced if and only if + some input bytes have been consumed. */ + if ((insize < bufcount) != (outsize < sizeof (scratchbuf))) + abort (); + if (outsize == sizeof (scratchbuf)) + { + /* No character has been produced. Must be an error. */ + if (res != (size_t)(-1)) + abort (); + + if (errno == EILSEQ) + { + /* An invalid multibyte sequence was encountered. */ + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Invalid multibyte sequence.\n\ +Please specify the correct source encoding through --from-code or through a\n\ +comment as specified in http://www.python.org/peps/pep-0263.html.\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + else if (errno == EINVAL) + { + /* An incomplete multibyte character. */ + int c; + + if (bufcount == MAX_PHASE1_PUSHBACK) + { + /* An overlong incomplete multibyte sequence was + encountered. */ + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Long incomplete multibyte sequence.\n\ +Please specify the correct source encoding through --from-code or through a\n\ +comment as specified in http://www.python.org/peps/pep-0263.html.\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + + /* Read one more byte and retry iconv. */ + c = phase1_getc (); + if (c == EOF) + { + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Incomplete multibyte sequence at end of file.\n\ +Please specify the correct source encoding through --from-code or through a\n\ +comment as specified in http://www.python.org/peps/pep-0263.html.\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + if (c == '\n') + { + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Incomplete multibyte sequence at end of line.\n\ +Please specify the correct source encoding through --from-code or through a\n\ +comment as specified in http://www.python.org/peps/pep-0263.html.\n"), + real_file_name, line_number - 1)); + exit (EXIT_FAILURE); + } + buf[bufcount++] = (unsigned char) c; + } + else + error (EXIT_FAILURE, errno, _("%s:%d: iconv failure"), + real_file_name, line_number); + } + else + { + size_t outbytes = sizeof (scratchbuf) - outsize; + size_t bytes = bufcount - insize; + ucs4_t uc; + + /* We expect that one character has been produced. */ + if (bytes == 0) + abort (); + if (outbytes == 0) + abort (); + /* Push back the unused bytes. */ + while (insize > 0) + phase1_ungetc (buf[--insize]); + /* Convert the character from UTF-8 to UCS-4. */ + if (u8_mbtoucr (&uc, scratchbuf, outbytes) < (int) outbytes) + { + /* scratchbuf contains an out-of-range Unicode character + (> 0x10ffff). */ + multiline_error (xstrdup (""), + xasprintf (_("\ +%s:%d: Invalid multibyte sequence.\n\ +Please specify the source encoding through --from-code or through a comment\n\ +as specified in http://www.python.org/peps/pep-0263.html.\n"), + real_file_name, line_number)); + exit (EXIT_FAILURE); + } + return uc; + } + } +#else + /* If we don't have iconv(), the only supported values for + xgettext_global_source_encoding and thus also for + xgettext_current_source_encoding are ASCII and UTF-8. */ + abort (); +#endif + } + else + { + /* Read an UTF-8 encoded character. */ + unsigned char buf[6]; + unsigned int count; + int c; + ucs4_t uc; + + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[0] = c; + count = 1; + + if (buf[0] >= 0xc0) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[1] = c; + count = 2; + } + + if (buf[0] >= 0xe0 + && ((buf[1] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[2] = c; + count = 3; + } + + if (buf[0] >= 0xf0 + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[3] = c; + count = 4; + } + + if (buf[0] >= 0xf8 + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40) + && ((buf[3] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[4] = c; + count = 5; + } + + if (buf[0] >= 0xfc + && ((buf[1] ^ 0x80) < 0x40) + && ((buf[2] ^ 0x80) < 0x40) + && ((buf[3] ^ 0x80) < 0x40) + && ((buf[4] ^ 0x80) < 0x40)) + { + c = phase1_getc (); + if (c == EOF) + return UEOF; + buf[5] = c; + count = 6; + } + + u8_mbtouc (&uc, buf, count); + return uc; + } +} + +/* Supports max (9, UNINAME_MAX + 3) pushback characters. */ +static void +phase2_ungetc (int c) +{ + if (c != UEOF) + { + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + } +} + + +/* ========================= Accumulating strings. ======================== */ + +/* A string buffer type that allows appending Unicode characters. + Returns the entire string in UTF-8 encoding. */ + +struct unicode_string_buffer +{ + /* The part of the string that has already been converted to UTF-8. */ + char *utf8_buffer; + size_t utf8_buflen; + size_t utf8_allocated; +}; + +/* Initialize a 'struct unicode_string_buffer' to empty. */ +static inline void +init_unicode_string_buffer (struct unicode_string_buffer *bp) +{ + bp->utf8_buffer = NULL; + bp->utf8_buflen = 0; + bp->utf8_allocated = 0; +} + +/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ +static inline void +unicode_string_buffer_append_unicode_grow (struct unicode_string_buffer *bp, + size_t count) +{ + if (bp->utf8_buflen + count > bp->utf8_allocated) + { + size_t new_allocated = 2 * bp->utf8_allocated + 10; + if (new_allocated < bp->utf8_buflen + count) + new_allocated = bp->utf8_buflen + count; + bp->utf8_allocated = new_allocated; + bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); + } +} + +/* Auxiliary function: Append a Unicode character to bp->utf8. + uc must be < 0x110000. */ +static inline void +unicode_string_buffer_append_unicode (struct unicode_string_buffer *bp, + unsigned int uc) +{ + unsigned char utf8buf[6]; + int count = u8_uctomb (utf8buf, uc, 6); + + if (count < 0) + /* The caller should have ensured that uc is not out-of-range. */ + abort (); + + unicode_string_buffer_append_unicode_grow (bp, count); + memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); + bp->utf8_buflen += count; +} + +/* Return the string buffer's contents. */ +static char * +unicode_string_buffer_result (struct unicode_string_buffer *bp) +{ + /* NUL-terminate it. */ + unicode_string_buffer_append_unicode_grow (bp, 1); + bp->utf8_buffer[bp->utf8_buflen] = '\0'; + /* Return it. */ + return bp->utf8_buffer; +} + +/* Free the memory pointed to by a 'struct unicode_string_buffer'. */ +static inline void +free_unicode_string_buffer (struct unicode_string_buffer *bp) +{ + free (bp->utf8_buffer); +} + + +/* ======================== Accumulating comments. ======================== */ + + +/* Accumulating a single comment line. */ + +static struct unicode_string_buffer comment_buffer; + +static inline void +comment_start () +{ + lexical_context = lc_comment; + comment_buffer.utf8_buflen = 0; +} + +static inline bool +comment_at_start () +{ + return (comment_buffer.utf8_buflen == 0); +} + +static inline void +comment_add (int c) +{ + unicode_string_buffer_append_unicode (&comment_buffer, c); +} + +static inline const char * +comment_line_end () +{ + char *buffer = unicode_string_buffer_result (&comment_buffer); + size_t buflen = strlen (buffer); + + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + buffer[buflen] = '\0'; + savable_comment_add (buffer); + lexical_context = lc_outside; + return buffer; +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ======================== Recognizing comments. ======================== */ + + +/* Recognizing the "coding" comment. + As specified in PEP 0263, it takes the form + "coding" [":"|"="] {alphanumeric or "-" or "_" or "*"}* + or + "set" "fileencoding" "=" {alphanumeric or "-" or "_" or "*"}* + and is located in a comment in a line that + - is either the first or second line, + - is not a continuation line, + - in the first form, contains no other tokens except this comment. */ + +/* Canonicalized encoding name for the current input file. */ +static const char *xgettext_current_file_source_encoding; + +#if HAVE_ICONV +/* Converter from xgettext_current_file_source_encoding to UTF-8 (except from + ASCII or UTF-8, when this conversion is a no-op). */ +static iconv_t xgettext_current_file_source_iconv; +#endif + +static inline void +set_current_file_source_encoding (const char *canon_encoding) +{ + xgettext_current_file_source_encoding = canon_encoding; + + if (xgettext_current_file_source_encoding != po_charset_ascii + && xgettext_current_file_source_encoding != po_charset_utf8) + { +#if HAVE_ICONV + iconv_t cd; + + /* Avoid glibc-2.1 bug with EUC-KR. */ +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \ + && !defined _LIBICONV_VERSION + if (strcmp (xgettext_current_file_source_encoding, "EUC-KR") == 0) + cd = (iconv_t)(-1); + else +# endif + cd = iconv_open (po_charset_utf8, xgettext_current_file_source_encoding); + if (cd == (iconv_t)(-1)) + error_at_line (EXIT_FAILURE, 0, logical_file_name, line_number - 1, _("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(), \ +and iconv() does not support this conversion."), + xgettext_current_file_source_encoding, po_charset_utf8, + basename (program_name)); + xgettext_current_file_source_iconv = cd; +#else + error_at_line (EXIT_FAILURE, 0, logical_file_name, line_number - 1, _("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(). \ +This version was built without iconv()."), + xgettext_global_source_encoding, po_charset_utf8, + basename (program_name)); +#endif + } + + xgettext_current_source_encoding = xgettext_current_file_source_encoding; +#if HAVE_ICONV + xgettext_current_source_iconv = xgettext_current_file_source_iconv; +#endif +} + +static inline void +try_to_extract_coding (const char *comment) +{ + const char *p = c_strstr (comment, "coding"); + + if (p != NULL) + { + p += 6; + if (*p == ':' || *p == '=') + { + p++; + while (*p == ' ' || *p == '\t') + p++; + { + const char *encoding_start = p; + + while (c_isalnum (*p) || *p == '-' || *p == '_' || *p == '.') + p++; + { + const char *encoding_end = p; + + if (encoding_end > encoding_start) + { + /* Extract the encoding string. */ + size_t encoding_len = encoding_end - encoding_start; + char *encoding = XNMALLOC (encoding_len + 1, char); + + memcpy (encoding, encoding_start, encoding_len); + encoding[encoding_len] = '\0'; + + { + /* Canonicalize it. */ + const char *canon_encoding = po_charset_canonicalize (encoding); + if (canon_encoding == NULL) + { + error_at_line (0, 0, + logical_file_name, line_number - 1, _("\ +Unknown encoding \"%s\". Proceeding with ASCII instead."), + encoding); + canon_encoding = po_charset_ascii; + } + + /* Activate it. */ + set_current_file_source_encoding (canon_encoding); + } + + free (encoding); + } + } + } + } + } +} + +/* Tracking whether the current line is a continuation line or contains a + non-blank character. */ +static bool continuation_or_nonblank_line = false; + + +/* Phase 3: Outside strings, replace backslash-newline with nothing and a + comment with nothing. */ + +static int +phase3_getc () +{ + int c; + + for (;;) + { + c = phase2_getc (); + if (c == '\\') + { + c = phase2_getc (); + if (c != '\n') + { + phase2_ungetc (c); + /* This shouldn't happen usually, because "A backslash is + illegal elsewhere on a line outside a string literal." */ + return '\\'; + } + /* Eat backslash-newline. */ + continuation_or_nonblank_line = true; + } + else if (c == '#') + { + /* Eat a comment. */ + const char *comment; + + last_comment_line = line_number; + comment_start (); + for (;;) + { + c = phase2_getc (); + if (c == UEOF || c == '\n') + break; + /* We skip all leading white space, but not EOLs. */ + if (!(comment_at_start () && (c == ' ' || c == '\t'))) + comment_add (c); + } + comment = comment_line_end (); + if (line_number - 1 <= 2 && !continuation_or_nonblank_line) + try_to_extract_coding (comment); + continuation_or_nonblank_line = false; + return c; + } + else + { + if (c == '\n') + continuation_or_nonblank_line = false; + else if (!(c == ' ' || c == '\t' || c == '\f')) + continuation_or_nonblank_line = true; + return c; + } + } +} + +/* Supports only one pushback character. */ +static void +phase3_ungetc (int c) +{ + phase2_ungetc (c); +} + + +/* ========================= Accumulating strings. ======================== */ + +/* Return value of phase7_getuc when EOF is reached. */ +#define P7_EOF (-1) +#define P7_STRING_END (-2) + +/* Convert an UTF-16 or UTF-32 code point to a return value that can be + distinguished from a single-byte return value. */ +#define UNICODE(code) (0x100 + (code)) + +/* Test a return value of phase7_getuc whether it designates an UTF-16 or + UTF-32 code point. */ +#define IS_UNICODE(p7_result) ((p7_result) >= 0x100) + +/* Extract the UTF-16 or UTF-32 code of a return value that satisfies + IS_UNICODE. */ +#define UNICODE_VALUE(p7_result) ((p7_result) - 0x100) + + +/* ========================== Reading of tokens. ========================== */ + + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_comma, /* , */ + token_type_lbracket, /* [ */ + token_type_rbracket, /* ] */ + token_type_string, /* "abc", 'abc', """abc""", '''abc''' */ + token_type_symbol, /* symbol, number */ + token_type_plus, /* + */ + token_type_other /* misc. operator */ +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_string, token_type_symbol */ + refcounted_string_list_ty *comment; /* for token_type_string */ + int line_number; +}; + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_string || tp->type == token_type_symbol) + free (tp->string); + if (tp->type == token_type_string) + drop_reference (tp->comment); +} + + +/* There are two different input syntaxes for strings, "abc" and r"abc", + and two different input syntaxes for Unicode strings, u"abc" and ur"abc". + Which escape sequences are understood, i.e. what is interpreted specially + after backslash? + "abc" \ \\ \' \" \a\b\f\n\r\t\v \ooo \xnn + r"abc" + u"abc" \ \\ \' \" \a\b\f\n\r\t\v \ooo \xnn \unnnn \Unnnnnnnn \N{...} + ur"abc" \unnnn + The \unnnn values are UTF-16 values; a single \Unnnnnnnn can expand to two + \unnnn items. The \ooo and \xnn values are in the current source encoding + for byte strings, and Unicode code points for Unicode strings. + */ + +static int +phase7_getuc (int quote_char, + bool triple, bool interpret_ansic, bool interpret_unicode, + unsigned int *backslash_counter) +{ + int c; + + for (;;) + { + /* Use phase 2, because phase 3 elides comments. */ + c = phase2_getc (); + + if (c == UEOF) + return P7_EOF; + + if (c == quote_char && (interpret_ansic || (*backslash_counter & 1) == 0)) + { + if (triple) + { + int c1 = phase2_getc (); + if (c1 == quote_char) + { + int c2 = phase2_getc (); + if (c2 == quote_char) + return P7_STRING_END; + phase2_ungetc (c2); + } + phase2_ungetc (c1); + return UNICODE (c); + } + else + return P7_STRING_END; + } + + if (c == '\n') + { + if (triple) + { + *backslash_counter = 0; + return UNICODE ('\n'); + } + /* In r"..." and ur"..." strings, newline is only allowed + immediately after an odd number of backslashes (although the + backslashes are not interpreted!). */ + if (!(interpret_ansic || (*backslash_counter & 1) == 0)) + { + *backslash_counter = 0; + return UNICODE ('\n'); + } + phase2_ungetc (c); + error_with_progname = false; + error (0, 0, _("%s:%d: warning: unterminated string"), + logical_file_name, line_number); + error_with_progname = true; + return P7_STRING_END; + } + + if (c != '\\') + { + *backslash_counter = 0; + return UNICODE (c); + } + + /* Backslash handling. */ + + if (!interpret_ansic && !interpret_unicode) + { + ++*backslash_counter; + return UNICODE ('\\'); + } + + /* Dispatch according to the character following the backslash. */ + c = phase2_getc (); + if (c == UEOF) + { + ++*backslash_counter; + return UNICODE ('\\'); + } + + if (interpret_ansic) + switch (c) + { + case '\n': + continue; + case '\\': + ++*backslash_counter; + return UNICODE (c); + case '\'': case '"': + *backslash_counter = 0; + return UNICODE (c); + case 'a': + *backslash_counter = 0; + return UNICODE ('\a'); + case 'b': + *backslash_counter = 0; + return UNICODE ('\b'); + case 'f': + *backslash_counter = 0; + return UNICODE ('\f'); + case 'n': + *backslash_counter = 0; + return UNICODE ('\n'); + case 'r': + *backslash_counter = 0; + return UNICODE ('\r'); + case 't': + *backslash_counter = 0; + return UNICODE ('\t'); + case 'v': + *backslash_counter = 0; + return UNICODE ('\v'); + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + { + int n = c - '0'; + + c = phase2_getc (); + if (c != UEOF) + { + if (c >= '0' && c <= '7') + { + n = (n << 3) + (c - '0'); + c = phase2_getc (); + if (c != UEOF) + { + if (c >= '0' && c <= '7') + n = (n << 3) + (c - '0'); + else + phase2_ungetc (c); + } + } + else + phase2_ungetc (c); + } + *backslash_counter = 0; + if (interpret_unicode) + return UNICODE (n); + else + return (unsigned char) n; + } + case 'x': + { + int c1 = phase2_getc (); + int n1; + + if (c1 >= '0' && c1 <= '9') + n1 = c1 - '0'; + else if (c1 >= 'A' && c1 <= 'F') + n1 = c1 - 'A' + 10; + else if (c1 >= 'a' && c1 <= 'f') + n1 = c1 - 'a' + 10; + else + n1 = -1; + + if (n1 >= 0) + { + int c2 = phase2_getc (); + int n2; + + if (c2 >= '0' && c2 <= '9') + n2 = c2 - '0'; + else if (c2 >= 'A' && c2 <= 'F') + n2 = c2 - 'A' + 10; + else if (c2 >= 'a' && c2 <= 'f') + n2 = c2 - 'a' + 10; + else + n2 = -1; + + if (n2 >= 0) + { + int n = (n1 << 4) + n2; + *backslash_counter = 0; + if (interpret_unicode) + return UNICODE (n); + else + return (unsigned char) n; + } + + phase2_ungetc (c2); + } + phase2_ungetc (c1); + phase2_ungetc (c); + ++*backslash_counter; + return UNICODE ('\\'); + } + } + + if (interpret_unicode) + { + if (c == 'u') + { + unsigned char buf[4]; + unsigned int n = 0; + int i; + + for (i = 0; i < 4; i++) + { + int c1 = phase2_getc (); + + if (c1 >= '0' && c1 <= '9') + n = (n << 4) + (c1 - '0'); + else if (c1 >= 'A' && c1 <= 'F') + n = (n << 4) + (c1 - 'A' + 10); + else if (c1 >= 'a' && c1 <= 'f') + n = (n << 4) + (c1 - 'a' + 10); + else + { + phase2_ungetc (c1); + while (--i >= 0) + phase2_ungetc (buf[i]); + phase2_ungetc (c); + ++*backslash_counter; + return UNICODE ('\\'); + } + + buf[i] = c1; + } + *backslash_counter = 0; + return UNICODE (n); + } + + if (interpret_ansic) + { + if (c == 'U') + { + unsigned char buf[8]; + unsigned int n = 0; + int i; + + for (i = 0; i < 8; i++) + { + int c1 = phase2_getc (); + + if (c1 >= '0' && c1 <= '9') + n = (n << 4) + (c1 - '0'); + else if (c1 >= 'A' && c1 <= 'F') + n = (n << 4) + (c1 - 'A' + 10); + else if (c1 >= 'a' && c1 <= 'f') + n = (n << 4) + (c1 - 'a' + 10); + else + { + phase2_ungetc (c1); + while (--i >= 0) + phase2_ungetc (buf[i]); + phase2_ungetc (c); + ++*backslash_counter; + return UNICODE ('\\'); + } + + buf[i] = c1; + } + if (n < 0x110000) + { + *backslash_counter = 0; + return UNICODE (n); + } + + error_with_progname = false; + error (0, 0, _("%s:%d: warning: invalid Unicode character"), + logical_file_name, line_number); + error_with_progname = true; + + while (--i >= 0) + phase2_ungetc (buf[i]); + phase2_ungetc (c); + ++*backslash_counter; + return UNICODE ('\\'); + } + + if (c == 'N') + { + int c1 = phase2_getc (); + if (c1 == '{') + { + unsigned char buf[UNINAME_MAX + 1]; + int i; + unsigned int n; + + for (i = 0; i < UNINAME_MAX; i++) + { + int c2 = phase2_getc (); + if (!(c2 >= ' ' && c2 <= '~')) + { + phase2_ungetc (c2); + while (--i >= 0) + phase2_ungetc (buf[i]); + phase2_ungetc (c1); + phase2_ungetc (c); + ++*backslash_counter; + return UNICODE ('\\'); + } + if (c2 == '}') + break; + buf[i] = c2; + } + buf[i] = '\0'; + + n = unicode_name_character ((char *) buf); + if (n != UNINAME_INVALID) + { + *backslash_counter = 0; + return UNICODE (n); + } + + phase2_ungetc ('}'); + while (--i >= 0) + phase2_ungetc (buf[i]); + } + phase2_ungetc (c1); + phase2_ungetc (c); + ++*backslash_counter; + return UNICODE ('\\'); + } + } + } + + phase2_ungetc (c); + ++*backslash_counter; + return UNICODE ('\\'); + } +} + + +/* Combine characters into tokens. Discard whitespace except newlines at + the end of logical lines. */ + +/* Number of pending open parentheses/braces/brackets. */ +static int open_pbb; + +static token_ty phase5_pushback[2]; +static int phase5_pushback_length; + +static void +phase5_get (token_ty *tp) +{ + int c; + + if (phase5_pushback_length) + { + *tp = phase5_pushback[--phase5_pushback_length]; + return; + } + + for (;;) + { + tp->line_number = line_number; + c = phase3_getc (); + + switch (c) + { + case UEOF: + tp->type = token_type_eof; + return; + + case ' ': + case '\t': + case '\f': + /* Ignore whitespace and comments. */ + continue; + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* Ignore newline if and only if it is used for implicit line + joining. */ + if (open_pbb > 0) + continue; + tp->type = token_type_other; + return; + } + + last_non_comment_line = tp->line_number; + + switch (c) + { + case '.': + { + int c1 = phase3_getc (); + phase3_ungetc (c1); + if (!(c1 >= '0' && c1 <= '9')) + { + + tp->type = token_type_other; + return; + } + } + /* FALLTHROUGH */ + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': + case 'S': case 'T': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': + case 's': case 't': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + symbol: + /* Symbol, or part of a number. */ + { + static char *buffer; + static int bufmax; + int bufpos; + + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase3_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + default: + phase3_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + tp->string = xstrdup (buffer); + tp->type = token_type_symbol; + return; + } + + /* Strings. */ + { + struct mixed_string_buffer *bp; + int quote_char; + bool interpret_ansic; + bool interpret_unicode; + bool triple; + unsigned int backslash_counter; + + case 'R': case 'r': + { + int c1 = phase2_getc (); + if (c1 == '"' || c1 == '\'') + { + quote_char = c1; + interpret_ansic = false; + interpret_unicode = false; + goto string; + } + phase2_ungetc (c1); + goto symbol; + } + + case 'U': case 'u': + { + int c1 = phase2_getc (); + if (c1 == '"' || c1 == '\'') + { + quote_char = c1; + interpret_ansic = true; + interpret_unicode = true; + goto string; + } + if (c1 == 'R' || c1 == 'r') + { + int c2 = phase2_getc (); + if (c2 == '"' || c2 == '\'') + { + quote_char = c2; + interpret_ansic = false; + interpret_unicode = true; + goto string; + } + phase2_ungetc (c2); + } + phase2_ungetc (c1); + goto symbol; + } + + case '"': case '\'': + quote_char = c; + interpret_ansic = true; + interpret_unicode = false; + string: + triple = false; + lexical_context = lc_string; + { + int c1 = phase2_getc (); + if (c1 == quote_char) + { + int c2 = phase2_getc (); + if (c2 == quote_char) + triple = true; + else + { + phase2_ungetc (c2); + phase2_ungetc (c1); + } + } + else + phase2_ungetc (c1); + } + backslash_counter = 0; + /* Start accumulating the string. */ + bp = mixed_string_buffer_alloc (lexical_context, + logical_file_name, + line_number); + for (;;) + { + int uc = phase7_getuc (quote_char, triple, interpret_ansic, + interpret_unicode, &backslash_counter); + + /* Keep line_number in sync. */ + bp->line_number = line_number; + + if (uc == P7_EOF || uc == P7_STRING_END) + break; + + if (IS_UNICODE (uc)) + { + assert (UNICODE_VALUE (uc) >= 0 + && UNICODE_VALUE (uc) < 0x110000); + mixed_string_buffer_append_unicode (bp, + UNICODE_VALUE (uc)); + } + else + mixed_string_buffer_append_char (bp, uc); + } + tp->string = mixed_string_buffer_done (bp); + tp->comment = add_reference (savable_comment); + lexical_context = lc_outside; + tp->type = token_type_string; + return; + } + + case '(': + open_pbb++; + tp->type = token_type_lparen; + return; + + case ')': + if (open_pbb > 0) + open_pbb--; + tp->type = token_type_rparen; + return; + + case ',': + tp->type = token_type_comma; + return; + + case '[': case '{': + open_pbb++; + tp->type = (c == '[' ? token_type_lbracket : token_type_other); + return; + + case ']': case '}': + if (open_pbb > 0) + open_pbb--; + tp->type = (c == ']' ? token_type_rbracket : token_type_other); + return; + + case '+': + tp->type = token_type_plus; + return; + + default: + /* We could carefully recognize each of the 2 and 3 character + operators, but it is not necessary, as we only need to recognize + gettext invocations. Don't bother. */ + tp->type = token_type_other; + return; + } + } +} + +/* Supports only one pushback token. */ +static void +phase5_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase5_pushback_length == SIZEOF (phase5_pushback)) + abort (); + phase5_pushback[phase5_pushback_length++] = *tp; + } +} + + +/* Combine adjacent strings to form a single string. Note that the end + of a logical line appears as a token of its own, therefore strings that + belong to different logical lines will not be concatenated. */ + +static void +x_python_lex (token_ty *tp) +{ + phase5_get (tp); + if (tp->type == token_type_string) + { + char *sum = tp->string; + size_t sum_len = strlen (sum); + + for (;;) + { + token_ty token2, *tp2 = NULL; + token_ty token3; + + phase5_get (&token2); + switch (token2.type) + { + case token_type_plus: + { + phase5_get (&token3); + if (token3.type == token_type_string) + { + free_token (&token2); + tp2 = &token3; + } + else + phase5_unget (&token3); + } + break; + case token_type_string: + tp2 = &token2; + break; + default: + break; + } + + if (tp2) + { + char *addend = tp2->string; + size_t addend_len = strlen (addend); + + sum = (char *) xrealloc (sum, sum_len + addend_len + 1); + memcpy (sum + sum_len, addend, addend_len + 1); + sum_len += addend_len; + + free_token (tp2); + continue; + } + phase5_unget (&token2); + break; + } + tp->string = sum; + } +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid C or C++ program, and leave the complaints about + the grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + + +/* Extract messages until the next balanced closing parenthesis or bracket. + Extracted messages are added to MLP. + DELIM can be either token_type_rparen or token_type_rbracket, or + token_type_eof to accept both. + Return true upon eof, false upon closing parenthesis or bracket. */ +static bool +extract_balanced (message_list_ty *mlp, + token_type_ty delim, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_python_lex (&token); + switch (token.type) + { + case token_type_symbol: + { + void *keyword_value; + + if (hash_find_entry (&keywords, token.string, strlen (token.string), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + } + else + state = 0; + } + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, strlen (token.string))); + free (token.string); + continue; + + case token_type_lparen: + if (extract_balanced (mlp, token_type_rparen, + inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rparen: + if (delim == token_type_rparen || delim == token_type_eof) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return false; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case token_type_lbracket: + if (extract_balanced (mlp, token_type_rbracket, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rbracket: + if (delim == token_type_rbracket || delim == token_type_eof) + { + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return false; + } + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_string: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + xgettext_current_source_encoding = po_charset_utf8; + if (extract_all) + remember_a_message (mlp, NULL, token.string, inner_context, + &pos, NULL, token.comment); + else + arglist_parser_remember (argparser, arg, token.string, + inner_context, + pos.file_name, pos.line_number, + token.comment); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + } + drop_reference (token.comment); + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_eof: + xgettext_current_source_encoding = po_charset_utf8; + arglist_parser_done (argparser, arg); + xgettext_current_source_encoding = xgettext_current_file_source_encoding; + return true; + + case token_type_plus: + case token_type_other: + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + default: + abort (); + } + } +} + + +void +extract_python (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + lexical_context = lc_outside; + + last_comment_line = -1; + last_non_comment_line = -1; + + xgettext_current_file_source_encoding = xgettext_global_source_encoding; +#if HAVE_ICONV + xgettext_current_file_source_iconv = xgettext_global_source_iconv; +#endif + + xgettext_current_source_encoding = xgettext_current_file_source_encoding; +#if HAVE_ICONV + xgettext_current_source_iconv = xgettext_current_file_source_iconv; +#endif + + continuation_or_nonblank_line = false; + + open_pbb = 0; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When extract_balanced returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_balanced (mlp, token_type_eof, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-python.h b/gettext-tools/src/x-python.h new file mode 100644 index 0000000..f7861fb --- /dev/null +++ b/gettext-tools/src/x-python.h @@ -0,0 +1,51 @@ +/* xgettext Python backend. + Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_PYTHON \ + { "py", "Python" }, \ + +#define SCANNERS_PYTHON \ + { "Python", extract_python, \ + &flag_table_python, &formatstring_python, &formatstring_python_brace, NULL }, \ + +/* Scan a Python file and add its translatable strings to mdlp. */ +extern void extract_python (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_python_keyword (const char *keyword); +extern void x_python_extract_all (void); + +extern void init_flag_table_python (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-rst.c b/gettext-tools/src/x-rst.c new file mode 100644 index 0000000..8b5a26a --- /dev/null +++ b/gettext-tools/src/x-rst.c @@ -0,0 +1,236 @@ +/* xgettext RST backend. + Copyright (C) 2001-2003, 2005-2009 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-rst.h" + +#include +#include +#include +#include + +#include "c-ctype.h" +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "error-progname.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(s) gettext(s) + +/* RST stands for Resource String Table. + + An RST file consists of several string definitions. A string definition + starts at the beginning of a line and looks like this: + ModuleName.ConstName=StringExpression + A StringExpression consists of string pieces of the form 'xyz', + single characters of the form #nnn (decimal integer), and + + at the end of the line to designate continuation on the next line. + String definitions can be separated by blank lines or comment lines + beginning with '#'. + + This backend attempts to be functionally equivalent to the 'rstconv' + program, part of the Free Pascal run time library, written by + Sebastian Guenther. Except that the locations are output as + "ModuleName.ConstName", not "ModuleName:ConstName". + */ + +void +extract_rst (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + static char *buffer; + static int bufmax; + message_list_ty *mlp = mdlp->item[0]->messages; + int line_number; + + line_number = 1; + for (;;) + { + int c; + int bufpos; + char *location; + char *msgid; + lex_pos_ty pos; + + c = getc (f); + if (c == EOF) + break; + + /* Ignore blank line. */ + if (c == '\n') + { + line_number++; + continue; + } + + /* Ignore comment line. */ + if (c == '#') + { + do + c = getc (f); + while (c != EOF && c != '\n'); + if (c == EOF) + break; + line_number++; + continue; + } + + /* Read ModuleName.ConstName. */ + bufpos = 0; + for (;;) + { + if (c == EOF || c == '\n') + { + error_with_progname = false; + error (EXIT_FAILURE, 0, _("%s:%d: invalid string definition"), + logical_filename, line_number); + error_with_progname = true; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + if (c == '=') + break; + buffer[bufpos++] = c; + c = getc (f); + if (c == EOF && ferror (f)) + goto bomb; + } + buffer[bufpos] = '\0'; + location = xstrdup (buffer); + + /* Read StringExpression. */ + bufpos = 0; + for (;;) + { + c = getc (f); + if (c == EOF) + break; + else if (c == '\n') + { + line_number++; + break; + } + else if (c == '\'') + { + for (;;) + { + c = getc (f); + /* Embedded single quotes like 'abc''def' don't occur. + See fpc-1.0.4/compiler/cresstr.pas. */ + if (c == EOF || c == '\n' || c == '\'') + break; + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + } + if (c == EOF) + break; + else if (c == '\n') + { + line_number++; + break; + } + } + else if (c == '#') + { + int n; + c = getc (f); + if (c == EOF && ferror (f)) + goto bomb; + if (c == EOF || !c_isdigit (c)) + { + error_with_progname = false; + error (EXIT_FAILURE, 0, _("%s:%d: missing number after #"), + logical_filename, line_number); + error_with_progname = true; + } + n = (c - '0'); + for (;;) + { + c = getc (f); + if (c == EOF || !c_isdigit (c)) + break; + n = n * 10 + (c - '0'); + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = (unsigned char) n; + if (c == EOF) + break; + ungetc (c, f); + } + else if (c == '+') + { + c = getc (f); + if (c == EOF) + break; + if (c == '\n') + line_number++; + else + ungetc (c, f); + } + else + { + error_with_progname = false; + error (EXIT_FAILURE, 0, _("%s:%d: invalid string expression"), + logical_filename, line_number); + error_with_progname = true; + } + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + msgid = xstrdup (buffer); + + pos.file_name = location; + pos.line_number = (size_t)(-1); + + remember_a_message (mlp, NULL, msgid, null_context, &pos, NULL, NULL); + + /* Here c is the last read character: EOF or '\n'. */ + if (c == EOF) + break; + } + + if (ferror (f)) + { + bomb: + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_filename); + } +} diff --git a/gettext-tools/src/x-rst.h b/gettext-tools/src/x-rst.h new file mode 100644 index 0000000..6507d0d --- /dev/null +++ b/gettext-tools/src/x-rst.h @@ -0,0 +1,46 @@ +/* xgettext RST backend. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_RST \ + { "rst", "RST" }, \ + +#define SCANNERS_RST \ + { "RST", extract_rst, \ + NULL, &formatstring_pascal, NULL, NULL }, \ + +/* Scan an RST file and add its translatable strings to mdlp. */ +extern void extract_rst (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-scheme.c b/gettext-tools/src/x-scheme.c new file mode 100644 index 0000000..e8cfe84 --- /dev/null +++ b/gettext-tools/src/x-scheme.c @@ -0,0 +1,1339 @@ +/* xgettext Scheme backend. + Copyright (C) 2004-2009, 2011 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2004-2005. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-scheme.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "hash.h" +#include "gettext.h" + +#define _(s) gettext(s) + + +/* The Scheme syntax is described in R5RS. It is implemented in + guile-2.0.0/libguile/read.c. + Since we are interested only in strings and in forms similar to + (gettext msgid ...) + or (ngettext msgid msgid_plural ...) + we make the following simplifications: + + - Assume the keywords and strings are in an ASCII compatible encoding. + This means we can read the input file one byte at a time, instead of + one character at a time. No need to worry about multibyte characters: + If they occur as part of identifiers, they most probably act as + constituent characters, and the byte based approach will do the same. + + - Assume the read-hash-procedures is in the default state. + Non-standard reader extensions are mostly used to read data, not programs. + + The remaining syntax rules are: + + - The syntax code assigned to each character, and how tokens are built + up from characters (single escape, multiple escape etc.). + + - Comment syntax: ';' and '#! ... !#' and '#| ... |#' (may be nested). + + - String syntax: "..." with single escapes. + + - Read macros and dispatch macro character '#'. Needed to be able to + tell which is the n-th argument of a function call. + + */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_scheme_extract_all () +{ + extract_all = true; +} + + +void +x_scheme_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid Lisp symbol. + Extract the symbol name part. */ + colon = strchr (name, ':'); + if (colon != NULL && colon < end) + { + name = colon + 1; + if (name < end && *name == ':') + name++; + colon = strchr (name, ':'); + if (colon != NULL && colon < end) + return; + } + + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_scheme_keyword ("gettext"); /* libguile/i18n.c */ + x_scheme_keyword ("ngettext:1,2"); /* libguile/i18n.c */ + x_scheme_keyword ("gettext-noop"); + default_keywords = false; + } +} + +void +init_flag_table_scheme () +{ + xgettext_record_flag ("gettext:1:pass-scheme-format"); + xgettext_record_flag ("ngettext:1:pass-scheme-format"); + xgettext_record_flag ("ngettext:2:pass-scheme-format"); + xgettext_record_flag ("gettext-noop:1:pass-scheme-format"); + xgettext_record_flag ("format:2:scheme-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* Fetch the next character from the input file. */ +static int +do_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_file_name); + } + else if (c == '\n') + line_number++; + + return c; +} + +/* Put back the last fetched character, not EOF. */ +static void +do_ungetc (int c) +{ + if (c == '\n') + line_number--; + ungetc (c, fp); +} + + +/* ========================== Reading of tokens. ========================== */ + + +/* A token consists of a sequence of characters. */ +struct token +{ + int allocated; /* number of allocated 'token_char's */ + int charcount; /* number of used 'token_char's */ + char *chars; /* the token's constituents */ +}; + +/* Initialize a 'struct token'. */ +static inline void +init_token (struct token *tp) +{ + tp->allocated = 10; + tp->chars = XNMALLOC (tp->allocated, char); + tp->charcount = 0; +} + +/* Free the memory pointed to by a 'struct token'. */ +static inline void +free_token (struct token *tp) +{ + free (tp->chars); +} + +/* Ensure there is enough room in the token for one more character. */ +static inline void +grow_token (struct token *tp) +{ + if (tp->charcount == tp->allocated) + { + tp->allocated *= 2; + tp->chars = (char *) xrealloc (tp->chars, tp->allocated * sizeof (char)); + } +} + +/* Read the next token. 'first' is the first character, which has already + been read. */ +static void +read_token (struct token *tp, int first) +{ + init_token (tp); + + grow_token (tp); + tp->chars[tp->charcount++] = first; + + for (;;) + { + int c = do_getc (); + + if (c == EOF) + break; + if (c == ' ' || c == '\r' || c == '\f' || c == '\t' || c == '\n' + || c == '"' || c == '(' || c == ')' || c == ';') + { + do_ungetc (c); + break; + } + grow_token (tp); + tp->chars[tp->charcount++] = c; + } +} + +/* Tests if a token represents an integer. + Taken from guile-1.6.4/libguile/numbers.c:scm_istr2int(). */ +static inline bool +is_integer_syntax (const char *str, int len, int radix) +{ + const char *p = str; + const char *p_end = str + len; + + /* The accepted syntax is + ['+'|'-'] DIGIT+ + where DIGIT is a hexadecimal digit whose value is below radix. */ + + if (p == p_end) + return false; + if (*p == '+' || *p == '-') + { + p++; + if (p == p_end) + return false; + } + do + { + int c = *p++; + + if (c >= '0' && c <= '9') + c = c - '0'; + else if (c >= 'A' && c <= 'F') + c = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + c = c - 'a' + 10; + else + return false; + if (c >= radix) + return false; + } + while (p < p_end); + return true; +} + +/* Tests if a token represents a rational, floating-point or complex number. + If unconstrained is false, only real numbers are accepted; otherwise, + complex numbers are accepted as well. + Taken from guile-1.6.4/libguile/numbers.c:scm_istr2flo(). */ +static inline bool +is_other_number_syntax (const char *str, int len, int radix, bool unconstrained) +{ + const char *p = str; + const char *p_end = str + len; + bool seen_sign; + bool seen_digits; + + /* The accepted syntaxes are: + for a floating-point number: + ['+'|'-'] DIGIT+ [EXPONENT] + ['+'|'-'] DIGIT* '.' DIGIT+ [EXPONENT] + where EXPONENT ::= ['d'|'e'|'f'|'l'|'s'] DIGIT+ + (Dot and exponent are allowed only if radix is 10.) + for a rational number: + ['+'|'-'] DIGIT+ '/' DIGIT+ + for a complex number: + REAL-NUMBER {'+'|'-'} REAL-NUMBER-WITHOUT-SIGN 'i' + REAL-NUMBER {'+'|'-'} 'i' + {'+'|'-'} REAL-NUMBER-WITHOUT-SIGN 'i' + {'+'|'-'} 'i' + REAL-NUMBER '@' REAL-NUMBER + */ + if (p == p_end) + return false; + /* Parse leading sign. */ + seen_sign = false; + if (*p == '+' || *p == '-') + { + p++; + if (p == p_end) + return false; + seen_sign = true; + /* Recognize complex number syntax: {'+'|'-'} 'i' */ + if (unconstrained && (*p == 'I' || *p == 'i') && p + 1 == p_end) + return true; + } + /* Parse digits before dot or exponent or slash. */ + seen_digits = false; + do + { + int c = *p; + + if (c >= '0' && c <= '9') + c = c - '0'; + else if (c >= 'A' && c <= 'F') + { + if (c >= 'D' && radix == 10) /* exponent? */ + break; + c = c - 'A' + 10; + } + else if (c >= 'a' && c <= 'f') + { + if (c >= 'd' && radix == 10) /* exponent? */ + break; + c = c - 'a' + 10; + } + else + break; + if (c >= radix) + return false; + seen_digits = true; + p++; + } + while (p < p_end); + /* If p == p_end, we know that seen_digits = true, and the number is an + integer without exponent. */ + if (p < p_end) + { + /* If we have no digits so far, we need a decimal point later. */ + if (!seen_digits && !(*p == '.' && radix == 10)) + return false; + /* Trailing '#' signs are equivalent to zeroes. */ + while (p < p_end && *p == '#') + p++; + if (p < p_end) + { + if (*p == '/') + { + /* Parse digits after the slash. */ + bool all_zeroes = true; + p++; + for (; p < p_end; p++) + { + int c = *p; + + if (c >= '0' && c <= '9') + c = c - '0'; + else if (c >= 'A' && c <= 'F') + c = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + c = c - 'a' + 10; + else + break; + if (c >= radix) + return false; + if (c != 0) + all_zeroes = false; + } + /* A zero denominator is not allowed. */ + if (all_zeroes) + return false; + /* Trailing '#' signs are equivalent to zeroes. */ + while (p < p_end && *p == '#') + p++; + } + else + { + if (*p == '.') + { + /* Decimal point notation. */ + if (radix != 10) + return false; + /* Parse digits after the decimal point. */ + p++; + for (; p < p_end; p++) + { + int c = *p; + + if (c >= '0' && c <= '9') + seen_digits = true; + else + break; + } + /* Digits are required before or after the decimal point. */ + if (!seen_digits) + return false; + /* Trailing '#' signs are equivalent to zeroes. */ + while (p < p_end && *p == '#') + p++; + } + if (p < p_end) + { + /* Parse exponent. */ + switch (*p) + { + case 'D': case 'd': + case 'E': case 'e': + case 'F': case 'f': + case 'L': case 'l': + case 'S': case 's': + if (radix != 10) + return false; + p++; + if (p == p_end) + return false; + if (*p == '+' || *p == '-') + { + p++; + if (p == p_end) + return false; + } + if (!(*p >= '0' && *p <= '9')) + return false; + for (;;) + { + p++; + if (p == p_end) + break; + if (!(*p >= '0' && *p <= '9')) + break; + } + break; + default: + break; + } + } + } + } + } + if (p == p_end) + return true; + /* Recognize complex number syntax. */ + if (unconstrained) + { + /* Recognize the syntax {'+'|'-'} REAL-NUMBER-WITHOUT-SIGN 'i' */ + if (seen_sign && (*p == 'I' || *p == 'i') && p + 1 == p_end) + return true; + /* Recognize the syntaxes + REAL-NUMBER {'+'|'-'} REAL-NUMBER-WITHOUT-SIGN 'i' + REAL-NUMBER {'+'|'-'} 'i' + */ + if (*p == '+' || *p == '-') + return (p_end[-1] == 'I' || p_end[-1] == 'i') + && (p + 1 == p_end - 1 + || is_other_number_syntax (p, p_end - 1 - p, radix, false)); + /* Recognize the syntax REAL-NUMBER '@' REAL-NUMBER */ + if (*p == '@') + { + p++; + return is_other_number_syntax (p, p_end - p, radix, false); + } + } + return false; +} + +/* Tests if a token represents a number. + Taken from guile-1.6.4/libguile/numbers.c:scm_istring2number(). */ +static bool +is_number (const struct token *tp) +{ + const char *str = tp->chars; + int len = tp->charcount; + enum { unknown, exact, inexact } exactness = unknown; + bool seen_radix_prefix = false; + bool seen_exactness_prefix = false; + + if (len == 1) + if (*str == '+' || *str == '-') + return false; + while (len >= 2 && *str == '#') + { + switch (str[1]) + { + case 'B': case 'b': + if (seen_radix_prefix) + return false; + seen_radix_prefix = true; + break; + case 'O': case 'o': + if (seen_radix_prefix) + return false; + seen_radix_prefix = true; + break; + case 'D': case 'd': + if (seen_radix_prefix) + return false; + seen_radix_prefix = true; + break; + case 'X': case 'x': + if (seen_radix_prefix) + return false; + seen_radix_prefix = true; + break; + case 'E': case 'e': + if (seen_exactness_prefix) + return false; + exactness = exact; + seen_exactness_prefix = true; + break; + case 'I': case 'i': + if (seen_exactness_prefix) + return false; + exactness = inexact; + seen_exactness_prefix = true; + break; + default: + return false; + } + str += 2; + len -= 2; + } + if (exactness != inexact) + { + /* Try to parse an integer. */ + if (is_integer_syntax (str, len, 10)) + return true; + /* FIXME: Other Scheme implementations support exact rational numbers + or exact complex numbers. */ + } + if (exactness != exact) + { + /* Try to parse a rational, floating-point or complex number. */ + if (is_other_number_syntax (str, len, 10, true)) + return true; + } + return false; +} + + +/* ========================= Accumulating comments ========================= */ + + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (chars_to_remove == 0 && buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ========================= Accumulating messages ========================= */ + + +static message_list_ty *mlp; + + +/* ========================== Reading of objects. ========================= */ + + +/* We are only interested in symbols (e.g. gettext or ngettext) and strings. + Other objects need not to be represented precisely. */ +enum object_type +{ + t_symbol, /* symbol */ + t_string, /* string */ + t_other, /* other kind of real object */ + t_dot, /* '.' pseudo object */ + t_close, /* ')' pseudo object */ + t_eof /* EOF marker */ +}; + +struct object +{ + enum object_type type; + struct token *token; /* for t_symbol and t_string */ + int line_number_at_start; /* for t_string */ +}; + +/* Free the memory pointed to by a 'struct object'. */ +static inline void +free_object (struct object *op) +{ + if (op->type == t_symbol || op->type == t_string) + { + free_token (op->token); + free (op->token); + } +} + +/* Convert a t_symbol/t_string token to a char*. */ +static char * +string_of_object (const struct object *op) +{ + char *str; + int n; + + if (!(op->type == t_symbol || op->type == t_string)) + abort (); + n = op->token->charcount; + str = XNMALLOC (n + 1, char); + memcpy (str, op->token->chars, n); + str[n] = '\0'; + return str; +} + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + +/* Read the next object. */ +static void +read_object (struct object *op, flag_context_ty outer_context) +{ + for (;;) + { + int c = do_getc (); + bool seen_underscore_prefix = false; + + switch (c) + { + case EOF: + op->type = t_eof; + return; + + case ' ': case '\r': case '\f': case '\t': + continue; + + case '\n': + /* Comments assumed to be grouped with a message must immediately + precede it, with no non-whitespace token on a line between + both. */ + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + continue; + + case ';': + { + bool all_semicolons = true; + + last_comment_line = line_number; + comment_start (); + for (;;) + { + c = do_getc (); + if (c == EOF || c == '\n') + break; + if (c != ';') + all_semicolons = false; + if (!all_semicolons) + { + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + } + comment_line_end (0); + continue; + } + + case '(': + { + int arg = 0; /* Current argument number. */ + flag_context_list_iterator_ty context_iter; + const struct callshapes *shapes = NULL; + struct arglist_parser *argparser = NULL; + + for (;; arg++) + { + struct object inner; + flag_context_ty inner_context; + + if (arg == 0) + inner_context = null_context; + else + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + + read_object (&inner, inner_context); + + /* Recognize end of list. */ + if (inner.type == t_close) + { + op->type = t_other; + last_non_comment_line = line_number; + if (argparser != NULL) + arglist_parser_done (argparser, arg); + return; + } + + /* Dots are not allowed in every position. + But be tolerant. */ + + /* EOF inside list is illegal. + But be tolerant. */ + if (inner.type == t_eof) + break; + + if (arg == 0) + { + /* This is the function position. */ + if (inner.type == t_symbol) + { + char *symbol_name = string_of_object (&inner); + void *keyword_value; + + if (hash_find_entry (&keywords, + symbol_name, strlen (symbol_name), + &keyword_value) + == 0) + shapes = (const struct callshapes *) keyword_value; + + argparser = arglist_parser_alloc (mlp, shapes); + + context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + symbol_name, strlen (symbol_name))); + + free (symbol_name); + } + else + context_iter = null_context_list_iterator; + } + else + { + /* These are the argument positions. */ + if (argparser != NULL && inner.type == t_string) + arglist_parser_remember (argparser, arg, + string_of_object (&inner), + inner_context, + logical_file_name, + inner.line_number_at_start, + savable_comment); + } + + free_object (&inner); + } + if (argparser != NULL) + arglist_parser_done (argparser, arg); + } + op->type = t_other; + last_non_comment_line = line_number; + return; + + case ')': + /* Tell the caller about the end of list. + Unmatched closing parenthesis is illegal. + But be tolerant. */ + op->type = t_close; + last_non_comment_line = line_number; + return; + + case ',': + { + int c = do_getc (); + /* The ,@ handling inside lists is wrong anyway, because + ,@form expands to an unknown number of elements. */ + if (c != EOF && c != '@') + do_ungetc (c); + } + /*FALLTHROUGH*/ + case '\'': + case '`': + { + struct object inner; + + read_object (&inner, null_context); + + /* Dots and EOF are not allowed here. But be tolerant. */ + + free_object (&inner); + + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '#': + /* Dispatch macro handling. */ + { + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + { + op->type = t_other; + return; + } + + switch (c) + { + case '(': /* Vector */ + do_ungetc (c); + { + struct object inner; + read_object (&inner, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case 'T': case 't': /* Boolean true */ + case 'F': case 'f': /* Boolean false */ + op->type = t_other; + last_non_comment_line = line_number; + return; + + case 'B': case 'b': + case 'O': case 'o': + case 'D': case 'd': + case 'X': case 'x': + case 'E': case 'e': + case 'I': case 'i': + { + struct token token; + do_ungetc (c); + read_token (&token, '#'); + if (is_number (&token)) + { + /* A number. */ + free_token (&token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + else + { + if (token.charcount == 2 + && (token.chars[1] == 'e' || token.chars[1] == 'i')) + { + c = do_getc (); + if (c != EOF) + do_ungetc (c); + if (c == '(') + /* Homogenous vector syntax, see arrays.scm. */ + case 'a': /* Vectors of char */ + case 'c': /* Vectors of complex */ + /*case 'e':*/ /* Vectors of long */ + case 'h': /* Vectors of short */ + /*case 'i':*/ /* Vectors of double-float */ + case 'l': /* Vectors of long long */ + case 's': /* Vectors of single-float */ + case 'u': /* Vectors of unsigned long */ + case 'y': /* Vectors of byte */ + { + struct object inner; + read_object (&inner, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_token (&token); + free_object (&inner); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + } + /* Unknown # object. But be tolerant. */ + free_token (&token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + } + + case '!': + /* Block comment '#! ... !#'. See + . */ + { + int c; + + comment_start (); + c = do_getc (); + for (;;) + { + if (c == EOF) + break; + if (c == '!') + { + c = do_getc (); + if (c == EOF) + break; + if (c == '#') + { + comment_line_end (0); + break; + } + else + comment_add ('!'); + } + else + { + /* We skip all leading white space. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + if (c == '\n') + { + comment_line_end (1); + comment_start (); + } + c = do_getc (); + } + } + if (c == EOF) + { + /* EOF not allowed here. But be tolerant. */ + op->type = t_eof; + return; + } + last_comment_line = line_number; + continue; + } + + case '|': + /* Block comment '#| ... |#'. See + + and . */ + { + int depth = 0; + int c; + + comment_start (); + c = do_getc (); + for (;;) + { + if (c == EOF) + break; + if (c == '|') + { + c = do_getc (); + if (c == EOF) + break; + if (c == '#') + { + if (depth == 0) + { + comment_line_end (0); + break; + } + depth--; + comment_add ('|'); + comment_add ('#'); + c = do_getc (); + } + else + comment_add ('|'); + } + else if (c == '#') + { + c = do_getc (); + if (c == EOF) + break; + comment_add ('#'); + if (c == '|') + { + depth++; + comment_add ('|'); + c = do_getc (); + } + } + else + { + /* We skip all leading white space. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + if (c == '\n') + { + comment_line_end (1); + comment_start (); + } + c = do_getc (); + } + } + if (c == EOF) + { + /* EOF not allowed here. But be tolerant. */ + op->type = t_eof; + return; + } + last_comment_line = line_number; + continue; + } + + case '*': + /* Bit vector. */ + { + struct token token; + read_token (&token, c); + /* The token should consists only of '0' and '1', except + for the initial '*'. But be tolerant. */ + free_token (&token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case '{': + /* Symbol with multiple escapes: #{...}# */ + { + op->token = XMALLOC (struct token); + + init_token (op->token); + + for (;;) + { + c = do_getc (); + + if (c == EOF) + break; + if (c == '\\') + { + c = do_getc (); + if (c == EOF) + break; + } + else if (c == '}') + { + c = do_getc (); + if (c == '#') + break; + if (c != EOF) + do_ungetc (c); + c = '}'; + } + grow_token (op->token); + op->token->chars[op->token->charcount++] = c; + } + + op->type = t_symbol; + last_non_comment_line = line_number; + return; + } + + case '\\': + /* Character. */ + { + struct token token; + c = do_getc (); + if (c != EOF) + { + read_token (&token, c); + free_token (&token); + } + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + case ':': /* Keyword. */ + case '&': /* Deprecated keyword, installed in optargs.scm. */ + { + struct token token; + read_token (&token, '-'); + free_token (&token); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + /* The following are installed through read-hash-extend. */ + + /* arrays.scm */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* Multidimensional array syntax: #nx(...) where + n ::= DIGIT+ + x ::= {'a'|'b'|'c'|'e'|'i'|'s'|'u'} + */ + do + c = do_getc (); + while (c >= '0' && c <= '9'); + /* c should be one of {'a'|'b'|'c'|'e'|'i'|'s'|'u'}. + But be tolerant. */ + /*FALLTHROUGH*/ + case '\'': /* boot-9.scm */ + case '.': /* boot-9.scm */ + case ',': /* srfi-10.scm */ + { + struct object inner; + read_object (&inner, null_context); + /* Dots and EOF are not allowed here. + But be tolerant. */ + free_object (&inner); + op->type = t_other; + last_non_comment_line = line_number; + return; + } + + default: + /* Unknown. */ + op->type = t_other; + last_non_comment_line = line_number; + return; + } + /*NOTREACHED*/ + abort (); + } + + case '_': + /* GIMP script-fu extension: '_' before a string literal is + considered a gettext call on the string. */ + { + int c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + { + op->type = t_other; + return; + } + if (c != '"') + { + do_ungetc (c); + + /* If '_' is not followed by a string literal, + consider it a part of symbol. */ + op->token = XMALLOC (struct token); + read_token (op->token, '_'); + op->type = t_symbol; + last_non_comment_line = line_number; + return; + } + seen_underscore_prefix = true; + } + /*FALLTHROUGH*/ + + case '"': + { + op->token = XMALLOC (struct token); + init_token (op->token); + op->line_number_at_start = line_number; + for (;;) + { + int c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + if (c == '"') + break; + if (c == '\\') + { + c = do_getc (); + if (c == EOF) + /* Invalid input. Be tolerant, no error message. */ + break; + switch (c) + { + case '\n': + continue; + case '0': + c = '\0'; + break; + case 'a': + c = '\a'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + default: + break; + } + } + grow_token (op->token); + op->token->chars[op->token->charcount++] = c; + } + op->type = t_string; + + if (seen_underscore_prefix || extract_all) + { + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = op->line_number_at_start; + remember_a_message (mlp, NULL, string_of_object (op), + null_context, &pos, NULL, savable_comment); + } + last_non_comment_line = line_number; + return; + } + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '+': case '-': case '.': + /* Read a number or symbol token. */ + op->token = XMALLOC (struct token); + read_token (op->token, c); + if (op->token->charcount == 1 && op->token->chars[0] == '.') + { + free_token (op->token); + free (op->token); + op->type = t_dot; + } + else if (is_number (op->token)) + { + /* A number. */ + free_token (op->token); + free (op->token); + op->type = t_other; + } + else + { + /* A symbol. */ + op->type = t_symbol; + } + last_non_comment_line = line_number; + return; + + case ':': + default: + /* Read a symbol token. */ + op->token = XMALLOC (struct token); + read_token (op->token, c); + op->type = t_symbol; + last_non_comment_line = line_number; + return; + } + } +} + + +void +extract_scheme (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When read_object returns + due to an unbalanced closing parenthesis, just restart it. */ + do + { + struct object toplevel_object; + + read_object (&toplevel_object, null_context); + + if (toplevel_object.type == t_eof) + break; + + free_object (&toplevel_object); + } + while (!feof (fp)); + + /* Close scanner. */ + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-scheme.h b/gettext-tools/src/x-scheme.h new file mode 100644 index 0000000..a159ab1 --- /dev/null +++ b/gettext-tools/src/x-scheme.h @@ -0,0 +1,54 @@ +/* xgettext Scheme backend. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2004. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_SCHEME \ + { "scm", "Scheme" }, \ + +#define SCANNERS_SCHEME \ + { "Scheme", extract_scheme, \ + &flag_table_scheme, &formatstring_scheme, NULL, NULL }, \ + +/* Scan a Scheme file and add its translatable strings to mdlp. */ +extern void extract_scheme (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +/* Handling of options specific to this language. */ + +extern void x_scheme_extract_all (void); +extern void x_scheme_keyword (const char *name); + +extern void init_flag_table_scheme (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-sh.c b/gettext-tools/src/x-sh.c new file mode 100644 index 0000000..9a0614f --- /dev/null +++ b/gettext-tools/src/x-sh.c @@ -0,0 +1,1360 @@ +/* xgettext sh backend. + Copyright (C) 2003, 2005-2009 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-sh.h" + +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "hash.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The sh syntax is defined in POSIX:2001, see + http://www.opengroup.org/onlinepubs/007904975/utilities/xcu_chap02.html + Summary of sh syntax: + - Input is broken into words, which are then subject to + - tilde expansion ~... + - command substitution `...` + - variable substitution $var + - arithmetic substitution $((...)) + - field splitting at whitespace (IFS) + - wildcard pattern expansion *? + - quote removal + - Strings are enclosed in "..."; command substitution, variable + substitution and arithmetic substitution are performed here as well. + - '...' is a string without substitutions. + - The list of resulting words is split into commands by semicolon and + newline. + - '#' at the beginning of a word introduces a comment until end of line. + The parser is implemented in bash-2.05b/parse.y. */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_sh_extract_all () +{ + extract_all = true; +} + + +void +x_sh_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_sh_keyword ("gettext"); + x_sh_keyword ("ngettext:1,2"); + x_sh_keyword ("eval_gettext"); + x_sh_keyword ("eval_ngettext:1,2"); + default_keywords = false; + } +} + +void +init_flag_table_sh () +{ + xgettext_record_flag ("gettext:1:pass-sh-format"); + xgettext_record_flag ("ngettext:1:pass-sh-format"); + xgettext_record_flag ("ngettext:2:pass-sh-format"); + xgettext_record_flag ("eval_gettext:1:sh-format"); + xgettext_record_flag ("eval_ngettext:1:sh-format"); + xgettext_record_flag ("eval_ngettext:2:sh-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* Fetch the next character from the input file. */ +static int +do_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_file_name); + } + else if (c == '\n') + line_number++; + + return c; +} + +/* Put back the last fetched character, not EOF. */ +static void +do_ungetc (int c) +{ + if (c == '\n') + line_number--; + ungetc (c, fp); +} + + +/* Remove backslash followed by newline from the input stream. */ + +static int phase1_pushback[1]; +static int phase1_pushback_length; + +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + { + c = phase1_pushback[--phase1_pushback_length]; + if (c == '\n') + ++line_number; + return c; + } + for (;;) + { + c = do_getc (); + if (c != '\\') + return c; + c = do_getc (); + if (c != '\n') + { + if (c != EOF) + do_ungetc (c); + return '\\'; + } + } +} + +/* Supports only one pushback character. */ +static void +phase1_ungetc (int c) +{ + switch (c) + { + case EOF: + break; + + case '\n': + --line_number; + /* FALLTHROUGH */ + + default: + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + break; + } +} + + +/* ========================== Reading of tokens. ========================== */ + + +/* A token consists of a sequence of characters. */ +struct token +{ + int allocated; /* number of allocated 'token_char's */ + int charcount; /* number of used 'token_char's */ + char *chars; /* the token's constituents */ +}; + +/* Initialize a 'struct token'. */ +static inline void +init_token (struct token *tp) +{ + tp->allocated = 10; + tp->chars = XNMALLOC (tp->allocated, char); + tp->charcount = 0; +} + +/* Free the memory pointed to by a 'struct token'. */ +static inline void +free_token (struct token *tp) +{ + free (tp->chars); +} + +/* Ensure there is enough room in the token for one more character. */ +static inline void +grow_token (struct token *tp) +{ + if (tp->charcount == tp->allocated) + { + tp->allocated *= 2; + tp->chars = (char *) xrealloc (tp->chars, tp->allocated * sizeof (char)); + } +} + +/* Convert a struct token * to a char*. */ +static char * +string_of_token (const struct token *tp) +{ + char *str; + int n; + + n = tp->charcount; + str = XNMALLOC (n + 1, char); + memcpy (str, tp->chars, n); + str[n] = '\0'; + return str; +} + + +/* ========================= Accumulating messages ========================= */ + + +static message_list_ty *mlp; + + +/* ========================= Accumulating comments ========================= */ + + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end () +{ + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ========================= Debackslashification ========================== */ + +/* This state tracks the effect of backquotes, double-quotes and single-quotes + on the parsing of backslashes. We make a single pass through the input + file, keeping the state up to date. This is much faster than accumulating + strings and processing them with explicit debackslashification, like the + shell does it. */ + +/* The number of nested `...` or "`...`" constructs. Assumed to be <= 32. */ +static unsigned int nested_backquotes; + +/* A bit mask indicating which of the currently open `...` or "`...`" + constructs is with double-quotes: "`...`". + A bit value of 1 stands for "`...`", a bit value of 0 stands for `...`. + Bit position 0 designates the outermost backquotes nesting, + bit position 1 the second-outermost backquotes nesting, + ... + bit position (nested_backquotes-1) the innermost backquotes nesting. */ +static unsigned int open_doublequotes_mask; + +/* A bit indicating whether a double-quote is currently open inside the + innermost backquotes nesting. */ +static bool open_doublequote; + +/* A bit indicating whether a single-quote is currently open inside the + innermost backquotes nesting. */ +static bool open_singlequote; + +/* The expected terminator of the currently open single-quote. + Usually '\'', but can be '"' for i18n-quotes. */ +static char open_singlequote_terminator; + + +/* Functions to update the state. */ + +static inline void +saw_opening_backquote () +{ + if (open_singlequote) + abort (); + if (open_doublequote) + open_doublequotes_mask |= (unsigned int) 1 << nested_backquotes; + nested_backquotes++; + open_doublequote = false; +} + +static inline void +saw_closing_backquote () +{ + nested_backquotes--; + open_doublequote = (open_doublequotes_mask >> nested_backquotes) & 1; + open_doublequotes_mask &= ((unsigned int) 1 << nested_backquotes) - 1; + open_singlequote = false; /* just for safety */ +} + +static inline void +saw_opening_doublequote () +{ + if (open_singlequote || open_doublequote) + abort (); + open_doublequote = true; +} + +static inline void +saw_closing_doublequote () +{ + if (open_singlequote || !open_doublequote) + abort (); + open_doublequote = false; +} + +static inline void +saw_opening_singlequote () +{ + if (open_doublequote || open_singlequote) + abort (); + open_singlequote = true; + open_singlequote_terminator = '\''; +} + +static inline void +saw_closing_singlequote () +{ + if (open_doublequote || !open_singlequote) + abort (); + open_singlequote = false; +} + + +/* ========================== Reading of commands ========================== */ + +/* We are only interested in constant strings. Other words need not to be + represented precisely. */ +enum word_type +{ + t_string, /* constant string */ + t_other, /* other string */ + t_separator, /* command separator: semicolon or newline */ + t_redirect, /* redirection: one of < > >| << <<- >> <> <& >& */ + t_backquote, /* closing '`' pseudo word */ + t_paren, /* closing ')' pseudo word */ + t_eof /* EOF marker */ +}; + +struct word +{ + enum word_type type; + struct token *token; /* for t_string */ + int line_number_at_start; /* for t_string */ +}; + +/* Free the memory pointed to by a 'struct word'. */ +static inline void +free_word (struct word *wp) +{ + if (wp->type == t_string) + { + free_token (wp->token); + free (wp->token); + } +} + +/* Convert a t_string token to a char*. */ +static char * +string_of_word (const struct word *wp) +{ + char *str; + int n; + + if (!(wp->type == t_string)) + abort (); + n = wp->token->charcount; + str = XNMALLOC (n + 1, char); + memcpy (str, wp->token->chars, n); + str[n] = '\0'; + return str; +} + + +/* Whitespace recognition. */ + +static inline bool +is_whitespace (int c) +{ + return (c == ' ' || c == '\t' || c == '\n'); +} + +/* Operator character recognition. */ + +static inline bool +is_operator_start (int c) +{ + return (c == '|' || c == '&' || c == ';' || c == '<' || c == '>' + || c == '(' || c == ')'); +} + + +/* Denotation of a quoted character. + The distinction between quoted and unquoted character is important only for + the special, whitespace and operator characters; it is irrelevant for + alphanumeric characters, '\\' and many others. */ +#define QUOTED(c) (UCHAR_MAX + 1 + (c)) +/* Values in the 'unsigned char' range are implicitly unquoted. Among these, + the following are important: + '"' opening or closing double quote + '\'' opening or closing single quote + '$' the unknown result of a dollar expansion + '`' does not occur - replaced with OPENING_BACKQUOTE or + CLOSING_BACKQUOTE + */ +#define OPENING_BACKQUOTE (2 * (UCHAR_MAX + 1) + '`') +#define CLOSING_BACKQUOTE (3 * (UCHAR_MAX + 1) + '`') + +/* 2 characters of pushback are supported. + 2 characters of pushback occur only when the first is an 'x'; in all + other cases only one character of pushback is needed. */ +static int phase2_pushback[2]; +static int phase2_pushback_length; + +/* Return the next character, with backslashes removed. + The result is QUOTED(c) for some unsigned char c, if the next character + is escaped sufficiently often to make it a regular constituent character, + or simply an 'unsigned char' if it has its special meaning (of special, + whitespace or operator charcter), or OPENING_BACKQUOTE, CLOSING_BACKQUOTE, + EOF. + It's the caller's responsibility to update the state. */ +static int +phase2_getc () +{ + int c; + + if (phase2_pushback_length) + { + c = phase2_pushback[--phase2_pushback_length]; + if (c == '\n') + ++line_number; + return c; + } + + c = phase1_getc (); + if (c == EOF) + return c; + if (c == '\'') + return ((open_doublequote + || (open_singlequote && open_singlequote_terminator != c)) + ? QUOTED (c) + : c); + if (open_singlequote) + { + if (c == open_singlequote_terminator) + return c; + } + else + { + if (c == '"' || c == '$') + return c; + if (c == '`') + return (nested_backquotes > 0 ? CLOSING_BACKQUOTE : OPENING_BACKQUOTE); + } + if (c == '\\') + { + /* Number of debackslashification passes that are active at the + current point. */ + unsigned int debackslashify = + nested_backquotes + (open_singlequote ? 0 : 1); + /* Normal number of backslashes that yield a single backslash in the + final output. */ + unsigned int expected_count = + (unsigned int) 1 << debackslashify; + /* Number of backslashes found. */ + unsigned int count; + + for (count = 1; count < expected_count; count++) + { + c = phase1_getc (); + if (c != '\\') + break; + } + if (count == expected_count) + return '\\'; + + /* The count of backslashes is > 0 and < expected_count, therefore the + result depends on c, the first character after the backslashes. + Note: The formulas below don't necessarily have a logic; they were + empirically determined such that 1. the xgettext-30 test succeeds, + 2. the behaviour for count == 0 would correspond to the one without + any baskslash. */ + if (c == '\'') + { + if (!open_singlequote && count > (expected_count >> 1)) + { + phase1_ungetc (c); + return '\\'; + } + else + return ((open_doublequote + || (open_singlequote && open_singlequote_terminator != c)) + ? QUOTED (c) + : c); + } + else if (c == '"') + { + /* Each debackslashification pass converts \\ to \ and \" to "; + passes corresponding to `...` drop a lone " whereas passes + corresponding to "`...`" leave it alone. Therefore, the + minimum number of backslashes needed to get one double-quote + in the end is open_doublequotes_mask + 1. */ + if (open_singlequote) + { + if (count > open_doublequotes_mask) + { + phase1_ungetc (c); + return '\\'; + } + else + return (open_singlequote_terminator != c ? QUOTED (c) : c); + } + else + { + if (count > open_doublequotes_mask) + return QUOTED (c); + else + /* Some of the count values <= open_doublequotes_mask are + actually invalid here, but we assume a syntactically + correct input file anyway. */ + return c; + } + } + else if (c == '`') + { + /* FIXME: This code looks fishy. */ + if (count == expected_count - 1) + return c; + else + /* Some of the count values < expected_count - 1 are + actually invalid here, but we assume a syntactically + correct input file anyway. */ + if (nested_backquotes > 0 && !open_singlequote + && count >= (expected_count >> 2)) + return OPENING_BACKQUOTE; + else + return CLOSING_BACKQUOTE; + } + else if (c == '$') + { + if (open_singlequote) + return QUOTED (c); + if (count >= (expected_count >> 1)) + return QUOTED (c); + else + return c; + } + else + { + /* When not followed by a quoting character or backslash or dollar, + a backslash survives a debackslashification pass unmodified. + Therefore each debackslashification pass performs a + count := (count + 1) >> 1 + operation. Therefore the minimum number of backslashes needed + to get one backslash in the end is (expected_count >> 1) + 1. */ + if (open_doublequote || open_singlequote) + { + if (count > 0) + { + phase1_ungetc (c); + return '\\'; + } + else + return QUOTED (c); + } + else + { + if (count > (expected_count >> 1)) + { + phase1_ungetc (c); + return '\\'; + } + else if (count > 0) + return QUOTED (c); + else + return c; + } + } + } + + return (open_singlequote || open_doublequote ? QUOTED (c) : c); +} + +/* Supports 2 characters of pushback. */ +static void +phase2_ungetc (int c) +{ + switch (c) + { + case EOF: + break; + + case '\n': + --line_number; + /* FALLTHROUGH */ + + default: + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + break; + } +} + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* Forward declaration of local functions. */ +static enum word_type read_command_list (int looking_for, + flag_context_ty outer_context); + + + +/* Read the next word. + 'looking_for' denotes a parse terminator, either CLOSING_BACKQUOTE, ')' + or '\0'. */ +static void +read_word (struct word *wp, int looking_for, flag_context_ty context) +{ + int c; + bool all_unquoted_digits; + + do + { + c = phase2_getc (); + if (c == '#') + { + /* Skip a comment up to end of line. */ + last_comment_line = line_number; + comment_start (); + for (;;) + { + c = phase1_getc (); + if (c == EOF || c == '\n') + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + comment_line_end (); + } + if (c == '\n') + { + /* Comments assumed to be grouped with a message must immediately + precede it, with no non-whitespace token on a line between + both. */ + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + wp->type = t_separator; + return; + } + } + while (is_whitespace (c)); + + if (c == EOF) + { + wp->type = t_eof; + return; + } + + if (c == '<' || c == '>') + { + /* Recognize the redirection operators < > >| << <<- >> <> <& >& + But <( and >) are handled below, not here. */ + int c2 = phase2_getc (); + if (c2 != '(') + { + if ((c == '<' ? c2 == '<' : c2 == '|') || c2 == '>' || c2 == '&') + { + if (c == '<' && c2 == '<') + { + int c3 = phase2_getc (); + if (c3 != '-') + phase2_ungetc (c3); + } + } + else + phase2_ungetc (c2); + wp->type = t_redirect; + return; + } + else + phase2_ungetc (c2); + } + + if (looking_for == CLOSING_BACKQUOTE && c == CLOSING_BACKQUOTE) + { + saw_closing_backquote (); + wp->type = t_backquote; + last_non_comment_line = line_number; + return; + } + + if (looking_for == ')' && c == ')') + { + wp->type = t_paren; + last_non_comment_line = line_number; + return; + } + + if (is_operator_start (c)) + { + wp->type = (c == ';' ? t_separator : t_other); + return; + } + + wp->type = t_string; + wp->token = XMALLOC (struct token); + init_token (wp->token); + wp->line_number_at_start = line_number; + all_unquoted_digits = true; + + for (;; c = phase2_getc ()) + { + if (c == EOF) + break; + + if (all_unquoted_digits && (c == '<' || c == '>')) + { + /* Recognize the redirection operators < > >| << <<- >> <> <& >& + prefixed with a nonempty sequence of unquoted digits. */ + int c2 = phase2_getc (); + if ((c == '<' ? c2 == '<' : c2 == '|') || c2 == '>' || c2 == '&') + { + if (c == '<' && c2 == '<') + { + int c3 = phase2_getc (); + if (c3 != '-') + phase2_ungetc (c3); + } + } + else + phase2_ungetc (c2); + + wp->type = t_redirect; + free_token (wp->token); + free (wp->token); + + last_non_comment_line = line_number; + + return; + } + + all_unquoted_digits = all_unquoted_digits && (c >= '0' && c <= '9'); + + if (c == '$') + { + int c2; + + /* An unquoted dollar indicates we are not inside '...'. */ + if (open_singlequote) + abort (); + /* After reading a dollar, we know that there is no pushed back + character from an earlier lookahead. */ + if (phase2_pushback_length > 0) + abort (); + /* Therefore we can use phase1 without interfering with phase2. + We need to recognize $( outside and inside double-quotes. + It would be incorrect to do + c2 = phase2_getc (); + if (c2 == '(' || c2 == QUOTED ('(')) + because that would also trigger for $\(. */ + c2 = phase1_getc (); + if (c2 == '(') + { + bool saved_open_doublequote; + int c3; + + phase1_ungetc (c2); + + /* The entire inner command or arithmetic expression is read + ignoring possible surrounding double-quotes. */ + saved_open_doublequote = open_doublequote; + open_doublequote = false; + + c2 = phase2_getc (); + if (c2 != '(') + abort (); + + c3 = phase2_getc (); + if (c3 == '(') + { + /* Arithmetic expression (Bash syntax). Skip until the + matching closing parenthesis. */ + unsigned int depth = 2; + + do + { + c = phase2_getc (); + if (c == '(') + depth++; + else if (c == ')') + if (--depth == 0) + break; + } + while (c != EOF); + } + else + { + /* Command substitution (Bash syntax). */ + phase2_ungetc (c3); + read_command_list (')', context); + } + + open_doublequote = saved_open_doublequote; + } + else + { + phase1_ungetc (c2); + c2 = phase2_getc (); + + if (c2 == '\'' && !open_singlequote) + { + /* Bash builtin for string with ANSI-C escape sequences. */ + for (;;) + { + /* We have to use phase1 throughout this loop, + because phase2 does debackslashification, + which is undesirable when parsing ANSI-C + escape sequences. */ + c = phase1_getc (); + if (c == EOF) + break; + if (c == '\'') + break; + if (c == '\\') + { + c = phase1_getc (); + switch (c) + { + default: + phase1_ungetc (c); + c = '\\'; + break; + + case '\\': + break; + case '\'': + break; + case '"': + break; + + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'e': + case 'E': + c = 0x1b; /* ESC */ + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + + case 'x': + c = phase1_getc (); + if ((c >= '0' && c <= '9') + || (c >= 'A' && c <= 'F') + || (c >= 'a' && c <= 'f')) + { + int n; + + if (c >= '0' && c <= '9') + n = c - '0'; + else if (c >= 'A' && c <= 'F') + n = 10 + c - 'A'; + else if (c >= 'a' && c <= 'f') + n = 10 + c - 'a'; + else + abort (); + + c = phase1_getc (); + if ((c >= '0' && c <= '9') + || (c >= 'A' && c <= 'F') + || (c >= 'a' && c <= 'f')) + { + if (c >= '0' && c <= '9') + n = n * 16 + c - '0'; + else if (c >= 'A' && c <= 'F') + n = n * 16 + 10 + c - 'A'; + else if (c >= 'a' && c <= 'f') + n = n * 16 + 10 + c - 'a'; + else + abort (); + } + else + phase1_ungetc (c); + + c = n; + } + else + { + phase1_ungetc (c); + phase1_ungetc ('x'); + c = '\\'; + } + break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + int n = c - '0'; + + c = phase1_getc (); + if (c >= '0' && c <= '7') + { + n = n * 8 + c - '0'; + + c = phase1_getc (); + if (c >= '0' && c <= '7') + n = n * 8 + c - '0'; + else + phase1_ungetc (c); + } + else + phase1_ungetc (c); + + c = n; + } + break; + } + } + if (wp->type == t_string) + { + grow_token (wp->token); + wp->token->chars[wp->token->charcount++] = + (unsigned char) c; + } + } + /* The result is a literal string. Don't change wp->type. */ + continue; + } + else if (c2 == '"' && !open_doublequote) + { + /* Bash builtin for internationalized string. */ + lex_pos_ty pos; + struct token string; + + saw_opening_singlequote (); + open_singlequote_terminator = '"'; + pos.file_name = logical_file_name; + pos.line_number = line_number; + init_token (&string); + for (;;) + { + c = phase2_getc (); + if (c == EOF) + break; + if (c == '"') + { + saw_closing_singlequote (); + break; + } + grow_token (&string); + string.chars[string.charcount++] = (unsigned char) c; + } + remember_a_message (mlp, NULL, string_of_token (&string), + context, &pos, NULL, savable_comment); + free_token (&string); + + error_with_progname = false; + error (0, 0, _("%s:%lu: warning: the syntax $\"...\" is deprecated due to security reasons; use eval_gettext instead"), + pos.file_name, (unsigned long) pos.line_number); + error_with_progname = true; + + /* The result at runtime is not constant. Therefore we + change wp->type. */ + } + else + phase2_ungetc (c2); + } + wp->type = t_other; + continue; + } + + if (c == '\'') + { + if (!open_singlequote) + { + /* Handle an opening single quote. */ + saw_opening_singlequote (); + } + else + { + /* Handle a closing single quote. */ + saw_closing_singlequote (); + } + continue; + } + + if (c == '"') + { + if (open_singlequote && open_singlequote_terminator == '"') + { + /* Handle a closing i18n quote. */ + saw_closing_singlequote (); + } + else if (!open_doublequote) + { + /* Handle an opening double quote. */ + saw_opening_doublequote (); + } + else + { + /* Handle a closing double quote. */ + saw_closing_doublequote (); + } + continue; + } + + if (c == OPENING_BACKQUOTE) + { + /* Handle an opening backquote. */ + saw_opening_backquote (); + + read_command_list (CLOSING_BACKQUOTE, context); + + wp->type = t_other; + continue; + } + if (c == CLOSING_BACKQUOTE) + break; + + if (c == '<' || c == '>') + { + int c2; + + /* An unquoted c indicates we are not inside '...' nor "...". */ + if (open_singlequote || open_doublequote) + abort (); + + c2 = phase2_getc (); + if (c2 == '(') + { + /* Process substitution (Bash syntax). */ + read_command_list (')', context); + + wp->type = t_other; + continue; + } + else + phase2_ungetc (c2); + } + + if (!open_singlequote && !open_doublequote + && (is_whitespace (c) || is_operator_start (c))) + break; + + if (wp->type == t_string) + { + grow_token (wp->token); + wp->token->chars[wp->token->charcount++] = (unsigned char) c; + } + } + + phase2_ungetc (c); + + if (wp->type != t_string) + { + free_token (wp->token); + free (wp->token); + } + last_non_comment_line = line_number; +} + + +/* Read the next command. + 'looking_for' denotes a parse terminator, either CLOSING_BACKQUOTE, ')' + or '\0'. + Returns the type of the word that terminated the command. */ +static enum word_type +read_command (int looking_for, flag_context_ty outer_context) +{ + /* Read the words that make up the command. + Here we completely ignore field splitting at whitespace and wildcard + expansions; i.e. we assume that the source is written in such a way that + every word in the program determines exactly one word in the resulting + command. + But we do not require that the 'gettext'/'ngettext' command is the + first in the command; this is because 1. we want to allow for prefixes + like "$verbose" that may expand to nothing, and 2. it's a big effort + to know where a command starts in a $(for ...) or $(case ...) compound + command. */ + int arg = 0; /* Current argument number. */ + bool arg_of_redirect = false; /* True right after a redirection operator. */ + flag_context_list_iterator_ty context_iter; + const struct callshapes *shapes = NULL; + struct arglist_parser *argparser = NULL; + + for (;;) + { + struct word inner; + flag_context_ty inner_context; + + if (arg == 0) + inner_context = null_context; + else + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + + read_word (&inner, looking_for, inner_context); + + /* Recognize end of command. */ + if (inner.type == t_separator + || inner.type == t_backquote || inner.type == t_paren + || inner.type == t_eof) + { + if (argparser != NULL) + arglist_parser_done (argparser, arg); + return inner.type; + } + + if (extract_all) + { + if (inner.type == t_string) + { + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = inner.line_number_at_start; + remember_a_message (mlp, NULL, string_of_word (&inner), + inner_context, &pos, NULL, savable_comment); + } + } + + if (arg_of_redirect) + { + /* Ignore arguments of redirection operators. */ + arg_of_redirect = false; + } + else if (inner.type == t_redirect) + { + /* Ignore this word and the following one. */ + arg_of_redirect = true; + } + else + { + if (argparser == NULL) + { + /* This is the function position. */ + arg = 0; + if (inner.type == t_string) + { + char *function_name = string_of_word (&inner); + void *keyword_value; + + if (hash_find_entry (&keywords, + function_name, strlen (function_name), + &keyword_value) + == 0) + shapes = (const struct callshapes *) keyword_value; + + argparser = arglist_parser_alloc (mlp, shapes); + + context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + function_name, strlen (function_name))); + + free (function_name); + } + else + context_iter = null_context_list_iterator; + } + else + { + /* These are the argument positions. */ + if (inner.type == t_string) + arglist_parser_remember (argparser, arg, + string_of_word (&inner), + inner_context, + logical_file_name, + inner.line_number_at_start, + savable_comment); + + if (arglist_parser_decidedp (argparser, arg)) + { + /* Stop looking for arguments of the last function_name. */ + /* FIXME: What about context_iter? */ + arglist_parser_done (argparser, arg); + shapes = NULL; + argparser = NULL; + } + } + + arg++; + } + + free_word (&inner); + } +} + + +/* Read a list of commands. + 'looking_for' denotes a parse terminator, either CLOSING_BACKQUOTE, ')' + or '\0'. + Returns the type of the word that terminated the command list. */ +static enum word_type +read_command_list (int looking_for, flag_context_ty outer_context) +{ + for (;;) + { + enum word_type terminator; + + terminator = read_command (looking_for, outer_context); + if (terminator != t_separator) + return terminator; + } +} + + +void +extract_sh (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + nested_backquotes = 0; + open_doublequotes_mask = 0; + open_doublequote = false; + open_singlequote = false; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. */ + read_command_list ('\0', null_context); + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-sh.h b/gettext-tools/src/x-sh.h new file mode 100644 index 0000000..d235751 --- /dev/null +++ b/gettext-tools/src/x-sh.h @@ -0,0 +1,52 @@ +/* xgettext sh backend. + Copyright (C) 2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_SH \ + { "sh", "Shell" }, \ + { "bash", "Shell" }, \ + +#define SCANNERS_SH \ + { "Shell", extract_sh, \ + &flag_table_sh, &formatstring_sh, NULL, NULL }, \ + +/* Scan a shell script file and add its translatable strings to mdlp. */ +extern void extract_sh (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_sh_keyword (const char *keyword); +extern void x_sh_extract_all (void); + +extern void init_flag_table_sh (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-smalltalk.c b/gettext-tools/src/x-smalltalk.c new file mode 100644 index 0000000..c3f9699 --- /dev/null +++ b/gettext-tools/src/x-smalltalk.c @@ -0,0 +1,598 @@ +/* xgettext Smalltalk backend. + Copyright (C) 2002-2003, 2005-2009, 2011 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-smalltalk.h" + +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The relevant parts of the Smalltalk syntax are: + + stringliteral ::= string | stringconst | symconst + stringconst ::= "#"string + string ::= "'"[char]*"'" + symconst ::= "#"symbol + symbol ::= id | binsel | keysel[keysel]* + keysel ::= id":" + id ::= letter[letter|digit]* + letter ::= "A".."Z" | "a".."z" + digit ::= "0".."9" + binsel ::= selchar[selchar] + selchar ::= "+" | "-" | "*" | "/" | "~" | "|" | "," | "<" | ">" + | "=" | "&" | "@" | "?" | "%" | "\" + + Strings can contain any characters; to include the string delimiter itself, + it must be duplicated. + + Character constants are written "$"char + + Comments are enclosed within double quotes. + + In well-formed expressions, {} and [] and () are balanced. + */ + + +/* ======================== Reading of characters. ======================== */ + + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* 1. line_number handling. */ + +static int +phase1_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + + if (c == '\n') + line_number++; + + return c; +} + +/* Supports only one pushback character. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + --line_number; + + ungetc (c, fp); + } +} + + +/* Accumulating comments. */ + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end () +{ + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ========================== Reading of tokens. ========================== */ + + +enum token_type_ty +{ + token_type_eof, + token_type_uniq, /* # */ + token_type_symbol, /* symbol */ + token_type_string_literal, /* string, stringconst, symbolconst */ + token_type_other /* misc. operator */ +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_string_literal, token_type_symbol */ + int line_number; +}; + + +/* 2. Combine characters into tokens. Discard comments and whitespace. */ + +static token_ty phase2_pushback[1]; +static int phase2_pushback_length; + +static void +phase2_get (token_ty *tp) +{ + static char *buffer; + static int bufmax; + int bufpos; + int c; + + if (phase2_pushback_length) + { + *tp = phase2_pushback[--phase2_pushback_length]; + return; + } + + tp->string = NULL; + + for (;;) + { + tp->line_number = line_number; + c = phase1_getc (); + switch (c) + { + case EOF: + tp->type = token_type_eof; + return; + + case '"': + { + /* Comment. */ + int lineno; + + comment_start (); + lineno = line_number; + for (;;) + { + c = phase1_getc (); + if (c == '"' || c == EOF) + break; + if (c == '\n') + { + comment_line_end (); + comment_start (); + } + else + { + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + } + comment_line_end (); + last_comment_line = lineno; + continue; + } + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* FALLTHROUGH */ + case ' ': + case '\t': + case '\r': + /* Ignore whitespace. */ + continue; + } + + last_non_comment_line = tp->line_number; + + switch (c) + { + case '\'': + /* String literal. */ + bufpos = 0; + for (;;) + { + c = phase1_getc (); + if (c == EOF) + break; + if (c == '\'') + { + c = phase1_getc (); + if (c != '\'') + { + phase1_ungetc (c); + break; + } + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = 0; + tp->type = token_type_string_literal; + tp->string = xstrdup (buffer); + return; + + case '+': + case '-': + case '*': + case '/': + case '~': + case '|': + case ',': + case '<': + case '>': + case '=': + case '&': + case '@': + case '?': + case '%': + case '\\': + { + char *name; + int c2 = phase1_getc (); + switch (c2) + { + case '+': + case '-': + case '*': + case '/': + case '~': + case '|': + case ',': + case '<': + case '>': + case '=': + case '&': + case '@': + case '?': + case '%': + name = XNMALLOC (3, char); + name[0] = c; + name[1] = c2; + name[2] = '\0'; + tp->type = token_type_symbol; + tp->string = name; + return; + default: + phase1_ungetc (c2); + break; + } + name = XNMALLOC (2, char); + name[0] = c; + name[1] = '\0'; + tp->type = token_type_symbol; + tp->string = name; + return; + } + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + /* Recognize id or id":"[id":"]* or id":"[id":"]*id. */ + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase1_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + case ':': + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase1_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + continue; + default: + phase1_ungetc (c); + break; + } + break; + default: + phase1_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + tp->string = xstrdup (buffer); + tp->type = token_type_symbol; + return; + + case '#': + /* Uniquification operator. */ + tp->type = token_type_uniq; + return; + + case '$': + c = phase1_getc (); + tp->type = token_type_other; + return; + + default: + tp->type = token_type_other; + return; + } + } +} + +/* Supports only one pushback token. */ +static void +phase2_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = *tp; + } +} + + +/* 3. Combine "# string_literal" and "# symbol" to a single token. */ + +static void +x_smalltalk_lex (token_ty *tp) +{ + phase2_get (tp); + if (tp->type == token_type_uniq) + { + token_ty token2; + + phase2_get (&token2); + if (token2.type == token_type_symbol + || token2.type == token_type_string_literal) + { + tp->type = token_type_string_literal; + tp->string = token2.string; + } + else + phase2_unget (&token2); + } +} + + +/* ========================= Extracting strings. ========================== */ + +/* The file is broken into tokens. Scan the token stream, looking for the + following patterns + NLS ? + NLS at: + NLS at: plural: + where is one of + string_literal + # string_literal + # symbol + */ + +void +extract_smalltalk (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + /* Eat tokens until eof is seen. */ + { + /* 0 when no "NLS" has been seen. + 1 after "NLS". + 2 after "NLS ?". + 3 after "NLS at:". + 4 after "NLS at: ". + 5 after "NLS at: plural:". */ + int state; + /* Remember the message containing the msgid, for msgid_plural. + Non-NULL in states 4, 5. */ + message_ty *plural_mp = NULL; + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_smalltalk_lex (&token); + + switch (token.type) + { + case token_type_symbol: + state = (strcmp (token.string, "NLS") == 0 ? 1 : + strcmp (token.string, "?") == 0 && state == 1 ? 2 : + strcmp (token.string, "at:") == 0 && state == 1 ? 3 : + strcmp (token.string, "plural:") == 0 && state == 4 ? 5 : + 0); + free (token.string); + break; + + case token_type_string_literal: + if (state == 2) + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + remember_a_message (mlp, NULL, token.string, null_context, + &pos, NULL, savable_comment); + state = 0; + break; + } + if (state == 3) + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + plural_mp = remember_a_message (mlp, NULL, token.string, + null_context, &pos, + NULL, savable_comment); + state = 4; + break; + } + if (state == 5) + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + if (plural_mp != NULL) + remember_a_message_plural (plural_mp, token.string, + null_context, &pos, + savable_comment); + state = 0; + break; + } + state = 0; + free (token.string); + break; + + case token_type_uniq: + case token_type_other: + state = 0; + break; + + case token_type_eof: + break; + + default: + abort (); + } + + if (token.type == token_type_eof) + break; + } + } + + /* Close scanner. */ + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-smalltalk.h b/gettext-tools/src/x-smalltalk.h new file mode 100644 index 0000000..10767d9 --- /dev/null +++ b/gettext-tools/src/x-smalltalk.h @@ -0,0 +1,46 @@ +/* xgettext Smalltalk backend. + Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_SMALLTALK \ + { "st", "Smalltalk" }, \ + +#define SCANNERS_SMALLTALK \ + { "Smalltalk", extract_smalltalk, \ + NULL, &formatstring_smalltalk, NULL, NULL }, \ + +/* Scan a Smalltalk file and add its translatable strings to mdlp. */ +extern void extract_smalltalk (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-stringtable.h b/gettext-tools/src/x-stringtable.h new file mode 100644 index 0000000..57df745 --- /dev/null +++ b/gettext-tools/src/x-stringtable.h @@ -0,0 +1,45 @@ +/* xgettext NXStringTable backend. + Copyright (C) 2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_STRINGTABLE \ + { "strings", "NXStringTable" }, \ + +#define SCANNERS_STRINGTABLE \ + { "NXStringTable", extract_stringtable, NULL, NULL, NULL, NULL }, \ + +/* Scan a JavaProperties file and add its translatable strings to mdlp. */ +extern void extract_stringtable (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-tcl.c b/gettext-tools/src/x-tcl.c new file mode 100644 index 0000000..37dd19e --- /dev/null +++ b/gettext-tools/src/x-tcl.c @@ -0,0 +1,999 @@ +/* xgettext Tcl backend. + Copyright (C) 2002-2003, 2005-2009 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-tcl.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "hash.h" +#include "c-ctype.h" +#include "po-charset.h" +#include "unistr.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The Tcl syntax is defined in the Tcl.n manual page. + Summary of Tcl syntax: + Like sh syntax, except that `...` is replaced with [...]. In detail: + - In a preprocessing pass, backslash-newline-anywhitespace is replaced + with single space. + - Input is broken into words, which are then subject to command + substitution [...] , variable substitution $var, backslash substitution + \escape. + - Strings are enclosed in "..."; command substitution, variable + substitution and backslash substitutions are performed here as well. + - {...} is a string without substitutions. + - The list of resulting words is split into commands by semicolon and + newline. + - '#' at the beginning of a command introduces a comment until end of line. + The parser is implemented in tcl8.3.3/generic/tclParse.c. */ + + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_tcl_extract_all () +{ + extract_all = true; +} + + +void +x_tcl_keyword (const char *name) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + + if (keywords.table == NULL) + hash_init (&keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid Tcl + function name. A leading "::" is redundant. */ + if (end - name >= 2 && name[0] == ':' && name[1] == ':') + name += 2; + + insert_keyword_callshape (&keywords, name, end - name, &shape); + } +} + +/* Finish initializing the keywords hash table. + Called after argument processing, before each file is processed. */ +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_tcl_keyword ("::msgcat::mc"); + default_keywords = false; + } +} + +void +init_flag_table_tcl () +{ + xgettext_record_flag ("::msgcat::mc:1:pass-tcl-format"); + xgettext_record_flag ("format:1:tcl-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* Fetch the next character from the input file. */ +static int +do_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), real_file_name); + } + else if (c == '\n') + line_number++; + + return c; +} + +/* Put back the last fetched character, not EOF. */ +static void +do_ungetc (int c) +{ + if (c == '\n') + line_number--; + ungetc (c, fp); +} + + +/* Combine backslash followed by newline and additional whitespace to + a single space. */ + +/* An int that becomes a space when casted to 'unsigned char'. */ +#define BS_NL (UCHAR_MAX + 1 + ' ') + +static int phase1_pushback[1]; +static int phase1_pushback_length; + +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + { + c = phase1_pushback[--phase1_pushback_length]; + if (c == '\n' || c == BS_NL) + ++line_number; + return c; + } + c = do_getc (); + if (c != '\\') + return c; + c = do_getc (); + if (c != '\n') + { + if (c != EOF) + do_ungetc (c); + return '\\'; + } + for (;;) + { + c = do_getc (); + if (!(c == ' ' || c == '\t')) + break; + } + if (c != EOF) + do_ungetc (c); + return BS_NL; +} + +/* Supports only one pushback character. */ +static void +phase1_ungetc (int c) +{ + switch (c) + { + case EOF: + break; + + case '\n': + case BS_NL: + --line_number; + /* FALLTHROUGH */ + + default: + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + break; + } +} + + +/* Keep track of brace nesting depth. + When a word starts with an opening brace, a character group begins that + ends with the corresponding closing brace. In theory these character + groups are string literals, but they are used by so many Tcl primitives + (proc, if, ...) as representing command lists, that we treat them as + command lists. */ + +/* An int that becomes a closing brace when casted to 'unsigned char'. */ +#define CL_BRACE (UCHAR_MAX + 1 + '}') + +static int phase2_pushback[2]; +static int phase2_pushback_length; + +/* Brace nesting depth inside the current character group. */ +static int brace_depth; + +static int +phase2_push () +{ + int previous_depth = brace_depth; + brace_depth = 1; + return previous_depth; +} + +static void +phase2_pop (int previous_depth) +{ + brace_depth = previous_depth; +} + +static int +phase2_getc () +{ + int c; + + if (phase2_pushback_length) + { + c = phase2_pushback[--phase2_pushback_length]; + if (c == '\n' || c == BS_NL) + ++line_number; + else if (c == '{') + ++brace_depth; + else if (c == '}') + --brace_depth; + return c; + } + c = phase1_getc (); + if (c == '{') + ++brace_depth; + else if (c == '}') + { + if (--brace_depth == 0) + c = CL_BRACE; + } + return c; +} + +/* Supports 2 characters of pushback. */ +static void +phase2_ungetc (int c) +{ + if (c != EOF) + { + switch (c) + { + case '\n': + case BS_NL: + --line_number; + break; + + case '{': + --brace_depth; + break; + + case '}': + ++brace_depth; + break; + } + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + } +} + + +/* ========================== Reading of tokens. ========================== */ + + +/* A token consists of a sequence of characters. */ +struct token +{ + int allocated; /* number of allocated 'token_char's */ + int charcount; /* number of used 'token_char's */ + char *chars; /* the token's constituents */ +}; + +/* Initialize a 'struct token'. */ +static inline void +init_token (struct token *tp) +{ + tp->allocated = 10; + tp->chars = XNMALLOC (tp->allocated, char); + tp->charcount = 0; +} + +/* Free the memory pointed to by a 'struct token'. */ +static inline void +free_token (struct token *tp) +{ + free (tp->chars); +} + +/* Ensure there is enough room in the token for one more character. */ +static inline void +grow_token (struct token *tp) +{ + if (tp->charcount == tp->allocated) + { + tp->allocated *= 2; + tp->chars = (char *) xrealloc (tp->chars, tp->allocated * sizeof (char)); + } +} + + +/* ========================= Accumulating comments ========================= */ + + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end () +{ + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* ========================= Accumulating messages ========================= */ + + +static message_list_ty *mlp; + + +/* ========================== Reading of commands ========================== */ + + +/* We are only interested in constant strings (e.g. "msgcat::mc" or other + string literals). Other words need not to be represented precisely. */ +enum word_type +{ + t_string, /* constant string */ + t_other, /* other string */ + t_separator, /* command separator: semicolon or newline */ + t_bracket, /* ']' pseudo word */ + t_brace, /* '}' pseudo word */ + t_eof /* EOF marker */ +}; + +struct word +{ + enum word_type type; + struct token *token; /* for t_string */ + int line_number_at_start; /* for t_string */ +}; + +/* Free the memory pointed to by a 'struct word'. */ +static inline void +free_word (struct word *wp) +{ + if (wp->type == t_string) + { + free_token (wp->token); + free (wp->token); + } +} + +/* Convert a t_string token to a char*. */ +static char * +string_of_word (const struct word *wp) +{ + char *str; + int n; + + if (!(wp->type == t_string)) + abort (); + n = wp->token->charcount; + str = XNMALLOC (n + 1, char); + memcpy (str, wp->token->chars, n); + str[n] = '\0'; + return str; +} + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* Read an escape sequence. The value is an ISO-8859-1 character (in the + range 0x00..0xff) or a Unicode character (in the range 0x0000..0xffff). */ +static int +do_getc_escaped () +{ + int c; + + c = phase1_getc (); + switch (c) + { + case EOF: + return '\\'; + case 'a': + return '\a'; + case 'b': + return '\b'; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'v': + return '\v'; + case 'x': + { + int n = 0; + unsigned int i; + + for (i = 0;; i++) + { + c = phase1_getc (); + if (c == EOF || !c_isxdigit ((unsigned char) c)) + break; + + if (c >= '0' && c <= '9') + n = (n << 4) + (c - '0'); + else if (c >= 'A' && c <= 'F') + n = (n << 4) + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + n = (n << 4) + (c - 'a' + 10); + } + phase1_ungetc (c); + return (i > 0 ? (unsigned char) n : 'x'); + } + case 'u': + { + int n = 0; + unsigned int i; + + for (i = 0; i < 4; i++) + { + c = phase1_getc (); + if (c == EOF || !c_isxdigit ((unsigned char) c)) + { + phase1_ungetc (c); + break; + } + + if (c >= '0' && c <= '9') + n = (n << 4) + (c - '0'); + else if (c >= 'A' && c <= 'F') + n = (n << 4) + (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + n = (n << 4) + (c - 'a' + 10); + } + return (i > 0 ? n : 'u'); + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + { + int n = c - '0'; + + c = phase1_getc (); + if (c != EOF) + { + if (c >= '0' && c <= '7') + { + n = (n << 3) + (c - '0'); + c = phase1_getc (); + if (c != EOF) + { + if (c >= '0' && c <= '7') + n = (n << 3) + (c - '0'); + else + phase1_ungetc (c); + } + } + else + phase1_ungetc (c); + } + return (unsigned char) n; + } + default: + /* Note: If c is non-ASCII, Tcl's behaviour is undefined here. */ + return (unsigned char) c; + } +} + + +enum terminator +{ + te_space_separator, /* looking for space semicolon newline */ + te_space_separator_bracket, /* looking for space semicolon newline ']' */ + te_paren, /* looking for ')' */ + te_quote /* looking for '"' */ +}; + +/* Forward declaration of local functions. */ +static enum word_type read_command_list (int looking_for, + flag_context_ty outer_context); + +/* Accumulate tokens into the given word. + 'looking_for' denotes a parse terminator combination. + Return the first character past the token. */ +static int +accumulate_word (struct word *wp, enum terminator looking_for, + flag_context_ty context) +{ + int c; + + for (;;) + { + c = phase2_getc (); + + if (c == EOF || c == CL_BRACE) + return c; + if ((looking_for == te_space_separator + || looking_for == te_space_separator_bracket) + && (c == ' ' || c == BS_NL + || c == '\t' || c == '\v' || c == '\f' || c == '\r' + || c == ';' || c == '\n')) + return c; + if (looking_for == te_space_separator_bracket && c == ']') + return c; + if (looking_for == te_paren && c == ')') + return c; + if (looking_for == te_quote && c == '"') + return c; + + if (c == '$') + { + /* Distinguish $varname, ${varname} and lone $. */ + c = phase2_getc (); + if (c == '{') + { + /* ${varname} */ + do + c = phase2_getc (); + while (c != EOF && c != '}'); + wp->type = t_other; + } + else + { + bool nonempty = false; + + for (; c != EOF && c != CL_BRACE; c = phase2_getc ()) + { + if (c_isalnum ((unsigned char) c) || (c == '_')) + { + nonempty = true; + continue; + } + if (c == ':') + { + c = phase2_getc (); + if (c == ':') + { + do + c = phase2_getc (); + while (c == ':'); + + phase2_ungetc (c); + nonempty = true; + continue; + } + phase2_ungetc (c); + c = ':'; + } + break; + } + if (c == '(') + { + /* $varname(index) */ + struct word index_word; + + index_word.type = t_other; + c = accumulate_word (&index_word, te_paren, null_context); + if (c != EOF && c != ')') + phase2_ungetc (c); + wp->type = t_other; + } + else + { + phase2_ungetc (c); + if (nonempty) + { + /* $varname */ + wp->type = t_other; + } + else + { + /* lone $ */ + if (wp->type == t_string) + { + grow_token (wp->token); + wp->token->chars[wp->token->charcount++] = '$'; + } + } + } + } + } + else if (c == '[') + { + read_command_list (']', context); + wp->type = t_other; + } + else if (c == '\\') + { + unsigned int uc; + unsigned char utf8buf[6]; + int count; + int i; + + uc = do_getc_escaped (); + assert (uc < 0x10000); + count = u8_uctomb (utf8buf, uc, 6); + assert (count > 0); + if (wp->type == t_string) + for (i = 0; i < count; i++) + { + grow_token (wp->token); + wp->token->chars[wp->token->charcount++] = utf8buf[i]; + } + } + else + { + if (wp->type == t_string) + { + grow_token (wp->token); + wp->token->chars[wp->token->charcount++] = (unsigned char) c; + } + } + } +} + + +/* Read the next word. + 'looking_for' denotes a parse terminator, either ']' or '\0'. */ +static void +read_word (struct word *wp, int looking_for, flag_context_ty context) +{ + int c; + + do + c = phase2_getc (); + while (c == ' ' || c == BS_NL + || c == '\t' || c == '\v' || c == '\f' || c == '\r'); + + if (c == EOF) + { + wp->type = t_eof; + return; + } + + if (c == CL_BRACE) + { + wp->type = t_brace; + last_non_comment_line = line_number; + return; + } + + if (c == '\n') + { + /* Comments assumed to be grouped with a message must immediately + precede it, with no non-whitespace token on a line between both. */ + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + wp->type = t_separator; + return; + } + + if (c == ';') + { + wp->type = t_separator; + last_non_comment_line = line_number; + return; + } + + if (looking_for == ']' && c == ']') + { + wp->type = t_bracket; + last_non_comment_line = line_number; + return; + } + + if (c == '{') + { + int previous_depth; + enum word_type terminator; + + /* Start a new nested character group, which lasts until the next + balanced '}' (ignoring \} things). */ + previous_depth = phase2_push () - 1; + + /* Interpret it as a command list. */ + terminator = read_command_list ('\0', null_context); + + if (terminator == t_brace) + phase2_pop (previous_depth); + + wp->type = t_other; + last_non_comment_line = line_number; + return; + } + + wp->type = t_string; + wp->token = XMALLOC (struct token); + init_token (wp->token); + wp->line_number_at_start = line_number; + + if (c == '"') + { + c = accumulate_word (wp, te_quote, context); + if (c != EOF && c != '"') + phase2_ungetc (c); + } + else + { + phase2_ungetc (c); + c = accumulate_word (wp, + looking_for == ']' + ? te_space_separator_bracket + : te_space_separator, + context); + if (c != EOF) + phase2_ungetc (c); + } + + if (wp->type != t_string) + { + free_token (wp->token); + free (wp->token); + } + last_non_comment_line = line_number; +} + + +/* Read the next command. + 'looking_for' denotes a parse terminator, either ']' or '\0'. + Returns the type of the word that terminated the command: t_separator or + t_bracket (only if looking_for is ']') or t_brace or t_eof. */ +static enum word_type +read_command (int looking_for, flag_context_ty outer_context) +{ + int c; + + /* Skip whitespace and comments. */ + for (;;) + { + c = phase2_getc (); + + if (c == ' ' || c == BS_NL + || c == '\t' || c == '\v' || c == '\f' || c == '\r') + continue; + if (c == '#') + { + /* Skip a comment up to end of line. */ + last_comment_line = line_number; + comment_start (); + for (;;) + { + c = phase2_getc (); + if (c == EOF || c == CL_BRACE || c == '\n') + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + comment_line_end (); + continue; + } + break; + } + phase2_ungetc (c); + + /* Read the words that make up the command. */ + { + int arg = 0; /* Current argument number. */ + flag_context_list_iterator_ty context_iter; + const struct callshapes *shapes = NULL; + struct arglist_parser *argparser = NULL; + + for (;; arg++) + { + struct word inner; + flag_context_ty inner_context; + + if (arg == 0) + inner_context = null_context; + else + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + + read_word (&inner, looking_for, inner_context); + + /* Recognize end of command. */ + if (inner.type == t_separator || inner.type == t_bracket + || inner.type == t_brace || inner.type == t_eof) + { + if (argparser != NULL) + arglist_parser_done (argparser, arg); + return inner.type; + } + + if (extract_all) + { + if (inner.type == t_string) + { + lex_pos_ty pos; + + pos.file_name = logical_file_name; + pos.line_number = inner.line_number_at_start; + remember_a_message (mlp, NULL, string_of_word (&inner), + inner_context, &pos, + NULL, savable_comment); + } + } + + if (arg == 0) + { + /* This is the function position. */ + if (inner.type == t_string) + { + char *function_name = string_of_word (&inner); + char *stripped_name; + void *keyword_value; + + /* A leading "::" is redundant. */ + stripped_name = function_name; + if (function_name[0] == ':' && function_name[1] == ':') + stripped_name += 2; + + if (hash_find_entry (&keywords, + stripped_name, strlen (stripped_name), + &keyword_value) + == 0) + shapes = (const struct callshapes *) keyword_value; + + argparser = arglist_parser_alloc (mlp, shapes); + + context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + stripped_name, strlen (stripped_name))); + + free (function_name); + } + else + context_iter = null_context_list_iterator; + } + else + { + /* These are the argument positions. */ + if (argparser != NULL && inner.type == t_string) + arglist_parser_remember (argparser, arg, + string_of_word (&inner), + inner_context, + logical_file_name, + inner.line_number_at_start, + savable_comment); + } + + free_word (&inner); + } + } +} + + +/* Read a list of commands. + 'looking_for' denotes a parse terminator, either ']' or '\0'. + Returns the type of the word that terminated the command list: + t_bracket (only if looking_for is ']') or t_brace or t_eof. */ +static enum word_type +read_command_list (int looking_for, flag_context_ty outer_context) +{ + for (;;) + { + enum word_type terminator; + + terminator = read_command (looking_for, outer_context); + if (terminator != t_separator) + return terminator; + } +} + + +void +extract_tcl (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + mlp = mdlp->item[0]->messages; + + /* We convert our strings to UTF-8 encoding. */ + xgettext_current_source_encoding = po_charset_utf8; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + /* Initially, no brace is open. */ + brace_depth = 1000000; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. */ + read_command_list ('\0', null_context); + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-tcl.h b/gettext-tools/src/x-tcl.h new file mode 100644 index 0000000..1dc5412 --- /dev/null +++ b/gettext-tools/src/x-tcl.h @@ -0,0 +1,54 @@ +/* xgettext Tcl Lisp backend. + Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2002. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_TCL \ + { "tcl", "Tcl" }, \ + +#define SCANNERS_TCL \ + { "Tcl", extract_tcl, \ + &flag_table_tcl, &formatstring_tcl, NULL, NULL }, \ + +/* Scan a Tcl file and add its translatable strings to mdlp. */ +extern void extract_tcl (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + + +/* Handling of options specific to this language. */ + +extern void x_tcl_extract_all (void); +extern void x_tcl_keyword (const char *name); + +extern void init_flag_table_tcl (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-vala.c b/gettext-tools/src/x-vala.c new file mode 100644 index 0000000..4806b36 --- /dev/null +++ b/gettext-tools/src/x-vala.c @@ -0,0 +1,1250 @@ +/* xgettext Vala backend. + Copyright (C) 2013 Free Software Foundation, Inc. + + This file was written by Daiki Ueno , 2013. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-vala.h" + +#include +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "error-progname.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "hash.h" +#include "po-charset.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + +/* The Vala syntax is defined in the Vala Reference Manual + http://www.vala-project.org/doc/vala/. + See also vala/valascanner.vala. */ + +/* ====================== Keyword set customization. ====================== */ + +/* If true extract all strings. */ +static bool extract_all = false; + +static hash_table keywords; +static bool default_keywords = true; + + +void +x_vala_extract_all () +{ + extract_all = true; +} + + +static void +add_keyword (const char *name, hash_table *keywords) +{ + if (name == NULL) + default_keywords = false; + else + { + const char *end; + struct callshape shape; + const char *colon; + + if (keywords->table == NULL) + hash_init (keywords, 100); + + split_keywordspec (name, &end, &shape); + + /* The characters between name and end should form a valid C identifier. + A colon means an invalid parse in split_keywordspec(). */ + colon = strchr (name, ':'); + if (colon == NULL || colon >= end) + insert_keyword_callshape (keywords, name, end - name, &shape); + } +} + +void +x_vala_keyword (const char *name) +{ + add_keyword (name, &keywords); +} + +static void +init_keywords () +{ + if (default_keywords) + { + /* When adding new keywords here, also update the documentation in + xgettext.texi! */ + x_vala_keyword ("dgettext:2"); + x_vala_keyword ("dcgettext:2"); + x_vala_keyword ("ngettext:1,2"); + x_vala_keyword ("dngettext:2,3"); + x_vala_keyword ("dpgettext:2g"); + x_vala_keyword ("dpgettext2:2c,3"); + x_vala_keyword ("_"); + x_vala_keyword ("Q_"); + x_vala_keyword ("N_"); + x_vala_keyword ("NC_:1c,2"); + + default_keywords = false; + } +} + +void +init_flag_table_vala () +{ + xgettext_record_flag ("dgettext:2:pass-c-format"); + xgettext_record_flag ("dcgettext:2:pass-c-format"); + xgettext_record_flag ("ngettext:1:pass-c-format"); + xgettext_record_flag ("ngettext:2:pass-c-format"); + xgettext_record_flag ("dngettext:2:pass-c-format"); + xgettext_record_flag ("dngettext:3:pass-c-format"); + xgettext_record_flag ("dpgettext:2:pass-c-format"); + xgettext_record_flag ("dpgettext2:3:pass-c-format"); + xgettext_record_flag ("_:1:pass-c-format"); + xgettext_record_flag ("Q_:1:pass-c-format"); + xgettext_record_flag ("N_:1:pass-c-format"); + xgettext_record_flag ("NC_:2:pass-c-format"); + + /* Vala leaves string formatting to Glib functions and thus the + format string is exactly same as C. See also + vapi/glib-2.0.vapi. */ + xgettext_record_flag ("printf:1:c-format"); + xgettext_record_flag ("vprintf:1:c-format"); +} + + +/* ======================== Reading of characters. ======================== */ + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; + +/* The input file stream. */ +static FILE *fp; + + +/* 1. line_number handling. */ + +#define MAX_PHASE1_PUSHBACK 16 +static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK]; +static int phase1_pushback_length; + + +static int +phase1_getc () +{ + int c; + + if (phase1_pushback_length) + c = phase1_pushback[--phase1_pushback_length]; + else + { + c = getc (fp); + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + } + + if (c == '\n') + ++line_number; + return c; +} + + +/* Supports 2 characters of pushback. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + --line_number; + + if (phase1_pushback_length == SIZEOF (phase1_pushback)) + abort (); + phase1_pushback[phase1_pushback_length++] = c; + } +} + + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + +/* Accumulating comments. */ + +static char *buffer; +static size_t bufmax; +static size_t buflen; + +static inline void +comment_start () +{ + buflen = 0; +} + +static inline void +comment_add (int c) +{ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; +} + +static inline void +comment_line_end (size_t chars_to_remove) +{ + buflen -= chars_to_remove; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) + --buflen; + if (chars_to_remove == 0 && buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); +} + + +/* 2. Replace each comment that is not inside a character constant or + string literal with a space character. */ + +static int +phase2_getc () +{ + int c; + bool last_was_star; + + c = phase1_getc (); + if (c != '/') + return c; + c = phase1_getc (); + switch (c) + { + default: + phase1_ungetc (c); + return '/'; + + case '*': + /* C comment. */ + comment_start (); + last_was_star = false; + for (;;) + { + c = phase1_getc (); + if (c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + switch (c) + { + case '\n': + comment_line_end (1); + comment_start (); + last_was_star = false; + continue; + + case '*': + last_was_star = true; + continue; + + case '/': + if (last_was_star) + { + comment_line_end (2); + break; + } + /* FALLTHROUGH */ + + default: + last_was_star = false; + continue; + } + break; + } + last_comment_line = line_number; + return ' '; + + case '/': + /* C++ or ISO C 99 comment. */ + comment_start (); + for (;;) + { + c = phase1_getc (); + if (c == '\n' || c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + comment_add (c); + } + comment_line_end (0); + last_comment_line = line_number; + return '\n'; + } +} + + +static void +phase2_ungetc (int c) +{ + phase1_ungetc (c); +} + + +/* ========================== Reading of tokens. ========================== */ + +enum token_type_ty +{ + token_type_character_constant, /* 'x' */ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_lbrace, /* { */ + token_type_rbrace, /* } */ + token_type_assign, /* = += -= *= /= %= <<= >>= &= |= ^= */ + token_type_return, /* return */ + token_type_plus, /* + */ + token_type_arithmetic_operator, /* - * / % << >> & | ^ */ + token_type_equality_test_operator, /* == < > >= <= != */ + token_type_logic_operator, /* ! && || */ + token_type_comma, /* , */ + token_type_question, /* ? */ + token_type_colon, /* : */ + token_type_number, /* 2.7 */ + token_type_string_literal, /* "abc" */ + token_type_string_template, /* @"abc" */ + token_type_regex_literal, /* /.../ */ + token_type_symbol, /* if else etc. */ + token_type_other +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_symbol, token_type_string_literal */ + refcounted_string_list_ty *comment; /* for token_type_string_literal */ + enum literalstring_escape_type escape; + int line_number; +}; + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_string_literal || tp->type == token_type_symbol) + free (tp->string); + if (tp->type == token_type_string_literal) + drop_reference (tp->comment); +} + + +/* 3. Parse each resulting logical line as preprocessing tokens and + white space. Preprocessing tokens and Vala tokens don't always + match. */ + +static token_ty phase3_pushback[2]; +static int phase3_pushback_length; + + +static token_type_ty last_token_type = token_type_other; + +static void +phase3_scan_regex () +{ + int c; + + for (;;) + { + c = phase1_getc (); + if (c == '/') + break; + if (c == '\\') + { + c = phase1_getc (); + if (c != EOF) + continue; + } + if (c == EOF) + { + error_with_progname = false; + error (0, 0, + _("%s:%d: warning: regular expression literal terminated too early"), + logical_file_name, line_number); + error_with_progname = true; + return; + } + } + + c = phase2_getc (); + if (!(c == 'i' || c == 's' || c == 'm' || c == 'x')) + phase2_ungetc (c); +} + +static void +phase3_get (token_ty *tp) +{ + static char *buffer; + static int bufmax; + int bufpos; + int last_was_backslash; + +#undef APPEND +#define APPEND(c) \ + do \ + { \ + if (bufpos >= bufmax) \ + { \ + bufmax = 2 * bufmax + 10; \ + buffer = xrealloc (buffer, bufmax); \ + } \ + buffer[bufpos++] = c; \ + } \ + while (0) + + if (phase3_pushback_length) + { + *tp = phase3_pushback[--phase3_pushback_length]; + last_token_type = tp->type; + return; + } + + for (;;) + { + bool template; + bool verbatim; + int c; + + tp->line_number = line_number; + c = phase2_getc (); + + switch (c) + { + case EOF: + tp->type = last_token_type = token_type_eof; + return; + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* FALLTHROUGH */ + case ' ': + case '\f': + case '\t': + /* Ignore whitespace and comments. */ + continue; + default: + break; + } + + last_non_comment_line = tp->line_number; + template = false; + verbatim = false; + + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + bufpos = 0; + for (;;) + { + APPEND (c); + c = phase2_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + + default: + phase2_ungetc (c); + break; + } + break; + } + APPEND (0); + if (strcmp (buffer, "return") == 0) + tp->type = last_token_type = token_type_return; + else + { + tp->string = xstrdup (buffer); + tp->type = last_token_type = token_type_symbol; + } + return; + + case '.': + c = phase2_getc (); + phase2_ungetc (c); + switch (c) + { + default: + tp->string = xstrdup ("."); + tp->type = last_token_type = token_type_symbol; + return; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c = '.'; + break; + } + /* FALLTHROUGH */ + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* The preprocessing number token is more "generous" than the C + number tokens. This is mostly due to token pasting (another + thing we can ignore here). */ + bufpos = 0; + for (;;) + { + APPEND (c); + c = phase2_getc (); + switch (c) + { + case 'e': + case 'E': + APPEND (c); + c = phase2_getc (); + if (c != '+' && c != '-') + { + phase2_ungetc (c); + break; + } + continue; + + case 'A': case 'B': case 'C': case 'D': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '.': + continue; + + default: + phase2_ungetc (c); + break; + } + break; + } + APPEND (0); + tp->type = last_token_type = token_type_number; + return; + + case '\'': + last_was_backslash = false; + for (;;) + { + c = phase2_getc (); + if (last_was_backslash) + { + last_was_backslash = false; + continue; + } + switch (c) + { + case '\\': + last_was_backslash = true; + /* FALLTHROUGH */ + default: + continue; + case '\n': + error_with_progname = false; + error (0, 0, _("%s:%d: warning: unterminated character constant"), + logical_file_name, line_number - 1); + error_with_progname = true; + phase2_ungetc ('\n'); + break; + case EOF: case '\'': + break; + } + break; + } + tp->type = last_token_type = token_type_character_constant; + return; + + /* Vala provides strings in three different formats. + + Usual string literals: + "..." + Verbatim string literals: + """...""" (where ... can include newlines and double quotes) + String templates. + @"...", @"""...""" + + Note that, with the current implementation string + templates are not subject to translation, because they are + inspected at compile time. For example, the following code + + string bar = "bar"; + string foo = _(@"foo $bar"); + + will be translated into the C code, like: + + _(g_strconcat ("foo ", "bar", NULL)); */ + case '@': + c = phase2_getc (); + if (c != '"') + { + phase2_ungetc (c); + tp->type = last_token_type = token_type_other; + return; + } + template = true; + /* FALLTHROUGH */ + case '"': + { + int c2 = phase2_getc (); + + if (c2 == '"') + { + int c3 = phase2_getc (); + if (c3 == '"') + verbatim = true; + else + { + phase2_ungetc (c3); + phase2_ungetc (c2); + } + } + else + phase2_ungetc (c2); + + if (verbatim) + { + bufpos = 0; + for (;;) + { + /* Use phase 1, because phase 2 elides comments. */ + c = phase1_getc (); + if (c == EOF) + break; + + if (c == '"') + { + int c2 = phase1_getc (); + if (c2 == '"') + { + int c3 = phase1_getc (); + if (c3 == '"') + break; + phase1_ungetc (c3); + } + phase1_ungetc (c2); + } + APPEND (c); + } + } + else + { + last_was_backslash = false; + bufpos = 0; + for (;;) + { + c = phase1_getc (); + if (last_was_backslash) + { + last_was_backslash = false; + APPEND (c); + continue; + } + + switch (c) + { + case '\\': + last_was_backslash = true; + /* FALLTHROUGH */ + default: + APPEND (c); + continue; + + case '\n': + error_with_progname = false; + error (0, 0, _("\ +%s:%d: warning: unterminated string literal"), + logical_file_name, line_number - 1); + error_with_progname = true; + phase1_ungetc ('\n'); + break; + case EOF: case '"': + break; + } + break; + } + } + APPEND (0); + tp->type = last_token_type = template + ? token_type_string_template : token_type_string_literal; + tp->string = xstrdup (buffer); + tp->comment = add_reference (savable_comment); + tp->escape = verbatim ? 0 : LET_ANSI_C | LET_UNICODE; + return; + } + + case '/': + switch (last_token_type) + { + case token_type_lparen: + case token_type_lbrace: + case token_type_assign: + case token_type_return: + case token_type_plus: + case token_type_arithmetic_operator: + case token_type_equality_test_operator: + case token_type_logic_operator: + case token_type_comma: + case token_type_question: + case token_type_colon: + phase3_scan_regex (); + tp->type = last_token_type = token_type_regex_literal; + break; + default: + { + int c2 = phase2_getc (); + if (c2 == '=') + tp->type = last_token_type = token_type_assign; + else + { + phase2_ungetc (c2); + tp->type = last_token_type = token_type_arithmetic_operator; + } + break; + } + } + return; + + case '(': + tp->type = last_token_type = token_type_lparen; + return; + + case ')': + tp->type = last_token_type = token_type_rparen; + return; + + case '{': + tp->type = last_token_type = token_type_lbrace; + return; + + case '}': + tp->type = last_token_type = token_type_rbrace; + return; + + case '+': + { + int c2 = phase2_getc (); + switch (c2) + { + case '+': + tp->type = last_token_type = token_type_other; + break; + case '=': + tp->type = last_token_type = token_type_assign; + break; + default: + phase2_ungetc (c2); + tp->type = last_token_type = token_type_plus; + break; + } + return; + } + + case '-': + { + int c2 = phase2_getc (); + switch (c2) + { + case '-': + tp->type = last_token_type = token_type_other; + break; + case '=': + tp->type = last_token_type = token_type_assign; + break; + default: + phase2_ungetc (c2); + tp->type = last_token_type = token_type_arithmetic_operator; + break; + } + return; + } + + case '%': + case '^': + { + int c2 = phase2_getc (); + if (c2 == '=') + tp->type = last_token_type = token_type_assign; + else + { + phase2_ungetc (c2); + tp->type = last_token_type = token_type_logic_operator; + } + return; + } + + case '=': + { + int c2 = phase2_getc (); + switch (c2) + { + case '=': + tp->type = last_token_type = token_type_equality_test_operator; + break; + case '>': + tp->type = last_token_type = token_type_other; + break; + default: + phase2_ungetc (c2); + tp->type = last_token_type = token_type_assign; + break; + } + return; + } + + case '!': + { + int c2 = phase2_getc (); + if (c2 == '=') + tp->type = last_token_type = token_type_equality_test_operator; + else + { + phase2_ungetc (c2); + tp->type = last_token_type = token_type_logic_operator; + } + return; + } + + case '>': + case '<': + { + int c2 = phase2_getc (); + if (c2 == '=') + tp->type = last_token_type = token_type_equality_test_operator; + else if (c2 == c) + { + int c3 = phase2_getc (); + if (c3 == '=') + tp->type = last_token_type = token_type_assign; + else + { + phase2_ungetc (c2); + phase2_ungetc (c3); + tp->type = last_token_type = token_type_other; + } + } + else + { + phase2_ungetc (c2); + tp->type = last_token_type = token_type_equality_test_operator; + } + return; + } + + case ',': + tp->type = last_token_type = token_type_comma; + return; + + case ':': + tp->type = last_token_type = token_type_colon; + return; + + case '&': + case '|': + { + int c2 = phase2_getc (); + if (c2 == c) + tp->type = last_token_type = token_type_logic_operator; + else if (c2 == '=') + tp->type = last_token_type = token_type_assign; + else + { + phase2_ungetc (c2); + tp->type = last_token_type = token_type_arithmetic_operator; + } + return; + } + + case '?': + { + int c2 = phase2_getc (); + if (c2 == '?') + tp->type = last_token_type = token_type_logic_operator; + else + { + phase2_ungetc (c2); + tp->type = last_token_type = token_type_question; + } + return; + } + + default: + tp->type = last_token_type = token_type_other; + return; + } + } +#undef APPEND +} + +static void +phase3_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase3_pushback_length == SIZEOF (phase3_pushback)) + abort (); + phase3_pushback[phase3_pushback_length++] = *tp; + } +} + + +/* String concatenation with '+'. */ + +static void +x_vala_lex (token_ty *tp) +{ + phase3_get (tp); + if (tp->type == token_type_string_literal) + { + char *sum = tp->string; + size_t sum_len = strlen (sum); + + for (;;) + { + token_ty token2; + + phase3_get (&token2); + if (token2.type == token_type_plus) + { + token_ty token3; + + phase3_get (&token3); + if (token3.type == token_type_string_literal) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = (char *) xrealloc (sum, sum_len + addend_len + 1); + memcpy (sum + sum_len, addend, addend_len + 1); + sum_len += addend_len; + + free_token (&token3); + free_token (&token2); + continue; + } + phase3_unget (&token3); + } + phase3_unget (&token2); + break; + } + tp->string = sum; + } +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + +/* Use the same literalstring_parser provided by the C scanner. */ +extern struct literalstring_parser literalstring_c; + +/* The file is broken into tokens. Scan the token stream, looking for + a keyword, followed by a left paren, followed by a string. When we + see this sequence, we have something to remember. We assume we are + looking at a valid Vala program, and leave the complaints about the + grammar to the compiler. + + Normal handling: Look for + keyword ( ... msgid ... ) + keyword msgid + Plural handling: Look for + keyword ( ... msgid ... msgid_plural ... ) + + We use recursion because the arguments before msgid or between msgid + and msgid_plural can contain subexpressions of the same form. */ + +/* Extract messages until the next balanced closing parenthesis or bracket. + Extracted messages are added to MLP. + DELIM can be either token_type_rparen or token_type_rbracket, or + token_type_eof to accept both. + Return true upon eof, false upon closing parenthesis or bracket. */ +static bool +extract_balanced (message_list_ty *mlp, token_type_ty delim, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + struct arglist_parser *argparser) +{ + /* Current argument number. */ + int arg = 1; + /* 0 when no keyword has been seen. 1 right after a keyword is seen. */ + int state; + /* Parameters of the keyword just seen. Defined only in state 1. */ + const struct callshapes *next_shapes = NULL; + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0. */ + state = 0; + + for (;;) + { + token_ty token; + + x_vala_lex (&token); + + switch (token.type) + { + case token_type_symbol: + { + void *keyword_value; + + if (hash_find_entry (&keywords, token.string, strlen (token.string), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + } + else + state = 0; + } + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, strlen (token.string))); + free (token.string); + continue; + + case token_type_lparen: + if (extract_balanced (mlp, token_type_rparen, + inner_context, next_context_iter, + arglist_parser_alloc (mlp, + state ? next_shapes : NULL))) + { + arglist_parser_done (argparser, arg); + return true; + } + next_context_iter = null_context_list_iterator; + state = 0; + break; + + case token_type_rparen: + if (delim == token_type_rparen || delim == token_type_eof) + { + arglist_parser_done (argparser, arg); + return false; + } + + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_comma: + arg++; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + state = 0; + continue; + + case token_type_eof: + arglist_parser_done (argparser, arg); + return true; + + case token_type_string_literal: + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + if (extract_all) + { + char *string; + refcounted_string_list_ty *comment; + const char *encoding; + + string = literalstring_c.parse (token.string, &pos, + token.escape); + free (token.string); + token.string = string; + + if (token.comment != NULL) + { + comment = savable_comment_convert_encoding (token.comment, + &pos); + drop_reference (token.comment); + token.comment = comment; + } + + /* token.string and token.comment are already converted + to UTF-8. Prevent further conversion in + remember_a_message. */ + encoding = xgettext_current_source_encoding; + xgettext_current_source_encoding = po_charset_utf8; + remember_a_message (mlp, NULL, token.string, inner_context, + &pos, NULL, token.comment); + xgettext_current_source_encoding = encoding; + } + else + { + /* A string immediately after a symbol means a + function call. */ + if (state) + { + struct arglist_parser *tmp_argparser; + tmp_argparser = arglist_parser_alloc (mlp, next_shapes); + + arglist_parser_remember_literal (tmp_argparser, 1, + token.string, + inner_context, + pos.file_name, + pos.line_number, + token.comment, + token.escape); + arglist_parser_done (tmp_argparser, 1); + } + else + arglist_parser_remember_literal (argparser, arg, token.string, + inner_context, pos.file_name, + pos.line_number, + token.comment, + token.escape); + } + } + drop_reference (token.comment); + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_character_constant: + case token_type_lbrace: + case token_type_rbrace: + case token_type_assign: + case token_type_return: + case token_type_plus: + case token_type_arithmetic_operator: + case token_type_equality_test_operator: + case token_type_logic_operator: + case token_type_question: + case token_type_colon: + case token_type_number: + case token_type_string_template: + case token_type_regex_literal: + case token_type_other: + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + default: + abort (); + } + } +} + +void +extract_vala (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + init_keywords (); + + /* Eat tokens until eof is seen. When extract_parenthesized returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_balanced (mlp, token_type_eof, + null_context, null_context_list_iterator, + arglist_parser_alloc (mlp, NULL))) + ; + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; +} diff --git a/gettext-tools/src/x-vala.h b/gettext-tools/src/x-vala.h new file mode 100644 index 0000000..9eb730b --- /dev/null +++ b/gettext-tools/src/x-vala.h @@ -0,0 +1,50 @@ +/* xgettext Vala backend. + Copyright (C) 2002-2003, 2006, 2013 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_VALA \ + { "vala", "Vala" }, \ + +#define SCANNERS_VALA \ + { "Vala", extract_vala, \ + &flag_table_vala, &formatstring_c, NULL, &literalstring_c }, \ + +/* Scan a Vala file and add its translatable strings to mdlp. */ +extern void extract_vala (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void x_vala_keyword (const char *keyword); +extern void x_vala_extract_all (void); + +extern void init_flag_table_vala (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/x-ycp.c b/gettext-tools/src/x-ycp.c new file mode 100644 index 0000000..ff8642b --- /dev/null +++ b/gettext-tools/src/x-ycp.c @@ -0,0 +1,788 @@ +/* xgettext YCP backend. + Copyright (C) 2001-2003, 2005-2009, 2011 Free Software Foundation, Inc. + + This file was written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "x-ycp.h" + +#include +#include +#include +#include +#include + +#include "message.h" +#include "xgettext.h" +#include "error.h" +#include "xalloc.h" +#include "gettext.h" + +#define _(s) gettext(s) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + + +/* The YCP syntax is defined in libycp/doc/syntax.html. + See also libycp/src/scanner.ll. + Both are part of the yast2-core package in SuSE Linux distributions. */ + + +void +init_flag_table_ycp () +{ + xgettext_record_flag ("sformat:1:ycp-format"); + xgettext_record_flag ("y2debug:1:ycp-format"); + xgettext_record_flag ("y2milestone:1:ycp-format"); + xgettext_record_flag ("y2warning:1:ycp-format"); + xgettext_record_flag ("y2error:1:ycp-format"); + xgettext_record_flag ("y2security:1:ycp-format"); + xgettext_record_flag ("y2internal:1:ycp-format"); +} + + +/* ======================== Reading of characters. ======================== */ + + +/* Real filename, used in error messages about the input file. */ +static const char *real_file_name; + +/* Logical filename and line number, used to label the extracted messages. */ +static char *logical_file_name; +static int line_number; +static int char_in_line; + +/* The input file stream. */ +static FILE *fp; + +/* These are for tracking whether comments count as immediately before + keyword. */ +static int last_comment_line; +static int last_non_comment_line; + + +/* 1. line_number handling. */ + +static int +phase1_getc () +{ + int c = getc (fp); + + if (c == EOF) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("error while reading \"%s\""), + real_file_name); + return EOF; + } + + if (c == '\n') + { + line_number++; + char_in_line = 0; + } + else + char_in_line++; + + return c; +} + +/* Supports only one pushback character. */ +static void +phase1_ungetc (int c) +{ + if (c != EOF) + { + if (c == '\n') + { + --line_number; + char_in_line = INT_MAX; + } + else + --char_in_line; + + ungetc (c, fp); + } +} + + +/* 2. Replace each comment that is not inside a character constant or + string literal with a space character. We need to remember the + comment for later, because it may be attached to a keyword string. + YCP comments can be in C comment syntax, C++ comment syntax or sh + comment syntax. */ + +static unsigned char phase2_pushback[1]; +static int phase2_pushback_length; + +static int +phase2_getc () +{ + static char *buffer; + static size_t bufmax; + size_t buflen; + int lineno; + int c; + bool last_was_star; + + if (phase2_pushback_length) + return phase2_pushback[--phase2_pushback_length]; + + if (char_in_line == 0) + { + /* Eat whitespace, to recognize ^[\t ]*# pattern. */ + do + c = phase1_getc (); + while (c == '\t' || c == ' '); + + if (c == '#') + { + /* sh comment. */ + buflen = 0; + lineno = line_number; + for (;;) + { + c = phase1_getc (); + if (c == '\n' || c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + { + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; + } + } + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); + last_comment_line = lineno; + return '\n'; + } + } + else + c = phase1_getc (); + + if (c == '/') + { + c = phase1_getc (); + + switch (c) + { + default: + phase1_ungetc (c); + return '/'; + + case '*': + /* C comment. */ + buflen = 0; + lineno = line_number; + last_was_star = false; + for (;;) + { + c = phase1_getc (); + if (c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (buflen == 0 && (c == ' ' || c == '\t')) + continue; + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; + switch (c) + { + case '\n': + --buflen; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' + || buffer[buflen - 1] == '\t')) + --buflen; + buffer[buflen] = '\0'; + savable_comment_add (buffer); + buflen = 0; + lineno = line_number; + last_was_star = false; + continue; + + case '*': + last_was_star = true; + continue; + + case '/': + if (last_was_star) + { + buflen -= 2; + while (buflen >= 1 + && (buffer[buflen - 1] == ' ' + || buffer[buflen - 1] == '\t')) + --buflen; + buffer[buflen] = '\0'; + savable_comment_add (buffer); + break; + } + /* FALLTHROUGH */ + + default: + last_was_star = false; + continue; + } + break; + } + last_comment_line = lineno; + return ' '; + + case '/': + /* C++ comment. */ + buflen = 0; + lineno = line_number; + for (;;) + { + c = phase1_getc (); + if (c == '\n' || c == EOF) + break; + /* We skip all leading white space, but not EOLs. */ + if (!(buflen == 0 && (c == ' ' || c == '\t'))) + { + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; + } + } + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + savable_comment_add (buffer); + last_comment_line = lineno; + return '\n'; + } + } + else + return c; +} + +/* Supports only one pushback character. */ +static void +phase2_ungetc (int c) +{ + if (c != EOF) + { + if (phase2_pushback_length == SIZEOF (phase2_pushback)) + abort (); + phase2_pushback[phase2_pushback_length++] = c; + } +} + + +/* ========================== Reading of tokens. ========================== */ + + +enum token_type_ty +{ + token_type_eof, + token_type_lparen, /* ( */ + token_type_rparen, /* ) */ + token_type_comma, /* , */ + token_type_i18n, /* _( */ + token_type_string_literal, /* "abc" */ + token_type_symbol, /* symbol, number */ + token_type_other /* misc. operator */ +}; +typedef enum token_type_ty token_type_ty; + +typedef struct token_ty token_ty; +struct token_ty +{ + token_type_ty type; + char *string; /* for token_type_string_literal, token_type_symbol */ + refcounted_string_list_ty *comment; /* for token_type_string_literal */ + int line_number; +}; + + +/* 7. Replace escape sequences within character strings with their + single character equivalents. */ + +#define P7_QUOTES (1000 + '"') + +static int +phase7_getc () +{ + int c; + + for (;;) + { + /* Use phase 1, because phase 2 elides comments. */ + c = phase1_getc (); + + if (c == '"') + return P7_QUOTES; + if (c != '\\') + return c; + c = phase1_getc (); + if (c != '\n') + switch (c) + { + case 'b': + return '\b'; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + + /* FIXME: What is the octal escape syntax? + syntax.html says: [0] [0-7]+ + scanner.ll says: [0-7] [0-7] [0-7] + */ +#if 0 + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + int n, j; + + n = 0; + for (j = 0; j < 3; ++j) + { + n = n * 8 + c - '0'; + c = phase1_getc (); + switch (c) + { + default: + break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + continue; + } + break; + } + phase1_ungetc (c); + return n; + } +#endif + + default: + return c; + } + } +} + + +/* Free the memory pointed to by a 'struct token_ty'. */ +static inline void +free_token (token_ty *tp) +{ + if (tp->type == token_type_string_literal || tp->type == token_type_symbol) + free (tp->string); + if (tp->type == token_type_string_literal) + drop_reference (tp->comment); +} + + +/* Combine characters into tokens. Discard whitespace. */ + +static token_ty phase5_pushback[1]; +static int phase5_pushback_length; + +static void +phase5_get (token_ty *tp) +{ + static char *buffer; + static int bufmax; + int bufpos; + int c; + + if (phase5_pushback_length) + { + *tp = phase5_pushback[--phase5_pushback_length]; + return; + } + for (;;) + { + tp->line_number = line_number; + c = phase2_getc (); + + switch (c) + { + case EOF: + tp->type = token_type_eof; + return; + + case '\n': + if (last_non_comment_line > last_comment_line) + savable_comment_reset (); + /* FALLTHROUGH */ + case '\r': + case '\t': + case ' ': + /* Ignore whitespace and comments. */ + continue; + } + + last_non_comment_line = tp->line_number; + + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* Symbol, or part of a number. */ + bufpos = 0; + for (;;) + { + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + c = phase2_getc (); + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': + case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': + case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + default: + if (bufpos == 1 && buffer[0] == '_' && c == '(') + { + tp->type = token_type_i18n; + return; + } + phase2_ungetc (c); + break; + } + break; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + tp->string = xstrdup (buffer); + tp->type = token_type_symbol; + return; + + case '"': + bufpos = 0; + for (;;) + { + c = phase7_getc (); + if (c == EOF || c == P7_QUOTES) + break; + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos++] = c; + } + if (bufpos >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[bufpos] = '\0'; + tp->string = xstrdup (buffer); + tp->type = token_type_string_literal; + tp->comment = add_reference (savable_comment); + return; + + case '(': + tp->type = token_type_lparen; + return; + + case ')': + tp->type = token_type_rparen; + return; + + case ',': + tp->type = token_type_comma; + return; + + default: + /* We could carefully recognize each of the 2 and 3 character + operators, but it is not necessary, as we only need to recognize + gettext invocations. Don't bother. */ + tp->type = token_type_other; + return; + } + } +} + +/* Supports only one pushback token. */ +static void +phase5_unget (token_ty *tp) +{ + if (tp->type != token_type_eof) + { + if (phase5_pushback_length == SIZEOF (phase5_pushback)) + abort (); + phase5_pushback[phase5_pushback_length++] = *tp; + } +} + + +/* Concatenate adjacent string literals to form single string literals. + (See libycp/src/parser.yy, rule 'string' vs. terminal 'STRING'.) */ + +static void +phase8_get (token_ty *tp) +{ + phase5_get (tp); + if (tp->type != token_type_string_literal) + return; + for (;;) + { + token_ty tmp; + size_t len; + + phase5_get (&tmp); + if (tmp.type != token_type_string_literal) + { + phase5_unget (&tmp); + return; + } + len = strlen (tp->string); + tp->string = xrealloc (tp->string, len + strlen (tmp.string) + 1); + strcpy (tp->string + len, tmp.string); + free_token (&tmp); + } +} + + +/* ========================= Extracting strings. ========================== */ + + +/* Context lookup table. */ +static flag_context_list_table_ty *flag_context_list_table; + + +/* The file is broken into tokens. + + Normal handling: Look for + [A] _( [B] msgid ... ) + Plural handling: Look for + [A] _( [B] msgid [C] , [D] msgid_plural ... ) + At point [A]: state == 0. + At point [B]: state == 1, plural_mp == NULL. + At point [C]: state == 2, plural_mp != NULL. + At point [D]: state == 1, plural_mp != NULL. + + We use recursion because we have to set the context according to the given + flags. */ + + +/* Extract messages until the next balanced closing parenthesis. + Extracted messages are added to MLP. + Return true upon eof, false upon closing parenthesis. */ +static bool +extract_parenthesized (message_list_ty *mlp, + flag_context_ty outer_context, + flag_context_list_iterator_ty context_iter, + bool in_i18n) +{ + int state; /* 1 or 2 inside _( ... ), otherwise 0 */ + int plural_state = 0; /* defined only when in states 1 and 2 */ + message_ty *plural_mp = NULL; /* defined only when in states 1 and 2 */ + /* Context iterator that will be used if the next token is a '('. */ + flag_context_list_iterator_ty next_context_iter = + passthrough_context_list_iterator; + /* Current context. */ + flag_context_ty inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance (&context_iter)); + + /* Start state is 0 or 1. */ + state = (in_i18n ? 1 : 0); + + for (;;) + { + token_ty token; + + if (in_i18n) + phase8_get (&token); + else + phase5_get (&token); + + switch (token.type) + { + case token_type_i18n: + if (extract_parenthesized (mlp, inner_context, next_context_iter, + true)) + return true; + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_string_literal: + if (state == 1) + { + lex_pos_ty pos; + pos.file_name = logical_file_name; + pos.line_number = token.line_number; + + if (plural_state == 0) + { + /* Seen an msgid. */ + plural_mp = remember_a_message (mlp, NULL, token.string, + inner_context, &pos, + NULL, token.comment); + plural_state = 1; + state = 2; + } + else + { + /* Seen an msgid_plural. */ + if (plural_mp != NULL) + remember_a_message_plural (plural_mp, token.string, + inner_context, &pos, + token.comment); + state = 0; + } + drop_reference (token.comment); + } + else + { + free_token (&token); + state = 0; + } + next_context_iter = null_context_list_iterator; + continue; + + case token_type_symbol: + next_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + token.string, strlen (token.string))); + free_token (&token); + state = 0; + continue; + + case token_type_lparen: + if (extract_parenthesized (mlp, inner_context, next_context_iter, + false)) + return true; + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_rparen: + return false; + + case token_type_comma: + if (state == 2) + state = 1; + else + state = 0; + inner_context = + inherited_context (outer_context, + flag_context_list_iterator_advance ( + &context_iter)); + next_context_iter = passthrough_context_list_iterator; + continue; + + case token_type_other: + next_context_iter = null_context_list_iterator; + state = 0; + continue; + + case token_type_eof: + return true; + + default: + abort (); + } + } +} + + +void +extract_ycp (FILE *f, + const char *real_filename, const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp) +{ + message_list_ty *mlp = mdlp->item[0]->messages; + + fp = f; + real_file_name = real_filename; + logical_file_name = xstrdup (logical_filename); + line_number = 1; + char_in_line = 0; + + last_comment_line = -1; + last_non_comment_line = -1; + + flag_context_list_table = flag_table; + + /* Eat tokens until eof is seen. When extract_parenthesized returns + due to an unbalanced closing parenthesis, just restart it. */ + while (!extract_parenthesized (mlp, null_context, null_context_list_iterator, + false)) + ; + + fp = NULL; + real_file_name = NULL; + logical_file_name = NULL; + line_number = 0; + char_in_line = 0; +} diff --git a/gettext-tools/src/x-ycp.h b/gettext-tools/src/x-ycp.h new file mode 100644 index 0000000..78cc853 --- /dev/null +++ b/gettext-tools/src/x-ycp.h @@ -0,0 +1,48 @@ +/* xgettext YCP backend. + Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc. + Written by Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include + +#include "message.h" +#include "xgettext.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define EXTENSIONS_YCP \ + { "ycp", "YCP" }, \ + +#define SCANNERS_YCP \ + { "YCP", extract_ycp, \ + &flag_table_ycp, &formatstring_ycp, NULL, NULL }, \ + +/* Scan an YCP file and add its translatable strings to mdlp. */ +extern void extract_ycp (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +extern void init_flag_table_ycp (void); + + +#ifdef __cplusplus +} +#endif diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c new file mode 100644 index 0000000..28d28a0 --- /dev/null +++ b/gettext-tools/src/xgettext.c @@ -0,0 +1,3701 @@ +/* Extracts strings from C source file to Uniforum style .po file. + Copyright (C) 1995-1998, 2000-2012 Free Software Foundation, Inc. + Written by Ulrich Drepper , April 1995. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xgettext.h" +#include "closeout.h" +#include "dir-list.h" +#include "file-list.h" +#include "str-list.h" +#include "error.h" +#include "error-progname.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "xerror.h" +#include "xvasprintf.h" +#include "xsize.h" +#include "xalloc.h" +#include "xmalloca.h" +#include "c-strstr.h" +#include "xerror.h" +#include "filename.h" +#include "concat-filename.h" +#include "c-strcase.h" +#include "open-catalog.h" +#include "read-catalog-abstract.h" +#include "read-po.h" +#include "message.h" +#include "po-charset.h" +#include "msgl-iconv.h" +#include "msgl-ascii.h" +#include "po-time.h" +#include "write-catalog.h" +#include "write-po.h" +#include "write-properties.h" +#include "write-stringtable.h" +#include "color.h" +#include "format.h" +#include "propername.h" +#include "unistr.h" +#include "gettext.h" + +/* A convenience macro. I don't like writing gettext() every time. */ +#define _(str) gettext (str) + + +#include "x-c.h" +#include "x-po.h" +#include "x-sh.h" +#include "x-python.h" +#include "x-lisp.h" +#include "x-elisp.h" +#include "x-librep.h" +#include "x-scheme.h" +#include "x-smalltalk.h" +#include "x-java.h" +#include "x-properties.h" +#include "x-csharp.h" +#include "x-awk.h" +#include "x-ycp.h" +#include "x-tcl.h" +#include "x-perl.h" +#include "x-php.h" +#include "x-stringtable.h" +#include "x-rst.h" +#include "x-glade.h" +#include "x-lua.h" +#include "x-javascript.h" +#include "x-vala.h" +#include "x-gsettings.h" +#include "x-desktop.h" + + +/* If nonzero add all comments immediately preceding one of the keywords. */ +static bool add_all_comments = false; + +/* Tag used in comment of prevailing domain. */ +static char *comment_tag; + +/* Name of default domain file. If not set defaults to messages.po. */ +static const char *default_domain; + +/* If called with --debug option the output reflects whether format + string recognition is done automatically or forced by the user. */ +static int do_debug; + +/* Content of .po files with symbols to be excluded. */ +message_list_ty *exclude; + +/* Force output of PO file even if empty. */ +static int force_po; + +/* Copyright holder of the output file and the translations. */ +static const char *copyright_holder = "THE PACKAGE'S COPYRIGHT HOLDER"; + +/* Package name. */ +static const char *package_name = NULL; + +/* Package version. */ +static const char *package_version = NULL; + +/* Email address or URL for reports of bugs in msgids. */ +static const char *msgid_bugs_address = NULL; + +/* String used as prefix for msgstr. */ +static const char *msgstr_prefix; + +/* String used as suffix for msgstr. */ +static const char *msgstr_suffix; + +/* Directory in which output files are created. */ +static char *output_dir; + +/* The output syntax: .pot or .properties or .strings. */ +static catalog_output_format_ty output_syntax = &output_format_po; + +/* If nonzero omit header with information about this run. */ +int xgettext_omit_header; + +/* Table of flag_context_list_ty tables. */ +static flag_context_list_table_ty flag_table_c; +static flag_context_list_table_ty flag_table_cxx_qt; +static flag_context_list_table_ty flag_table_cxx_kde; +static flag_context_list_table_ty flag_table_cxx_boost; +static flag_context_list_table_ty flag_table_objc; +static flag_context_list_table_ty flag_table_gcc_internal; +static flag_context_list_table_ty flag_table_sh; +static flag_context_list_table_ty flag_table_python; +static flag_context_list_table_ty flag_table_lisp; +static flag_context_list_table_ty flag_table_elisp; +static flag_context_list_table_ty flag_table_librep; +static flag_context_list_table_ty flag_table_scheme; +static flag_context_list_table_ty flag_table_java; +static flag_context_list_table_ty flag_table_csharp; +static flag_context_list_table_ty flag_table_awk; +static flag_context_list_table_ty flag_table_ycp; +static flag_context_list_table_ty flag_table_tcl; +static flag_context_list_table_ty flag_table_perl; +static flag_context_list_table_ty flag_table_php; +static flag_context_list_table_ty flag_table_lua; +static flag_context_list_table_ty flag_table_javascript; +static flag_context_list_table_ty flag_table_vala; + +/* If true, recognize Qt format strings. */ +static bool recognize_format_qt; + +/* If true, recognize KDE format strings. */ +static bool recognize_format_kde; + +/* If true, recognize Boost format strings. */ +static bool recognize_format_boost; + +/* Canonicalized encoding name for all input files. */ +const char *xgettext_global_source_encoding; + +#if HAVE_ICONV +/* Converter from xgettext_global_source_encoding to UTF-8 (except from + ASCII or UTF-8, when this conversion is a no-op). */ +iconv_t xgettext_global_source_iconv; +#endif + +/* Canonicalized encoding name for the current input file. */ +const char *xgettext_current_source_encoding; + +#if HAVE_ICONV +/* Converter from xgettext_current_source_encoding to UTF-8 (except from + ASCII or UTF-8, when this conversion is a no-op). */ +iconv_t xgettext_current_source_iconv; +#endif + +/* Long options. */ +static const struct option long_options[] = +{ + { "add-comments", optional_argument, NULL, 'c' }, + { "add-location", optional_argument, NULL, 'n' }, + { "boost", no_argument, NULL, CHAR_MAX + 11 }, + { "c++", no_argument, NULL, 'C' }, + { "color", optional_argument, NULL, CHAR_MAX + 14 }, + { "copyright-holder", required_argument, NULL, CHAR_MAX + 1 }, + { "debug", no_argument, &do_debug, 1 }, + { "default-domain", required_argument, NULL, 'd' }, + { "directory", required_argument, NULL, 'D' }, + { "escape", no_argument, NULL, 'E' }, + { "exclude-file", required_argument, NULL, 'x' }, + { "extract-all", no_argument, NULL, 'a' }, + { "files-from", required_argument, NULL, 'f' }, + { "flag", required_argument, NULL, CHAR_MAX + 8 }, + { "force-po", no_argument, &force_po, 1 }, + { "foreign-user", no_argument, NULL, CHAR_MAX + 2 }, + { "from-code", required_argument, NULL, CHAR_MAX + 3 }, + { "help", no_argument, NULL, 'h' }, + { "indent", no_argument, NULL, 'i' }, + { "join-existing", no_argument, NULL, 'j' }, + { "kde", no_argument, NULL, CHAR_MAX + 10 }, + { "keyword", optional_argument, NULL, 'k' }, + { "language", required_argument, NULL, 'L' }, + { "msgid-bugs-address", required_argument, NULL, CHAR_MAX + 5 }, + { "msgstr-prefix", optional_argument, NULL, 'm' }, + { "msgstr-suffix", optional_argument, NULL, 'M' }, + { "no-escape", no_argument, NULL, 'e' }, + { "no-location", no_argument, NULL, CHAR_MAX + 16 }, + { "no-wrap", no_argument, NULL, CHAR_MAX + 4 }, + { "omit-header", no_argument, &xgettext_omit_header, 1 }, + { "output", required_argument, NULL, 'o' }, + { "output-dir", required_argument, NULL, 'p' }, + { "package-name", required_argument, NULL, CHAR_MAX + 12 }, + { "package-version", required_argument, NULL, CHAR_MAX + 13 }, + { "properties-output", no_argument, NULL, CHAR_MAX + 6 }, + { "qt", no_argument, NULL, CHAR_MAX + 9 }, + { "sort-by-file", no_argument, NULL, 'F' }, + { "sort-output", no_argument, NULL, 's' }, + { "strict", no_argument, NULL, 'S' }, + { "string-limit", required_argument, NULL, 'l' }, + { "stringtable-output", no_argument, NULL, CHAR_MAX + 7 }, + { "style", required_argument, NULL, CHAR_MAX + 15 }, + { "trigraphs", no_argument, NULL, 'T' }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w', }, + { NULL, 0, NULL, 0 } +}; + + +/* The extractors must all be functions returning void and taking three + arguments designating the input stream and one message domain list argument + in which to add the messages. */ +typedef void (*extractor_func) (FILE *fp, const char *real_filename, + const char *logical_filename, + flag_context_list_table_ty *flag_table, + msgdomain_list_ty *mdlp); + +typedef struct extractor_ty extractor_ty; +struct extractor_ty +{ + extractor_func func; + flag_context_list_table_ty *flag_table; + struct formatstring_parser *formatstring_parser1; + struct formatstring_parser *formatstring_parser2; + struct formatstring_parser *formatstring_parser3; + struct literalstring_parser *literalstring_parser; +}; + + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ > 4) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void read_exclusion_file (char *file_name); +static void extract_from_file (const char *file_name, extractor_ty extractor, + msgdomain_list_ty *mdlp); +static message_ty *construct_header (void); +static void finalize_header (msgdomain_list_ty *mdlp); +static extractor_ty language_to_extractor (const char *name); +static const char *extension_to_language (const char *extension); + + +int +main (int argc, char *argv[]) +{ + int optchar; + bool do_help = false; + bool do_version = false; + msgdomain_list_ty *mdlp; + bool join_existing = false; + bool no_default_keywords = false; + bool some_additional_keywords = false; + bool sort_by_msgid = false; + bool sort_by_filepos = false; + const char *file_name; + const char *files_from = NULL; + string_list_ty *file_list; + char *output_file = NULL; + const char *language = NULL; + extractor_ty extractor = { NULL, NULL, NULL, NULL }; + int cnt; + size_t i; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Set initial value of variables. */ + default_domain = MESSAGE_DOMAIN_DEFAULT; + xgettext_global_source_encoding = po_charset_ascii; + init_flag_table_c (); + init_flag_table_objc (); + init_flag_table_gcc_internal (); + init_flag_table_sh (); + init_flag_table_python (); + init_flag_table_lisp (); + init_flag_table_elisp (); + init_flag_table_librep (); + init_flag_table_scheme (); + init_flag_table_java (); + init_flag_table_csharp (); + init_flag_table_awk (); + init_flag_table_ycp (); + init_flag_table_tcl (); + init_flag_table_perl (); + init_flag_table_php (); + init_flag_table_lua (); + init_flag_table_javascript (); + init_flag_table_vala (); + + while ((optchar = getopt_long (argc, argv, + "ac::Cd:D:eEf:Fhijk::l:L:m::M::no:p:sTVw:x:", + long_options, NULL)) != EOF) + switch (optchar) + { + case '\0': /* Long option. */ + break; + + case 'a': + x_c_extract_all (); + x_sh_extract_all (); + x_python_extract_all (); + x_lisp_extract_all (); + x_elisp_extract_all (); + x_librep_extract_all (); + x_scheme_extract_all (); + x_java_extract_all (); + x_csharp_extract_all (); + x_awk_extract_all (); + x_tcl_extract_all (); + x_perl_extract_all (); + x_php_extract_all (); + x_glade_extract_all (); + x_lua_extract_all (); + x_javascript_extract_all (); + x_vala_extract_all (); + break; + + case 'c': + if (optarg == NULL) + { + add_all_comments = true; + comment_tag = NULL; + } + else + { + add_all_comments = false; + comment_tag = optarg; + /* We ignore leading white space. */ + while (isspace ((unsigned char) *comment_tag)) + ++comment_tag; + } + break; + + case 'C': + language = "C++"; + break; + + case 'd': + default_domain = optarg; + break; + + case 'D': + dir_list_append (optarg); + break; + + case 'e': + message_print_style_escape (false); + break; + + case 'E': + message_print_style_escape (true); + break; + + case 'f': + files_from = optarg; + break; + + case 'F': + sort_by_filepos = true; + break; + + case 'h': + do_help = true; + break; + + case 'i': + message_print_style_indent (); + break; + + case 'j': + join_existing = true; + break; + + case 'k': + if (optarg != NULL && *optarg == '\0') + /* Make "--keyword=" work like "--keyword" and "-k". */ + optarg = NULL; + x_c_keyword (optarg); + x_objc_keyword (optarg); + x_sh_keyword (optarg); + x_python_keyword (optarg); + x_lisp_keyword (optarg); + x_elisp_keyword (optarg); + x_librep_keyword (optarg); + x_scheme_keyword (optarg); + x_java_keyword (optarg); + x_csharp_keyword (optarg); + x_awk_keyword (optarg); + x_tcl_keyword (optarg); + x_perl_keyword (optarg); + x_php_keyword (optarg); + x_glade_keyword (optarg); + x_lua_keyword (optarg); + x_javascript_keyword (optarg); + x_vala_keyword (optarg); + x_desktop_keyword (optarg); + if (optarg == NULL) + no_default_keywords = true; + else + some_additional_keywords = true; + break; + + case 'l': + /* Accepted for backward compatibility with 0.10.35. */ + break; + + case 'L': + language = optarg; + break; + + case 'm': + /* -m takes an optional argument. If none is given "" is assumed. */ + msgstr_prefix = optarg == NULL ? "" : optarg; + break; + + case 'M': + /* -M takes an optional argument. If none is given "" is assumed. */ + msgstr_suffix = optarg == NULL ? "" : optarg; + break; + + case 'n': + if (handle_filepos_comment_option (optarg)) + usage (EXIT_FAILURE); + break; + + case 'o': + output_file = optarg; + break; + + case 'p': + { + size_t len = strlen (optarg); + + if (output_dir != NULL) + free (output_dir); + + if (optarg[len - 1] == '/') + output_dir = xstrdup (optarg); + else + output_dir = xasprintf ("%s/", optarg); + } + break; + + case 's': + sort_by_msgid = true; + break; + + case 'S': + message_print_style_uniforum (); + break; + + case 'T': + x_c_trigraphs (); + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + case 'x': + read_exclusion_file (optarg); + break; + + case CHAR_MAX + 1: /* --copyright-holder */ + copyright_holder = optarg; + break; + + case CHAR_MAX + 2: /* --foreign-user */ + copyright_holder = ""; + break; + + case CHAR_MAX + 3: /* --from-code */ + xgettext_global_source_encoding = po_charset_canonicalize (optarg); + if (xgettext_global_source_encoding == NULL) + { + multiline_warning (xasprintf (_("warning: ")), + xasprintf (_("\ +'%s' is not a valid encoding name. Using ASCII as fallback.\n"), + optarg)); + xgettext_global_source_encoding = po_charset_ascii; + } + break; + + case CHAR_MAX + 4: /* --no-wrap */ + message_page_width_ignore (); + break; + + case CHAR_MAX + 5: /* --msgid-bugs-address */ + msgid_bugs_address = optarg; + break; + + case CHAR_MAX + 6: /* --properties-output */ + output_syntax = &output_format_properties; + break; + + case CHAR_MAX + 7: /* --stringtable-output */ + output_syntax = &output_format_stringtable; + break; + + case CHAR_MAX + 8: /* --flag */ + xgettext_record_flag (optarg); + break; + + case CHAR_MAX + 9: /* --qt */ + recognize_format_qt = true; + break; + + case CHAR_MAX + 10: /* --kde */ + recognize_format_kde = true; + break; + + case CHAR_MAX + 11: /* --boost */ + recognize_format_boost = true; + break; + + case CHAR_MAX + 12: /* --package-name */ + package_name = optarg; + break; + + case CHAR_MAX + 13: /* --package-version */ + package_version = optarg; + break; + + case CHAR_MAX + 14: /* --color */ + if (handle_color_option (optarg) || color_test_mode) + usage (EXIT_FAILURE); + break; + + case CHAR_MAX + 15: /* --style */ + handle_style_option (optarg); + break; + + case CHAR_MAX + 16: /* --no-location */ + message_print_style_filepos (filepos_comment_none); + break; + + default: + usage (EXIT_FAILURE); + /* NOTREACHED */ + } + + /* Version information requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +License GPLv3+: GNU GPL version 3 or later \n\ +This is free software: you are free to change and redistribute it.\n\ +There is NO WARRANTY, to the extent permitted by law.\n\ +"), + "1995-1998, 2000-2013"); + printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper")); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Verify selected options. */ + if (sort_by_msgid && sort_by_filepos) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--sort-output", "--sort-by-file"); + + /* We cannot support both Qt and KDE, or Qt and Boost, or KDE and Boost + format strings, because there are only two formatstring parsers per + language, and formatstring_c is the first one for C++. */ + if (recognize_format_qt && recognize_format_kde) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--qt", "--kde"); + if (recognize_format_qt && recognize_format_boost) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--qt", "--boost"); + if (recognize_format_kde && recognize_format_boost) + error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), + "--kde", "--boost"); + + if (join_existing && strcmp (default_domain, "-") == 0) + error (EXIT_FAILURE, 0, _("\ +--join-existing cannot be used when output is written to stdout")); + + if (no_default_keywords && !some_additional_keywords) + { + error (0, 0, _("\ +xgettext cannot work without keywords to look for")); + usage (EXIT_FAILURE); + } + + /* Test whether we have some input files given. */ + if (files_from == NULL && optind >= argc) + { + error (EXIT_SUCCESS, 0, _("no input file given")); + usage (EXIT_FAILURE); + } + + /* Determine extractor from language. */ + if (language != NULL) + extractor = language_to_extractor (language); + + /* Canonize msgstr prefix/suffix. */ + if (msgstr_prefix != NULL && msgstr_suffix == NULL) + msgstr_suffix = ""; + else if (msgstr_prefix == NULL && msgstr_suffix != NULL) + msgstr_prefix = ""; + + /* Default output directory is the current directory. */ + if (output_dir == NULL) + output_dir = "."; + + /* Construct the name of the output file. If the default domain has + the special name "-" we write to stdout. */ + if (output_file) + { + if (IS_ABSOLUTE_PATH (output_file) || strcmp (output_file, "-") == 0) + file_name = xstrdup (output_file); + else + /* Please do NOT add a .po suffix! */ + file_name = xconcatenated_filename (output_dir, output_file, NULL); + } + else if (strcmp (default_domain, "-") == 0) + file_name = "-"; + else + file_name = xconcatenated_filename (output_dir, default_domain, ".po"); + + /* Determine list of files we have to process. */ + if (files_from != NULL) + file_list = read_names_from_file (files_from); + else + file_list = string_list_alloc (); + /* Append names from command line. */ + for (cnt = optind; cnt < argc; ++cnt) + string_list_append_unique (file_list, argv[cnt]); + + /* Allocate converter from xgettext_global_source_encoding to UTF-8 (except + from ASCII or UTF-8, when this conversion is a no-op). */ + if (xgettext_global_source_encoding != po_charset_ascii + && xgettext_global_source_encoding != po_charset_utf8) + { +#if HAVE_ICONV + iconv_t cd; + + /* Avoid glibc-2.1 bug with EUC-KR. */ +# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \ + && !defined _LIBICONV_VERSION + if (strcmp (xgettext_global_source_encoding, "EUC-KR") == 0) + cd = (iconv_t)(-1); + else +# endif + cd = iconv_open (po_charset_utf8, xgettext_global_source_encoding); + if (cd == (iconv_t)(-1)) + error (EXIT_FAILURE, 0, _("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(), \ +and iconv() does not support this conversion."), + xgettext_global_source_encoding, po_charset_utf8, + basename (program_name)); + xgettext_global_source_iconv = cd; +#else + error (EXIT_FAILURE, 0, _("\ +Cannot convert from \"%s\" to \"%s\". %s relies on iconv(). \ +This version was built without iconv()."), + xgettext_global_source_encoding, po_charset_utf8, + basename (program_name)); +#endif + } + + /* Allocate a message list to remember all the messages. */ + mdlp = msgdomain_list_alloc (true); + + /* Generate a header, so that we know how and when this PO file was + created. */ + if (!xgettext_omit_header) + message_list_append (mdlp->item[0]->messages, construct_header ()); + + /* Read in the old messages, so that we can add to them. */ + if (join_existing) + { + /* Temporarily reset the directory list to empty, because file_name + is an output file and therefore should not be searched for. */ + void *saved_directory_list = dir_list_save_reset (); + extractor_ty po_extractor = { extract_po, NULL, NULL, NULL }; + + extract_from_file (file_name, po_extractor, mdlp); + if (!is_ascii_msgdomain_list (mdlp)) + mdlp = iconv_msgdomain_list (mdlp, "UTF-8", true, file_name); + + dir_list_restore (saved_directory_list); + } + + /* Process all input files. */ + for (i = 0; i < file_list->nitems; i++) + { + const char *filename; + extractor_ty this_file_extractor; + + filename = file_list->item[i]; + + if (extractor.func) + this_file_extractor = extractor; + else + { + const char *base; + char *reduced; + const char *extension; + const char *language; + const char *p; + + base = strrchr (filename, '/'); + if (!base) + base = filename; + + reduced = xstrdup (base); + /* Remove a trailing ".in" - it's a generic suffix. */ + while (strlen (reduced) >= 3 + && memcmp (reduced + strlen (reduced) - 3, ".in", 3) == 0) + reduced[strlen (reduced) - 3] = '\0'; + + /* Work out what the file extension is. */ + language = NULL; + p = reduced + strlen (reduced); + for (; p > reduced && language == NULL; p--) + { + if (*p == '.') + { + extension = p + 1; + + /* Derive the language from the extension, and the extractor + function from the language. */ + language = extension_to_language (extension); + } + } + + if (language == NULL) + { + extension = strrchr (reduced, '.'); + if (extension == NULL) + extension = ""; + else + extension++; + error (0, 0, _("\ +warning: file '%s' extension '%s' is unknown; will try C"), filename, extension); + language = "C"; + } + this_file_extractor = language_to_extractor (language); + + free (reduced); + } + + /* Extract the strings from the file. */ + extract_from_file (filename, this_file_extractor, mdlp); + } + string_list_free (file_list); + + /* Finalize the constructed header. */ + if (!xgettext_omit_header) + finalize_header (mdlp); + + /* Free the allocated converter. */ +#if HAVE_ICONV + if (xgettext_global_source_encoding != po_charset_ascii + && xgettext_global_source_encoding != po_charset_utf8) + iconv_close (xgettext_global_source_iconv); +#endif + + /* Sorting the list of messages. */ + if (sort_by_filepos) + msgdomain_list_sort_by_filepos (mdlp); + else if (sort_by_msgid) + msgdomain_list_sort_by_msgid (mdlp); + + /* Write the PO file. */ + msgdomain_list_print (mdlp, file_name, output_syntax, force_po, do_debug); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try '%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [INPUTFILE]...\n\ +"), program_name); + printf ("\n"); + printf (_("\ +Extract translatable strings from given input files.\n\ +")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +Similarly for optional arguments.\n\ +")); + printf ("\n"); + printf (_("\ +Input file location:\n")); + printf (_("\ + INPUTFILE ... input files\n")); + printf (_("\ + -f, --files-from=FILE get list of input files from FILE\n")); + printf (_("\ + -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); + printf (_("\ +If input file is -, standard input is read.\n")); + printf ("\n"); + printf (_("\ +Output file location:\n")); + printf (_("\ + -d, --default-domain=NAME use NAME.po for output (instead of messages.po)\n")); + printf (_("\ + -o, --output=FILE write output to specified file\n")); + printf (_("\ + -p, --output-dir=DIR output files will be placed in directory DIR\n")); + printf (_("\ +If output file is -, output is written to standard output.\n")); + printf ("\n"); + printf (_("\ +Choice of input file language:\n")); + printf (_("\ + -L, --language=NAME recognise the specified language\n\ + (C, C++, ObjectiveC, PO, Shell, Python, Lisp,\n\ + EmacsLisp, librep, Scheme, Smalltalk, Java,\n\ + JavaProperties, C#, awk, YCP, Tcl, Perl, PHP,\n\ + GCC-source, NXStringTable, RST, Glade, Lua,\n\ + JavaScript, Vala, Desktop)\n")); + printf (_("\ + -C, --c++ shorthand for --language=C++\n")); + printf (_("\ +By default the language is guessed depending on the input file name extension.\n")); + printf ("\n"); + printf (_("\ +Input file interpretation:\n")); + printf (_("\ + --from-code=NAME encoding of input files\n\ + (except for Python, Tcl, Glade)\n")); + printf (_("\ +By default the input files are assumed to be in ASCII.\n")); + printf ("\n"); + printf (_("\ +Operation mode:\n")); + printf (_("\ + -j, --join-existing join messages with existing file\n")); + printf (_("\ + -x, --exclude-file=FILE.po entries from FILE.po are not extracted\n")); + printf (_("\ + -cTAG, --add-comments=TAG place comment blocks starting with TAG and\n\ + preceding keyword lines in output file\n\ + -c, --add-comments place all comment blocks preceding keyword lines\n\ + in output file\n")); + printf ("\n"); + printf (_("\ +Language specific options:\n")); + printf (_("\ + -a, --extract-all extract all strings\n")); + printf (_("\ + (only languages C, C++, ObjectiveC, Shell,\n\ + Python, Lisp, EmacsLisp, librep, Scheme, Java,\n\ + C#, awk, Tcl, Perl, PHP, GCC-source, Glade,\n\ + Lua, JavaScript, Vala)\n")); + printf (_("\ + -kWORD, --keyword=WORD look for WORD as an additional keyword\n\ + -k, --keyword do not to use default keywords\n")); + printf (_("\ + (only languages C, C++, ObjectiveC, Shell,\n\ + Python, Lisp, EmacsLisp, librep, Scheme, Java,\n\ + C#, awk, Tcl, Perl, PHP, GCC-source, Glade,\n\ + Lua, JavaScript, Vala, Desktop)\n")); + printf (_("\ + --flag=WORD:ARG:FLAG additional flag for strings inside the argument\n\ + number ARG of keyword WORD\n")); + printf (_("\ + (only languages C, C++, ObjectiveC, Shell,\n\ + Python, Lisp, EmacsLisp, librep, Scheme, Java,\n\ + C#, awk, YCP, Tcl, Perl, PHP, GCC-source,\n\ + Lua, JavaScript, Vala)\n")); + printf (_("\ + -T, --trigraphs understand ANSI C trigraphs for input\n")); + printf (_("\ + (only languages C, C++, ObjectiveC)\n")); + printf (_("\ + --qt recognize Qt format strings\n")); + printf (_("\ + (only language C++)\n")); + printf (_("\ + --kde recognize KDE 4 format strings\n")); + printf (_("\ + (only language C++)\n")); + printf (_("\ + --boost recognize Boost format strings\n")); + printf (_("\ + (only language C++)\n")); + printf (_("\ + --debug more detailed formatstring recognition result\n")); + printf ("\n"); + printf (_("\ +Output details:\n")); + printf (_("\ + --color use colors and other text attributes always\n\ + --color=WHEN use colors and other text attributes if WHEN.\n\ + WHEN may be 'always', 'never', 'auto', or 'html'.\n")); + printf (_("\ + --style=STYLEFILE specify CSS style rule file for --color\n")); + printf (_("\ + -e, --no-escape do not use C escapes in output (default)\n")); + printf (_("\ + -E, --escape use C escapes in output, no extended chars\n")); + printf (_("\ + --force-po write PO file even if empty\n")); + printf (_("\ + -i, --indent write the .po file using indented style\n")); + printf (_("\ + --no-location do not write '#: filename:line' lines\n")); + printf (_("\ + -n, --add-location generate '#: filename:line' lines (default)\n")); + printf (_("\ + --strict write out strict Uniforum conforming .po file\n")); + printf (_("\ + --properties-output write out a Java .properties file\n")); + printf (_("\ + --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); + printf (_("\ + -w, --width=NUMBER set output page width\n")); + printf (_("\ + --no-wrap do not break long message lines, longer than\n\ + the output page width, into several lines\n")); + printf (_("\ + -s, --sort-output generate sorted output\n")); + printf (_("\ + -F, --sort-by-file sort output by file location\n")); + printf (_("\ + --omit-header don't write header with 'msgid \"\"' entry\n")); + printf (_("\ + --copyright-holder=STRING set copyright holder in output\n")); + printf (_("\ + --foreign-user omit FSF copyright in output for foreign user\n")); + printf (_("\ + --package-name=PACKAGE set package name in output\n")); + printf (_("\ + --package-version=VERSION set package version in output\n")); + printf (_("\ + --msgid-bugs-address=EMAIL@ADDRESS set report address for msgid bugs\n")); + printf (_("\ + -m[STRING], --msgstr-prefix[=STRING] use STRING or \"\" as prefix for msgstr\n\ + values\n")); + printf (_("\ + -M[STRING], --msgstr-suffix[=STRING] use STRING or \"\" as suffix for msgstr\n\ + values\n")); + printf ("\n"); + printf (_("\ +Informative output:\n")); + printf (_("\ + -h, --help display this help and exit\n")); + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* TRANSLATORS: The placeholder indicates the bug-reporting address + for this package. Please add _another line_ saying + "Report translation bugs to <...>\n" with the address for translation + bugs (typically your translation team's web or email address). */ + fputs (_("Report bugs to .\n"), + stdout); + } + + exit (status); +} + + +static void +exclude_directive_domain (abstract_catalog_reader_ty *pop, char *name) +{ + po_gram_error_at_line (&gram_pos, + _("this file may not contain domain directives")); +} + + +static void +exclude_directive_message (abstract_catalog_reader_ty *pop, + char *msgctxt, + char *msgid, + lex_pos_ty *msgid_pos, + char *msgid_plural, + char *msgstr, size_t msgstr_len, + lex_pos_ty *msgstr_pos, + char *prev_msgctxt, + char *prev_msgid, + char *prev_msgid_plural, + bool force_fuzzy, bool obsolete) +{ + message_ty *mp; + + /* See if this message ID has been seen before. */ + if (exclude == NULL) + exclude = message_list_alloc (true); + mp = message_list_search (exclude, msgctxt, msgid); + if (mp != NULL) + free (msgid); + else + { + mp = message_alloc (msgctxt, msgid, msgid_plural, "", 1, msgstr_pos); + /* Do not free msgid. */ + message_list_append (exclude, mp); + } + + /* All we care about is the msgid. Throw the msgstr away. + Don't even check for duplicate msgids. */ + free (msgstr); +} + + +/* So that the one parser can be used for multiple programs, and also + use good data hiding and encapsulation practices, an object + oriented approach has been taken. An object instance is allocated, + and all actions resulting from the parse will be through + invocations of method functions of that object. */ + +static abstract_catalog_reader_class_ty exclude_methods = +{ + sizeof (abstract_catalog_reader_ty), + NULL, /* constructor */ + NULL, /* destructor */ + NULL, /* parse_brief */ + NULL, /* parse_debrief */ + exclude_directive_domain, + exclude_directive_message, + NULL, /* comment */ + NULL, /* comment_dot */ + NULL, /* comment_filepos */ + NULL, /* comment_special */ +}; + + +static void +read_exclusion_file (char *filename) +{ + char *real_filename; + FILE *fp = open_catalog_file (filename, &real_filename, true); + abstract_catalog_reader_ty *pop; + + pop = catalog_reader_alloc (&exclude_methods); + catalog_reader_parse (pop, fp, real_filename, filename, &input_format_po); + catalog_reader_free (pop); + + if (fp != stdin) + fclose (fp); +} + + +void +split_keywordspec (const char *spec, + const char **endp, struct callshape *shapep) +{ + const char *p; + int argnum1 = 0; + int argnum2 = 0; + int argnumc = 0; + bool argnum1_glib_context = false; + bool argnum2_glib_context = false; + int argtotal = 0; + string_list_ty xcomments; + + string_list_init (&xcomments); + + /* Start parsing from the end. */ + p = spec + strlen (spec); + while (p > spec) + { + if (isdigit ((unsigned char) p[-1]) + || ((p[-1] == 'c' || p[-1] == 'g' || p[-1] == 't') + && p - 1 > spec && isdigit ((unsigned char) p[-2]))) + { + bool contextp = (p[-1] == 'c'); + bool glibp = (p[-1] == 'g'); + bool totalp = (p[-1] == 't'); + + do + p--; + while (p > spec && isdigit ((unsigned char) p[-1])); + + if (p > spec && (p[-1] == ',' || p[-1] == ':')) + { + char *dummy; + int arg = strtol (p, &dummy, 10); + + if (contextp) + { + if (argnumc != 0) + /* Only one context argument can be given. */ + break; + argnumc = arg; + } + else if (totalp) + { + if (argtotal != 0) + /* Only one total number of arguments can be given. */ + break; + argtotal = arg; + } + else + { + if (argnum2 != 0) + /* At most two normal arguments can be given. */ + break; + argnum2 = argnum1; + argnum2_glib_context = argnum1_glib_context; + argnum1 = arg; + argnum1_glib_context = glibp; + } + } + else + break; + } + else if (p[-1] == '"') + { + const char *xcomment_end; + + p--; + xcomment_end = p; + + while (p > spec && p[-1] != '"') + p--; + + if (p > spec /* && p[-1] == '"' */) + { + const char *xcomment_start; + + xcomment_start = p; + p--; + if (p > spec && (p[-1] == ',' || p[-1] == ':')) + { + size_t xcomment_len = xcomment_end - xcomment_start; + char *xcomment = XNMALLOC (xcomment_len + 1, char); + + memcpy (xcomment, xcomment_start, xcomment_len); + xcomment[xcomment_len] = '\0'; + string_list_append (&xcomments, xcomment); + } + else + break; + } + else + break; + } + else + break; + + /* Here an element of the comma-separated list has been parsed. */ + if (!(p > spec && (p[-1] == ',' || p[-1] == ':'))) + abort (); + p--; + if (*p == ':') + { + size_t i; + + if (argnum1 == 0 && argnum2 == 0) + /* At least one non-context argument must be given. */ + break; + if (argnumc != 0 + && (argnum1_glib_context || argnum2_glib_context)) + /* Incompatible ways to specify the context. */ + break; + *endp = p; + shapep->argnum1 = (argnum1 > 0 ? argnum1 : 1); + shapep->argnum2 = argnum2; + shapep->argnumc = argnumc; + shapep->argnum1_glib_context = argnum1_glib_context; + shapep->argnum2_glib_context = argnum2_glib_context; + shapep->argtotal = argtotal; + /* Reverse the order of the xcomments. */ + string_list_init (&shapep->xcomments); + for (i = xcomments.nitems; i > 0; ) + string_list_append (&shapep->xcomments, xcomments.item[--i]); + string_list_destroy (&xcomments); + return; + } + } + + /* Couldn't parse the desired syntax. */ + *endp = spec + strlen (spec); + shapep->argnum1 = 1; + shapep->argnum2 = 0; + shapep->argnumc = 0; + shapep->argnum1_glib_context = false; + shapep->argnum2_glib_context = false; + shapep->argtotal = 0; + string_list_init (&shapep->xcomments); + string_list_destroy (&xcomments); +} + + +void +insert_keyword_callshape (hash_table *table, + const char *keyword, size_t keyword_len, + const struct callshape *shape) +{ + void *old_value; + + if (hash_find_entry (table, keyword, keyword_len, &old_value)) + { + /* Create a one-element 'struct callshapes'. */ + struct callshapes *shapes = XMALLOC (struct callshapes); + shapes->nshapes = 1; + shapes->shapes[0] = *shape; + keyword = + (const char *) hash_insert_entry (table, keyword, keyword_len, shapes); + if (keyword == NULL) + abort (); + shapes->keyword = keyword; + shapes->keyword_len = keyword_len; + } + else + { + /* Found a 'struct callshapes'. See whether it already contains the + desired shape. */ + struct callshapes *old_shapes = (struct callshapes *) old_value; + bool found; + size_t i; + + found = false; + for (i = 0; i < old_shapes->nshapes; i++) + if (old_shapes->shapes[i].argnum1 == shape->argnum1 + && old_shapes->shapes[i].argnum2 == shape->argnum2 + && old_shapes->shapes[i].argnumc == shape->argnumc + && old_shapes->shapes[i].argnum1_glib_context + == shape->argnum1_glib_context + && old_shapes->shapes[i].argnum2_glib_context + == shape->argnum2_glib_context + && old_shapes->shapes[i].argtotal == shape->argtotal) + { + old_shapes->shapes[i].xcomments = shape->xcomments; + found = true; + break; + } + + if (!found) + { + /* Replace the existing 'struct callshapes' with a new one. */ + struct callshapes *shapes = + (struct callshapes *) + xmalloc (xsum (sizeof (struct callshapes), + xtimes (old_shapes->nshapes, + sizeof (struct callshape)))); + + shapes->keyword = old_shapes->keyword; + shapes->keyword_len = old_shapes->keyword_len; + shapes->nshapes = old_shapes->nshapes + 1; + for (i = 0; i < old_shapes->nshapes; i++) + shapes->shapes[i] = old_shapes->shapes[i]; + shapes->shapes[i] = *shape; + if (hash_set_value (table, keyword, keyword_len, shapes)) + abort (); + free (old_shapes); + } + } +} + + +/* Null context. */ +flag_context_ty null_context = { undecided, false, undecided, false }; + +/* Transparent context. */ +flag_context_ty passthrough_context = { undecided, true, undecided, true }; + + +flag_context_ty +inherited_context (flag_context_ty outer_context, + flag_context_ty modifier_context) +{ + flag_context_ty result = modifier_context; + + if (result.pass_format1) + { + result.is_format1 = outer_context.is_format1; + result.pass_format1 = false; + } + if (result.pass_format2) + { + result.is_format2 = outer_context.is_format2; + result.pass_format2 = false; + } + if (result.pass_format3) + { + result.is_format3 = outer_context.is_format3; + result.pass_format3 = false; + } + return result; +} + + +/* Null context list iterator. */ +flag_context_list_iterator_ty null_context_list_iterator = { 1, NULL }; + +/* Transparent context list iterator. */ +static flag_context_list_ty passthrough_context_circular_list = + { + 1, + { undecided, true, undecided, true }, + &passthrough_context_circular_list + }; +flag_context_list_iterator_ty passthrough_context_list_iterator = + { + 1, + &passthrough_context_circular_list + }; + + +flag_context_list_iterator_ty +flag_context_list_iterator (flag_context_list_ty *list) +{ + flag_context_list_iterator_ty result; + + result.argnum = 1; + result.head = list; + return result; +} + + +flag_context_ty +flag_context_list_iterator_advance (flag_context_list_iterator_ty *iter) +{ + if (iter->head == NULL) + return null_context; + if (iter->argnum == iter->head->argnum) + { + flag_context_ty result = iter->head->flags; + + /* Special casing of circular list. */ + if (iter->head != iter->head->next) + { + iter->head = iter->head->next; + iter->argnum++; + } + + return result; + } + else + { + iter->argnum++; + return null_context; + } +} + + +flag_context_list_ty * +flag_context_list_table_lookup (flag_context_list_table_ty *flag_table, + const void *key, size_t keylen) +{ + void *entry; + + if (flag_table->table != NULL + && hash_find_entry (flag_table, key, keylen, &entry) == 0) + return (flag_context_list_ty *) entry; + else + return NULL; +} + + +static void +flag_context_list_table_insert (flag_context_list_table_ty *table, + unsigned int index, + const char *name_start, const char *name_end, + int argnum, enum is_format value, bool pass) +{ + char *allocated_name = NULL; + + if (table == &flag_table_lisp) + { + /* Convert NAME to upper case. */ + size_t name_len = name_end - name_start; + char *name = allocated_name = (char *) xmalloca (name_len); + size_t i; + + for (i = 0; i < name_len; i++) + name[i] = (name_start[i] >= 'a' && name_start[i] <= 'z' + ? name_start[i] - 'a' + 'A' + : name_start[i]); + name_start = name; + name_end = name + name_len; + } + else if (table == &flag_table_tcl) + { + /* Remove redundant "::" prefix. */ + if (name_end - name_start > 2 + && name_start[0] == ':' && name_start[1] == ':') + name_start += 2; + } + + /* Insert the pair (VALUE, PASS) at INDEX in the element numbered ARGNUM + of the list corresponding to NAME in the TABLE. */ + if (table->table == NULL) + hash_init (table, 100); + { + void *entry; + + if (hash_find_entry (table, name_start, name_end - name_start, &entry) != 0) + { + /* Create new hash table entry. */ + flag_context_list_ty *list = XMALLOC (flag_context_list_ty); + list->argnum = argnum; + memset (&list->flags, '\0', sizeof (list->flags)); + switch (index) + { + case 0: + list->flags.is_format1 = value; + list->flags.pass_format1 = pass; + break; + case 1: + list->flags.is_format2 = value; + list->flags.pass_format2 = pass; + break; + case 2: + list->flags.is_format3 = value; + list->flags.pass_format3 = pass; + break; + default: + abort (); + } + list->next = NULL; + hash_insert_entry (table, name_start, name_end - name_start, list); + } + else + { + flag_context_list_ty *list = (flag_context_list_ty *)entry; + flag_context_list_ty **lastp = NULL; + /* Invariant: list == (lastp != NULL ? *lastp : entry). */ + + while (list != NULL && list->argnum < argnum) + { + lastp = &list->next; + list = *lastp; + } + if (list != NULL && list->argnum == argnum) + { + /* Add this flag to the current argument number. */ + switch (index) + { + case 0: + list->flags.is_format1 = value; + list->flags.pass_format1 = pass; + break; + case 1: + list->flags.is_format2 = value; + list->flags.pass_format2 = pass; + break; + case 2: + list->flags.is_format3 = value; + list->flags.pass_format3 = pass; + break; + default: + abort (); + } + } + else if (lastp != NULL) + { + /* Add a new list entry for this argument number. */ + list = XMALLOC (flag_context_list_ty); + list->argnum = argnum; + memset (&list->flags, '\0', sizeof (list->flags)); + switch (index) + { + case 0: + list->flags.is_format1 = value; + list->flags.pass_format1 = pass; + break; + case 1: + list->flags.is_format2 = value; + list->flags.pass_format2 = pass; + break; + case 2: + list->flags.is_format3 = value; + list->flags.pass_format3 = pass; + break; + default: + abort (); + } + list->next = *lastp; + *lastp = list; + } + else + { + /* Add a new list entry for this argument number, at the beginning + of the list. Since we don't have an API for replacing the + value of a key in the hash table, we have to copy the first + list element. */ + flag_context_list_ty *copy = XMALLOC (flag_context_list_ty); + *copy = *list; + + list->argnum = argnum; + memset (&list->flags, '\0', sizeof (list->flags)); + switch (index) + { + case 0: + list->flags.is_format1 = value; + list->flags.pass_format1 = pass; + break; + case 1: + list->flags.is_format2 = value; + list->flags.pass_format2 = pass; + break; + case 2: + list->flags.is_format3 = value; + list->flags.pass_format3 = pass; + break; + default: + abort (); + } + list->next = copy; + } + } + } + + if (allocated_name != NULL) + freea (allocated_name); +} + + +void +xgettext_record_flag (const char *optionstring) +{ + /* Check the string has at least two colons. (Colons in the name are + allowed, needed for the Lisp and the Tcl backends.) */ + const char *colon1; + const char *colon2; + + for (colon2 = optionstring + strlen (optionstring); ; ) + { + if (colon2 == optionstring) + goto err; + colon2--; + if (*colon2 == ':') + break; + } + for (colon1 = colon2; ; ) + { + if (colon1 == optionstring) + goto err; + colon1--; + if (*colon1 == ':') + break; + } + { + const char *name_start = optionstring; + const char *name_end = colon1; + const char *argnum_start = colon1 + 1; + const char *argnum_end = colon2; + const char *flag = colon2 + 1; + int argnum; + + /* Check the parts' syntax. */ + if (name_end == name_start) + goto err; + if (argnum_end == argnum_start) + goto err; + { + char *endp; + argnum = strtol (argnum_start, &endp, 10); + if (endp != argnum_end) + goto err; + } + if (argnum <= 0) + goto err; + + /* Analyze the flag part. */ + { + bool pass; + + pass = false; + if (strlen (flag) >= 5 && memcmp (flag, "pass-", 5) == 0) + { + pass = true; + flag += 5; + } + + /* Unlike po_parse_comment_special(), we don't accept "fuzzy" or "wrap" + here - it has no sense. */ + if (strlen (flag) >= 7 + && memcmp (flag + strlen (flag) - 7, "-format", 7) == 0) + { + const char *p; + size_t n; + enum is_format value; + size_t type; + + p = flag; + n = strlen (flag) - 7; + + if (n >= 3 && memcmp (p, "no-", 3) == 0) + { + p += 3; + n -= 3; + value = no; + } + else if (n >= 9 && memcmp (p, "possible-", 9) == 0) + { + p += 9; + n -= 9; + value = possible; + } + else if (n >= 11 && memcmp (p, "impossible-", 11) == 0) + { + p += 11; + n -= 11; + value = impossible; + } + else + value = yes_according_to_context; + + for (type = 0; type < NFORMATS; type++) + if (strlen (format_language[type]) == n + && memcmp (format_language[type], p, n) == 0) + { + switch (type) + { + case format_c: + flag_context_list_table_insert (&flag_table_c, 0, + name_start, name_end, + argnum, value, pass); + flag_context_list_table_insert (&flag_table_cxx_qt, 0, + name_start, name_end, + argnum, value, pass); + flag_context_list_table_insert (&flag_table_cxx_kde, 0, + name_start, name_end, + argnum, value, pass); + flag_context_list_table_insert (&flag_table_cxx_boost, 0, + name_start, name_end, + argnum, value, pass); + flag_context_list_table_insert (&flag_table_objc, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_objc: + flag_context_list_table_insert (&flag_table_objc, 1, + name_start, name_end, + argnum, value, pass); + break; + case format_sh: + flag_context_list_table_insert (&flag_table_sh, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_python: + flag_context_list_table_insert (&flag_table_python, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_python_brace: + flag_context_list_table_insert (&flag_table_python, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_lisp: + flag_context_list_table_insert (&flag_table_lisp, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_elisp: + flag_context_list_table_insert (&flag_table_elisp, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_librep: + flag_context_list_table_insert (&flag_table_librep, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_scheme: + flag_context_list_table_insert (&flag_table_scheme, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_smalltalk: + break; + case format_java: + flag_context_list_table_insert (&flag_table_java, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_csharp: + flag_context_list_table_insert (&flag_table_csharp, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_awk: + flag_context_list_table_insert (&flag_table_awk, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_pascal: + break; + case format_ycp: + flag_context_list_table_insert (&flag_table_ycp, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_tcl: + flag_context_list_table_insert (&flag_table_tcl, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_perl: + flag_context_list_table_insert (&flag_table_perl, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_perl_brace: + flag_context_list_table_insert (&flag_table_perl, 1, + name_start, name_end, + argnum, value, pass); + break; + case format_php: + flag_context_list_table_insert (&flag_table_php, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_gcc_internal: + flag_context_list_table_insert (&flag_table_gcc_internal, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_gfc_internal: + flag_context_list_table_insert (&flag_table_gcc_internal, 1, + name_start, name_end, + argnum, value, pass); + break; + case format_qt: + flag_context_list_table_insert (&flag_table_cxx_qt, 1, + name_start, name_end, + argnum, value, pass); + break; + case format_qt_plural: + flag_context_list_table_insert (&flag_table_cxx_qt, 2, + name_start, name_end, + argnum, value, pass); + break; + case format_kde: + flag_context_list_table_insert (&flag_table_cxx_kde, 1, + name_start, name_end, + argnum, value, pass); + break; + case format_boost: + flag_context_list_table_insert (&flag_table_cxx_boost, 1, + name_start, name_end, + argnum, value, pass); + break; + case format_lua: + flag_context_list_table_insert (&flag_table_lua, 0, + name_start, name_end, + argnum, value, pass); + break; + case format_javascript: + flag_context_list_table_insert (&flag_table_javascript, 0, + name_start, name_end, + argnum, value, pass); + break; + default: + abort (); + } + return; + } + /* If the flag is not among the valid values, the optionstring is + invalid. */ + } + } + } + +err: + error (EXIT_FAILURE, 0, _("\ +A --flag argument doesn't have the ::[pass-] syntax: %s"), + optionstring); +} + + +/* Comment handling: There is a list of automatic comments that may be appended + to the next message. Used by remember_a_message(). */ + +static string_list_ty *comment; + +static void +xgettext_comment_add (const char *str) +{ + if (comment == NULL) + comment = string_list_alloc (); + string_list_append (comment, str); +} + +static const char * +xgettext_comment (size_t n) +{ + if (comment == NULL || n >= comment->nitems) + return NULL; + return comment->item[n]; +} + +static void +xgettext_comment_reset () +{ + if (comment != NULL) + { + string_list_free (comment); + comment = NULL; + } +} + + +refcounted_string_list_ty *savable_comment; + +void +savable_comment_add (const char *str) +{ + if (savable_comment == NULL) + { + savable_comment = XMALLOC (refcounted_string_list_ty); + savable_comment->refcount = 1; + string_list_init (&savable_comment->contents); + } + else if (savable_comment->refcount > 1) + { + /* Unshare the list by making copies. */ + struct string_list_ty *oldcontents; + size_t i; + + savable_comment->refcount--; + oldcontents = &savable_comment->contents; + + savable_comment = XMALLOC (refcounted_string_list_ty); + savable_comment->refcount = 1; + string_list_init (&savable_comment->contents); + for (i = 0; i < oldcontents->nitems; i++) + string_list_append (&savable_comment->contents, oldcontents->item[i]); + } + string_list_append (&savable_comment->contents, str); +} + +void +savable_comment_reset () +{ + drop_reference (savable_comment); + savable_comment = NULL; +} + +static void +savable_comment_to_xgettext_comment (refcounted_string_list_ty *rslp) +{ + xgettext_comment_reset (); + if (rslp != NULL) + { + size_t i; + + for (i = 0; i < rslp->contents.nitems; i++) + xgettext_comment_add (rslp->contents.item[i]); + } +} + +refcounted_string_list_ty * +savable_comment_convert_encoding (refcounted_string_list_ty *comment, + lex_pos_ty *pos) +{ + refcounted_string_list_ty *result; + size_t i; + + result = XMALLOC (refcounted_string_list_ty); + result->refcount = 1; + string_list_init (&result->contents); + + for (i = 0; i < comment->contents.nitems; i++) + { + const char *old_string = comment->contents.item[i]; + char *string = from_current_source_encoding (old_string, + lc_comment, + pos->file_name, + pos->line_number); + string_list_append (&result->contents, string); + if (string != old_string) + free (string); + } + + return result; +} + + + +static FILE * +xgettext_open (const char *fn, + char **logical_file_name_p, char **real_file_name_p) +{ + FILE *fp; + char *new_name; + char *logical_file_name; + + if (strcmp (fn, "-") == 0) + { + new_name = xstrdup (_("standard input")); + logical_file_name = xstrdup (new_name); + fp = stdin; + } + else if (IS_ABSOLUTE_PATH (fn)) + { + new_name = xstrdup (fn); + fp = fopen (fn, "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("\ +error while opening \"%s\" for reading"), fn); + logical_file_name = xstrdup (new_name); + } + else + { + int j; + + for (j = 0; ; ++j) + { + const char *dir = dir_list_nth (j); + + if (dir == NULL) + error (EXIT_FAILURE, ENOENT, _("\ +error while opening \"%s\" for reading"), fn); + + new_name = xconcatenated_filename (dir, fn, NULL); + + fp = fopen (new_name, "r"); + if (fp != NULL) + break; + + if (errno != ENOENT) + error (EXIT_FAILURE, errno, _("\ +error while opening \"%s\" for reading"), new_name); + free (new_name); + } + + /* Note that the NEW_NAME variable contains the actual file name + and the logical file name is what is reported by xgettext. In + this case NEW_NAME is set to the file which was found along the + directory search path, and LOGICAL_FILE_NAME is is set to the + file name which was searched for. */ + logical_file_name = xstrdup (fn); + } + + *logical_file_name_p = logical_file_name; + *real_file_name_p = new_name; + return fp; +} + + +/* Language dependent format string parser. + NULL if the language has no notion of format strings. */ +static struct formatstring_parser *current_formatstring_parser1; +static struct formatstring_parser *current_formatstring_parser2; +static struct formatstring_parser *current_formatstring_parser3; + +static struct literalstring_parser *current_literalstring_parser; + +static void +extract_from_file (const char *file_name, extractor_ty extractor, + msgdomain_list_ty *mdlp) +{ + char *logical_file_name; + char *real_file_name; + FILE *fp = xgettext_open (file_name, &logical_file_name, &real_file_name); + + /* Set the default for the source file encoding. May be overridden by + the extractor function. */ + xgettext_current_source_encoding = xgettext_global_source_encoding; +#if HAVE_ICONV + xgettext_current_source_iconv = xgettext_global_source_iconv; +#endif + + current_formatstring_parser1 = extractor.formatstring_parser1; + current_formatstring_parser2 = extractor.formatstring_parser2; + current_formatstring_parser3 = extractor.formatstring_parser3; + current_literalstring_parser = extractor.literalstring_parser; + extractor.func (fp, real_file_name, logical_file_name, extractor.flag_table, + mdlp); + + if (fp != stdin) + fclose (fp); + free (logical_file_name); + free (real_file_name); +} + + + +/* Error message about non-ASCII character in a specific lexical context. */ +char * +non_ascii_error_message (lexical_context_ty lcontext, + const char *file_name, size_t line_number) +{ + char buffer[21]; + char *errmsg; + + if (line_number == (size_t)(-1)) + buffer[0] = '\0'; + else + sprintf (buffer, ":%ld", (long) line_number); + + switch (lcontext) + { + case lc_outside: + errmsg = + xasprintf (_("Non-ASCII character at %s%s."), file_name, buffer); + break; + case lc_comment: + errmsg = + xasprintf (_("Non-ASCII comment at or before %s%s."), + file_name, buffer); + break; + case lc_string: + errmsg = + xasprintf (_("Non-ASCII string at %s%s."), file_name, buffer); + break; + default: + abort (); + } + return errmsg; +} + +/* Convert the given string from xgettext_current_source_encoding to + the output file encoding (i.e. ASCII or UTF-8). + The resulting string is either the argument string, or freshly allocated. + The file_name and line_number are only used for error message purposes. */ +char * +from_current_source_encoding (const char *string, + lexical_context_ty lcontext, + const char *file_name, size_t line_number) +{ + if (xgettext_current_source_encoding == po_charset_ascii) + { + if (!is_ascii_string (string)) + { + multiline_error (xstrdup (""), + xasprintf ("%s\n%s\n", + non_ascii_error_message (lcontext, + file_name, + line_number), + _("\ +Please specify the source encoding through --from-code."))); + exit (EXIT_FAILURE); + } + } + else if (xgettext_current_source_encoding != po_charset_utf8) + { +#if HAVE_ICONV + struct conversion_context context; + + context.from_code = xgettext_current_source_encoding; + context.to_code = po_charset_utf8; + context.from_filename = file_name; + context.message = NULL; + + string = convert_string_directly (xgettext_current_source_iconv, string, + &context); +#else + /* If we don't have iconv(), the only supported values for + xgettext_global_source_encoding and thus also for + xgettext_current_source_encoding are ASCII and UTF-8. + convert_string_directly() should not be called in this case. */ + abort (); +#endif + } + + return (char *) string; +} + +#define CONVERT_STRING(string, lcontext) \ + string = from_current_source_encoding (string, lcontext, pos->file_name, \ + pos->line_number); + + +/* Update the is_format[] flags depending on the information given in the + context. */ +static void +set_format_flags_from_context (enum is_format is_format[NFORMATS], + flag_context_ty context, const char *string, + lex_pos_ty *pos, const char *pretty_msgstr) +{ + size_t i; + + if (context.is_format1 != undecided + || context.is_format2 != undecided + || context.is_format3 != undecided) + for (i = 0; i < NFORMATS; i++) + { + if (is_format[i] == undecided) + { + if (formatstring_parsers[i] == current_formatstring_parser1 + && context.is_format1 != undecided) + is_format[i] = (enum is_format) context.is_format1; + if (formatstring_parsers[i] == current_formatstring_parser2 + && context.is_format2 != undecided) + is_format[i] = (enum is_format) context.is_format2; + if (formatstring_parsers[i] == current_formatstring_parser3 + && context.is_format3 != undecided) + is_format[i] = (enum is_format) context.is_format3; + } + if (possible_format_p (is_format[i])) + { + struct formatstring_parser *parser = formatstring_parsers[i]; + char *invalid_reason = NULL; + void *descr = parser->parse (string, false, NULL, &invalid_reason); + + if (descr != NULL) + parser->free (descr); + else + { + /* The string is not a valid format string. */ + if (is_format[i] != possible) + { + char buffer[21]; + + error_with_progname = false; + if (pos->line_number == (size_t)(-1)) + buffer[0] = '\0'; + else + sprintf (buffer, ":%ld", (long) pos->line_number); + multiline_warning (xasprintf (_("%s%s: warning: "), + pos->file_name, buffer), + xasprintf (is_format[i] == yes_according_to_context + ? _("Although being used in a format string position, the %s is not a valid %s format string. Reason: %s\n") + : _("Although declared as such, the %s is not a valid %s format string. Reason: %s\n"), + pretty_msgstr, + format_language_pretty[i], + invalid_reason)); + error_with_progname = true; + } + + is_format[i] = impossible; + free (invalid_reason); + } + } + } +} + + +static void +warn_format_string (enum is_format is_format[NFORMATS], const char *string, + lex_pos_ty *pos, const char *pretty_msgstr) +{ + if (possible_format_p (is_format[format_python]) + && get_python_format_unnamed_arg_count (string) > 1) + { + char buffer[21]; + + error_with_progname = false; + if (pos->line_number == (size_t)(-1)) + buffer[0] = '\0'; + else + sprintf (buffer, ":%ld", (long) pos->line_number); + multiline_warning (xasprintf (_("%s%s: warning: "), + pos->file_name, buffer), + xasprintf (_("\ +'%s' format string with unnamed arguments cannot be properly localized:\n\ +The translator cannot reorder the arguments.\n\ +Please consider using a format string with named arguments,\n\ +and a mapping instead of a tuple for the arguments.\n"), + pretty_msgstr)); + error_with_progname = true; + } +} + + +message_ty * +remember_a_message (message_list_ty *mlp, char *msgctxt, char *msgid, + flag_context_ty context, lex_pos_ty *pos, + const char *extracted_comment, + refcounted_string_list_ty *comment) +{ + enum is_format is_format[NFORMATS]; + struct argument_range range; + enum is_wrap do_wrap; + message_ty *mp; + char *msgstr; + size_t i; + + /* See whether we shall exclude this message. */ + if (exclude != NULL && message_list_search (exclude, msgctxt, msgid) != NULL) + { + /* Tell the lexer to reset its comment buffer, so that the next + message gets the correct comments. */ + xgettext_comment_reset (); + savable_comment_reset (); + + if (msgctxt != NULL) + free (msgctxt); + free (msgid); + + return NULL; + } + + savable_comment_to_xgettext_comment (comment); + + for (i = 0; i < NFORMATS; i++) + is_format[i] = undecided; + range.min = -1; + range.max = -1; + do_wrap = undecided; + + if (msgctxt != NULL) + CONVERT_STRING (msgctxt, lc_string); + CONVERT_STRING (msgid, lc_string); + + if (msgctxt == NULL && msgid[0] == '\0' && !xgettext_omit_header) + { + char buffer[21]; + + error_with_progname = false; + if (pos->line_number == (size_t)(-1)) + buffer[0] = '\0'; + else + sprintf (buffer, ":%ld", (long) pos->line_number); + multiline_warning (xasprintf (_("%s%s: warning: "), pos->file_name, + buffer), + xstrdup (_("\ +Empty msgid. It is reserved by GNU gettext:\n\ +gettext(\"\") returns the header entry with\n\ +meta information, not the empty string.\n"))); + error_with_progname = true; + } + + /* See if we have seen this message before. */ + mp = message_list_search (mlp, msgctxt, msgid); + if (mp != NULL) + { + if (msgctxt != NULL) + free (msgctxt); + free (msgid); + for (i = 0; i < NFORMATS; i++) + is_format[i] = mp->is_format[i]; + do_wrap = mp->do_wrap; + } + else + { + /* Construct the msgstr from the prefix and suffix, otherwise use the + empty string. */ + if (msgstr_prefix) + msgstr = xasprintf ("%s%s%s", msgstr_prefix, msgid, msgstr_suffix); + else + msgstr = ""; + + /* Allocate a new message and append the message to the list. */ + mp = message_alloc (msgctxt, msgid, NULL, msgstr, strlen (msgstr) + 1, + pos); + /* Do not free msgctxt and msgid. */ + message_list_append (mlp, mp); + } + + /* Determine whether the context specifies that the msgid is a format + string. */ + set_format_flags_from_context (is_format, context, mp->msgid, pos, "msgid"); + + /* Ask the lexer for the comments it has seen. */ + { + size_t nitems_before; + size_t nitems_after; + int j; + bool add_all_remaining_comments; + /* The string before the comment tag. For example, If "** TRANSLATORS:" + is seen and the comment tag is "TRANSLATORS:", + then comment_tag_prefix is set to "** ". */ + const char *comment_tag_prefix = NULL; + size_t comment_tag_prefix_length = 0; + + nitems_before = (mp->comment_dot != NULL ? mp->comment_dot->nitems : 0); + + if (extracted_comment != NULL) + { + char *copy = xstrdup (extracted_comment); + char *rest; + + rest = copy; + while (*rest != '\0') + { + char *newline = strchr (rest, '\n'); + + if (newline != NULL) + { + *newline = '\0'; + message_comment_dot_append (mp, rest); + rest = newline + 1; + } + else + { + message_comment_dot_append (mp, rest); + break; + } + } + free (copy); + } + + add_all_remaining_comments = add_all_comments; + for (j = 0; ; ++j) + { + const char *s = xgettext_comment (j); + const char *t; + if (s == NULL) + break; + + CONVERT_STRING (s, lc_comment); + + /* To reduce the possibility of unwanted matches we do a two + step match: the line must contain 'xgettext:' and one of + the possible format description strings. */ + if ((t = c_strstr (s, "xgettext:")) != NULL) + { + bool tmp_fuzzy; + enum is_format tmp_format[NFORMATS]; + struct argument_range tmp_range; + enum is_wrap tmp_wrap; + bool interesting; + + t += strlen ("xgettext:"); + + po_parse_comment_special (t, &tmp_fuzzy, tmp_format, &tmp_range, + &tmp_wrap); + + interesting = false; + for (i = 0; i < NFORMATS; i++) + if (tmp_format[i] != undecided) + { + is_format[i] = tmp_format[i]; + interesting = true; + } + if (has_range_p (tmp_range)) + { + range = tmp_range; + interesting = true; + } + if (tmp_wrap != undecided) + { + do_wrap = tmp_wrap; + interesting = true; + } + + /* If the "xgettext:" marker was followed by an interesting + keyword, and we updated our is_format/do_wrap variables, + we don't print the comment as a #. comment. */ + if (interesting) + continue; + } + + if (!add_all_remaining_comments && comment_tag != NULL) + { + /* When the comment tag is seen, it drags in not only the line + which it starts, but all remaining comment lines. */ + if ((t = c_strstr (s, comment_tag)) != NULL) + { + add_all_remaining_comments = true; + comment_tag_prefix = s; + comment_tag_prefix_length = t - s; + } + } + + if (add_all_remaining_comments) + { + if (strncmp (s, comment_tag_prefix, comment_tag_prefix_length) == 0) + s += comment_tag_prefix_length; + message_comment_dot_append (mp, s); + } + } + + nitems_after = (mp->comment_dot != NULL ? mp->comment_dot->nitems : 0); + + /* Don't add the comments if they are a repetition of the tail of the + already present comments. This avoids unneeded duplication if the + same message appears several times, each time with the same comment. */ + if (nitems_before < nitems_after) + { + size_t added = nitems_after - nitems_before; + + if (added <= nitems_before) + { + bool repeated = true; + + for (i = 0; i < added; i++) + if (strcmp (mp->comment_dot->item[nitems_before - added + i], + mp->comment_dot->item[nitems_before + i]) != 0) + { + repeated = false; + break; + } + + if (repeated) + { + for (i = 0; i < added; i++) + free ((char *) mp->comment_dot->item[nitems_before + i]); + mp->comment_dot->nitems = nitems_before; + } + } + } + } + + /* If it is not already decided, through programmer comments, whether the + msgid is a format string, examine the msgid. This is a heuristic. */ + for (i = 0; i < NFORMATS; i++) + { + if (is_format[i] == undecided + && (formatstring_parsers[i] == current_formatstring_parser1 + || formatstring_parsers[i] == current_formatstring_parser2 + || formatstring_parsers[i] == current_formatstring_parser3) + /* But avoid redundancy: objc-format is stronger than c-format. */ + && !(i == format_c && possible_format_p (is_format[format_objc])) + && !(i == format_objc && possible_format_p (is_format[format_c])) + /* Avoid flagging a string as c-format when it's known to be a + qt-format or qt-plural-format or kde-format or boost-format + string. */ + && !(i == format_c + && (possible_format_p (is_format[format_qt]) + || possible_format_p (is_format[format_qt_plural]) + || possible_format_p (is_format[format_kde]) + || possible_format_p (is_format[format_boost])))) + { + struct formatstring_parser *parser = formatstring_parsers[i]; + char *invalid_reason = NULL; + void *descr = parser->parse (mp->msgid, false, NULL, &invalid_reason); + + if (descr != NULL) + { + /* msgid is a valid format string. We mark only those msgids + as format strings which contain at least one format directive + and thus are format strings with a high probability. We + don't mark strings without directives as format strings, + because that would force the programmer to add + "xgettext: no-c-format" anywhere where a translator wishes + to use a percent sign. So, the msgfmt checking will not be + perfect. Oh well. */ + if (parser->get_number_of_directives (descr) > 0 + && !(parser->is_unlikely_intentional != NULL + && parser->is_unlikely_intentional (descr))) + is_format[i] = possible; + + parser->free (descr); + } + else + { + /* msgid is not a valid format string. */ + is_format[i] = impossible; + free (invalid_reason); + } + } + mp->is_format[i] = is_format[i]; + } + + if (has_range_p (range)) + { + if (has_range_p (mp->range)) + { + if (range.min < mp->range.min) + mp->range.min = range.min; + if (range.max > mp->range.max) + mp->range.max = range.max; + } + else + mp->range = range; + } + + mp->do_wrap = do_wrap == no ? no : yes; /* By default we wrap. */ + + /* Warn about the use of non-reorderable format strings when the programming + language also provides reorderable format strings. */ + warn_format_string (is_format, mp->msgid, pos, "msgid"); + + /* Remember where we saw this msgid. */ + message_comment_filepos (mp, pos->file_name, pos->line_number); + + /* Tell the lexer to reset its comment buffer, so that the next + message gets the correct comments. */ + xgettext_comment_reset (); + savable_comment_reset (); + + return mp; +} + + +void +remember_a_message_plural (message_ty *mp, char *string, + flag_context_ty context, lex_pos_ty *pos, + refcounted_string_list_ty *comment) +{ + char *msgid_plural; + char *msgstr1; + size_t msgstr1_len; + char *msgstr; + size_t i; + + msgid_plural = string; + + savable_comment_to_xgettext_comment (comment); + + CONVERT_STRING (msgid_plural, lc_string); + + /* See if the message is already a plural message. */ + if (mp->msgid_plural == NULL) + { + mp->msgid_plural = msgid_plural; + + /* Construct the first plural form from the prefix and suffix, + otherwise use the empty string. The translator will have to + provide additional plural forms. */ + if (msgstr_prefix) + msgstr1 = + xasprintf ("%s%s%s", msgstr_prefix, msgid_plural, msgstr_suffix); + else + msgstr1 = ""; + msgstr1_len = strlen (msgstr1) + 1; + msgstr = XNMALLOC (mp->msgstr_len + msgstr1_len, char); + memcpy (msgstr, mp->msgstr, mp->msgstr_len); + memcpy (msgstr + mp->msgstr_len, msgstr1, msgstr1_len); + mp->msgstr = msgstr; + mp->msgstr_len = mp->msgstr_len + msgstr1_len; + if (msgstr_prefix) + free (msgstr1); + + /* Determine whether the context specifies that the msgid_plural is a + format string. */ + set_format_flags_from_context (mp->is_format, context, mp->msgid_plural, + pos, "msgid_plural"); + + /* If it is not already decided, through programmer comments or + the msgid, whether the msgid is a format string, examine the + msgid_plural. This is a heuristic. */ + for (i = 0; i < NFORMATS; i++) + if ((formatstring_parsers[i] == current_formatstring_parser1 + || formatstring_parsers[i] == current_formatstring_parser2 + || formatstring_parsers[i] == current_formatstring_parser3) + && (mp->is_format[i] == undecided || mp->is_format[i] == possible) + /* But avoid redundancy: objc-format is stronger than c-format. */ + && !(i == format_c + && possible_format_p (mp->is_format[format_objc])) + && !(i == format_objc + && possible_format_p (mp->is_format[format_c])) + /* Avoid flagging a string as c-format when it's known to be a + qt-format or qt-plural-format or boost-format string. */ + && !(i == format_c + && (possible_format_p (mp->is_format[format_qt]) + || possible_format_p (mp->is_format[format_qt_plural]) + || possible_format_p (mp->is_format[format_kde]) + || possible_format_p (mp->is_format[format_boost])))) + { + struct formatstring_parser *parser = formatstring_parsers[i]; + char *invalid_reason = NULL; + void *descr = + parser->parse (mp->msgid_plural, false, NULL, &invalid_reason); + + if (descr != NULL) + { + /* Same heuristic as in remember_a_message. */ + if (parser->get_number_of_directives (descr) > 0 + && !(parser->is_unlikely_intentional != NULL + && parser->is_unlikely_intentional (descr))) + mp->is_format[i] = possible; + + parser->free (descr); + } + else + { + /* msgid_plural is not a valid format string. */ + mp->is_format[i] = impossible; + free (invalid_reason); + } + } + + /* Warn about the use of non-reorderable format strings when the programming + language also provides reorderable format strings. */ + warn_format_string (mp->is_format, mp->msgid_plural, pos, "msgid_plural"); + } + else + free (msgid_plural); + + /* Tell the lexer to reset its comment buffer, so that the next + message gets the correct comments. */ + xgettext_comment_reset (); + savable_comment_reset (); +} + + +struct arglist_parser * +arglist_parser_alloc (message_list_ty *mlp, const struct callshapes *shapes) +{ + if (shapes == NULL || shapes->nshapes == 0) + { + struct arglist_parser *ap = + (struct arglist_parser *) + xmalloc (offsetof (struct arglist_parser, alternative[0])); + + ap->mlp = mlp; + ap->keyword = NULL; + ap->keyword_len = 0; + ap->nalternatives = 0; + + return ap; + } + else + { + struct arglist_parser *ap = + (struct arglist_parser *) + xmalloc (xsum (sizeof (struct arglist_parser), + xtimes (shapes->nshapes - 1, + sizeof (struct partial_call)))); + size_t i; + + ap->mlp = mlp; + ap->keyword = shapes->keyword; + ap->keyword_len = shapes->keyword_len; + ap->nalternatives = shapes->nshapes; + for (i = 0; i < shapes->nshapes; i++) + { + ap->alternative[i].argnumc = shapes->shapes[i].argnumc; + ap->alternative[i].argnum1 = shapes->shapes[i].argnum1; + ap->alternative[i].argnum2 = shapes->shapes[i].argnum2; + ap->alternative[i].argnum1_glib_context = + shapes->shapes[i].argnum1_glib_context; + ap->alternative[i].argnum2_glib_context = + shapes->shapes[i].argnum2_glib_context; + ap->alternative[i].argtotal = shapes->shapes[i].argtotal; + ap->alternative[i].xcomments = shapes->shapes[i].xcomments; + ap->alternative[i].msgctxt = NULL; + ap->alternative[i].msgctxt_escape = LET_NONE; + ap->alternative[i].msgctxt_pos.file_name = NULL; + ap->alternative[i].msgctxt_pos.line_number = (size_t)(-1); + ap->alternative[i].msgid = NULL; + ap->alternative[i].msgid_escape = LET_NONE; + ap->alternative[i].msgid_context = null_context; + ap->alternative[i].msgid_pos.file_name = NULL; + ap->alternative[i].msgid_pos.line_number = (size_t)(-1); + ap->alternative[i].msgid_comment = NULL; + ap->alternative[i].msgid_plural = NULL; + ap->alternative[i].msgid_plural_escape = LET_NONE; + ap->alternative[i].msgid_plural_context = null_context; + ap->alternative[i].msgid_plural_pos.file_name = NULL; + ap->alternative[i].msgid_plural_pos.line_number = (size_t)(-1); + } + + return ap; + } +} + + +struct arglist_parser * +arglist_parser_clone (struct arglist_parser *ap) +{ + struct arglist_parser *copy = + (struct arglist_parser *) + xmalloc (xsum (sizeof (struct arglist_parser) - sizeof (struct partial_call), + xtimes (ap->nalternatives, sizeof (struct partial_call)))); + size_t i; + + copy->mlp = ap->mlp; + copy->keyword = ap->keyword; + copy->keyword_len = ap->keyword_len; + copy->nalternatives = ap->nalternatives; + for (i = 0; i < ap->nalternatives; i++) + { + const struct partial_call *cp = &ap->alternative[i]; + struct partial_call *ccp = ©->alternative[i]; + + ccp->argnumc = cp->argnumc; + ccp->argnum1 = cp->argnum1; + ccp->argnum2 = cp->argnum2; + ccp->argnum1_glib_context = cp->argnum1_glib_context; + ccp->argnum2_glib_context = cp->argnum2_glib_context; + ccp->argtotal = cp->argtotal; + ccp->xcomments = cp->xcomments; + ccp->msgctxt = (cp->msgctxt != NULL ? xstrdup (cp->msgctxt) : NULL); + ccp->msgctxt_escape = cp->msgctxt_escape; + ccp->msgctxt_pos = cp->msgctxt_pos; + ccp->msgid = (cp->msgid != NULL ? xstrdup (cp->msgid) : NULL); + ccp->msgid_escape = cp->msgid_escape; + ccp->msgid_context = cp->msgid_context; + ccp->msgid_pos = cp->msgctxt_pos; + ccp->msgid_comment = add_reference (cp->msgid_comment); + ccp->msgid_plural = + (cp->msgid_plural != NULL ? xstrdup (cp->msgid_plural) : NULL); + ccp->msgid_plural_escape = cp->msgid_plural_escape; + ccp->msgid_plural_context = cp->msgid_plural_context; + ccp->msgid_plural_pos = cp->msgid_plural_pos; + } + + return copy; +} + + +void +arglist_parser_remember_literal (struct arglist_parser *ap, + int argnum, char *string, + flag_context_ty context, + char *file_name, size_t line_number, + refcounted_string_list_ty *comment, + enum literalstring_escape_type type) +{ + bool stored_string = false; + size_t nalternatives = ap->nalternatives; + size_t i; + + if (!(argnum > 0)) + abort (); + for (i = 0; i < nalternatives; i++) + { + struct partial_call *cp = &ap->alternative[i]; + + if (argnum == cp->argnumc) + { + cp->msgctxt = string; + cp->msgctxt_escape = type; + cp->msgctxt_pos.file_name = file_name; + cp->msgctxt_pos.line_number = line_number; + stored_string = true; + /* Mark msgctxt as done. */ + cp->argnumc = 0; + } + else + { + if (argnum == cp->argnum1) + { + cp->msgid = string; + cp->msgid_escape = type; + cp->msgid_context = context; + cp->msgid_pos.file_name = file_name; + cp->msgid_pos.line_number = line_number; + cp->msgid_comment = add_reference (comment); + stored_string = true; + /* Mark msgid as done. */ + cp->argnum1 = 0; + } + if (argnum == cp->argnum2) + { + cp->msgid_plural = string; + cp->msgid_plural_escape = type; + cp->msgid_plural_context = context; + cp->msgid_plural_pos.file_name = file_name; + cp->msgid_plural_pos.line_number = line_number; + stored_string = true; + /* Mark msgid_plural as done. */ + cp->argnum2 = 0; + } + } + } + /* Note: There is a memory leak here: When string was stored but is later + not used by arglist_parser_done, we don't free it. */ + if (!stored_string) + free (string); +} + +void +arglist_parser_remember (struct arglist_parser *ap, + int argnum, char *string, + flag_context_ty context, + char *file_name, size_t line_number, + refcounted_string_list_ty *comment) +{ + arglist_parser_remember_literal (ap, argnum, string, context, + file_name, line_number, + comment, LET_NONE); +} + +bool +arglist_parser_decidedp (struct arglist_parser *ap, int argnum) +{ + size_t i; + + /* Test whether all alternatives are decided. + Note: A decided alternative can be complete + cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 + && cp->argtotal == 0 + or it can be failed if no literal strings were found at the specified + argument positions: + cp->argnumc <= argnum && cp->argnum1 <= argnum && cp->argnum2 <= argnum + or it can be failed if the number of arguments is exceeded: + cp->argtotal > 0 && cp->argtotal < argnum + */ + for (i = 0; i < ap->nalternatives; i++) + { + struct partial_call *cp = &ap->alternative[i]; + + if (!((cp->argnumc <= argnum + && cp->argnum1 <= argnum + && cp->argnum2 <= argnum) + || (cp->argtotal > 0 && cp->argtotal < argnum))) + /* cp is still undecided. */ + return false; + } + return true; +} + + +void +arglist_parser_done (struct arglist_parser *ap, int argnum) +{ + size_t ncomplete; + size_t i; + + /* Determine the number of complete calls. */ + ncomplete = 0; + for (i = 0; i < ap->nalternatives; i++) + { + struct partial_call *cp = &ap->alternative[i]; + + if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 + && (cp->argtotal == 0 || cp->argtotal == argnum)) + ncomplete++; + } + + if (ncomplete > 0) + { + struct partial_call *best_cp = NULL; + bool ambiguous = false; + + /* Find complete calls where msgctxt, msgid, msgid_plural are all + provided. */ + for (i = 0; i < ap->nalternatives; i++) + { + struct partial_call *cp = &ap->alternative[i]; + + if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 + && (cp->argtotal == 0 || cp->argtotal == argnum) + && cp->msgctxt != NULL + && cp->msgid != NULL + && cp->msgid_plural != NULL) + { + if (best_cp != NULL) + { + ambiguous = true; + break; + } + best_cp = cp; + } + } + + if (best_cp == NULL) + { + struct partial_call *best_cp1 = NULL; + struct partial_call *best_cp2 = NULL; + + /* Find complete calls where msgctxt, msgid are provided. */ + for (i = 0; i < ap->nalternatives; i++) + { + struct partial_call *cp = &ap->alternative[i]; + + if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 + && (cp->argtotal == 0 || cp->argtotal == argnum) + && cp->msgctxt != NULL + && cp->msgid != NULL) + { + if (best_cp1 != NULL) + { + ambiguous = true; + break; + } + best_cp1 = cp; + } + } + + /* Find complete calls where msgid, msgid_plural are provided. */ + for (i = 0; i < ap->nalternatives; i++) + { + struct partial_call *cp = &ap->alternative[i]; + + if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 + && (cp->argtotal == 0 || cp->argtotal == argnum) + && cp->msgid != NULL + && cp->msgid_plural != NULL) + { + if (best_cp2 != NULL) + { + ambiguous = true; + break; + } + best_cp2 = cp; + } + } + + if (best_cp1 != NULL) + best_cp = best_cp1; + if (best_cp2 != NULL) + { + if (best_cp != NULL) + ambiguous = true; + else + best_cp = best_cp2; + } + } + + if (best_cp == NULL) + { + /* Find complete calls where msgid is provided. */ + for (i = 0; i < ap->nalternatives; i++) + { + struct partial_call *cp = &ap->alternative[i]; + + if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 + && (cp->argtotal == 0 || cp->argtotal == argnum) + && cp->msgid != NULL) + { + if (best_cp != NULL) + { + ambiguous = true; + break; + } + best_cp = cp; + } + } + } + + if (ambiguous) + { + error_with_progname = false; + error_at_line (0, 0, + best_cp->msgid_pos.file_name, + best_cp->msgid_pos.line_number, + _("ambiguous argument specification for keyword '%.*s'"), + (int) ap->keyword_len, ap->keyword); + error_with_progname = true; + } + + if (best_cp != NULL) + { + /* best_cp indicates the best found complete call. + Now call remember_a_message. */ + message_ty *mp; + + /* Split strings in the GNOME glib syntax "msgctxt|msgid". */ + if (best_cp->argnum1_glib_context || best_cp->argnum2_glib_context) + /* split_keywordspec should not allow the context to be specified + in two different ways. */ + if (best_cp->msgctxt != NULL) + abort (); + if (best_cp->argnum1_glib_context) + { + const char *separator = strchr (best_cp->msgid, '|'); + + if (separator == NULL) + { + error_with_progname = false; + error_at_line (0, 0, + best_cp->msgid_pos.file_name, + best_cp->msgid_pos.line_number, + _("warning: missing context for keyword '%.*s'"), + (int) ap->keyword_len, ap->keyword); + error_with_progname = true; + } + else + { + size_t ctxt_len = separator - best_cp->msgid; + char *ctxt = XNMALLOC (ctxt_len + 1, char); + + memcpy (ctxt, best_cp->msgid, ctxt_len); + ctxt[ctxt_len] = '\0'; + best_cp->msgctxt = ctxt; + best_cp->msgid = xstrdup (separator + 1); + } + } + if (best_cp->msgid_plural != NULL && best_cp->argnum2_glib_context) + { + const char *separator = strchr (best_cp->msgid_plural, '|'); + + if (separator == NULL) + { + error_with_progname = false; + error_at_line (0, 0, + best_cp->msgid_plural_pos.file_name, + best_cp->msgid_plural_pos.line_number, + _("warning: missing context for plural argument of keyword '%.*s'"), + (int) ap->keyword_len, ap->keyword); + error_with_progname = true; + } + else + { + size_t ctxt_len = separator - best_cp->msgid_plural; + char *ctxt = XNMALLOC (ctxt_len + 1, char); + + memcpy (ctxt, best_cp->msgid_plural, ctxt_len); + ctxt[ctxt_len] = '\0'; + if (best_cp->msgctxt == NULL) + best_cp->msgctxt = ctxt; + else + { + if (strcmp (ctxt, best_cp->msgctxt) != 0) + { + error_with_progname = false; + error_at_line (0, 0, + best_cp->msgid_plural_pos.file_name, + best_cp->msgid_plural_pos.line_number, + _("context mismatch between singular and plural form")); + error_with_progname = true; + } + free (ctxt); + } + best_cp->msgid_plural = xstrdup (separator + 1); + } + } + + { + flag_context_ty msgid_context = best_cp->msgid_context; + flag_context_ty msgid_plural_context = best_cp->msgid_plural_context; + struct literalstring_parser *parser = current_literalstring_parser; + const char *encoding; + + /* Special support for the 3-argument tr operator in Qt: + When --qt and --keyword=tr:1,1,2c,3t are specified, add to the + context the information that the argument is expeected to be a + qt-plural-format. */ + if (recognize_format_qt + && current_formatstring_parser3 == &formatstring_qt_plural + && best_cp->msgid_plural == best_cp->msgid) + { + msgid_context.is_format3 = yes_according_to_context; + msgid_plural_context.is_format3 = yes_according_to_context; + } + + if (best_cp->msgctxt != NULL) + { + if (parser != NULL && best_cp->msgctxt_escape != 0) + { + char *msgctxt = + parser->parse (best_cp->msgctxt, + &best_cp->msgctxt_pos, + best_cp->msgctxt_escape); + free (best_cp->msgctxt); + best_cp->msgctxt = msgctxt; + } + else + { + lex_pos_ty *pos = &best_cp->msgctxt_pos; + CONVERT_STRING (best_cp->msgctxt, lc_string); + } + } + + if (parser != NULL && best_cp->msgid_escape != 0) + { + char *msgid = parser->parse (best_cp->msgid, + &best_cp->msgid_pos, + best_cp->msgid_escape); + if (best_cp->msgid_plural == best_cp->msgid) + best_cp->msgid_plural = msgid; + free (best_cp->msgid); + best_cp->msgid = msgid; + } + else + { + lex_pos_ty *pos = &best_cp->msgid_pos; + CONVERT_STRING (best_cp->msgid, lc_string); + } + + if (best_cp->msgid_plural) + { + /* best_cp->msgid_plural may point to best_cp->msgid. + In that case, it is already interpreted and converted. */ + if (best_cp->msgid_plural != best_cp->msgid) + { + if (parser != NULL + && best_cp->msgid_plural_escape != 0) + { + char *msgid_plural = + parser->parse (best_cp->msgid_plural, + &best_cp->msgid_plural_pos, + best_cp->msgid_plural_escape); + free (best_cp->msgid_plural); + best_cp->msgid_plural = msgid_plural; + } + else + { + lex_pos_ty *pos = &best_cp->msgid_plural_pos; + CONVERT_STRING (best_cp->msgid_plural, lc_string); + } + } + + /* If best_cp->msgid_plural equals to best_cp->msgid, + the ownership will be transferred to + remember_a_message before it is passed to + remember_a_message_plural. + + Make a copy of the string in that case. */ + if (best_cp->msgid_plural == best_cp->msgid) + best_cp->msgid_plural = xstrdup (best_cp->msgid); + } + + if (best_cp->msgid_comment != NULL) + { + refcounted_string_list_ty *msgid_comment = + savable_comment_convert_encoding (best_cp->msgid_comment, + &best_cp->msgid_pos); + drop_reference (best_cp->msgid_comment); + best_cp->msgid_comment = msgid_comment; + } + + /* best_cp->msgctxt, best_cp->msgid, and best_cp->msgid_plural + are already in UTF-8. Prevent further conversion in + remember_a_message. */ + encoding = xgettext_current_source_encoding; + xgettext_current_source_encoding = po_charset_utf8; + mp = remember_a_message (ap->mlp, best_cp->msgctxt, best_cp->msgid, + msgid_context, + &best_cp->msgid_pos, + NULL, best_cp->msgid_comment); + if (mp != NULL && best_cp->msgid_plural != NULL) + remember_a_message_plural (mp, + best_cp->msgid_plural, + msgid_plural_context, + &best_cp->msgid_plural_pos, + NULL); + xgettext_current_source_encoding = encoding; + } + + if (best_cp->xcomments.nitems > 0) + { + /* Add best_cp->xcomments to mp->comment_dot, unless already + present. */ + size_t i; + + for (i = 0; i < best_cp->xcomments.nitems; i++) + { + const char *xcomment = best_cp->xcomments.item[i]; + bool found = false; + + if (mp != NULL && mp->comment_dot != NULL) + { + size_t j; + + for (j = 0; j < mp->comment_dot->nitems; j++) + if (strcmp (xcomment, mp->comment_dot->item[j]) == 0) + { + found = true; + break; + } + } + if (!found) + message_comment_dot_append (mp, xcomment); + } + } + } + } + else + { + /* No complete call was parsed. */ + /* Note: There is a memory leak here: When there is more than one + alternative, the same string can be stored in multiple alternatives, + and it's not easy to free all strings reliably. */ + if (ap->nalternatives == 1) + { + if (ap->alternative[0].msgctxt != NULL) + free (ap->alternative[0].msgctxt); + if (ap->alternative[0].msgid != NULL) + free (ap->alternative[0].msgid); + if (ap->alternative[0].msgid_plural != NULL) + free (ap->alternative[0].msgid_plural); + } + } + + for (i = 0; i < ap->nalternatives; i++) + drop_reference (ap->alternative[i].msgid_comment); + free (ap); +} + + +struct mixed_string_buffer * +mixed_string_buffer_alloc (lexical_context_ty lcontext, + const char *logical_file_name, + int line_number) +{ + struct mixed_string_buffer *bp = XMALLOC (struct mixed_string_buffer); + bp->utf8_buffer = NULL; + bp->utf8_buflen = 0; + bp->utf8_allocated = 0; + bp->utf16_surr = 0; + bp->curr_buffer = NULL; + bp->curr_buflen = 0; + bp->curr_allocated = 0; + bp->lcontext = lcontext; + bp->logical_file_name = logical_file_name; + bp->line_number = line_number; + return bp; +} + +/* Auxiliary function: Append a byte to bp->curr. */ +static inline void +mixed_string_buffer_append_to_curr_buffer (struct mixed_string_buffer *bp, + unsigned char c) +{ + if (bp->curr_buflen == bp->curr_allocated) + { + bp->curr_allocated = 2 * bp->curr_allocated + 10; + bp->curr_buffer = xrealloc (bp->curr_buffer, bp->curr_allocated); + } + bp->curr_buffer[bp->curr_buflen++] = c; +} + +/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */ +static inline void +mixed_string_buffer_grow_utf8_buffer (struct mixed_string_buffer *bp, + size_t count) +{ + if (bp->utf8_buflen + count > bp->utf8_allocated) + { + size_t new_allocated = 2 * bp->utf8_allocated + 10; + if (new_allocated < bp->utf8_buflen + count) + new_allocated = bp->utf8_buflen + count; + bp->utf8_allocated = new_allocated; + bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated); + } +} + +/* Auxiliary function: Append a Unicode character to bp->utf8. + uc must be < 0x110000. */ +static inline void +mixed_string_buffer_append_to_utf8_buffer (struct mixed_string_buffer *bp, + ucs4_t uc) +{ + unsigned char utf8buf[6]; + int count = u8_uctomb (utf8buf, uc, 6); + + if (count < 0) + /* The caller should have ensured that uc is not out-of-range. */ + abort (); + + mixed_string_buffer_grow_utf8_buffer (bp, count); + memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count); + bp->utf8_buflen += count; +} + +/* Auxiliary function: Flush bp->utf16_surr into bp->utf8_buffer. */ +static inline void +mixed_string_buffer_flush_utf16_surr (struct mixed_string_buffer *bp) +{ + if (bp->utf16_surr != 0) + { + /* A half surrogate is invalid, therefore use U+FFFD instead. */ + mixed_string_buffer_append_to_utf8_buffer (bp, 0xfffd); + bp->utf16_surr = 0; + } +} + +/* Auxiliary function: Flush bp->curr_buffer into bp->utf8_buffer. */ +static inline void +mixed_string_buffer_flush_curr_buffer (struct mixed_string_buffer *bp, + int line_number) +{ + if (bp->curr_buflen > 0) + { + char *curr; + size_t count; + + mixed_string_buffer_append_to_curr_buffer (bp, '\0'); + + /* Convert from the source encoding to UTF-8. */ + curr = from_current_source_encoding (bp->curr_buffer, bp->lcontext, + bp->logical_file_name, + line_number); + + /* Append it to bp->utf8_buffer. */ + count = strlen (curr); + mixed_string_buffer_grow_utf8_buffer (bp, count); + memcpy (bp->utf8_buffer + bp->utf8_buflen, curr, count); + bp->utf8_buflen += count; + + if (curr != bp->curr_buffer) + free (curr); + bp->curr_buflen = 0; + } +} + +void +mixed_string_buffer_append_char (struct mixed_string_buffer *bp, int c) +{ + /* Switch from Unicode character mode to multibyte character mode. */ + mixed_string_buffer_flush_utf16_surr (bp); + + /* When a newline is seen, convert the accumulated multibyte sequence. + This ensures a correct line number in the error message in case of + a conversion error. The "- 1" is to account for the newline. */ + if (c == '\n') + mixed_string_buffer_flush_curr_buffer (bp, bp->line_number - 1); + + mixed_string_buffer_append_to_curr_buffer (bp, (unsigned char) c); +} + +void +mixed_string_buffer_append_unicode (struct mixed_string_buffer *bp, int c) +{ + /* Switch from multibyte character mode to Unicode character mode. */ + mixed_string_buffer_flush_curr_buffer (bp, bp->line_number); + + /* Test whether this character and the previous one form a Unicode + surrogate character pair. */ + if (bp->utf16_surr != 0 && (c >= 0xdc00 && c < 0xe000)) + { + unsigned short utf16buf[2]; + ucs4_t uc; + + utf16buf[0] = bp->utf16_surr; + utf16buf[1] = c; + if (u16_mbtouc (&uc, utf16buf, 2) != 2) + abort (); + + mixed_string_buffer_append_to_utf8_buffer (bp, uc); + bp->utf16_surr = 0; + } + else + { + mixed_string_buffer_flush_utf16_surr (bp); + + if (c >= 0xd800 && c < 0xdc00) + bp->utf16_surr = c; + else if (c >= 0xdc00 && c < 0xe000) + { + /* A half surrogate is invalid, therefore use U+FFFD instead. */ + mixed_string_buffer_append_to_utf8_buffer (bp, 0xfffd); + } + else + mixed_string_buffer_append_to_utf8_buffer (bp, c); + } +} + +char * +mixed_string_buffer_done (struct mixed_string_buffer *bp) +{ + char *utf8_buffer; + + /* Flush all into bp->utf8_buffer. */ + mixed_string_buffer_flush_utf16_surr (bp); + mixed_string_buffer_flush_curr_buffer (bp, bp->line_number); + /* NUL-terminate it. */ + mixed_string_buffer_grow_utf8_buffer (bp, 1); + bp->utf8_buffer[bp->utf8_buflen] = '\0'; + + /* Free curr_buffer and bp itself. */ + utf8_buffer = bp->utf8_buffer; + free (bp->curr_buffer); + free (bp); + + /* Return it. */ + return utf8_buffer; +} + + +static message_ty * +construct_header () +{ + char *project_id_version; + time_t now; + char *timestring; + message_ty *mp; + char *msgstr; + char *comment; + static lex_pos_ty pos = { __FILE__, __LINE__ }; + + if (package_name != NULL) + { + if (package_version != NULL) + project_id_version = xasprintf ("%s %s", package_name, package_version); + else + project_id_version = xasprintf ("%s", package_name); + } + else + project_id_version = xstrdup ("PACKAGE VERSION"); + + if (msgid_bugs_address != NULL && msgid_bugs_address[0] == '\0') + multiline_warning (xasprintf (_("warning: ")), + xstrdup (_("\ +The option --msgid-bugs-address was not specified.\n\ +If you are using a 'Makevars' file, please specify\n\ +the MSGID_BUGS_ADDRESS variable there; otherwise please\n\ +specify an --msgid-bugs-address command line option.\n\ +"))); + + time (&now); + timestring = po_strftime (&now); + + msgstr = xasprintf ("\ +Project-Id-Version: %s\n\ +Report-Msgid-Bugs-To: %s\n\ +POT-Creation-Date: %s\n\ +PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n\ +Last-Translator: FULL NAME \n\ +Language-Team: LANGUAGE \n\ +Language: \n\ +MIME-Version: 1.0\n\ +Content-Type: text/plain; charset=CHARSET\n\ +Content-Transfer-Encoding: 8bit\n", + project_id_version, + msgid_bugs_address != NULL ? msgid_bugs_address : "", + timestring); + free (timestring); + free (project_id_version); + + mp = message_alloc (NULL, "", NULL, msgstr, strlen (msgstr) + 1, &pos); + + if (copyright_holder[0] != '\0') + comment = xasprintf ("\ +SOME DESCRIPTIVE TITLE.\n\ +Copyright (C) YEAR %s\n\ +This file is distributed under the same license as the PACKAGE package.\n\ +FIRST AUTHOR , YEAR.\n", + copyright_holder); + else + comment = xstrdup ("\ +SOME DESCRIPTIVE TITLE.\n\ +This file is put in the public domain.\n\ +FIRST AUTHOR , YEAR.\n"); + message_comment_append (mp, comment); + free (comment); + + mp->is_fuzzy = true; + + return mp; +} + +static void +finalize_header (msgdomain_list_ty *mdlp) +{ + /* If the generated PO file has plural forms, add a Plural-Forms template + to the constructed header. */ + { + bool has_plural; + size_t i, j; + + has_plural = false; + for (i = 0; i < mdlp->nitems; i++) + { + message_list_ty *mlp = mdlp->item[i]->messages; + + for (j = 0; j < mlp->nitems; j++) + { + message_ty *mp = mlp->item[j]; + + if (mp->msgid_plural != NULL) + { + has_plural = true; + break; + } + } + if (has_plural) + break; + } + + if (has_plural) + { + message_ty *header = + message_list_search (mdlp->item[0]->messages, NULL, ""); + if (header != NULL + && c_strstr (header->msgstr, "Plural-Forms:") == NULL) + { + size_t insertpos = strlen (header->msgstr); + const char *suffix; + size_t suffix_len; + char *new_msgstr; + + suffix = "\nPlural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"; + if (insertpos == 0 || header->msgstr[insertpos-1] == '\n') + suffix++; + suffix_len = strlen (suffix); + new_msgstr = XNMALLOC (header->msgstr_len + suffix_len, char); + memcpy (new_msgstr, header->msgstr, insertpos); + memcpy (new_msgstr + insertpos, suffix, suffix_len); + memcpy (new_msgstr + insertpos + suffix_len, + header->msgstr + insertpos, + header->msgstr_len - insertpos); + header->msgstr = new_msgstr; + header->msgstr_len = header->msgstr_len + suffix_len; + } + } + } + + /* If not all the strings were plain ASCII, or if the output syntax + requires a charset conversion, set the charset in the header to UTF-8. + All messages have already been converted to UTF-8 in remember_a_message + and remember_a_message_plural. */ + { + bool has_nonascii = false; + size_t i; + + for (i = 0; i < mdlp->nitems; i++) + { + message_list_ty *mlp = mdlp->item[i]->messages; + + if (!is_ascii_message_list (mlp)) + has_nonascii = true; + } + + if (has_nonascii || output_syntax->requires_utf8) + { + message_list_ty *mlp = mdlp->item[0]->messages; + + iconv_message_list (mlp, po_charset_utf8, po_charset_utf8, NULL); + } + } +} + + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) +#define ENDOF(a) ((a) + SIZEOF(a)) + + +static extractor_ty +language_to_extractor (const char *name) +{ + struct table_ty + { + const char *name; + extractor_func func; + flag_context_list_table_ty *flag_table; + struct formatstring_parser *formatstring_parser1; + struct formatstring_parser *formatstring_parser2; + struct literalstring_parser *literalstring_parser; + }; + typedef struct table_ty table_ty; + + static table_ty table[] = + { + SCANNERS_C + SCANNERS_PO + SCANNERS_SH + SCANNERS_PYTHON + SCANNERS_LISP + SCANNERS_ELISP + SCANNERS_LIBREP + SCANNERS_SCHEME + SCANNERS_SMALLTALK + SCANNERS_JAVA + SCANNERS_PROPERTIES + SCANNERS_CSHARP + SCANNERS_AWK + SCANNERS_YCP + SCANNERS_TCL + SCANNERS_PERL + SCANNERS_PHP + SCANNERS_STRINGTABLE + SCANNERS_RST + SCANNERS_GLADE + SCANNERS_LUA + SCANNERS_JAVASCRIPT + SCANNERS_VALA + SCANNERS_GSETTINGS + SCANNERS_DESKTOP + /* Here may follow more languages and their scanners: pike, etc... + Make sure new scanners honor the --exclude-file option. */ + }; + + table_ty *tp; + + for (tp = table; tp < ENDOF(table); ++tp) + if (c_strcasecmp (name, tp->name) == 0) + { + extractor_ty result; + + result.func = tp->func; + result.flag_table = tp->flag_table; + result.formatstring_parser1 = tp->formatstring_parser1; + result.formatstring_parser2 = tp->formatstring_parser2; + result.formatstring_parser3 = NULL; + result.literalstring_parser = tp->literalstring_parser; + + /* Handle --qt. It's preferrable to handle this facility here rather + than through an option --language=C++/Qt because the latter would + conflict with the language "C++" regarding the file extensions. */ + if (recognize_format_qt && strcmp (tp->name, "C++") == 0) + { + result.flag_table = &flag_table_cxx_qt; + result.formatstring_parser2 = &formatstring_qt; + result.formatstring_parser3 = &formatstring_qt_plural; + } + /* Likewise for --kde. */ + if (recognize_format_kde && strcmp (tp->name, "C++") == 0) + { + result.flag_table = &flag_table_cxx_kde; + result.formatstring_parser2 = &formatstring_kde; + } + /* Likewise for --boost. */ + if (recognize_format_boost && strcmp (tp->name, "C++") == 0) + { + result.flag_table = &flag_table_cxx_boost; + result.formatstring_parser2 = &formatstring_boost; + } + + return result; + } + + error (EXIT_FAILURE, 0, _("language '%s' unknown"), name); + /* NOTREACHED */ + { + extractor_ty result = { NULL, NULL, NULL, NULL }; + return result; + } +} + + +static const char * +extension_to_language (const char *extension) +{ + struct table_ty + { + const char *extension; + const char *language; + }; + typedef struct table_ty table_ty; + + static table_ty table[] = + { + EXTENSIONS_C + EXTENSIONS_PO + EXTENSIONS_SH + EXTENSIONS_PYTHON + EXTENSIONS_LISP + EXTENSIONS_ELISP + EXTENSIONS_LIBREP + EXTENSIONS_SCHEME + EXTENSIONS_SMALLTALK + EXTENSIONS_JAVA + EXTENSIONS_PROPERTIES + EXTENSIONS_CSHARP + EXTENSIONS_AWK + EXTENSIONS_YCP + EXTENSIONS_TCL + EXTENSIONS_PERL + EXTENSIONS_PHP + EXTENSIONS_STRINGTABLE + EXTENSIONS_RST + EXTENSIONS_GLADE + EXTENSIONS_LUA + EXTENSIONS_JAVASCRIPT + EXTENSIONS_VALA + EXTENSIONS_GSETTINGS + EXTENSIONS_DESKTOP + /* Here may follow more file extensions... */ + }; + + table_ty *tp; + + for (tp = table; tp < ENDOF(table); ++tp) + if (strcmp (extension, tp->extension) == 0) + return tp->language; + return NULL; +} diff --git a/gettext-tools/src/xgettext.h b/gettext-tools/src/xgettext.h new file mode 100644 index 0000000..c852ae3 --- /dev/null +++ b/gettext-tools/src/xgettext.h @@ -0,0 +1,416 @@ +/* xgettext common functions. + Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011 Free Software Foundation, Inc. + Written by Peter Miller + and Bruno Haible , 2001. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _XGETTEXT_H +#define _XGETTEXT_H + +#include +#include +#include + +#if HAVE_ICONV +#include +#endif + +#include "message.h" +#include "pos.h" +#include "str-list.h" + +/* Declare 'line_comment' and 'input_syntax'. */ +#include "read-catalog.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* If true, omit the header entry. + If false, keep the header entry present in the input. */ +extern int xgettext_omit_header; + +extern bool substring_match; + + +/* Calling convention for a given keyword. */ +struct callshape +{ + int argnum1; /* argument number to use for msgid */ + int argnum2; /* argument number to use for msgid_plural */ + int argnumc; /* argument number to use for msgctxt */ + bool argnum1_glib_context; /* argument argnum1 has the syntax "ctxt|msgid" */ + bool argnum2_glib_context; /* argument argnum2 has the syntax "ctxt|msgid" */ + int argtotal; /* total number of arguments */ + string_list_ty xcomments; /* auto-extracted comments */ +}; + +/* Split keyword spec into keyword, argnum1, argnum2, argnumc. */ +extern void split_keywordspec (const char *spec, const char **endp, + struct callshape *shapep); + +/* Set of alternative calling conventions for a given keyword. */ +struct callshapes +{ + const char *keyword; /* the keyword, not NUL terminated */ + size_t keyword_len; /* the keyword's length */ + size_t nshapes; + struct callshape shapes[1]; /* actually nshapes elements */ +}; + +/* Insert a (keyword, callshape) pair into a hash table mapping keyword to + 'struct callshapes *'. */ +extern void insert_keyword_callshape (hash_table *table, + const char *keyword, size_t keyword_len, + const struct callshape *shape); + + +/* Context representing some flags. */ +typedef struct flag_context_ty flag_context_ty; +struct flag_context_ty +{ + /* Regarding the primary formatstring type. */ + /*enum is_format*/ unsigned int is_format1 : 3; + /*bool*/ unsigned int pass_format1 : 1; + /* Regarding the secondary formatstring type. */ + /*enum is_format*/ unsigned int is_format2 : 3; + /*bool*/ unsigned int pass_format2 : 1; + /* Regarding the tertiary formatstring type. */ + /*enum is_format*/ unsigned int is_format3 : 3; + /*bool*/ unsigned int pass_format3 : 1; +}; +/* Null context. */ +extern flag_context_ty null_context; +/* Transparent context. */ +extern flag_context_ty passthrough_context; +/* Compute an inherited context. + The outer_context is assumed to have all pass_format* flags = false. + The result will then also have all pass_format* flags = false. */ +extern flag_context_ty + inherited_context (flag_context_ty outer_context, + flag_context_ty modifier_context); + +/* Context representing some flags, for each possible argument number. + This is a linked list, sorted according to the argument number. */ +typedef struct flag_context_list_ty flag_context_list_ty; +struct flag_context_list_ty +{ + int argnum; /* current argument number, > 0 */ + flag_context_ty flags; /* flags for current argument */ + flag_context_list_ty *next; +}; + +/* Iterator through a flag_context_list_ty. */ +typedef struct flag_context_list_iterator_ty flag_context_list_iterator_ty; +struct flag_context_list_iterator_ty +{ + int argnum; /* current argument number, > 0 */ + const flag_context_list_ty* head; /* tail of list */ +}; +extern flag_context_list_iterator_ty null_context_list_iterator; +extern flag_context_list_iterator_ty passthrough_context_list_iterator; +extern flag_context_list_iterator_ty + flag_context_list_iterator (flag_context_list_ty *list); +extern flag_context_ty + flag_context_list_iterator_advance (flag_context_list_iterator_ty *iter); + +/* For nearly each backend, we have a separate table mapping a keyword to + a flag_context_list_ty *. */ +typedef hash_table /* char[] -> flag_context_list_ty * */ + flag_context_list_table_ty; +extern flag_context_list_ty * + flag_context_list_table_lookup (flag_context_list_table_ty *flag_table, + const void *key, size_t keylen); +/* Record a flag in the appropriate backend's table. */ +extern void xgettext_record_flag (const char *optionstring); + + +/* Context while building up lexical tokens. */ +typedef enum + { + lc_outside, /* Initial context: outside of comments and strings. */ + lc_comment, /* Inside a comment. */ + lc_string, /* Inside a string literal. */ + + /* For embedded XML in programming code, like E4X in JavaScript. */ + lc_xml_open_tag, /* Inside an opening tag of an XML element. */ + lc_xml_close_tag, /* Inside a closing tag of an XML element. */ + lc_xml_content /* Inside an XML text node. */ + } + lexical_context_ty; + +/* Error message about non-ASCII character in a specific lexical context. */ +extern char *non_ascii_error_message (lexical_context_ty lcontext, + const char *file_name, + size_t line_number); + + +/* Canonicalized encoding name for all input files. */ +extern const char *xgettext_global_source_encoding; + +#if HAVE_ICONV +/* Converter from xgettext_global_source_encoding to UTF-8 (except from + ASCII or UTF-8, when this conversion is a no-op). */ +extern iconv_t xgettext_global_source_iconv; +#endif + +/* Canonicalized encoding name for the current input file. */ +extern const char *xgettext_current_source_encoding; + +#if HAVE_ICONV +/* Converter from xgettext_current_source_encoding to UTF-8 (except from + ASCII or UTF-8, when this conversion is a no-op). */ +extern iconv_t xgettext_current_source_iconv; +#endif + +/* Convert the given string from xgettext_current_source_encoding to + the output file encoding (i.e. ASCII or UTF-8). + The resulting string is either the argument string, or freshly allocated. + The lcontext, file_name and line_number are only used for error message + purposes. */ +extern char *from_current_source_encoding (const char *string, + lexical_context_ty lcontext, + const char *file_name, + size_t line_number); + + +/* List of messages whose msgids must not be extracted, or NULL. + Used by remember_a_message(). */ +extern message_list_ty *exclude; + + +/* Comment handling for backends which support combining adjacent strings + even across lines. + In these backends we cannot use the xgettext_comment* functions directly, + because in multiline string expressions like + "string1" + + "string2" + the newline between "string1" and "string2" would cause a call to + xgettext_comment_reset(), thus destroying the accumulated comments + that we need a little later, when we have concatenated the two strings + and pass them to remember_a_message(). + Instead, we do the bookkeeping of the accumulated comments directly, + and save a pointer to the accumulated comments when we read "string1". + In order to avoid excessive copying of strings, we use reference + counting. */ + +typedef struct refcounted_string_list_ty refcounted_string_list_ty; +struct refcounted_string_list_ty +{ + unsigned int refcount; + struct string_list_ty contents; +}; + +static inline refcounted_string_list_ty * +add_reference (refcounted_string_list_ty *rslp) +{ + if (rslp != NULL) + rslp->refcount++; + return rslp; +} + +static inline void +drop_reference (refcounted_string_list_ty *rslp) +{ + if (rslp != NULL) + { + if (rslp->refcount > 1) + rslp->refcount--; + else + { + string_list_destroy (&rslp->contents); + free (rslp); + } + } +} + +extern refcounted_string_list_ty *savable_comment; +extern void savable_comment_add (const char *str); +extern void savable_comment_reset (void); + +/* Convert character encoding of COMMENT according to the current + source encoding. Returns a new refcounted_string_list_ty. */ +extern refcounted_string_list_ty * + savable_comment_convert_encoding (refcounted_string_list_ty *comment, + lex_pos_ty *pos); + + +enum literalstring_escape_type +{ + LET_NONE = 0, + LET_ANSI_C = 1 << 0, + LET_UNICODE = 1 << 1 +}; + +struct literalstring_parser +{ + char * (*parse) (const char *string, lex_pos_ty *pos, + enum literalstring_escape_type type); +}; + +/* Add a message to the list of extracted messages. + msgctxt must be either NULL or a malloc()ed string; its ownership is passed + to the callee. + MSGID must be a malloc()ed string; its ownership is passed to the callee. + POS->file_name must be allocated with indefinite extent. + EXTRACTED_COMMENT is a comment that needs to be copied into the POT file, + or NULL. + COMMENT may be savable_comment, or it may be a saved copy of savable_comment + (then add_reference must be used when saving it, and drop_reference while + dropping it). Clear savable_comment. + Return the new or found message, or NULL if the message is excluded. */ +extern message_ty *remember_a_message (message_list_ty *mlp, + char *msgctxt, + char *msgid, + flag_context_ty context, + lex_pos_ty *pos, + const char *extracted_comment, + refcounted_string_list_ty *comment); + +/* Add an msgid_plural to a message previously returned by + remember_a_message. + STRING must be a malloc()ed string; its ownership is passed to the callee. + POS->file_name must be allocated with indefinite extent. + COMMENT may be savable_comment, or it may be a saved copy of savable_comment + (then add_reference must be used when saving it, and drop_reference while + dropping it). Clear savable_comment. */ +extern void remember_a_message_plural (message_ty *mp, + char *string, + flag_context_ty context, + lex_pos_ty *pos, + refcounted_string_list_ty *comment); + +/* Represents the progressive parsing of an argument list w.r.t. a single + 'struct callshape'. */ +struct partial_call +{ + int argnumc; /* number of context argument, 0 when seen */ + int argnum1; /* number of singular argument, 0 when seen */ + int argnum2; /* number of plural argument, 0 when seen */ + bool argnum1_glib_context; /* argument argnum1 has the syntax "ctxt|msgid" */ + bool argnum2_glib_context; /* argument argnum2 has the syntax "ctxt|msgid" */ + int argtotal; /* total number of arguments, 0 if unspecified */ + string_list_ty xcomments; /* auto-extracted comments */ + char *msgctxt; /* context - owned string, or NULL */ + enum literalstring_escape_type msgctxt_escape; + lex_pos_ty msgctxt_pos; + char *msgid; /* msgid - owned string, or NULL */ + enum literalstring_escape_type msgid_escape; + flag_context_ty msgid_context; + lex_pos_ty msgid_pos; + refcounted_string_list_ty *msgid_comment; + char *msgid_plural; /* msgid_plural - owned string, or NULL */ + enum literalstring_escape_type msgid_plural_escape; + flag_context_ty msgid_plural_context; + lex_pos_ty msgid_plural_pos; +}; + +/* Represents the progressive parsing of an argument list w.r.t. an entire + 'struct callshapes'. */ +struct arglist_parser +{ + message_list_ty *mlp; /* list where the message shall be added */ + const char *keyword; /* the keyword, not NUL terminated */ + size_t keyword_len; /* the keyword's length */ + size_t nalternatives; /* number of partial_call alternatives */ + struct partial_call alternative[1]; /* partial_call alternatives */ +}; + +/* Creates a fresh arglist_parser recognizing calls. + You can pass shapes = NULL for a parser not recognizing any calls. */ +extern struct arglist_parser * arglist_parser_alloc (message_list_ty *mlp, + const struct callshapes *shapes); +/* Clones an arglist_parser. */ +extern struct arglist_parser * arglist_parser_clone (struct arglist_parser *ap); +/* Adds a string argument to an arglist_parser. ARGNUM must be > 0. + STRING must be malloc()ed string; its ownership is passed to the callee. + FILE_NAME must be allocated with indefinite extent. + COMMENT may be savable_comment, or it may be a saved copy of savable_comment + (then add_reference must be used when saving it, and drop_reference while + dropping it). Clear savable_comment. */ +extern void arglist_parser_remember (struct arglist_parser *ap, + int argnum, char *string, + flag_context_ty context, + char *file_name, size_t line_number, + refcounted_string_list_ty *comment); +/* Adds an uninterpreted string argument to an arglist_parser. ARGNUM + must be > 0. + STRING is must be malloc()ed string; its ownership is passed to the callee. + FILE_NAME must be allocated with indefinite extent. + COMMENT may be savable_comment, or it may be a saved copy of savable_comment + (then add_reference must be used when saving it, and drop_reference while + dropping it). Clear savable_comment. */ +extern void arglist_parser_remember_literal (struct arglist_parser *ap, + int argnum, char *string, + flag_context_ty context, + char *file_name, size_t line_number, + refcounted_string_list_ty *comment, + enum literalstring_escape_type type); +/* Tests whether an arglist_parser has is not waiting for more arguments after + argument ARGNUM. */ +extern bool arglist_parser_decidedp (struct arglist_parser *ap, int argnum); +/* Terminates the processing of an arglist_parser after argument ARGNUM and + deletes it. */ +extern void arglist_parser_done (struct arglist_parser *ap, int argnum); + + +/* A string buffer type that allows appending bytes (in the + xgettext_current_source_encoding) or Unicode characters. + Returns the entire string in UTF-8 encoding. */ + +struct mixed_string_buffer +{ + /* The part of the string that has already been converted to UTF-8. */ + char *utf8_buffer; + size_t utf8_buflen; + size_t utf8_allocated; + /* The first half of an UTF-16 surrogate character. */ + unsigned short utf16_surr; + /* The part of the string that is still in the source encoding. */ + char *curr_buffer; + size_t curr_buflen; + size_t curr_allocated; + /* The lexical context. Used only for error message purposes. */ + lexical_context_ty lcontext; + const char *logical_file_name; + int line_number; +}; + +/* Creates a fresh mixed_string_buffer. */ +extern struct mixed_string_buffer * + mixed_string_buffer_alloc (lexical_context_ty lcontext, + const char *logical_file_name, + int line_number); + +/* Appends a character to a mixed_string_buffer. */ +extern void mixed_string_buffer_append_char (struct mixed_string_buffer *bp, + int c); + +/* Appends a Unicode character to a mixed_string_buffer. */ +extern void mixed_string_buffer_append_unicode (struct mixed_string_buffer *bp, + int c); + +/* Frees mixed_string_buffer and returns the accumulated string in UTF-8. */ +extern char * mixed_string_buffer_done (struct mixed_string_buffer *bp); + + +#ifdef __cplusplus +} +#endif + + +#endif /* _XGETTEXT_H */ -- cgit v1.2.1