Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / its.c
1 /* Internationalization Tag Set (ITS) handling
2    Copyright (C) 2015 Free Software Foundation, Inc.
3
4    This file was written by Daiki Ueno <ueno@gnu.org>, 2015.
5
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.
10
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.
15
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/>.  */
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 /* Specification.  */
24 #include "its.h"
25
26 #include <assert.h>
27 #include <errno.h>
28 #include "error.h"
29 #include "gettext.h"
30 #include "hash.h"
31 #include <stdint.h>
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>
37 #include <stdlib.h>
38 #include "trim.h"
39 #include "xalloc.h"
40 #include "xvasprintf.h"
41
42 #define _(str) gettext (str)
43
44 /* The Internationalization Tag Set (ITS) 2.0 standard is available at:
45    http://www.w3.org/TR/its20/
46
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
50    init_classes().
51
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
57    nodes.
58
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().  */
63
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"
67
68 struct its_value_ty
69 {
70   char *name;
71   char *value;
72 };
73
74 struct its_value_list_ty
75 {
76   struct its_value_ty *items;
77   size_t nitems;
78   size_t nitems_max;
79 };
80
81 static void
82 its_value_list_append (struct its_value_list_ty *values,
83                        const char *name,
84                        const char *value)
85 {
86   struct its_value_ty _value;
87
88   _value.name = xstrdup (name);
89   _value.value = xstrdup (value);
90
91   if (values->nitems == values->nitems_max)
92     {
93       values->nitems_max = 2 * values->nitems_max + 1;
94       values->items =
95         xrealloc (values->items,
96                   sizeof (struct its_value_ty) * values->nitems_max);
97     }
98   memcpy (&values->items[values->nitems++], &_value,
99           sizeof (struct its_value_ty));
100 }
101
102 static const char *
103 its_value_list_get_value (struct its_value_list_ty *values,
104                           const char *name)
105 {
106   size_t i;
107
108   for (i = 0; i < values->nitems; i++)
109     {
110       struct its_value_ty *value = &values->items[i];
111       if (strcmp (value->name, name) == 0)
112         return value->value;
113     }
114   return NULL;
115 }
116
117 static void
118 its_value_list_set_value (struct its_value_list_ty *values,
119                           const char *name,
120                           const char *value)
121 {
122   size_t i;
123
124   for (i = 0; i < values->nitems; i++)
125     {
126       struct its_value_ty *_value = &values->items[i];
127       if (strcmp (_value->name, name) == 0)
128         {
129           free (_value->value);
130           _value->value = xstrdup (value);
131           break;
132         }
133     }
134
135   if (i == values->nitems)
136     its_value_list_append (values, name, value);
137 }
138
139 static void
140 its_value_list_merge (struct its_value_list_ty *values,
141                       struct its_value_list_ty *other)
142 {
143   size_t i;
144
145   for (i = 0; i < other->nitems; i++)
146     {
147       struct its_value_ty *other_value = &other->items[i];
148       size_t j;
149
150       for (j = 0; j < values->nitems; j++)
151         {
152           struct its_value_ty *value = &values->items[j];
153
154           if (strcmp (value->name, other_value->name) == 0
155               && strcmp (value->value, other_value->value) != 0)
156             {
157               free (value->value);
158               value->value = xstrdup (other_value->value);
159               break;
160             }
161         }
162
163       if (j == values->nitems)
164         its_value_list_append (values, other_value->name, other_value->value);
165     }
166 }
167
168 static void
169 its_value_list_destroy (struct its_value_list_ty *values)
170 {
171   size_t i;
172
173   for (i = 0; i < values->nitems; i++)
174     {
175       free (values->items[i].name);
176       free (values->items[i].value);
177     }
178   free (values->items);
179 }
180
181 struct its_pool_ty
182 {
183   struct its_value_list_ty *items;
184   size_t nitems;
185   size_t nitems_max;
186 };
187
188 static struct its_value_list_ty *
189 its_pool_alloc_value_list (struct its_pool_ty *pool)
190 {
191   struct its_value_list_ty *values;
192
193   if (pool->nitems == pool->nitems_max)
194     {
195       pool->nitems_max = 2 * pool->nitems_max + 1;
196       pool->items =
197         xrealloc (pool->items,
198                   sizeof (struct its_value_list_ty) * pool->nitems_max);
199     }
200
201   values = &pool->items[pool->nitems++];
202   memset (values, 0, sizeof (struct its_value_list_ty));
203   return values;
204 }
205
206 static const char *
207 its_pool_get_value_for_node (struct its_pool_ty *pool, xmlNode *node,
208                               const char *name)
209 {
210   intptr_t index = (intptr_t) node->_private;
211   if (index > 0)
212     {
213       struct its_value_list_ty *values;
214
215       assert (index <= pool->nitems);
216       values = &pool->items[index - 1];
217
218       return its_value_list_get_value (values, name);
219     }
220   return NULL;
221 }
222
223 static void
224 its_pool_destroy (struct its_pool_ty *pool)
225 {
226   size_t i;
227
228   for (i = 0; i < pool->nitems; i++)
229     its_value_list_destroy (&pool->items[i]);
230   free (pool->items);
231 }
232
233 struct its_rule_list_ty
234 {
235   struct its_rule_ty **items;
236   size_t nitems;
237   size_t nitems_max;
238
239   struct its_pool_ty pool;
240 };
241
242 struct its_node_list_ty
243 {
244   xmlNode **items;
245   size_t nitems;
246   size_t nitems_max;
247 };
248
249 static void
250 its_node_list_append (struct its_node_list_ty *nodes,
251                       xmlNode *node)
252 {
253   if (nodes->nitems == nodes->nitems_max)
254     {
255       nodes->nitems_max = 2 * nodes->nitems_max + 1;
256       nodes->items =
257         xrealloc (nodes->items, sizeof (xmlNode *) * nodes->nitems_max);
258     }
259   nodes->items[nodes->nitems++] = node;
260 }
261
262 /* Base class representing an ITS rule in global definition.  */
263 struct its_rule_class_ty
264 {
265   /* How many bytes to malloc for an instance of this class.  */
266   size_t size;
267
268   /* What to do immediately after the instance is malloc()ed.  */
269   void (*constructor) (struct its_rule_ty *pop, xmlNode *node);
270
271   /* What to do immediately before the instance is free()ed.  */
272   void (*destructor) (struct its_rule_ty *pop);
273
274   /* How to apply the rule to all elements in DOC.  */
275   void (* apply) (struct its_rule_ty *pop, struct its_pool_ty *pool,
276                   xmlDoc *doc);
277
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);
281 };
282
283 #define ITS_RULE_TY                             \
284   struct its_rule_class_ty *methods;            \
285   char *selector;                               \
286   struct its_value_list_ty values;              \
287   xmlNs **namespaces;
288
289 struct its_rule_ty
290 {
291   ITS_RULE_TY
292 };
293
294 static hash_table classes;
295
296 static void
297 its_rule_destructor (struct its_rule_ty *pop)
298 {
299   free (pop->selector);
300   its_value_list_destroy (&pop->values);
301   if (pop->namespaces)
302     {
303       size_t i;
304       for (i = 0; pop->namespaces[i] != NULL; i++)
305         xmlFreeNs (pop->namespaces[i]);
306       free (pop->namespaces);
307     }
308 }
309
310 static void
311 its_rule_apply (struct its_rule_ty *rule, struct its_pool_ty *pool, xmlDoc *doc)
312 {
313   xmlXPathContext *context;
314   xmlXPathObject *object;
315   size_t i;
316
317   if (!rule->selector)
318     {
319       error (0, 0, _("selector is not specified"));
320       return;
321     }
322
323   context = xmlXPathNewContext (doc);
324   if (!context)
325     {
326       error (0, 0, _("cannot create XPath context"));
327       return;
328     }
329
330   if (rule->namespaces)
331     {
332       size_t i;
333       for (i = 0; rule->namespaces[i] != NULL; i++)
334         {
335           xmlNs *ns = rule->namespaces[i];
336           xmlXPathRegisterNs (context, ns->prefix, ns->href);
337         }
338     }
339
340   object = xmlXPathEval (BAD_CAST rule->selector, context);
341   if (!object)
342     {
343       xmlXPathFreeContext (context);
344       error (0, 0, _("cannot evaluate XPath expression: %s"), rule->selector);
345       return;
346     }
347
348   if (object->nodesetval)
349     {
350       xmlNodeSet *nodes = object->nodesetval;
351       for (i = 0; i < nodes->nodeNr; i++)
352         {
353           xmlNode *node = nodes->nodeTab[i];
354           struct its_value_list_ty *values;
355
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;
359
360           assert (index <= pool->nitems);
361           if (index > 0)
362             values = &pool->items[index - 1];
363           else
364             {
365               values = its_pool_alloc_value_list (pool);
366               node->_private = (void *) pool->nitems;
367             }
368
369           its_value_list_merge (values, &rule->values);
370         }
371     }
372
373   xmlXPathFreeObject (object);
374   xmlXPathFreeContext (context);
375 }
376
377 static char *
378 _its_get_attribute (xmlNode *node, const char *attr, const char *namespace)
379 {
380   xmlChar *value;
381   char *result;
382
383   value = xmlGetNsProp (node, BAD_CAST attr, BAD_CAST namespace);
384
385   result = xstrdup ((const char *) value);
386   xmlFree (value);
387
388   return result;
389 }
390
391 static char *
392 normalize_whitespace (const char *text, enum its_whitespace_type_ty whitespace)
393 {
394   switch (whitespace)
395     {
396     case ITS_WHITESPACE_PRESERVE:
397       return xstrdup (text);
398
399     case ITS_WHITESPACE_TRIM:
400       return trim (text);
401
402     default:
403       /* Normalize whitespaces within the text, but not at the beginning
404          nor the end of the text.  */
405       {
406         char *result, *p, *end;
407
408         result = xstrdup (text);
409         end = result + strlen (result);
410         for (p = result; *p != '\0';)
411           {
412             size_t len = strspn (p, " \t\n");
413             if (len > 0)
414               {
415                 *p = ' ';
416                 memmove (p + 1, p + len, end - (p + len));
417                 end -= len - 1;
418                 *end = '\0';
419                 p++;
420               }
421             p += strcspn (p, " \t\n");
422           }
423         return result;
424       }
425     }
426 }
427
428 static char *
429 _its_encode_special_chars (const char *content, bool is_attribute)
430 {
431   const char *str;
432   size_t amount = 0;
433   char *result, *p;
434
435   for (str = content; *str != '\0'; str++)
436     {
437       switch (*str)
438         {
439         case '&':
440           amount += sizeof ("&amp;");
441           break;
442         case '<':
443           amount += sizeof ("&lt;");
444           break;
445         case '>':
446           amount += sizeof ("&gt;");
447           break;
448         case '"':
449           if (is_attribute)
450             amount += sizeof ("&quot;");
451           else
452             amount += 1;
453           break;
454         default:
455           amount += 1;
456           break;
457         }
458     }
459
460   result = XNMALLOC (amount + 1, char);
461   *result = '\0';
462   p = result;
463   for (str = content; *str != '\0'; str++)
464     {
465       switch (*str)
466         {
467         case '&':
468           p = stpcpy (p, "&amp;");
469           break;
470         case '<':
471           p = stpcpy (p, "&lt;");
472           break;
473         case '>':
474           p = stpcpy (p, "&gt;");
475           break;
476         case '"':
477           if (is_attribute)
478             p = stpcpy (p, "&quot;");
479           else
480             *p++ = '"';
481           break;
482         default:
483           *p++ = *str;
484           break;
485         }
486     }
487   *p = '\0';
488   return result;
489 }
490
491 static char *
492 _its_collect_text_content (xmlNode *node,
493                            enum its_whitespace_type_ty whitespace,
494                            bool no_escape)
495 {
496   char *buffer = NULL;
497   size_t bufmax = 0;
498   size_t bufpos = 0;
499   xmlNode *n;
500
501   for (n = node->children; n; n = n->next)
502     {
503       char *content = NULL;
504
505       switch (n->type)
506         {
507         case XML_TEXT_NODE:
508         case XML_CDATA_SECTION_NODE:
509           {
510             xmlChar *xcontent = xmlNodeGetContent (n);
511             char *econtent;
512             const char *ccontent;
513
514             /* We can't expect xmlTextWriterWriteString() encode
515                special characters as we write text outside of the
516                element.  */
517             if (no_escape)
518               econtent = xstrdup ((const char *) xcontent);
519             else
520               econtent =
521                 _its_encode_special_chars ((const char *) xcontent,
522                                            node->type == XML_ATTRIBUTE_NODE);
523             xmlFree (xcontent);
524
525             /* Skip whitespaces at the beginning of the text, if this
526                is the first node.  */
527             ccontent = econtent;
528             if (whitespace == ITS_WHITESPACE_NORMALIZE && !n->prev)
529               ccontent = ccontent + strspn (ccontent, " \t\n");
530             content =
531               normalize_whitespace (ccontent, whitespace);
532             free (econtent);
533
534             /* Skip whitespaces at the end of the text, if this
535                is the last node.  */
536             if (whitespace == ITS_WHITESPACE_NORMALIZE && !n->next)
537               {
538                 char *p = content + strlen (content);
539                 for (; p > content; p--)
540                   {
541                     int c = *(p - 1);
542                     if (!(c == ' ' || c == '\t' || c == '\n'))
543                       {
544                         *p = '\0';
545                         break;
546                       }
547                   }
548               }
549           }
550           break;
551
552         case XML_ELEMENT_NODE:
553           {
554             xmlOutputBuffer *buffer = xmlAllocOutputBuffer (NULL);
555             xmlTextWriter *writer = xmlNewTextWriter (buffer);
556             char *p = _its_collect_text_content (n, whitespace,
557                                                  no_escape);
558             const char *ccontent;
559
560             xmlTextWriterStartElement (writer, BAD_CAST n->name);
561             if (n->properties)
562               {
563                 xmlAttr *attr = n->properties;
564                 for (; attr; attr = attr->next)
565                   {
566                     xmlChar *prop = xmlGetProp (n, attr->name);
567                     xmlTextWriterWriteAttribute (writer,
568                                                  attr->name,
569                                                  prop);
570                     xmlFree (prop);
571                   }
572               }
573             if (*p != '\0')
574               xmlTextWriterWriteRaw (writer, BAD_CAST p);
575             xmlTextWriterEndElement (writer);
576             ccontent = (const char *) xmlOutputBufferGetContent (buffer);
577             content = normalize_whitespace (ccontent, whitespace);
578             xmlFreeTextWriter (writer);
579             free (p);
580           }
581           break;
582
583         case XML_ENTITY_REF_NODE:
584           content = xasprintf ("&%s;", (const char *) n->name);
585           break;
586
587         default:
588           break;
589         }
590
591       if (content != NULL)
592         {
593           size_t length = strlen (content);
594
595           if (bufpos + length + 1 >= bufmax)
596             {
597               bufmax = 2 * bufmax + length + 1;
598               buffer = xrealloc (buffer, bufmax);
599             }
600           strcpy (&buffer[bufpos], content);
601           bufpos += length;
602         }
603       free (content);
604     }
605
606   if (buffer == NULL)
607     buffer = xstrdup ("");
608   return buffer;
609 }
610
611 static void
612 _its_error_missing_attribute (xmlNode *node, const char *attribute)
613 {
614   error (0, 0, _("\"%s\" node does not contain \"%s\""),
615          node->name, attribute);
616 }
617
618 /* Implementation of Translate data category.  */
619 static void
620 its_translate_rule_constructor (struct its_rule_ty *pop, xmlNode *node)
621 {
622   char *prop;
623
624   if (!xmlHasProp (node, BAD_CAST "selector"))
625     {
626       _its_error_missing_attribute (node, "selector");
627       return;
628     }
629
630   if (!xmlHasProp (node, BAD_CAST "translate"))
631     {
632       _its_error_missing_attribute (node, "translate");
633       return;
634     }
635
636   prop = _its_get_attribute (node, "selector", NULL);
637   if (prop)
638     pop->selector = prop;
639
640   prop = _its_get_attribute (node, "translate", NULL);
641   its_value_list_append (&pop->values, "translate", prop);
642   free (prop);
643 }
644
645 struct its_value_list_ty *
646 its_translate_rule_eval (struct its_rule_ty *pop, struct its_pool_ty *pool,
647                          xmlNode *node)
648 {
649   struct its_value_list_ty *result;
650
651   result = XCALLOC (1, struct its_value_list_ty);
652
653   switch (node->type)
654     {
655     case XML_ATTRIBUTE_NODE:
656       /* Attribute nodes don't inherit from the parent elements.  */
657       {
658         const char *value =
659           its_pool_get_value_for_node (pool, node, "translate");
660         if (value != NULL)
661           {
662             its_value_list_set_value (result, "translate", value);
663             return result;
664           }
665
666         /* The default value is translate="no".  */
667         its_value_list_append (result, "translate", "no");
668       }
669       break;
670
671     case XML_ELEMENT_NODE:
672       /* Inherit from the parent elements.  */
673       {
674         const char *value;
675
676         /* A local attribute overrides the global rule.  */
677         if (xmlHasNsProp (node, BAD_CAST "translate", BAD_CAST ITS_NS))
678           {
679             char *prop;
680
681             prop = _its_get_attribute (node, "translate", ITS_NS);
682             its_value_list_append (result, "translate", prop);
683             free (prop);
684             return result;
685           }
686
687         /* Check value for the current node.  */
688         value = its_pool_get_value_for_node (pool, node, "translate");
689         if (value != NULL)
690           {
691             its_value_list_set_value (result, "translate", value);
692             return result;
693           }
694
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");
700         else
701           {
702             struct its_value_list_ty *values;
703
704             values = its_translate_rule_eval (pop, pool, node->parent);
705             its_value_list_merge (result, values);
706             its_value_list_destroy (values);
707             free (values);
708           }
709       }
710       break;
711
712     default:
713       break;
714     }
715
716   return result;
717 }
718
719 static struct its_rule_class_ty its_translate_rule_class =
720   {
721     sizeof (struct its_rule_ty),
722     its_translate_rule_constructor,
723     its_rule_destructor,
724     its_rule_apply,
725     its_translate_rule_eval,
726   };
727
728 /* Implementation of Localization Note data category.  */
729 static void
730 its_localization_note_rule_constructor (struct its_rule_ty *pop, xmlNode *node)
731 {
732   char *prop;
733   xmlNode *n;
734
735   if (!xmlHasProp (node, BAD_CAST "selector"))
736     {
737       _its_error_missing_attribute (node, "selector");
738       return;
739     }
740
741   if (!xmlHasProp (node, BAD_CAST "locNoteType"))
742     {
743       _its_error_missing_attribute (node, "locNoteType");
744       return;
745     }
746
747   prop = _its_get_attribute (node, "selector", NULL);
748   if (prop)
749     pop->selector = prop;
750
751   for (n = node->children; n; n = n->next)
752     {
753       if (n->type == XML_ELEMENT_NODE
754           && xmlStrEqual (n->name, BAD_CAST "locNote")
755           && xmlStrEqual (n->ns->href, BAD_CAST ITS_NS))
756         break;
757     }
758
759   prop = _its_get_attribute (node, "locNoteType", NULL);
760   if (prop)
761     its_value_list_append (&pop->values, "locNoteType", prop);
762   free (prop);
763
764   if (n)
765     {
766       /* FIXME: Respect space attribute.  */
767       char *content = _its_collect_text_content (n, ITS_WHITESPACE_NORMALIZE,
768                                                  false);
769       its_value_list_append (&pop->values, "locNote", content);
770       free (content);
771     }
772   else if (xmlHasProp (node, BAD_CAST "locNotePointer"))
773     {
774       prop = _its_get_attribute (node, "locNotePointer", NULL);
775       its_value_list_append (&pop->values, "locNotePointer", prop);
776       free (prop);
777     }
778   /* FIXME: locNoteRef and locNoteRefPointer */
779 }
780
781 struct its_value_list_ty *
782 its_localization_note_rule_eval (struct its_rule_ty *pop,
783                                  struct its_pool_ty *pool,
784                                  xmlNode *node)
785 {
786   struct its_value_list_ty *result;
787
788   result = XCALLOC (1, struct its_value_list_ty);
789
790   switch (node->type)
791     {
792     case XML_ATTRIBUTE_NODE:
793       /* Attribute nodes don't inherit from the parent elements.  */
794       {
795         const char *value;
796
797         value = its_pool_get_value_for_node (pool, node, "locNoteType");
798         if (value != NULL)
799           its_value_list_set_value (result, "locNoteType", value);
800
801         value = its_pool_get_value_for_node (pool, node, "locNote");
802         if (value != NULL)
803           {
804             its_value_list_set_value (result, "locNote", value);
805             return result;
806           }
807
808         value = its_pool_get_value_for_node (pool, node, "locNotePointer");
809         if (value != NULL)
810           {
811             its_value_list_set_value (result, "locNotePointer", value);
812             return result;
813           }
814       }
815       break;
816
817     case XML_ELEMENT_NODE:
818       /* Inherit from the parent elements.  */
819       {
820         const char *value;
821
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))
826           {
827             char *prop;
828
829             if (xmlHasNsProp (node, BAD_CAST "locNote", BAD_CAST ITS_NS))
830               {
831                 prop = _its_get_attribute (node, "locNote", ITS_NS);
832                 its_value_list_append (result, "locNote", prop);
833                 free (prop);
834               }
835
836             /* FIXME: locNoteRef */
837
838             if (xmlHasNsProp (node, BAD_CAST "locNoteType", BAD_CAST ITS_NS))
839               {
840                 prop = _its_get_attribute (node, "locNoteType", ITS_NS);
841                 its_value_list_append (result, "locNoteType", prop);
842                 free (prop);
843               }
844
845             return result;
846           }
847
848         /* Check value for the current node.  */
849         value = its_pool_get_value_for_node (pool, node, "locNoteType");
850         if (value != NULL)
851           its_value_list_set_value (result, "locNoteType", value);
852
853         value = its_pool_get_value_for_node (pool, node, "locNote");
854         if (value != NULL)
855           {
856             its_value_list_set_value (result, "locNote", value);
857             return result;
858           }
859
860         value = its_pool_get_value_for_node (pool, node, "locNotePointer");
861         if (value != NULL)
862           {
863             its_value_list_set_value (result, "locNotePointer", value);
864             return result;
865           }
866
867         /* Recursively check value for the parent node.  */
868         if (node->parent == NULL
869             || node->parent->type != XML_ELEMENT_NODE)
870           return result;
871         else
872           {
873             struct its_value_list_ty *values;
874
875             values = its_localization_note_rule_eval (pop, pool, node->parent);
876             its_value_list_merge (result, values);
877             its_value_list_destroy (values);
878             free (values);
879           }
880       }
881       break;
882
883     default:
884       break;
885     }
886
887   /* The default value is None.  */
888   return result;
889 }
890
891 static struct its_rule_class_ty its_localization_note_rule_class =
892   {
893     sizeof (struct its_rule_ty),
894     its_localization_note_rule_constructor,
895     its_rule_destructor,
896     its_rule_apply,
897     its_localization_note_rule_eval,
898   };
899
900 /* Implementation of Element Within Text data category.  */
901 static void
902 its_element_within_text_rule_constructor (struct its_rule_ty *pop,
903                                           xmlNode *node)
904 {
905   char *prop;
906
907   if (!xmlHasProp (node, BAD_CAST "selector"))
908     {
909       _its_error_missing_attribute (node, "selector");
910       return;
911     }
912
913   if (!xmlHasProp (node, BAD_CAST "withinText"))
914     {
915       _its_error_missing_attribute (node, "withinText");
916       return;
917     }
918
919   prop = _its_get_attribute (node, "selector", NULL);
920   if (prop)
921     pop->selector = prop;
922
923   prop = _its_get_attribute (node, "withinText", NULL);
924   its_value_list_append (&pop->values, "withinText", prop);
925   free (prop);
926 }
927
928 struct its_value_list_ty *
929 its_element_within_text_rule_eval (struct its_rule_ty *pop,
930                                    struct its_pool_ty *pool,
931                                    xmlNode *node)
932 {
933   struct its_value_list_ty *result;
934   const char *value;
935
936   result = XCALLOC (1, struct its_value_list_ty);
937
938   if (node->type != XML_ELEMENT_NODE)
939     return result;
940
941   /* A local attribute overrides the global rule.  */
942   if (xmlHasNsProp (node, BAD_CAST "withinText", BAD_CAST ITS_NS))
943     {
944       char *prop;
945
946       prop = _its_get_attribute (node, "withinText", ITS_NS);
947       its_value_list_append (result, "withinText", prop);
948       free (prop);
949       return result;
950     }
951
952   /* Doesn't inherit from the parent elements, and the default value
953      is None.  */
954   value = its_pool_get_value_for_node (pool, node, "withinText");
955   if (value != NULL)
956     its_value_list_set_value (result, "withinText", value);
957
958   return result;
959 }
960
961 static struct its_rule_class_ty its_element_within_text_rule_class =
962   {
963     sizeof (struct its_rule_ty),
964     its_element_within_text_rule_constructor,
965     its_rule_destructor,
966     its_rule_apply,
967     its_element_within_text_rule_eval,
968   };
969
970 /* Implementation of Preserve Space data category.  */
971 static void
972 its_preserve_space_rule_constructor (struct its_rule_ty *pop,
973                                      xmlNode *node)
974 {
975   char *prop;
976
977   if (!xmlHasProp (node, BAD_CAST "selector"))
978     {
979       _its_error_missing_attribute (node, "selector");
980       return;
981     }
982
983   if (!xmlHasProp (node, BAD_CAST "space"))
984     {
985       _its_error_missing_attribute (node, "space");
986       return;
987     }
988
989   prop = _its_get_attribute (node, "selector", NULL);
990   if (prop)
991     pop->selector = prop;
992
993   prop = _its_get_attribute (node, "space", NULL);
994   if (prop
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)))
1000     {
1001       error (0, 0, _("invalid attribute value \"%s\" for \"%s\""),
1002              prop, "space");
1003       free (prop);
1004       return;
1005     }
1006
1007   its_value_list_append (&pop->values, "space", prop);
1008   free (prop);
1009 }
1010
1011 struct its_value_list_ty *
1012 its_preserve_space_rule_eval (struct its_rule_ty *pop,
1013                               struct its_pool_ty *pool,
1014                               xmlNode *node)
1015 {
1016   struct its_value_list_ty *result;
1017   struct its_value_list_ty *values;
1018   const char *value;
1019
1020   result = XCALLOC (1, struct its_value_list_ty);
1021
1022   if (node->type != XML_ELEMENT_NODE)
1023     return result;
1024
1025   /* A local attribute overrides the global rule.  */
1026   if (xmlHasNsProp (node, BAD_CAST "space", BAD_CAST XML_NS))
1027     {
1028       char *prop;
1029
1030       prop = _its_get_attribute (node, "space", XML_NS);
1031       its_value_list_append (result, "space", prop);
1032       free (prop);
1033       return result;
1034     }
1035
1036   /* Check value for the current node.  */
1037   value = its_pool_get_value_for_node (pool, node, "space");
1038   if (value != NULL)
1039     {
1040       its_value_list_set_value (result, "space", value);
1041       return result;
1042     }
1043
1044   if (node->parent == NULL
1045       || node->parent->type != XML_ELEMENT_NODE)
1046     {
1047       /* The default value is space="default".  */
1048       its_value_list_append (result, "space", "default");
1049       return result;
1050     }
1051
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);
1056   free (values);
1057
1058   return result;
1059 }
1060
1061 static struct its_rule_class_ty its_preserve_space_rule_class =
1062   {
1063     sizeof (struct its_rule_ty),
1064     its_preserve_space_rule_constructor,
1065     its_rule_destructor,
1066     its_rule_apply,
1067     its_preserve_space_rule_eval,
1068   };
1069
1070 /* Implementation of Context data category.  */
1071 static void
1072 its_extension_context_rule_constructor (struct its_rule_ty *pop, xmlNode *node)
1073 {
1074   char *prop;
1075
1076   if (!xmlHasProp (node, BAD_CAST "selector"))
1077     {
1078       _its_error_missing_attribute (node, "selector");
1079       return;
1080     }
1081
1082   if (!xmlHasProp (node, BAD_CAST "contextPointer"))
1083     {
1084       _its_error_missing_attribute (node, "contextPointer");
1085       return;
1086     }
1087
1088   prop = _its_get_attribute (node, "selector", NULL);
1089   if (prop)
1090     pop->selector = prop;
1091
1092   prop = _its_get_attribute (node, "contextPointer", NULL);
1093   its_value_list_append (&pop->values, "contextPointer", prop);
1094   free (prop);
1095
1096   if (xmlHasProp (node, BAD_CAST "textPointer"))
1097     {
1098       prop = _its_get_attribute (node, "textPointer", NULL);
1099       its_value_list_append (&pop->values, "textPointer", prop);
1100       free (prop);
1101     }
1102 }
1103
1104 struct its_value_list_ty *
1105 its_extension_context_rule_eval (struct its_rule_ty *pop,
1106                                  struct its_pool_ty *pool,
1107                                  xmlNode *node)
1108 {
1109   struct its_value_list_ty *result;
1110   const char *value;
1111
1112   result = XCALLOC (1, struct its_value_list_ty);
1113
1114   /* Doesn't inherit from the parent elements, and the default value
1115      is None.  */
1116   value = its_pool_get_value_for_node (pool, node, "contextPointer");
1117   if (value != NULL)
1118     its_value_list_set_value (result, "contextPointer", value);
1119
1120   value = its_pool_get_value_for_node (pool, node, "textPointer");
1121   if (value != NULL)
1122     its_value_list_set_value (result, "textPointer", value);
1123
1124   return result;
1125 }
1126
1127 static struct its_rule_class_ty its_extension_context_rule_class =
1128   {
1129     sizeof (struct its_rule_ty),
1130     its_extension_context_rule_constructor,
1131     its_rule_destructor,
1132     its_rule_apply,
1133     its_extension_context_rule_eval,
1134   };
1135
1136 /* Implementation of Escape Special Characters data category.  */
1137 static void
1138 its_extension_escape_rule_constructor (struct its_rule_ty *pop, xmlNode *node)
1139 {
1140   char *prop;
1141
1142   if (!xmlHasProp (node, BAD_CAST "selector"))
1143     {
1144       _its_error_missing_attribute (node, "selector");
1145       return;
1146     }
1147
1148   if (!xmlHasProp (node, BAD_CAST "escape"))
1149     {
1150       _its_error_missing_attribute (node, "escape");
1151       return;
1152     }
1153
1154   prop = _its_get_attribute (node, "selector", NULL);
1155   if (prop)
1156     pop->selector = prop;
1157
1158   prop = _its_get_attribute (node, "escape", NULL);
1159   its_value_list_append (&pop->values, "escape", prop);
1160   free (prop);
1161 }
1162
1163 struct its_value_list_ty *
1164 its_extension_escape_rule_eval (struct its_rule_ty *pop,
1165                                 struct its_pool_ty *pool,
1166                                 xmlNode *node)
1167 {
1168   struct its_value_list_ty *result;
1169
1170   result = XCALLOC (1, struct its_value_list_ty);
1171
1172   switch (node->type)
1173     {
1174     case XML_ATTRIBUTE_NODE:
1175       /* Attribute nodes don't inherit from the parent elements.  */
1176       {
1177         const char *value =
1178           its_pool_get_value_for_node (pool, node, "escape");
1179         if (value != NULL)
1180           {
1181             its_value_list_set_value (result, "escape", value);
1182             return result;
1183           }
1184       }
1185       break;
1186
1187     case XML_ELEMENT_NODE:
1188       /* Inherit from the parent elements.  */
1189       {
1190         const char *value;
1191
1192         /* Check value for the current node.  */
1193         value = its_pool_get_value_for_node (pool, node, "escape");
1194         if (value != NULL)
1195           {
1196             its_value_list_set_value (result, "escape", value);
1197             return result;
1198           }
1199
1200         /* Recursively check value for the parent node.  */
1201         if (node->parent != NULL
1202             && node->parent->type == XML_ELEMENT_NODE)
1203           {
1204             struct its_value_list_ty *values;
1205
1206             values = its_extension_escape_rule_eval (pop, pool, node->parent);
1207             its_value_list_merge (result, values);
1208             its_value_list_destroy (values);
1209             free (values);
1210           }
1211       }
1212       break;
1213
1214     default:
1215       break;
1216     }
1217
1218   return result;
1219 }
1220
1221 static struct its_rule_class_ty its_extension_escape_rule_class =
1222   {
1223     sizeof (struct its_rule_ty),
1224     its_extension_escape_rule_constructor,
1225     its_rule_destructor,
1226     its_rule_apply,
1227     its_extension_escape_rule_eval,
1228   };
1229
1230 static struct its_rule_ty *
1231 its_rule_alloc (struct its_rule_class_ty *method_table, xmlNode *node)
1232 {
1233   struct its_rule_ty *pop;
1234
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);
1239   return pop;
1240 }
1241
1242 static struct its_rule_ty *
1243 its_rule_parse (xmlDoc *doc, xmlNode *node)
1244 {
1245   const char *name = (const char *) node->name;
1246   void *value;
1247
1248   if (hash_find_entry (&classes, name, strlen (name), &value) == 0)
1249     {
1250       struct its_rule_ty *result;
1251       xmlNs **namespaces;
1252
1253       result = its_rule_alloc ((struct its_rule_class_ty *) value, node);
1254       namespaces = xmlGetNsList (doc, node);
1255       if (namespaces)
1256         {
1257           size_t i;
1258           for (i = 0; namespaces[i] != NULL; i++)
1259             ;
1260           result->namespaces = XCALLOC (i + 1, xmlNs *);
1261           for (i = 0; namespaces[i] != NULL; i++)
1262             result->namespaces[i] = xmlCopyNamespace (namespaces[i]);
1263         }
1264       xmlFree (namespaces);
1265       return result;
1266     }
1267
1268   return NULL;
1269 }
1270
1271 static void
1272 its_rule_destroy (struct its_rule_ty *pop)
1273 {
1274   if (pop->methods->destructor)
1275     pop->methods->destructor (pop);
1276 }
1277
1278 static void
1279 init_classes (void)
1280 {
1281 #define ADD_RULE_CLASS(n, c) \
1282   hash_insert_entry (&classes, n, strlen (n), &c);
1283
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);
1290
1291 #undef ADD_RULE_CLASS
1292 }
1293
1294 struct its_rule_list_ty *
1295 its_rule_list_alloc (void)
1296 {
1297   struct its_rule_list_ty *result;
1298
1299   if (classes.table == NULL)
1300     {
1301       hash_init (&classes, 10);
1302       init_classes ();
1303     }
1304
1305   result = XCALLOC (1, struct its_rule_list_ty);
1306   return result;
1307 }
1308
1309 void
1310 its_rule_list_free (struct its_rule_list_ty *rules)
1311 {
1312   size_t i;
1313
1314   for (i = 0; i < rules->nitems; i++)
1315     {
1316       its_rule_destroy (rules->items[i]);
1317       free (rules->items[i]);
1318     }
1319   free (rules->items);
1320   its_pool_destroy (&rules->pool);
1321 }
1322
1323 static bool
1324 its_rule_list_add_from_doc (struct its_rule_list_ty *rules,
1325                             xmlDoc *doc)
1326 {
1327   xmlNode *root, *node;
1328
1329   root = xmlDocGetRootElement (doc);
1330   if (!(xmlStrEqual (root->name, BAD_CAST "rules")
1331         && xmlStrEqual (root->ns->href, BAD_CAST ITS_NS)))
1332     {
1333       error (0, 0, _("the root element is not \"rules\""
1334                      " under namespace %s"),
1335              ITS_NS);
1336       xmlFreeDoc (doc);
1337       return false;
1338     }
1339
1340   for (node = root->children; node; node = node->next)
1341     {
1342       struct its_rule_ty *rule;
1343
1344       rule = its_rule_parse (doc, node);
1345       if (!rule)
1346         continue;
1347
1348       if (rules->nitems == rules->nitems_max)
1349         {
1350           rules->nitems_max = 2 * rules->nitems_max + 1;
1351           rules->items =
1352             xrealloc (rules->items,
1353                       sizeof (struct its_rule_ty *) * rules->nitems_max);
1354         }
1355       rules->items[rules->nitems++] = rule;
1356     }
1357
1358   return true;
1359 }
1360
1361 bool
1362 its_rule_list_add_from_file (struct its_rule_list_ty *rules,
1363                              const char *filename)
1364 {
1365   xmlDoc *doc;
1366   bool result;
1367
1368   doc = xmlReadFile (filename, "utf-8",
1369                      XML_PARSE_NONET
1370                      | XML_PARSE_NOWARNING
1371                      | XML_PARSE_NOBLANKS
1372                      | XML_PARSE_NOERROR);
1373   if (doc == NULL)
1374     {
1375       xmlError *err = xmlGetLastError ();
1376       error (0, 0, _("cannot read %s: %s"), filename, err->message);
1377       return false;
1378     }
1379
1380   result = its_rule_list_add_from_doc (rules, doc);
1381   xmlFreeDoc (doc);
1382   return result;
1383 }
1384
1385 bool
1386 its_rule_list_add_from_string (struct its_rule_list_ty *rules,
1387                                const char *rule)
1388 {
1389   xmlDoc *doc;
1390   bool result;
1391
1392   doc = xmlReadMemory (rule, strlen (rule),
1393                        "(internal)",
1394                        NULL,
1395                        XML_PARSE_NONET
1396                        | XML_PARSE_NOWARNING
1397                        | XML_PARSE_NOBLANKS
1398                        | XML_PARSE_NOERROR);
1399   if (doc == NULL)
1400     {
1401       xmlError *err = xmlGetLastError ();
1402       error (0, 0, _("cannot read %s: %s"), "(internal)", err->message);
1403       return false;
1404     }
1405
1406   result = its_rule_list_add_from_doc (rules, doc);
1407   xmlFreeDoc (doc);
1408   return result;
1409 }
1410
1411 static void
1412 its_rule_list_apply (struct its_rule_list_ty *rules, xmlDoc *doc)
1413 {
1414   size_t i;
1415
1416   for (i = 0; i < rules->nitems; i++)
1417     {
1418       struct its_rule_ty *rule = rules->items[i];
1419       rule->methods->apply (rule, &rules->pool, doc);
1420     }
1421 }
1422
1423 static struct its_value_list_ty *
1424 its_rule_list_eval (its_rule_list_ty *rules, xmlNode *node)
1425 {
1426   struct its_value_list_ty *result;
1427   size_t i;
1428
1429   result = XCALLOC (1, struct its_value_list_ty);
1430   for (i = 0; i < rules->nitems; i++)
1431     {
1432       struct its_rule_ty *rule = rules->items[i];
1433       struct its_value_list_ty *values;
1434
1435       values = rule->methods->eval (rule, &rules->pool, node);
1436       its_value_list_merge (result, values);
1437       its_value_list_destroy (values);
1438       free (values);
1439     }
1440
1441   return result;
1442 }
1443
1444 static bool
1445 its_rule_list_is_translatable (its_rule_list_ty *rules,
1446                                xmlNode *node,
1447                                int depth)
1448 {
1449   struct its_value_list_ty *values;
1450   const char *value;
1451   xmlNode *n;
1452
1453   if (node->type != XML_ELEMENT_NODE
1454       && node->type != XML_ATTRIBUTE_NODE)
1455     return false;
1456
1457   values = its_rule_list_eval (rules, node);
1458
1459   /* Check if NODE has translate="yes".  */
1460   value = its_value_list_get_value (values, "translate");
1461   if (!(value && strcmp (value, "yes") == 0))
1462     {
1463       its_value_list_destroy (values);
1464       free (values);
1465       return false;
1466     }
1467
1468   /* Check if NODE has withinText="yes", if NODE is not top-level.  */
1469   if (depth > 0)
1470     {
1471       value = its_value_list_get_value (values, "withinText");
1472       if (!(value && strcmp (value, "yes") == 0))
1473         {
1474           its_value_list_destroy (values);
1475           free (values);
1476           return false;
1477         }
1478     }
1479
1480   its_value_list_destroy (values);
1481   free (values);
1482
1483   for (n = node->children; n; n = n->next)
1484     {
1485       switch (n->type)
1486         {
1487         case XML_ELEMENT_NODE:
1488           if (!its_rule_list_is_translatable (rules, n, depth + 1))
1489             return false;
1490           break;
1491
1492         case XML_TEXT_NODE:
1493         case XML_CDATA_SECTION_NODE:
1494         case XML_ENTITY_REF_NODE:
1495         case XML_COMMENT_NODE:
1496           break;
1497
1498         default:
1499           return false;
1500         }
1501     }
1502
1503   return true;
1504 }
1505
1506 static void
1507 its_rule_list_extract_nodes (its_rule_list_ty *rules,
1508                              struct its_node_list_ty *nodes,
1509                              xmlNode *node)
1510 {
1511   if (node->type == XML_ELEMENT_NODE)
1512     {
1513       xmlNode *n;
1514
1515       if (node->properties)
1516         {
1517           xmlAttr *attr = node->properties;
1518           for (; attr; attr = attr->next)
1519             {
1520               xmlNode *n = (xmlNode *) attr;
1521               if (its_rule_list_is_translatable (rules, n, 0))
1522                 its_node_list_append (nodes, n);
1523             }
1524         }
1525
1526       if (its_rule_list_is_translatable (rules, node, 0))
1527         its_node_list_append (nodes, node);
1528       else
1529         {
1530           for (n = node->children; n; n = n->next)
1531             its_rule_list_extract_nodes (rules, nodes, n);
1532         }
1533     }
1534 }
1535
1536 static char *
1537 _its_get_content (struct its_rule_list_ty *rules, xmlNode *node,
1538                   const char *pointer,
1539                   enum its_whitespace_type_ty whitespace,
1540                   bool no_escape)
1541 {
1542   xmlXPathContext *context;
1543   xmlXPathObject *object;
1544   size_t i;
1545   char *result = NULL;
1546
1547   context = xmlXPathNewContext (node->doc);
1548   if (!context)
1549     {
1550       error (0, 0, _("cannot create XPath context"));
1551       return NULL;
1552     }
1553
1554   for (i = 0; i < rules->nitems; i++)
1555     {
1556       struct its_rule_ty *rule = rules->items[i];
1557       if (rule->namespaces)
1558         {
1559           size_t i;
1560           for (i = 0; rule->namespaces[i] != NULL; i++)
1561             {
1562               xmlNs *ns = rule->namespaces[i];
1563               xmlXPathRegisterNs (context, ns->prefix, ns->href);
1564             }
1565         }
1566     }
1567
1568   xmlXPathSetContextNode (node, context);
1569   object = xmlXPathEvalExpression (BAD_CAST pointer, context);
1570   if (!object)
1571     {
1572       xmlXPathFreeContext (context);
1573       error (0, 0, _("cannot evaluate XPath location path: %s"),
1574              pointer);
1575       return NULL;
1576     }
1577
1578   switch (object->type)
1579     {
1580     case XPATH_NODESET:
1581       {
1582         xmlNodeSet *nodes = object->nodesetval;
1583         string_list_ty sl;
1584         size_t i;
1585
1586         string_list_init (&sl);
1587         for (i = 0; i < nodes->nodeNr; i++)
1588           {
1589             char *content = _its_collect_text_content (nodes->nodeTab[i],
1590                                                        whitespace,
1591                                                        no_escape);
1592             string_list_append (&sl, content);
1593             free (content);
1594           }
1595         result = string_list_concat (&sl);
1596         string_list_destroy (&sl);
1597       }
1598       break;
1599
1600     case XPATH_STRING:
1601       result = xstrdup ((const char *) object->stringval);
1602       break;
1603
1604     default:
1605       break;
1606     }
1607
1608   xmlXPathFreeObject (object);
1609   xmlXPathFreeContext (context);
1610
1611   return result;
1612 }
1613
1614 static void
1615 _its_comment_append (string_list_ty *comments, const char *data)
1616 {
1617   /* Split multiline comment into lines, and remove leading and trailing
1618      whitespace.  */
1619   char *copy = xstrdup (data);
1620   char *p;
1621   char *q;
1622
1623   for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1)
1624     {
1625       while (p[0] == ' ' || p[0] == '\t')
1626         p++;
1627       while (q > p && (q[-1] == ' ' || q[-1] == '\t'))
1628         q--;
1629       *q = '\0';
1630       string_list_append (comments, p);
1631     }
1632   q = p + strlen (p);
1633   while (p[0] == ' ' || p[0] == '\t')
1634     p++;
1635   while (q > p && (q[-1] == ' ' || q[-1] == '\t'))
1636     q--;
1637   *q = '\0';
1638   string_list_append (comments, p);
1639   free (copy);
1640 }
1641
1642 static void
1643 its_rule_list_extract_text (its_rule_list_ty *rules,
1644                             xmlNode *node,
1645                             const char *logical_filename,
1646                             flag_context_list_table_ty *flag_table,
1647                             message_list_ty *mlp,
1648                             its_extract_callback_ty callback)
1649 {
1650   if (node->type == XML_ELEMENT_NODE
1651       || node->type == XML_ATTRIBUTE_NODE)
1652     {
1653       struct its_value_list_ty *values;
1654       const char *value;
1655       char *msgid = NULL, *msgctxt = NULL, *comment = NULL;
1656       enum its_whitespace_type_ty whitespace;
1657       bool no_escape;
1658       
1659       values = its_rule_list_eval (rules, node);
1660
1661       value = its_value_list_get_value (values, "locNote");
1662       if (value)
1663         comment = xstrdup (value);
1664       else
1665         {
1666           value = its_value_list_get_value (values, "escape");
1667           no_escape = value != NULL && strcmp (value, "no") == 0;
1668
1669           value = its_value_list_get_value (values, "locNotePointer");
1670           if (value)
1671             comment = _its_get_content (rules, node, value, ITS_WHITESPACE_TRIM,
1672                                         no_escape);
1673         }
1674
1675       if (comment != NULL && *comment != '\0')
1676         {
1677           string_list_ty comments;
1678           char *tmp;
1679
1680           string_list_init (&comments);
1681           _its_comment_append (&comments, comment);
1682           tmp = string_list_join (&comments, "\n", '\0', false);
1683           free (comment);
1684           comment = tmp;
1685         }
1686       else
1687         /* Extract comments preceding the node.  */
1688         {
1689           xmlNode *sibling;
1690           string_list_ty comments;
1691
1692           string_list_init (&comments);
1693           for (sibling = node->prev; sibling; sibling = sibling->prev)
1694             if (sibling->type != XML_COMMENT_NODE || sibling->prev == NULL)
1695               break;
1696           if (sibling)
1697             {
1698               if (sibling->type != XML_COMMENT_NODE)
1699                 sibling = sibling->next;
1700               for (; sibling && sibling->type == XML_COMMENT_NODE;
1701                    sibling = sibling->next)
1702                 {
1703                   xmlChar *content = xmlNodeGetContent (sibling);
1704                   _its_comment_append (&comments, (const char *) content);
1705                   xmlFree (content);
1706                 }
1707               free (comment);
1708               comment = string_list_join (&comments, "\n", '\0', false);
1709               string_list_destroy (&comments);
1710             }
1711         }
1712       
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;
1718       else
1719         whitespace = ITS_WHITESPACE_NORMALIZE;
1720
1721       value = its_value_list_get_value (values, "escape");
1722       no_escape = value != NULL && strcmp (value, "no") == 0;
1723
1724       value = its_value_list_get_value (values, "contextPointer");
1725       if (value)
1726         msgctxt = _its_get_content (rules, node, value, ITS_WHITESPACE_PRESERVE,
1727                                     no_escape);
1728
1729       value = its_value_list_get_value (values, "textPointer");
1730       if (value)
1731         msgid = _its_get_content (rules, node, value, ITS_WHITESPACE_PRESERVE,
1732                                   no_escape);
1733       its_value_list_destroy (values);
1734       free (values);
1735
1736       if (msgid == NULL)
1737         msgid = _its_collect_text_content (node, whitespace, no_escape);
1738       if (*msgid != '\0')
1739         {
1740           lex_pos_ty pos;
1741           char *marker;
1742
1743           pos.file_name = xstrdup (logical_filename);
1744           pos.line_number = xmlGetLineNo (node);
1745
1746           if (node->type == XML_ELEMENT_NODE)
1747             {
1748               assert (node->parent);
1749               marker = xasprintf ("%s/%s", node->parent->name, node->name);
1750             }
1751           else
1752             {
1753               assert (node->parent && node->parent->parent);
1754               marker = xasprintf ("%s/%s@%s",
1755                                   node->parent->parent->name,
1756                                   node->parent->name,
1757                                   node->name);
1758             }
1759
1760           if (msgctxt != NULL && *msgctxt == '\0')
1761             {
1762               free (msgctxt);
1763               msgctxt = NULL;
1764             }
1765
1766           callback (mlp, msgctxt, msgid, &pos, comment, marker, whitespace);
1767           free (marker);
1768         }
1769       free (msgctxt);
1770       free (msgid);
1771       free (comment);
1772     }
1773 }
1774
1775 void
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)
1782 {
1783   xmlDoc *doc;
1784   struct its_node_list_ty nodes;
1785   size_t i;
1786
1787   doc = xmlReadFd (fileno (fp), logical_filename, NULL,
1788                    XML_PARSE_NONET
1789                    | XML_PARSE_NOWARNING
1790                    | XML_PARSE_NOBLANKS
1791                    | XML_PARSE_NOERROR);
1792   if (doc == NULL)
1793     {
1794       xmlError *err = xmlGetLastError ();
1795       error (0, 0, _("cannot read %s: %s"), logical_filename, err->message);
1796       return;
1797     }
1798
1799   its_rule_list_apply (rules, doc);
1800
1801   memset (&nodes, 0, sizeof (struct its_node_list_ty));
1802   its_rule_list_extract_nodes (rules,
1803                                &nodes,
1804                                xmlDocGetRootElement (doc));
1805
1806   for (i = 0; i < nodes.nitems; i++)
1807     its_rule_list_extract_text (rules, nodes.items[i],
1808                                 logical_filename,
1809                                 flag_table,
1810                                 mdlp->item[0]->messages,
1811                                 callback);
1812
1813   free (nodes.items);
1814   xmlFreeDoc (doc);
1815 }
1816
1817 struct its_merge_context_ty
1818 {
1819   its_rule_list_ty *rules;
1820   xmlDoc *doc;
1821   struct its_node_list_ty nodes;
1822 };
1823
1824 static void
1825 its_merge_context_merge_node (struct its_merge_context_ty *context,
1826                               xmlNode *node,
1827                               const char *language,
1828                               message_list_ty *mlp)
1829 {
1830   if (node->type == XML_ELEMENT_NODE)
1831     {
1832       struct its_value_list_ty *values;
1833       const char *value;
1834       char *msgid = NULL, *msgctxt = NULL;
1835       enum its_whitespace_type_ty whitespace;
1836       bool no_escape;
1837
1838       values = its_rule_list_eval (context->rules, node);
1839
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;
1845       else
1846         whitespace = ITS_WHITESPACE_NORMALIZE;
1847
1848       value = its_value_list_get_value (values, "escape");
1849       no_escape = value != NULL && strcmp (value, "no") == 0;
1850
1851       value = its_value_list_get_value (values, "contextPointer");
1852       if (value)
1853         msgctxt = _its_get_content (context->rules, node, value,
1854                                     ITS_WHITESPACE_PRESERVE, no_escape);
1855
1856       value = its_value_list_get_value (values, "textPointer");
1857       if (value)
1858         msgid = _its_get_content (context->rules, node, value,
1859                                   ITS_WHITESPACE_PRESERVE, no_escape);
1860       its_value_list_destroy (values);
1861       free (values);
1862
1863       if (msgid == NULL)
1864         msgid = _its_collect_text_content (node, whitespace, no_escape);
1865       if (*msgid != '\0')
1866         {
1867           message_ty *mp;
1868
1869           mp = message_list_search (mlp, msgctxt, msgid);
1870           if (mp && *mp->msgstr != '\0')
1871             {
1872               xmlNode *translated;
1873
1874               translated = xmlNewNode (node->ns, node->name);
1875               xmlSetProp (translated, BAD_CAST "xml:lang", BAD_CAST language);
1876
1877               xmlNodeAddContent (translated, BAD_CAST mp->msgstr);
1878               xmlAddNextSibling (node, translated);
1879             }
1880         }
1881       free (msgctxt);
1882       free (msgid);
1883     }
1884 }
1885
1886 void
1887 its_merge_context_merge (its_merge_context_ty *context,
1888                          const char *language,
1889                          message_list_ty *mlp)
1890 {
1891   size_t i;
1892
1893   for (i = 0; i < context->nodes.nitems; i++)
1894     its_merge_context_merge_node (context, context->nodes.items[i],
1895                                   language,
1896                                   mlp);
1897 }
1898
1899 struct its_merge_context_ty *
1900 its_merge_context_alloc (its_rule_list_ty *rules,
1901                          const char *filename)
1902 {
1903   xmlDoc *doc;
1904   struct its_merge_context_ty *result;
1905
1906   doc = xmlReadFile (filename, NULL,
1907                      XML_PARSE_NONET
1908                      | XML_PARSE_NOWARNING
1909                      | XML_PARSE_NOBLANKS
1910                      | XML_PARSE_NOERROR);
1911   if (doc == NULL)
1912     {
1913       xmlError *err = xmlGetLastError ();
1914       error (0, 0, _("cannot read %s: %s"), filename, err->message);
1915       return NULL;
1916     }
1917
1918   its_rule_list_apply (rules, doc);
1919
1920   result = XMALLOC (struct its_merge_context_ty);
1921   result->rules = rules;
1922   result->doc = doc;
1923
1924   /* Collect translatable nodes.  */
1925   memset (&result->nodes, 0, sizeof (struct its_node_list_ty));
1926   its_rule_list_extract_nodes (result->rules,
1927                                &result->nodes,
1928                                xmlDocGetRootElement (result->doc));
1929
1930   return result;
1931 }
1932
1933 void
1934 its_merge_context_write (struct its_merge_context_ty *context,
1935                          FILE *fp)
1936 {
1937   xmlDocFormatDump (fp, context->doc, 1);
1938 }
1939
1940 void
1941 its_merge_context_free (struct its_merge_context_ty *context)
1942 {
1943   xmlFreeDoc (context->doc);
1944   free (context->nodes.items);
1945   free (context);
1946 }