1 /* KUIT (KDE User Interface Text) format strings.
2 Copyright (C) 2015 Free Software Foundation, Inc.
3 Written by Daiki Ueno <ueno@gnu.org>, 2015.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
29 #include "xvasprintf.h"
33 /* Use included markup parser to avoid extra dependency from
34 libgettextpo to libxml2. */
35 # ifndef FORMAT_KDE_KUIT_FALLBACK_MARKUP
36 # define FORMAT_KDE_KUIT_USE_FALLBACK_MARKUP 1
39 # define FORMAT_KDE_KUIT_USE_LIBXML2 1
42 #if FORMAT_KDE_KUIT_USE_LIBXML2
43 # include <libxml/parser.h>
44 #elif FORMAT_KDE_KUIT_USE_FALLBACK_MARKUP
49 #define _(str) gettext (str)
51 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
54 /* KUIT (KDE User Interface Text) is an XML-like markup which augments
55 translatable strings with semantic information:
56 http://api.kde.org/frameworks-api/frameworks5-apidocs/ki18n/html/prg_guide.html#kuit_markup
57 KUIT can be seen as a fragment of a well-formed XML document,
58 except that it allows '&' as a Qt accelerator marker and '%' as a
63 /* A format string descriptor returned from formatstring_kde.parse. */
67 #define XML_NS "https://www.gnu.org/s/gettext/kde"
75 /* Character ranges for NameStartChar defined in:
76 http://www.w3.org/TR/REC-xml/#NT-NameStartChar */
77 static const struct char_range name_chars1[] =
97 /* Character ranges for NameChar, excluding NameStartChar:
98 http://www.w3.org/TR/REC-xml/#NT-NameChar */
99 static const struct char_range name_chars2[] =
109 /* Return true if INPUT is an XML reference. */
111 is_reference (const char *input)
113 const char *str = input;
114 const char *str_limit = str + strlen (input);
118 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
121 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
126 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
129 while (str < str_limit)
131 str += u8_mbtouc (&uc, (const unsigned char *) str,
133 if (!(('0' <= uc && uc <= '9')
134 || ('A' <= uc && uc <= 'F')
135 || ('a' <= uc && uc <= 'f')))
140 else if ('0' <= uc && uc <= '9')
142 while (str < str_limit)
144 str += u8_mbtouc (&uc, (const unsigned char *) str,
146 if (!('0' <= uc && uc <= '9'))
155 for (i = 0; i < SIZEOF (name_chars1); i++)
156 if (name_chars1[i].start <= uc && uc <= name_chars1[i].end)
159 if (i == SIZEOF (name_chars1))
162 while (str < str_limit)
164 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
165 for (i = 0; i < SIZEOF (name_chars1); i++)
166 if (name_chars1[i].start <= uc && uc <= name_chars1[i].end)
168 if (i == SIZEOF (name_chars1))
170 for (i = 0; i < SIZEOF (name_chars2); i++)
171 if (name_chars2[i].start <= uc && uc <= name_chars2[i].end)
173 if (i == SIZEOF (name_chars2))
185 format_parse (const char *format, bool translated, char *fdi,
186 char **invalid_reason)
191 const char *str_limit;
197 /* Preprocess the input, putting the content in a <gt:kuit> element. */
199 str_limit = str + strlen (format);
201 for (amp_count = 0; str < str_limit; amp_count++)
203 const char *amp = strchrnul (str, '&');
209 buffer = xmalloc (amp_count * 4
211 + strlen ("<gt:kuit xmlns:gt=\"" XML_NS "\"></gt:kuit>")
216 bp = stpcpy (bp, "<gt:kuit xmlns:gt=\"" XML_NS "\">");
218 while (str < str_limit)
220 const char *amp = strchrnul (str, '&');
222 bp = stpncpy (bp, str, amp - str);
226 bp = stpcpy (bp, is_reference (amp) ? "&" : "&");
229 stpcpy (bp, "</gt:kuit>");
231 #if FORMAT_KDE_KUIT_USE_LIBXML2
235 doc = xmlReadMemory (buffer, strlen (buffer), "", NULL,
237 | XML_PARSE_NOWARNING
239 | XML_PARSE_NOBLANKS);
242 xmlError *err = xmlGetLastError ();
244 xasprintf (_("error while parsing: %s"),
254 #elif FORMAT_KDE_KUIT_FALLBACK_MARKUP
256 markup_parser_ty parser;
257 markup_parse_context_ty *context;
259 memset (&parser, 0, sizeof (markup_parser_ty));
260 context = markup_parse_context_new (&parser, 0, NULL);
261 if (!markup_parse_context_parse (context, buffer, strlen (buffer)))
264 xasprintf (_("error while parsing: %s"),
265 markup_parse_context_get_error (context));
267 markup_parse_context_free (context);
271 if (!markup_parse_context_end_parse (context))
274 xasprintf (_("error while parsing: %s"),
275 markup_parse_context_get_error (context));
277 markup_parse_context_free (context);
282 markup_parse_context_free (context);
285 /* No support for XML. */
288 spec.base = formatstring_kde.parse (format, translated, fdi, invalid_reason);
289 if (spec.base == NULL)
292 result = XMALLOC (struct spec);
298 format_free (void *descr)
300 struct spec *spec = descr;
301 formatstring_kde.free (spec->base);
306 format_get_number_of_directives (void *descr)
308 struct spec *spec = descr;
309 return formatstring_kde.get_number_of_directives (spec->base);
313 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
314 formatstring_error_logger_t error_logger,
315 const char *pretty_msgid, const char *pretty_msgstr)
317 struct spec *msgid_spec = msgid_descr;
318 struct spec *msgstr_spec = msgstr_descr;
320 return formatstring_kde.check (msgid_spec->base, msgstr_spec->base, equality,
322 pretty_msgid, pretty_msgstr);
325 struct formatstring_parser formatstring_kde_kuit =
329 format_get_number_of_directives,
337 /* Test program: Print the argument list specification returned by
338 format_parse for strings read from standard input. */
343 format_print (void *descr)
345 struct spec *spec = (struct spec *) descr;
357 for (i = 0; i < spec->numbered_arg_count; i++)
359 unsigned int number = spec->numbered[i].number;
365 for (; last < number; last++)
378 size_t line_size = 0;
380 char *invalid_reason;
383 line_len = getline (&line, &line_size, stdin);
386 if (line_len > 0 && line[line_len - 1] == '\n')
387 line[--line_len] = '\0';
389 invalid_reason = NULL;
390 descr = format_parse (line, false, NULL, &invalid_reason);
392 format_print (descr);
395 printf ("%s\n", invalid_reason);
397 free (invalid_reason);
405 * For Emacs M-x compile
407 * 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-kuit.c ../gnulib-lib/libgettextlib.la"