171e45b271fa9c7b7e895c0523a62acf9e263802
[platform/upstream/libxslt.git] / libxslt / transform.c
1 /*
2  * transform.c: Implemetation of the XSL Transformation 1.0 engine
3  *            transform part, i.e. applying a Stylesheet to a document
4  *
5  * Reference:
6  *   http://www.w3.org/TR/1999/REC-xslt-19991116
7  *
8  * See Copyright for the status of this software.
9  *
10  * Daniel.Veillard@imag.fr
11  */
12
13 #include "xsltconfig.h"
14
15 #include <string.h>
16
17 #include <libxml/xmlmemory.h>
18 #include <libxml/parser.h>
19 #include <libxml/tree.h>
20 #include <libxml/valid.h>
21 #include <libxml/hash.h>
22 #include <libxml/encoding.h>
23 #include <libxml/xmlerror.h>
24 #include <libxml/xpath.h>
25 #include <libxml/xpathInternals.h>
26 #include <libxml/HTMLtree.h>
27 #include "xslt.h"
28 #include "xsltInternals.h"
29 #include "xsltutils.h"
30 #include "pattern.h"
31 #include "transform.h"
32 #include "variables.h"
33 #include "namespaces.h"
34 #include "templates.h"
35
36 #define DEBUG_PROCESS
37
38 /*
39  * Useful macros
40  */
41
42 #define IS_BLANK_NODE(n)                                                \
43     (((n)->type == XML_TEXT_NODE) && (xsltIsBlank((n)->content)))
44
45
46 /************************************************************************
47  *                                                                      *
48  *                      
49  *                                                                      *
50  ************************************************************************/
51
52 /**
53  * xsltNewTransformContext:
54  *
55  * Create a new XSLT TransformContext
56  *
57  * Returns the newly allocated xsltTransformContextPtr or NULL in case of error
58  */
59 xsltTransformContextPtr
60 xsltNewTransformContext(void) {
61     xsltTransformContextPtr cur;
62
63     cur = (xsltTransformContextPtr) xmlMalloc(sizeof(xsltTransformContext));
64     if (cur == NULL) {
65         xsltGenericError(xsltGenericErrorContext,
66                 "xsltNewTransformContext : malloc failed\n");
67         return(NULL);
68     }
69     memset(cur, 0, sizeof(xsltTransformContext));
70     return(cur);
71 }
72
73 /**
74  * xsltFreeTransformContext:
75  * @ctxt:  an XSLT parser context
76  *
77  * Free up the memory allocated by @ctxt
78  */
79 void
80 xsltFreeTransformContext(xsltTransformContextPtr ctxt) {
81     xmlDocPtr doc, next;
82
83     if (ctxt == NULL)
84         return;
85     doc = ctxt->extraDocs;
86     while (doc != NULL) {
87         next = (xmlDocPtr) doc->next;
88         xmlFreeDoc(doc);
89         doc = next;
90     }
91     if (ctxt->xpathCtxt != NULL)
92         xmlXPathFreeContext(ctxt->xpathCtxt);
93     xsltFreeVariableHashes(ctxt);
94     memset(ctxt, -1, sizeof(xsltTransformContext));
95     xmlFree(ctxt);
96 }
97
98 /************************************************************************
99  *                                                                      *
100  *                      
101  *                                                                      *
102  ************************************************************************/
103
104 void xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node);
105 void xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr node,
106                  xmlNodePtr inst);
107 void xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst);
108
109 /**
110  * xsltSort:
111  * @ctxt:  a XSLT process context
112  * @node:  the node in the source tree.
113  * @inst:  the xslt sort node
114  *
115  * Process the xslt sort node on the source node
116  */
117 void
118 xsltSort(xsltTransformContextPtr ctxt, xmlNodePtr node,
119                    xmlNodePtr inst) {
120     xmlXPathObjectPtr *results = NULL;
121     xmlNodeSetPtr list = NULL;
122     xmlXPathParserContextPtr xpathParserCtxt = NULL;
123     xmlChar *prop = NULL;
124     xmlXPathObjectPtr res, tmp;
125     const xmlChar *start;
126     int descending = 0;
127     int number = 0;
128     int len = 0;
129     int i;
130
131     if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
132         return;
133
134     list = ctxt->nodeList;
135     if ((list == NULL) || (list->nodeNr <= 1))
136         goto error; /* nothing to do */
137
138     len = list->nodeNr;
139
140     /* TODO: process attributes as attribute value templates */
141     prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *)"data-type");
142     if (prop != NULL) {
143         if (xmlStrEqual(prop, (const xmlChar *) "text"))
144             number = 0;
145         else if (xmlStrEqual(prop, (const xmlChar *) "number"))
146             number = 1;
147         else {
148             xsltGenericError(xsltGenericErrorContext,
149                  "xsltSort: no support for data-type = %s\n", prop);
150             goto error;
151         }
152         xmlFree(prop);
153     }
154     prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *)"order");
155     if (prop != NULL) {
156         if (xmlStrEqual(prop, (const xmlChar *) "ascending"))
157             descending = 0;
158         else if (xmlStrEqual(prop, (const xmlChar *) "descending"))
159             descending = 1;
160         else {
161             xsltGenericError(xsltGenericErrorContext,
162                  "xsltSort: invalid value %s for order\n", prop);
163             goto error;
164         }
165         xmlFree(prop);
166     }
167     /* TODO: xsl:sort lang attribute */
168     /* TODO: xsl:sort case-order attribute */
169
170     prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
171     if (prop == NULL) {
172         prop = xmlNodeGetContent(inst);
173         if (prop == NULL) {
174             xsltGenericError(xsltGenericErrorContext,
175                  "xsltSort: select is not defined\n");
176             return;
177         }
178     }
179
180     xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
181     if (xpathParserCtxt == NULL)
182         goto error;
183     results = xmlMalloc(len * sizeof(xmlXPathObjectPtr));
184     if (results == NULL) {
185         xsltGenericError(xsltGenericErrorContext,
186              "xsltSort: memory allocation failure\n");
187         goto error;
188     }
189
190     start = xpathParserCtxt->cur;
191     for (i = 0;i < len;i++) {
192         xpathParserCtxt->cur = start;
193         node = ctxt->node = list->nodeTab[i];
194         ctxt->xpathCtxt->proximityPosition = i + 1;
195         valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
196         xmlXPathEvalExpr(xpathParserCtxt);
197         xmlXPathStringFunction(xpathParserCtxt, 1);
198         if (number)
199             xmlXPathNumberFunction(xpathParserCtxt, 1);
200         res = valuePop(xpathParserCtxt);
201         do {
202             tmp = valuePop(xpathParserCtxt);
203             if (tmp != NULL) {
204                 xmlXPathFreeObject(tmp);
205             }
206         } while (tmp != NULL);
207
208         if (res != NULL) {
209             if (number) {
210                 if (res->type == XPATH_NUMBER) {
211                     results[i] = res;
212                 } else {
213 #ifdef DEBUG_PROCESS
214                     xsltGenericDebug(xsltGenericDebugContext,
215                         "xsltSort: select didn't evaluate to a number\n");
216 #endif
217                     results[i] = NULL;
218                 }
219             } else {
220                 if (res->type == XPATH_STRING) {
221                     results[i] = res;
222                 } else {
223 #ifdef DEBUG_PROCESS
224                     xsltGenericDebug(xsltGenericDebugContext,
225                         "xsltSort: select didn't evaluate to a string\n");
226 #endif
227                     results[i] = NULL;
228                 }
229             }
230         }
231     }
232
233     xsltSortFunction(list, &results[0], descending, number);
234
235 error:
236     if (xpathParserCtxt != NULL)
237         xmlXPathFreeParserContext(xpathParserCtxt);
238     if (prop != NULL)
239         xmlFree(prop);
240     if (results != NULL) {
241         for (i = 0;i < len;i++)
242             xmlXPathFreeObject(results[i]);
243         xmlFree(results);
244     }
245 }
246
247 /**
248  * xsltComment:
249  * @ctxt:  a XSLT process context
250  * @node:  the node in the source tree.
251  * @inst:  the xslt comment node
252  *
253  * Process the xslt comment node on the source node
254  */
255 void
256 xsltComment(xsltTransformContextPtr ctxt, xmlNodePtr node,
257                    xmlNodePtr inst) {
258     xmlChar *value = NULL;
259     xmlNodePtr comment;
260
261     value = xsltEvalTemplateString(ctxt, node, inst);
262     /* TODO: check that there is no -- sequence and doesn't end up with - */
263 #ifdef DEBUG_PROCESS
264     if (value == NULL)
265         xsltGenericDebug(xsltGenericDebugContext,
266              "xsl:comment: empty\n");
267     else
268         xsltGenericDebug(xsltGenericDebugContext,
269              "xsl:comment: content %s\n", value);
270 #endif
271
272     comment = xmlNewComment(value);
273     xmlAddChild(ctxt->insert, comment);
274
275     if (value != NULL)
276         xmlFree(value);
277 }
278
279 /**
280  * xsltProcessingInstruction:
281  * @ctxt:  a XSLT process context
282  * @node:  the node in the source tree.
283  * @inst:  the xslt processing-instruction node
284  *
285  * Process the xslt processing-instruction node on the source node
286  */
287 void
288 xsltProcessingInstruction(xsltTransformContextPtr ctxt, xmlNodePtr node,
289                    xmlNodePtr inst) {
290     xmlChar *ncname = NULL;
291     xmlChar *value = NULL;
292     xmlNodePtr pi;
293
294
295     if (ctxt->insert == NULL)
296         return;
297     ncname = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *)"name");
298     if (ncname == NULL) {
299         xsltGenericError(xsltGenericErrorContext,
300              "xslt:processing-instruction : name is missing\n");
301         goto error;
302     }
303     /* TODO: check that it's both an an NCName and a PITarget. */
304
305
306     value = xsltEvalTemplateString(ctxt, node, inst);
307     /* TODO: check that there is no ?> sequence */
308 #ifdef DEBUG_PROCESS
309     if (value == NULL)
310         xsltGenericDebug(xsltGenericDebugContext,
311              "xsl:processing-instruction: %s empty\n", ncname);
312     else
313         xsltGenericDebug(xsltGenericDebugContext,
314              "xsl:processing-instruction: %s content %s\n", ncname, value);
315 #endif
316
317     pi = xmlNewPI(ncname, value);
318     xmlAddChild(ctxt->insert, pi);
319
320 error:
321     if (ncname != NULL)
322         xmlFree(ncname);
323     if (value != NULL)
324         xmlFree(value);
325 }
326
327 /**
328  * xsltAttribute:
329  * @ctxt:  a XSLT process context
330  * @node:  the node in the source tree.
331  * @inst:  the xslt attribute node
332  *
333  * Process the xslt attribute node on the source node
334  */
335 void
336 xsltAttribute(xsltTransformContextPtr ctxt, xmlNodePtr node,
337                    xmlNodePtr inst) {
338     xmlChar *prop = NULL;
339     xmlChar *ncname = NULL;
340     xmlChar *prefix = NULL;
341     xmlChar *value = NULL;
342     xmlNsPtr ns = NULL;
343     xmlAttrPtr attr;
344
345
346     if (ctxt->insert == NULL)
347         return;
348     if (ctxt->insert->children != NULL) {
349         xsltGenericError(xsltGenericErrorContext,
350              "xslt:attribute : node has already children\n");
351         return;
352     }
353     prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *)"name");
354     if (prop == NULL) {
355         xsltGenericError(xsltGenericErrorContext,
356              "xslt:attribute : name is missing\n");
357         goto error;
358     }
359
360     ncname = xmlSplitQName2(prop, &prefix);
361     if (ncname == NULL) {
362         ncname = prop;
363         prop = NULL;
364         prefix = NULL;
365     }
366     if (xmlStrEqual(ncname, (const xmlChar *) "xmlns")) {
367         xsltGenericError(xsltGenericErrorContext,
368              "xslt:attribute : xmlns forbidden\n");
369         goto error;
370     }
371     prop = xsltEvalAttrValueTemplate(ctxt, inst, (const xmlChar *)"namespace");
372     if (prop != NULL) {
373         TODO /* xsl:attribute namespace */
374         xmlFree(prop);
375         return;
376     } else {
377         if (prefix != NULL) {
378             ns = xmlSearchNs(inst->doc, inst, prefix);
379             if (ns == NULL) {
380                 xsltGenericError(xsltGenericErrorContext,
381                     "no namespace bound to prefix %s\n", prefix);
382             } else {
383                 ns = xsltGetNamespace(ctxt, inst, ns, ctxt->insert);
384             }
385         }
386     }
387     
388
389     value = xsltEvalTemplateString(ctxt, node, inst);
390     if (value == NULL) {
391         if (ns) {
392             attr = xmlSetNsProp(ctxt->insert, ns, ncname, 
393                                 (const xmlChar *)"");
394         } else
395             attr = xmlSetProp(ctxt->insert, ncname, (const xmlChar *)"");
396     } else {
397         if (ns) {
398             attr = xmlSetNsProp(ctxt->insert, ns, ncname, value);
399         } else
400             attr = xmlSetProp(ctxt->insert, ncname, value);
401         
402     }
403
404 error:
405     if (prop != NULL)
406         xmlFree(prop);
407     if (ncname != NULL)
408         xmlFree(ncname);
409     if (prefix != NULL)
410         xmlFree(prefix);
411     if (value != NULL)
412         xmlFree(value);
413 }
414
415 /**
416  * xsltValueOf:
417  * @ctxt:  a XSLT process context
418  * @node:  the node in the source tree.
419  * @inst:  the xsltValueOf node
420  *
421  * Process the xsltValueOf node on the source node
422  */
423 void
424 xsltValueOf(xsltTransformContextPtr ctxt, xmlNodePtr node,
425                    xmlNodePtr inst) {
426     xmlChar *prop;
427     int disableEscaping = 0;
428     xmlXPathObjectPtr res = NULL, tmp;
429     xmlXPathParserContextPtr xpathParserCtxt = NULL;
430     xmlNodePtr copy = NULL;
431
432     if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
433         return;
434
435     prop = xmlGetNsProp(inst, (const xmlChar *)"disable-output-escaping",
436                         XSLT_NAMESPACE);
437     if (prop != NULL) {
438         if (xmlStrEqual(prop, (const xmlChar *)"yes"))
439             disableEscaping = 1;
440         else if (xmlStrEqual(prop, (const xmlChar *)"no"))
441             disableEscaping = 0;
442         else 
443             xsltGenericError(xsltGenericErrorContext,
444                  "invalud value %s for disable-output-escaping\n", prop);
445
446         xmlFree(prop);
447         if (disableEscaping) {
448             TODO /* disable-output-escaping */
449         }
450     }
451     prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
452     if (prop == NULL) {
453         xsltGenericError(xsltGenericErrorContext,
454              "xsltValueOf: select is not defined\n");
455         return;
456     }
457 #ifdef DEBUG_PROCESS
458     xsltGenericDebug(xsltGenericDebugContext,
459          "xsltValueOf: select %s\n", prop);
460 #endif
461
462     if (ctxt->xpathCtxt == NULL) {
463         xmlXPathInit();
464         ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
465         if (ctxt->xpathCtxt == NULL)
466             goto error;
467         XSLT_REGISTER_VARIABLE_LOOKUP(ctxt);
468     }
469     xpathParserCtxt =
470         xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
471     if (xpathParserCtxt == NULL)
472         goto error;
473     ctxt->xpathCtxt->node = node;
474     valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
475     xmlXPathEvalExpr(xpathParserCtxt);
476     xmlXPathStringFunction(xpathParserCtxt, 1);
477     res = valuePop(xpathParserCtxt);
478     do {
479         tmp = valuePop(xpathParserCtxt);
480         if (tmp != NULL) {
481             xmlXPathFreeObject(tmp);
482         }
483     } while (tmp != NULL);
484     if (res != NULL) {
485         if (res->type == XPATH_STRING) {
486             copy = xmlNewText(res->stringval);
487             if (copy != NULL) {
488                 xmlAddChild(ctxt->insert, copy);
489             }
490         }
491     }
492     if (copy == NULL) {
493         xsltGenericError(xsltGenericErrorContext,
494             "xsltDefaultProcessOneNode: text copy failed\n");
495     }
496 #ifdef DEBUG_PROCESS
497     else
498         xsltGenericDebug(xsltGenericDebugContext,
499              "xsltValueOf: result %s\n", res->stringval);
500 #endif
501 error:
502     if (xpathParserCtxt != NULL) {
503         xmlXPathFreeParserContext(xpathParserCtxt);
504         xpathParserCtxt = NULL;
505     }
506     if (prop != NULL)
507         xmlFree(prop);
508     if (res != NULL)
509         xmlXPathFreeObject(res);
510 }
511
512 /**
513  * xsltCopyNode:
514  * @ctxt:  a XSLT process context
515  * @node:  the element node in the source tree.
516  * @insert:  the parent in the result tree.
517  *
518  * Make a copy of the element node @node
519  * and insert it as last child of @insert
520  *
521  * Returns a pointer to the new node, or NULL in case of error
522  */
523 xmlNodePtr
524 xsltCopyNode(xsltTransformContextPtr ctxt, xmlNodePtr node,
525              xmlNodePtr insert) {
526     xmlNodePtr copy;
527
528     copy = xmlCopyNode(node, 0);
529     copy->doc = ctxt->output;
530     if (copy != NULL) {
531         xmlAddChild(insert, copy);
532         /*
533          * Add namespaces as they are needed
534          */
535         if (node->nsDef != NULL)
536             xsltCopyNamespaceList(ctxt, copy, node->nsDef);
537         if (node->ns != NULL) {
538             copy->ns = xsltGetNamespace(ctxt, node, node->ns, insert);
539         }
540     } else {
541         xsltGenericError(xsltGenericErrorContext,
542                 "xsltCopyNode: copy %s failed\n", node->name);
543     }
544     return(copy);
545 }
546
547 /**
548  * xsltDefaultProcessOneNode:
549  * @ctxt:  a XSLT process context
550  * @node:  the node in the source tree.
551  *
552  * Process the source node with the default built-in template rule:
553  * <xsl:template match="*|/">
554  *   <xsl:apply-templates/>
555  * </xsl:template>
556  *
557  * and
558  *
559  * <xsl:template match="text()|@*">
560  *   <xsl:value-of select="."/>
561  * </xsl:template>
562  *
563  * Note also that namespaces declarations are copied directly:
564  *
565  * the built-in template rule is the only template rule that is applied
566  * for namespace nodes.
567  */
568 void
569 xsltDefaultProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) {
570     xmlNodePtr copy;
571     xmlNodePtr delete = NULL;
572     int strip_spaces = -1;
573
574     switch (node->type) {
575         case XML_DOCUMENT_NODE:
576         case XML_HTML_DOCUMENT_NODE:
577         case XML_ELEMENT_NODE:
578             break;
579         case XML_TEXT_NODE:
580             copy = xmlCopyNode(node, 0);
581             if (copy != NULL) {
582                 xmlAddChild(ctxt->insert, copy);
583             } else {
584                 xsltGenericError(xsltGenericErrorContext,
585                     "xsltDefaultProcessOneNode: text copy failed\n");
586             }
587             return;
588         default:
589             return;
590     }
591     node = node->children;
592     while (node != NULL) {
593         switch (node->type) {
594             case XML_DOCUMENT_NODE:
595             case XML_HTML_DOCUMENT_NODE:
596             case XML_ELEMENT_NODE:
597                 xsltProcessOneNode(ctxt, node);
598                 break;
599             case XML_TEXT_NODE:
600                 /* TODO: check the whitespace stripping rules ! */
601                 if ((IS_BLANK_NODE(node)) &&
602                     (node->parent != NULL) &&
603                     (ctxt->style->stripSpaces != NULL)) {
604                     const xmlChar *val;
605
606                     if (strip_spaces == -1) {
607                         /* TODO: add namespaces support */
608                         val = (const xmlChar *)
609                               xmlHashLookup(ctxt->style->stripSpaces,
610                                             node->parent->name);
611                         if (val != NULL) {
612                             if (xmlStrEqual(val, (xmlChar *) "strip"))
613                                 strip_spaces = 1;
614                             if (xmlStrEqual(val, (xmlChar *) "preserve"))
615                                 strip_spaces = 0;
616                         } 
617                         if (strip_spaces == -1) {
618                             val = (const xmlChar *)
619                                   xmlHashLookup(ctxt->style->stripSpaces,
620                                                 (const xmlChar *)"*");
621                             if ((val != NULL) &&
622                                 (xmlStrEqual(val, (xmlChar *) "strip")))
623                                 strip_spaces = 1;
624                             else
625                                 strip_spaces = 0;
626                         }
627                     }
628                     if (strip_spaces == 1) {
629                         delete = node;
630                         break;
631                     }
632                 }
633                 /* no break on purpose */
634             case XML_CDATA_SECTION_NODE:
635                 copy = xmlCopyNode(node, 0);
636                 if (copy != NULL) {
637                     xmlAddChild(ctxt->insert, copy);
638                 } else {
639                     xsltGenericError(xsltGenericErrorContext,
640                         "xsltDefaultProcessOneNode: text copy failed\n");
641                 }
642                 break;
643             default:
644 #ifdef DEBUG_PROCESS
645                 xsltGenericDebug(xsltGenericDebugContext,
646                  "xsltDefaultProcessOneNode: skipping node type %d\n",
647                                  node->type);
648 #endif
649                 delete = node;
650         }
651         node = node->next;
652         if (delete != NULL) {
653 #ifdef DEBUG_PROCESS
654             xsltGenericDebug(xsltGenericDebugContext,
655                  "xsltDefaultProcessOneNode: removing ignorable blank node\n");
656 #endif
657             xmlUnlinkNode(delete);
658             xmlFreeNode(delete);
659             delete = NULL;
660         }
661     }
662 }
663
664 /**
665  * xsltCallTemplate:
666  * @ctxt:  a XSLT process context
667  * @node:  the node in the source tree.
668  * @inst:  the xslt call-template node
669  *
670  * Process the xslt call-template node on the source node
671  */
672 void
673 xsltCallTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node,
674                    xmlNodePtr inst) {
675     xmlChar *prop = NULL;
676     xmlChar *ncname = NULL;
677     xmlChar *prefix = NULL;
678     xmlNsPtr ns = NULL;
679     xsltTemplatePtr template;
680     xmlNodePtr cur = NULL;
681     int has_param = 0;
682
683
684     if (ctxt->insert == NULL)
685         return;
686     prop = xmlGetNsProp(inst, (const xmlChar *)"name", XSLT_NAMESPACE);
687     if (prop == NULL) {
688         xsltGenericError(xsltGenericErrorContext,
689              "xslt:call-template : name is missing\n");
690         goto error;
691     }
692
693     ncname = xmlSplitQName2(prop, &prefix);
694     if (ncname == NULL) {
695         ncname = prop;
696         prop = NULL;
697         prefix = NULL;
698     }
699     if ((prefix != NULL) && (ns == NULL)) {
700         ns = xmlSearchNs(ctxt->insert->doc, ctxt->insert, prefix);
701         if (ns == NULL) {
702             xsltGenericError(xsltGenericErrorContext,
703                 "no namespace bound to prefix %s\n", prefix);
704         }
705     }
706     if (ns != NULL)
707         template = xsltFindTemplate(ctxt->style, ncname, ns->href);
708     else
709         template = xsltFindTemplate(ctxt->style, ncname, NULL);
710     if (template == NULL) {
711         xsltGenericError(xsltGenericDebugContext,
712              "xslt:call-template: template %s not found\n", cur->name);
713         goto error;
714     }
715     cur = inst->children;
716     while (cur != NULL) {
717         if (IS_XSLT_ELEM(cur)) {
718             if (IS_XSLT_NAME(cur, "with-param")) {
719                 if (has_param == 0) {
720                     xsltPushStack(ctxt);
721                     has_param = 1;
722                 }
723                 xsltParseStylesheetParam(ctxt, cur);
724             } else {
725                 xsltGenericError(xsltGenericDebugContext,
726                      "xslt:call-template: misplaced xslt:%s\n", cur->name);
727             }
728         } else {
729             xsltGenericError(xsltGenericDebugContext,
730                  "xslt:call-template: misplaced %s element\n", cur->name);
731         }
732         cur = cur->next;
733     }
734     xsltApplyOneTemplate(ctxt, node, template->content);
735
736 error:
737     if (has_param == 1)
738         xsltPopStack(ctxt);
739     if (prop != NULL)
740         xmlFree(prop);
741     if (ncname != NULL)
742         xmlFree(ncname);
743     if (prefix != NULL)
744         xmlFree(prefix);
745 }
746
747 /**
748  * xsltApplyTemplates:
749  * @ctxt:  a XSLT process context
750  * @node:  the node in the source tree.
751  * @inst:  the apply-templates node
752  *
753  * Process the apply-templates node on the source node
754  */
755 void
756 xsltApplyTemplates(xsltTransformContextPtr ctxt, xmlNodePtr node,
757                    xmlNodePtr inst) {
758     xmlChar *prop = NULL;
759     xmlNodePtr cur, delete = NULL;
760     xmlXPathObjectPtr res = NULL, tmp;
761     xmlNodePtr replacement;
762     xmlNodeSetPtr list = NULL, oldlist;
763     xmlXPathParserContextPtr xpathParserCtxt = NULL;
764     int i, oldProximityPosition, oldContextSize;
765
766     if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
767         return;
768
769 #ifdef DEBUG_PROCESS
770     xsltGenericDebug(xsltGenericDebugContext,
771          "xsltApplyTemplates: node: %s\n", node->name);
772 #endif
773     prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
774     if (prop != NULL) {
775 #ifdef DEBUG_PROCESS
776         xsltGenericDebug(xsltGenericDebugContext,
777              "xsltApplyTemplates: select %s\n", prop);
778 #endif
779
780         if (ctxt->xpathCtxt == NULL) {
781             xmlXPathInit();
782             ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
783             if (ctxt->xpathCtxt == NULL)
784                 goto error;
785             XSLT_REGISTER_VARIABLE_LOOKUP(ctxt);
786         }
787         xpathParserCtxt =
788             xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
789         if (xpathParserCtxt == NULL)
790             goto error;
791         ctxt->xpathCtxt->node = node;
792         valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
793         xmlXPathEvalExpr(xpathParserCtxt);
794         res = valuePop(xpathParserCtxt);
795         do {
796             tmp = valuePop(xpathParserCtxt);
797             if (tmp != NULL) {
798                 xmlXPathFreeObject(tmp);
799             }
800         } while (tmp != NULL);
801         if (res != NULL) {
802             if (res->type == XPATH_NODESET) {
803                 list = res->nodesetval;
804                 res->nodesetval = NULL;
805              } else {
806 #ifdef DEBUG_PROCESS
807                 xsltGenericDebug(xsltGenericDebugContext,
808                     "xsltApplyTemplates: select didn't evaluate to a node list\n");
809 #endif
810                 goto error;
811             }
812         }
813     } else {
814         /*
815          * Build an XPath nodelist with the children
816          */
817         list = xmlXPathNodeSetCreate(NULL);
818         cur = node->children;
819         while (cur != NULL) {
820             switch (cur->type) {
821                 case XML_TEXT_NODE:
822                     /* TODO: check the whitespace stripping rules ! */
823                     if ((IS_BLANK_NODE(cur)) &&
824                         (cur->parent != NULL) &&
825                         (ctxt->style->stripSpaces != NULL)) {
826                         const xmlChar *val;
827
828                         val = (const xmlChar *)
829                               xmlHashLookup(ctxt->style->stripSpaces,
830                                             cur->parent->name);
831                         if ((val != NULL) &&
832                             (xmlStrEqual(val, (xmlChar *) "strip"))) {
833                             delete = cur;
834                             break;
835                         }
836                     }
837                     /* no break on purpose */
838                 case XML_DOCUMENT_NODE:
839                 case XML_HTML_DOCUMENT_NODE:
840                 case XML_ELEMENT_NODE:
841                 case XML_CDATA_SECTION_NODE:
842                     xmlXPathNodeSetAdd(list, cur);
843                     break;
844                 default:
845 #ifdef DEBUG_PROCESS
846                     xsltGenericDebug(xsltGenericDebugContext,
847                      "xsltApplyTemplates: skipping cur type %d\n",
848                                      cur->type);
849 #endif
850                     delete = cur;
851             }
852             cur = cur->next;
853             if (delete != NULL) {
854 #ifdef DEBUG_PROCESS
855                 xsltGenericDebug(xsltGenericDebugContext,
856                      "xsltApplyTemplates: removing ignorable blank cur\n");
857 #endif
858                 xmlUnlinkNode(delete);
859                 xmlFreeNode(delete);
860                 delete = NULL;
861             }
862         }
863     }
864
865 #ifdef DEBUG_PROCESS
866     xsltGenericDebug(xsltGenericDebugContext,
867         "xsltApplyTemplates: list of %d nodes\n", list->nodeNr);
868 #endif
869
870     oldlist = ctxt->nodeList;
871     ctxt->nodeList = list;
872     oldContextSize = ctxt->xpathCtxt->contextSize;
873     oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
874     ctxt->xpathCtxt->contextSize = list->nodeNr;
875
876     /* 
877      * handle and skip the xsl:sort
878      */
879     replacement = inst->children;
880     while (IS_XSLT_ELEM(replacement) && (IS_XSLT_NAME(replacement, "sort"))) {
881         xsltSort(ctxt, node, replacement);
882         replacement = replacement->next;
883     }
884
885     for (i = 0;i < list->nodeNr;i++) {
886         ctxt->node = list->nodeTab[i];
887         ctxt->xpathCtxt->proximityPosition = i + 1;
888         xsltProcessOneNode(ctxt, list->nodeTab[i]);
889     }
890     ctxt->nodeList = oldlist;
891     ctxt->xpathCtxt->contextSize = oldContextSize;
892     ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
893
894 error:
895     if (xpathParserCtxt != NULL)
896         xmlXPathFreeParserContext(xpathParserCtxt);
897     if (prop != NULL)
898         xmlFree(prop);
899     if (res != NULL)
900         xmlXPathFreeObject(res);
901     if (list != NULL)
902         xmlXPathFreeNodeSet(list);
903 }
904
905 /**
906  * xsltApplyOneTemplate:
907  * @ctxt:  a XSLT process context
908  * @node:  the node in the source tree.
909  * @list:  the template replacement nodelist
910  *
911  * Process the apply-templates node on the source node
912  */
913 void
914 xsltApplyOneTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node,
915                      xmlNodePtr list) {
916     xmlNodePtr cur = NULL, insert, copy = NULL;
917     xmlNodePtr oldInsert;
918     int has_variables = 0;
919
920     oldInsert = insert = ctxt->insert;
921     /*
922      * Insert all non-XSLT nodes found in the template
923      */
924     cur = list;
925     while (cur != NULL) {
926         /*
927          * test, we must have a valid insertion point
928          */
929         if (insert == NULL) {
930 #ifdef DEBUG_PROCESS
931             xsltGenericDebug(xsltGenericDebugContext,
932                  "xsltApplyOneTemplate: insert == NULL !\n");
933 #endif
934             return;
935         }
936
937         if (IS_XSLT_ELEM(cur)) {
938             if (IS_XSLT_NAME(cur, "apply-templates")) {
939                 ctxt->insert = insert;
940                 xsltApplyTemplates(ctxt, node, cur);
941                 ctxt->insert = oldInsert;
942             } else if (IS_XSLT_NAME(cur, "value-of")) {
943                 ctxt->insert = insert;
944                 xsltValueOf(ctxt, node, cur);
945                 ctxt->insert = oldInsert;
946             } else if (IS_XSLT_NAME(cur, "if")) {
947                 ctxt->insert = insert;
948                 xsltIf(ctxt, node, cur);
949                 ctxt->insert = oldInsert;
950             } else if (IS_XSLT_NAME(cur, "for-each")) {
951                 ctxt->insert = insert;
952                 xsltForEach(ctxt, node, cur);
953                 ctxt->insert = oldInsert;
954             } else if (IS_XSLT_NAME(cur, "attribute")) {
955                 ctxt->insert = insert;
956                 xsltAttribute(ctxt, node, cur);
957                 ctxt->insert = oldInsert;
958                 /*******
959             } else if (IS_XSLT_NAME(cur, "element")) {
960                 ctxt->insert = insert;
961                 xsltElement(ctxt, node, cur);
962                 ctxt->insert = oldInsert;
963                 *******/
964             } else if (IS_XSLT_NAME(cur, "comment")) {
965                 ctxt->insert = insert;
966                 xsltComment(ctxt, node, cur);
967                 ctxt->insert = oldInsert;
968             } else if (IS_XSLT_NAME(cur, "processing-instruction")) {
969                 ctxt->insert = insert;
970                 xsltProcessingInstruction(ctxt, node, cur);
971                 ctxt->insert = oldInsert;
972             } else if (IS_XSLT_NAME(cur, "variable")) {
973                 if (has_variables == 0) {
974                     xsltPushStack(ctxt);
975                     has_variables = 1;
976                 }
977                 xsltParseStylesheetVariable(ctxt, cur);
978             } else if (IS_XSLT_NAME(cur, "param")) {
979                 if (has_variables == 0) {
980                     xsltPushStack(ctxt);
981                     has_variables = 1;
982                 }
983                 xsltParseStylesheetParam(ctxt, cur);
984             } else if (IS_XSLT_NAME(cur, "call-template")) {
985                 if (has_variables == 0) {
986                     xsltPushStack(ctxt);
987                     has_variables = 1;
988                 }
989                 xsltCallTemplate(ctxt, node, cur);
990             } else {
991 #ifdef DEBUG_PROCESS
992                 xsltGenericError(xsltGenericDebugContext,
993                      "xsltApplyOneTemplate: found xslt:%s\n", cur->name);
994 #endif
995                 TODO
996             }
997             goto skip_children;
998         } else if (cur->type == XML_TEXT_NODE) {
999             /*
1000              * This text comes from the stylesheet
1001              * For stylesheets, the set of whitespace-preserving
1002              * element names consists of just xsl:text.
1003              */
1004 #ifdef DEBUG_PROCESS
1005             xsltGenericDebug(xsltGenericDebugContext,
1006                  "xsltApplyOneTemplate: copy text %s\n", cur->content);
1007 #endif
1008             copy = xmlCopyNode(cur, 0);
1009             if (copy != NULL) {
1010                 xmlAddChild(insert, copy);
1011             } else {
1012                 xsltGenericError(xsltGenericErrorContext,
1013                         "xsltApplyOneTemplate: text copy failed\n");
1014             }
1015         } else if (cur->type == XML_ELEMENT_NODE) {
1016 #ifdef DEBUG_PROCESS
1017             xsltGenericDebug(xsltGenericDebugContext,
1018                  "xsltApplyOneTemplate: copy node %s\n", cur->name);
1019 #endif
1020             copy = xsltCopyNode(ctxt, cur, insert);
1021             /*
1022              * all the attributes are directly inherited
1023              * TODO: Do the substitution of {} XPath expressions !!!
1024              */
1025             if (cur->properties != NULL)
1026                 copy->properties = xsltAttrListTemplateProcess(ctxt,
1027                                                copy, cur->properties);
1028         }
1029
1030         /*
1031          * Skip to next node, in document order.
1032          */
1033         if (cur->children != NULL) {
1034             if (cur->children->type != XML_ENTITY_DECL) {
1035                 cur = cur->children;
1036                 if (copy != NULL)
1037                     insert = copy;
1038                 continue;
1039             }
1040         }
1041 skip_children:
1042         if (cur->next != NULL) {
1043             cur = cur->next;
1044             continue;
1045         }
1046         
1047         do {
1048             cur = cur->parent;
1049             insert = insert->parent;
1050             if (cur == NULL)
1051                 break;
1052             if (cur == list->parent) {
1053                 cur = NULL;
1054                 break;
1055             }
1056             if (cur->next != NULL) {
1057                 cur = cur->next;
1058                 break;
1059             }
1060         } while (cur != NULL);
1061     }
1062     if (has_variables != 0) {
1063         xsltPopStack(ctxt);
1064     }
1065 }
1066
1067 /**
1068  * xsltIf:
1069  * @ctxt:  a XSLT process context
1070  * @node:  the node in the source tree.
1071  * @inst:  the xslt if node
1072  *
1073  * Process the xslt if node on the source node
1074  */
1075 void
1076 xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr node,
1077                    xmlNodePtr inst) {
1078     xmlChar *prop;
1079     xmlXPathObjectPtr res = NULL, tmp;
1080     xmlXPathParserContextPtr xpathParserCtxt = NULL;
1081     int doit = 1;
1082
1083     if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
1084         return;
1085
1086     prop = xmlGetNsProp(inst, (const xmlChar *)"test", XSLT_NAMESPACE);
1087     if (prop == NULL) {
1088         xsltGenericError(xsltGenericErrorContext,
1089              "xsltIf: test is not defined\n");
1090         return;
1091     }
1092 #ifdef DEBUG_PROCESS
1093     xsltGenericDebug(xsltGenericDebugContext,
1094          "xsltIf: test %s\n", prop);
1095 #endif
1096
1097     if (ctxt->xpathCtxt == NULL) {
1098         xmlXPathInit();
1099         ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
1100         if (ctxt->xpathCtxt == NULL)
1101             goto error;
1102         XSLT_REGISTER_VARIABLE_LOOKUP(ctxt);
1103     }
1104     xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
1105     if (xpathParserCtxt == NULL)
1106         goto error;
1107     ctxt->xpathCtxt->node = node;
1108     valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
1109     xmlXPathEvalExpr(xpathParserCtxt);
1110     xmlXPathBooleanFunction(xpathParserCtxt, 1);
1111     res = valuePop(xpathParserCtxt);
1112     do {
1113         tmp = valuePop(xpathParserCtxt);
1114         if (tmp != NULL) {
1115             xmlXPathFreeObject(tmp);
1116         }
1117     } while (tmp != NULL);
1118
1119     if (res != NULL) {
1120         if (res->type == XPATH_BOOLEAN)
1121             doit = res->boolval;
1122         else {
1123 #ifdef DEBUG_PROCESS
1124             xsltGenericDebug(xsltGenericDebugContext,
1125                 "xsltIf: test didn't evaluate to a boolean\n");
1126 #endif
1127             goto error;
1128         }
1129     }
1130
1131 #ifdef DEBUG_PROCESS
1132     xsltGenericDebug(xsltGenericDebugContext,
1133         "xsltIf: test evaluate to %d\n", doit);
1134 #endif
1135     if (doit) {
1136         xsltApplyOneTemplate(ctxt, ctxt->node, inst->children);
1137     }
1138
1139 error:
1140     if (xpathParserCtxt != NULL)
1141         xmlXPathFreeParserContext(xpathParserCtxt);
1142     if (prop != NULL)
1143         xmlFree(prop);
1144     if (res != NULL)
1145         xmlXPathFreeObject(res);
1146 }
1147
1148 /**
1149  * xsltForEach:
1150  * @ctxt:  a XSLT process context
1151  * @node:  the node in the source tree.
1152  * @inst:  the xslt for-each node
1153  *
1154  * Process the xslt for-each node on the source node
1155  */
1156 void
1157 xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr node,
1158                    xmlNodePtr inst) {
1159     xmlChar *prop;
1160     xmlXPathObjectPtr res = NULL, tmp;
1161     xmlNodePtr replacement;
1162     xmlNodeSetPtr list = NULL, oldlist;
1163     xmlXPathParserContextPtr xpathParserCtxt = NULL;
1164     int i, oldProximityPosition, oldContextSize;
1165
1166     if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
1167         return;
1168
1169     prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
1170     if (prop == NULL) {
1171         xsltGenericError(xsltGenericErrorContext,
1172              "xsltForEach: select is not defined\n");
1173         return;
1174     }
1175 #ifdef DEBUG_PROCESS
1176     xsltGenericDebug(xsltGenericDebugContext,
1177          "xsltForEach: select %s\n", prop);
1178 #endif
1179
1180     if (ctxt->xpathCtxt == NULL) {
1181         xmlXPathInit();
1182         ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
1183         if (ctxt->xpathCtxt == NULL)
1184             goto error;
1185         XSLT_REGISTER_VARIABLE_LOOKUP(ctxt);
1186     }
1187     xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
1188     if (xpathParserCtxt == NULL)
1189         goto error;
1190     ctxt->xpathCtxt->node = node;
1191     valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
1192     xmlXPathEvalExpr(xpathParserCtxt);
1193     res = valuePop(xpathParserCtxt);
1194     do {
1195         tmp = valuePop(xpathParserCtxt);
1196         if (tmp != NULL) {
1197             xmlXPathFreeObject(tmp);
1198         }
1199     } while (tmp != NULL);
1200
1201     if (res != NULL) {
1202         if (res->type == XPATH_NODESET)
1203             list = res->nodesetval;
1204         else {
1205 #ifdef DEBUG_PROCESS
1206             xsltGenericDebug(xsltGenericDebugContext,
1207                 "xsltForEach: select didn't evaluate to a node list\n");
1208 #endif
1209             goto error;
1210         }
1211     }
1212
1213 #ifdef DEBUG_PROCESS
1214     xsltGenericDebug(xsltGenericDebugContext,
1215         "xsltForEach: select evaluate to %d nodes\n", list->nodeNr);
1216 #endif
1217
1218     oldlist = ctxt->nodeList;
1219     ctxt->nodeList = list;
1220     oldContextSize = ctxt->xpathCtxt->contextSize;
1221     oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
1222     ctxt->xpathCtxt->contextSize = list->nodeNr;
1223
1224     /* 
1225      * handle and skip the xsl:sort
1226      */
1227     replacement = inst->children;
1228     while (IS_XSLT_ELEM(replacement) && (IS_XSLT_NAME(replacement, "sort"))) {
1229         xsltSort(ctxt, node, replacement);
1230         replacement = replacement->next;
1231     }
1232
1233     for (i = 0;i < list->nodeNr;i++) {
1234         ctxt->node = list->nodeTab[i];
1235         ctxt->xpathCtxt->proximityPosition = i + 1;
1236         xsltApplyOneTemplate(ctxt, list->nodeTab[i], replacement);
1237     }
1238     ctxt->nodeList = oldlist;
1239     ctxt->xpathCtxt->contextSize = oldContextSize;
1240     ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
1241
1242 error:
1243     if (xpathParserCtxt != NULL)
1244         xmlXPathFreeParserContext(xpathParserCtxt);
1245     if (prop != NULL)
1246         xmlFree(prop);
1247     if (res != NULL)
1248         xmlXPathFreeObject(res);
1249 }
1250
1251 /**
1252  * xsltProcessOneNode:
1253  * @ctxt:  a XSLT process context
1254  * @node:  the node in the source tree.
1255  *
1256  * Process the source node.
1257  */
1258 void
1259 xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) {
1260     xsltTemplatePtr template;
1261
1262     template = xsltGetTemplate(ctxt->style, node);
1263     /*
1264      * If no template is found, apply the default rule.
1265      */
1266     if (template == NULL) {
1267 #ifdef DEBUG_PROCESS
1268         if (node->type == XML_DOCUMENT_NODE)
1269             xsltGenericDebug(xsltGenericDebugContext,
1270              "xsltProcessOneNode: no template found for /\n");
1271         else 
1272             xsltGenericDebug(xsltGenericDebugContext,
1273              "xsltProcessOneNode: no template found for %s\n", node->name);
1274 #endif
1275         xsltDefaultProcessOneNode(ctxt, node);
1276         return;
1277     }
1278
1279     xsltApplyOneTemplate(ctxt, node, template->content);
1280 }
1281
1282 /**
1283  * xsltApplyStylesheet:
1284  * @style:  a parsed XSLT stylesheet
1285  * @doc:  a parsed XML document
1286  *
1287  * Apply the stylesheet to the document
1288  * NOTE: This may lead to a non-wellformed output XML wise !
1289  *
1290  * Returns the result document or NULL in case of error
1291  */
1292 xmlDocPtr
1293 xsltApplyStylesheet(xsltStylesheetPtr style, xmlDocPtr doc) {
1294     xmlDocPtr res = NULL;
1295     xsltTransformContextPtr ctxt = NULL;
1296     xmlNodePtr root;
1297
1298     if ((style == NULL) || (doc == NULL))
1299         return(NULL);
1300     ctxt = xsltNewTransformContext();
1301     if (ctxt == NULL)
1302         return(NULL);
1303     ctxt->doc = doc;
1304     ctxt->style = style;
1305     xsltEvalGlobalVariables(ctxt);
1306     if ((style->method != NULL) &&
1307         (!xmlStrEqual(style->method, (const xmlChar *) "xml"))) {
1308         if (xmlStrEqual(style->method, (const xmlChar *) "html")) {
1309             ctxt->type = XSLT_OUTPUT_HTML;
1310             res = htmlNewDoc(style->doctypePublic, style->doctypeSystem);
1311             if (res == NULL)
1312                 goto error;
1313         } else if (xmlStrEqual(style->method, (const xmlChar *) "text")) {
1314             ctxt->type = XSLT_OUTPUT_TEXT;
1315             res = xmlNewDoc(style->version);
1316             if (res == NULL)
1317                 goto error;
1318         } else {
1319             xsltGenericError(xsltGenericErrorContext,
1320                              "xsltApplyStylesheet: insupported method %s\n",
1321                              style->method);
1322             goto error;
1323         }
1324     } else {
1325         ctxt->type = XSLT_OUTPUT_XML;
1326         res = xmlNewDoc(style->version);
1327         if (res == NULL)
1328             goto error;
1329     }
1330     res->charset = XML_CHAR_ENCODING_UTF8;
1331     if (style->encoding != NULL)
1332         res->encoding = xmlStrdup(style->encoding);
1333
1334     /*
1335      * Start.
1336      */
1337     ctxt->output = res;
1338     ctxt->insert = (xmlNodePtr) res;
1339     ctxt->node = (xmlNodePtr) doc;
1340     xsltProcessOneNode(ctxt, ctxt->node);
1341
1342
1343     if ((ctxt->type = XSLT_OUTPUT_XML) &&
1344         ((style->doctypePublic != NULL) ||
1345          (style->doctypeSystem != NULL))) {
1346         root = xmlDocGetRootElement(res);
1347         if (root != NULL)
1348             res->intSubset = xmlCreateIntSubset(res, root->name,
1349                          style->doctypePublic, style->doctypeSystem);
1350     }
1351     xmlXPathFreeNodeSet(ctxt->nodeList);
1352     xsltFreeTransformContext(ctxt);
1353     return(res);
1354
1355 error:
1356     if (res != NULL)
1357         xmlFreeDoc(res);
1358     if (ctxt != NULL)
1359         xsltFreeTransformContext(ctxt);
1360     return(NULL);
1361 }
1362