68c7442979c8ecc27c365a63e5d6368f216b6fed
[pspp] / lib / rpmatch.c
1 /* Determine whether string value is affirmation or negative response
2    according to current locale's data.
3
4    Copyright (C) 1996, 1998, 2000, 2002, 2003, 2006-2008 Free Software
5    Foundation, Inc.
6
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20 #include <config.h>
21
22 #include <stdbool.h>
23 #include <stddef.h>
24 #include <stdlib.h>
25
26 #if ENABLE_NLS
27 # include <sys/types.h>
28 # include <limits.h>
29 # include <string.h>
30 # if HAVE_LANGINFO_YESEXPR
31 #  include <langinfo.h>
32 # endif
33 # include <regex.h>
34 # include "gettext.h"
35 # define _(msgid) gettext (msgid)
36 # define N_(msgid) gettext_noop (msgid)
37
38 # if HAVE_LANGINFO_YESEXPR
39 /* Return the localized regular expression pattern corresponding to
40    ENGLISH_PATTERN.  NL_INDEX can be used with nl_langinfo.
41    The resulting string may only be used until the next nl_langinfo call.  */
42 static const char *
43 localized_pattern (const char *english_pattern, nl_item nl_index,
44                    bool posixly_correct)
45 {
46   const char *translated_pattern;
47
48   /* We prefer to get the patterns from a PO file.  It would be possible to
49      always use nl_langinfo (YESEXPR) instead of _("^[yY]"), and
50      nl_langinfo (NOEXPR) instead of _("^[nN]"), if we could assume that the
51      system's locale support is good.  But this is not the case e.g. on Cygwin.
52      The localizations of gnulib.pot are of better quality in general.
53      Also, if we use locale info from non-free systems that don't have a
54      'localedef' command, we deprive the users of the freedom to localize
55      this pattern for their preferred language.
56      But some programs, such as 'cp', 'mv', 'rm', 'find', 'xargs', are
57      specified by POSIX to use nl_langinfo (YESEXPR).  We implement this
58      behaviour if POSIXLY_CORRECT is set, for the sake of these programs.  */
59
60   /* If the user wants strict POSIX compliance, use nl_langinfo.  */
61   if (posixly_correct)
62     {
63       translated_pattern = nl_langinfo (nl_index);
64       /* Check against a broken system return value.  */
65       if (translated_pattern != NULL && translated_pattern[0] != '\0')
66         return translated_pattern;
67    }
68
69   /* Look in the gnulib message catalog.  */
70   translated_pattern = _(english_pattern);
71   if (translated_pattern == english_pattern)
72     {
73       /* The gnulib message catalog provides no translation.
74          Try the system's message catalog.  */
75       translated_pattern = nl_langinfo (nl_index);
76       /* Check against a broken system return value.  */
77       if (translated_pattern != NULL && translated_pattern[0] != '\0')
78         return translated_pattern;
79       /* Fall back to English.  */
80       translated_pattern = english_pattern;
81     }
82   return translated_pattern;
83 }
84 # else
85 #  define localized_pattern(english_pattern,nl_index,posixly_correct) \
86      _(english_pattern)
87 # endif
88
89 static int
90 try (const char *response, const char *pattern, char **lastp, regex_t *re)
91 {
92   if (*lastp == NULL || strcmp (pattern, *lastp) != 0)
93     {
94       char *safe_pattern;
95
96       /* The pattern has changed.  */
97       if (*lastp != NULL)
98         {
99           /* Free the old compiled pattern.  */
100           regfree (re);
101           free (*lastp);
102           *lastp = NULL;
103         }
104       /* Put the PATTERN into safe memory before calling regcomp.
105          (regcomp may call nl_langinfo, overwriting PATTERN's storage.  */
106       safe_pattern = strdup (pattern);
107       if (safe_pattern == NULL)
108         return -1;
109       /* Compile the pattern and cache it for future runs.  */
110       if (regcomp (re, safe_pattern, REG_EXTENDED) != 0)
111         return -1;
112       *lastp = safe_pattern;
113     }
114
115   /* See if the regular expression matches RESPONSE.  */
116   return regexec (re, response, 0, NULL, 0) == 0;
117 }
118 #endif
119
120
121 /* Test a user response to a question.
122    Return 1 if it is affirmative, 0 if it is negative, or -1 if not clear.  */
123
124 int
125 rpmatch (const char *response)
126 {
127 #if ENABLE_NLS
128   /* Match against one of the response patterns, compiling the pattern
129      first if necessary.  */
130
131   /* We cache the response patterns and compiled regexps here.  */
132   static char *last_yesexpr, *last_noexpr;
133   static regex_t cached_yesre, cached_nore;
134
135 # if HAVE_LANGINFO_YESEXPR
136   bool posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL);
137 # endif
138
139   const char *yesexpr, *noexpr;
140   int result;
141
142   /* TRANSLATORS: A regular expression testing for an affirmative answer
143      (english: "yes").  Testing the first character may be sufficient.
144      Take care to consider upper and lower case.
145      To enquire the regular expression that your system uses for this
146      purpose, you can use the command
147        locale -k LC_MESSAGES | grep '^yesexpr='  */
148   yesexpr = localized_pattern (N_("^[yY]"), YESEXPR, posixly_correct);
149   result = try (response, yesexpr, &last_yesexpr, &cached_yesre);
150   if (result < 0)
151     return -1;
152   if (result)
153     return 1;
154
155   /* TRANSLATORS: A regular expression testing for a negative answer
156      (english: "no").  Testing the first character may be sufficient.
157      Take care to consider upper and lower case.
158      To enquire the regular expression that your system uses for this
159      purpose, you can use the command
160        locale -k LC_MESSAGES | grep '^noexpr='  */
161   noexpr = localized_pattern (N_("^[nN]"), NOEXPR, posixly_correct);
162   result = try (response, noexpr, &last_noexpr, &cached_nore);
163   if (result < 0)
164     return -1;
165   if (result)
166     return 0;
167
168   return -1;
169 #else
170   /* Test against "^[yY]" and "^[nN]", hardcoded to avoid requiring regex */
171   return (*response == 'y' || *response == 'Y' ? 1
172           : *response == 'n' || *response == 'N' ? 0 : -1);
173 #endif
174 }