/* * Copyright (C) 2004, 2005 Jean-Yves Lefort * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include #include #include #include "translate.h" #include "translate-generic-parser.h" #include "translate-generic-service.h" typedef struct { char *name; char *nick; unsigned int max_chunk_len; TranslateGenericGroup *group; GSList *groups; } ServiceDefinition; typedef struct { GMarkupParseContext *context; const char *filename; char *path; ServiceDefinition *definition; GSList *definitions; } ParseInfo; typedef enum { REQUIRED, OPTIONAL } AttributeType; static void translate_generic_parser_start_element_cb (GMarkupParseContext *context, const char *element_name, const char **attribute_names, const char **attribute_values, gpointer user_data, GError **err); static void translate_generic_parser_end_element_cb (GMarkupParseContext *context, const char *element_name, gpointer user_data, GError **err); static void translate_generic_parser_scan_attributes (ParseInfo *info, const char **attribute_names, const char **attribute_values, GError **err, ...); static void translate_generic_parser_warning (ParseInfo *info, const char *format, ...) G_GNUC_PRINTF(2, 3); static void translate_generic_parser_set_error (GError **err, ParseInfo *info, const char *format, ...) G_GNUC_PRINTF(3, 4); static void translate_generic_definition_free (ServiceDefinition *definition); static void translate_generic_parser_handle_http_header (ParseInfo *info, const char **attribute_names, const char **attribute_values, GSList **list, GError **err); static void translate_generic_http_header_free (TranslateGenericHttpHeader *header); static void translate_generic_parser_handle_location (ParseInfo *info, const char **attribute_names, const char **attribute_values, TranslateGenericLocation **location, GError **err); static void translate_generic_location_free (TranslateGenericLocation *location); void translate_generic_parse (const char *filename) { GError *err = NULL; GIOChannel *channel; char *contents; gsize length; ParseInfo info; GMarkupParser parser = { translate_generic_parser_start_element_cb, translate_generic_parser_end_element_cb, NULL, NULL, NULL }; g_return_if_fail(filename != NULL); if (! g_file_test(filename, G_FILE_TEST_IS_REGULAR)) return; channel = g_io_channel_new_file(filename, "r", &err); if (! channel) { g_warning(_("unable to open %s: %s"), filename, err->message); g_error_free(err); return; } if (g_io_channel_read_to_end(channel, &contents, &length, &err) != G_IO_STATUS_NORMAL) { g_warning(_("unable to read %s: %s"), filename, err->message); g_error_free(err); goto end; } info.context = g_markup_parse_context_new(&parser, 0, &info, NULL); info.filename = filename; info.path = NULL; info.definition = NULL; info.definitions = NULL; if (g_markup_parse_context_parse(info.context, contents, length, &err) && g_markup_parse_context_end_parse(info.context, &err)) { GSList *l; for (l = info.definitions; l != NULL; l = l->next) { ServiceDefinition *definition = l->data; TranslateService *service; service = translate_generic_service_new(definition->name, definition->nick, definition->max_chunk_len, definition->groups); if (! translate_add_service(service)) g_warning(_("%s: service \"%s\" already exists, ignored"), filename, translate_service_get_name(service)); g_object_unref(service); } } else { g_warning(_("unable to parse %s: %s"), filename, err->message); g_error_free(err); } g_markup_parse_context_free(info.context); g_free(info.path); if (info.definition) translate_generic_definition_free(info.definition); g_slist_foreach(info.definitions, (GFunc) translate_generic_definition_free, NULL); g_slist_free(info.definitions); end: g_io_channel_shutdown(channel, TRUE, NULL); g_io_channel_unref(channel); } static void translate_generic_parser_start_element_cb (GMarkupParseContext *context, const char *element_name, const char **attribute_names, const char **attribute_values, gpointer user_data, GError **err) { ParseInfo *info = user_data; char *new_path; if (info->path) { new_path = g_strconcat(info->path, "/", element_name, NULL); g_free(info->path); } else new_path = g_strconcat("/", element_name, NULL); info->path = new_path; if (! strcmp(info->path, "/services")) translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, NULL); else if (! strcmp(info->path, "/services/custom-language")) { const char *tag; const char *name; translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, "tag", REQUIRED, &tag, "name", REQUIRED, &name, NULL); if (! *err) { if (! translate_add_language(tag, _(name))) translate_generic_parser_warning(info, _("language \"%s\" already exists, ignored"), tag); } } else if (! strcmp(info->path, "/services/service")) { const char *name; const char *nick; const char *max_chunk_len; translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, "name", REQUIRED, &name, "nick", OPTIONAL, &nick, "max-chunk-len", OPTIONAL, &max_chunk_len, NULL); if (! *err) { unsigned int max_chunk_len_i = 1000; /* keep in sync with services.dtd */ if (max_chunk_len) { if (! *max_chunk_len) translate_generic_parser_set_error(err, info, _("max-chunk-len is empty")); else if (strspn(max_chunk_len, "0123456789") != strlen(max_chunk_len)) translate_generic_parser_set_error(err, info, _("max-chunk-len \"%s\" is not an unsigned integer"), max_chunk_len); else max_chunk_len_i = atoi(max_chunk_len); } if (! *err) { info->definition = g_new0(ServiceDefinition, 1); info->definition->name = g_strdup(name); info->definition->nick = g_strdup(nick ? nick : name); info->definition->max_chunk_len = max_chunk_len_i; } } } else if (! strcmp(info->path, "/services/service/group")) { translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, NULL); if (! *err) { info->definition->group = g_new0(TranslateGenericGroup, 1); info->definition->group->ref_count = 1; info->definition->group->service_tags = g_hash_table_new_full(translate_ascii_strcase_hash, translate_ascii_strcase_equal, g_free, g_free); } } else if (! strcmp(info->path, "/services/service/group/language")) { const char *tag; const char *service_tag; const char *to; translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, "tag", REQUIRED, &tag, "service-tag", OPTIONAL, &service_tag, "to", OPTIONAL, &to, NULL); if (! *err) { TranslateGenericLanguage *language; language = g_new(TranslateGenericLanguage, 1); language->tag = g_strdup(tag); language->target_tags = to ? g_strsplit(to, ",", 0) : NULL; info->definition->group->languages = g_slist_append(info->definition->group->languages, language); if (service_tag) g_hash_table_insert(info->definition->group->service_tags, g_strdup(tag), g_strdup(service_tag)); } } else if (! strcmp(info->path, "/services/service/group/http-header")) translate_generic_parser_handle_http_header(info, attribute_names, attribute_values, &info->definition->group->http_headers, err); else if (! strcmp(info->path, "/services/service/group/text-translation")) { if (info->definition->group->text_location) translate_generic_parser_warning(info, _("element \"text-translation\" already specified")); else translate_generic_parser_handle_location(info, attribute_names, attribute_values, &info->definition->group->text_location, err); } else if (! strcmp(info->path, "/services/service/group/text-translation/pre-marker")) { const char *text; translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, "text", REQUIRED, &text, NULL); if (! *err) info->definition->group->text_pre_markers = g_slist_append(info->definition->group->text_pre_markers, g_strdup(text)); } else if (! strcmp(info->path, "/services/service/group/text-translation/error-marker")) { const char *text; translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, "text", REQUIRED, &text, NULL); if (! *err) info->definition->group->text_error_markers = g_slist_append(info->definition->group->text_error_markers, g_strdup(text)); } else if (! strcmp(info->path, "/services/service/group/text-translation/post-marker")) { if (info->definition->group->text_post_marker) translate_generic_parser_warning(info, _("element \"post-marker\" already specified")); else { const char *text; translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, "text", REQUIRED, &text, NULL); if (! *err) info->definition->group->text_post_marker = g_strdup(text); } } else if (! strcmp(info->path, "/services/service/group/text-translation/http-header")) translate_generic_parser_handle_http_header(info, attribute_names, attribute_values, &info->definition->group->text_location->http_headers, err); else if (! strcmp(info->path, "/services/service/group/web-page-translation")) { if (info->definition->group->web_page_location) translate_generic_parser_warning(info, _("element \"web-page-translation\" already specified")); else translate_generic_parser_handle_location(info, attribute_names, attribute_values, &info->definition->group->web_page_location, err); } else if (! strcmp(info->path, "/services/service/group/web-page-translation/http-header")) translate_generic_parser_handle_http_header(info, attribute_names, attribute_values, &info->definition->group->web_page_location->http_headers, err); else translate_generic_parser_warning(info, _("unknown element \"%s\", ignored"), element_name); } static void translate_generic_parser_end_element_cb (GMarkupParseContext *context, const char *element_name, gpointer user_data, GError **err) { ParseInfo *info = user_data; char *slash; g_return_if_fail(info->path != NULL); if (! strcmp(info->path, "/services/service")) { info->definitions = g_slist_append(info->definitions, info->definition); info->definition = NULL; } else if (! strcmp(info->path, "/services/service/group")) { info->definition->groups = g_slist_append(info->definition->groups, info->definition->group); info->definition->group = NULL; } slash = strrchr(info->path, '/'); if (slash) *slash = 0; else { g_free(info->path); info->path = NULL; } } static void translate_generic_parser_scan_attributes (ParseInfo *info, const char **attribute_names, const char **attribute_values, GError **err, ...) { va_list args; const char *name; GSList *seen_attributes = NULL; GSList *l; int i; gboolean found; g_return_if_fail(info != NULL); g_return_if_fail(attribute_names != NULL); g_return_if_fail(attribute_values != NULL); va_start(args, err); while ((name = va_arg(args, const char *))) { AttributeType type; const char **ptr; type = va_arg(args, AttributeType); ptr = va_arg(args, const char **); g_return_if_fail(ptr != NULL); *ptr = NULL; found = FALSE; for (i = 0; attribute_names[i] && attribute_values[i]; i++) if (! strcmp(attribute_names[i], name)) { if (found) translate_generic_parser_warning(info, _("attribute \"%s\" already specified"), name); else { seen_attributes = g_slist_append(seen_attributes, (gpointer) name); *ptr = attribute_values[i]; found = TRUE; } } if (! found && type == REQUIRED) { translate_generic_parser_set_error(err, info, _("required attribute \"%s\" missing"), name); goto end; } } for (i = 0; attribute_names[i] && attribute_values[i]; i++) { found = FALSE; for (l = seen_attributes; l != NULL && ! found; l = l->next) if (! strcmp(l->data, attribute_names[i])) found = TRUE; if (! found) translate_generic_parser_warning(info, _("unknown attribute \"%s\", ignored"), attribute_names[i]); } end: va_end(args); g_slist_free(seen_attributes); } static void translate_generic_parser_warning (ParseInfo *info, const char *format, ...) { va_list args; char *message; int line_number; g_return_if_fail(info != NULL); g_return_if_fail(format != NULL); va_start(args, format); message = g_strdup_vprintf(format, args); va_end(args); g_markup_parse_context_get_position(info->context, &line_number, NULL); g_warning(_("%s: around line %i: %s"), info->filename, line_number, message); g_free(message); } static void translate_generic_parser_set_error (GError **err, ParseInfo *info, const char *format, ...) { va_list args; char *message; int line_number; g_return_if_fail(info != NULL); g_return_if_fail(format != NULL); va_start(args, format); message = g_strdup_vprintf(format, args); va_end(args); g_markup_parse_context_get_position(info->context, &line_number, NULL); g_set_error(err, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "around line %i: %s", line_number, message); g_free(message); } static void translate_generic_definition_free (ServiceDefinition *definition) { g_return_if_fail(definition != NULL); g_free(definition->name); g_free(definition->nick); if (definition->group) translate_generic_group_unref(definition->group); g_slist_foreach(definition->groups, (GFunc) translate_generic_group_unref, NULL); g_slist_free(definition->groups); g_free(definition); } TranslateGenericGroup * translate_generic_group_ref (TranslateGenericGroup *group) { g_return_val_if_fail(group != NULL, NULL); g_atomic_int_inc(&group->ref_count); return group; } void translate_generic_group_unref (TranslateGenericGroup *group) { g_return_if_fail(group != NULL); if (g_atomic_int_dec_and_test(&group->ref_count)) { GSList *l; for (l = group->languages; l != NULL; l = l->next) { TranslateGenericLanguage *language = l->data; g_free(language->tag); g_strfreev(language->target_tags); g_free(language); } g_slist_free(group->languages); g_hash_table_destroy(group->service_tags); g_slist_foreach(group->http_headers, (GFunc) translate_generic_http_header_free, NULL); g_slist_free(group->http_headers); if (group->text_location) translate_generic_location_free(group->text_location); g_slist_foreach(group->text_pre_markers, (GFunc) g_free, NULL); g_slist_free(group->text_pre_markers); g_free(group->text_post_marker); g_slist_foreach(group->text_error_markers, (GFunc) g_free, NULL); g_slist_free(group->text_error_markers); if (group->web_page_location) translate_generic_location_free(group->web_page_location); g_free(group); } } const char * translate_generic_group_get_service_tag (TranslateGenericGroup *group, const char *tag) { const char *service_tag; g_return_val_if_fail(group != NULL, NULL); g_return_val_if_fail(tag != NULL, NULL); service_tag = g_hash_table_lookup(group->service_tags, tag); return service_tag ? service_tag : tag; } void translate_generic_group_foreach_pair (TranslateGenericGroup *group, TranslateGenericGroupForeachPairFunc func, gpointer user_data) { GSList *l; g_return_if_fail(group != NULL); g_return_if_fail(func != NULL); for (l = group->languages; l != NULL; l = l->next) { TranslateGenericLanguage *language = l->data; if (language->target_tags) { int i; for (i = 0; language->target_tags[i]; i++) if (! strcmp(language->target_tags[i], "*")) { GSList *m; for (m = group->languages; m != NULL; m = m->next) { TranslateGenericLanguage *to = m->data; if (g_ascii_strcasecmp(language->tag, to->tag)) { if (! func(language->tag, to->tag, user_data)) return; } } } else { if (! func(language->tag, language->target_tags[i], user_data)) return; } } } } static void translate_generic_parser_handle_http_header (ParseInfo *info, const char **attribute_names, const char **attribute_values, GSList **list, GError **err) { const char *name; const char *value; g_return_if_fail(info != NULL); g_return_if_fail(attribute_names != NULL); g_return_if_fail(attribute_values != NULL); g_return_if_fail(list != NULL); translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, "name", REQUIRED, &name, "value", REQUIRED, &value, NULL); if (! *err) { TranslateGenericHttpHeader *header; header = g_new(TranslateGenericHttpHeader, 1); header->name = g_strdup(name); header->value = g_strdup(value); *list = g_slist_append(*list, header); } } static void translate_generic_http_header_free (TranslateGenericHttpHeader *header) { g_return_if_fail(header != NULL); g_free(header->name); g_free(header->value); g_free(header); } static void translate_generic_parser_handle_location (ParseInfo *info, const char **attribute_names, const char **attribute_values, TranslateGenericLocation **location, GError **err) { const char *url; const char *post; const char *content_type; g_return_if_fail(info != NULL); g_return_if_fail(attribute_names != NULL); g_return_if_fail(attribute_values != NULL); g_return_if_fail(location != NULL); translate_generic_parser_scan_attributes(info, attribute_names, attribute_values, err, "url", REQUIRED, &url, "post", OPTIONAL, &post, "content-type", OPTIONAL, &content_type, NULL); if (! *err) { *location = g_new0(TranslateGenericLocation, 1); (*location)->url = g_strdup(url); (*location)->post = g_strdup(post); (*location)->content_type = g_strdup(content_type ? content_type : "application/x-www-form-urlencoded"); } } static void translate_generic_location_free (TranslateGenericLocation *location) { g_return_if_fail(location != NULL); g_free(location->url); g_free(location->post); g_free(location->content_type); g_slist_foreach(location->http_headers, (GFunc) translate_generic_http_header_free, NULL); g_slist_free(location->http_headers); g_free(location); }