Initial revision
[TestXSLT.git] / libsablot / src / engine / jsext.cpp
1 /* 
2  * The contents of this file are subject to the Mozilla Public
3  * License Version 1.1 (the "License"); you may not use this file
4  * except in compliance with the License. You may obtain a copy of
5  * the License at http://www.mozilla.org/MPL/
6  * 
7  * Software distributed under the License is distributed on an "AS
8  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9  * implied. See the License for the specific language governing
10  * rights and limitations under the License.
11  * 
12  * The Original Code is the Sablotron XSLT Processor.
13  * 
14  * The Initial Developer of the Original Code is Ginger Alliance Ltd.
15  * Portions created by Ginger Alliance are Copyright (C) 2000-2002
16  * Ginger Alliance Ltd. All Rights Reserved.
17  * 
18  * Contributor(s): Han Qi
19  * 
20  * Alternatively, the contents of this file may be used under the
21  * terms of the GNU General Public License Version 2 or later (the
22  * "GPL"), in which case the provisions of the GPL are applicable 
23  * instead of those above.  If you wish to allow use of your 
24  * version of this file only under the terms of the GPL and not to
25  * allow others to use your version of this file under the MPL,
26  * indicate your decision by deleting the provisions above and
27  * replace them with the notice and other provisions required by
28  * the GPL.  If you do not delete the provisions above, a recipient
29  * may use your version of this file under either the MPL or the
30  * GPL.
31  */
32
33 #include "jsext.h"
34
35 #ifdef ENABLE_JS
36
37 #include "expr.h"
38 #include "error.h"
39 #include "context.h"
40 #include "guard.h"
41 #include "domprovider.h"
42 #include "jsdom.h"
43
44 /************* extern constants ******************/
45 const char* theArrayProtoRoot = "_array_prototype_root_";
46
47 /*************** JS error reporter ******************/
48
49 void SJSErrorReporter(JSContext *cx, const char *message,
50                       JSErrorReport *report)
51 {
52   JSContextItem *item = (JSContextItem*)JS_GetContextPrivate(cx);
53   if (item) {
54     if (item -> errInfo.message) delete item -> errInfo.message;
55     if (item -> errInfo.token) delete item -> errInfo.token;
56     item -> errInfo.message = NULL;
57     item -> errInfo.token = NULL;
58     if (message) {
59       item -> errInfo.message = new char[strlen(message) + 1];
60       strcpy(item -> errInfo.message, message);
61     }
62     if (report -> tokenptr) {
63       item -> errInfo.token = new char[strlen(report -> tokenptr) + 1];
64       strcpy(item -> errInfo.token, report -> tokenptr);
65     }
66     item -> errInfo.line = report -> lineno;
67     item -> errInfo.errNumber = report -> errorNumber;
68   }
69 }
70
71 /************************ MANAGER *******************/
72
73 JSRuntime_Sab* gJSRuntime;
74
75 JSRuntime_Sab* JSManager::getRuntime()
76 {
77   if (! gJSRuntime ) {
78     gJSRuntime = JS_NewRuntime(JS_RUNTIME_SIZE);
79   }
80   return gJSRuntime;
81 }
82
83 JSContext_Sab* JSManager::createContext(int size /* =JS_CONTEXT_SIZE */) 
84 {
85   return JS_NewContext(getRuntime(), size);
86 }
87
88
89 void JSManager::finalize() 
90 {
91   if ( gJSRuntime ) JS_DestroyRuntime(gJSRuntime);
92 }
93
94 /******************************delegates etc. ******************/
95
96 JS_METHOD(jsglobalLog) {
97   JSContextItem *item = (JSContextItem*)JS_GetContextPrivate(cx);
98   Situation *sit = item -> proc -> recallSituation();
99   JSString *str = JS_ValueToString(cx, argv[0]);
100   char *msg = JS_GetStringBytes(str);
101   sit -> message(MT_LOG, L_JS_LOG, (const char*) msg, (const char*)NULL);
102   return TRUE;
103 }
104
105 /****************************************************************
106
107 JSContexts
108
109 ****************************************************************/
110
111 JSContextItem::JSContextItem(JSContext_Sab *cx_, Str &uri_, Processor *proc_) 
112   : cx(cx_), uri(uri_), proc(proc_)
113 {
114   //cls = NULL;
115   errInfo.message = NULL;
116   errInfo.token = NULL;
117   node = NULL;
118   domex = NULL;
119   domimpl = NULL;
120   nlclass = NULL;
121   array_proto = NULL;
122 };
123
124 JSContextItem::~JSContextItem()
125 {
126   names.freeall(FALSE);
127   if (errInfo.message) delete errInfo.message;
128   if (errInfo.token) delete errInfo.token;
129   if (cx) 
130     {
131       if (array_proto) JS_RemoveRoot(cx, &array_proto);
132       JS_GC(cx);
133       JS_DestroyContext(cx);
134     }
135   //if (cls) delete cls;
136 }
137
138 //js class for global object
139 JSClass sabGlobalClass = {
140   "global",
141   JSCLASS_HAS_PRIVATE,
142   JS_PropertyStub, JS_PropertyStub,
143   JS_PropertyStub,
144   JS_PropertyStub,
145   JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
146   JS_FinalizeStub, 0, 0, NULL, NULL, NULL, NULL, 0, 0
147 };
148
149 JSContextItem* JSContextList::find(Str uri, Bool canCreate /*TRUE*/)
150 {
151   JSContextItem *item = NULL;
152   for (int i = 0; i < number(); i++) 
153     {
154       if ((*this)[i] -> uri == uri) item = (*this)[i];
155     }
156   if (!item && canCreate) 
157     {
158       JSContext_Sab *cx;
159       cx = JSManager::createContext();
160       item = new JSContextItem(cx, uri, proc);
161       append(item);
162       //initalize context
163       //JSClass *cls = SJSGetGlobalClass();
164       //item -> cls = cls;
165       JSObject *global = JS_NewObject(cx, &sabGlobalClass, NULL, NULL);
166       JS_InitStandardClasses(cx, global);
167       //set debugging globals
168       JS_DefineFunction(cx, global, "log", jsglobalLog, 1, 0);
169       //private data and error handling
170       JS_SetContextPrivate(cx, (void*)item);
171       JS_SetErrorReporter(cx, SJSErrorReporter);
172       //create persistent objects
173       jsdom_delegateDOM(cx);
174     }
175   return item;
176 }
177
178 /************************************************************/
179 // JSExternalPrivate
180 /************************************************************/
181
182 JSExternalPrivate::JSExternalPrivate(void *p_, void *v_)
183 : priv(p_), value(v_), refcnt(1) 
184 {
185   if (value)
186     {
187       JS_AddRoot((JSContext*)priv, &value);
188     }
189 };
190
191 JSExternalPrivate::~JSExternalPrivate() 
192 {
193   if (value) 
194     {
195       JS_RemoveRoot((JSContext*)priv, &value); 
196     }
197 }
198
199 /**************** ordinary functions */
200
201 Bool sjs_instanceOf(JSContext_Sab *cx, JSObject_Sab *obj, JSClass_Sab *cls)
202 {
203   JSObject *o = obj;
204   while (o)
205     {
206       JSClass *c = JS_GET_CLASS(cx, o);
207       if (c && c == cls) return TRUE;
208       o = JS_GetPrototype(cx, o);
209     }
210   return FALSE;
211 }
212
213 //  const char* gClassName = "global";
214
215 //  JSClass* SJSGetGlobalClass() 
216 //  {
217 //  #ifdef HAVE_JSAPI_H
218 //    JSClass *ret = new JSClass();
219 //    ret -> name = gClassName;
220 //    ret -> flags = 0;
221 //    ret -> addProperty = JS_PropertyStub;
222 //    ret -> delProperty = JS_PropertyStub;
223 //    ret -> getProperty = JS_PropertyStub;
224 //    ret -> setProperty = JS_PropertyStub;
225 //    ret -> enumerate = JS_EnumerateStub;
226 //    ret -> resolve = JS_ResolveStub;
227 //    ret -> convert = JS_ConvertStub;
228 //    ret -> finalize = JS_FinalizeStub;
229   
230 //    return ret;
231 //  #else
232 //    return NULL;
233 //  #endif
234 //  }
235
236 Bool SJSEvaluate(JSContextItem &item, DStr &script)
237 {
238   jsval rval;
239   JSBool status;
240   JSContext *cx = item.cx;
241
242   /*
243     int len = utf8StrLength((const char*) script);
244     wchar_t *ucscript = new wchar_t[len + 1];
245     utf8ToUtf16(ucscript, (const char*) script);
246     
247     status = JS_EvaluateUCScript(cx, JS_GetGlobalObject(cx), 
248     (jschar*)ucscript, len,
249     "stylesheet", 0, &rval);
250     delete ucscript;
251   */
252   
253   char *scr = (char *) script;
254   status = JS_EvaluateScript(cx, JS_GetGlobalObject(cx), 
255                              scr, strlen(scr),
256                              "stylesheet", 0, &rval);
257   if ( JS_IsExceptionPending(cx) ) 
258     {
259       JS_ClearPendingException(cx);
260     }
261   
262   if (status) {
263     //read all functions
264     JSIdArray *arr = JS_Enumerate(cx, JS_GetGlobalObject(cx));
265     item.names.freeall(FALSE);
266     if (arr) {
267       for (int i = 0; i < arr -> length; i++) {
268         jsval propname;
269         jsid id = arr -> vector[i];
270         JS_IdToValue(cx, id, &propname);
271         
272         JSString *str = JS_ValueToString(cx, propname);
273         jsval prop;
274         JS_GetProperty(cx, JS_GetGlobalObject(cx), 
275                        JS_GetStringBytes(str), &prop);
276         
277         JSFunction *func = JS_ValueToFunction(cx, prop);
278         if (func) {
279           const char* fname = JS_GetFunctionName(func);
280           item.names.append(new Str(fname));
281         }
282       }
283       JS_DestroyIdArray(cx, arr);
284     }
285   } 
286   else { //error occured
287     
288   }
289   return (Bool)status;
290 }
291
292 /************** XSLTContext *******************/
293
294 struct XSLTContextPrivate {
295   Context *ctx;
296   Situation *situa;
297 };
298
299 void ctxFinalize(JSContext *cx, JSObject *obj)
300 {
301   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
302   if (priv) delete priv;
303 }
304
305 JS_PROP(ctxGetPosition)
306 {
307   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
308   if (priv) {
309     *rval = INT_TO_JSVAL(priv -> ctx -> getPosition() + 1);
310     return TRUE;
311   } 
312   else {
313     return FALSE;
314   };
315 }
316
317 JS_PROP(ctxGetSize)
318 {
319   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
320   if (priv) {
321     *rval = INT_TO_JSVAL(priv -> ctx -> getSize());
322     return TRUE;
323   } 
324   else {
325     return FALSE;
326   };
327 }
328
329 JS_PROP(ctxGetContextNode)
330 {
331   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
332   if (priv) {
333     JSObject *obj = jsdom_wrapNode(*(priv->situa), cx, priv->ctx->current());
334     *rval = OBJECT_TO_JSVAL(obj);
335     return TRUE;
336   } else {
337     return FALSE;
338   }
339 }
340
341 JS_PROP(ctxGetCurrentNode)
342 {
343   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
344   if (priv) {
345     JSObject *obj = jsdom_wrapNode(*(priv->situa), cx, 
346                                    priv->ctx->getCurrentNode());
347     *rval = OBJECT_TO_JSVAL(obj);
348     return TRUE;
349   } else {
350     return FALSE;
351   }
352 }
353
354 JS_PROP(ctxGetOwnerDocument)
355 {
356   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
357   if (priv) {
358     Tree &tree = toV(priv->ctx->current())->getOwner();
359     JSObject *obj = jsdom_wrapNode(*(priv -> situa), cx, 
360                                    &(tree.getRoot()));
361     *rval = OBJECT_TO_JSVAL(obj);
362     return TRUE;
363   } else {
364     return FALSE;
365   }
366 }
367
368 JS_METHOD(ctxSystemProperty)
369 {
370   JSString *str =  JS_NewStringCopyN(cx, "not supported", 13);
371   *rval = STRING_TO_JSVAL(str);
372   return TRUE;
373 }
374
375 JS_METHOD(ctxStringValue)
376 {
377 //    if (argc > 0 && JSVAL_IS_OBJECT(argv[0]) 
378 //        && sjs_instanceOf(cx, JSVAL_TO_OBJECT(argv[0]), &nodeClass))
379 //      {
380 //        JSObject *node = JSVAL_TO_OBJECT(argv[0]);
381 //        NodePrivate *np = (NodePrivate*)JS_GetPrivate(cx, obj);
382 //        assert(np);
383 //        DStr val;
384 //        np->situa->dom().constructStringValue(np->node, val);
385 //        JSString *str = JS_NewStringCopyZ(cx, (char*)val);
386 //        *rval = STRING_TO_JSVAL(str);
387 //        return TRUE;
388 //      } else {
389 //        return FALSE;
390 //      }
391   DOM_EX( 9 );
392   return TRUE;
393 }
394
395 JSClass ctxClass = {
396   "XSLTContext",
397   JSCLASS_HAS_PRIVATE,
398   JS_PropertyStub, JS_PropertyStub,
399   JS_PropertyStub, JS_PropertyStub,
400   JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, 
401   ctxFinalize, 0, 0, NULL, NULL, NULL, NULL, 0, 0
402 };
403
404 JSPropertySpec ctxProps[] =
405 {
406   {"contextPosition", 0, PROP_OPT, ctxGetPosition, NULL},
407   {"contextSize", 0, PROP_OPT, ctxGetSize, NULL},
408   {"contextNode", 0, PROP_OPT, ctxGetContextNode, NULL},
409   {"currentNode", 0, PROP_OPT, ctxGetCurrentNode, NULL},
410   {"ownerDocument", 0, PROP_OPT, ctxGetOwnerDocument, NULL},
411   {NULL, 0, 0, 0, 0}
412 };
413
414 JSFunctionSpec ctxFunctions[] = 
415 {
416   {"systemProperty", ctxSystemProperty, 0, 0, 0},
417   {"stringValue", ctxStringValue, 0, 0, 0},
418   {NULL, 0, 0, 0, 0}
419 };
420
421 void sjs_PublishContext(Sit S, JSContext_Sab *cx, Context *c) 
422 {
423   JSObject *obj = JS_DefineObject(cx, JS_GetGlobalObject(cx), "XSLTContext",
424                                   &ctxClass, NULL, 
425                                   JSPROP_ENUMERATE | 
426                                   JSPROP_READONLY);
427   
428   XSLTContextPrivate *priv = new XSLTContextPrivate;
429   priv -> ctx = c;
430   priv -> situa = &S;
431   JS_SetPrivate(cx, obj, priv);
432   JS_DefineProperties(cx, obj, ctxProps);
433   JS_DefineFunctions(cx, obj, ctxFunctions);
434 }
435
436 /************* function call ******************/
437 Bool SJSCallFunction(Sit S, Context *c, JSContextItem &item, Str &name, 
438                       ExprList &atoms, Expression &retxpr)
439 {
440   JSContext *cx = item.cx;
441   assert(cx);
442
443   sjs_PublishContext(S, cx, c);
444
445   //get args
446   int argc = atoms.number();
447   jsval *args = new jsval[argc];
448   for (int i = 0; i < argc; i++) {
449     switch (atoms[i] -> type) {
450     case EX_NUMBER:
451       {
452         jsdouble d = (double)(atoms[i]->tonumber(S));
453         JS_NewDoubleValue(cx, d, &args[i]);
454       }; break;
455     case EX_BOOLEAN:
456       {
457         args[i] = BOOLEAN_TO_JSVAL(atoms[i]->tobool());
458       }; break;
459     case EX_STRING:
460       {
461         Str str;
462         atoms[i]->tostring(S, str);
463         char *p = (char*)str;
464         JSString *s = JS_NewStringCopyN(cx, p, strlen(p));
465         args[i] = STRING_TO_JSVAL(s);
466       }; break;
467     case EX_NODESET:
468       {
469         const Context &ctx = atoms[i]->tonodesetRef();
470         int num = ctx.getSize();
471         JSObject *arr = jsdom_createNodeList(cx, num);
472         args[i] = OBJECT_TO_JSVAL(arr);
473         //iterate
474         for (int j = 0; j < num; j++) {
475           NodeHandle node = ctx[j];
476           jsval val;// = new jsval;
477           val = OBJECT_TO_JSVAL(jsdom_wrapNode(S, cx, node));
478           JS_SetElement(cx, arr, j, &val);
479         }
480       }; break;
481     case EX_EXTERNAL:
482       {
483         External e;
484         e.assign(atoms[i]->toexternal(S));
485         JSObject *o = (JSObject*)e.getValue();
486         if ( o )
487           {
488             args[i] = OBJECT_TO_JSVAL(o);
489           }
490         else
491           {
492             args[i] = JSVAL_NULL;
493           }
494       }; break;
495     default:
496       {
497         //convert to string
498         Str str;
499         atoms[i]->tostring(S, str);
500         char *p = (char*)str;
501         JSString *s = JS_NewStringCopyN(cx, p, strlen(p));
502         args[i] = STRING_TO_JSVAL(s);
503       }
504     }
505   }
506   
507   //call function
508   jsval rval;
509   JSBool status = JS_CallFunctionName(cx, JS_GetGlobalObject(cx), 
510                                       (char*)name, argc, args, &rval);
511   if ( JS_IsExceptionPending(cx) ) 
512     {
513       JS_ClearPendingException(cx);
514     }
515
516   //remove XSLT context
517   JS_DeleteProperty(cx, JS_GetGlobalObject(cx), "XSLTContext");
518
519   delete[] args;
520
521   if (status) {
522     if (JSVAL_IS_VOID(rval))
523       {
524         //return an emtpy nodeset
525         GP( Context ) newc = new Context(c->getCurrentNode());
526         retxpr.setAtom(newc.keep());
527       }
528     if (JSVAL_IS_NULL(rval)) 
529       {
530         External e(cx, NULL);
531         retxpr.setAtom(e);
532       }
533     if (JSVAL_IS_OBJECT(rval)) 
534       {
535         JSObject *obj = JSVAL_TO_OBJECT(rval);
536         //node lists
537         if (sjs_instanceOf(cx, obj, &nlistClass))
538           {
539             jsuint len;
540             JS_GetArrayLength(cx, obj, &len);
541             GP( Context ) newc = new Context(c->getCurrentNode());
542             for (unsigned int i = 0; i < len; i++)
543               {
544                 jsval jnode;
545                 JS_GetElement(cx, obj, i, &jnode);
546                 JSObject *onode = JSVAL_TO_OBJECT(jnode);
547                 if (onode && sjs_instanceOf(cx, onode, &nodeClass))
548                   {
549                     NodePrivate *priv = (NodePrivate*)JS_GetPrivate(cx, onode);
550                     assert(priv);
551                     (*newc).append(priv -> node);
552                   }
553               }
554             retxpr.setAtom(newc.keep());
555           }
556         //single nodes
557         else if (sjs_instanceOf(cx, obj, &nodeClass))
558           {
559             GP( Context ) newc = new Context(c->getCurrentNode());
560
561             NodePrivate *priv = (NodePrivate*)JS_GetPrivate(cx, obj);
562             assert(priv);
563             (*newc).append(priv -> node);
564
565             retxpr.setAtom(newc.keep());
566           }
567         //other objects
568         else
569           {
570             External e(cx, obj);
571             retxpr.setAtom(e);
572           }
573       }
574     //strings
575     else if (JSVAL_IS_STRING(rval))
576       {
577         JSString *str = JS_ValueToString(cx, rval);
578         Str rstr = (char*) JS_GetStringBytes(str);
579         retxpr.setAtom(rstr);
580       }
581     else if (JSVAL_IS_NUMBER(rval))
582       {
583         jsdouble d;
584         JS_ValueToNumber(cx, rval, &d);
585         Number num((double)d);
586         retxpr.setAtom(num);
587       }
588     else if (JSVAL_IS_BOOLEAN(rval))
589       {
590         retxpr.setAtom(JSVAL_TO_BOOLEAN(rval));
591       }
592     else 
593       {
594         //all other types are treated as strings (shouldn't happen)
595         JSString *str = JS_ValueToString(cx, rval);
596         Str rstr = (char*) JS_GetStringBytes(str);
597         retxpr.setAtom(rstr);
598       }
599   }
600   //run GC
601   JS_MaybeGC(cx);
602   //JS_GC(cx);
603   return status;
604 }
605
606 #endif //ENABLE_JS