Code

Merging in 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  *
9  * Copyright (C) 2002-2005,2007 Authors
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <unistd.h>
22 #include <errno.h>
23 #include <gtkmm.h>
25 #include "ui/view/view.h"
26 #include "desktop-handles.h"
27 #include "desktop.h"
28 #include "selection.h"
29 #include "sp-namedview.h"
30 #include "io/sys.h"
31 #include "preferences.h"
32 #include "../system.h"
33 #include "extension/effect.h"
34 #include "extension/output.h"
35 #include "extension/input.h"
36 #include "extension/db.h"
37 #include "script.h"
38 #include "dialogs/dialog-events.h"
39 #include "application/application.h"
40 #include "xml/node.h"
41 #include "xml/attribute-record.h"
43 #include "util/glib-list-iterators.h"
47 #ifdef WIN32
48 #include <windows.h>
49 #include <sys/stat.h>
50 #include "registrytool.h"
51 #endif
55 /** This is the command buffer that gets allocated from the stack */
56 #define BUFSIZE (255)
60 /* Namespaces */
61 namespace Inkscape {
62 namespace Extension {
63 namespace Implementation {
65 /** \brief  Make GTK+ events continue to come through a little bit
67         This just keeps coming the events through so that we'll make the GUI
68         update and look pretty.
69 */
70 void
71 Script::pump_events (void) {
72     while( Gtk::Main::events_pending() )
73         Gtk::Main::iteration();
74     return;
75 }
78 /** \brief  A table of what interpreters to call for a given language
80     This table is used to keep track of all the programs to execute a
81     given script.  It also tracks the preference to use to overwrite
82     the given interpreter to a custom one per user.
83 */
84 Script::interpreter_t const Script::interpreterTab[] = {
85         {"perl",   "perl-interpreter",   "perl"   },
86 #ifdef WIN32
87         {"python", "python-interpreter", "pythonw" },
88 #else
89         {"python", "python-interpreter", "python" },
90 #endif
91         {"ruby",   "ruby-interpreter",   "ruby"   },
92         {"shell",  "shell-interpreter",  "sh"     },
93         { NULL,    NULL,                  NULL    }
94 };
98 /** \brief Look up an interpreter name, and translate to something that
99            is executable
100     \param interpNameArg  The name of the interpreter that we're looking
101                               for, should be an entry in interpreterTab
102 */
103 Glib::ustring
104 Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
107     Glib::ustring interpName = interpNameArg;
109     interpreter_t const *interp;
110     bool foundInterp = false;
111     for (interp =  interpreterTab ; interp->identity ; interp++ ){
112         if (interpName == interp->identity) {
113             foundInterp = true;
114             break;
115         }
116     }
118     // Do we have a supported interpreter type?
119     if (!foundInterp)
120         return "";
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         Glib::ustring interpPath = path;
141         interpPath.append("\\");
142         interpPath.append(interpNameArg);
143         interpPath.append("\\");
144         interpPath.append(interpName);
145         interpPath.append(".exe");
146         struct stat finfo;
147         if (stat(interpPath .c_str(), &finfo) ==0) {
148             g_message("Found local interpreter, '%s',  Size: %d",
149                       interpPath .c_str(),
150                       (int)finfo.st_size);
151             return interpPath;
152         }
153     }
155     // 3. Try searching the path
156     char szExePath[MAX_PATH];
157     char szCurrentDir[MAX_PATH];
158     GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
159     unsigned int ret = (unsigned int)FindExecutable(
160                   interpName.c_str(), szCurrentDir, szExePath);
161     if (ret > 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;
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
265 Script::check_existance(const Glib::ustring &command)
268     // Check the simple case first
269     if (command.size() == 0) {
270         return false;
271     }
273     //Don't search when it contains a slash. */
274     if (command.find(G_DIR_SEPARATOR) != command.npos) {
275         if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
276             return true;
277         else
278             return false;
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;
291     std::string::size_type pos  = 0;
292     std::string::size_type pos2 = 0;
293     while ( pos < path.size() ) {
295         Glib::ustring localPath;
297         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
298         if (pos2 == path.npos) {
299             localPath = path.substr(pos);
300             pos = path.size();
301         } else {
302             localPath = path.substr(pos, pos2-pos);
303             pos = pos2+1;
304         }
306         //printf("### %s\n", localPath.c_str());
307         Glib::ustring candidatePath =
308                       Glib::build_filename(localPath, command);
310         if (Inkscape::IO::file_test(candidatePath .c_str(),
311                       G_FILE_TEST_EXISTS)) {
312             return true;
313         }
315     }
317     return false;
324 /**
325     \return   none
326     \brief    This function 'loads' an extention, basically it determines
327               the full command for the extention and stores that.
328     \param    module  The extention to be loaded.
330     The most difficult part about this function is finding the actual
331     command through all of the Reprs.  Basically it is hidden down a
332     couple of layers, and so the code has to move down too.  When
333     the command is actually found, it has its relative directory
334     solved.
336     At that point all of the loops are exited, and there is an
337     if statement to make sure they didn't exit because of not finding
338     the command.  If that's the case, the extention doesn't get loaded
339     and should error out at a higher level.
340 */
342 bool
343 Script::load(Inkscape::Extension::Extension *module)
345     if (module->loaded())
346         return true;
348     helper_extension = "";
350     /* This should probably check to find the executable... */
351     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
352     while (child_repr != NULL) {
353         if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
354             child_repr = sp_repr_children(child_repr);
355             while (child_repr != NULL) {
356                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
357                     const gchar *interpretstr = child_repr->attribute("interpreter");
358                     if (interpretstr != NULL) {
359                         Glib::ustring interpString =
360                             resolveInterpreterExecutable(interpretstr);
361                         //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
362                         command.insert(command.end(), interpretstr);
363                     }
364                     Glib::ustring tmp = "\"";
365                     tmp += solve_reldir(child_repr);
366                     tmp += "\"";
368                     command.insert(command.end(), tmp);
369                 }
370                 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
371                     helper_extension = sp_repr_children(child_repr)->content();
372                 }
373                 child_repr = sp_repr_next(child_repr);
374             }
376             break;
377         }
378         child_repr = sp_repr_next(child_repr);
379     }
381     //g_return_val_if_fail(command.length() > 0, false);
383     return true;
387 /**
388     \return   None.
389     \brief    Unload this puppy!
390     \param    module  Extension to be unloaded.
392     This function just sets the module to unloaded.  It free's the
393     command if it has been allocated.
394 */
395 void
396 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 *
492 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
493     return new ScriptDocCache(view);
497 /**
498     \return   A dialog for preferences
499     \brief    A stub funtion right now
500     \param    module    Module who's preferences need getting
501     \param    filename  Hey, the file you're getting might be important
503     This function should really do something, right now it doesn't.
504 */
505 Gtk::Widget *
506 Script::prefs_input(Inkscape::Extension::Input *module,
507                     const gchar */*filename*/)
509     return module->autogui(NULL, NULL);
514 /**
515     \return   A dialog for preferences
516     \brief    A stub funtion right now
517     \param    module    Module whose preferences need getting
519     This function should really do something, right now it doesn't.
520 */
521 Gtk::Widget *
522 Script::prefs_output(Inkscape::Extension::Output *module)
524     return module->autogui(NULL, NULL);
527 /**
528     \return  A new document that has been opened
529     \brief   This function uses a filename that is put in, and calls
530              the extension's command to create an SVG file which is
531              returned.
532     \param   module   Extension to use.
533     \param   filename File to open.
535     First things first, this function needs a temporary file name.  To
536     create on of those the function g_file_open_tmp is used with
537     the header of ink_ext_.
539     The extension is then executed using the 'execute' function
540     with the filname coming in, and the temporary filename.  After
541     That executing, the SVG should be in the temporary file.
543     Finally, the temporary file is opened using the SVG input module and
544     a document is returned.  That document has its filename set to
545     the incoming filename (so that it's not the temporary filename).
546     That document is then returned from this function.
547 */
548 SPDocument *
549 Script::open(Inkscape::Extension::Input *module,
550              const gchar *filenameArg)
552     std::list<std::string> params;
553     module->paramListString(params);
555     std::string tempfilename_out;
556     int tempfd_out = 0;
557     try {
558         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
559     } catch (...) {
560         /// \todo Popup dialog here
561         return NULL;
562     }
564     std::string lfilename = Glib::filename_from_utf8(filenameArg);
566     file_listener fileout;
567     int data_read = execute(command, params, lfilename, fileout);
568     fileout.toFile(tempfilename_out);
570     SPDocument * mydoc = NULL;
571     if (data_read > 10) {
572         if (helper_extension.size()==0) {
573             mydoc = Inkscape::Extension::open(
574                   Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
575                   tempfilename_out.c_str());
576         } else {
577             mydoc = Inkscape::Extension::open(
578                   Inkscape::Extension::db.get(helper_extension.c_str()),
579                   tempfilename_out.c_str());
580         }
581     } // data_read
583     if (mydoc != NULL) {
584         g_free(mydoc->base);
585         mydoc->base = NULL;
586         sp_document_change_uri_and_hrefs(mydoc, filenameArg);
587     }
589     // make sure we don't leak file descriptors from g_file_open_tmp
590     close(tempfd_out);
592     unlink(tempfilename_out.c_str());
594     return mydoc;
595 } // open
599 /**
600     \return   none
601     \brief    This function uses an extention to save a document.  It first
602               creates an SVG file of the document, and then runs it through
603               the script.
604     \param    module    Extention to be used
605     \param    doc       Document to be saved
606     \param    filename  The name to save the final file as
607     \return   false in case of any failure writing the file, otherwise true
609     Well, at some point people need to save - it is really what makes
610     the entire application useful.  And, it is possible that someone
611     would want to use an extetion for this, so we need a function to
612     do that eh?
614     First things first, the document is saved to a temporary file that
615     is an SVG file.  To get the temporary filename g_file_open_tmp is used with
616     ink_ext_ as a prefix.  Don't worry, this file gets deleted at the
617     end of the function.
619     After we have the SVG file, then extention_execute is called with
620     the temporary file name and the final output filename.  This should
621     put the output of the script into the final output file.  We then
622     delete the temporary file.
623 */
624 void
625 Script::save(Inkscape::Extension::Output *module,
626              SPDocument *doc,
627              const gchar *filenameArg)
629     std::list<std::string> params;
630     module->paramListString(params);
632     std::string tempfilename_in;
633     int tempfd_in = 0;
634     try {
635         tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
636     } catch (...) {
637         /// \todo Popup dialog here
638         throw Inkscape::Extension::Output::save_failed();
639     }
641     if (helper_extension.size() == 0) {
642         Inkscape::Extension::save(
643                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
644                    doc, tempfilename_in.c_str(), false, false, false,
645                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
646     } else {
647         Inkscape::Extension::save(
648                    Inkscape::Extension::db.get(helper_extension.c_str()),
649                    doc, tempfilename_in.c_str(), false, false, false,
650                    Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
651     }
654     file_listener fileout;
655     execute(command, params, tempfilename_in, fileout);
657     std::string lfilename = Glib::filename_from_utf8(filenameArg);
658     bool success = fileout.toFile(lfilename);
660     // make sure we don't leak file descriptors from g_file_open_tmp
661     close(tempfd_in);
662     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
663     unlink(tempfilename_in.c_str());
665     if(success == false) {
666         throw Inkscape::Extension::Output::save_failed();
667     }
669     return;
674 /**
675     \return    none
676     \brief     This function uses an extention as a effect on a document.
677     \param     module   Extention to effect with.
678     \param     doc      Document to run through the effect.
680     This function is a little bit trickier than the previous two.  It
681     needs two temporary files to get it's work done.  Both of these
682     files have random names created for them using the g_file_open_temp function
683     with the ink_ext_ prefix in the temporary directory.  Like the other
684     functions, the temporary files are deleted at the end.
686     To save/load the two temporary documents (both are SVG) the internal
687     modules for SVG load and save are used.  They are both used through
688     the module system function by passing their keys into the functions.
690     The command itself is built a little bit differently than in other
691     functions because the effect support selections.  So on the command
692     line a list of all the ids that are selected is included.  Currently,
693     this only works for a single selected object, but there will be more.
694     The command string is filled with the data, and then after the execution
695     it is freed.
697     The execute function is used at the core of this function
698     to execute the Script on the two SVG documents (actually only one
699     exists at the time, the other is created by that script).  At that
700     point both should be full, and the second one is loaded.
701 */
702 void
703 Script::effect(Inkscape::Extension::Effect *module,
704                Inkscape::UI::View::View *doc,
705                ImplementationDocumentCache * docCache)
707     if (docCache == NULL) {
708         docCache = newDocCache(module, doc);
709     }
710     ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
711     if (dc == NULL) {
712         printf("TOO BAD TO LIVE!!!");
713         exit(1);
714     }
716     SPDesktop *desktop = (SPDesktop *)doc;
717     sp_namedview_document_from_window(desktop);
719     std::list<std::string> params;
720     module->paramListString(params);
722     if (module->no_doc) {
723         // this is a no-doc extension, e.g. a Help menu command;
724         // just run the command without any files, ignoring errors
726         Glib::ustring empty;
727         file_listener outfile;
728         execute(command, params, empty, outfile);
730         return;
731     }
733     std::string tempfilename_out;
734     int tempfd_out = 0;
735     try {
736         tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
737     } catch (...) {
738         /// \todo Popup dialog here
739         return;
740     }
742     if (desktop != NULL) {
743         Inkscape::Util::GSListConstIterator<SPItem *> selected =
744              sp_desktop_selection(desktop)->itemList();
745         while ( selected != NULL ) {
746             Glib::ustring selected_id;
747             selected_id += "--id=";
748             selected_id += SP_OBJECT_ID(*selected);
749             params.insert(params.begin(), selected_id);
750             ++selected;
751         }
752     }
754     file_listener fileout;
755     int data_read = execute(command, params, dc->_filename, fileout);
756     fileout.toFile(tempfilename_out);
758     pump_events();
760     SPDocument * mydoc = NULL;
761     if (data_read > 10) {
762         mydoc = Inkscape::Extension::open(
763               Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
764               tempfilename_out.c_str());
765     } // data_read
767     pump_events();
769     // make sure we don't leak file descriptors from g_file_open_tmp
770     close(tempfd_out);
772     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
773     unlink(tempfilename_out.c_str());
775     /* Do something with mydoc.... */
776     if (mydoc) {
777         doc->doc()->emitReconstructionStart();
778         copy_doc(doc->doc()->rroot, mydoc->rroot);
779         doc->doc()->emitReconstructionFinish();
780         mydoc->release();
781         sp_namedview_update_layers_from_document(desktop);
782     }
784     return;
789 /**
790     \brief  A function to take all the svg elements from one document
791             and put them in another.
792     \param  oldroot  The root node of the document to be replaced
793     \param  newroot  The root node of the document to replace it with
795     This function first deletes all of the data in the old document.  It
796     does this by creating a list of what needs to be deleted, and then
797     goes through the list.  This two pass approach removes issues with
798     the list being change while parsing through it.  Lots of nasty bugs.
800     Then, it goes through the new document, duplicating all of the
801     elements and putting them into the old document.  The copy
802     is then complete.
803 */
804 void
805 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
807     std::vector<Inkscape::XML::Node *> delete_list;
808     Inkscape::XML::Node * oldroot_namedview = NULL;
810     for (Inkscape::XML::Node * child = oldroot->firstChild();
811             child != NULL;
812             child = child->next()) {
813         if (!strcmp("sodipodi:namedview", child->name())) {
814             oldroot_namedview = child;
815             for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
816                     oldroot_namedview_child != NULL;
817                     oldroot_namedview_child = oldroot_namedview_child->next()) {
818                 delete_list.push_back(oldroot_namedview_child);
819             }
820         } else {
821             delete_list.push_back(child);
822         }
823     }
824     for (unsigned int i = 0; i < delete_list.size(); i++)
825         sp_repr_unparent(delete_list[i]);
827     for (Inkscape::XML::Node * child = newroot->firstChild();
828             child != NULL;
829             child = child->next()) {
830         if (!strcmp("sodipodi:namedview", child->name())) {
831             if (oldroot_namedview != NULL) {
832                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
833                         newroot_namedview_child != NULL;
834                         newroot_namedview_child = newroot_namedview_child->next()) {
835                     oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
836                 }
837             }
838         } else {
839             oldroot->appendChild(child->duplicate(oldroot->document()));
840         }
841     }
843     {
844         using Inkscape::Util::List;
845         using Inkscape::XML::AttributeRecord;
846         std::vector<gchar const *> attribs;
848         // Make a list of all attributes of the old root node.
849         for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
850             attribs.push_back(g_quark_to_string(iter->key));
851         }
853         // Delete the attributes of the old root nodes.
854         for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++)
855             oldroot->setAttribute(*it, NULL);
857         // Set the new attributes.
858         for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
859             gchar const *name = g_quark_to_string(iter->key);
860             oldroot->setAttribute(name, newroot->attribute(name));
861         }
862     }
864     /** \todo  Restore correct layer */
865     /** \todo  Restore correct selection */
868 /**  \brief  This function checks the stderr file, and if it has data,
869              shows it in a warning dialog to the user
870      \param  filename  Filename of the stderr file
871 */
872 void
873 Script::checkStderr (const Glib::ustring &data,
874                            Gtk::MessageType type,
875                      const Glib::ustring &message)
877     Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
878     warning.set_resizable(true);
879     GtkWidget *dlg = GTK_WIDGET(warning.gobj());
880     sp_transientize(dlg);
882     Gtk::VBox * vbox = warning.get_vbox();
884     /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
885     Gtk::TextView * textview = new Gtk::TextView();
886     textview->set_editable(false);
887     textview->set_wrap_mode(Gtk::WRAP_WORD);
888     textview->show();
890     textview->get_buffer()->set_text(data.c_str());
892     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
893     scrollwindow->add(*textview);
894     scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
895     scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
896     scrollwindow->show();
898     vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
900     warning.run();
902     return;
905 bool
906 Script::cancelProcessing (void) {
907     _canceled = true;
908     _main_loop->quit();
909     Glib::spawn_close_pid(_pid);
911     return true;
915 /** \brief    This is the core of the extension file as it actually does
916               the execution of the extension.
917     \param    in_command  The command to be executed
918     \param    filein      Filename coming in
919     \param    fileout     Filename of the out file
920     \return   Number of bytes that were read into the output file.
922     The first thing that this function does is build the command to be
923     executed.  This consists of the first string (in_command) and then
924     the filename for input (filein).  This file is put on the command
925     line.
927     The next thing is that this function does is open a pipe to the
928     command and get the file handle in the ppipe variable.  It then
929     opens the output file with the output file handle.  Both of these
930     operations are checked extensively for errors.
932     After both are opened, then the data is copied from the output
933     of the pipe into the file out using fread and fwrite.  These two
934     functions are used because of their primitive nature they make
935     no assumptions about the data.  A buffer is used in the transfer,
936     but the output of fread is stored so the exact number of bytes
937     is handled gracefully.
939     At the very end (after the data has been copied) both of the files
940     are closed, and we return to what we were doing.
941 */
942 int
943 Script::execute (const std::list<std::string> &in_command,
944                  const std::list<std::string> &in_params,
945                  const Glib::ustring &filein,
946                  file_listener &fileout)
948     g_return_val_if_fail(in_command.size() > 0, 0);
949     // printf("Executing\n");
951     std::vector <std::string> argv;
953 /*
954     for (std::list<std::string>::const_iterator i = in_command.begin();
955             i != in_command.end(); i++) {
956         argv.push_back(*i);
957     }
958 */
959     // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
960     // we tokenize so that spwan does not need to quote over all params
961     for (std::list<std::string>::const_iterator i = in_command.begin();
962             i != in_command.end(); i++) {
963         std::string param_str = *i;
964         do {
965             //g_message("param: %s", param_str.c_str());
966             size_t first_space = param_str.find_first_of(' ');
967             size_t first_quote = param_str.find_first_of('"');
968             //std::cout << "first space " << first_space << std::endl;
969             //std::cout << "first quote " << first_quote << std::endl;
971             if((first_quote != std::string::npos) && (first_quote == 0)) {
972                 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
973                 //std::cout << "next quote " << next_quote << std::endl;
975                 if(next_quote != std::string::npos) {
976                     //std::cout << "now split " << next_quote << std::endl;
977                     //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
978                     //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
979                     std::string part_str = param_str.substr(1, next_quote - 1);
980                     if(part_str.size() > 0)
981                         argv.push_back(part_str);
982                     param_str = param_str.substr(next_quote + 1);
984                 }
985                 else {
986                     if(param_str.size() > 0)
987                         argv.push_back(param_str);
988                     param_str = "";
989                 }
991             }
992             else if(first_space != std::string::npos) {
993                 //std::cout << "now split " << first_space << std::endl;
994                 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
995                 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
996                 std::string part_str = param_str.substr(0, first_space);
997                 if(part_str.size() > 0)
998                     argv.push_back(part_str);
999                 param_str = param_str.substr(first_space + 1);
1000             }
1001             else {
1002                 if(param_str.size() > 0)
1003                     argv.push_back(param_str);
1004                 param_str = "";
1005             }
1006         } while(param_str.size() > 0);
1007     }
1009     for (std::list<std::string>::const_iterator i = in_params.begin();
1010             i != in_params.end(); i++) {
1011         //g_message("Script parameter: %s",(*i)g.c_str());
1012         argv.push_back(*i);
1013     }
1015     if (!(filein.empty())) {
1016                 argv.push_back(filein);
1017     }
1019     int stdout_pipe, stderr_pipe;
1021     try {
1022         Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1023                                      argv,  // arg v
1024                                      Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1025                                      sigc::slot<void>(),
1026                                      &_pid,          // Pid
1027                                      NULL,           // STDIN
1028                                      &stdout_pipe,   // STDOUT
1029                                      &stderr_pipe);  // STDERR
1030     } catch (Glib::SpawnError e) {
1031         printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1032         return 0;
1033     }
1035     _main_loop = Glib::MainLoop::create(false);
1037     file_listener fileerr;
1038     fileout.init(stdout_pipe, _main_loop);
1039     fileerr.init(stderr_pipe, _main_loop);
1041     _canceled = false;
1042     _main_loop->run();
1044     // Ensure all the data is out of the pipe
1045     while (!fileout.isDead())
1046         fileout.read(Glib::IO_IN);
1047     while (!fileerr.isDead())
1048         fileerr.read(Glib::IO_IN);
1050     if (_canceled) {
1051         // std::cout << "Script Canceled" << std::endl;
1052         return 0;
1053     }
1055     Glib::ustring stderr_data = fileerr.string();
1056     if (stderr_data.length() != 0 &&
1057         Inkscape::NSApplication::Application::getUseGui()
1058        ) {
1059         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1060                                  _("Inkscape has received additional data from the script executed.  "
1061                                    "The script did not return an error, but this may indicate the results will not be as expected."));
1062     }
1064     Glib::ustring stdout_data = fileout.string();
1065     if (stdout_data.length() == 0) {
1066         return 0;
1067     }
1069     // std::cout << "Finishing Execution." << std::endl;
1070     return stdout_data.length();
1076 }  // namespace Implementation
1077 }  // namespace Extension
1078 }  // namespace Inkscape
1080 /*
1081   Local Variables:
1082   mode:c++
1083   c-file-style:"stroustrup"
1084   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1085   indent-tabs-mode:nil
1086   fill-column:99
1087   End:
1088 */
1089 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :