Code

fix 64-bit issues with width of npos -- patch #1675697 from mellum
[inkscape.git] / src / extension / implementation / script.cpp
1 /** \file
2  * Code for handling extensions (i.e.\ scripts).
3  */
4 /*
5  * Authors:
6  *   Bryce Harrington <bryce@osdl.org>
7  *   Ted Gould <ted@gould.cx>
8  *
9  * Copyright (C) 2002-2005 Authors
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 /*
15 TODO:
16 FIXME:
17   After Inkscape makes a formal requirement for a GTK version above 2.11.4, please
18   replace all the instances of ink_ext_XXXXXX in this file that represent
19   svg files with ink_ext_XXXXXX.svg . Doing so will prevent errors in extensions
20   that call inkscape to manipulate the file.
21   
22   "** (inkscape:5848): WARNING **: Format autodetect failed. The file is being opened as SVG."
23   
24   references:
25   http://www.gtk.org/api/2.6/glib/glib-File-Utilities.html#g-mkstemp
26   http://ftp.gnome.org/pub/gnome/sources/glib/2.11/glib-2.11.4.changes
27   http://developer.gnome.org/doc/API/2.0/glib/glib-File-Utilities.html#g-mkstemp
28   
29   --Aaron Spike
30 */
31 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
33 #ifdef HAVE_CONFIG_H
34 # include <config.h>
35 #endif
37 #include <unistd.h>
39 #include <errno.h>
40 #include <gtkmm.h>
42 #include "ui/view/view.h"
43 #include "desktop-handles.h"
44 #include "selection.h"
45 #include "sp-namedview.h"
46 #include "io/sys.h"
47 #include "prefs-utils.h"
48 #include "../system.h"
49 #include "extension/effect.h"
50 #include "extension/output.h"
51 #include "extension/db.h"
52 #include "script.h"
53 #include "dialogs/dialog-events.h"
55 #include "util/glib-list-iterators.h"
59 #ifdef WIN32
60 #include <windows.h>
61 #include <sys/stat.h>
62 #include "registrytool.h"
63 #endif
67 /** This is the command buffer that gets allocated from the stack */
68 #define BUFSIZE (255)
72 /* Namespaces */
73 namespace Inkscape {
74 namespace Extension {
75 namespace Implementation {
79 //Interpreter lookup table
80 struct interpreter_t {
81         gchar * identity;
82         gchar * prefstring;
83         gchar * defaultval;
84 };
87 static interpreter_t interpreterTab[] = {
88         {"perl",   "perl-interpreter",   "perl"   },
89         {"python", "python-interpreter", "python" },
90         {"ruby",   "ruby-interpreter",   "ruby"   },
91         {"shell",  "shell-interpreter",  "sh"     },
92         { NULL,    NULL,                  NULL    }
93 };
97 /**
98  * Look up an interpreter name, and translate to something that
99  * is executable
100  */
101 static Glib::ustring
102 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
105     Glib::ustring interpName = interpNameArg;
107     interpreter_t *interp;
108     bool foundInterp = false;
109     for (interp =  interpreterTab ; interp->identity ; interp++ ){
110         if (interpName == interp->identity) {
111             foundInterp = true;
112             break;
113         }
114     }
116     // Do we have a supported interpreter type?
117     if (!foundInterp)
118         return "";
119     interpName = interp->defaultval;
121     // 1.  Check preferences
122     gchar *prefInterp = (gchar *)prefs_get_string_attribute(
123                                 "extensions", interp->prefstring);
125     if (prefInterp) {
126         interpName = prefInterp;
127         return interpName;
128     }
130 #ifdef _WIN32
132     // 2.  Windows.  Try looking relative to inkscape.exe
133     RegistryTool rt;
134     Glib::ustring fullPath;
135     Glib::ustring path;
136     Glib::ustring exeName;
137     if (rt.getExeInfo(fullPath, path, exeName)) {
138         Glib::ustring interpPath = path;
139         interpPath.append("\\");
140         interpPath.append(interpName);
141         interpPath.append("\\");
142         interpPath.append(interpName);
143         interpPath.append(".exe");
144         struct stat finfo;
145         if (stat(interpPath .c_str(), &finfo) ==0) {
146             g_message("Found local interpreter, '%s',  Size: %d",
147                       interpPath .c_str(),
148                       (int)finfo.st_size);
149             return interpPath;
150         }                       
151     }
153     // 3. Try searching the path
154     char szExePath[MAX_PATH];
155     char szCurrentDir[MAX_PATH];
156     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
157     unsigned int ret = (unsigned int)FindExecutable(
158                   interpName.c_str(), szCurrentDir, szExePath);
159     if (ret > 32) {
160         interpName = szExePath;
161         return interpName;
162     }
164 #endif // win32
167     return interpName;
175 /**
176     \return    A script object
177     \brief     This function creates a script object and sets up the
178                variables.
180    This function just sets the command to NULL.  It should get built
181    officially in the load function.  This allows for less allocation
182    of memory in the unloaded state.
183 */
184 Script::Script() :
185     Implementation()
190 /**
191  *   brief     Destructor
192  */
193 Script::~Script()
199 /**
200     \return    A string with the complete string with the relative directory expanded
201     \brief     This function takes in a Repr that contains a reldir entry
202                and returns that data with the relative directory expanded.
203                Mostly it is here so that relative directories all get used
204                the same way.
205     \param     reprin   The Inkscape::XML::Node with the reldir in it.
207     Basically this function looks at an attribute of the Repr, and makes
208     a decision based on that.  Currently, it is only working with the
209     'extensions' relative directory, but there will be more of them.
210     One thing to notice is that this function always returns an allocated
211     string.  This means that the caller of this function can always
212     free what they are given (and should do it too!).
213 */
214 Glib::ustring
215 Script::solve_reldir(Inkscape::XML::Node *reprin) {
217     gchar const *s = reprin->attribute("reldir");
219     if (!s) {
220         Glib::ustring str = sp_repr_children(reprin)->content();
221         return str;
222     }
224     Glib::ustring reldir = s;
226     if (reldir == "extensions") {
228         for (unsigned int i=0;
229             i < Inkscape::Extension::Extension::search_path.size();
230             i++) {
232             gchar * fname = g_build_filename(
233                Inkscape::Extension::Extension::search_path[i],
234                sp_repr_children(reprin)->content(),
235                NULL);
236             Glib::ustring filename = fname;
237             g_free(fname);
239             if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
240                 return filename;
242         }
243     } else {
244         Glib::ustring str = sp_repr_children(reprin)->content();
245         return str;
246     }
248     return "";
253 /**
254     \return   Whether the command given exists, including in the path
255     \brief    This function is used to find out if something exists for
256               the check command.  It can look in the path if required.
257     \param    command   The command or file that should be looked for
259     The first thing that this function does is check to see if the
260     incoming file name has a directory delimiter in it.  This would
261     mean that it wants to control the directories, and should be
262     used directly.
264     If not, the path is used.  Each entry in the path is stepped through,
265     attached to the string, and then tested.  If the file is found
266     then a TRUE is returned.  If we get all the way through the path
267     then a FALSE is returned, the command could not be found.
268 */
269 bool
270 Script::check_existance(const Glib::ustring &command)
273     // Check the simple case first
274     if (command.size() == 0) {
275         return false;
276     }
278     //Don't search when it contains a slash. */
279     if (command.find(G_DIR_SEPARATOR) != command.npos) {
280         if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
281             return true;
282         else
283             return false;
284     }
287     Glib::ustring path; 
288     gchar *s = (gchar *) g_getenv("PATH");
289     if (s)
290         path = s;
291     else
292        /* There is no `PATH' in the environment.
293            The default search path is the current directory */
294         path = G_SEARCHPATH_SEPARATOR_S;
296     std::string::size_type pos  = 0;
297     std::string::size_type pos2 = 0;
298     while ( pos < path.size() ) {
300         Glib::ustring localPath;
302         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
303         if (pos2 == path.npos) {
304             localPath = path.substr(pos);
305             pos = path.size();
306         } else {
307             localPath = path.substr(pos, pos2-pos);
308             pos = pos2+1;
309         }
310         
311         //printf("### %s\n", localPath.c_str());
312         Glib::ustring candidatePath = 
313                       Glib::build_filename(localPath, command);
315         if (Inkscape::IO::file_test(candidatePath .c_str(),
316                       G_FILE_TEST_EXISTS))
317             return true;
319     }
321     return false;
328 /**
329     \return   none
330     \brief    This function 'loads' an extention, basically it determines
331               the full command for the extention and stores that.
332     \param    module  The extention to be loaded.
334     The most difficult part about this function is finding the actual
335     command through all of the Reprs.  Basically it is hidden down a
336     couple of layers, and so the code has to move down too.  When
337     the command is actually found, it has its relative directory
338     solved.
340     At that point all of the loops are exited, and there is an
341     if statement to make sure they didn't exit because of not finding
342     the command.  If that's the case, the extention doesn't get loaded
343     and should error out at a higher level.
344 */
346 bool
347 Script::load(Inkscape::Extension::Extension *module)
349     if (module->loaded())
350         return TRUE;
352     helper_extension = "";
354     /* This should probably check to find the executable... */
355     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
356     Glib::ustring command_text;
357     while (child_repr != NULL) {
358         if (!strcmp(child_repr->name(), "script")) {
359             child_repr = sp_repr_children(child_repr);
360             while (child_repr != NULL) {
361                 if (!strcmp(child_repr->name(), "command")) {
362                     command_text = solve_reldir(child_repr);
364                     const gchar *interpretstr = child_repr->attribute("interpreter");
365                     if (interpretstr != NULL) {
366                         Glib::ustring interpString =
367                             resolveInterpreterExecutable(interpretstr);
368                         interpString .append(" \"");
369                         interpString .append(command_text);
370                         interpString .append("\"");                        
371                         command_text = interpString;
372                     }
373                 }
374                 if (!strcmp(child_repr->name(), "helper_extension"))
375                     helper_extension = sp_repr_children(child_repr)->content();
376                 child_repr = sp_repr_next(child_repr);
377             }
379             break;
380         }
381         child_repr = sp_repr_next(child_repr);
382     }
384     g_return_val_if_fail(command_text.size() > 0, FALSE);
386     command = command_text;
387     return true;
391 /**
392     \return   None.
393     \brief    Unload this puppy!
394     \param    module  Extension to be unloaded.
396     This function just sets the module to unloaded.  It free's the
397     command if it has been allocated.
398 */
399 void
400 Script::unload(Inkscape::Extension::Extension *module)
402     command          = "";
403     helper_extension = "";
409 /**
410     \return   Whether the check passed or not
411     \brief    Check every dependency that was given to make sure we should keep this extension
412     \param    module  The Extension in question
414 */
415 bool
416 Script::check(Inkscape::Extension::Extension *module)
418     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
419     while (child_repr != NULL) {
420         if (!strcmp(child_repr->name(), "script")) {
421             child_repr = sp_repr_children(child_repr);
422             while (child_repr != NULL) {
423                 if (!strcmp(child_repr->name(), "check")) {
424                     Glib::ustring command_text = solve_reldir(child_repr);
425                     if (command_text.size() > 0) {
426                         /* I've got the command */
427                         bool existance = check_existance(command_text);
428                         if (!existance)
429                             return FALSE;
430                     }
431                 }
433                 if (!strcmp(child_repr->name(), "helper_extension")) {
434                     gchar const *helper = sp_repr_children(child_repr)->content();
435                     if (Inkscape::Extension::db.get(helper) == NULL) {
436                         return FALSE;
437                     }
438                 }
440                 child_repr = sp_repr_next(child_repr);
441             }
443             break;
444         }
445         child_repr = sp_repr_next(child_repr);
446     }
448     return true;
453 /**
454     \return   A dialog for preferences
455     \brief    A stub funtion right now
456     \param    module    Module who's preferences need getting
457     \param    filename  Hey, the file you're getting might be important
459     This function should really do something, right now it doesn't.
460 */
461 Gtk::Widget *
462 Script::prefs_input(Inkscape::Extension::Input *module,
463                     const gchar *filename)
465     /*return module->autogui(); */
466     return NULL;
471 /**
472     \return   A dialog for preferences
473     \brief    A stub funtion right now
474     \param    module    Module whose preferences need getting
476     This function should really do something, right now it doesn't.
477 */
478 Gtk::Widget *
479 Script::prefs_output(Inkscape::Extension::Output *module)
481     return module->autogui(NULL, NULL); 
486 /**
487     \return   A dialog for preferences
488     \brief    A stub funtion right now
489     \param    module    Module who's preferences need getting
491     This function should really do something, right now it doesn't.
492 */
493 Gtk::Widget *
494 Script::prefs_effect(Inkscape::Extension::Effect *module,
495                      Inkscape::UI::View::View *view)
498     SPDocument * current_document = view->doc();
500     using Inkscape::Util::GSListConstIterator;
501     GSListConstIterator<SPItem *> selected =
502            sp_desktop_selection((SPDesktop *)view)->itemList();
503     Inkscape::XML::Node * first_select = NULL;
504     if (selected != NULL) 
505            first_select = SP_OBJECT_REPR(*selected);
507     return module->autogui(current_document, first_select);
513 /**
514     \return  A new document that has been opened
515     \brief   This function uses a filename that is put in, and calls
516              the extension's command to create an SVG file which is
517              returned.
518     \param   module   Extension to use.
519     \param   filename File to open.
521     First things first, this function needs a temporary file name.  To
522     create on of those the function g_file_open_tmp is used with
523     the header of ink_ext_.
525     The extension is then executed using the 'execute' function
526     with the filname coming in, and the temporary filename.  After
527     That executing, the SVG should be in the temporary file.
529     Finally, the temporary file is opened using the SVG input module and
530     a document is returned.  That document has its filename set to
531     the incoming filename (so that it's not the temporary filename).
532     That document is then returned from this function.
533 */
534 SPDocument *
535 Script::open(Inkscape::Extension::Input *module,
536              const gchar *filenameArg)
539     Glib::ustring filename = filenameArg;
541     gchar *tmpname;
543     // FIXME: process the GError instead of passing NULL
544     gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
545     if (tempfd == -1) {
546         /* Error, couldn't create temporary filename */
547         if (errno == EINVAL) {
548             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
549             perror("Extension::Script:  template for filenames is misconfigured.\n");
550             exit(-1);
551         } else if (errno == EEXIST) {
552             /* Now the  contents of template are undefined. */
553             perror("Extension::Script:  Could not create a unique temporary filename\n");
554             return NULL;
555         } else {
556             perror("Extension::Script:  Unknown error creating temporary filename\n");
557             exit(-1);
558         }
559     }
561     Glib::ustring tempfilename_out = tmpname;
562     g_free(tmpname);
564     gsize bytesRead = 0;
565     gsize bytesWritten = 0;
566     GError *error = NULL;
567     Glib::ustring local_filename =
568             g_filename_from_utf8( filename.c_str(), -1,
569                                   &bytesRead,  &bytesWritten, &error);
571     int data_read = execute(command, local_filename, tempfilename_out);
573     SPDocument *mydoc = NULL;
574     if (data_read > 10) {
575         if (helper_extension.size()==0) {
576             mydoc = Inkscape::Extension::open(
577                 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
578                                             tempfilename_out.c_str());
579         } else {
580             mydoc = Inkscape::Extension::open(
581                 Inkscape::Extension::db.get(helper_extension.c_str()),
582                                             tempfilename_out.c_str());
583         }
584     }
586     if (mydoc != NULL)
587         sp_document_set_uri(mydoc, (const gchar *)filename.c_str());
589     // make sure we don't leak file descriptors from g_file_open_tmp
590     close(tempfd);
591     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
592     unlink(tempfilename_out.c_str());
595     return mydoc;
600 /**
601     \return   none
602     \brief    This function uses an extention to save a document.  It first
603               creates an SVG file of the document, and then runs it through
604               the script.
605     \param    module    Extention to be used
606     \param    doc       Document to be saved
607     \param    filename  The name to save the final file as
609     Well, at some point people need to save - it is really what makes
610     the entire application useful.  And, it is possible that someone
611     would want to use an extetion for this, so we need a function to
612     do that eh?
614     First things first, the document is saved to a temporary file that
615     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
616     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
617     end of the function.
619     After we have the SVG file, then extention_execute is called with
620     the temporary file name and the final output filename.  This should
621     put the output of the script into the final output file.  We then
622     delete the temporary file.
623 */
624 void
625 Script::save(Inkscape::Extension::Output *module,
626              SPDocument *doc,
627              const gchar *filenameArg)
630     Glib::ustring filename = filenameArg;
632     gchar *tmpname;
633     // FIXME: process the GError instead of passing NULL
634     gint tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
635     if (tempfd == -1) {
636         /* Error, couldn't create temporary filename */
637         if (errno == EINVAL) {
638             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
639             perror("Extension::Script:  template for filenames is misconfigured.\n");
640             exit(-1);
641         } else if (errno == EEXIST) {
642             /* Now the  contents of template are undefined. */
643             perror("Extension::Script:  Could not create a unique temporary filename\n");
644             return;
645         } else {
646             perror("Extension::Script:  Unknown error creating temporary filename\n");
647             exit(-1);
648         }
649     }
651     Glib::ustring tempfilename_in = tmpname;
652     g_free(tmpname);
654     if (helper_extension.size() == 0) {
655         Inkscape::Extension::save(
656                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
657                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
658     } else {
659         Inkscape::Extension::save(
660                    Inkscape::Extension::db.get(helper_extension.c_str()),
661                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
662     }
664     gsize bytesRead = 0;
665     gsize bytesWritten = 0;
666     GError *error = NULL;
667     Glib::ustring local_filename =
668             g_filename_from_utf8( filename.c_str(), -1,
669                                  &bytesRead,  &bytesWritten, &error);
671     Glib::ustring local_command = command;
672     Glib::ustring paramString   = *module->paramString();
673     local_command.append(paramString);
675     execute(local_command, tempfilename_in, local_filename);
678     // make sure we don't leak file descriptors from g_file_open_tmp
679     close(tempfd);
680     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
681     unlink(tempfilename_in.c_str());
686 /**
687     \return    none
688     \brief     This function uses an extention as a effect on a document.
689     \param     module   Extention to effect with.
690     \param     doc      Document to run through the effect.
692     This function is a little bit trickier than the previous two.  It
693     needs two temporary files to get it's work done.  Both of these
694     files have random names created for them using the g_file_open_temp function
695     with the ink_ext_ prefix in the temporary directory.  Like the other
696     functions, the temporary files are deleted at the end.
698     To save/load the two temporary documents (both are SVG) the internal
699     modules for SVG load and save are used.  They are both used through
700     the module system function by passing their keys into the functions.
702     The command itself is built a little bit differently than in other
703     functions because the effect support selections.  So on the command
704     line a list of all the ids that are selected is included.  Currently,
705     this only works for a single selected object, but there will be more.
706     The command string is filled with the data, and then after the execution
707     it is freed.
709     The execute function is used at the core of this function
710     to execute the Script on the two SVG documents (actually only one
711     exists at the time, the other is created by that script).  At that
712     point both should be full, and the second one is loaded.
713 */
714 void
715 Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc)
717     if (module->no_doc) { 
718         // this is a no-doc extension, e.g. a Help menu command; 
719         // just run the command without any files, ignoring errors
720         Glib::ustring local_command(command);
721         Glib::ustring paramString = *module->paramString();
722         local_command.append(paramString);
724         Glib::ustring empty;
725         execute(local_command, empty, empty);
727         return;
728     }
730     gchar *tmpname;
731     // FIXME: process the GError instead of passing NULL
732     gint tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
733     if (tempfd_in == -1) {
734         /* Error, couldn't create temporary filename */
735         if (errno == EINVAL) {
736             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
737             perror("Extension::Script:  template for filenames is misconfigured.\n");
738             exit(-1);
739         } else if (errno == EEXIST) {
740             /* Now the  contents of template are undefined. */
741             perror("Extension::Script:  Could not create a unique temporary filename\n");
742             return;
743         } else {
744             perror("Extension::Script:  Unknown error creating temporary filename\n");
745             exit(-1);
746         }
747     }
749     Glib::ustring tempfilename_in = tmpname;
750     g_free(tmpname);
753     // FIXME: process the GError instead of passing NULL
754     gint tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tmpname, NULL);
755     if (tempfd_out == -1) {
756         /* Error, couldn't create temporary filename */
757         if (errno == EINVAL) {
758             /* The  last  six characters of template were not XXXXXX.  Now template is unchanged. */
759             perror("Extension::Script:  template for filenames is misconfigured.\n");
760             exit(-1);
761         } else if (errno == EEXIST) {
762             /* Now the  contents of template are undefined. */
763             perror("Extension::Script:  Could not create a unique temporary filename\n");
764             return;
765         } else {
766             perror("Extension::Script:  Unknown error creating temporary filename\n");
767             exit(-1);
768         }
769     }
771     Glib::ustring tempfilename_out= tmpname;
772     g_free(tmpname);
774     SPDesktop *desktop = (SPDesktop *) doc;
775     sp_namedview_document_from_window(desktop);
777     Inkscape::Extension::save(
778               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
779               doc->doc(), tempfilename_in.c_str(), FALSE, FALSE, FALSE);
781     Glib::ustring local_command(command);
783     /* fixme: Should be some sort of checking here.  Don't know how to do this with structs instead
784      * of classes. */
785     if (desktop != NULL) {
786         Inkscape::Util::GSListConstIterator<SPItem *> selected =
787              sp_desktop_selection(desktop)->itemList();
788         while ( selected != NULL ) {
789             local_command += " --id=";
790             local_command += SP_OBJECT_ID(*selected);
791             ++selected;
792         }
793     }
795     Glib::ustring paramString = *module->paramString();
796     local_command.append(paramString);
799     // std::cout << local_command << std::endl;
801     int data_read = execute(local_command, tempfilename_in, tempfilename_out);
803     SPDocument * mydoc = NULL;
804     if (data_read > 10)
805         mydoc = Inkscape::Extension::open(
806               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
807               tempfilename_out.c_str());
809     // make sure we don't leak file descriptors from g_file_open_tmp
810     close(tempfd_in);
811     close(tempfd_out);
813     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
814     unlink(tempfilename_in.c_str());
815     unlink(tempfilename_out.c_str());
818     /* Do something with mydoc.... */
819     if (mydoc) {
820         doc->doc()->emitReconstructionStart();
821         copy_doc(doc->doc()->rroot, mydoc->rroot);
822         doc->doc()->emitReconstructionFinish();
823         mydoc->release();
824         sp_namedview_update_layers_from_document(desktop);
825     }
830 /**
831     \brief  A function to take all the svg elements from one document
832             and put them in another.
833     \param  oldroot  The root node of the document to be replaced
834     \param  newroot  The root node of the document to replace it with
836     This function first deletes all of the data in the old document.  It
837     does this by creating a list of what needs to be deleted, and then
838     goes through the list.  This two pass approach removes issues with
839     the list being change while parsing through it.  Lots of nasty bugs.
841     Then, it goes through the new document, duplicating all of the
842     elements and putting them into the old document.  The copy
843     is then complete.
844 */
845 void
846 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
848     std::vector<Inkscape::XML::Node *> delete_list;
849     for (Inkscape::XML::Node * child = oldroot->firstChild();
850             child != NULL;
851             child = child->next()) {
852         if (!strcmp("sodipodi:namedview", child->name()))
853             continue;
854         delete_list.push_back(child);
855     }
856     for (unsigned int i = 0; i < delete_list.size(); i++)
857         sp_repr_unparent(delete_list[i]);
859     for (Inkscape::XML::Node * child = newroot->firstChild();
860             child != NULL;
861             child = child->next()) {
862         if (!strcmp("sodipodi:namedview", child->name()))
863             continue;
864         oldroot->appendChild(child->duplicate());
865     }
867     /** \todo  Restore correct layer */
868     /** \todo  Restore correct selection */
873 /* Helper class used by Script::execute */
874 class pipe_t {
875 public:
876     /* These functions set errno if they return false.
877        I'm not sure whether that's a good idea or not, but it should be reasonably
878        straightforward to change it if needed. */
879     bool open(const Glib::ustring &command,
880               const Glib::ustring &errorFile,
881               int mode);
882     bool close();
884     /* These return the number of bytes read/written. */
885     size_t read(void *buffer, size_t size);
886     size_t write(void const *buffer, size_t size);
888     enum {
889         mode_read  = 1 << 0,
890         mode_write = 1 << 1,
891     };
893 private:
894 #ifdef WIN32
895     /* This is used to translate win32 errors into errno errors.
896        It only recognizes a few win32 errors for the moment though. */
897     static int translate_error(DWORD err);
899     HANDLE hpipe;
900 #else
901     FILE *ppipe;
902 #endif
903 };
908 /**
909     \brief    This is the core of the extension file as it actually does
910               the execution of the extension.
911     \param    in_command  The command to be executed
912     \param    filein      Filename coming in
913     \param    fileout     Filename of the out file
914     \return   Number of bytes that were read into the output file.
916     The first thing that this function does is build the command to be
917     executed.  This consists of the first string (in_command) and then
918     the filename for input (filein).  This file is put on the command
919     line.
921     The next thing is that this function does is open a pipe to the
922     command and get the file handle in the ppipe variable.  It then
923     opens the output file with the output file handle.  Both of these
924     operations are checked extensively for errors.
926     After both are opened, then the data is copied from the output
927     of the pipe into the file out using fread and fwrite.  These two
928     functions are used because of their primitive nature they make
929     no assumptions about the data.  A buffer is used in the transfer,
930     but the output of fread is stored so the exact number of bytes
931     is handled gracefully.
933     At the very end (after the data has been copied) both of the files
934     are closed, and we return to what we were doing.
935 */
936 int
937 Script::execute (const Glib::ustring &in_command,
938                  const Glib::ustring &filein,
939                  const Glib::ustring &fileout)
941     g_return_val_if_fail(in_command.size() > 0, 0);
942     // printf("Executing: %s\n", in_command);
944     gchar *tmpname;
945     gint errorFileNum;
946     errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &tmpname, NULL);
947     if (errorFileNum != 0) {
948         close(errorFileNum);
949     } else {
950         g_free(tmpname);
951     }
953     Glib::ustring errorFile = tmpname;
954     g_free(tmpname);
956     Glib::ustring localCommand = in_command;
958     if (!(filein.empty())) {
959         localCommand .append(" \"");
960         localCommand .append(filein);
961         localCommand .append("\"");
962     }
964     // std::cout << "Command to run: " << command << std::endl;
966     pipe_t pipe;
967     bool open_success = pipe.open((char *)localCommand.c_str(),
968                                   errorFile.c_str(),
969                                   pipe_t::mode_read);
971     /* Run script */
972     if (!open_success) {
973         /* Error - could not open pipe - check errno */
974         if (errno == EINVAL) {
975             perror("Extension::Script:  Invalid mode argument in popen\n");
976         } else if (errno == ECHILD) {
977             perror("Extension::Script:  Cannot obtain child extension status in popen\n");
978         } else {
979             perror("Extension::Script:  Unknown error for popen\n");
980         }
981         return 0;
982     }
984     if (fileout.empty()) { // no output file to create; just close everything and return 0
985         if (errorFile.size()>0) {
986             unlink(errorFile.c_str());
987         }
988         pipe.close();
989         return 0;
990     }
992     /* Copy pipe output to fileout (temporary file) */
993     Inkscape::IO::dump_fopen_call(fileout.c_str(), "J");
994     FILE *pfile = Inkscape::IO::fopen_utf8name(fileout.c_str(), "w");
996     if (pfile == NULL) {
997         /* Error - could not open file */
998         if (errno == EINVAL) {
999             perror("Extension::Script:  The mode provided to fopen was invalid\n");
1000         } else {
1001             perror("Extension::Script:  Unknown error attempting to open temporary file\n");
1002         }
1003         return 0;
1004     }
1006     int amount_read = 0;
1007     char buf[BUFSIZE];
1008     int num_read;
1009     while ((num_read = pipe.read(buf, BUFSIZE)) != 0) {
1010         amount_read += num_read;
1011         fwrite(buf, 1, num_read, pfile);
1012     }
1014     /* Close file */
1015     if (fclose(pfile) == EOF) {
1016         if (errno == EBADF) {
1017             perror("Extension::Script:  The filedescriptor for the temporary file is invalid\n");
1018             return 0;
1019         } else {
1020             perror("Extension::Script:  Unknown error closing temporary file\n");
1021         }
1022     }
1024     /* Close pipe */
1025     if (!pipe.close()) {
1026         if (errno == EINVAL) {
1027             perror("Extension::Script:  Invalid mode set for pclose\n");
1028         } else if (errno == ECHILD) {
1029             perror("Extension::Script:  Could not obtain child status for pclose\n");
1030         } else {
1031             if (!errorFile.empty()) {
1032                 checkStderr(errorFile, Gtk::MESSAGE_ERROR,
1033                     _("Inkscape has received an error from the script that it called.  "
1034                       "The text returned with the error is included below.  "
1035                       "Inkscape will continue working, but the action you requested has been cancelled."));
1036             } else {
1037                 perror("Extension::Script:  Unknown error for pclose\n");
1038             }
1039         }
1040         /* Could be a lie, but if there is an error, we don't want
1041          * to count on what was read being good */
1042         amount_read = 0;
1043     } else {
1044         if (errorFile.size()>0) {
1045             checkStderr(errorFile, Gtk::MESSAGE_INFO,
1046                 _("Inkscape has received additional data from the script executed.  "
1047                   "The script did not return an error, but this may indicate the results will not be as expected."));
1048         }
1049     }
1051     if (errorFile.size()>0) {
1052         unlink(errorFile.c_str());
1053     }
1055     return amount_read;
1061 /**  \brief  This function checks the stderr file, and if it has data,
1062              shows it in a warning dialog to the user
1063      \param  filename  Filename of the stderr file
1064 */
1065 void
1066 Script::checkStderr (const Glib::ustring &filename,
1067                      Gtk::MessageType type,
1068                      const Glib::ustring &message)
1071     // magic win32 crlf->lf conversion means the file length is not the same as
1072     // the text length, but luckily gtk will accept crlf in textviews so we can
1073     // just use binary mode
1074     std::ifstream stderrf (filename.c_str(), std::ios_base::in | std::ios_base::binary);
1075     if (!stderrf.is_open()) return;
1077     stderrf.seekg(0, std::ios::end);
1078     int length = stderrf.tellg();
1079     if (0 == length) return;
1080     stderrf.seekg(0, std::ios::beg);
1082     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
1083     warning.set_resizable(true);
1084     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
1085     sp_transientize(dlg);
1087     Gtk::VBox * vbox = warning.get_vbox();
1089     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
1090     Gtk::TextView * textview = new Gtk::TextView();
1091     textview->set_editable(false);
1092     textview->set_wrap_mode(Gtk::WRAP_WORD);
1093     textview->show();
1095     char * buffer = new char [length];
1096     stderrf.read(buffer, length);
1097     textview->get_buffer()->set_text(buffer, buffer + length);
1098     delete buffer;
1099     stderrf.close();
1101     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
1102     scrollwindow->add(*textview);
1103     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
1104     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
1105     scrollwindow->show();
1107     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
1109     warning.run();
1111     return;
1117 #ifdef WIN32
1120 bool pipe_t::open(const Glib::ustring &command,
1121                   const Glib::ustring &errorFile,
1122                   int mode_p) {
1123     HANDLE pipe_write;
1125     //###############  Create pipe
1126     SECURITY_ATTRIBUTES secattrs;
1127     ZeroMemory(&secattrs, sizeof(secattrs));
1128     secattrs.nLength = sizeof(secattrs);
1129     secattrs.lpSecurityDescriptor = 0;
1130     secattrs.bInheritHandle = TRUE;
1131     HANDLE t_pipe_read = 0;
1132     if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) {
1133         errno = translate_error(GetLastError());
1134         return false;
1135     }
1136     // This duplicate handle makes the read pipe uninheritable
1137     BOOL ret = DuplicateHandle(GetCurrentProcess(),
1138                                t_pipe_read,
1139                                GetCurrentProcess(),
1140                                &hpipe, 0, FALSE,
1141                                DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1142     if (!ret) {
1143         int en = translate_error(GetLastError());
1144         CloseHandle(t_pipe_read);
1145         CloseHandle(pipe_write);
1146         errno = en;
1147         return false;
1148     }
1150     //############### Open stderr file
1151     HANDLE hStdErrFile = CreateFile(errorFile.c_str(),
1152                       GENERIC_WRITE,
1153                       FILE_SHARE_READ | FILE_SHARE_WRITE,
1154                       NULL, CREATE_ALWAYS, 0, NULL);
1155     HANDLE hInheritableStdErr;
1156     DuplicateHandle(GetCurrentProcess(),
1157                     hStdErrFile,
1158                     GetCurrentProcess(),
1159                     &hInheritableStdErr,
1160                     0, 
1161                     TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
1163     //############### Create process
1164     PROCESS_INFORMATION procinfo;
1165     STARTUPINFO startupinfo;
1166     ZeroMemory(&procinfo, sizeof(procinfo));
1167     ZeroMemory(&startupinfo, sizeof(startupinfo));
1168     startupinfo.cb = sizeof(startupinfo);
1169     //startupinfo.lpReserved = 0;
1170     //startupinfo.lpDesktop = 0;
1171     //startupinfo.lpTitle = 0;
1172     startupinfo.dwFlags = STARTF_USESTDHANDLES;
1173     startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
1174     startupinfo.hStdOutput = pipe_write;
1175     startupinfo.hStdError = hInheritableStdErr;
1177     if ( !CreateProcess(NULL, (CHAR *)command.c_str(),
1178                         NULL, NULL, TRUE,
1179                         0, NULL, NULL,
1180                         &startupinfo, &procinfo) ) {
1181         errno = translate_error(GetLastError());
1182         return false;
1183     }
1184     CloseHandle(procinfo.hThread);
1185     CloseHandle(procinfo.hProcess);
1187     // Close our copy of the write handle
1188     CloseHandle(hInheritableStdErr);
1189     CloseHandle(pipe_write);
1191     return true;
1196 bool pipe_t::close() {
1197     BOOL retval = CloseHandle(hpipe);
1198     if ( !retval )
1199         errno = translate_error(GetLastError());
1200     return retval != FALSE;
1203 size_t pipe_t::read(void *buffer, size_t size) {
1204     DWORD bytes_read = 0;
1205     ReadFile(hpipe, buffer, size, &bytes_read, 0);
1206     return bytes_read;
1209 size_t pipe_t::write(void const *buffer, size_t size) {
1210     DWORD bytes_written = 0;
1211     WriteFile(hpipe, buffer, size, &bytes_written, 0);
1212     return bytes_written;
1215 int pipe_t::translate_error(DWORD err) {
1216     switch (err) {
1217         case ERROR_FILE_NOT_FOUND:
1218             return ENOENT;
1219         case ERROR_INVALID_HANDLE:
1220         case ERROR_INVALID_PARAMETER:
1221             return EINVAL;
1222         default:
1223             return 0;
1224     }
1228 #else // not Win32
1231 bool pipe_t::open(const Glib::ustring &command,
1232                   const Glib::ustring &errorFile,
1233                   int mode_p) {
1235     Glib::ustring popen_mode;
1237     if ( (mode_p & mode_read) != 0 )
1238         popen_mode.append("r");
1240     if ( (mode_p & mode_write) != 0 )
1241         popen_mode.append("w");
1243     // Get the commandline to be run
1244     Glib::ustring pipeStr = command;
1245     if (errorFile.size()>0) {
1246         pipeStr .append(" 2> ");
1247         pipeStr .append(errorFile);
1248     }
1250     ppipe = popen(pipeStr.c_str(), popen_mode.c_str());
1252     return ppipe != NULL;
1256 bool pipe_t::close() {
1257     return fclose(ppipe) == 0;
1261 size_t pipe_t::read(void *buffer, size_t size) {
1262     return fread(buffer, 1, size, ppipe);
1266 size_t pipe_t::write(void const *buffer, size_t size) {
1267     return fwrite(buffer, 1, size, ppipe);
1273 #endif // (Non-)Win32
1278 }  // namespace Implementation
1279 }  // namespace Extension
1280 }  // namespace Inkscape
1285 /*
1286   Local Variables:
1287   mode:c++
1288   c-file-style:"stroustrup"
1289   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1290   indent-tabs-mode:nil
1291   fill-column:99
1292   End:
1293 */
1294 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :