copied over the apibuild.py from libxml2, adapted a bit and regenerated
[platform/upstream/libxslt.git] / libxslt / variables.c
1 /*
2  * variables.c: Implementation of the variable storage and lookup
3  *
4  * Reference:
5  *   http://www.w3.org/TR/1999/REC-xslt-19991116
6  *
7  * See Copyright for the status of this software.
8  *
9  * daniel@veillard.com
10  */
11
12 #define IN_LIBXSLT
13 #include "libxslt.h"
14
15 #include <string.h>
16
17 #include <libxml/xmlmemory.h>
18 #include <libxml/tree.h>
19 #include <libxml/valid.h>
20 #include <libxml/hash.h>
21 #include <libxml/xmlerror.h>
22 #include <libxml/xpath.h>
23 #include <libxml/xpathInternals.h>
24 #include <libxml/parserInternals.h>
25 #include "xslt.h"
26 #include "xsltInternals.h"
27 #include "xsltutils.h"
28 #include "variables.h"
29 #include "transform.h"
30 #include "imports.h"
31 #include "preproc.h"
32
33 #ifdef WITH_XSLT_DEBUG
34 #define WITH_XSLT_DEBUG_VARIABLE
35 #endif
36
37 /************************************************************************
38  *                                                                      *
39  *                      Module interfaces                               *
40  *                                                                      *
41  ************************************************************************/
42
43 /**
44  * xsltNewStackElem:
45  *
46  * Create a new XSLT ParserContext
47  *
48  * Returns the newly allocated xsltParserStackElem or NULL in case of error
49  */
50 static xsltStackElemPtr
51 xsltNewStackElem(void) {
52     xsltStackElemPtr cur;
53
54     cur = (xsltStackElemPtr) xmlMalloc(sizeof(xsltStackElem));
55     if (cur == NULL) {
56         xsltTransformError(NULL, NULL, NULL,
57                 "xsltNewStackElem : malloc failed\n");
58         return(NULL);
59     }
60     cur->computed = 0;
61     cur->name = NULL;
62     cur->nameURI = NULL;
63     cur->select = NULL;
64     cur->tree = NULL;
65     cur->value = NULL;
66     cur->comp = NULL;
67     return(cur);
68 }
69
70 /**
71  * xsltCopyStackElem:
72  * @elem:  an XSLT stack element
73  *
74  * Makes a copy of the stack element
75  *
76  * Returns the copy of NULL
77  */
78 static xsltStackElemPtr
79 xsltCopyStackElem(xsltStackElemPtr elem) {
80     xsltStackElemPtr cur;
81
82     cur = (xsltStackElemPtr) xmlMalloc(sizeof(xsltStackElem));
83     if (cur == NULL) {
84         xsltTransformError(NULL, NULL, NULL,
85                 "xsltCopyStackElem : malloc failed\n");
86         return(NULL);
87     }
88     cur->name = xmlStrdup(elem->name);
89     cur->nameURI = xmlStrdup(elem->nameURI);
90     cur->select = xmlStrdup(elem->select);
91     cur->tree = elem->tree;
92     cur->comp = elem->comp;
93     cur->computed = 0;
94     cur->value = NULL;
95     return(cur);
96 }
97
98 /**
99  * xsltFreeStackElem:
100  * @elem:  an XSLT stack element
101  *
102  * Free up the memory allocated by @elem
103  */
104 static void
105 xsltFreeStackElem(xsltStackElemPtr elem) {
106     if (elem == NULL)
107         return;
108     if (elem->name != NULL)
109         xmlFree(elem->name);
110     if (elem->nameURI != NULL)
111         xmlFree(elem->nameURI);
112     if (elem->select != NULL)
113         xmlFree(elem->select);
114     if (elem->value != NULL)
115         xmlXPathFreeObject(elem->value);
116
117     xmlFree(elem);
118 }
119
120 /**
121  * xsltFreeStackElemList:
122  * @elem:  an XSLT stack element
123  *
124  * Free up the memory allocated by @elem
125  */
126 void
127 xsltFreeStackElemList(xsltStackElemPtr elem) {
128     xsltStackElemPtr next;
129
130     while(elem != NULL) {
131         next = elem->next;
132         xsltFreeStackElem(elem);
133         elem = next;
134     }
135 }
136
137 /**
138  * xsltCheckStackElem:
139  * @ctxt:  xn XSLT transformation context
140  * @name:  the variable name
141  * @nameURI:  the variable namespace URI
142  *
143  * check wether the variable or param is already defined
144  *
145  * Returns 1 if variable is present, 2 if param is present, 3 if this
146  *         is an inherited param, 0 if not found, -1 in case of failure.
147  */
148 static int
149 xsltCheckStackElem(xsltTransformContextPtr ctxt, const xmlChar *name,
150                    const xmlChar *nameURI) {
151     xsltStackElemPtr cur;
152
153     if ((ctxt == NULL) || (name == NULL))
154         return(-1);
155
156     cur = ctxt->vars;
157     while (cur != NULL) {
158         if (xmlStrEqual(name, cur->name)) {
159             if (((nameURI == NULL) && (cur->nameURI == NULL)) ||
160                 ((nameURI != NULL) && (cur->nameURI != NULL) &&
161                  (xmlStrEqual(nameURI, cur->nameURI)))) {
162                 if ((cur->comp != NULL) &&
163                     (cur->comp->type == XSLT_FUNC_WITHPARAM))
164                     return(3);
165                 if ((cur->comp != NULL) &&
166                     (cur->comp->type == XSLT_FUNC_PARAM))
167                     return(2);
168                 return(1);
169             }
170         }
171         cur = cur->next;
172     }
173     return(0);
174 }
175
176 /**
177  * xsltAddStackElem:
178  * @ctxt:  xn XSLT transformation context
179  * @elem:  a stack element
180  *
181  * add a new element at this level of the stack.
182  *
183  * Returns 0 in case of success, -1 in case of failure.
184  */
185 static int
186 xsltAddStackElem(xsltTransformContextPtr ctxt, xsltStackElemPtr elem) {
187     if ((ctxt == NULL) || (elem == NULL))
188         return(-1);
189
190     elem->next = ctxt->varsTab[ctxt->varsNr - 1];
191     ctxt->varsTab[ctxt->varsNr - 1] = elem;
192     ctxt->vars = elem;
193     return(0);
194 }
195
196 /**
197  * xsltAddStackElemList:
198  * @ctxt:  xn XSLT transformation context
199  * @elems:  a stack element list
200  *
201  * add the new element list at this level of the stack.
202  *
203  * Returns 0 in case of success, -1 in case of failure.
204  */
205 int
206 xsltAddStackElemList(xsltTransformContextPtr ctxt, xsltStackElemPtr elems) {
207     xsltStackElemPtr cur;
208
209     if ((ctxt == NULL) || (elems == NULL))
210         return(-1);
211
212     /* TODO: check doublons */
213     if (ctxt->varsTab[ctxt->varsNr - 1] != NULL) {
214         cur = ctxt->varsTab[ctxt->varsNr - 1];
215         while (cur->next != NULL)
216             cur = cur->next;
217         cur->next = elems;
218     } else {
219         elems->next = ctxt->varsTab[ctxt->varsNr - 1];
220         ctxt->varsTab[ctxt->varsNr - 1] = elems;
221         ctxt->vars = elems;
222     }
223     return(0);
224 }
225
226 /**
227  * xsltStackLookup:
228  * @ctxt:  an XSLT transformation context
229  * @name:  the local part of the name
230  * @nameURI:  the URI part of the name
231  *
232  * Locate an element in the stack based on its name.
233  */
234 static xsltStackElemPtr
235 xsltStackLookup(xsltTransformContextPtr ctxt, const xmlChar *name,
236                 const xmlChar *nameURI) {
237     xsltStackElemPtr ret = NULL;
238     int i;
239     xsltStackElemPtr cur;
240
241     if ((ctxt == NULL) || (name == NULL) || (ctxt->varsNr == 0))
242         return(NULL);
243
244     /*
245      * Do the lookup from the top of the stack, but
246      * don't use params being computed in a call-param
247      */
248     ;
249
250     for (i = ctxt->varsNr; i > ctxt->varsBase; i--) {
251         cur = ctxt->varsTab[i-1];
252         while (cur != NULL) {
253             if (xmlStrEqual(cur->name, name)) {
254                 if (nameURI == NULL) {
255                     if (cur->nameURI == NULL) {
256                         return(cur);
257                     }
258                 } else {
259                     if ((cur->nameURI != NULL) &&
260                         (xmlStrEqual(cur->nameURI, nameURI))) {
261                         return(cur);
262                     }
263                 }
264
265             }
266             cur = cur->next;
267         }
268     }
269     return(ret);
270 }
271
272 /************************************************************************
273  *                                                                      *
274  *                      Module interfaces                               *
275  *                                                                      *
276  ************************************************************************/
277
278 /**
279  * xsltEvalVariable:
280  * @ctxt:  the XSLT transformation context
281  * @elem:  the variable or parameter.
282  * @precomp: pointer to precompiled data
283  *
284  * Evaluate a variable value.
285  *
286  * Returns the XPath Object value or NULL in case of error
287  */
288 static xmlXPathObjectPtr
289 xsltEvalVariable(xsltTransformContextPtr ctxt, xsltStackElemPtr elem,
290                  xsltStylePreCompPtr precomp) {
291     xmlXPathObjectPtr result = NULL;
292     int oldProximityPosition, oldContextSize;
293     xmlNodePtr oldInst, oldNode;
294     xsltDocumentPtr oldDoc;
295     int oldNsNr;
296     xmlNsPtr *oldNamespaces;
297
298     if ((ctxt == NULL) || (elem == NULL))
299         return(NULL);
300
301 #ifdef WITH_XSLT_DEBUG_VARIABLE
302     xsltGenericDebug(xsltGenericDebugContext,
303         "Evaluating variable %s\n", elem->name);
304 #endif
305     if (elem->select != NULL) {
306         xmlXPathCompExprPtr comp = NULL;
307
308         if ((precomp != NULL) && (precomp->comp != NULL)) {
309             comp = precomp->comp;
310         } else {
311             comp = xmlXPathCompile(elem->select);
312         }
313         if (comp == NULL)
314             return(NULL);
315         oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
316         oldContextSize = ctxt->xpathCtxt->contextSize;
317         ctxt->xpathCtxt->node = (xmlNodePtr) ctxt->node;
318         oldDoc = ctxt->document;
319         oldNode = ctxt->node;
320         oldInst = ctxt->inst;
321         oldNsNr = ctxt->xpathCtxt->nsNr;
322         oldNamespaces = ctxt->xpathCtxt->namespaces;
323         if (precomp != NULL) {
324             ctxt->inst = precomp->inst;
325             ctxt->xpathCtxt->namespaces = precomp->nsList;
326             ctxt->xpathCtxt->nsNr = precomp->nsNr;
327         } else {
328             ctxt->inst = NULL;
329             ctxt->xpathCtxt->namespaces = NULL;
330             ctxt->xpathCtxt->nsNr = 0;
331         }
332         result = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
333         ctxt->xpathCtxt->contextSize = oldContextSize;
334         ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
335         ctxt->xpathCtxt->nsNr = oldNsNr;
336         ctxt->xpathCtxt->namespaces = oldNamespaces;
337         ctxt->inst = oldInst;
338         ctxt->node = oldNode;
339         ctxt->document = oldDoc;
340         if ((precomp == NULL) || (precomp->comp == NULL))
341             xmlXPathFreeCompExpr(comp);
342         if (result == NULL) {
343             xsltTransformError(ctxt, NULL, precomp->inst,
344                 "Evaluating variable %s failed\n", elem->name);
345             ctxt->state = XSLT_STATE_STOPPED;
346 #ifdef WITH_XSLT_DEBUG_VARIABLE
347 #ifdef LIBXML_DEBUG_ENABLED
348         } else {
349             if ((xsltGenericDebugContext == stdout) ||
350                 (xsltGenericDebugContext == stderr))
351                 xmlXPathDebugDumpObject((FILE *)xsltGenericDebugContext,
352                                         result, 0);
353 #endif
354 #endif
355         }
356     } else {
357         if (elem->tree == NULL) {
358             result = xmlXPathNewCString("");
359         } else {
360             /*
361              * This is a result tree fragment.
362              */
363             xmlNodePtr container;
364             xmlNodePtr oldInsert;
365             xmlDocPtr  oldoutput;
366
367             container = xmlNewDocNode(ctxt->document->doc, NULL,
368                               (const xmlChar *) "fake node libxslt", NULL);
369             if (container == NULL)
370                 return(NULL);
371             container->parent = NULL;
372
373             oldoutput = ctxt->output;
374             ctxt->output = NULL;
375             oldInsert = ctxt->insert;
376             ctxt->insert = container;
377             xsltApplyOneTemplate(ctxt, ctxt->node, elem->tree, NULL, NULL);
378             ctxt->insert = oldInsert;
379             ctxt->output = oldoutput;
380
381             result = xmlXPathNewValueTree(container);
382             if (result == NULL) {
383                 result = xmlXPathNewCString("");
384             } else {
385                 /*
386                  * Tag the subtree for removal once consumed
387                  */
388                 result->boolval = 1;
389             }
390 #ifdef WITH_XSLT_DEBUG_VARIABLE
391 #ifdef LIBXML_DEBUG_ENABLED
392             if ((xsltGenericDebugContext == stdout) ||
393                 (xsltGenericDebugContext == stderr))
394                 xmlXPathDebugDumpObject((FILE *)xsltGenericDebugContext,
395                                         result, 0);
396 #endif
397 #endif
398         }
399     }
400     return(result);
401 }
402
403 /**
404  * xsltEvalGlobalVariable:
405  * @elem:  the variable or parameter.
406  * @ctxt:  the XSLT transformation context
407  *
408  * Evaluate a global variable value.
409  *
410  * Returns the XPath Object value or NULL in case of error
411  */
412 static xmlXPathObjectPtr
413 xsltEvalGlobalVariable(xsltStackElemPtr elem, xsltTransformContextPtr ctxt) {
414     xmlXPathObjectPtr result = NULL;
415     xsltStylePreCompPtr precomp;
416     int oldProximityPosition, oldContextSize;
417     xmlNodePtr oldInst;
418     int oldNsNr;
419     xmlNsPtr *oldNamespaces;
420
421     if ((ctxt == NULL) || (elem == NULL))
422         return(NULL);
423     if (elem->computed)
424         return(elem->value);
425
426
427 #ifdef WITH_XSLT_DEBUG_VARIABLE
428     xsltGenericDebug(xsltGenericDebugContext,
429         "Evaluating global variable %s\n", elem->name);
430 #endif
431
432 #ifdef WITH_DEBUGGER
433     if ((xslDebugStatus != XSLT_DEBUG_NONE) &&
434         elem->comp && elem->comp->inst)
435         xslHandleDebugger(elem->comp->inst, NULL, NULL, ctxt);
436 #endif
437
438     precomp = elem->comp;
439     if (elem->select != NULL) {
440         xmlXPathCompExprPtr comp = NULL;
441
442         if ((precomp != NULL) && (precomp->comp != NULL)) {
443             comp = precomp->comp;
444         } else {
445             comp = xmlXPathCompile(elem->select);
446         }
447         if (comp == NULL)
448             return(NULL);
449         oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
450         oldContextSize = ctxt->xpathCtxt->contextSize;
451         oldInst = ctxt->inst;
452         oldNsNr = ctxt->xpathCtxt->nsNr;
453         oldNamespaces = ctxt->xpathCtxt->namespaces;
454         if (precomp != NULL) {
455             ctxt->inst = precomp->inst;
456             ctxt->xpathCtxt->namespaces = precomp->nsList;
457             ctxt->xpathCtxt->nsNr = precomp->nsNr;
458         } else {
459             ctxt->inst = NULL;
460             ctxt->xpathCtxt->namespaces = NULL;
461             ctxt->xpathCtxt->nsNr = 0;
462         }
463         ctxt->xpathCtxt->node = (xmlNodePtr) ctxt->node;
464         result = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
465         ctxt->xpathCtxt->contextSize = oldContextSize;
466         ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
467         ctxt->inst = oldInst;
468         ctxt->xpathCtxt->nsNr = oldNsNr;
469         ctxt->xpathCtxt->namespaces = oldNamespaces;
470         if ((precomp == NULL) || (precomp->comp == NULL))
471             xmlXPathFreeCompExpr(comp);
472         if (result == NULL) {
473             xsltTransformError(ctxt, NULL, precomp->inst,
474                 "Evaluating global variable %s failed\n", elem->name);
475             ctxt->state = XSLT_STATE_STOPPED;
476 #ifdef WITH_XSLT_DEBUG_VARIABLE
477 #ifdef LIBXML_DEBUG_ENABLED
478         } else {
479             if ((xsltGenericDebugContext == stdout) ||
480                 (xsltGenericDebugContext == stderr))
481                 xmlXPathDebugDumpObject((FILE *)xsltGenericDebugContext,
482                                         result, 0);
483 #endif
484 #endif
485         }
486     } else {
487         if (elem->tree == NULL) {
488             result = xmlXPathNewCString("");
489         } else {
490             /*
491              * This is a result tree fragment.
492              */
493             xmlNodePtr container;
494             xmlNodePtr oldInsert;
495             xmlDocPtr  oldoutput;
496
497             container = xmlNewDocNode(ctxt->document->doc, NULL,
498                               (const xmlChar *) "fake node libxslt", NULL);
499             if (container == NULL)
500                 return(NULL);
501             container->parent = NULL;
502
503             oldoutput = ctxt->output;
504             ctxt->output = NULL;
505             oldInsert = ctxt->insert;
506             ctxt->insert = container;
507             xsltApplyOneTemplate(ctxt, ctxt->node, elem->tree, NULL, NULL);
508             ctxt->insert = oldInsert;
509             ctxt->output = oldoutput;
510
511             result = xmlXPathNewValueTree(container);
512             if (result == NULL) {
513                 result = xmlXPathNewCString("");
514             } else {
515                 /*
516                  * Tag the subtree for removal once consumed
517                  */
518                 result->boolval = 1;
519             }
520 #ifdef WITH_XSLT_DEBUG_VARIABLE
521 #ifdef LIBXML_DEBUG_ENABLED
522             if ((xsltGenericDebugContext == stdout) ||
523                 (xsltGenericDebugContext == stderr))
524                 xmlXPathDebugDumpObject((FILE *)xsltGenericDebugContext,
525                                         result, 0);
526 #endif
527 #endif
528         }
529     }
530     if (result != NULL) {
531         elem->value = result;
532         elem->computed = 1;
533     }
534     return(result);
535 }
536
537 /**
538  * xsltEvalGlobalVariables:
539  * @ctxt:  the XSLT transformation context
540  *
541  * Evaluate the global variables of a stylesheet. This need to be
542  * done on parsed stylesheets before starting to apply transformations
543  *
544  * Returns 0 in case of success, -1 in case of error
545  */
546 int
547 xsltEvalGlobalVariables(xsltTransformContextPtr ctxt) {
548     xsltStackElemPtr elem;
549     xsltStylesheetPtr style;
550
551     if (ctxt == NULL)
552         return(-1);
553  
554 #ifdef WITH_XSLT_DEBUG_VARIABLE
555     xsltGenericDebug(xsltGenericDebugContext,
556         "Registering global variables\n");
557 #endif
558     ctxt->node = (xmlNodePtr) ctxt->document->doc;
559     ctxt->xpathCtxt->contextSize = 1;
560     ctxt->xpathCtxt->proximityPosition = 1;
561
562     /*
563      * Walk the list from the stylesheets and populate the hash table
564      */
565     style = ctxt->style;
566     while (style != NULL) {
567         elem = style->variables;
568         
569 #ifdef WITH_XSLT_DEBUG_VARIABLE
570         if ((style->doc != NULL) && (style->doc->URL != NULL)) {
571             xsltGenericDebug(xsltGenericDebugContext,
572                              "Registering global variables from %s\n",
573                              style->doc->URL);
574         }
575 #endif
576
577         while (elem != NULL) {
578             xsltStackElemPtr def;
579
580             /*
581              * Global variables are stored in the variables pool.
582              */
583             def = (xsltStackElemPtr) 
584                     xmlHashLookup2(ctxt->globalVars,
585                                  elem->name, elem->nameURI);
586             if (def == NULL) {
587                 int res;
588
589                 def = xsltCopyStackElem(elem);
590                 res = xmlHashAddEntry2(ctxt->globalVars,
591                                  elem->name, elem->nameURI, def);
592             } else if ((elem->comp != NULL) &&
593                        (elem->comp->type == XSLT_FUNC_VARIABLE)) {
594                 /*
595                  * Redefinition of variables from a different stylesheet
596                  * should not generate a message.
597                  */
598                 if ((elem->comp->inst != NULL) &&
599                     (def->comp != NULL) && (def->comp->inst != NULL) &&
600                     (elem->comp->inst->doc == def->comp->inst->doc)) {
601                     xsltTransformError(ctxt, style, elem->comp->inst,
602                         "Global variable %s already defined\n", elem->name);
603                     style->errors++;
604                 }
605             }
606             elem = elem->next;
607         }
608
609         style = xsltNextImport(style);
610     }
611
612     /*
613      * This part does the actual evaluation
614      */
615     ctxt->node = (xmlNodePtr) ctxt->document->doc;
616     ctxt->xpathCtxt->contextSize = 1;
617     ctxt->xpathCtxt->proximityPosition = 1;
618     xmlHashScan(ctxt->globalVars,
619                 (xmlHashScanner) xsltEvalGlobalVariable, ctxt);
620
621     return(0);
622 }
623
624 /**
625  * xsltRegisterGlobalVariable:
626  * @style:  the XSLT transformation context
627  * @name:  the variable name
628  * @ns_uri:  the variable namespace URI
629  * @select:  the expression which need to be evaluated to generate a value
630  * @tree:  the subtree if select is NULL
631  * @comp:  the precompiled value
632  * @value:  the string value if available
633  *
634  * Register a new variable value. If @value is NULL it unregisters
635  * the variable
636  *
637  * Returns 0 in case of success, -1 in case of error
638  */
639 static int
640 xsltRegisterGlobalVariable(xsltStylesheetPtr style, const xmlChar *name,
641                      const xmlChar *ns_uri, const xmlChar *select,
642                      xmlNodePtr tree, xsltStylePreCompPtr comp,
643                      const xmlChar *value) {
644     xsltStackElemPtr elem, tmp;
645     if (style == NULL)
646         return(-1);
647     if (name == NULL)
648         return(-1);
649     if (comp == NULL)
650         return(-1);
651
652 #ifdef WITH_XSLT_DEBUG_VARIABLE
653     if (comp->type == XSLT_FUNC_PARAM)
654         xsltGenericDebug(xsltGenericDebugContext,
655                          "Defining global param %s\n", name);
656     else
657         xsltGenericDebug(xsltGenericDebugContext,
658                          "Defining global variable %s\n", name);
659 #endif
660
661     elem = xsltNewStackElem();
662     if (elem == NULL)
663         return(-1);
664     elem->comp = comp;
665     elem->name = xmlStrdup(name);
666     elem->select = xmlStrdup(select);
667     if (ns_uri)
668         elem->nameURI = xmlStrdup(ns_uri);
669     elem->tree = tree;
670     tmp = style->variables;
671     if (tmp == NULL) {
672         elem->next = NULL;
673         style->variables = elem;
674     } else {
675         while (tmp != NULL) {
676             if ((elem->comp->type == XSLT_FUNC_VARIABLE) &&
677                 (tmp->comp->type == XSLT_FUNC_VARIABLE) &&
678                 (xmlStrEqual(elem->name, tmp->name)) &&
679                 ((elem->nameURI == tmp->nameURI) ||
680                  (xmlStrEqual(elem->nameURI, tmp->nameURI)))) {
681                 xsltTransformError(NULL, style, comp->inst,
682                 "redefinition of global variable %s\n", elem->name);
683                 style->errors++;
684             }
685             if (tmp->next == NULL)
686                 break;
687             tmp = tmp->next;
688         }
689         elem->next = NULL;
690         tmp->next = elem;
691     }
692     if (value != NULL) {
693         elem->computed = 1;
694         elem->value = xmlXPathNewString(value);
695     }
696     return(0);
697 }
698
699 /**
700  * xsltProcessUserParamInternal
701  *
702  * @ctxt:  the XSLT transformation context
703  * @name:  a null terminated parameter name
704  * @value: a null terminated value (may be an XPath expression)
705  * @eval:  0 to treat the value literally, else evaluate as XPath expression
706  *
707  * If @eval is 0 then @value is treated literally and is stored in the global
708  * parameter/variable table without any change.
709  *
710  * Uf @eval is 1 then @value is treated as an XPath expression and is
711  * evaluated.  In this case, if you want to pass a string which will be
712  * interpreted literally then it must be enclosed in single or double quotes.
713  * If the string contains single quotes (double quotes) then it cannot be
714  * enclosed single quotes (double quotes).  If the string which you want to
715  * be treated literally contains both single and double quotes (e.g. Meet
716  * at Joe's for "Twelfth Night" at 7 o'clock) then there is no suitable
717  * quoting character.  You cannot use &apos; or &quot; inside the string
718  * because the replacement of character entities with their equivalents is
719  * done at a different stage of processing.  The solution is to call
720  * xsltQuoteUserParams or xsltQuoteOneUserParam.
721  *
722  * This needs to be done on parsed stylesheets before starting to apply
723  * transformations.  Normally this will be called (directly or indirectly)
724  * only from xsltEvalUserParams, xsltEvalOneUserParam, xsltQuoteUserParams,
725  * or xsltQuoteOneUserParam.
726  *
727  * Returns 0 in case of success, -1 in case of error
728  */
729
730 static
731 int
732 xsltProcessUserParamInternal(xsltTransformContextPtr ctxt,
733                              const xmlChar * name,
734                              const xmlChar * value,
735                              int eval) {
736
737     xsltStylesheetPtr style;
738     xmlChar *ncname;
739     xmlChar *prefix;
740     const xmlChar *href;
741     xmlXPathCompExprPtr comp;
742     xmlXPathObjectPtr result;
743     int oldProximityPosition;
744     int oldContextSize;
745     int oldNsNr;
746     xmlNsPtr *oldNamespaces;
747     xsltStackElemPtr elem;
748     int res;
749
750     if (ctxt == NULL)
751         return(-1);
752     if (name == NULL)
753         return(0);
754     if (value == NULL)
755         return(0);
756
757     style = ctxt->style;
758
759 #ifdef WITH_XSLT_DEBUG_VARIABLE
760     xsltGenericDebug(xsltGenericDebugContext,
761             "Evaluating user parameter %s=%s\n", name, value);
762 #endif
763
764     /*
765      * Name lookup
766      */
767
768     ncname = xmlSplitQName2(name, &prefix);
769     href = NULL;
770     if (ncname != NULL) {
771         if (prefix != NULL) {
772             xmlNsPtr ns;
773
774             ns = xmlSearchNs(style->doc, xmlDocGetRootElement(style->doc),
775                              prefix);
776             if (ns == NULL) {
777                 xsltTransformError(ctxt, style, NULL,
778                 "user param : no namespace bound to prefix %s\n", prefix);
779                 href = NULL;
780             } else {
781                 href = ns->href;
782             }
783             xmlFree(prefix);
784             prefix = NULL;
785         } else {
786             href = NULL;
787         }
788         xmlFree(ncname);
789         ncname = NULL;
790     } else {
791         href = NULL;
792         ncname = xmlStrdup(name);
793     }
794
795     if (ncname == NULL)
796         return (-1);
797
798     /*
799      * Do the evaluation if @eval is non-zero.
800      */
801
802     result = NULL;
803     if (eval != 0) {
804         comp = xmlXPathCompile(value);
805         if (comp != NULL) {
806             oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
807             oldContextSize = ctxt->xpathCtxt->contextSize;
808             ctxt->xpathCtxt->node = (xmlNodePtr) ctxt->node;
809
810             /* 
811              * There is really no in scope namespace for parameters on the
812              * command line.
813              */
814
815             oldNsNr = ctxt->xpathCtxt->nsNr;
816             oldNamespaces = ctxt->xpathCtxt->namespaces;
817             ctxt->xpathCtxt->namespaces = NULL;
818             ctxt->xpathCtxt->nsNr = 0;
819             result = xmlXPathCompiledEval(comp, ctxt->xpathCtxt);
820             ctxt->xpathCtxt->contextSize = oldContextSize;
821             ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
822             ctxt->xpathCtxt->nsNr = oldNsNr;
823             ctxt->xpathCtxt->namespaces = oldNamespaces;
824             xmlXPathFreeCompExpr(comp);
825         }
826         if (result == NULL) {
827             xsltTransformError(ctxt, style, NULL,
828                 "Evaluating user parameter %s failed\n", name);
829             ctxt->state = XSLT_STATE_STOPPED;
830             xmlFree(ncname);
831             return(-1);
832         }
833     }
834
835     /* 
836      * If @eval is 0 then @value is to be taken literally and result is NULL
837      * 
838      * If @eval is not 0, then @value is an XPath expression and has been
839      * successfully evaluated and result contains the resulting value and
840      * is not NULL.
841      *
842      * Now create an xsltStackElemPtr for insertion into the context's
843      * global variable/parameter hash table.
844      */
845
846 #ifdef WITH_XSLT_DEBUG_VARIABLE
847 #ifdef LIBXML_DEBUG_ENABLED
848     if ((xsltGenericDebugContext == stdout) ||
849         (xsltGenericDebugContext == stderr))
850             xmlXPathDebugDumpObject((FILE *)xsltGenericDebugContext,
851                                     result, 0);
852 #endif
853 #endif
854
855     elem = xsltNewStackElem();
856     if (elem != NULL) {
857         elem->name = xmlStrdup(ncname);
858         if (value != NULL)
859             elem->select = xmlStrdup(value);
860         else
861             elem->select = NULL;
862         if (href)
863             elem->nameURI = xmlStrdup(href);
864         elem->tree = NULL;
865         elem->computed = 1;
866         if (eval == 0) {
867             elem->value = xmlXPathNewString(value);
868         } 
869         else {
870             elem->value = result;
871         }
872     }
873
874     /*
875      * Global parameters are stored in the XPath context variables pool.
876      */
877
878     res = xmlHashAddEntry2(ctxt->globalVars, ncname, href, elem);
879     if (res != 0) {
880         xsltFreeStackElem(elem);
881         xsltTransformError(ctxt, style, NULL,
882             "Global parameter %s already defined\n", ncname);
883     }
884     xmlFree(ncname);
885     return(0);
886 }
887
888 /**
889  * xsltEvalUserParams:
890  *
891  * @ctxt:  the XSLT transformation context
892  * @params:  a NULL terminated array of parameters name/value tuples
893  *
894  * Evaluate the global variables of a stylesheet. This needs to be
895  * done on parsed stylesheets before starting to apply transformations.
896  * Each of the parameters is evaluated as an XPath expression and stored
897  * in the global variables/parameter hash table.  If you want your
898  * parameter used literally, use xsltQuoteUserParams.
899  *
900  * Returns 0 in case of success, -1 in case of error
901  */
902  
903 int
904 xsltEvalUserParams(xsltTransformContextPtr ctxt, const char **params) {
905     int indx = 0;
906     const xmlChar *name;
907     const xmlChar *value;
908
909     if (params == NULL)
910         return(0);
911     while (params[indx] != NULL) {
912         name = (const xmlChar *) params[indx++];
913         value = (const xmlChar *) params[indx++];
914         if (xsltEvalOneUserParam(ctxt, name, value) != 0) 
915             return(-1);
916     }
917     return 0;
918 }
919
920 /**
921  * xsltQuoteUserParams:
922  *
923  * @ctxt:  the XSLT transformation context
924  * @params:  a NULL terminated arry of parameters names/values tuples
925  *
926  * Similar to xsltEvalUserParams, but the values are treated literally and
927  * are * *not* evaluated as XPath expressions. This should be done on parsed
928  * stylesheets before starting to apply transformations.
929  *
930  * Returns 0 in case of success, -1 in case of error.
931  */
932  
933 int
934 xsltQuoteUserParams(xsltTransformContextPtr ctxt, const char **params) {
935     int indx = 0;
936     const xmlChar *name;
937     const xmlChar *value;
938
939     if (params == NULL)
940         return(0);
941     while (params[indx] != NULL) {
942         name = (const xmlChar *) params[indx++];
943         value = (const xmlChar *) params[indx++];
944         if (xsltQuoteOneUserParam(ctxt, name, value) != 0) 
945             return(-1);
946     }
947     return 0;
948 }
949
950 /**
951  * xsltEvalOneUserParam:
952  * @ctxt:  the XSLT transformation context
953  * @name:  a null terminated string giving the name of the parameter
954  * @value:  a null terminated string giving the XPath expression to be evaluated
955  *
956  * This is normally called from xsltEvalUserParams to process a single
957  * parameter from a list of parameters.  The @value is evaluated as an
958  * XPath expression and the result is stored in the context's global
959  * variable/parameter hash table.
960  *
961  * To have a parameter treated literally (not as an XPath expression)
962  * use xsltQuoteUserParams (or xsltQuoteOneUserParam).  For more
963  * details see description of xsltProcessOneUserParamInternal.
964  *
965  * Returns 0 in case of success, -1 in case of error.
966  */
967
968 int
969 xsltEvalOneUserParam(xsltTransformContextPtr ctxt,
970                      const xmlChar * name,
971                      const xmlChar * value) {
972     return xsltProcessUserParamInternal(ctxt, name, value,
973                                         1 /* xpath eval ? */);
974 }
975
976 /**
977  * xsltQuoteOneUserParam:
978  * @ctxt:  the XSLT transformation context
979  * @name:  a null terminated string giving the name of the parameter
980  * @value:  a null terminated string giving the parameter value
981  *
982  * This is normally called from xsltQuoteUserParams to process a single
983  * parameter from a list of parameters.  The @value is stored in the
984  * context's global variable/parameter hash table.
985  *
986  * Returns 0 in case of success, -1 in case of error.
987  */
988
989 int
990 xsltQuoteOneUserParam(xsltTransformContextPtr ctxt,
991                          const xmlChar * name,
992                          const xmlChar * value) {
993     return xsltProcessUserParamInternal(ctxt, name, value,
994                                         0 /* xpath eval ? */);
995 }
996
997 /**
998  * xsltBuildVariable:
999  * @ctxt:  the XSLT transformation context
1000  * @comp:  the precompiled form
1001  * @tree:  the tree if select is NULL
1002  *
1003  * Computes a new variable value.
1004  *
1005  * Returns the xsltStackElemPtr or NULL in case of error
1006  */
1007 static xsltStackElemPtr
1008 xsltBuildVariable(xsltTransformContextPtr ctxt, xsltStylePreCompPtr comp,
1009                   xmlNodePtr tree) {
1010     xsltStackElemPtr elem;
1011
1012 #ifdef WITH_XSLT_DEBUG_VARIABLE
1013     xsltGenericDebug(xsltGenericDebugContext,
1014                      "Building variable %s", comp->name);
1015     if (comp->select != NULL)
1016         xsltGenericDebug(xsltGenericDebugContext,
1017                          " select %s", comp->select);
1018     xsltGenericDebug(xsltGenericDebugContext, "\n");
1019 #endif
1020
1021     elem = xsltNewStackElem();
1022     if (elem == NULL)
1023         return(NULL);
1024     elem->comp = comp;
1025     elem->name = xmlStrdup(comp->name);
1026     if (comp->select != NULL)
1027         elem->select = xmlStrdup(comp->select);
1028     else
1029         elem->select = NULL;
1030     if (comp->ns)
1031         elem->nameURI = xmlStrdup(comp->ns);
1032     elem->tree = tree;
1033     if (elem->computed == 0) {
1034         elem->value = xsltEvalVariable(ctxt, elem, comp);
1035         if (elem->value != NULL)
1036             elem->computed = 1;
1037     }
1038     return(elem);
1039 }
1040
1041 /**
1042  * xsltRegisterVariable:
1043  * @ctxt:  the XSLT transformation context
1044  * @comp:  pointer to precompiled data
1045  * @tree:  the tree if select is NULL
1046  * @param:  this is a parameter actually
1047  *
1048  * Computes and register a new variable value. 
1049  *
1050  * Returns 0 in case of success, -1 in case of error
1051  */
1052 static int
1053 xsltRegisterVariable(xsltTransformContextPtr ctxt, xsltStylePreCompPtr comp,
1054                      xmlNodePtr tree, int param) {
1055     xsltStackElemPtr elem;
1056     int present;
1057
1058     present = xsltCheckStackElem(ctxt, comp->name, comp->ns);
1059     if (param == 0) {
1060         if ((present != 0) && (present != 3)) {
1061             xsltTransformError(ctxt, NULL, comp->inst,
1062                 "xsl:variable : redefining %s\n", comp->name);
1063             return(0);
1064         }
1065     } else if (present != 0) {
1066         if ((present == 1) || (present == 2)) {
1067             xsltTransformError(ctxt, NULL, comp->inst,
1068                 "xsl:param : redefining %s\n", comp->name);
1069             return(0);
1070         }
1071 #ifdef WITH_XSLT_DEBUG_VARIABLE
1072         xsltGenericDebug(xsltGenericDebugContext,
1073                  "param %s defined by caller\n", comp->name);
1074 #endif
1075         return(0);
1076     }
1077     elem = xsltBuildVariable(ctxt, comp, tree);
1078     xsltAddStackElem(ctxt, elem);
1079     return(0);
1080 }
1081
1082 /**
1083  * xsltGlobalVariableLookup:
1084  * @ctxt:  the XSLT transformation context
1085  * @name:  the variable name
1086  * @ns_uri:  the variable namespace URI
1087  *
1088  * Search in the Variable array of the context for the given
1089  * variable value.
1090  *
1091  * Returns the value or NULL if not found
1092  */
1093 static xmlXPathObjectPtr
1094 xsltGlobalVariableLookup(xsltTransformContextPtr ctxt, const xmlChar *name,
1095                          const xmlChar *ns_uri) {
1096     xsltStackElemPtr elem;
1097     xmlXPathObjectPtr ret = NULL;
1098
1099     /*
1100      * Lookup the global variables in XPath global variable hash table
1101      */
1102     if ((ctxt->xpathCtxt == NULL) || (ctxt->globalVars == NULL))
1103         return(NULL);
1104     elem = (xsltStackElemPtr)
1105             xmlHashLookup2(ctxt->globalVars, name, ns_uri);
1106     if (elem == NULL) {
1107 #ifdef WITH_XSLT_DEBUG_VARIABLE
1108         xsltGenericDebug(xsltGenericDebugContext,
1109                          "global variable not found %s\n", name);
1110 #endif
1111         return(NULL);
1112     }
1113     if (elem->computed == 0)
1114         ret = xsltEvalGlobalVariable(elem, ctxt);
1115     else
1116         ret = elem->value;
1117     return(xmlXPathObjectCopy(ret));
1118 }
1119
1120 /**
1121  * xsltVariableLookup:
1122  * @ctxt:  the XSLT transformation context
1123  * @name:  the variable name
1124  * @ns_uri:  the variable namespace URI
1125  *
1126  * Search in the Variable array of the context for the given
1127  * variable value.
1128  *
1129  * Returns the value or NULL if not found
1130  */
1131 xmlXPathObjectPtr
1132 xsltVariableLookup(xsltTransformContextPtr ctxt, const xmlChar *name,
1133                    const xmlChar *ns_uri) {
1134     xsltStackElemPtr elem;
1135
1136     if (ctxt == NULL)
1137         return(NULL);
1138
1139     elem = xsltStackLookup(ctxt, name, ns_uri);
1140     if (elem == NULL) {
1141         return(xsltGlobalVariableLookup(ctxt, name, ns_uri));
1142     }
1143     if (elem->computed == 0) {
1144 #ifdef WITH_XSLT_DEBUG_VARIABLE
1145         xsltGenericDebug(xsltGenericDebugContext,
1146                          "uncomputed variable %s\n", name);
1147 #endif
1148         elem->value = xsltEvalVariable(ctxt, elem, NULL);
1149         elem->computed = 1;
1150     }
1151     if (elem->value != NULL)
1152         return(xmlXPathObjectCopy(elem->value));
1153 #ifdef WITH_XSLT_DEBUG_VARIABLE
1154     xsltGenericDebug(xsltGenericDebugContext,
1155                      "variable not found %s\n", name);
1156 #endif
1157     return(NULL);
1158 }
1159
1160 /**
1161  * xsltParseStylesheetCallerParam:
1162  * @ctxt:  the XSLT transformation context
1163  * @cur:  the "param" element
1164  *
1165  * parse an XSLT transformation param declaration, compute
1166  * its value but doesn't record it.
1167  *
1168  * Returns the new xsltStackElemPtr or NULL
1169  */
1170
1171 xsltStackElemPtr
1172 xsltParseStylesheetCallerParam(xsltTransformContextPtr ctxt, xmlNodePtr cur) {
1173     xmlNodePtr tree = NULL;
1174     xsltStackElemPtr elem = NULL;
1175     xsltStylePreCompPtr comp;
1176
1177     if ((cur == NULL) || (ctxt == NULL))
1178         return(NULL);
1179     comp = (xsltStylePreCompPtr) cur->_private;
1180     if (comp == NULL) {
1181         xsltTransformError(ctxt, NULL, cur,
1182             "xsl:param : compilation error\n");
1183         return(NULL);
1184     }
1185
1186     if (comp->name == NULL) {
1187         xsltTransformError(ctxt, NULL, cur,
1188             "xsl:param : missing name attribute\n");
1189         return(NULL);
1190     }
1191
1192 #ifdef WITH_XSLT_DEBUG_VARIABLE
1193     xsltGenericDebug(xsltGenericDebugContext,
1194             "Handling param %s\n", comp->name);
1195 #endif
1196
1197     if (comp->select == NULL) {
1198         tree = cur->children;
1199     } else {
1200 #ifdef WITH_XSLT_DEBUG_VARIABLE
1201         xsltGenericDebug(xsltGenericDebugContext,
1202             "        select %s\n", comp->select);
1203 #endif
1204         tree = cur;
1205     }
1206
1207     elem = xsltBuildVariable(ctxt, comp, tree);
1208
1209     return(elem);
1210 }
1211
1212 /**
1213  * xsltParseGlobalVariable:
1214  * @style:  the XSLT stylesheet
1215  * @cur:  the "variable" element
1216  *
1217  * parse an XSLT transformation variable declaration and record
1218  * its value.
1219  */
1220
1221 void
1222 xsltParseGlobalVariable(xsltStylesheetPtr style, xmlNodePtr cur) {
1223     xsltStylePreCompPtr comp;
1224
1225     if ((cur == NULL) || (style == NULL))
1226         return;
1227
1228     xsltStylePreCompute(style, cur);
1229     comp = (xsltStylePreCompPtr) cur->_private;
1230     if (comp == NULL) {
1231         xsltTransformError(NULL, style, cur,
1232              "xsl:variable : compilation failed\n");
1233         return;
1234     }
1235
1236     if (comp->name == NULL) {
1237         xsltTransformError(NULL, style, cur,
1238             "xsl:variable : missing name attribute\n");
1239         return;
1240     }
1241
1242 #ifdef WITH_XSLT_DEBUG_VARIABLE
1243     xsltGenericDebug(xsltGenericDebugContext,
1244         "Registering global variable %s\n", comp->name);
1245 #endif
1246
1247     xsltRegisterGlobalVariable(style, comp->name, comp->ns, comp->select,
1248                                cur->children, comp, NULL);
1249 }
1250
1251 /**
1252  * xsltParseGlobalParam:
1253  * @style:  the XSLT stylesheet
1254  * @cur:  the "param" element
1255  *
1256  * parse an XSLT transformation param declaration and record
1257  * its value.
1258  */
1259
1260 void
1261 xsltParseGlobalParam(xsltStylesheetPtr style, xmlNodePtr cur) {
1262     xsltStylePreCompPtr comp;
1263
1264     if ((cur == NULL) || (style == NULL))
1265         return;
1266
1267     xsltStylePreCompute(style, cur);
1268     comp = (xsltStylePreCompPtr) cur->_private;
1269     if (comp == NULL) {
1270         xsltTransformError(NULL, style, cur,
1271              "xsl:param : compilation failed\n");
1272         return;
1273     }
1274
1275     if (comp->name == NULL) {
1276         xsltTransformError(NULL, style, cur,
1277             "xsl:param : missing name attribute\n");
1278         return;
1279     }
1280
1281 #ifdef WITH_XSLT_DEBUG_VARIABLE
1282     xsltGenericDebug(xsltGenericDebugContext,
1283         "Registering global param %s\n", comp->name);
1284 #endif
1285
1286     xsltRegisterGlobalVariable(style, comp->name, comp->ns, comp->select,
1287                                cur->children, comp, NULL);
1288 }
1289
1290 /**
1291  * xsltParseStylesheetVariable:
1292  * @ctxt:  the XSLT transformation context
1293  * @cur:  the "variable" element
1294  *
1295  * parse an XSLT transformation variable declaration and record
1296  * its value.
1297  */
1298
1299 void
1300 xsltParseStylesheetVariable(xsltTransformContextPtr ctxt, xmlNodePtr cur) {
1301     xsltStylePreCompPtr comp;
1302
1303     if ((cur == NULL) || (ctxt == NULL))
1304         return;
1305
1306     comp = (xsltStylePreCompPtr) cur->_private;
1307     if (comp == NULL) {
1308         xsltTransformError(ctxt, NULL, cur,
1309              "xsl:variable : compilation failed\n");
1310         return;
1311     }
1312
1313     if (comp->name == NULL) {
1314         xsltTransformError(ctxt, NULL, cur,
1315             "xsl:variable : missing name attribute\n");
1316         return;
1317     }
1318
1319 #ifdef WITH_XSLT_DEBUG_VARIABLE
1320     xsltGenericDebug(xsltGenericDebugContext,
1321         "Registering variable %s\n", comp->name);
1322 #endif
1323
1324     xsltRegisterVariable(ctxt, comp, cur->children, 0);
1325 }
1326
1327 /**
1328  * xsltParseStylesheetParam:
1329  * @ctxt:  the XSLT transformation context
1330  * @cur:  the "param" element
1331  *
1332  * parse an XSLT transformation param declaration and record
1333  * its value.
1334  */
1335
1336 void
1337 xsltParseStylesheetParam(xsltTransformContextPtr ctxt, xmlNodePtr cur) {
1338     xsltStylePreCompPtr comp;
1339
1340     if ((cur == NULL) || (ctxt == NULL))
1341         return;
1342
1343     comp = (xsltStylePreCompPtr) cur->_private;
1344     if (comp == NULL) {
1345         xsltTransformError(ctxt, NULL, cur,
1346              "xsl:param : compilation failed\n");
1347         return;
1348     }
1349
1350     if (comp->name == NULL) {
1351         xsltTransformError(ctxt, NULL, cur,
1352             "xsl:param : missing name attribute\n");
1353         return;
1354     }
1355
1356 #ifdef WITH_XSLT_DEBUG_VARIABLE
1357     xsltGenericDebug(xsltGenericDebugContext,
1358         "Registering param %s\n", comp->name);
1359 #endif
1360
1361     xsltRegisterVariable(ctxt, comp, cur->children, 1);
1362 }
1363
1364 /**
1365  * xsltFreeGlobalVariables:
1366  * @ctxt:  the XSLT transformation context
1367  *
1368  * Free up the data associated to the global variables
1369  * its value.
1370  */
1371
1372 void
1373 xsltFreeGlobalVariables(xsltTransformContextPtr ctxt) {
1374     xmlHashFree(ctxt->globalVars, (xmlHashDeallocator) xsltFreeStackElem);
1375 }
1376
1377 /**
1378  * xsltXPathVariableLookup:
1379  * @ctxt:  a void * but the the XSLT transformation context actually
1380  * @name:  the variable name
1381  * @ns_uri:  the variable namespace URI
1382  *
1383  * This is the entry point when a varibale is needed by the XPath
1384  * interpretor.
1385  *
1386  * Returns the value or NULL if not found
1387  */
1388 xmlXPathObjectPtr
1389 xsltXPathVariableLookup(void *ctxt, const xmlChar *name,
1390                         const xmlChar *ns_uri) {
1391     xsltTransformContextPtr context;
1392     xmlXPathObjectPtr ret;
1393
1394     if ((ctxt == NULL) || (name == NULL))
1395         return(NULL);
1396
1397 #ifdef WITH_XSLT_DEBUG_VARIABLE
1398     xsltGenericDebug(xsltGenericDebugContext,
1399             "Lookup variable %s\n", name);
1400 #endif
1401     context = (xsltTransformContextPtr) ctxt;
1402     ret = xsltVariableLookup(context, name, ns_uri);
1403     if (ret == NULL) {
1404         xsltTransformError(ctxt, NULL, NULL,
1405             "unregistered variable %s\n", name);
1406     }
1407 #ifdef WITH_XSLT_DEBUG_VARIABLE
1408     if (ret != NULL)
1409         xsltGenericDebug(xsltGenericDebugContext,
1410             "found variable %s\n", name);
1411 #endif
1412     return(ret);
1413 }
1414
1415