Code

r19083@shi: ted | 2008-04-21 16:09:24 -0700
[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(), INKSCAPE_EXTENSION_NS "script")) {
370             child_repr = sp_repr_children(child_repr);
371             while (child_repr != NULL) {
372                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
373                     const gchar *interpretstr = child_repr->attribute("interpreter");
374                     if (interpretstr != NULL) {
375                         Glib::ustring interpString =
376                             resolveInterpreterExecutable(interpretstr);
377                         //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
378                         command.insert(command.end(), interpretstr);
379                     }
380                     Glib::ustring tmp = "\"";
381                     tmp += solve_reldir(child_repr);
382                     tmp += "\"";
384                     command.insert(command.end(), tmp);
385                 }
386                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
387                     helper_extension = sp_repr_children(child_repr)->content();
388                 }
389                 child_repr = sp_repr_next(child_repr);
390             }
392             break;
393         }
394         child_repr = sp_repr_next(child_repr);
395     }
397     //g_return_val_if_fail(command.length() > 0, false);
399     return true;
403 /**
404     \return   None.
405     \brief    Unload this puppy!
406     \param    module  Extension to be unloaded.
408     This function just sets the module to unloaded.  It free's the
409     command if it has been allocated.
410 */
411 void
412 Script::unload(Inkscape::Extension::Extension */*module*/)
414     command.clear();
415     helper_extension = "";
421 /**
422     \return   Whether the check passed or not
423     \brief    Check every dependency that was given to make sure we should keep this extension
424     \param    module  The Extension in question
426 */
427 bool
428 Script::check(Inkscape::Extension::Extension *module)
430         int script_count = 0;
431     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
432     while (child_repr != NULL) {
433         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
434                         script_count++;
435             child_repr = sp_repr_children(child_repr);
436             while (child_repr != NULL) {
437                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
438                     Glib::ustring command_text = solve_reldir(child_repr);
439                     if (command_text.size() > 0) {
440                         /* I've got the command */
441                         bool existance = check_existance(command_text);
442                         if (!existance)
443                             return false;
444                     }
445                 }
447                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
448                     gchar const *helper = sp_repr_children(child_repr)->content();
449                     if (Inkscape::Extension::db.get(helper) == NULL) {
450                         return false;
451                     }
452                 }
454                 child_repr = sp_repr_next(child_repr);
455             }
457             break;
458         }
459         child_repr = sp_repr_next(child_repr);
460     }
462         if (script_count == 0) {
463                 return false;
464         }
466     return true;
469 class ScriptDocCache : public ImplementationDocumentCache {
470     friend class Script;
471 protected:
472     std::string _filename;
473     int _tempfd;
474 public:
475     ScriptDocCache (Inkscape::UI::View::View * view);
476     ~ScriptDocCache ( );
477 };
479 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
480     ImplementationDocumentCache(view),
481     _filename(""),
482     _tempfd(0)
484     try {
485         _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
486     } catch (...) {
487         /// \todo Popup dialog here
488         return;
489     }
491     SPDesktop *desktop = (SPDesktop *) view;
492     sp_namedview_document_from_window(desktop);
494     Inkscape::Extension::save(
495               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
496               view->doc(), _filename.c_str(), false, false, false);
498     return;
501 ScriptDocCache::~ScriptDocCache ( )
503     close(_tempfd);
504     unlink(_filename.c_str());
507 ImplementationDocumentCache *
508 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
509     return new ScriptDocCache(view);
513 /**
514     \return   A dialog for preferences
515     \brief    A stub funtion right now
516     \param    module    Module who's preferences need getting
517     \param    filename  Hey, the file you're getting might be important
519     This function should really do something, right now it doesn't.
520 */
521 Gtk::Widget *
522 Script::prefs_input(Inkscape::Extension::Input *module,
523                     const gchar */*filename*/)
525     return module->autogui(NULL, NULL);
530 /**
531     \return   A dialog for preferences
532     \brief    A stub funtion right now
533     \param    module    Module whose preferences need getting
535     This function should really do something, right now it doesn't.
536 */
537 Gtk::Widget *
538 Script::prefs_output(Inkscape::Extension::Output *module)
540     return module->autogui(NULL, NULL);
543 /**
544     \return  A new document that has been opened
545     \brief   This function uses a filename that is put in, and calls
546              the extension's command to create an SVG file which is
547              returned.
548     \param   module   Extension to use.
549     \param   filename File to open.
551     First things first, this function needs a temporary file name.  To
552     create on of those the function g_file_open_tmp is used with
553     the header of ink_ext_.
555     The extension is then executed using the 'execute' function
556     with the filname coming in, and the temporary filename.  After
557     That executing, the SVG should be in the temporary file.
559     Finally, the temporary file is opened using the SVG input module and
560     a document is returned.  That document has its filename set to
561     the incoming filename (so that it's not the temporary filename).
562     That document is then returned from this function.
563 */
564 SPDocument *
565 Script::open(Inkscape::Extension::Input *module,
566              const gchar *filenameArg)
568     std::list<std::string> params;
569     module->paramListString(params);
571     std::string tempfilename_out;
572     int tempfd_out = 0;
573     try {
574         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
575     } catch (...) {
576         /// \todo Popup dialog here
577         return NULL;
578     }
580     std::string lfilename = Glib::filename_from_utf8(filenameArg);
582     file_listener fileout;
583     int data_read = execute(command, params, lfilename, fileout);
584     fileout.toFile(tempfilename_out);
586     SPDocument * mydoc = NULL;
587     if (data_read > 10) {
588         if (helper_extension.size()==0) {
589             mydoc = Inkscape::Extension::open(
590                   Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
591                   tempfilename_out.c_str());
592         } else {
593             mydoc = Inkscape::Extension::open(
594                   Inkscape::Extension::db.get(helper_extension.c_str()),
595                   tempfilename_out.c_str());
596         }
597     } // data_read
599     if (mydoc != NULL) {
600         sp_document_set_uri(mydoc, filenameArg);
601     }
603     // make sure we don't leak file descriptors from g_file_open_tmp
604     close(tempfd_out);
606     unlink(tempfilename_out.c_str());
608     return mydoc;
609 } // open
613 /**
614     \return   none
615     \brief    This function uses an extention to save a document.  It first
616               creates an SVG file of the document, and then runs it through
617               the script.
618     \param    module    Extention to be used
619     \param    doc       Document to be saved
620     \param    filename  The name to save the final file as
622     Well, at some point people need to save - it is really what makes
623     the entire application useful.  And, it is possible that someone
624     would want to use an extetion for this, so we need a function to
625     do that eh?
627     First things first, the document is saved to a temporary file that
628     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
629     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
630     end of the function.
632     After we have the SVG file, then extention_execute is called with
633     the temporary file name and the final output filename.  This should
634     put the output of the script into the final output file.  We then
635     delete the temporary file.
636 */
637 void
638 Script::save(Inkscape::Extension::Output *module,
639              SPDocument *doc,
640              const gchar *filenameArg)
642     std::list<std::string> params;
643     module->paramListString(params);
645     std::string tempfilename_in;
646     int tempfd_in = 0;
647     try {
648         tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
649     } catch (...) {
650         /// \todo Popup dialog here
651         return;
652     }
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     }
665     file_listener fileout;
666     execute(command, params, tempfilename_in, fileout);
668     std::string lfilename = Glib::filename_from_utf8(filenameArg);
669     fileout.toFile(lfilename);
671     // make sure we don't leak file descriptors from g_file_open_tmp
672     close(tempfd_in);
673     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
674     unlink(tempfilename_in.c_str());
676     return;
681 /**
682     \return    none
683     \brief     This function uses an extention as a effect on a document.
684     \param     module   Extention to effect with.
685     \param     doc      Document to run through the effect.
687     This function is a little bit trickier than the previous two.  It
688     needs two temporary files to get it's work done.  Both of these
689     files have random names created for them using the g_file_open_temp function
690     with the ink_ext_ prefix in the temporary directory.  Like the other
691     functions, the temporary files are deleted at the end.
693     To save/load the two temporary documents (both are SVG) the internal
694     modules for SVG load and save are used.  They are both used through
695     the module system function by passing their keys into the functions.
697     The command itself is built a little bit differently than in other
698     functions because the effect support selections.  So on the command
699     line a list of all the ids that are selected is included.  Currently,
700     this only works for a single selected object, but there will be more.
701     The command string is filled with the data, and then after the execution
702     it is freed.
704     The execute function is used at the core of this function
705     to execute the Script on the two SVG documents (actually only one
706     exists at the time, the other is created by that script).  At that
707     point both should be full, and the second one is loaded.
708 */
709 void
710 Script::effect(Inkscape::Extension::Effect *module,
711                Inkscape::UI::View::View *doc,
712                ImplementationDocumentCache * docCache)
714     if (docCache == NULL) {
715         docCache = newDocCache(module, doc);
716     }
717     ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
718     if (dc == NULL) {
719         printf("TOO BAD TO LIVE!!!");
720         exit(1);
721     }
723     SPDesktop *desktop = (SPDesktop *)doc;
724     sp_namedview_document_from_window(desktop);
726     gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
728     std::list<std::string> params;
729     module->paramListString(params);
731     if (module->no_doc) {
732         // this is a no-doc extension, e.g. a Help menu command;
733         // just run the command without any files, ignoring errors
735         Glib::ustring empty;
736         file_listener outfile;
737         execute(command, params, empty, outfile);
739         return;
740     }
742     std::string tempfilename_out;
743     int tempfd_out = 0;
744     try {
745         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
746     } catch (...) {
747         /// \todo Popup dialog here
748         return;
749     }
751     if (desktop != NULL) {
752         Inkscape::Util::GSListConstIterator<SPItem *> selected =
753              sp_desktop_selection(desktop)->itemList();
754         while ( selected != NULL ) {
755             Glib::ustring selected_id;
756             selected_id += "--id=";
757             selected_id += SP_OBJECT_ID(*selected);
758             params.insert(params.begin(), selected_id);
759             ++selected;
760         }
761     }
763     file_listener fileout;
764     int data_read = execute(command, params, dc->_filename, fileout);
765     fileout.toFile(tempfilename_out);
767     pump_events();
769     SPDocument * mydoc = NULL;
770     if (data_read > 10) {
771         mydoc = Inkscape::Extension::open(
772               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
773               tempfilename_out.c_str());
774     } // data_read
776     pump_events();
778     // make sure we don't leak file descriptors from g_file_open_tmp
779     close(tempfd_out);
781     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
782     unlink(tempfilename_out.c_str());
784     /* Do something with mydoc.... */
785     if (mydoc) {
786         doc->doc()->emitReconstructionStart();
787         copy_doc(doc->doc()->rroot, mydoc->rroot);
788         doc->doc()->emitReconstructionFinish();
789         mydoc->release();
790         sp_namedview_update_layers_from_document(desktop);
792         sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
793     }
794     g_free(orig_output_extension);
796     return;
801 /**
802     \brief  A function to take all the svg elements from one document
803             and put them in another.
804     \param  oldroot  The root node of the document to be replaced
805     \param  newroot  The root node of the document to replace it with
807     This function first deletes all of the data in the old document.  It
808     does this by creating a list of what needs to be deleted, and then
809     goes through the list.  This two pass approach removes issues with
810     the list being change while parsing through it.  Lots of nasty bugs.
812     Then, it goes through the new document, duplicating all of the
813     elements and putting them into the old document.  The copy
814     is then complete.
815 */
816 void
817 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
819     std::vector<Inkscape::XML::Node *> delete_list;
820     Inkscape::XML::Node * oldroot_namedview = NULL;
822     for (Inkscape::XML::Node * child = oldroot->firstChild();
823             child != NULL;
824             child = child->next()) {
825         if (!strcmp("sodipodi:namedview", child->name())) {
826             oldroot_namedview = child;
827             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
828                     oldroot_namedview_child != NULL;
829                     oldroot_namedview_child = oldroot_namedview_child->next()) {
830                 delete_list.push_back(oldroot_namedview_child);
831             }
832         } else {
833             delete_list.push_back(child);
834         }
835     }
836     for (unsigned int i = 0; i < delete_list.size(); i++)
837         sp_repr_unparent(delete_list[i]);
839     for (Inkscape::XML::Node * child = newroot->firstChild();
840             child != NULL;
841             child = child->next()) {
842         if (!strcmp("sodipodi:namedview", child->name())) {
843             if (oldroot_namedview != NULL) {
844                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
845                         newroot_namedview_child != NULL;
846                         newroot_namedview_child = newroot_namedview_child->next()) {
847                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(child->document()));
848                 }
849             }
850         } else {
851             oldroot->appendChild(child->duplicate(newroot->document()));
852         }
853     }
855     oldroot->setAttribute("width", newroot->attribute("width"));
856     oldroot->setAttribute("height", newroot->attribute("height"));
858     /** \todo  Restore correct layer */
859     /** \todo  Restore correct selection */
862 /**  \brief  This function checks the stderr file, and if it has data,
863              shows it in a warning dialog to the user
864      \param  filename  Filename of the stderr file
865 */
866 void
867 Script::checkStderr (const Glib::ustring &data,
868                            Gtk::MessageType type,
869                      const Glib::ustring &message)
871     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
872     warning.set_resizable(true);
873     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
874     sp_transientize(dlg);
876     Gtk::VBox * vbox = warning.get_vbox();
878     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
879     Gtk::TextView * textview = new Gtk::TextView();
880     textview->set_editable(false);
881     textview->set_wrap_mode(Gtk::WRAP_WORD);
882     textview->show();
884     textview->get_buffer()->set_text(data.c_str());
886     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
887     scrollwindow->add(*textview);
888     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
889     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
890     scrollwindow->show();
892     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
894     warning.run();
896     return;
899 bool
900 Script::cancelProcessing (void) {
901     _canceled = true;
902     _main_loop->quit();
903     Glib::spawn_close_pid(_pid);
905     return true;
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 std::list<std::string> &in_command,
938                  const std::list<std::string> &in_params,
939                  const Glib::ustring &filein,
940                  file_listener &fileout)
942     g_return_val_if_fail(in_command.size() > 0, 0);
943     // printf("Executing\n");
945     std::vector <std::string> argv;
947 /*
948     for (std::list<std::string>::const_iterator i = in_command.begin();
949             i != in_command.end(); i++) {
950         argv.push_back(*i);
951     }
952 */
953     // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
954     // we tokenize so that spwan does not need to quote over all params
955     for (std::list<std::string>::const_iterator i = in_command.begin();
956             i != in_command.end(); i++) {
957         std::string param_str = *i;
958         do {
959             //g_message("param: %s", param_str.c_str());
960             size_t first_space = param_str.find_first_of(' ');
961             size_t first_quote = param_str.find_first_of('"');
962             //std::cout << "first space " << first_space << std::endl;
963             //std::cout << "first quote " << first_quote << std::endl;
965             if((first_quote != std::string::npos) && (first_quote == 0)) {
966                 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
967                 //std::cout << "next quote " << next_quote << std::endl;
969                 if(next_quote != std::string::npos) {
970                     //std::cout << "now split " << next_quote << std::endl;
971                     //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
972                     //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
973                     std::string part_str = param_str.substr(1, next_quote - 1);
974                     if(part_str.size() > 0)
975                         argv.push_back(part_str);
976                     param_str = param_str.substr(next_quote + 1);
978                 }
979                 else {
980                     if(param_str.size() > 0)
981                         argv.push_back(param_str);
982                     param_str = "";
983                 }
985             }
986             else if(first_space != std::string::npos) {
987                 //std::cout << "now split " << first_space << std::endl;
988                 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
989                 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
990                 std::string part_str = param_str.substr(0, first_space);
991                 if(part_str.size() > 0)
992                     argv.push_back(part_str);
993                 param_str = param_str.substr(first_space + 1);
994             }
995             else {
996                 if(param_str.size() > 0)
997                     argv.push_back(param_str);
998                 param_str = "";
999             }
1000         } while(param_str.size() > 0);
1001     }
1003     for (std::list<std::string>::const_iterator i = in_params.begin();
1004             i != in_params.end(); i++) {
1005         //g_message("Script parameter: %s",(*i)g.c_str());
1006         argv.push_back(*i);        
1007     }
1009     if (!(filein.empty())) {
1010                 argv.push_back(filein);
1011     }
1013     int stdout_pipe, stderr_pipe;
1015     try {
1016         Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1017                                      argv,  // arg v
1018                                      Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1019                                      sigc::slot<void>(),
1020                                      &_pid,          // Pid
1021                                      NULL,           // STDIN
1022                                      &stdout_pipe,   // STDOUT
1023                                      &stderr_pipe);  // STDERR
1024     } catch (Glib::SpawnError e) {
1025         printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1026         return 0;
1027     }
1029     _main_loop = Glib::MainLoop::create(false);
1031     file_listener fileerr;
1032     fileout.init(stdout_pipe, _main_loop);
1033     fileerr.init(stderr_pipe, _main_loop);
1035     _canceled = false;
1036     _main_loop->run();
1038     // Ensure all the data is out of the pipe
1039     while (!fileout.isDead())
1040         fileout.read(Glib::IO_IN);
1041     while (!fileerr.isDead())
1042         fileerr.read(Glib::IO_IN);
1044     if (_canceled) {
1045         // std::cout << "Script Canceled" << std::endl;
1046         return 0;
1047     }
1049     Glib::ustring stderr_data = fileerr.string();
1050     if (stderr_data.length() != 0) {
1051         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1052                                  _("Inkscape has received additional data from the script executed.  "
1053                                    "The script did not return an error, but this may indicate the results will not be as expected."));
1054     }
1056     Glib::ustring stdout_data = fileout.string();
1057     if (stdout_data.length() == 0) {
1058         return 0;
1059     }
1061     // std::cout << "Finishing Execution." << std::endl;
1062     return stdout_data.length();
1068 }  // namespace Implementation
1069 }  // namespace Extension
1070 }  // namespace Inkscape
1072 /*
1073   Local Variables:
1074   mode:c++
1075   c-file-style:"stroustrup"
1076   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1077   indent-tabs-mode:nil
1078   fill-column:99
1079   End:
1080 */
1081 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :