DocXSL._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * $Id: DocXSL.pike,v 1.1 2008/03/31 13:39:57 exodusd Exp $
18  */
19 inherit "/base/xml_data";
20 inherit "/base/xml_parser";
21 inherit "/classes/Document";
22  inherit Events.Listener;
23 #include <macros.h>
24 #include <database.h>
25 #include <attributes.h>
26 #include <config.h>
27 #include <classes.h>
28 #include <events.h>
29 #include <exception.h>
30 #include <access.h>
31 //! This class keep XSL Stylesheets and is able to do XSLT with libxslt
32 class DocXSL : public xml_data,xml_parser,Document{
33 public:
34 
35 
36 
37 
38 
39 
40 
41 //#define DOCXSL_DEBUG
42 
43 #ifdef DOCXSL_DEBUG
44 #define DEBUG_DOCXSL(s, args...) werror(s+"\n", args)
45 #else
46 #define DEBUG_DOCXSL
47 #endif
48 
49  object XML;
50  mapping mXML = ([ ]);
51  mapping mDepend = ([ ]);
52  mapping mLang = ([ ]);
53  bool xmlIsLoad = false;
54 
55 
56  mapping mStylesheets = ([ ]); // all stylesheet objects
57  object oDescriptionXML;
58 
59 
60  Thread.Mutex xmlMutex = Thread.Mutex();
61 
62 protected:
63  mapping mAttrConv = ([
64  101 : "OBJ_OWNER", 102 : "OBJ_NAME", 104 : "OBJ_DESC",
65  105 : "OBJ_ICON", 111 : "OBJ_KEYWORDS", 112 : "OBJ_COMMAND_MAP",
66  113 : "OBJ_POSITION_X", 114 : "OBJ_POSITION_Y", 115 : "OBJ_POSITION_Z",
67  116 : "OBJ_LAST_CHANGED", 119 : "OBJ_CREATION_TIME", "url" : "OBJ_URL",
68  "obj:link_icon" : "OBJ_LINK_ICON", "obj_script" : "OBJ_SCRIPT",
69  "obj_annotations_changed" : "OBJ_ANNOTATIONS_CHANGED",
70  207 : "DOC_TYPE", 208 : "DOC_MIME_TYPE", 213 :
71  "DOC_USER_MODIFIED",
72  214 : "DOC_LAST_MODIFIED", 215 : "DOC_LAST_ACCESSED",
73  216 : "DOC_EXTERN_URL", 217 : "DOC_TIMES_READ",
74  218 : "DOC_IMAGE_ROTATION", 219 : "DOC_IMAGE_THUMBNAIL",
75  220 : "DOC_IMAGE_SIZEX", 221 : "DOC_IMAGE_SIZEY",
76  300 : "CONT_SIZE_X", 301 : "CONT_SIZE_Y", 302 : "CONT_SIZE_Z",
77  303 : "CONT_EXCHANGE_LINKS", "cont:monitor" : "CONT_MONITOR",
78  "cont_last_modified" : "CONT_LAST_MODIFIED",
79  500 : "GROUP_MEMBERSHIP_REQS", 501 : "GROUP_EXITS",
80  502 : "GROUP_MAXSIZE", 503 : "GROUP_MSG_ACCEPT",
81  504 : "GROUP_MAXPENDING", 611 : "USER_ADRESS", 612 :
82  "USER_FULLNAME",
83  613 : "USER_MAILBOX", 614 : "USER_WORKROOM", 615 :
84  "USER_LAST_LOGIN",
85  616 : "USER_EMAIL", 617 : "USER_UMASK", 618 : "USER_MODE",
86  619 : "USER_MODE_MSG", 620 : "USER_LOGOUT_PLACE",
87  621 : "USER_TRASHBIN", 622 : "USER_BOOKMARKROOM",
88  623 : "USER_FORWARD_MSG", 624 : "USER_IRC_PASSWORD",
89  "user_firstname" : "USER_FIRSTNAME", "user_language" :
90  "USER_LANGUAGE",
91  "user_selection" : "USER_SELECTION",
92  "user_favorites" : "USER_FAVOURITES", 700 : "DRAWING_TYPE",
93  701 : "DRAWING_WIDTH", 702 : "DRAWING_HEIGHT", 703 :
94  "Drawing_COLOR",
95  704 : "DRAWING_THICKNESS", 705 : "DRAWING_FILLED",
96  800 : "GROUP_WORKROOM", 801 : "GROUP_EXCLUSIVE_SUBGROUPS",
97  1000 : "LAB_TUTOR", 1001 : "LAB_SIZE", 1002 : "LAB_ROOM",
98  1003 : "LAB_APPTIME", 1100 : "MAIL_MIMEHEADERS",
99  1101 : "MAIL_IMAPFLAGS"
100  ]);
101 
102 mapping mUpdates = ([ ]);
103 
104 class UpdateListener {
105 public:
106 
107  object __obj;
108 
109  void create(object obj, object _this) {
110  ::create(EVENT_UPLOAD, PHASE_NOTIFY, obj, 0);
111  __obj = _this;
112  obj->listen_event(this_object());
113  }
114  void notify(int event, mixed args) {
115  if ( zero_type(::get_object()) ) {
116  destruct(this_object());
117  return;
118  }
119  if ( _Server->query_config("xsl_disable_auto_reload") ) {
120  //MESSAGE("XSL: no auto reload (server config) ...");
121  return;
122  }
123 
124  __obj->load_xml_structure(); // reload
125  __obj->inc_stylesheet_changed(); // refresh xsl
126  }
127  mapping save() {
128  // do not save !
129  return 0;
130  }
131 
132  string describe() {
133  return "UpdateListener("+
134  (objectp(__obj)?__obj->get_identifier():"null")+")";
135  }
136  string _sprintf() { return describe(); }
137 
138 }
139 
140 public:
141 
142 /**
143  * load the document - initialize the xslt.Stylesheet() object.
144  * This is called when the XSL stylesheet is loaded.
145  *
146  */
147 protected:
148  void load_document()
149 {
150  XML = _Server->get_module("Converter:XML");
151  if ( !do_query_attribute(OBJ_VERSIONOF) )
152  load_xml_structure();
153 }
154 
155 public:
156 
157 
158 /**
159  * Add a dependant stylesheet and notify it when the content
160  * of this stylesheet changed.
161  *
162  * @param object o - the dependant stylesheet
163  */
164 void add_depend(object o)
165 {
166  mDepend[o] = 1;
167 }
168 
169 protected:
170  void xsl_add_depend(object obj)
171 {
172  array listenings = do_query_attribute("DOCXSL_DEPENDS") || ({ });
173  if ( search(listenings, obj) >= 0 )
174  return;
175  listenings += ({ obj });
176  do_set_attribute("DOCXSL_DEPENDS", listenings);
177 }
178 
179 public:
180 
181 protected:
182  void xsl_listen_depends()
183 {
184  array listenings = do_query_attribute("DOCXSL_DEPENDS") || ({ });
185  foreach(listenings, object l) {
186  if ( !objectp(mUpdates[l]) )
187  }
188 }
189 
190 public:
191 
192 
193 /**
194  * callback function to find a stylesheet.
195  *
196  * @param string uri - the uri to locate the stylesheet
197  * @return the stylesheet content or zero.
198  */
199 protected:
200  string|int
201 find_stylesheet(string uri, string language)
202 {
203  object obj;
204  object cont;
205  int i;
206 
207  LOG("find_stylesheet("+uri+","+language+")");
208  uri = uri[1..];
209  if ( uri[0] == '/' ) {
210  obj = _FILEPATH->path_to_object(uri);
211  if ( objectp(obj) ) {
212  string contstr = obj->get_language_content(language);
213  return contstr;
214  }
215  FATAL("Failed to find Stylesheet: "+ uri +" !");
216  return 0;
217  }
218 
219  cont = _ROOTROOM;
220  while ( (i=search(uri, "../")) == 0 && objectp(cont) ) {
221  cont = cont->get_environment();
222  uri = uri[3..];
223  }
224  LOG("Looking up in " + _FILEPATH->object_to_filename(cont));
225  obj = _FILEPATH->resolve_path(cont, uri);
226 
227  if ( objectp(obj) ) {
228  return obj->get_language_content(language);
229  }
230  return 0;
231 }
232 
233 public:
234 
235 protected:
236  int match_stylesheet(string uri)
237 {
238  if ( search(uri, "steam:") == 0 )
239  return 1;
240  return 0;
241 }
242 
243 public:
244 
245 protected:
246  object open_stylesheet(string uri)
247 {
248  sscanf(uri, "steam:/%s", uri);
249  object obj = _FILEPATH->path_to_object(uri);
250 
251  if ( !objectp(obj) ) {
252  FATAL("Stylesheet " + uri + " not found !");
253  return 0;
254  }
255  DEBUG_DOCXSL("open_stylesheet("+uri+") - " + (objectp(obj)?"success":"failed"));
256  return obj;
257 }
258 
259 public:
260 
261 protected:
262  string|int
263 read_stylesheet(object|string obj, string language, int position)
264 {
265  if ( stringp(obj) ) {
266  sscanf(obj, "steam://%s", obj);
267  obj = find_object(obj);
268  }
269 
270  if ( objectp(obj) ) {
271 
272  DEBUG_DOCXSL("read_stylesheet(language="+language+")");
273  string contstr = obj->get_language_content(language);
274  DEBUG_DOCXSL("length="+strlen(contstr) + " of " + obj->get_object_id());
275  return contstr;
276  }
277  DEBUG_DOCXSL("No stylesheet given for reading...");
278  return 0;
279 }
280 
281 public:
282 
283 protected:
284  void
285 close_stylesheet(object obj)
286 {
287 }
288 
289 public:
290 
291 protected:
292  void clean_xsls()
293 {
294  foreach(values(mStylesheets), object stylesheet)
295  destruct(stylesheet);
296 
297  mStylesheets = ([ ]);
298  foreach( indices(mDepend), object o ) {
299  if ( objectp(o) )
300  o->inc_stylesheet_changed();
301  }
302 }
303 
304 public:
305 
306 void inc_stylesheet_changed()
307 {
308  DEBUG_DOCXSL("Reloading XSL Stylesheet: "+get_identifier());
309  clean_xsls();
310 }
311 
312 
313 /**
314  * Unserialize a myobject tag from the xml description file.
315  *
316  * @param string data - myobject data.
317  * @return the corresponding object
318  */
319 protected:
320  object unserialize_myobject(string data)
321 {
322  int id = (int) data;
323  if ( id != 0 ) {
324  return (object)find_object(id);
325  }
326  string prefix, val;
327  if ( sscanf(data, "%s:%s", prefix, val) == 2 ) {
328  switch ( prefix ) {
329  case "group":
330  return MODULE_GROUPS->lookup(val);
331  case "instance":
332  case "orb":
333  object oOrb = _FILEPATH->path_to_object(val);
334  if (objectp(oOrb) && oOrb->get_object_class() & CLASS_DOCLPC &&
335  functionp(oOrb->provide_instance))
336  {
337  oOrb = oOrb->provide_instance();
338  catch{ oOrb->get_identifier(); };
339  }
340  return oOrb;
341  case "url":
342  return get_module("filepath:url")->path_to_object(val);
343  }
344  }
345 
346  switch(data) {
347  case "THIS":
348  return XML->THIS;
349  break;
350  case "ENV":
351  return XML->THIS->ENV;
352  case "CONV":
353  return XML;
354  break;
355  case "THIS_USER":
356  return XML->THIS_USER;
357  break;
358  case "SERVER":
359  return _Server;
360  break;
361  case "LAST":
362  return XML->LAST;
363  break;
364  case "ACTIVE":
365  return XML->ACTIVE;
366  break;
367  case "ENTRY":
368  return XML->ENTRY;
369  break;
370  case "XSL":
371  return XML->XSL;
372  break;
373  case "STEAM":
374  return MODULE_GROUPS->lookup("sTeam");
375  break;
376  case "local":
377  return this_object();
378  break;
379  case "master":
380  return master();
381  break;
382  case "server":
383  return _Server;
384  break;
385  case "ADMIN":
386  return MODULE_GROUPS->lookup("admin");
387  break;
388  default:
389  return _Server->get_module(data);
390  break;
391  }
392  return steam_error("Failed to serialize object:"+data);
393 }
394 
395 public:
396 
397 mapping get_default_map(string data)
398 {
399  switch (data) {
400  case "objects":
401  return XML->objXML;
402  break;
403  case "exits":
404  return XML->exitXML;
405  break;
406  case "links":
407  return XML->linkXML;
408  break;
409  case "users":
410  return XML->userXML;
411  break;
412  case "usersInv":
413  return XML->userInvXML;
414  break;
415  case "container":
416  return XML->containerXML;
417  break;
418  case "properties":
419  return 0;
420  }
421  FATAL("Warning: Default map of " + data + " not found in %s",
422  get_identifier());
423  return 0;
424 }
425 
426 mixed unserialize_var(mixed name, mixed defval)
427 {
428  return XML->get_var(name, defval);
429 }
430 
431 /**
432  *
433  *
434  * @param
435  * @return
436  * @see
437  */
438 mixed unserialize(NodeXML n)
439 {
440  function func;
441  if ( n->name == "myobject" || n->name == "o" ) {
442  return unserialize_myobject(n->data);
443  }
444  else if ( n->name == "var" ) {
445  object datanode = n->get_node("def");
446  if ( sizeof(datanode->children) ) {
447  FATAL("datanode with children found:" +
448  datanode->children[0]->data+"\n");
449  return unserialize_var(
450  n->get_node("name")->data,
451  unserialize(datanode->children[0]));
452  }
453  else
454  return unserialize_var(
455  n->get_node("name")->data,
456  n->get_node("def")->data);
457  }
458  else if ( n->name == "object" ) {
459  object node = n->get_node("path");
460  if ( objectp(node) ) {
461  if ( node->name="group" )
462  return MODULE_GROUPS->lookup(node->data);
463  else if ( node->name = "path" )
464  return get_module("filepath:tree")->path_to_object(node->data);
465  }
466  }
467  else if ( n->name == "maps" ) {
468  mapping res = ([ ]);
469  foreach(n->children, NodeXML children) {
470  mapping m = get_default_map(children->data);
471  res |= m;
472  }
473  return res;
474  }
475  else if ( n->name == "function" ) {
476  NodeXML obj = n->get_node("object");
477  NodeXML f = n->get_node("name");
478  NodeXML id = n->get_node("id");
479  switch ( obj->data ) {
480  case "local":
481  func = (function)this_object()[f->data];
482  break;
483  case "master":
484  object m = master();
485  func = [function](m[f->data]);
486  break;
487  case "server":
488  func = [function](_Server[f->data]);
489  break;
490  default:
491  object o;
492  o = _Server->get_module(obj->data);
493  if ( !objectp(o) ) {
494  FATAL("Module not found: " + obj->data);
495  return 0;
496  }
497  mixed err = catch {
498  func = o->find_function(f->data);
499  if (!functionp(func))
500  FATAL("Failed to find function " + f->data + " in %O", o);
501  };
502  if ( err != 0 ) {
503  FATAL("Failed to deserialize function: " + f->data +
504  " inside " + master()->describe_object(o) + "\n"+
505  err[0] + "\n" + sprintf("%O", err[1]));
506  return 0;
507  }
508  break;
509  }
510  if ( !functionp(func) )
511  LOG("unserialize_function: no functionp :"+
512  sprintf("%O\n",func));
513  return func;
514  }
515  return ::unserialize(n);
516 }
517 
518 /**
519  *
520  *
521  * @param
522  * @return
523  * @see
524  */
525 string compose_scalar(mixed val)
526 {
527  if ( objectp(val) ) {
528  if ( val == XML->THIS )
529  return "<myobject>THIS</myobject>";
530  else if ( val == XML->CONV )
531  return "<myobject>CONV</myobject>";
532  else if ( XML->THIS_USER == val )
533  return "<myobject>THIS_USER</myobject>";
534  else if ( _Server == val )
535  return "<myobject>SERVER</myobject>";
536  else if ( XML->LAST == val )
537  return "<myobject>LAST</myobject>";
538  else if ( XML->ACTIVE == val )
539  return "<myobject>ACTIVE</myobject>";
540  else if ( XML->ENTRY == val )
541  return "<myobject>ENTRY</myobject>";
542  else if ( XML->XSL == val )
543  return "<myobject>XSL</myobject>";
544  }
545  return ::compose_scalar(val);
546 }
547 
548 /**
549  *
550  *
551  * @param
552  * @return
553  * @see
554  */
555 mixed compose(mixed m)
556 {
557  if ( functionp(m) ) {
558  object o = function_object(m);
559  if ( !objectp(o) ) {
560  LOG("function without object:" + function_name(m));
561  return "";
562  }
563  if ( o == this_object())
564  return "<function><object>local</object><name>"+function_name(m)+
565  "</name></function>";
566  else if ( o == master() )
567  return "<function><object>master</object>"+
568  "<name>"+function_name(m) + "</name></function>";
569  else if ( o == _Server )
570  return "<function><object>server</object>"+
571  "<name>"+function_name(m) + "</name></function>";
572  else
573  return "<function><id>"+o->get_object_id()+
574  "</id><object>"+o->get_identifier()+"</object>"+
575  "<name>"+function_name(m) + "</name></function>";
576  }
577  return ::compose(m);
578 }
579 
580 object find_xml(void|object xml)
581 {
582  string name;
583  if ( objectp(xml) )
584  return xml;
585 
586  if ( objectp(oEnvironment) ) {
587  xml = oEnvironment->get_object_byname(get_identifier()+".xml");
588  sscanf(get_identifier(), "%s.%*s", name);
589 
590  if ( !objectp(xml) ) {
591  xml = oEnvironment->get_object_byname(name+".xgl");
592  }
593  }
594  if ( !objectp(xml) )
595  xml = do_query_attribute(DOC_XSL_XML);
596 
597  // fall back to public Stylesheet, if available
598  if ( !objectp(xml) || xml->status() < 0 || xml->status() == PSTAT_DELETED ) {
599  xml = OBJ("/stylesheets/public.xsl.xml");
600  }
601  return xml;
602 }
603 
604 array load_imports(object n)
605 {
606  array nodes;
607  if ( objectp(n) )
608  nodes = n->get_nodes("language");
609 
610  if ( arrayp(nodes) )
611  {
612  foreach(nodes, object node) {
613  array imports = ({ });
614 
615  if ( stringp(node->attributes->auto) ) {
616  object languageCont = OBJ("/languages");
617  if ( objectp(languageCont) ) {
618  array languageNodes = ({ });
619  foreach( languageCont->get_inventory_by_class(CLASS_DOCXML), object langXML)
620  {
621  NodeXML langnode = xmlCache.parse(langXML);
622  imports += langnode->get_nodes("import");
623  languageNodes += ({ langnode });
624  }
625  node->replace_node( languageNodes );
626  }
627  }
628  else
629  imports = node->get_nodes("import");
630 
631  foreach(imports, object imp) {
632  mixed e = catch {
633  object importNodes = parse_import(imp->attributes->file);
634  if ( !objectp(importNodes) )
635  continue;
636  imp->replace_node(importNodes,
637  imp->attributes->xpath);
638  };
639  if ( e ) {
640  if ( !stringp(imp->attributes->file) )
641  FATAL("Importing language: "+
642  "Missing file attribute on %s tag !",
643  imp->get_name());
644  else
645  FATAL("Failed to import file: %s, %O\%O",
646  imp->attributes->file, e[0], e[1]);
647  }
648  }
649  }
650  }
651  return nodes;
652 }
653 
654 protected:
655  int in_backtrace(object obj)
656 {
657  if ( !objectp(obj) || !functionp(obj->get_object) ||
658  !objectp(obj->get_object()) )
659  return 0;
660 
661  foreach( backtrace(), mixed preceed ) {
662  if ( function_object(preceed[2]) == obj->get_object() )
663  return 1;
664  }
665  return 0;
666 }
667 
668 public:
669 
670 /**
671  * Load the related xgl (or xsl.xml) document
672  *
673  */
674 void load_xml_structure(void|object xml)
675 {
676  if ( in_backtrace(get_module("tar")) ) {
677  call(load_xml_structure, 1);
678  return;
679  }
680 
681  return; // do not load xml for old versions
682 
683  // factories must be loaded
684  if ( !objectp(get_factory(CLASS_OBJECT)) ) {
685  call(load_xml_structure, 1);
686  return;
687  }
688 
689  object xmlLock = xmlMutex->trylock();
690  if ( !objectp(xmlLock) )
691  steam_error("Failed to obtain xml-lock in %O", get_identifier());
692 
693  mixed xmlErr = catch {
694  xml = find_xml(xml);
695  if ( objectp(xml) ) {
696  xsl_add_depend(xml);
697 
698  NodeXML n = parse_data(xml->get_content());
699  array nodes = load_imports(n);
700  mLang = read_languages(nodes);
701 
702  object xmlscript = do_query_attribute(DOC_XSL_PIKE);
703 
704  if ( !objectp(n) ) {
705  throw( ({ "Root node '<structure>' not found in "+
706  "XML description file !", backtrace() }) );
707  }
708  if ( n->name == "structure" ) {
709  if ( (stringp(n->attributes->generate) &&
710  n->attributes->generate == "pike") ||
711  stringp(n->get_pi()["steam"]) )
712  {
713  object oldeuid = geteuid();
714  seteuid(get_creator());
715  hook_xml(xml, n);
716  seteuid(oldeuid);
717  }
718  else {
719  if ( objectp(xmlscript) )
720  xmlscript->delete();
721  do_set_attribute(DOC_XSL_PIKE);
722  mXML = xmlTags(n);
723  }
724  }
725  }
726  else {
727  if ( CALLER != this_object() ) {
728  destruct(xmlLock);
729  FATAL("No description file ("+get_identifier()+".xml) found for Stylesheet !");
730  return;
731  }
732  }
733  xsl_listen_depends();
734  };
735 
736  xmlIsLoad = true;
737  if ( xmlErr ) {
738  FATAL("Failed to load xml structure for %s\n%O",
739  _FILEPATH->object_to_filename(this_object()),
740  xmlErr);
741  clean_xsls();
742  if ( CALLER != this_object() ) {
743  destruct(xmlLock);
744  throw(xmlErr);
745  }
746  }
747  destruct(xmlLock);
748 }
749 
750 protected:
751  string read_import(string fname)
752 {
753  object f = _FILEPATH->path_to_object(fname);
754  if ( !objectp(f) )
755  return 0;
756 
757  xsl_add_depend(f);
758  return f->get_content();
759 }
760 
761 public:
762 
763 protected:
764  object parse_import(string fname)
765 {
766  object f = _FILEPATH->path_to_object(fname);
767  if ( !objectp(f) )
768  return 0;
769 
770  xsl_add_depend(f);
771  return xmlCache.parse(f);
772 }
773 
774 public:
775 
776 int check_xgl_consistency(object xmlscript, object xmlObj)
777 {
778  if ( objectp(xmlscript) ) {
779  object script_xml = xmlscript->query_attribute(DOCLPC_XGL);
780  if ( objectp(script_xml) && do_query_attribute(DOC_XSL_XML) != script_xml )
781  {
782  FATAL("DocXSL: mismatched Pike-Script and XGL File !");
783  do_set_attribute(DOC_XSL_XML);
784  xmlObj->set_attribute(DOC_LAST_MODIFIED, time());
785  return 0;
786  }
787  }
788  return 1;
789 }
790 
791 
792 protected:
793  void hook_xml(object xmlObj, void|object rootNode)
794 {
795  object xmlscript = do_query_attribute(DOC_XSL_PIKE);
796  if (objectp(xmlscript) && xmlscript->status() < 0)
797  xmlscript=0;
798 
799  check_xgl_consistency(xmlscript, xmlObj);
800  if ( xmlObj == OBJ("/stylesheets/public.xsl.xml") ) {
801  object oxgl = do_query_attribute(DOC_XSL_XML);
802  }
803 
804  // check for update
805  int tss = 0;
806  if (objectp(xmlscript))
807  tss = xmlscript->query_attribute(DOCLPC_INSTANCETIME);
808  int tsc = xmlObj->query_attribute(DOC_LAST_MODIFIED);
809 
810  DEBUG_DOCXSL("Timestamp test when hooking XML: (tss=%d, tsc=%d)", tss, tsc);
811  // also see if XGL file has not changed !
812 
813  if ( tss > tsc && do_query_attribute(DOC_XSL_XML) == xmlObj )
814  return;
815 
816  string pikecode = XMLCodegen.codegen(xmlObj, read_import);
817  set_attribute(DOC_XSL_XML, xmlObj);
818 
819  DEBUG_DOCXSL("Updating Pike XML Generation ...");
820  string scriptname = get_object_id() + ".pike";
821  if ( !objectp(xmlscript) )
822  xmlscript = get_factory(CLASS_DOCUMENT)->execute(([ "name": scriptname,]));
823 
824  mixed err;
825  do_set_attribute(DOC_XSL_PIKE, xmlscript);
826  xmlscript->set_attribute(DOCLPC_XGL, xmlObj);
827 
828  if ( !check_xgl_consistency(xmlscript, xmlObj) )
829  steam_error("DocXSL: consistency check failed .... !");
830 
831  err = catch(xmlscript->set_content(pikecode));
832  if ( arrayp(xmlscript->get_errors()) && sizeof(xmlscript->get_errors()) > 0 ) {
833  FATAL("Errors in xml Script: %O", xmlscript->get_errors());
834  }
835  catch(xmlscript->sanction_object(GROUP("everyone"),SANCTION_READ|SANCTION_EXECUTE));
836  if ( err )
837  FATAL("Failed to hook xml content: %s", err[0]);
838  xmlscript->provide_instance(); // should create an instance when loaded
839 }
840 
841 public:
842 
843 /**
844  * Get the xml structure as a mapping.
845  *
846  * @return the xml structure mapping
847  */
848 mapping get_xml_structure()
849 {
850  return copy_value(mXML);
851 }
852 
853 
854 
855 /**
856  * Resolves the show function of the xml tag definition and returns a
857  * mapping or function with corresponding informations.
858  *
859  * @param NodeXML n - the node to convert
860  * @return mapping or function of node information
861  */
862 function|mapping xmlTagShowFunc(NodeXML n)
863 {
864  if ( !objectp(n) )
865  return 0;
866  NodeXML f = n->get_node("f");
867  if ( !objectp(f) ) {
868  NodeXML m = n->get_node("map");
869  if ( !objectp(m) )
870  m = n->get_node("structure");
871  if ( !objectp(m) )
872  return 0;
873  mapping res = ([ ]);
874  res += xmlTags(m);
875  if ( m->attributes->name )
876  res["name"] = m->attributes->name;
877 
878  foreach(m->children, object tag) {
879  if ( tag->name == "tag" ) {
880  res[tag->attributes->name] += xmlTag(tag);
881  }
882  else if ( tag->name == "def" ) {
883  mapping def = get_default_map(tag->data);
884  if ( mappingp(def) ) {
885  res += def;
886  }
887  }
888  }
889  return res;
890  }
891  function func;
892  object obj;
893 
894  NodeXML na = f->get_node("n");
895  NodeXML o = f->get_node("o");
896  if ( !objectp(n) )
897  THROW("Function tag (f) has no sub tag 'n' !", E_ERROR);
898 
899  if ( !objectp(o) )
900  obj = XML;
901  else {
902  obj = unserialize_myobject(o->data);
903  if (!objectp(obj))
904  steam_error("Failed to find object " + o->data);
905  }
906 
907 
908  mixed err = catch {
909  if ( !objectp(na) )
910  steam_user_error("Missing 'n' Node inside <f> to specify function!");
911  func = [function]obj->find_function(na->data);
912  if (!functionp(func))
913  FATAL("Failed to find function " + na->data + " in %O", obj);
914  };
915  if ( err != 0 ) {
916  FATAL("Failed to deserialize function in: " + f->dump() +
917  return 0;
918  }
919  return func;
920 }
921 
922 /**
923  * Convert possible calls to Attribute functions to new string format
924  * (previously numbers where used.
925  *
926  * @param string fname - name of the function to be called.
927  * @param array params - the params to convert
928  * @return new parameters
929  */
930 protected:
931  array attribute_conversion(string fname, array params)
932 {
933  if ( fname == "query_attribute" ) {
934  for ( int i = sizeof(params) - 1; i >= 0; i-- ) {
935  if ( stringp(mAttrConv[params[i]]) ) {
936  params[i] = mAttrConv[params[i]];
937  }
938  }
939  }
940  return params;
941 }
942 
943 public:
944 
945 /**
946  * Resolve the xml tag call function definition. Which function to call
947  * for the tag.
948  *
949  * @param NodeXML n - the Node <tag>
950  * @return corresponding information array with structure used by converter
951  */
952 array xmlTagCallFunc(NodeXML n)
953 {
954  array res, params;
955 
956  if ( !objectp(n) )
957  return ({ XML->THIS, "null", ({ }), 0 });
958 
959  NodeXML f = n->get_node("n");
960  NodeXML o = n->get_node("o");
961  NodeXML p = n->get_node("p");
962 
963  object obj;
964 
965  if ( !objectp(f) )
966  THROW("No Node n (function-name) found at function tag", E_ERROR);
967 
968  if ( !objectp(o) )
969  obj = XML->THIS;
970  else
971  obj = unserialize_myobject(o->data);
972  if ( objectp(p) ) {
973  p->data = String.trim_whites(p->data - "\n");
974  if ( stringp(p->data) && strlen(p->data) > 0 )
975  steam_error("Found data in param tag - all params need to be "+
976  "in type tags like <int>42</int>.\n"+
977  "Context: "+n->get_xml()+"\nOffending:\n"+p->data);
978  params = xmlArray(p);
979  params = attribute_conversion(f->data, params);
980  }
981  if ( !arrayp(params) )
982  params = ({ });
983 
984  res = ({ obj, f->data, params, 0 });
985  return res;
986 }
987 
988 private:
989 private array xmlTag(NodeXML n)
990 {
991  array res;
992  object call = n->get_node("call/f");
993  res = xmlTagCallFunc(call);
994  res[3] = xmlTagShowFunc(n->get_node("show"));
995  return res;
996 }
997 
998 public:
999 
1000 private:
1001 private mapping xmlTags(NodeXML n)
1002 {
1003  mapping res = ([ ]);
1004  foreach(n->children, object node) {
1005  if ( node->name == "class" ) {
1006  int t;
1007  string type = node->attributes->type;
1008  t = (int) type;
1009  if ( t == 0 ) {
1010  object f = _Server->get_factory(type);
1011  if ( objectp(f) )
1012  t = f->get_class_id();
1013  else
1014  steam_error("Unable to find factory for " + type);
1015  }
1016  if ( t == 0 ) {
1017  steam_error("Fatal error loading xml structure: " +
1018  "Unable to identify class-id for '" + type + "'.");
1019  }
1020  res[t] = ([ ]);
1021  foreach(node->get_nodes("tag"), object tag) {
1022  if ( !objectp(tag->get_node("call") ) )
1023  continue; // ignore nodes with call
1024  res[t][tag->attributes->name] = xmlTag(tag);
1025  }
1026  }
1027  }
1028  if ( n->attributes->name )
1029  res["name"] = n->attributes->name;
1030 
1031  return res;
1032 }
1033 
1034 public:
1035 
1036 protected:
1037  mapping read_languages(array(NodeXML) nodes, void|object xml)
1038 {
1039  mapping res = ([ ]);
1040 
1041  if ( !arrayp(nodes) )
1042  return res;
1043 
1044  foreach(nodes, object node) {
1045  if ( node->name == "language" ) {
1046  res[node->attributes->name] = ([ ]);
1047  foreach(node->get_nodes("term"), object term) {
1048  res[node->attributes->name]["{"+term->attributes->name+"}"]=term->get_data();
1049  }
1050  }
1051  }
1052  return res;
1053 }
1054 
1055 public:
1056 
1057 protected:
1058  void
1059 content_finished()
1060 {
1061  // successfull upload...
1062  ::content_finished();
1063  clean_xsls();
1064 }
1065 
1066 public:
1067 
1068 /**
1069  * Get the content of this object with replacements for the language.
1070  * This means that templates like {TEMPLATE_NAME} are replaced with
1071  * the corresponding entry in the xml file. If the language is not
1072  * defined then english is used as default language.
1073  *
1074  * @param void|string language - the language
1075  * @return the stylesheet.
1076  */
1077 
1078 string get_language_content(void|string language)
1079 {
1080  string content;
1081  if ( stringp(language) ) {
1082  string str = ::get_content();
1083 
1084  if ( mappingp(mLang[language]) ) {
1085  mapping m = mLang[language];
1086  content = replace(str, indices(m), values(m));
1087  }
1088  else if ( mappingp(mLang->english) ) {
1089  content = replace(str, indices(mLang->english),values(mLang->english));
1090  }
1091  else {
1092  //FATAL("No Languages initialized for %s", get_identifier());
1093  return str;
1094  }
1095  return content;
1096  }
1097  else
1098  return ::get_content();
1099 }
1100 
1101 
1102 /**
1103  * Get the xslt.Stylesheet() object.
1104  *
1105  * @return the stylesheet.
1106  */
1107 object get_stylesheet(string|void language)
1108 {
1109  mixed err;
1110 
1111  object lock = xmlMutex->lock();
1112  if ( !xmlIsLoad ) {
1113  destruct(lock);
1114  steam_error("DocXSL: Cannot retrieve data from uninitialized Stylesheet!");
1115  }
1116 
1117  if ( !stringp(language) )
1118  language = "english";
1119 
1120  if ( !objectp(mStylesheets[language]) ) {
1121  // now I got some (the real one, but it needs internationalization)
1122  object stylesheet;
1123  err = catch {
1124 
1125  string xsl_code = get_language_content(language);
1126  xsl_code = replace( xsl_code, "&nbsp;", "&#160;" );
1127 
1128  stylesheet = xslt.Stylesheet();
1129  mStylesheets[language] = stylesheet;
1130 
1131  stylesheet->set_include_callbacks(match_stylesheet,
1132  open_stylesheet,
1133  read_stylesheet,
1134  close_stylesheet);
1135  stylesheet->set_language(language);
1136  stylesheet->set_content(xsl_code);
1137  };
1138  if ( err != 0 ) {
1139  destruct(stylesheet);
1140  mStylesheets[language] = 0;
1141  err[0] = "Error Parsing " + get_identifier() + "\n" + err[0];
1142  destruct(lock);
1143  throw(err);
1144  }
1145  }
1146 
1147  destruct(lock);
1148  if ( objectp(mStylesheets[language]) ) {
1149  return mStylesheets[language];
1150  }
1151  return mStylesheets["english"];
1152 }
1153 
1154 string get_method()
1155 {
1156  object xsl = get_stylesheet();
1157  if ( objectp(xsl) )
1158  return xsl->get_method();
1159  return "plain";
1160 }
1161 
1162 string get_encoding()
1163 {
1164  object xsl = get_stylesheet();
1165  if ( objectp(xsl) )
1166  return xsl->get_encoding();
1167  return "utf-8";
1168 }
1169 
1170 array get_styles()
1171 {
1172  return ({ "content", "attributes", "access", "annotations" });
1173 }
1174 
1175 bool check_swap() { return false; } // do not swap out stylesheets
1176 int get_object_class() { return ::get_object_class() | CLASS_DOCXSL; }
1177 string get_class() { return "DocXSL"; }
1178 
1179 
1180 {
1181  // check xsl.xml here...
1182  MESSAGE("* Testing DocXSL functionality and libxslt ...");
1183  MESSAGE("Creating test container...");
1184  object testcont = OBJ("/home/steam/__xsltestcont");
1185  if ( !objectp(testcont) ) {
1186  testcont=get_factory(CLASS_CONTAINER)->execute( (["name":"__xsltestcont"]) );
1187  testcont->move(OBJ("/home/steam"));
1188  } else {
1189  foreach(testcont->get_inventory(), object inv) {
1190  inv->delete();
1191  }
1192  }
1193  move(testcont);
1194 
1195  MESSAGE("Creating language file...");
1196  object languages = get_factory(CLASS_DOCUMENT)->execute( (["name":"terms.xml",]) );
1197  MESSAGE("Language file is %O", languages);
1198  languages->set_content("<?xml version='1.0' encoding='utf-8'?>\n<language><term name='TERM1'>term_1</term></language>\n");
1199  languages->move(testcont);
1200 
1201  string pathname = _FILEPATH->object_to_filename(languages);
1202 
1203  MESSAGE("Creating XGL File...");
1204  object xgl = get_factory(CLASS_DOCUMENT)->execute( (["name": "test.xgl", ]) );
1205  xgl->set_content("<?xml version='1.0' encoding='utf-8'?>\n<structure generate='pike'>\n<class type='Object'>\n<tag name='test'><call><function name='get_identifier' /></call><show><function name='show' /></show></tag></class><language name='english'><import file='"+pathname+"' xpath='term' /></language>\n</structure>\n");
1206 
1207  xgl->move(testcont);
1208  MESSAGE("Testing xsl...");
1209  set_content("<?xml version='1.0' encoding='utf-8'?>\n<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>\n<xsl:output method='html' encoding='utf-8' />\n<xsl:template match='Object/test'>{TERM1}: testing</xsl:template>\n</xsl:stylesheet>");
1210  if (find_xml()!=xgl)
1211  steam_error("find_xml() does not return proper XGL file !");
1212  load_xml_structure(xgl);
1213 
1214  object pikescript = do_query_attribute(DOC_XSL_PIKE);
1215  if ( !objectp(pikescript) )
1216  steam_error("Generation of Pike-Script failed !");
1217 
1218  MESSAGE("Looking for UpdateListener ...");
1219  if ( !objectp(mUpdates[xgl]) )
1220  steam_error("No Update Listener for XGL ...");
1221  if ( !objectp(mUpdates[languages]) ) {
1222  MESSAGE("Dumping Update Listeners:\n%O", indices(mUpdates));
1223  steam_error("No Update Listener for Language Term File (%O) ...", languages);
1224  }
1225  call(test_more, 1, pikescript, xgl, languages, testcont, "", 0);
1226 }
1227 
1228 void test_more(object pikescript, object xgl, object languages, object testcont, string xml, int test)
1229 {
1230  if ( pikescript->status() != PSTAT_DISK ) {
1231  pikescript->drop();
1232  call(test_more, 5, pikescript, xgl, languages, testcont, xml, test);
1233  return;
1234  }
1235  switch(test) {
1236  case 0:
1237  MESSAGE("Testing xml generation !");
1238  mapping params = ([ "this_user": USER("root")->get_object_id(), ]);
1239  MESSAGE("DOC_XSL_PIKE=%O", do_query_attribute(DOC_XSL_PIKE));
1240  // xgl file controls xml generatoin - should be simple xml
1241  MESSAGE("XML Generated: \n%s---", xml);
1242  if ( search(xml, "XGL File: "+_FILEPATH->object_to_filename(xgl)) == -1 )
1243  steam_error("XGL Test: Used wrong xgl file for XML Generation !");
1244  MESSAGE("OUTPUT = %s", result);
1245  call(test_more, 2, pikescript, xgl, languages, testcont, xml, 1);
1246  break;
1247  default:
1248  MESSAGE("Testing update of depend...");
1249  languages->set_content("<?xml version='1.0' encoding='utf-8'?>\n<language><term name='TERM1'>modified</term></language>\n");
1250  if ( search(result, "modified") == -1 )
1251  steam_error("Language File Update did not update Term Definition in %O !"+
1252  "\nDump: %s", languages, result);
1253  languages->delete();
1254  testcont->delete();
1255  xgl->delete();
1256  delete();
1257  }
1258 }
1259 
1260 
1261 };