Code

Merge from trunk
[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  *   Jon A. Cruz <jon@joncruz.org>
9  *
10  * Copyright (C) 2002-2005,2007 Authors
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
15 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
21 #include <unistd.h>
23 #include <errno.h>
24 #include <gtkmm.h>
26 #include "ui/view/view.h"
27 #include "desktop-handles.h"
28 #include "desktop.h"
29 #include "selection.h"
30 #include "sp-namedview.h"
31 #include "io/sys.h"
32 #include "preferences.h"
33 #include "../system.h"
34 #include "extension/effect.h"
35 #include "extension/output.h"
36 #include "extension/input.h"
37 #include "extension/db.h"
38 #include "script.h"
39 #include "dialogs/dialog-events.h"
40 #include "application/application.h"
41 #include "xml/node.h"
42 #include "xml/attribute-record.h"
44 #include "util/glib-list-iterators.h"
48 #ifdef WIN32
49 #include <windows.h>
50 #include <sys/stat.h>
51 #include "registrytool.h"
52 #endif
56 /** This is the command buffer that gets allocated from the stack */
57 #define BUFSIZE (255)
61 /* Namespaces */
62 namespace Inkscape {
63 namespace Extension {
64 namespace Implementation {
66 /** \brief  Make GTK+ events continue to come through a little bit
68     This just keeps coming the events through so that we'll make the GUI
69     update and look pretty.
70 */
71 void Script::pump_events (void) {
72     while ( Gtk::Main::events_pending() ) {
73         Gtk::Main::iteration();
74     }
75     return;
76 }
79 /** \brief  A table of what interpreters to call for a given language
81     This table is used to keep track of all the programs to execute a
82     given script.  It also tracks the preference to use to overwrite
83     the given interpreter to a custom one per user.
84 */
85 Script::interpreter_t const Script::interpreterTab[] = {
86         {"perl",   "perl-interpreter",   "perl"   },
87 #ifdef WIN32
88         {"python", "python-interpreter", "pythonw" },
89 #else
90         {"python", "python-interpreter", "python" },
91 #endif
92         {"ruby",   "ruby-interpreter",   "ruby"   },
93         {"shell",  "shell-interpreter",  "sh"     },
94         { NULL,    NULL,                  NULL    }
95 };
99 /** \brief Look up an interpreter name, and translate to something that
100     is executable
101     \param interpNameArg  The name of the interpreter that we're looking
102     for, should be an entry in interpreterTab
103 */
104 Glib::ustring Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
106     Glib::ustring interpName = interpNameArg;
108     interpreter_t const *interp = 0;
109     bool foundInterp = false;
110     for (interp =  interpreterTab ; interp->identity ; interp++ ){
111         if (interpName == interp->identity) {
112             foundInterp = true;
113             break;
114         }
115     }
117     // Do we have a supported interpreter type?
118     if (!foundInterp) {
119         return "";
120     }
121     interpName = interp->defaultval;
123     // 1.  Check preferences
124     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
125     Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
127     if (!prefInterp.empty()) {
128         interpName = prefInterp;
129         return interpName;
130     }
132 #ifdef WIN32
134     // 2.  Windows.  Try looking relative to inkscape.exe
135     RegistryTool rt;
136     Glib::ustring fullPath;
137     Glib::ustring path;
138     Glib::ustring exeName;
139     if (rt.getExeInfo(fullPath, path, exeName)) {
140 // TODO replace with proper glib/glibmm path building routines:
141         Glib::ustring interpPath = path;
142         interpPath.append("\\");
143         interpPath.append(interpNameArg);
144         interpPath.append("\\");
145         interpPath.append(interpName);
146         interpPath.append(".exe");
147         struct stat finfo;
148         if (stat(interpPath .c_str(), &finfo) == 0) {
149             g_message("Found local interpreter, '%s',  Size: %d",
150                       interpPath .c_str(),
151                       (int)finfo.st_size);
152             return interpPath;
153         }
154     }
156     // 3. Try searching the path
157     char szExePath[MAX_PATH] = {0};
158     char szCurrentDir[MAX_PATH] = {0};
159     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
160     HINSTANCE ret = FindExecutable(interpName.c_str(), szCurrentDir, szExePath);
161     if (ret > reinterpret_cast<HINSTANCE>(32)) {
162         interpName = szExePath;
163         return interpName;
164     }
166 #endif // win32
169     return interpName;
172 /** \brief     This function creates a script object and sets up the
173                variables.
174     \return    A script object
176    This function just sets the command to NULL.  It should get built
177    officially in the load function.  This allows for less allocation
178    of memory in the unloaded state.
179 */
180 Script::Script() :
181     Implementation()
185 /**
186  *   brief     Destructor
187  */
188 Script::~Script()
194 /**
195     \return    A string with the complete string with the relative directory expanded
196     \brief     This function takes in a Repr that contains a reldir entry
197                and returns that data with the relative directory expanded.
198                Mostly it is here so that relative directories all get used
199                the same way.
200     \param     reprin   The Inkscape::XML::Node with the reldir in it.
202     Basically this function looks at an attribute of the Repr, and makes
203     a decision based on that.  Currently, it is only working with the
204     'extensions' relative directory, but there will be more of them.
205     One thing to notice is that this function always returns an allocated
206     string.  This means that the caller of this function can always
207     free what they are given (and should do it too!).
208 */
209 Glib::ustring
210 Script::solve_reldir(Inkscape::XML::Node *reprin) {
212     gchar const *s = reprin->attribute("reldir");
214     if (!s) {
215         Glib::ustring str = sp_repr_children(reprin)->content();
216         return str;
217     }
219     Glib::ustring reldir = s;
221     if (reldir == "extensions") {
223         for (unsigned int i=0;
224             i < Inkscape::Extension::Extension::search_path.size();
225             i++) {
227             gchar * fname = g_build_filename(
228                Inkscape::Extension::Extension::search_path[i],
229                sp_repr_children(reprin)->content(),
230                NULL);
231             Glib::ustring filename = fname;
232             g_free(fname);
234             if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) ) {
235                 return filename;
236             }
237         }
238     } else {
239         Glib::ustring str = sp_repr_children(reprin)->content();
240         return str;
241     }
243     return "";
248 /**
249     \return   Whether the command given exists, including in the path
250     \brief    This function is used to find out if something exists for
251               the check command.  It can look in the path if required.
252     \param    command   The command or file that should be looked for
254     The first thing that this function does is check to see if the
255     incoming file name has a directory delimiter in it.  This would
256     mean that it wants to control the directories, and should be
257     used directly.
259     If not, the path is used.  Each entry in the path is stepped through,
260     attached to the string, and then tested.  If the file is found
261     then a TRUE is returned.  If we get all the way through the path
262     then a FALSE is returned, the command could not be found.
263 */
264 bool Script::check_existance(const Glib::ustring &command)
267     // Check the simple case first
268     if (command.size() == 0) {
269         return false;
270     }
272     //Don't search when it contains a slash. */
273     if (command.find(G_DIR_SEPARATOR) != command.npos) {
274         if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS)) {
275             return true;
276         } else {
277             return false;
278         }
279     }
282     Glib::ustring path;
283     gchar *s = (gchar *) g_getenv("PATH");
284     if (s) {
285         path = s;
286     } else {
287        /* There is no `PATH' in the environment.
288            The default search path is the current directory */
289         path = G_SEARCHPATH_SEPARATOR_S;
290     }
292     std::string::size_type pos  = 0;
293     std::string::size_type pos2 = 0;
294     while ( pos < path.size() ) {
296         Glib::ustring localPath;
298         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
299         if (pos2 == path.npos) {
300             localPath = path.substr(pos);
301             pos = path.size();
302         } else {
303             localPath = path.substr(pos, pos2-pos);
304             pos = pos2+1;
305         }
307         //printf("### %s\n", localPath.c_str());
308         Glib::ustring candidatePath =
309                       Glib::build_filename(localPath, command);
311         if (Inkscape::IO::file_test(candidatePath .c_str(),
312                       G_FILE_TEST_EXISTS)) {
313             return true;
314         }
316     }
318     return false;
325 /**
326     \return   none
327     \brief    This function 'loads' an extention, basically it determines
328               the full command for the extention and stores that.
329     \param    module  The extention to be loaded.
331     The most difficult part about this function is finding the actual
332     command through all of the Reprs.  Basically it is hidden down a
333     couple of layers, and so the code has to move down too.  When
334     the command is actually found, it has its relative directory
335     solved.
337     At that point all of the loops are exited, and there is an
338     if statement to make sure they didn't exit because of not finding
339     the command.  If that's the case, the extention doesn't get loaded
340     and should error out at a higher level.
341 */
343 bool Script::load(Inkscape::Extension::Extension *module)
345     if (module->loaded()) {
346         return true;
347     }
349     helper_extension = "";
351     /* This should probably check to find the executable... */
352     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
353     while (child_repr != NULL) {
354         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
355             child_repr = sp_repr_children(child_repr);
356             while (child_repr != NULL) {
357                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
358                     const gchar *interpretstr = child_repr->attribute("interpreter");
359                     if (interpretstr != NULL) {
360                         Glib::ustring interpString =
361                             resolveInterpreterExecutable(interpretstr);
362                         //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
363                         command.insert(command.end(), interpretstr);
364                     }
365                     Glib::ustring tmp = "\"";
366                     tmp += solve_reldir(child_repr);
367                     tmp += "\"";
369                     command.insert(command.end(), tmp);
370                 }
371                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
372                     helper_extension = sp_repr_children(child_repr)->content();
373                 }
374                 child_repr = sp_repr_next(child_repr);
375             }
377             break;
378         }
379         child_repr = sp_repr_next(child_repr);
380     }
382     //g_return_val_if_fail(command.length() > 0, false);
384     return true;
388 /**
389     \return   None.
390     \brief    Unload this puppy!
391     \param    module  Extension to be unloaded.
393     This function just sets the module to unloaded.  It free's the
394     command if it has been allocated.
395 */
396 void Script::unload(Inkscape::Extension::Extension */*module*/)
398     command.clear();
399     helper_extension = "";
405 /**
406     \return   Whether the check passed or not
407     \brief    Check every dependency that was given to make sure we should keep this extension
408     \param    module  The Extension in question
410 */
411 bool
412 Script::check(Inkscape::Extension::Extension *module)
414     int script_count = 0;
415     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
416     while (child_repr != NULL) {
417         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
418             script_count++;
419             child_repr = sp_repr_children(child_repr);
420             while (child_repr != NULL) {
421                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
422                     Glib::ustring command_text = solve_reldir(child_repr);
423                     if (command_text.size() > 0) {
424                         /* I've got the command */
425                         bool existance = check_existance(command_text);
426                         if (!existance)
427                             return false;
428                     }
429                 }
431                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
432                     gchar const *helper = sp_repr_children(child_repr)->content();
433                     if (Inkscape::Extension::db.get(helper) == NULL) {
434                         return false;
435                     }
436                 }
438                 child_repr = sp_repr_next(child_repr);
439             }
441             break;
442         }
443         child_repr = sp_repr_next(child_repr);
444     }
446     if (script_count == 0) {
447         return false;
448     }
450     return true;
453 class ScriptDocCache : public ImplementationDocumentCache {
454     friend class Script;
455 protected:
456     std::string _filename;
457     int _tempfd;
458 public:
459     ScriptDocCache (Inkscape::UI::View::View * view);
460     ~ScriptDocCache ( );
461 };
463 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
464     ImplementationDocumentCache(view),
465     _filename(""),
466     _tempfd(0)
468     try {
469         _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
470     } catch (...) {
471         /// \todo Popup dialog here
472         return;
473     }
475     SPDesktop *desktop = (SPDesktop *) view;
476     sp_namedview_document_from_window(desktop);
478     Inkscape::Extension::save(
479               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
480               view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
482     return;
485 ScriptDocCache::~ScriptDocCache ( )
487     close(_tempfd);
488     unlink(_filename.c_str());
491 ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
492     return new ScriptDocCache(view);
496 /**
497     \return   A dialog for preferences
498     \brief    A stub funtion right now
499     \param    module    Module who's preferences need getting
500     \param    filename  Hey, the file you're getting might be important
502     This function should really do something, right now it doesn't.
503 */
504 Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
505                     const gchar */*filename*/)
507     return module->autogui(NULL, NULL);
512 /**
513     \return   A dialog for preferences
514     \brief    A stub funtion right now
515     \param    module    Module whose preferences need getting
517     This function should really do something, right now it doesn't.
518 */
519 Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
521     return module->autogui(NULL, NULL);
524 /**
525     \return  A new document that has been opened
526     \brief   This function uses a filename that is put in, and calls
527              the extension's command to create an SVG file which is
528              returned.
529     \param   module   Extension to use.
530     \param   filename File to open.
532     First things first, this function needs a temporary file name.  To
533     create on of those the function g_file_open_tmp is used with
534     the header of ink_ext_.
536     The extension is then executed using the 'execute' function
537     with the filname coming in, and the temporary filename.  After
538     That executing, the SVG should be in the temporary file.
540     Finally, the temporary file is opened using the SVG input module and
541     a document is returned.  That document has its filename set to
542     the incoming filename (so that it's not the temporary filename).
543     That document is then returned from this function.
544 */
545 SPDocument *Script::open(Inkscape::Extension::Input *module,
546              const gchar *filenameArg)
548     std::list<std::string> params;
549     module->paramListString(params);
551     std::string tempfilename_out;
552     int tempfd_out = 0;
553     try {
554         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
555     } catch (...) {
556         /// \todo Popup dialog here
557         return NULL;
558     }
560     std::string lfilename = Glib::filename_from_utf8(filenameArg);
562     file_listener fileout;
563     int data_read = execute(command, params, lfilename, fileout);
564     fileout.toFile(tempfilename_out);
566     SPDocument * mydoc = NULL;
567     if (data_read > 10) {
568         if (helper_extension.size()==0) {
569             mydoc = Inkscape::Extension::open(
570                   Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
571                   tempfilename_out.c_str());
572         } else {
573             mydoc = Inkscape::Extension::open(
574                   Inkscape::Extension::db.get(helper_extension.c_str()),
575                   tempfilename_out.c_str());
576         }
577     } // data_read
579     if (mydoc != NULL) {
580         g_free(mydoc->base);
581         mydoc->base = NULL;
582         sp_document_change_uri_and_hrefs(mydoc, filenameArg);
583     }
585     // make sure we don't leak file descriptors from g_file_open_tmp
586     close(tempfd_out);
588     unlink(tempfilename_out.c_str());
590     return mydoc;
591 } // open
595 /**
596     \return   none
597     \brief    This function uses an extention to save a document.  It first
598               creates an SVG file of the document, and then runs it through
599               the script.
600     \param    module    Extention to be used
601     \param    doc       Document to be saved
602     \param    filename  The name to save the final file as
603     \return   false in case of any failure writing the file, otherwise true
605     Well, at some point people need to save - it is really what makes
606     the entire application useful.  And, it is possible that someone
607     would want to use an extetion for this, so we need a function to
608     do that eh?
610     First things first, the document is saved to a temporary file that
611     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
612     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
613     end of the function.
615     After we have the SVG file, then extention_execute is called with
616     the temporary file name and the final output filename.  This should
617     put the output of the script into the final output file.  We then
618     delete the temporary file.
619 */
620 void Script::save(Inkscape::Extension::Output *module,
621              SPDocument *doc,
622              const gchar *filenameArg)
624     std::list<std::string> params;
625     module->paramListString(params);
627     std::string tempfilename_in;
628     int tempfd_in = 0;
629     try {
630         tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
631     } catch (...) {
632         /// \todo Popup dialog here
633         throw Inkscape::Extension::Output::save_failed();
634     }
636     if (helper_extension.size() == 0) {
637         Inkscape::Extension::save(
638                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
639                    doc, tempfilename_in.c_str(), false, false, false,
640                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
641     } else {
642         Inkscape::Extension::save(
643                    Inkscape::Extension::db.get(helper_extension.c_str()),
644                    doc, tempfilename_in.c_str(), false, false, false,
645                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
646     }
649     file_listener fileout;
650     execute(command, params, tempfilename_in, fileout);
652     std::string lfilename = Glib::filename_from_utf8(filenameArg);
653     bool success = fileout.toFile(lfilename);
655     // make sure we don't leak file descriptors from g_file_open_tmp
656     close(tempfd_in);
657     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
658     unlink(tempfilename_in.c_str());
660     if (success == false) {
661         throw Inkscape::Extension::Output::save_failed();
662     }
664     return;
669 /**
670     \return    none
671     \brief     This function uses an extention as a effect on a document.
672     \param     module   Extention to effect with.
673     \param     doc      Document to run through the effect.
675     This function is a little bit trickier than the previous two.  It
676     needs two temporary files to get it's work done.  Both of these
677     files have random names created for them using the g_file_open_temp function
678     with the ink_ext_ prefix in the temporary directory.  Like the other
679     functions, the temporary files are deleted at the end.
681     To save/load the two temporary documents (both are SVG) the internal
682     modules for SVG load and save are used.  They are both used through
683     the module system function by passing their keys into the functions.
685     The command itself is built a little bit differently than in other
686     functions because the effect support selections.  So on the command
687     line a list of all the ids that are selected is included.  Currently,
688     this only works for a single selected object, but there will be more.
689     The command string is filled with the data, and then after the execution
690     it is freed.
692     The execute function is used at the core of this function
693     to execute the Script on the two SVG documents (actually only one
694     exists at the time, the other is created by that script).  At that
695     point both should be full, and the second one is loaded.
696 */
697 void Script::effect(Inkscape::Extension::Effect *module,
698                Inkscape::UI::View::View *doc,
699                ImplementationDocumentCache * docCache)
701     if (docCache == NULL) {
702         docCache = newDocCache(module, doc);
703     }
704     ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
705     if (dc == NULL) {
706         printf("TOO BAD TO LIVE!!!");
707         exit(1);
708     }
710     SPDesktop *desktop = (SPDesktop *)doc;
711     sp_namedview_document_from_window(desktop);
713     std::list<std::string> params;
714     module->paramListString(params);
716     if (module->no_doc) {
717         // this is a no-doc extension, e.g. a Help menu command;
718         // just run the command without any files, ignoring errors
720         Glib::ustring empty;
721         file_listener outfile;
722         execute(command, params, empty, outfile);
724         return;
725     }
727     std::string tempfilename_out;
728     int tempfd_out = 0;
729     try {
730         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
731     } catch (...) {
732         /// \todo Popup dialog here
733         return;
734     }
736     if (desktop != NULL) {
737         Inkscape::Util::GSListConstIterator<SPItem *> selected =
738              sp_desktop_selection(desktop)->itemList();
739         while ( selected != NULL ) {
740             Glib::ustring selected_id;
741             selected_id += "--id=";
742             selected_id += (*selected)->getId();
743             params.insert(params.begin(), selected_id);
744             ++selected;
745         }
746     }
748     file_listener fileout;
749     int data_read = execute(command, params, dc->_filename, fileout);
750     fileout.toFile(tempfilename_out);
752     pump_events();
754     SPDocument * mydoc = NULL;
755     if (data_read > 10) {
756         mydoc = Inkscape::Extension::open(
757               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
758               tempfilename_out.c_str());
759     } // data_read
761     pump_events();
763     // make sure we don't leak file descriptors from g_file_open_tmp
764     close(tempfd_out);
766     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
767     unlink(tempfilename_out.c_str());
769     /* Do something with mydoc.... */
770     if (mydoc) {
771         doc->doc()->emitReconstructionStart();
772         copy_doc(doc->doc()->rroot, mydoc->rroot);
773         doc->doc()->emitReconstructionFinish();
774         mydoc->release();
775         sp_namedview_update_layers_from_document(desktop);
776     }
778     return;
783 /**
784     \brief  A function to take all the svg elements from one document
785             and put them in another.
786     \param  oldroot  The root node of the document to be replaced
787     \param  newroot  The root node of the document to replace it with
789     This function first deletes all of the data in the old document.  It
790     does this by creating a list of what needs to be deleted, and then
791     goes through the list.  This two pass approach removes issues with
792     the list being change while parsing through it.  Lots of nasty bugs.
794     Then, it goes through the new document, duplicating all of the
795     elements and putting them into the old document.  The copy
796     is then complete.
797 */
798 void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
800     std::vector<Inkscape::XML::Node *> delete_list;
801     Inkscape::XML::Node * oldroot_namedview = NULL;
803     for (Inkscape::XML::Node * child = oldroot->firstChild();
804             child != NULL;
805             child = child->next()) {
806         if (!strcmp("sodipodi:namedview", child->name())) {
807             oldroot_namedview = child;
808             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
809                     oldroot_namedview_child != NULL;
810                     oldroot_namedview_child = oldroot_namedview_child->next()) {
811                 delete_list.push_back(oldroot_namedview_child);
812             }
813         } else {
814             delete_list.push_back(child);
815         }
816     }
817     for (unsigned int i = 0; i < delete_list.size(); i++) {
818         sp_repr_unparent(delete_list[i]);
819     }
821     for (Inkscape::XML::Node * child = newroot->firstChild();
822             child != NULL;
823             child = child->next()) {
824         if (!strcmp("sodipodi:namedview", child->name())) {
825             if (oldroot_namedview != NULL) {
826                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
827                         newroot_namedview_child != NULL;
828                         newroot_namedview_child = newroot_namedview_child->next()) {
829                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
830                 }
831             }
832         } else {
833             oldroot->appendChild(child->duplicate(oldroot->document()));
834         }
835     }
837     {
838         using Inkscape::Util::List;
839         using Inkscape::XML::AttributeRecord;
840         std::vector<gchar const *> attribs;
842         // Make a list of all attributes of the old root node.
843         for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
844             attribs.push_back(g_quark_to_string(iter->key));
845         }
847         // Delete the attributes of the old root nodes.
848         for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
849             oldroot->setAttribute(*it, NULL);
850         }
852         // Set the new attributes.
853         for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
854             gchar const *name = g_quark_to_string(iter->key);
855             oldroot->setAttribute(name, newroot->attribute(name));
856         }
857     }
859     /** \todo  Restore correct layer */
860     /** \todo  Restore correct selection */
863 /**  \brief  This function checks the stderr file, and if it has data,
864              shows it in a warning dialog to the user
865      \param  filename  Filename of the stderr file
866 */
867 void 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 Script::cancelProcessing (void) {
900     _canceled = true;
901     _main_loop->quit();
902     Glib::spawn_close_pid(_pid);
904     return true;
908 /** \brief    This is the core of the extension file as it actually does
909               the execution of the extension.
910     \param    in_command  The command to be executed
911     \param    filein      Filename coming in
912     \param    fileout     Filename of the out file
913     \return   Number of bytes that were read into the output file.
915     The first thing that this function does is build the command to be
916     executed.  This consists of the first string (in_command) and then
917     the filename for input (filein).  This file is put on the command
918     line.
920     The next thing is that this function does is open a pipe to the
921     command and get the file handle in the ppipe variable.  It then
922     opens the output file with the output file handle.  Both of these
923     operations are checked extensively for errors.
925     After both are opened, then the data is copied from the output
926     of the pipe into the file out using fread and fwrite.  These two
927     functions are used because of their primitive nature they make
928     no assumptions about the data.  A buffer is used in the transfer,
929     but the output of fread is stored so the exact number of bytes
930     is handled gracefully.
932     At the very end (after the data has been copied) both of the files
933     are closed, and we return to what we were doing.
934 */
935 int Script::execute (const std::list<std::string> &in_command,
936                  const std::list<std::string> &in_params,
937                  const Glib::ustring &filein,
938                  file_listener &fileout)
940     g_return_val_if_fail(in_command.size() > 0, 0);
941     // printf("Executing\n");
943     std::vector <std::string> argv;
945 /*
946     for (std::list<std::string>::const_iterator i = in_command.begin();
947             i != in_command.end(); i++) {
948         argv.push_back(*i);
949     }
950 */
951     // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
952     // we tokenize so that spwan does not need to quote over all params
953     for (std::list<std::string>::const_iterator i = in_command.begin();
954             i != in_command.end(); i++) {
955         std::string param_str = *i;
956         do {
957             //g_message("param: %s", param_str.c_str());
958             size_t first_space = param_str.find_first_of(' ');
959             size_t first_quote = param_str.find_first_of('"');
960             //std::cout << "first space " << first_space << std::endl;
961             //std::cout << "first quote " << first_quote << std::endl;
963             if ((first_quote != std::string::npos) && (first_quote == 0)) {
964                 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
965                 //std::cout << "next quote " << next_quote << std::endl;
967                 if (next_quote != std::string::npos) {
968                     //std::cout << "now split " << next_quote << std::endl;
969                     //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
970                     //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
971                     std::string part_str = param_str.substr(1, next_quote - 1);
972                     if (part_str.size() > 0)
973                         argv.push_back(part_str);
974                     param_str = param_str.substr(next_quote + 1);
976                 } else {
977                     if (param_str.size() > 0)
978                         argv.push_back(param_str);
979                     param_str = "";
980                 }
982             } else if (first_space != std::string::npos) {
983                 //std::cout << "now split " << first_space << std::endl;
984                 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
985                 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
986                 std::string part_str = param_str.substr(0, first_space);
987                 if (part_str.size() > 0) {
988                     argv.push_back(part_str);
989                 }
990                 param_str = param_str.substr(first_space + 1);
991             } else {
992                 if (param_str.size() > 0) {
993                     argv.push_back(param_str);
994                 }
995                 param_str = "";
996             }
997         } while (param_str.size() > 0);
998     }
1000     for (std::list<std::string>::const_iterator i = in_params.begin();
1001             i != in_params.end(); i++) {
1002         //g_message("Script parameter: %s",(*i)g.c_str());
1003         argv.push_back(*i);
1004     }
1006     if (!(filein.empty())) {
1007         argv.push_back(filein);
1008     }
1010     int stdout_pipe, stderr_pipe;
1012     try {
1013         Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1014                                      argv,  // arg v
1015                                      Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1016                                      sigc::slot<void>(),
1017                                      &_pid,          // Pid
1018                                      NULL,           // STDIN
1019                                      &stdout_pipe,   // STDOUT
1020                                      &stderr_pipe);  // STDERR
1021     } catch (Glib::SpawnError e) {
1022         printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1023         return 0;
1024     }
1026     _main_loop = Glib::MainLoop::create(false);
1028     file_listener fileerr;
1029     fileout.init(stdout_pipe, _main_loop);
1030     fileerr.init(stderr_pipe, _main_loop);
1032     _canceled = false;
1033     _main_loop->run();
1035     // Ensure all the data is out of the pipe
1036     while (!fileout.isDead()) {
1037         fileout.read(Glib::IO_IN);
1038     }
1039     while (!fileerr.isDead()) {
1040         fileerr.read(Glib::IO_IN);
1041     }
1043     if (_canceled) {
1044         // std::cout << "Script Canceled" << std::endl;
1045         return 0;
1046     }
1048     Glib::ustring stderr_data = fileerr.string();
1049     if (stderr_data.length() != 0 &&
1050         Inkscape::NSApplication::Application::getUseGui()
1051        ) {
1052         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1053                                  _("Inkscape has received additional data from the script executed.  "
1054                                    "The script did not return an error, but this may indicate the results will not be as expected."));
1055     }
1057     Glib::ustring stdout_data = fileout.string();
1058     if (stdout_data.length() == 0) {
1059         return 0;
1060     }
1062     // std::cout << "Finishing Execution." << std::endl;
1063     return stdout_data.length();
1069 }  // namespace Implementation
1070 }  // namespace Extension
1071 }  // namespace Inkscape
1073 /*
1074   Local Variables:
1075   mode:c++
1076   c-file-style:"stroustrup"
1077   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1078   indent-tabs-mode:nil
1079   fill-column:99
1080   End:
1081 */
1082 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :