1 /* Internationalization Tag Set (ITS) handling
2 Copyright (C) 2015 Free Software Foundation, Inc.
4 This file was written by Daiki Ueno <ueno@gnu.org>, 2015.
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>. */
32 #include <libxml/tree.h>
33 #include <libxml/parser.h>
34 #include <libxml/xmlwriter.h>
35 #include <libxml/xpath.h>
36 #include <libxml/xpathInternals.h>
40 #include "xvasprintf.h"
42 #define _(str) gettext (str)
44 /* The Internationalization Tag Set (ITS) 2.0 standard is available at:
45 http://www.w3.org/TR/its20/
47 This implementation supports only a few data categories, useful for
48 gettext-based projects. Other data categories can be added by
49 extending the its_rule_class_ty class and registering it in
52 The message extraction is performed in three steps. In the first
53 step, its_rule_list_apply() assigns values to nodes in an XML
54 document. In the second step, its_rule_list_extract_nodes() marks
55 translatable nodes. In the final step,
56 its_rule_list_extract_text() extracts text contents from the marked
59 The values assigned to a node are represented as an array of
60 key-value pairs, where both keys and values are string. The array
61 is stored in node->_private. To retrieve the values for a node,
62 use its_rule_list_eval(). */
64 #define ITS_NS "http://www.w3.org/2005/11/its"
65 #define XML_NS "http://www.w3.org/XML/1998/namespace"
66 #define GT_NS "https://www.gnu.org/s/gettext/ns/its/extensions/1.0"
74 struct its_value_list_ty
76 struct its_value_ty *items;
82 its_value_list_append (struct its_value_list_ty *values,
86 struct its_value_ty _value;
88 _value.name = xstrdup (name);
89 _value.value = xstrdup (value);
91 if (values->nitems == values->nitems_max)
93 values->nitems_max = 2 * values->nitems_max + 1;
95 xrealloc (values->items,
96 sizeof (struct its_value_ty) * values->nitems_max);
98 memcpy (&values->items[values->nitems++], &_value,
99 sizeof (struct its_value_ty));
103 its_value_list_get_value (struct its_value_list_ty *values,
108 for (i = 0; i < values->nitems; i++)
110 struct its_value_ty *value = &values->items[i];
111 if (strcmp (value->name, name) == 0)
118 its_value_list_set_value (struct its_value_list_ty *values,
124 for (i = 0; i < values->nitems; i++)
126 struct its_value_ty *_value = &values->items[i];
127 if (strcmp (_value->name, name) == 0)
129 free (_value->value);
130 _value->value = xstrdup (value);
135 if (i == values->nitems)
136 its_value_list_append (values, name, value);
140 its_value_list_merge (struct its_value_list_ty *values,
141 struct its_value_list_ty *other)
145 for (i = 0; i < other->nitems; i++)
147 struct its_value_ty *other_value = &other->items[i];
150 for (j = 0; j < values->nitems; j++)
152 struct its_value_ty *value = &values->items[j];
154 if (strcmp (value->name, other_value->name) == 0
155 && strcmp (value->value, other_value->value) != 0)
158 value->value = xstrdup (other_value->value);
163 if (j == values->nitems)
164 its_value_list_append (values, other_value->name, other_value->value);
169 its_value_list_destroy (struct its_value_list_ty *values)
173 for (i = 0; i < values->nitems; i++)
175 free (values->items[i].name);
176 free (values->items[i].value);
178 free (values->items);
183 struct its_value_list_ty *items;
188 static struct its_value_list_ty *
189 its_pool_alloc_value_list (struct its_pool_ty *pool)
191 struct its_value_list_ty *values;
193 if (pool->nitems == pool->nitems_max)
195 pool->nitems_max = 2 * pool->nitems_max + 1;
197 xrealloc (pool->items,
198 sizeof (struct its_value_list_ty) * pool->nitems_max);
201 values = &pool->items[pool->nitems++];
202 memset (values, 0, sizeof (struct its_value_list_ty));
207 its_pool_get_value_for_node (struct its_pool_ty *pool, xmlNode *node,
210 intptr_t index = (intptr_t) node->_private;
213 struct its_value_list_ty *values;
215 assert (index <= pool->nitems);
216 values = &pool->items[index - 1];
218 return its_value_list_get_value (values, name);
224 its_pool_destroy (struct its_pool_ty *pool)
228 for (i = 0; i < pool->nitems; i++)
229 its_value_list_destroy (&pool->items[i]);
233 struct its_rule_list_ty
235 struct its_rule_ty **items;
239 struct its_pool_ty pool;
242 struct its_node_list_ty
250 its_node_list_append (struct its_node_list_ty *nodes,
253 if (nodes->nitems == nodes->nitems_max)
255 nodes->nitems_max = 2 * nodes->nitems_max + 1;
257 xrealloc (nodes->items, sizeof (xmlNode *) * nodes->nitems_max);
259 nodes->items[nodes->nitems++] = node;
262 /* Base class representing an ITS rule in global definition. */
263 struct its_rule_class_ty
265 /* How many bytes to malloc for an instance of this class. */
268 /* What to do immediately after the instance is malloc()ed. */
269 void (*constructor) (struct its_rule_ty *pop, xmlNode *node);
271 /* What to do immediately before the instance is free()ed. */
272 void (*destructor) (struct its_rule_ty *pop);
274 /* How to apply the rule to all elements in DOC. */
275 void (* apply) (struct its_rule_ty *pop, struct its_pool_ty *pool,
278 /* How to evaluate the value of NODE according to the rule. */
279 struct its_value_list_ty *(* eval) (struct its_rule_ty *pop,
280 struct its_pool_ty *pool, xmlNode *node);
283 #define ITS_RULE_TY \
284 struct its_rule_class_ty *methods; \
286 struct its_value_list_ty values; \
294 static hash_table classes;
297 its_rule_destructor (struct its_rule_ty *pop)
299 free (pop->selector);
300 its_value_list_destroy (&pop->values);
304 for (i = 0; pop->namespaces[i] != NULL; i++)
305 xmlFreeNs (pop->namespaces[i]);
306 free (pop->namespaces);
311 its_rule_apply (struct its_rule_ty *rule, struct its_pool_ty *pool, xmlDoc *doc)
313 xmlXPathContext *context;
314 xmlXPathObject *object;
319 error (0, 0, _("selector is not specified"));
323 context = xmlXPathNewContext (doc);
326 error (0, 0, _("cannot create XPath context"));
330 if (rule->namespaces)
333 for (i = 0; rule->namespaces[i] != NULL; i++)
335 xmlNs *ns = rule->namespaces[i];
336 xmlXPathRegisterNs (context, ns->prefix, ns->href);
340 object = xmlXPathEval (BAD_CAST rule->selector, context);
343 xmlXPathFreeContext (context);
344 error (0, 0, _("cannot evaluate XPath expression: %s"), rule->selector);
348 if (object->nodesetval)
350 xmlNodeSet *nodes = object->nodesetval;
351 for (i = 0; i < nodes->nodeNr; i++)
353 xmlNode *node = nodes->nodeTab[i];
354 struct its_value_list_ty *values;
356 /* We can't store VALUES in NODE, since the address can
357 change when realloc()ed. */
358 intptr_t index = (intptr_t) node->_private;
360 assert (index <= pool->nitems);
362 values = &pool->items[index - 1];
365 values = its_pool_alloc_value_list (pool);
366 node->_private = (void *) pool->nitems;
369 its_value_list_merge (values, &rule->values);
373 xmlXPathFreeObject (object);
374 xmlXPathFreeContext (context);
378 _its_get_attribute (xmlNode *node, const char *attr, const char *namespace)
383 value = xmlGetNsProp (node, BAD_CAST attr, BAD_CAST namespace);
385 result = xstrdup ((const char *) value);
392 normalize_whitespace (const char *text, enum its_whitespace_type_ty whitespace)
396 case ITS_WHITESPACE_PRESERVE:
397 return xstrdup (text);
399 case ITS_WHITESPACE_TRIM:
403 /* Normalize whitespaces within the text, but not at the beginning
404 nor the end of the text. */
406 char *result, *p, *end;
408 result = xstrdup (text);
409 end = result + strlen (result);
410 for (p = result; *p != '\0';)
412 size_t len = strspn (p, " \t\n");
416 memmove (p + 1, p + len, end - (p + len));
421 p += strcspn (p, " \t\n");
429 _its_encode_special_chars (const char *content, bool is_attribute)
435 for (str = content; *str != '\0'; str++)
440 amount += sizeof ("&");
443 amount += sizeof ("<");
446 amount += sizeof (">");
450 amount += sizeof (""");
460 result = XNMALLOC (amount + 1, char);
463 for (str = content; *str != '\0'; str++)
468 p = stpcpy (p, "&");
471 p = stpcpy (p, "<");
474 p = stpcpy (p, ">");
478 p = stpcpy (p, """);
492 _its_collect_text_content (xmlNode *node,
493 enum its_whitespace_type_ty whitespace,
501 for (n = node->children; n; n = n->next)
503 char *content = NULL;
508 case XML_CDATA_SECTION_NODE:
510 xmlChar *xcontent = xmlNodeGetContent (n);
512 const char *ccontent;
514 /* We can't expect xmlTextWriterWriteString() encode
515 special characters as we write text outside of the
518 econtent = xstrdup ((const char *) xcontent);
521 _its_encode_special_chars ((const char *) xcontent,
522 node->type == XML_ATTRIBUTE_NODE);
525 /* Skip whitespaces at the beginning of the text, if this
526 is the first node. */
528 if (whitespace == ITS_WHITESPACE_NORMALIZE && !n->prev)
529 ccontent = ccontent + strspn (ccontent, " \t\n");
531 normalize_whitespace (ccontent, whitespace);
534 /* Skip whitespaces at the end of the text, if this
536 if (whitespace == ITS_WHITESPACE_NORMALIZE && !n->next)
538 char *p = content + strlen (content);
539 for (; p > content; p--)
542 if (!(c == ' ' || c == '\t' || c == '\n'))
552 case XML_ELEMENT_NODE:
554 xmlOutputBuffer *buffer = xmlAllocOutputBuffer (NULL);
555 xmlTextWriter *writer = xmlNewTextWriter (buffer);
556 char *p = _its_collect_text_content (n, whitespace,
558 const char *ccontent;
560 xmlTextWriterStartElement (writer, BAD_CAST n->name);
563 xmlAttr *attr = n->properties;
564 for (; attr; attr = attr->next)
566 xmlChar *prop = xmlGetProp (n, attr->name);
567 xmlTextWriterWriteAttribute (writer,
574 xmlTextWriterWriteRaw (writer, BAD_CAST p);
575 xmlTextWriterEndElement (writer);
576 ccontent = (const char *) xmlOutputBufferGetContent (buffer);
577 content = normalize_whitespace (ccontent, whitespace);
578 xmlFreeTextWriter (writer);
583 case XML_ENTITY_REF_NODE:
584 content = xasprintf ("&%s;", (const char *) n->name);
593 size_t length = strlen (content);
595 if (bufpos + length + 1 >= bufmax)
597 bufmax = 2 * bufmax + length + 1;
598 buffer = xrealloc (buffer, bufmax);
600 strcpy (&buffer[bufpos], content);
607 buffer = xstrdup ("");
612 _its_error_missing_attribute (xmlNode *node, const char *attribute)
614 error (0, 0, _("\"%s\" node does not contain \"%s\""),
615 node->name, attribute);
618 /* Implementation of Translate data category. */
620 its_translate_rule_constructor (struct its_rule_ty *pop, xmlNode *node)
624 if (!xmlHasProp (node, BAD_CAST "selector"))
626 _its_error_missing_attribute (node, "selector");
630 if (!xmlHasProp (node, BAD_CAST "translate"))
632 _its_error_missing_attribute (node, "translate");
636 prop = _its_get_attribute (node, "selector", NULL);
638 pop->selector = prop;
640 prop = _its_get_attribute (node, "translate", NULL);
641 its_value_list_append (&pop->values, "translate", prop);
645 struct its_value_list_ty *
646 its_translate_rule_eval (struct its_rule_ty *pop, struct its_pool_ty *pool,
649 struct its_value_list_ty *result;
651 result = XCALLOC (1, struct its_value_list_ty);
655 case XML_ATTRIBUTE_NODE:
656 /* Attribute nodes don't inherit from the parent elements. */
659 its_pool_get_value_for_node (pool, node, "translate");
662 its_value_list_set_value (result, "translate", value);
666 /* The default value is translate="no". */
667 its_value_list_append (result, "translate", "no");
671 case XML_ELEMENT_NODE:
672 /* Inherit from the parent elements. */
676 /* A local attribute overrides the global rule. */
677 if (xmlHasNsProp (node, BAD_CAST "translate", BAD_CAST ITS_NS))
681 prop = _its_get_attribute (node, "translate", ITS_NS);
682 its_value_list_append (result, "translate", prop);
687 /* Check value for the current node. */
688 value = its_pool_get_value_for_node (pool, node, "translate");
691 its_value_list_set_value (result, "translate", value);
695 /* Recursively check value for the parent node. */
696 if (node->parent == NULL
697 || node->parent->type != XML_ELEMENT_NODE)
698 /* The default value is translate="yes". */
699 its_value_list_append (result, "translate", "yes");
702 struct its_value_list_ty *values;
704 values = its_translate_rule_eval (pop, pool, node->parent);
705 its_value_list_merge (result, values);
706 its_value_list_destroy (values);
719 static struct its_rule_class_ty its_translate_rule_class =
721 sizeof (struct its_rule_ty),
722 its_translate_rule_constructor,
725 its_translate_rule_eval,
728 /* Implementation of Localization Note data category. */
730 its_localization_note_rule_constructor (struct its_rule_ty *pop, xmlNode *node)
735 if (!xmlHasProp (node, BAD_CAST "selector"))
737 _its_error_missing_attribute (node, "selector");
741 if (!xmlHasProp (node, BAD_CAST "locNoteType"))
743 _its_error_missing_attribute (node, "locNoteType");
747 prop = _its_get_attribute (node, "selector", NULL);
749 pop->selector = prop;
751 for (n = node->children; n; n = n->next)
753 if (n->type == XML_ELEMENT_NODE
754 && xmlStrEqual (n->name, BAD_CAST "locNote")
755 && xmlStrEqual (n->ns->href, BAD_CAST ITS_NS))
759 prop = _its_get_attribute (node, "locNoteType", NULL);
761 its_value_list_append (&pop->values, "locNoteType", prop);
766 /* FIXME: Respect space attribute. */
767 char *content = _its_collect_text_content (n, ITS_WHITESPACE_NORMALIZE,
769 its_value_list_append (&pop->values, "locNote", content);
772 else if (xmlHasProp (node, BAD_CAST "locNotePointer"))
774 prop = _its_get_attribute (node, "locNotePointer", NULL);
775 its_value_list_append (&pop->values, "locNotePointer", prop);
778 /* FIXME: locNoteRef and locNoteRefPointer */
781 struct its_value_list_ty *
782 its_localization_note_rule_eval (struct its_rule_ty *pop,
783 struct its_pool_ty *pool,
786 struct its_value_list_ty *result;
788 result = XCALLOC (1, struct its_value_list_ty);
792 case XML_ATTRIBUTE_NODE:
793 /* Attribute nodes don't inherit from the parent elements. */
797 value = its_pool_get_value_for_node (pool, node, "locNoteType");
799 its_value_list_set_value (result, "locNoteType", value);
801 value = its_pool_get_value_for_node (pool, node, "locNote");
804 its_value_list_set_value (result, "locNote", value);
808 value = its_pool_get_value_for_node (pool, node, "locNotePointer");
811 its_value_list_set_value (result, "locNotePointer", value);
817 case XML_ELEMENT_NODE:
818 /* Inherit from the parent elements. */
822 /* Local attributes overrides the global rule. */
823 if (xmlHasNsProp (node, BAD_CAST "locNote", BAD_CAST ITS_NS)
824 || xmlHasNsProp (node, BAD_CAST "locNoteRef", BAD_CAST ITS_NS)
825 || xmlHasNsProp (node, BAD_CAST "locNoteType", BAD_CAST ITS_NS))
829 if (xmlHasNsProp (node, BAD_CAST "locNote", BAD_CAST ITS_NS))
831 prop = _its_get_attribute (node, "locNote", ITS_NS);
832 its_value_list_append (result, "locNote", prop);
836 /* FIXME: locNoteRef */
838 if (xmlHasNsProp (node, BAD_CAST "locNoteType", BAD_CAST ITS_NS))
840 prop = _its_get_attribute (node, "locNoteType", ITS_NS);
841 its_value_list_append (result, "locNoteType", prop);
848 /* Check value for the current node. */
849 value = its_pool_get_value_for_node (pool, node, "locNoteType");
851 its_value_list_set_value (result, "locNoteType", value);
853 value = its_pool_get_value_for_node (pool, node, "locNote");
856 its_value_list_set_value (result, "locNote", value);
860 value = its_pool_get_value_for_node (pool, node, "locNotePointer");
863 its_value_list_set_value (result, "locNotePointer", value);
867 /* Recursively check value for the parent node. */
868 if (node->parent == NULL
869 || node->parent->type != XML_ELEMENT_NODE)
873 struct its_value_list_ty *values;
875 values = its_localization_note_rule_eval (pop, pool, node->parent);
876 its_value_list_merge (result, values);
877 its_value_list_destroy (values);
887 /* The default value is None. */
891 static struct its_rule_class_ty its_localization_note_rule_class =
893 sizeof (struct its_rule_ty),
894 its_localization_note_rule_constructor,
897 its_localization_note_rule_eval,
900 /* Implementation of Element Within Text data category. */
902 its_element_within_text_rule_constructor (struct its_rule_ty *pop,
907 if (!xmlHasProp (node, BAD_CAST "selector"))
909 _its_error_missing_attribute (node, "selector");
913 if (!xmlHasProp (node, BAD_CAST "withinText"))
915 _its_error_missing_attribute (node, "withinText");
919 prop = _its_get_attribute (node, "selector", NULL);
921 pop->selector = prop;
923 prop = _its_get_attribute (node, "withinText", NULL);
924 its_value_list_append (&pop->values, "withinText", prop);
928 struct its_value_list_ty *
929 its_element_within_text_rule_eval (struct its_rule_ty *pop,
930 struct its_pool_ty *pool,
933 struct its_value_list_ty *result;
936 result = XCALLOC (1, struct its_value_list_ty);
938 if (node->type != XML_ELEMENT_NODE)
941 /* A local attribute overrides the global rule. */
942 if (xmlHasNsProp (node, BAD_CAST "withinText", BAD_CAST ITS_NS))
946 prop = _its_get_attribute (node, "withinText", ITS_NS);
947 its_value_list_append (result, "withinText", prop);
952 /* Doesn't inherit from the parent elements, and the default value
954 value = its_pool_get_value_for_node (pool, node, "withinText");
956 its_value_list_set_value (result, "withinText", value);
961 static struct its_rule_class_ty its_element_within_text_rule_class =
963 sizeof (struct its_rule_ty),
964 its_element_within_text_rule_constructor,
967 its_element_within_text_rule_eval,
970 /* Implementation of Preserve Space data category. */
972 its_preserve_space_rule_constructor (struct its_rule_ty *pop,
977 if (!xmlHasProp (node, BAD_CAST "selector"))
979 _its_error_missing_attribute (node, "selector");
983 if (!xmlHasProp (node, BAD_CAST "space"))
985 _its_error_missing_attribute (node, "space");
989 prop = _its_get_attribute (node, "selector", NULL);
991 pop->selector = prop;
993 prop = _its_get_attribute (node, "space", NULL);
995 && !(strcmp (prop, "preserve") ==0
996 || strcmp (prop, "default") == 0
997 /* gettext extension: remove leading/trailing whitespaces only. */
998 || (node->ns && xmlStrEqual (node->ns->href, BAD_CAST GT_NS)
999 && strcmp (prop, "trim") == 0)))
1001 error (0, 0, _("invalid attribute value \"%s\" for \"%s\""),
1007 its_value_list_append (&pop->values, "space", prop);
1011 struct its_value_list_ty *
1012 its_preserve_space_rule_eval (struct its_rule_ty *pop,
1013 struct its_pool_ty *pool,
1016 struct its_value_list_ty *result;
1017 struct its_value_list_ty *values;
1020 result = XCALLOC (1, struct its_value_list_ty);
1022 if (node->type != XML_ELEMENT_NODE)
1025 /* A local attribute overrides the global rule. */
1026 if (xmlHasNsProp (node, BAD_CAST "space", BAD_CAST XML_NS))
1030 prop = _its_get_attribute (node, "space", XML_NS);
1031 its_value_list_append (result, "space", prop);
1036 /* Check value for the current node. */
1037 value = its_pool_get_value_for_node (pool, node, "space");
1040 its_value_list_set_value (result, "space", value);
1044 if (node->parent == NULL
1045 || node->parent->type != XML_ELEMENT_NODE)
1047 /* The default value is space="default". */
1048 its_value_list_append (result, "space", "default");
1052 /* Recursively check value for the parent node. */
1053 values = its_preserve_space_rule_eval (pop, pool, node->parent);
1054 its_value_list_merge (result, values);
1055 its_value_list_destroy (values);
1061 static struct its_rule_class_ty its_preserve_space_rule_class =
1063 sizeof (struct its_rule_ty),
1064 its_preserve_space_rule_constructor,
1065 its_rule_destructor,
1067 its_preserve_space_rule_eval,
1070 /* Implementation of Context data category. */
1072 its_extension_context_rule_constructor (struct its_rule_ty *pop, xmlNode *node)
1076 if (!xmlHasProp (node, BAD_CAST "selector"))
1078 _its_error_missing_attribute (node, "selector");
1082 if (!xmlHasProp (node, BAD_CAST "contextPointer"))
1084 _its_error_missing_attribute (node, "contextPointer");
1088 prop = _its_get_attribute (node, "selector", NULL);
1090 pop->selector = prop;
1092 prop = _its_get_attribute (node, "contextPointer", NULL);
1093 its_value_list_append (&pop->values, "contextPointer", prop);
1096 if (xmlHasProp (node, BAD_CAST "textPointer"))
1098 prop = _its_get_attribute (node, "textPointer", NULL);
1099 its_value_list_append (&pop->values, "textPointer", prop);
1104 struct its_value_list_ty *
1105 its_extension_context_rule_eval (struct its_rule_ty *pop,
1106 struct its_pool_ty *pool,
1109 struct its_value_list_ty *result;
1112 result = XCALLOC (1, struct its_value_list_ty);
1114 /* Doesn't inherit from the parent elements, and the default value
1116 value = its_pool_get_value_for_node (pool, node, "contextPointer");
1118 its_value_list_set_value (result, "contextPointer", value);
1120 value = its_pool_get_value_for_node (pool, node, "textPointer");
1122 its_value_list_set_value (result, "textPointer", value);
1127 static struct its_rule_class_ty its_extension_context_rule_class =
1129 sizeof (struct its_rule_ty),
1130 its_extension_context_rule_constructor,
1131 its_rule_destructor,
1133 its_extension_context_rule_eval,
1136 /* Implementation of Escape Special Characters data category. */
1138 its_extension_escape_rule_constructor (struct its_rule_ty *pop, xmlNode *node)
1142 if (!xmlHasProp (node, BAD_CAST "selector"))
1144 _its_error_missing_attribute (node, "selector");
1148 if (!xmlHasProp (node, BAD_CAST "escape"))
1150 _its_error_missing_attribute (node, "escape");
1154 prop = _its_get_attribute (node, "selector", NULL);
1156 pop->selector = prop;
1158 prop = _its_get_attribute (node, "escape", NULL);
1159 its_value_list_append (&pop->values, "escape", prop);
1163 struct its_value_list_ty *
1164 its_extension_escape_rule_eval (struct its_rule_ty *pop,
1165 struct its_pool_ty *pool,
1168 struct its_value_list_ty *result;
1170 result = XCALLOC (1, struct its_value_list_ty);
1174 case XML_ATTRIBUTE_NODE:
1175 /* Attribute nodes don't inherit from the parent elements. */
1178 its_pool_get_value_for_node (pool, node, "escape");
1181 its_value_list_set_value (result, "escape", value);
1187 case XML_ELEMENT_NODE:
1188 /* Inherit from the parent elements. */
1192 /* Check value for the current node. */
1193 value = its_pool_get_value_for_node (pool, node, "escape");
1196 its_value_list_set_value (result, "escape", value);
1200 /* Recursively check value for the parent node. */
1201 if (node->parent != NULL
1202 && node->parent->type == XML_ELEMENT_NODE)
1204 struct its_value_list_ty *values;
1206 values = its_extension_escape_rule_eval (pop, pool, node->parent);
1207 its_value_list_merge (result, values);
1208 its_value_list_destroy (values);
1221 static struct its_rule_class_ty its_extension_escape_rule_class =
1223 sizeof (struct its_rule_ty),
1224 its_extension_escape_rule_constructor,
1225 its_rule_destructor,
1227 its_extension_escape_rule_eval,
1230 static struct its_rule_ty *
1231 its_rule_alloc (struct its_rule_class_ty *method_table, xmlNode *node)
1233 struct its_rule_ty *pop;
1235 pop = (struct its_rule_ty *) xcalloc (1, method_table->size);
1236 pop->methods = method_table;
1237 if (method_table->constructor)
1238 method_table->constructor (pop, node);
1242 static struct its_rule_ty *
1243 its_rule_parse (xmlDoc *doc, xmlNode *node)
1245 const char *name = (const char *) node->name;
1248 if (hash_find_entry (&classes, name, strlen (name), &value) == 0)
1250 struct its_rule_ty *result;
1253 result = its_rule_alloc ((struct its_rule_class_ty *) value, node);
1254 namespaces = xmlGetNsList (doc, node);
1258 for (i = 0; namespaces[i] != NULL; i++)
1260 result->namespaces = XCALLOC (i + 1, xmlNs *);
1261 for (i = 0; namespaces[i] != NULL; i++)
1262 result->namespaces[i] = xmlCopyNamespace (namespaces[i]);
1264 xmlFree (namespaces);
1272 its_rule_destroy (struct its_rule_ty *pop)
1274 if (pop->methods->destructor)
1275 pop->methods->destructor (pop);
1281 #define ADD_RULE_CLASS(n, c) \
1282 hash_insert_entry (&classes, n, strlen (n), &c);
1284 ADD_RULE_CLASS ("translateRule", its_translate_rule_class);
1285 ADD_RULE_CLASS ("locNoteRule", its_localization_note_rule_class);
1286 ADD_RULE_CLASS ("withinTextRule", its_element_within_text_rule_class);
1287 ADD_RULE_CLASS ("preserveSpaceRule", its_preserve_space_rule_class);
1288 ADD_RULE_CLASS ("contextRule", its_extension_context_rule_class);
1289 ADD_RULE_CLASS ("escapeRule", its_extension_escape_rule_class);
1291 #undef ADD_RULE_CLASS
1294 struct its_rule_list_ty *
1295 its_rule_list_alloc (void)
1297 struct its_rule_list_ty *result;
1299 if (classes.table == NULL)
1301 hash_init (&classes, 10);
1305 result = XCALLOC (1, struct its_rule_list_ty);
1310 its_rule_list_free (struct its_rule_list_ty *rules)
1314 for (i = 0; i < rules->nitems; i++)
1316 its_rule_destroy (rules->items[i]);
1317 free (rules->items[i]);
1319 free (rules->items);
1320 its_pool_destroy (&rules->pool);
1324 its_rule_list_add_from_doc (struct its_rule_list_ty *rules,
1327 xmlNode *root, *node;
1329 root = xmlDocGetRootElement (doc);
1330 if (!(xmlStrEqual (root->name, BAD_CAST "rules")
1331 && xmlStrEqual (root->ns->href, BAD_CAST ITS_NS)))
1333 error (0, 0, _("the root element is not \"rules\""
1334 " under namespace %s"),
1340 for (node = root->children; node; node = node->next)
1342 struct its_rule_ty *rule;
1344 rule = its_rule_parse (doc, node);
1348 if (rules->nitems == rules->nitems_max)
1350 rules->nitems_max = 2 * rules->nitems_max + 1;
1352 xrealloc (rules->items,
1353 sizeof (struct its_rule_ty *) * rules->nitems_max);
1355 rules->items[rules->nitems++] = rule;
1362 its_rule_list_add_from_file (struct its_rule_list_ty *rules,
1363 const char *filename)
1368 doc = xmlReadFile (filename, "utf-8",
1370 | XML_PARSE_NOWARNING
1371 | XML_PARSE_NOBLANKS
1372 | XML_PARSE_NOERROR);
1375 xmlError *err = xmlGetLastError ();
1376 error (0, 0, _("cannot read %s: %s"), filename, err->message);
1380 result = its_rule_list_add_from_doc (rules, doc);
1386 its_rule_list_add_from_string (struct its_rule_list_ty *rules,
1392 doc = xmlReadMemory (rule, strlen (rule),
1396 | XML_PARSE_NOWARNING
1397 | XML_PARSE_NOBLANKS
1398 | XML_PARSE_NOERROR);
1401 xmlError *err = xmlGetLastError ();
1402 error (0, 0, _("cannot read %s: %s"), "(internal)", err->message);
1406 result = its_rule_list_add_from_doc (rules, doc);
1412 its_rule_list_apply (struct its_rule_list_ty *rules, xmlDoc *doc)
1416 for (i = 0; i < rules->nitems; i++)
1418 struct its_rule_ty *rule = rules->items[i];
1419 rule->methods->apply (rule, &rules->pool, doc);
1423 static struct its_value_list_ty *
1424 its_rule_list_eval (its_rule_list_ty *rules, xmlNode *node)
1426 struct its_value_list_ty *result;
1429 result = XCALLOC (1, struct its_value_list_ty);
1430 for (i = 0; i < rules->nitems; i++)
1432 struct its_rule_ty *rule = rules->items[i];
1433 struct its_value_list_ty *values;
1435 values = rule->methods->eval (rule, &rules->pool, node);
1436 its_value_list_merge (result, values);
1437 its_value_list_destroy (values);
1445 its_rule_list_is_translatable (its_rule_list_ty *rules,
1449 struct its_value_list_ty *values;
1453 if (node->type != XML_ELEMENT_NODE
1454 && node->type != XML_ATTRIBUTE_NODE)
1457 values = its_rule_list_eval (rules, node);
1459 /* Check if NODE has translate="yes". */
1460 value = its_value_list_get_value (values, "translate");
1461 if (!(value && strcmp (value, "yes") == 0))
1463 its_value_list_destroy (values);
1468 /* Check if NODE has withinText="yes", if NODE is not top-level. */
1471 value = its_value_list_get_value (values, "withinText");
1472 if (!(value && strcmp (value, "yes") == 0))
1474 its_value_list_destroy (values);
1480 its_value_list_destroy (values);
1483 for (n = node->children; n; n = n->next)
1487 case XML_ELEMENT_NODE:
1488 if (!its_rule_list_is_translatable (rules, n, depth + 1))
1493 case XML_CDATA_SECTION_NODE:
1494 case XML_ENTITY_REF_NODE:
1495 case XML_COMMENT_NODE:
1507 its_rule_list_extract_nodes (its_rule_list_ty *rules,
1508 struct its_node_list_ty *nodes,
1511 if (node->type == XML_ELEMENT_NODE)
1515 if (node->properties)
1517 xmlAttr *attr = node->properties;
1518 for (; attr; attr = attr->next)
1520 xmlNode *n = (xmlNode *) attr;
1521 if (its_rule_list_is_translatable (rules, n, 0))
1522 its_node_list_append (nodes, n);
1526 if (its_rule_list_is_translatable (rules, node, 0))
1527 its_node_list_append (nodes, node);
1530 for (n = node->children; n; n = n->next)
1531 its_rule_list_extract_nodes (rules, nodes, n);
1537 _its_get_content (struct its_rule_list_ty *rules, xmlNode *node,
1538 const char *pointer,
1539 enum its_whitespace_type_ty whitespace,
1542 xmlXPathContext *context;
1543 xmlXPathObject *object;
1545 char *result = NULL;
1547 context = xmlXPathNewContext (node->doc);
1550 error (0, 0, _("cannot create XPath context"));
1554 for (i = 0; i < rules->nitems; i++)
1556 struct its_rule_ty *rule = rules->items[i];
1557 if (rule->namespaces)
1560 for (i = 0; rule->namespaces[i] != NULL; i++)
1562 xmlNs *ns = rule->namespaces[i];
1563 xmlXPathRegisterNs (context, ns->prefix, ns->href);
1568 xmlXPathSetContextNode (node, context);
1569 object = xmlXPathEvalExpression (BAD_CAST pointer, context);
1572 xmlXPathFreeContext (context);
1573 error (0, 0, _("cannot evaluate XPath location path: %s"),
1578 switch (object->type)
1582 xmlNodeSet *nodes = object->nodesetval;
1586 string_list_init (&sl);
1587 for (i = 0; i < nodes->nodeNr; i++)
1589 char *content = _its_collect_text_content (nodes->nodeTab[i],
1592 string_list_append (&sl, content);
1595 result = string_list_concat (&sl);
1596 string_list_destroy (&sl);
1601 result = xstrdup ((const char *) object->stringval);
1608 xmlXPathFreeObject (object);
1609 xmlXPathFreeContext (context);
1615 _its_comment_append (string_list_ty *comments, const char *data)
1617 /* Split multiline comment into lines, and remove leading and trailing
1619 char *copy = xstrdup (data);
1623 for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1)
1625 while (p[0] == ' ' || p[0] == '\t')
1627 while (q > p && (q[-1] == ' ' || q[-1] == '\t'))
1630 string_list_append (comments, p);
1633 while (p[0] == ' ' || p[0] == '\t')
1635 while (q > p && (q[-1] == ' ' || q[-1] == '\t'))
1638 string_list_append (comments, p);
1643 its_rule_list_extract_text (its_rule_list_ty *rules,
1645 const char *logical_filename,
1646 flag_context_list_table_ty *flag_table,
1647 message_list_ty *mlp,
1648 its_extract_callback_ty callback)
1650 if (node->type == XML_ELEMENT_NODE
1651 || node->type == XML_ATTRIBUTE_NODE)
1653 struct its_value_list_ty *values;
1655 char *msgid = NULL, *msgctxt = NULL, *comment = NULL;
1656 enum its_whitespace_type_ty whitespace;
1659 values = its_rule_list_eval (rules, node);
1661 value = its_value_list_get_value (values, "locNote");
1663 comment = xstrdup (value);
1666 value = its_value_list_get_value (values, "escape");
1667 no_escape = value != NULL && strcmp (value, "no") == 0;
1669 value = its_value_list_get_value (values, "locNotePointer");
1671 comment = _its_get_content (rules, node, value, ITS_WHITESPACE_TRIM,
1675 if (comment != NULL && *comment != '\0')
1677 string_list_ty comments;
1680 string_list_init (&comments);
1681 _its_comment_append (&comments, comment);
1682 tmp = string_list_join (&comments, "\n", '\0', false);
1687 /* Extract comments preceding the node. */
1690 string_list_ty comments;
1692 string_list_init (&comments);
1693 for (sibling = node->prev; sibling; sibling = sibling->prev)
1694 if (sibling->type != XML_COMMENT_NODE || sibling->prev == NULL)
1698 if (sibling->type != XML_COMMENT_NODE)
1699 sibling = sibling->next;
1700 for (; sibling && sibling->type == XML_COMMENT_NODE;
1701 sibling = sibling->next)
1703 xmlChar *content = xmlNodeGetContent (sibling);
1704 _its_comment_append (&comments, (const char *) content);
1708 comment = string_list_join (&comments, "\n", '\0', false);
1709 string_list_destroy (&comments);
1713 value = its_value_list_get_value (values, "space");
1714 if (value && strcmp (value, "preserve") == 0)
1715 whitespace = ITS_WHITESPACE_PRESERVE;
1716 else if (value && strcmp (value, "trim") == 0)
1717 whitespace = ITS_WHITESPACE_TRIM;
1719 whitespace = ITS_WHITESPACE_NORMALIZE;
1721 value = its_value_list_get_value (values, "escape");
1722 no_escape = value != NULL && strcmp (value, "no") == 0;
1724 value = its_value_list_get_value (values, "contextPointer");
1726 msgctxt = _its_get_content (rules, node, value, ITS_WHITESPACE_PRESERVE,
1729 value = its_value_list_get_value (values, "textPointer");
1731 msgid = _its_get_content (rules, node, value, ITS_WHITESPACE_PRESERVE,
1733 its_value_list_destroy (values);
1737 msgid = _its_collect_text_content (node, whitespace, no_escape);
1743 pos.file_name = xstrdup (logical_filename);
1744 pos.line_number = xmlGetLineNo (node);
1746 if (node->type == XML_ELEMENT_NODE)
1748 assert (node->parent);
1749 marker = xasprintf ("%s/%s", node->parent->name, node->name);
1753 assert (node->parent && node->parent->parent);
1754 marker = xasprintf ("%s/%s@%s",
1755 node->parent->parent->name,
1760 if (msgctxt != NULL && *msgctxt == '\0')
1766 callback (mlp, msgctxt, msgid, &pos, comment, marker, whitespace);
1776 its_rule_list_extract (its_rule_list_ty *rules,
1777 FILE *fp, const char *real_filename,
1778 const char *logical_filename,
1779 flag_context_list_table_ty *flag_table,
1780 msgdomain_list_ty *mdlp,
1781 its_extract_callback_ty callback)
1784 struct its_node_list_ty nodes;
1787 doc = xmlReadFd (fileno (fp), logical_filename, NULL,
1789 | XML_PARSE_NOWARNING
1790 | XML_PARSE_NOBLANKS
1791 | XML_PARSE_NOERROR);
1794 xmlError *err = xmlGetLastError ();
1795 error (0, 0, _("cannot read %s: %s"), logical_filename, err->message);
1799 its_rule_list_apply (rules, doc);
1801 memset (&nodes, 0, sizeof (struct its_node_list_ty));
1802 its_rule_list_extract_nodes (rules,
1804 xmlDocGetRootElement (doc));
1806 for (i = 0; i < nodes.nitems; i++)
1807 its_rule_list_extract_text (rules, nodes.items[i],
1810 mdlp->item[0]->messages,
1817 struct its_merge_context_ty
1819 its_rule_list_ty *rules;
1821 struct its_node_list_ty nodes;
1825 its_merge_context_merge_node (struct its_merge_context_ty *context,
1827 const char *language,
1828 message_list_ty *mlp)
1830 if (node->type == XML_ELEMENT_NODE)
1832 struct its_value_list_ty *values;
1834 char *msgid = NULL, *msgctxt = NULL;
1835 enum its_whitespace_type_ty whitespace;
1838 values = its_rule_list_eval (context->rules, node);
1840 value = its_value_list_get_value (values, "space");
1841 if (value && strcmp (value, "preserve") == 0)
1842 whitespace = ITS_WHITESPACE_PRESERVE;
1843 else if (value && strcmp (value, "trim") == 0)
1844 whitespace = ITS_WHITESPACE_TRIM;
1846 whitespace = ITS_WHITESPACE_NORMALIZE;
1848 value = its_value_list_get_value (values, "escape");
1849 no_escape = value != NULL && strcmp (value, "no") == 0;
1851 value = its_value_list_get_value (values, "contextPointer");
1853 msgctxt = _its_get_content (context->rules, node, value,
1854 ITS_WHITESPACE_PRESERVE, no_escape);
1856 value = its_value_list_get_value (values, "textPointer");
1858 msgid = _its_get_content (context->rules, node, value,
1859 ITS_WHITESPACE_PRESERVE, no_escape);
1860 its_value_list_destroy (values);
1864 msgid = _its_collect_text_content (node, whitespace, no_escape);
1869 mp = message_list_search (mlp, msgctxt, msgid);
1870 if (mp && *mp->msgstr != '\0')
1872 xmlNode *translated;
1874 translated = xmlNewNode (node->ns, node->name);
1875 xmlSetProp (translated, BAD_CAST "xml:lang", BAD_CAST language);
1877 xmlNodeAddContent (translated, BAD_CAST mp->msgstr);
1878 xmlAddNextSibling (node, translated);
1887 its_merge_context_merge (its_merge_context_ty *context,
1888 const char *language,
1889 message_list_ty *mlp)
1893 for (i = 0; i < context->nodes.nitems; i++)
1894 its_merge_context_merge_node (context, context->nodes.items[i],
1899 struct its_merge_context_ty *
1900 its_merge_context_alloc (its_rule_list_ty *rules,
1901 const char *filename)
1904 struct its_merge_context_ty *result;
1906 doc = xmlReadFile (filename, NULL,
1908 | XML_PARSE_NOWARNING
1909 | XML_PARSE_NOBLANKS
1910 | XML_PARSE_NOERROR);
1913 xmlError *err = xmlGetLastError ();
1914 error (0, 0, _("cannot read %s: %s"), filename, err->message);
1918 its_rule_list_apply (rules, doc);
1920 result = XMALLOC (struct its_merge_context_ty);
1921 result->rules = rules;
1924 /* Collect translatable nodes. */
1925 memset (&result->nodes, 0, sizeof (struct its_node_list_ty));
1926 its_rule_list_extract_nodes (result->rules,
1928 xmlDocGetRootElement (result->doc));
1934 its_merge_context_write (struct its_merge_context_ty *context,
1937 xmlDocFormatDump (fp, context->doc, 1);
1941 its_merge_context_free (struct its_merge_context_ty *context)
1943 xmlFreeDoc (context->doc);
1944 free (context->nodes.items);