Code

09dc9eb30dd4f99a00294da59cef9fcdb69fb361
[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         {"python", "python-interpreter", "python" },
102         {"ruby",   "ruby-interpreter",   "ruby"   },
103         {"shell",  "shell-interpreter",  "sh"     },
104         { NULL,    NULL,                  NULL    }
105 };
109 /**
110  * Look up an interpreter name, and translate to something that
111  * is executable
112  */
113 static Glib::ustring
114 resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
117     Glib::ustring interpName = interpNameArg;
119     interpreter_t const *interp;
120     bool foundInterp = false;
121     for (interp =  interpreterTab ; interp->identity ; interp++ ){
122         if (interpName == interp->identity) {
123             foundInterp = true;
124             break;
125         }
126     }
128     // Do we have a supported interpreter type?
129     if (!foundInterp)
130         return "";
131     interpName = interp->defaultval;
133     // 1.  Check preferences
134     gchar const *prefInterp = prefs_get_string_attribute("extensions", interp->prefstring);
136     if (prefInterp) {
137         interpName = prefInterp;
138         return interpName;
139     }
141 #ifdef _WIN32
143     // 2.  Windows.  Try looking relative to inkscape.exe
144     RegistryTool rt;
145     Glib::ustring fullPath;
146     Glib::ustring path;
147     Glib::ustring exeName;
148     if (rt.getExeInfo(fullPath, path, exeName)) {
149         Glib::ustring interpPath = path;
150         interpPath.append("\\");
151         interpPath.append(interpName);
152         interpPath.append("\\");
153         interpPath.append(interpName);
154         interpPath.append(".exe");
155         struct stat finfo;
156         if (stat(interpPath .c_str(), &finfo) ==0) {
157             g_message("Found local interpreter, '%s',  Size: %d",
158                       interpPath .c_str(),
159                       (int)finfo.st_size);
160             return interpPath;
161         }
162     }
164     // 3. Try searching the path
165     char szExePath[MAX_PATH];
166     char szCurrentDir[MAX_PATH];
167     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
168     unsigned int ret = (unsigned int)FindExecutable(
169                   interpName.c_str(), szCurrentDir, szExePath);
170     if (ret > 32) {
171         interpName = szExePath;
172         return interpName;
173     }
175 #endif // win32
178     return interpName;
183 /** \brief     This function creates a script object and sets up the
184                variables.
185     \return    A script object
187    This function just sets the command to NULL.  It should get built
188    officially in the load function.  This allows for less allocation
189    of memory in the unloaded state.
190 */
191 Script::Script() :
192     Implementation()
197 /**
198  *   brief     Destructor
199  */
200 Script::~Script()
206 /**
207     \return    A string with the complete string with the relative directory expanded
208     \brief     This function takes in a Repr that contains a reldir entry
209                and returns that data with the relative directory expanded.
210                Mostly it is here so that relative directories all get used
211                the same way.
212     \param     reprin   The Inkscape::XML::Node with the reldir in it.
214     Basically this function looks at an attribute of the Repr, and makes
215     a decision based on that.  Currently, it is only working with the
216     'extensions' relative directory, but there will be more of them.
217     One thing to notice is that this function always returns an allocated
218     string.  This means that the caller of this function can always
219     free what they are given (and should do it too!).
220 */
221 Glib::ustring
222 Script::solve_reldir(Inkscape::XML::Node *reprin) {
224     gchar const *s = reprin->attribute("reldir");
226     if (!s) {
227         Glib::ustring str = sp_repr_children(reprin)->content();
228         return str;
229     }
231     Glib::ustring reldir = s;
233     if (reldir == "extensions") {
235         for (unsigned int i=0;
236             i < Inkscape::Extension::Extension::search_path.size();
237             i++) {
239             gchar * fname = g_build_filename(
240                Inkscape::Extension::Extension::search_path[i],
241                sp_repr_children(reprin)->content(),
242                NULL);
243             Glib::ustring filename = fname;
244             g_free(fname);
246             if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
247                 return filename;
249         }
250     } else {
251         Glib::ustring str = sp_repr_children(reprin)->content();
252         return str;
253     }
255     return "";
260 /**
261     \return   Whether the command given exists, including in the path
262     \brief    This function is used to find out if something exists for
263               the check command.  It can look in the path if required.
264     \param    command   The command or file that should be looked for
266     The first thing that this function does is check to see if the
267     incoming file name has a directory delimiter in it.  This would
268     mean that it wants to control the directories, and should be
269     used directly.
271     If not, the path is used.  Each entry in the path is stepped through,
272     attached to the string, and then tested.  If the file is found
273     then a TRUE is returned.  If we get all the way through the path
274     then a FALSE is returned, the command could not be found.
275 */
276 bool
277 Script::check_existance(const Glib::ustring &command)
280     // Check the simple case first
281     if (command.size() == 0) {
282         return false;
283     }
285     //Don't search when it contains a slash. */
286     if (command.find(G_DIR_SEPARATOR) != command.npos) {
287         if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
288             return true;
289         else
290             return false;
291     }
294     Glib::ustring path;
295     gchar *s = (gchar *) g_getenv("PATH");
296     if (s)
297         path = s;
298     else
299        /* There is no `PATH' in the environment.
300            The default search path is the current directory */
301         path = G_SEARCHPATH_SEPARATOR_S;
303     std::string::size_type pos  = 0;
304     std::string::size_type pos2 = 0;
305     while ( pos < path.size() ) {
307         Glib::ustring localPath;
309         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
310         if (pos2 == path.npos) {
311             localPath = path.substr(pos);
312             pos = path.size();
313         } else {
314             localPath = path.substr(pos, pos2-pos);
315             pos = pos2+1;
316         }
318         //printf("### %s\n", localPath.c_str());
319         Glib::ustring candidatePath =
320                       Glib::build_filename(localPath, command);
322         if (Inkscape::IO::file_test(candidatePath .c_str(),
323                       G_FILE_TEST_EXISTS)) {
324             return true;
325         }
327     }
329     return false;
336 /**
337     \return   none
338     \brief    This function 'loads' an extention, basically it determines
339               the full command for the extention and stores that.
340     \param    module  The extention to be loaded.
342     The most difficult part about this function is finding the actual
343     command through all of the Reprs.  Basically it is hidden down a
344     couple of layers, and so the code has to move down too.  When
345     the command is actually found, it has its relative directory
346     solved.
348     At that point all of the loops are exited, and there is an
349     if statement to make sure they didn't exit because of not finding
350     the command.  If that's the case, the extention doesn't get loaded
351     and should error out at a higher level.
352 */
354 bool
355 Script::load(Inkscape::Extension::Extension *module)
357     if (module->loaded())
358         return TRUE;
360     helper_extension = "";
362     /* This should probably check to find the executable... */
363     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
364     while (child_repr != NULL) {
365         if (!strcmp(child_repr->name(), "script")) {
366             child_repr = sp_repr_children(child_repr);
367             while (child_repr != NULL) {
368                 if (!strcmp(child_repr->name(), "command")) {
369                     const gchar *interpretstr = child_repr->attribute("interpreter");
370                     if (interpretstr != NULL) {
371                         Glib::ustring interpString =
372                             resolveInterpreterExecutable(interpretstr);
373                         command.insert(command.end(), interpretstr);
374                     }
376                     command.insert(command.end(), solve_reldir(child_repr));
377                 }
378                 if (!strcmp(child_repr->name(), "helper_extension")) {
379                     helper_extension = sp_repr_children(child_repr)->content();
380                 }
381                 child_repr = sp_repr_next(child_repr);
382             }
384             break;
385         }
386         child_repr = sp_repr_next(child_repr);
387     }
389     //g_return_val_if_fail(command.length() > 0, FALSE);
391     return true;
395 /**
396     \return   None.
397     \brief    Unload this puppy!
398     \param    module  Extension to be unloaded.
400     This function just sets the module to unloaded.  It free's the
401     command if it has been allocated.
402 */
403 void
404 Script::unload(Inkscape::Extension::Extension */*module*/)
406     command.clear();
407     helper_extension = "";
413 /**
414     \return   Whether the check passed or not
415     \brief    Check every dependency that was given to make sure we should keep this extension
416     \param    module  The Extension in question
418 */
419 bool
420 Script::check(Inkscape::Extension::Extension *module)
422     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
423     while (child_repr != NULL) {
424         if (!strcmp(child_repr->name(), "script")) {
425             child_repr = sp_repr_children(child_repr);
426             while (child_repr != NULL) {
427                 if (!strcmp(child_repr->name(), "check")) {
428                     Glib::ustring command_text = solve_reldir(child_repr);
429                     if (command_text.size() > 0) {
430                         /* I've got the command */
431                         bool existance = check_existance(command_text);
432                         if (!existance)
433                             return FALSE;
434                     }
435                 }
437                 if (!strcmp(child_repr->name(), "helper_extension")) {
438                     gchar const *helper = sp_repr_children(child_repr)->content();
439                     if (Inkscape::Extension::db.get(helper) == NULL) {
440                         return FALSE;
441                     }
442                 }
444                 child_repr = sp_repr_next(child_repr);
445             }
447             break;
448         }
449         child_repr = sp_repr_next(child_repr);
450     }
452     return true;
455 class ScriptDocCache : public ImplementationDocumentCache {
456         friend class Script;
457 protected:
458         std::string _filename;
459     int _tempfd;
460 public:
461         ScriptDocCache (Inkscape::UI::View::View * view);
462         ~ScriptDocCache ( );
463 };
465 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
466         ImplementationDocumentCache(view),
467         _filename(""),
468         _tempfd(0)
470     try {
471         _tempfd = Glib::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
472     } catch (...) {
473         /// \todo Popup dialog here
474         return;
475     }
477     SPDesktop *desktop = (SPDesktop *) view;
478     sp_namedview_document_from_window(desktop);
480     Inkscape::Extension::save(
481               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
482               view->doc(), _filename.c_str(), FALSE, FALSE, FALSE);
484         return;
487 ScriptDocCache::~ScriptDocCache ( )
489     close(_tempfd);
490     unlink(_filename.c_str());
493 ImplementationDocumentCache *
494 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
495     return new ScriptDocCache(view);
499 /**
500     \return   A dialog for preferences
501     \brief    A stub funtion right now
502     \param    module    Module who's preferences need getting
503     \param    filename  Hey, the file you're getting might be important
505     This function should really do something, right now it doesn't.
506 */
507 Gtk::Widget *
508 Script::prefs_input(Inkscape::Extension::Input *module,
509                     const gchar */*filename*/)
511     return module->autogui(NULL, NULL);
516 /**
517     \return   A dialog for preferences
518     \brief    A stub funtion right now
519     \param    module    Module whose preferences need getting
521     This function should really do something, right now it doesn't.
522 */
523 Gtk::Widget *
524 Script::prefs_output(Inkscape::Extension::Output *module)
526     return module->autogui(NULL, NULL);
531 /**
532     \return   A dialog for preferences
533     \brief    A stub funtion right now
534     \param    module    Module who's preferences need getting
536     This function should really do something, right now it doesn't.
537 */
538 Gtk::Widget *
539 Script::prefs_effect( Inkscape::Extension::Effect *module,
540                       Inkscape::UI::View::View *view,
541                       sigc::signal<void> * changeSignal,
542                       ImplementationDocumentCache * /*docCache*/ )
544     SPDocument * current_document = view->doc();
546     using Inkscape::Util::GSListConstIterator;
547     GSListConstIterator<SPItem *> selected =
548            sp_desktop_selection((SPDesktop *)view)->itemList();
549     Inkscape::XML::Node * first_select = NULL;
550     if (selected != NULL) {
551         const SPItem * item = *selected;
552         first_select = SP_OBJECT_REPR(item);
553     }
555     return module->autogui(current_document, first_select, changeSignal);
561 /**
562     \return  A new document that has been opened
563     \brief   This function uses a filename that is put in, and calls
564              the extension's command to create an SVG file which is
565              returned.
566     \param   module   Extension to use.
567     \param   filename File to open.
569     First things first, this function needs a temporary file name.  To
570     create on of those the function g_file_open_tmp is used with
571     the header of ink_ext_.
573     The extension is then executed using the 'execute' function
574     with the filname coming in, and the temporary filename.  After
575     That executing, the SVG should be in the temporary file.
577     Finally, the temporary file is opened using the SVG input module and
578     a document is returned.  That document has its filename set to
579     the incoming filename (so that it's not the temporary filename).
580     That document is then returned from this function.
581 */
582 SPDocument *
583 Script::open(Inkscape::Extension::Input *module,
584              const gchar *filenameArg)
586     std::list<std::string> params;
587     module->paramListString(params);
589     std::string tempfilename_out;
590     int tempfd_out = 0;
591     try {
592         tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
593     } catch (...) {
594         /// \todo Popup dialog here
595         return NULL;
596     }
598     std::string lfilename = Glib::filename_from_utf8(filenameArg);
600     file_listener fileout;
601     int data_read = execute(command, params, lfilename, fileout);
602     fileout.toFile(tempfilename_out);
604     SPDocument * mydoc = NULL;
605     if (data_read > 10) {
606         if (helper_extension.size()==0) {
607             mydoc = Inkscape::Extension::open(
608                   Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
609                   tempfilename_out.c_str());
610         } else {
611             mydoc = Inkscape::Extension::open(
612                   Inkscape::Extension::db.get(helper_extension.c_str()),
613                   tempfilename_out.c_str());
614         }
615     } // data_read
617     if (mydoc != NULL) {
618         sp_document_set_uri(mydoc, filenameArg);
619     }
621     // make sure we don't leak file descriptors from g_file_open_tmp
622     close(tempfd_out);
624     unlink(tempfilename_out.c_str());
626     return mydoc;
627 } // open
631 /**
632     \return   none
633     \brief    This function uses an extention to save a document.  It first
634               creates an SVG file of the document, and then runs it through
635               the script.
636     \param    module    Extention to be used
637     \param    doc       Document to be saved
638     \param    filename  The name to save the final file as
640     Well, at some point people need to save - it is really what makes
641     the entire application useful.  And, it is possible that someone
642     would want to use an extetion for this, so we need a function to
643     do that eh?
645     First things first, the document is saved to a temporary file that
646     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
647     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
648     end of the function.
650     After we have the SVG file, then extention_execute is called with
651     the temporary file name and the final output filename.  This should
652     put the output of the script into the final output file.  We then
653     delete the temporary file.
654 */
655 void
656 Script::save(Inkscape::Extension::Output *module,
657              SPDocument *doc,
658              const gchar *filenameArg)
660     std::list<std::string> params;
661     module->paramListString(params);
663     std::string tempfilename_in;
664     int tempfd_in = 0;
665     try {
666         tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
667     } catch (...) {
668         /// \todo Popup dialog here
669         return;
670     }
672     if (helper_extension.size() == 0) {
673         Inkscape::Extension::save(
674                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
675                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
676     } else {
677         Inkscape::Extension::save(
678                    Inkscape::Extension::db.get(helper_extension.c_str()),
679                    doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
680     }
683     file_listener fileout;
684     execute(command, params, tempfilename_in, fileout);
686     std::string lfilename = Glib::filename_from_utf8(filenameArg);
687     fileout.toFile(lfilename);
689     // make sure we don't leak file descriptors from g_file_open_tmp
690     close(tempfd_in);
691     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
692     unlink(tempfilename_in.c_str());
694     return;
699 /**
700     \return    none
701     \brief     This function uses an extention as a effect on a document.
702     \param     module   Extention to effect with.
703     \param     doc      Document to run through the effect.
705     This function is a little bit trickier than the previous two.  It
706     needs two temporary files to get it's work done.  Both of these
707     files have random names created for them using the g_file_open_temp function
708     with the ink_ext_ prefix in the temporary directory.  Like the other
709     functions, the temporary files are deleted at the end.
711     To save/load the two temporary documents (both are SVG) the internal
712     modules for SVG load and save are used.  They are both used through
713     the module system function by passing their keys into the functions.
715     The command itself is built a little bit differently than in other
716     functions because the effect support selections.  So on the command
717     line a list of all the ids that are selected is included.  Currently,
718     this only works for a single selected object, but there will be more.
719     The command string is filled with the data, and then after the execution
720     it is freed.
722     The execute function is used at the core of this function
723     to execute the Script on the two SVG documents (actually only one
724     exists at the time, the other is created by that script).  At that
725     point both should be full, and the second one is loaded.
726 */
727 void
728 Script::effect(Inkscape::Extension::Effect *module,
729                Inkscape::UI::View::View *doc,
730                            ImplementationDocumentCache * docCache)
732         if (docCache == NULL) {
733                 docCache = newDocCache(module, doc);
734         }
735         ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
736         if (dc == NULL) {
737                 printf("TOO BAD TO LIVE!!!");
738                 exit(1);
739         }
741     SPDesktop *desktop = (SPDesktop *)doc;
742     sp_namedview_document_from_window(desktop);
744         gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
746     std::list<std::string> params;
747     module->paramListString(params);
749     if (module->no_doc) {
750         // this is a no-doc extension, e.g. a Help menu command;
751         // just run the command without any files, ignoring errors
753         Glib::ustring empty;
754         file_listener outfile;
755         execute(command, params, empty, outfile);
757         return;
758     }
760     std::string tempfilename_out;
761     int tempfd_out = 0;
762     try {
763         tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
764     } catch (...) {
765         /// \todo Popup dialog here
766         return;
767     }
769     if (desktop != NULL) {
770         Inkscape::Util::GSListConstIterator<SPItem *> selected =
771              sp_desktop_selection(desktop)->itemList();
772         while ( selected != NULL ) {
773             Glib::ustring selected_id;
774             selected_id += "--id=";
775             selected_id += SP_OBJECT_ID(*selected);
776             params.insert(params.begin(), selected_id);
777             ++selected;
778         }
779     }
781     file_listener fileout;
782     int data_read = execute(command, params, dc->_filename, fileout);
783     fileout.toFile(tempfilename_out);
785     pump_events();
787     SPDocument * mydoc = NULL;
788     if (data_read > 10) {
789         mydoc = Inkscape::Extension::open(
790               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
791               tempfilename_out.c_str());
792     } // data_read
794     pump_events();
796     // make sure we don't leak file descriptors from g_file_open_tmp
797     close(tempfd_out);
799     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
800     unlink(tempfilename_out.c_str());
802     /* Do something with mydoc.... */
803     if (mydoc) {
804         doc->doc()->emitReconstructionStart();
805         copy_doc(doc->doc()->rroot, mydoc->rroot);
806         doc->doc()->emitReconstructionFinish();
807         mydoc->release();
808         sp_namedview_update_layers_from_document(desktop);
810                 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
811     }
812         g_free(orig_output_extension);
814     return;
819 /**
820     \brief  A function to take all the svg elements from one document
821             and put them in another.
822     \param  oldroot  The root node of the document to be replaced
823     \param  newroot  The root node of the document to replace it with
825     This function first deletes all of the data in the old document.  It
826     does this by creating a list of what needs to be deleted, and then
827     goes through the list.  This two pass approach removes issues with
828     the list being change while parsing through it.  Lots of nasty bugs.
830     Then, it goes through the new document, duplicating all of the
831     elements and putting them into the old document.  The copy
832     is then complete.
833 */
834 void
835 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
837     std::vector<Inkscape::XML::Node *> delete_list;
838     Inkscape::XML::Node * oldroot_namedview = NULL;
840     for (Inkscape::XML::Node * child = oldroot->firstChild();
841             child != NULL;
842             child = child->next()) {
843         if (!strcmp("sodipodi:namedview", child->name())) {
844             oldroot_namedview = child;
845             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
846                     oldroot_namedview_child != NULL;
847                     oldroot_namedview_child = oldroot_namedview_child->next()) {
848                 delete_list.push_back(oldroot_namedview_child);
849             }
850         } else {
851             delete_list.push_back(child);
852         }
853     }
854     for (unsigned int i = 0; i < delete_list.size(); i++)
855         sp_repr_unparent(delete_list[i]);
857     for (Inkscape::XML::Node * child = newroot->firstChild();
858             child != NULL;
859             child = child->next()) {
860         if (!strcmp("sodipodi:namedview", child->name())) {
861             if (oldroot_namedview != NULL) {
862                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
863                         newroot_namedview_child != NULL;
864                         newroot_namedview_child = newroot_namedview_child->next()) {
865                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(child->document()));
866                 }
867             }
868         } else {
869             oldroot->appendChild(child->duplicate(newroot->document()));
870         }
871     }
873     oldroot->setAttribute("width", newroot->attribute("width"));
874     oldroot->setAttribute("height", newroot->attribute("height"));
876     /** \todo  Restore correct layer */
877     /** \todo  Restore correct selection */
880 /**  \brief  This function checks the stderr file, and if it has data,
881              shows it in a warning dialog to the user
882      \param  filename  Filename of the stderr file
883 */
884 void
885 Script::checkStderr (const Glib::ustring &data,
886                            Gtk::MessageType type,
887                      const Glib::ustring &message)
889     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
890     warning.set_resizable(true);
891     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
892     sp_transientize(dlg);
894     Gtk::VBox * vbox = warning.get_vbox();
896     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
897     Gtk::TextView * textview = new Gtk::TextView();
898     textview->set_editable(false);
899     textview->set_wrap_mode(Gtk::WRAP_WORD);
900     textview->show();
902     textview->get_buffer()->set_text(data.c_str());
904     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
905     scrollwindow->add(*textview);
906     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
907     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
908     scrollwindow->show();
910     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
912     warning.run();
914     return;
917 bool
918 Script::cancelProcessing (void) {
919     _canceled = true;
920     _main_loop->quit();
921     Glib::spawn_close_pid(_pid);
923     return true;
927 /** \brief    This is the core of the extension file as it actually does
928               the execution of the extension.
929     \param    in_command  The command to be executed
930     \param    filein      Filename coming in
931     \param    fileout     Filename of the out file
932     \return   Number of bytes that were read into the output file.
934     The first thing that this function does is build the command to be
935     executed.  This consists of the first string (in_command) and then
936     the filename for input (filein).  This file is put on the command
937     line.
939     The next thing is that this function does is open a pipe to the
940     command and get the file handle in the ppipe variable.  It then
941     opens the output file with the output file handle.  Both of these
942     operations are checked extensively for errors.
944     After both are opened, then the data is copied from the output
945     of the pipe into the file out using fread and fwrite.  These two
946     functions are used because of their primitive nature they make
947     no assumptions about the data.  A buffer is used in the transfer,
948     but the output of fread is stored so the exact number of bytes
949     is handled gracefully.
951     At the very end (after the data has been copied) both of the files
952     are closed, and we return to what we were doing.
953 */
954 int
955 Script::execute (const std::list<std::string> &in_command,
956                  const std::list<std::string> &in_params,
957                  const Glib::ustring &filein,
958                  file_listener &fileout)
960     g_return_val_if_fail(in_command.size() > 0, 0);
961     // printf("Executing\n");
963     std::vector <std::string> argv;
965 /*
966     for (std::list<std::string>::const_iterator i = in_command.begin();
967             i != in_command.end(); i++) {
968         argv.push_back(*i);
969     }
970 */
971     // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
972     // we tokenize so that spwan does not need to quote over all params
973     for (std::list<std::string>::const_iterator i = in_command.begin();
974             i != in_command.end(); i++) {
975         std::string param_str = *i;
976         //std::cout << "params " << param_str << std::endl;
977         do {
978             //std::cout << "param " << param_str << std::endl;
979             size_t first_space = param_str.find_first_of(' ');
980             size_t first_quote = param_str.find_first_of('"');
981             //std::cout << "first space " << first_space << std::endl;
982             //std::cout << "first quote " << first_quote << std::endl;
984             if((first_quote != std::string::npos) && (first_quote == 0)) {
985                 size_t next_quote = param_str.find_first_of('"', first_quote);
986                 //std::cout << "next quote " << next_quote << std::endl;
988                 if(next_quote != std::string::npos) {
989                     //std::cout << "now split " << next_quote << std::endl;
990                     //std::cout << "now split " << param_str.substr(1, next_quote) << std::endl;
991                     //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
992                     std::string part_str = param_str.substr(1, next_quote);
993                     if(part_str.size() > 0)
994                         argv.push_back(part_str);
995                     param_str = param_str.substr(next_quote + 1);
997                 }
998             }
999             else if(first_space != std::string::npos) {
1000                 //std::cout << "now split " << first_space << std::endl;
1001                 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
1002                 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
1003                 std::string part_str = param_str.substr(0, first_space);
1004                 if(part_str.size() > 0)
1005                     argv.push_back(part_str);
1006                 param_str = param_str.substr(first_space + 1);
1007             }
1008             else {
1009                 if(param_str.size() > 0)
1010                     argv.push_back(param_str);
1011                 param_str = "";
1012             }
1013         } while(param_str.size() > 0);
1014     }
1016     if (!(filein.empty())) {
1017 //        if(argv.size() == 1) {
1018             argv.push_back(filein);
1019 /*        }
1020         else {
1021             std::string parameter_str = argv.back();
1022             argv.pop_back();
1023             //TODO: quote
1024             parameter_str += " " + filein;
1025             argv.push_back(parameter_str);
1026         }
1027 */
1028     }
1030     for (std::list<std::string>::const_iterator i = in_params.begin();
1031             i != in_params.end(); i++) {
1032         argv.push_back(*i);
1033     }
1035 /*
1036     for (std::vector<std::string>::const_iterator i = argv.begin();
1037             i != argv.end(); i++) {
1038         std::cout << "_" << *i << "_" << std::endl;
1039     }
1040 */
1042     int stdout_pipe, stderr_pipe;
1044     try {
1045         Glib::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1046                                      argv,  // arg v
1047                                      Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1048                                      sigc::slot<void>(),
1049                                      &_pid,          // Pid
1050                                      NULL,           // STDIN
1051                                      &stdout_pipe,   // STDOUT
1052                                      &stderr_pipe);  // STDERR
1053     } catch (Glib::SpawnError e) {
1054         printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1055         return 0;
1056     }
1058     _main_loop = Glib::MainLoop::create(false);
1060     file_listener fileerr;
1061     fileout.init(stdout_pipe, _main_loop);
1062     fileerr.init(stderr_pipe, _main_loop);
1064     _canceled = false;
1065     _main_loop->run();
1067     // Ensure all the data is out of the pipe
1068     while (!fileout.isDead())
1069         fileout.read(Glib::IO_IN);
1070     while (!fileerr.isDead())
1071         fileerr.read(Glib::IO_IN);
1073     if (_canceled) {
1074         // std::cout << "Script Canceled" << std::endl;
1075         return 0;
1076     }
1078     Glib::ustring stderr_data = fileerr.string();
1079     if (stderr_data.length() != 0) {
1080         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1081                                  _("Inkscape has received additional data from the script executed.  "
1082                                    "The script did not return an error, but this may indicate the results will not be as expected."));
1083     }
1085     Glib::ustring stdout_data = fileout.string();
1086     if (stdout_data.length() == 0) {
1087         return 0;
1088     }
1090     // std::cout << "Finishing Execution." << std::endl;
1091     return stdout_data.length();
1097 }  // namespace Implementation
1098 }  // namespace Extension
1099 }  // namespace Inkscape
1101 /*
1102   Local Variables:
1103   mode:c++
1104   c-file-style:"stroustrup"
1105   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1106   indent-tabs-mode:nil
1107   fill-column:99
1108   End:
1109 */
1110 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :