Code

Bug #204779 win32 crash on help menu - win32 spawn problem
[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,2007 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.
22   "** (inkscape:5848): WARNING **: Format autodetect failed. The file is being opened as SVG."
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
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 "desktop.h"
45 #include "selection.h"
46 #include "sp-namedview.h"
47 #include "io/sys.h"
48 #include "prefs-utils.h"
49 #include "../system.h"
50 #include "extension/effect.h"
51 #include "extension/output.h"
52 #include "extension/input.h"
53 #include "extension/db.h"
54 #include "script.h"
55 #include "dialogs/dialog-events.h"
57 #include "util/glib-list-iterators.h"
61 #ifdef WIN32
62 #include <windows.h>
63 #include <sys/stat.h>
64 #include "registrytool.h"
65 #endif
69 /** This is the command buffer that gets allocated from the stack */
70 #define BUFSIZE (255)
74 /* Namespaces */
75 namespace Inkscape {
76 namespace Extension {
77 namespace Implementation {
79 void pump_events (void) {
80     while( Gtk::Main::events_pending() )
81         Gtk::Main::iteration();
82     return;
83 }
85 //Interpreter lookup table
86 struct interpreter_t {
87         gchar const *identity;
88         gchar const *prefstring;
89         gchar const *defaultval;
90 };
93 /** \brief  A table of what interpreters to call for a given language
95     This table is used to keep track of all the programs to execute a
96     given script.  It also tracks the preference to use to overwrite
97     the given interpreter to a custom one per user.
98 */
99 static interpreter_t const interpreterTab[] = {
100         {"perl",   "perl-interpreter",   "perl"   },
101 #ifdef _WIN32
102         {"python", "python-interpreter", "pythonw" },
103 #else
104         {"python", "python-interpreter", "python" },
105 #endif
106         {"ruby",   "ruby-interpreter",   "ruby"   },
107         {"shell",  "shell-interpreter",  "sh"     },
108         { NULL,    NULL,                  NULL    }
109 };
113 /**
114  * Look up an interpreter name, and translate to something that
115  * is executable
116  */
117 static Glib::ustring
118 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
121     Glib::ustring interpName = interpNameArg;
123     interpreter_t const *interp;
124     bool foundInterp = false;
125     for (interp =  interpreterTab ; interp->identity ; interp++ ){
126         if (interpName == interp->identity) {
127             foundInterp = true;
128             break;
129         }
130     }
132     // Do we have a supported interpreter type?
133     if (!foundInterp)
134         return "";
135     interpName = interp->defaultval;
137     // 1.  Check preferences
138     gchar const *prefInterp = prefs_get_string_attribute("extensions", interp->prefstring);
140     if (prefInterp) {
141         interpName = prefInterp;
142         return interpName;
143     }
145 #ifdef _WIN32
147     // 2.  Windows.  Try looking relative to inkscape.exe
148     RegistryTool rt;
149     Glib::ustring fullPath;
150     Glib::ustring path;
151     Glib::ustring exeName;
152     if (rt.getExeInfo(fullPath, path, exeName)) {
153         Glib::ustring interpPath = path;
154         interpPath.append("\\");
155         interpPath.append(interpNameArg);
156         interpPath.append("\\");
157         interpPath.append(interpName);
158         interpPath.append(".exe");
159         struct stat finfo;
160         if (stat(interpPath .c_str(), &finfo) ==0) {
161             g_message("Found local interpreter, '%s',  Size: %d",
162                       interpPath .c_str(),
163                       (int)finfo.st_size);
164             return interpPath;
165         }
166     }
168     // 3. Try searching the path
169     char szExePath[MAX_PATH];
170     char szCurrentDir[MAX_PATH];
171     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
172     unsigned int ret = (unsigned int)FindExecutable(
173                   interpName.c_str(), szCurrentDir, szExePath);
174     if (ret > 32) {
175         interpName = szExePath;
176         return interpName;
177     }
179 #endif // win32
182     return interpName;
187 /** \brief     This function creates a script object and sets up the
188                variables.
189     \return    A script object
191    This function just sets the command to NULL.  It should get built
192    officially in the load function.  This allows for less allocation
193    of memory in the unloaded state.
194 */
195 Script::Script() :
196     Implementation()
201 /**
202  *   brief     Destructor
203  */
204 Script::~Script()
210 /**
211     \return    A string with the complete string with the relative directory expanded
212     \brief     This function takes in a Repr that contains a reldir entry
213                and returns that data with the relative directory expanded.
214                Mostly it is here so that relative directories all get used
215                the same way.
216     \param     reprin   The Inkscape::XML::Node with the reldir in it.
218     Basically this function looks at an attribute of the Repr, and makes
219     a decision based on that.  Currently, it is only working with the
220     'extensions' relative directory, but there will be more of them.
221     One thing to notice is that this function always returns an allocated
222     string.  This means that the caller of this function can always
223     free what they are given (and should do it too!).
224 */
225 Glib::ustring
226 Script::solve_reldir(Inkscape::XML::Node *reprin) {
228     gchar const *s = reprin->attribute("reldir");
230     if (!s) {
231         Glib::ustring str = sp_repr_children(reprin)->content();
232         return str;
233     }
235     Glib::ustring reldir = s;
237     if (reldir == "extensions") {
239         for (unsigned int i=0;
240             i < Inkscape::Extension::Extension::search_path.size();
241             i++) {
243             gchar * fname = g_build_filename(
244                Inkscape::Extension::Extension::search_path[i],
245                sp_repr_children(reprin)->content(),
246                NULL);
247             Glib::ustring filename = fname;
248             g_free(fname);
250             if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
251                 return filename;
253         }
254     } else {
255         Glib::ustring str = sp_repr_children(reprin)->content();
256         return str;
257     }
259     return "";
264 /**
265     \return   Whether the command given exists, including in the path
266     \brief    This function is used to find out if something exists for
267               the check command.  It can look in the path if required.
268     \param    command   The command or file that should be looked for
270     The first thing that this function does is check to see if the
271     incoming file name has a directory delimiter in it.  This would
272     mean that it wants to control the directories, and should be
273     used directly.
275     If not, the path is used.  Each entry in the path is stepped through,
276     attached to the string, and then tested.  If the file is found
277     then a TRUE is returned.  If we get all the way through the path
278     then a FALSE is returned, the command could not be found.
279 */
280 bool
281 Script::check_existance(const Glib::ustring &command)
284     // Check the simple case first
285     if (command.size() == 0) {
286         return false;
287     }
289     //Don't search when it contains a slash. */
290     if (command.find(G_DIR_SEPARATOR) != command.npos) {
291         if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
292             return true;
293         else
294             return false;
295     }
298     Glib::ustring path;
299     gchar *s = (gchar *) g_getenv("PATH");
300     if (s)
301         path = s;
302     else
303        /* There is no `PATH' in the environment.
304            The default search path is the current directory */
305         path = G_SEARCHPATH_SEPARATOR_S;
307     std::string::size_type pos  = 0;
308     std::string::size_type pos2 = 0;
309     while ( pos < path.size() ) {
311         Glib::ustring localPath;
313         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
314         if (pos2 == path.npos) {
315             localPath = path.substr(pos);
316             pos = path.size();
317         } else {
318             localPath = path.substr(pos, pos2-pos);
319             pos = pos2+1;
320         }
322         //printf("### %s\n", localPath.c_str());
323         Glib::ustring candidatePath =
324                       Glib::build_filename(localPath, command);
326         if (Inkscape::IO::file_test(candidatePath .c_str(),
327                       G_FILE_TEST_EXISTS)) {
328             return true;
329         }
331     }
333     return false;
340 /**
341     \return   none
342     \brief    This function 'loads' an extention, basically it determines
343               the full command for the extention and stores that.
344     \param    module  The extention to be loaded.
346     The most difficult part about this function is finding the actual
347     command through all of the Reprs.  Basically it is hidden down a
348     couple of layers, and so the code has to move down too.  When
349     the command is actually found, it has its relative directory
350     solved.
352     At that point all of the loops are exited, and there is an
353     if statement to make sure they didn't exit because of not finding
354     the command.  If that's the case, the extention doesn't get loaded
355     and should error out at a higher level.
356 */
358 bool
359 Script::load(Inkscape::Extension::Extension *module)
361     if (module->loaded())
362         return TRUE;
364     helper_extension = "";
366     /* This should probably check to find the executable... */
367     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
368     while (child_repr != NULL) {
369         if (!strcmp(child_repr->name(), "script")) {
370             child_repr = sp_repr_children(child_repr);
371             while (child_repr != NULL) {
372                 if (!strcmp(child_repr->name(), "command")) {
373                     const gchar *interpretstr = child_repr->attribute("interpreter");
374                     if (interpretstr != NULL) {
375                         Glib::ustring interpString =
376                             resolveInterpreterExecutable(interpretstr);
377                         command.insert(command.end(), interpretstr);
378                     }
380                     command.insert(command.end(), solve_reldir(child_repr));
381                 }
382                 if (!strcmp(child_repr->name(), "helper_extension")) {
383                     helper_extension = sp_repr_children(child_repr)->content();
384                 }
385                 child_repr = sp_repr_next(child_repr);
386             }
388             break;
389         }
390         child_repr = sp_repr_next(child_repr);
391     }
393     //g_return_val_if_fail(command.length() > 0, FALSE);
395     return true;
399 /**
400     \return   None.
401     \brief    Unload this puppy!
402     \param    module  Extension to be unloaded.
404     This function just sets the module to unloaded.  It free's the
405     command if it has been allocated.
406 */
407 void
408 Script::unload(Inkscape::Extension::Extension */*module*/)
410     command.clear();
411     helper_extension = "";
417 /**
418     \return   Whether the check passed or not
419     \brief    Check every dependency that was given to make sure we should keep this extension
420     \param    module  The Extension in question
422 */
423 bool
424 Script::check(Inkscape::Extension::Extension *module)
426     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
427     while (child_repr != NULL) {
428         if (!strcmp(child_repr->name(), "script")) {
429             child_repr = sp_repr_children(child_repr);
430             while (child_repr != NULL) {
431                 if (!strcmp(child_repr->name(), "check")) {
432                     Glib::ustring command_text = solve_reldir(child_repr);
433                     if (command_text.size() > 0) {
434                         /* I've got the command */
435                         bool existance = check_existance(command_text);
436                         if (!existance)
437                             return FALSE;
438                     }
439                 }
441                 if (!strcmp(child_repr->name(), "helper_extension")) {
442                     gchar const *helper = sp_repr_children(child_repr)->content();
443                     if (Inkscape::Extension::db.get(helper) == NULL) {
444                         return FALSE;
445                     }
446                 }
448                 child_repr = sp_repr_next(child_repr);
449             }
451             break;
452         }
453         child_repr = sp_repr_next(child_repr);
454     }
456     return true;
459 class ScriptDocCache : public ImplementationDocumentCache {
460     friend class Script;
461 protected:
462     std::string _filename;
463     int _tempfd;
464 public:
465     ScriptDocCache (Inkscape::UI::View::View * view);
466     ~ScriptDocCache ( );
467 };
469 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
470     ImplementationDocumentCache(view),
471     _filename(""),
472     _tempfd(0)
474     try {
475         _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
476     } catch (...) {
477         /// \todo Popup dialog here
478         return;
479     }
481     SPDesktop *desktop = (SPDesktop *) view;
482     sp_namedview_document_from_window(desktop);
484     Inkscape::Extension::save(
485               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
486               view->doc(), _filename.c_str(), FALSE, FALSE, FALSE);
488     return;
491 ScriptDocCache::~ScriptDocCache ( )
493     close(_tempfd);
494     unlink(_filename.c_str());
497 ImplementationDocumentCache *
498 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
499     return new ScriptDocCache(view);
503 /**
504     \return   A dialog for preferences
505     \brief    A stub funtion right now
506     \param    module    Module who's preferences need getting
507     \param    filename  Hey, the file you're getting might be important
509     This function should really do something, right now it doesn't.
510 */
511 Gtk::Widget *
512 Script::prefs_input(Inkscape::Extension::Input *module,
513                     const gchar */*filename*/)
515     return module->autogui(NULL, NULL);
520 /**
521     \return   A dialog for preferences
522     \brief    A stub funtion right now
523     \param    module    Module whose preferences need getting
525     This function should really do something, right now it doesn't.
526 */
527 Gtk::Widget *
528 Script::prefs_output(Inkscape::Extension::Output *module)
530     return module->autogui(NULL, NULL);
535 /**
536     \return   A dialog for preferences
537     \brief    A stub funtion right now
538     \param    module    Module who's preferences need getting
540     This function should really do something, right now it doesn't.
541 */
542 Gtk::Widget *
543 Script::prefs_effect( Inkscape::Extension::Effect *module,
544                       Inkscape::UI::View::View *view,
545                       sigc::signal<void> * changeSignal,
546                       ImplementationDocumentCache * /*docCache*/ )
548     SPDocument * current_document = view->doc();
550     using Inkscape::Util::GSListConstIterator;
551     GSListConstIterator<SPItem *> selected =
552            sp_desktop_selection((SPDesktop *)view)->itemList();
553     Inkscape::XML::Node * first_select = NULL;
554     if (selected != NULL) {
555         const SPItem * item = *selected;
556         first_select = SP_OBJECT_REPR(item);
557     }
559     return module->autogui(current_document, first_select, changeSignal);
565 /**
566     \return  A new document that has been opened
567     \brief   This function uses a filename that is put in, and calls
568              the extension's command to create an SVG file which is
569              returned.
570     \param   module   Extension to use.
571     \param   filename File to open.
573     First things first, this function needs a temporary file name.  To
574     create on of those the function g_file_open_tmp is used with
575     the header of ink_ext_.
577     The extension is then executed using the 'execute' function
578     with the filname coming in, and the temporary filename.  After
579     That executing, the SVG should be in the temporary file.
581     Finally, the temporary file is opened using the SVG input module and
582     a document is returned.  That document has its filename set to
583     the incoming filename (so that it's not the temporary filename).
584     That document is then returned from this function.
585 */
586 SPDocument *
587 Script::open(Inkscape::Extension::Input *module,
588              const gchar *filenameArg)
590     std::list<std::string> params;
591     module->paramListString(params);
593     std::string tempfilename_out;
594     int tempfd_out = 0;
595     try {
596         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
597     } catch (...) {
598         /// \todo Popup dialog here
599         return NULL;
600     }
602     std::string lfilename = Glib::filename_from_utf8(filenameArg);
604     file_listener fileout;
605     int data_read = execute(command, params, lfilename, fileout);
606     fileout.toFile(tempfilename_out);
608     SPDocument * mydoc = NULL;
609     if (data_read > 10) {
610         if (helper_extension.size()==0) {
611             mydoc = Inkscape::Extension::open(
612                   Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
613                   tempfilename_out.c_str());
614         } else {
615             mydoc = Inkscape::Extension::open(
616                   Inkscape::Extension::db.get(helper_extension.c_str()),
617                   tempfilename_out.c_str());
618         }
619     } // data_read
621     if (mydoc != NULL) {
622         sp_document_set_uri(mydoc, filenameArg);
623     }
625     // make sure we don't leak file descriptors from g_file_open_tmp
626     close(tempfd_out);
628     unlink(tempfilename_out.c_str());
630     return mydoc;
631 } // open
635 /**
636     \return   none
637     \brief    This function uses an extention to save a document.  It first
638               creates an SVG file of the document, and then runs it through
639               the script.
640     \param    module    Extention to be used
641     \param    doc       Document to be saved
642     \param    filename  The name to save the final file as
644     Well, at some point people need to save - it is really what makes
645     the entire application useful.  And, it is possible that someone
646     would want to use an extetion for this, so we need a function to
647     do that eh?
649     First things first, the document is saved to a temporary file that
650     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
651     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
652     end of the function.
654     After we have the SVG file, then extention_execute is called with
655     the temporary file name and the final output filename.  This should
656     put the output of the script into the final output file.  We then
657     delete the temporary file.
658 */
659 void
660 Script::save(Inkscape::Extension::Output *module,
661              SPDocument *doc,
662              const gchar *filenameArg)
664     std::list<std::string> params;
665     module->paramListString(params);
667     std::string tempfilename_in;
668     int tempfd_in = 0;
669     try {
670         tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
671     } catch (...) {
672         /// \todo Popup dialog here
673         return;
674     }
676     if (helper_extension.size() == 0) {
677         Inkscape::Extension::save(
678                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
679                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
680     } else {
681         Inkscape::Extension::save(
682                    Inkscape::Extension::db.get(helper_extension.c_str()),
683                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
684     }
687     file_listener fileout;
688     execute(command, params, tempfilename_in, fileout);
690     std::string lfilename = Glib::filename_from_utf8(filenameArg);
691     fileout.toFile(lfilename);
693     // make sure we don't leak file descriptors from g_file_open_tmp
694     close(tempfd_in);
695     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
696     unlink(tempfilename_in.c_str());
698     return;
703 /**
704     \return    none
705     \brief     This function uses an extention as a effect on a document.
706     \param     module   Extention to effect with.
707     \param     doc      Document to run through the effect.
709     This function is a little bit trickier than the previous two.  It
710     needs two temporary files to get it's work done.  Both of these
711     files have random names created for them using the g_file_open_temp function
712     with the ink_ext_ prefix in the temporary directory.  Like the other
713     functions, the temporary files are deleted at the end.
715     To save/load the two temporary documents (both are SVG) the internal
716     modules for SVG load and save are used.  They are both used through
717     the module system function by passing their keys into the functions.
719     The command itself is built a little bit differently than in other
720     functions because the effect support selections.  So on the command
721     line a list of all the ids that are selected is included.  Currently,
722     this only works for a single selected object, but there will be more.
723     The command string is filled with the data, and then after the execution
724     it is freed.
726     The execute function is used at the core of this function
727     to execute the Script on the two SVG documents (actually only one
728     exists at the time, the other is created by that script).  At that
729     point both should be full, and the second one is loaded.
730 */
731 void
732 Script::effect(Inkscape::Extension::Effect *module,
733                Inkscape::UI::View::View *doc,
734                ImplementationDocumentCache * docCache)
736     if (docCache == NULL) {
737         docCache = newDocCache(module, doc);
738     }
739     ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
740     if (dc == NULL) {
741         printf("TOO BAD TO LIVE!!!");
742         exit(1);
743     }
745     SPDesktop *desktop = (SPDesktop *)doc;
746     sp_namedview_document_from_window(desktop);
748     gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
750     std::list<std::string> params;
751     module->paramListString(params);
753     if (module->no_doc) {
754         // this is a no-doc extension, e.g. a Help menu command;
755         // just run the command without any files, ignoring errors
757         Glib::ustring empty;
758         file_listener outfile;
759         execute(command, params, empty, outfile);
761         return;
762     }
764     std::string tempfilename_out;
765     int tempfd_out = 0;
766     try {
767         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
768     } catch (...) {
769         /// \todo Popup dialog here
770         return;
771     }
773     if (desktop != NULL) {
774         Inkscape::Util::GSListConstIterator<SPItem *> selected =
775              sp_desktop_selection(desktop)->itemList();
776         while ( selected != NULL ) {
777             Glib::ustring selected_id;
778             selected_id += "--id=";
779             selected_id += SP_OBJECT_ID(*selected);
780             params.insert(params.begin(), selected_id);
781             ++selected;
782         }
783     }
785     file_listener fileout;
786     int data_read = execute(command, params, dc->_filename, fileout);
787     fileout.toFile(tempfilename_out);
789     pump_events();
791     SPDocument * mydoc = NULL;
792     if (data_read > 10) {
793         mydoc = Inkscape::Extension::open(
794               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
795               tempfilename_out.c_str());
796     } // data_read
798     pump_events();
800     // make sure we don't leak file descriptors from g_file_open_tmp
801     close(tempfd_out);
803     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
804     unlink(tempfilename_out.c_str());
806     /* Do something with mydoc.... */
807     if (mydoc) {
808         doc->doc()->emitReconstructionStart();
809         copy_doc(doc->doc()->rroot, mydoc->rroot);
810         doc->doc()->emitReconstructionFinish();
811         mydoc->release();
812         sp_namedview_update_layers_from_document(desktop);
814         sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
815     }
816     g_free(orig_output_extension);
818     return;
823 /**
824     \brief  A function to take all the svg elements from one document
825             and put them in another.
826     \param  oldroot  The root node of the document to be replaced
827     \param  newroot  The root node of the document to replace it with
829     This function first deletes all of the data in the old document.  It
830     does this by creating a list of what needs to be deleted, and then
831     goes through the list.  This two pass approach removes issues with
832     the list being change while parsing through it.  Lots of nasty bugs.
834     Then, it goes through the new document, duplicating all of the
835     elements and putting them into the old document.  The copy
836     is then complete.
837 */
838 void
839 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
841     std::vector<Inkscape::XML::Node *> delete_list;
842     Inkscape::XML::Node * oldroot_namedview = NULL;
844     for (Inkscape::XML::Node * child = oldroot->firstChild();
845             child != NULL;
846             child = child->next()) {
847         if (!strcmp("sodipodi:namedview", child->name())) {
848             oldroot_namedview = child;
849             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
850                     oldroot_namedview_child != NULL;
851                     oldroot_namedview_child = oldroot_namedview_child->next()) {
852                 delete_list.push_back(oldroot_namedview_child);
853             }
854         } else {
855             delete_list.push_back(child);
856         }
857     }
858     for (unsigned int i = 0; i < delete_list.size(); i++)
859         sp_repr_unparent(delete_list[i]);
861     for (Inkscape::XML::Node * child = newroot->firstChild();
862             child != NULL;
863             child = child->next()) {
864         if (!strcmp("sodipodi:namedview", child->name())) {
865             if (oldroot_namedview != NULL) {
866                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
867                         newroot_namedview_child != NULL;
868                         newroot_namedview_child = newroot_namedview_child->next()) {
869                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(child->document()));
870                 }
871             }
872         } else {
873             oldroot->appendChild(child->duplicate(newroot->document()));
874         }
875     }
877     oldroot->setAttribute("width", newroot->attribute("width"));
878     oldroot->setAttribute("height", newroot->attribute("height"));
880     /** \todo  Restore correct layer */
881     /** \todo  Restore correct selection */
884 /**  \brief  This function checks the stderr file, and if it has data,
885              shows it in a warning dialog to the user
886      \param  filename  Filename of the stderr file
887 */
888 void
889 Script::checkStderr (const Glib::ustring &data,
890                            Gtk::MessageType type,
891                      const Glib::ustring &message)
893     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
894     warning.set_resizable(true);
895     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
896     sp_transientize(dlg);
898     Gtk::VBox * vbox = warning.get_vbox();
900     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
901     Gtk::TextView * textview = new Gtk::TextView();
902     textview->set_editable(false);
903     textview->set_wrap_mode(Gtk::WRAP_WORD);
904     textview->show();
906     textview->get_buffer()->set_text(data.c_str());
908     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
909     scrollwindow->add(*textview);
910     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
911     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
912     scrollwindow->show();
914     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
916     warning.run();
918     return;
921 bool
922 Script::cancelProcessing (void) {
923     _canceled = true;
924     _main_loop->quit();
925     Glib::spawn_close_pid(_pid);
927     return true;
931 /** \brief    This is the core of the extension file as it actually does
932               the execution of the extension.
933     \param    in_command  The command to be executed
934     \param    filein      Filename coming in
935     \param    fileout     Filename of the out file
936     \return   Number of bytes that were read into the output file.
938     The first thing that this function does is build the command to be
939     executed.  This consists of the first string (in_command) and then
940     the filename for input (filein).  This file is put on the command
941     line.
943     The next thing is that this function does is open a pipe to the
944     command and get the file handle in the ppipe variable.  It then
945     opens the output file with the output file handle.  Both of these
946     operations are checked extensively for errors.
948     After both are opened, then the data is copied from the output
949     of the pipe into the file out using fread and fwrite.  These two
950     functions are used because of their primitive nature they make
951     no assumptions about the data.  A buffer is used in the transfer,
952     but the output of fread is stored so the exact number of bytes
953     is handled gracefully.
955     At the very end (after the data has been copied) both of the files
956     are closed, and we return to what we were doing.
957 */
958 int
959 Script::execute (const std::list<std::string> &in_command,
960                  const std::list<std::string> &in_params,
961                  const Glib::ustring &filein,
962                  file_listener &fileout)
964     g_return_val_if_fail(in_command.size() > 0, 0);
965     // printf("Executing\n");
967     std::vector <std::string> argv;
969 /*
970     for (std::list<std::string>::const_iterator i = in_command.begin();
971             i != in_command.end(); i++) {
972         argv.push_back(*i);
973     }
974 */
975     // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
976     // we tokenize so that spwan does not need to quote over all params
977     for (std::list<std::string>::const_iterator i = in_command.begin();
978             i != in_command.end(); i++) {
979         std::string param_str = *i;
980         //std::cout << "params " << param_str << std::endl;
981         do {
982             //std::cout << "param " << param_str << std::endl;
983             size_t first_space = param_str.find_first_of(' ');
984             size_t first_quote = param_str.find_first_of('"');
985             //std::cout << "first space " << first_space << std::endl;
986             //std::cout << "first quote " << first_quote << std::endl;
988             if((first_quote != std::string::npos) && (first_quote == 0)) {
989                 size_t next_quote = param_str.find_first_of('"', first_quote);
990                 //std::cout << "next quote " << next_quote << std::endl;
992                 if(next_quote != std::string::npos) {
993                     //std::cout << "now split " << next_quote << std::endl;
994                     //std::cout << "now split " << param_str.substr(1, next_quote) << std::endl;
995                     //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
996                     std::string part_str = param_str.substr(1, next_quote);
997                     if(part_str.size() > 0)
998                         argv.push_back(part_str);
999                     param_str = param_str.substr(next_quote + 1);
1001                 }
1002             }
1003             else if(first_space != std::string::npos) {
1004                 //std::cout << "now split " << first_space << std::endl;
1005                 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
1006                 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
1007                 std::string part_str = param_str.substr(0, first_space);
1008                 if(part_str.size() > 0)
1009                     argv.push_back(part_str);
1010                 param_str = param_str.substr(first_space + 1);
1011             }
1012             else {
1013                 if(param_str.size() > 0)
1014                     argv.push_back(param_str);
1015                 param_str = "";
1016             }
1017         } while(param_str.size() > 0);
1018     }
1020     for (std::list<std::string>::const_iterator i = in_params.begin();
1021             i != in_params.end(); i++) {
1022         argv.push_back(*i);
1023     }
1025     if (!(filein.empty())) {
1026                 argv.push_back(filein);
1027     }
1029     int stdout_pipe, stderr_pipe;
1031     try {
1032         Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1033                                      argv,  // arg v
1034                                      Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1035                                      sigc::slot<void>(),
1036                                      &_pid,          // Pid
1037                                      NULL,           // STDIN
1038                                      &stdout_pipe,   // STDOUT
1039                                      &stderr_pipe);  // STDERR
1040     } catch (Glib::SpawnError e) {
1041         printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1042         return 0;
1043     }
1045     _main_loop = Glib::MainLoop::create(false);
1047     file_listener fileerr;
1048     fileout.init(stdout_pipe, _main_loop);
1049     fileerr.init(stderr_pipe, _main_loop);
1051     _canceled = false;
1052     _main_loop->run();
1054     // Ensure all the data is out of the pipe
1055     while (!fileout.isDead())
1056         fileout.read(Glib::IO_IN);
1057     while (!fileerr.isDead())
1058         fileerr.read(Glib::IO_IN);
1060     if (_canceled) {
1061         // std::cout << "Script Canceled" << std::endl;
1062         return 0;
1063     }
1065     Glib::ustring stderr_data = fileerr.string();
1066     if (stderr_data.length() != 0) {
1067         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1068                                  _("Inkscape has received additional data from the script executed.  "
1069                                    "The script did not return an error, but this may indicate the results will not be as expected."));
1070     }
1072     Glib::ustring stdout_data = fileout.string();
1073     if (stdout_data.length() == 0) {
1074         return 0;
1075     }
1077     // std::cout << "Finishing Execution." << std::endl;
1078     return stdout_data.length();
1084 }  // namespace Implementation
1085 }  // namespace Extension
1086 }  // namespace Inkscape
1088 /*
1089   Local Variables:
1090   mode:c++
1091   c-file-style:"stroustrup"
1092   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1093   indent-tabs-mode:nil
1094   fill-column:99
1095   End:
1096 */
1097 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :