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"
42 #include "util/glib-list-iterators.h"
46 #ifdef WIN32
47 #include <windows.h>
48 #include <sys/stat.h>
49 #include "registrytool.h"
50 #endif
54 /** This is the command buffer that gets allocated from the stack */
55 #define BUFSIZE (255)
59 /* Namespaces */
60 namespace Inkscape {
61 namespace Extension {
62 namespace Implementation {
64 /** \brief Make GTK+ events continue to come through a little bit
66 This just keeps coming the events through so that we'll make the GUI
67 update and look pretty.
68 */
69 void
70 Script::pump_events (void) {
71 while( Gtk::Main::events_pending() )
72 Gtk::Main::iteration();
73 return;
74 }
77 /** \brief A table of what interpreters to call for a given language
79 This table is used to keep track of all the programs to execute a
80 given script. It also tracks the preference to use to overwrite
81 the given interpreter to a custom one per user.
82 */
83 Script::interpreter_t const Script::interpreterTab[] = {
84 {"perl", "perl-interpreter", "perl" },
85 #ifdef WIN32
86 {"python", "python-interpreter", "pythonw" },
87 #else
88 {"python", "python-interpreter", "python" },
89 #endif
90 {"ruby", "ruby-interpreter", "ruby" },
91 {"shell", "shell-interpreter", "sh" },
92 { NULL, NULL, NULL }
93 };
97 /** \brief Look up an interpreter name, and translate to something that
98 is executable
99 \param interpNameArg The name of the interpreter that we're looking
100 for, should be an entry in interpreterTab
101 */
102 Glib::ustring
103 Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
104 {
106 Glib::ustring interpName = interpNameArg;
108 interpreter_t const *interp;
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 interpName = interp->defaultval;
122 // 1. Check preferences
123 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
124 Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
126 if (!prefInterp.empty()) {
127 interpName = prefInterp;
128 return interpName;
129 }
131 #ifdef WIN32
133 // 2. Windows. Try looking relative to inkscape.exe
134 RegistryTool rt;
135 Glib::ustring fullPath;
136 Glib::ustring path;
137 Glib::ustring exeName;
138 if (rt.getExeInfo(fullPath, path, exeName)) {
139 Glib::ustring interpPath = path;
140 interpPath.append("\\");
141 interpPath.append(interpNameArg);
142 interpPath.append("\\");
143 interpPath.append(interpName);
144 interpPath.append(".exe");
145 struct stat finfo;
146 if (stat(interpPath .c_str(), &finfo) ==0) {
147 g_message("Found local interpreter, '%s', Size: %d",
148 interpPath .c_str(),
149 (int)finfo.st_size);
150 return interpPath;
151 }
152 }
154 // 3. Try searching the path
155 char szExePath[MAX_PATH];
156 char szCurrentDir[MAX_PATH];
157 GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
158 unsigned int ret = (unsigned int)FindExecutable(
159 interpName.c_str(), szCurrentDir, szExePath);
160 if (ret > 32) {
161 interpName = szExePath;
162 return interpName;
163 }
165 #endif // win32
168 return interpName;
169 }
171 /** \brief This function creates a script object and sets up the
172 variables.
173 \return A script object
175 This function just sets the command to NULL. It should get built
176 officially in the load function. This allows for less allocation
177 of memory in the unloaded state.
178 */
179 Script::Script() :
180 Implementation()
181 {
182 }
184 /**
185 * brief Destructor
186 */
187 Script::~Script()
188 {
189 }
193 /**
194 \return A string with the complete string with the relative directory expanded
195 \brief This function takes in a Repr that contains a reldir entry
196 and returns that data with the relative directory expanded.
197 Mostly it is here so that relative directories all get used
198 the same way.
199 \param reprin The Inkscape::XML::Node with the reldir in it.
201 Basically this function looks at an attribute of the Repr, and makes
202 a decision based on that. Currently, it is only working with the
203 'extensions' relative directory, but there will be more of them.
204 One thing to notice is that this function always returns an allocated
205 string. This means that the caller of this function can always
206 free what they are given (and should do it too!).
207 */
208 Glib::ustring
209 Script::solve_reldir(Inkscape::XML::Node *reprin) {
211 gchar const *s = reprin->attribute("reldir");
213 if (!s) {
214 Glib::ustring str = sp_repr_children(reprin)->content();
215 return str;
216 }
218 Glib::ustring reldir = s;
220 if (reldir == "extensions") {
222 for (unsigned int i=0;
223 i < Inkscape::Extension::Extension::search_path.size();
224 i++) {
226 gchar * fname = g_build_filename(
227 Inkscape::Extension::Extension::search_path[i],
228 sp_repr_children(reprin)->content(),
229 NULL);
230 Glib::ustring filename = fname;
231 g_free(fname);
233 if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
234 return filename;
236 }
237 } else {
238 Glib::ustring str = sp_repr_children(reprin)->content();
239 return str;
240 }
242 return "";
243 }
247 /**
248 \return Whether the command given exists, including in the path
249 \brief This function is used to find out if something exists for
250 the check command. It can look in the path if required.
251 \param command The command or file that should be looked for
253 The first thing that this function does is check to see if the
254 incoming file name has a directory delimiter in it. This would
255 mean that it wants to control the directories, and should be
256 used directly.
258 If not, the path is used. Each entry in the path is stepped through,
259 attached to the string, and then tested. If the file is found
260 then a TRUE is returned. If we get all the way through the path
261 then a FALSE is returned, the command could not be found.
262 */
263 bool
264 Script::check_existance(const Glib::ustring &command)
265 {
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 }
281 Glib::ustring path;
282 gchar *s = (gchar *) g_getenv("PATH");
283 if (s)
284 path = s;
285 else
286 /* There is no `PATH' in the environment.
287 The default search path is the current directory */
288 path = G_SEARCHPATH_SEPARATOR_S;
290 std::string::size_type pos = 0;
291 std::string::size_type pos2 = 0;
292 while ( pos < path.size() ) {
294 Glib::ustring localPath;
296 pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
297 if (pos2 == path.npos) {
298 localPath = path.substr(pos);
299 pos = path.size();
300 } else {
301 localPath = path.substr(pos, pos2-pos);
302 pos = pos2+1;
303 }
305 //printf("### %s\n", localPath.c_str());
306 Glib::ustring candidatePath =
307 Glib::build_filename(localPath, command);
309 if (Inkscape::IO::file_test(candidatePath .c_str(),
310 G_FILE_TEST_EXISTS)) {
311 return true;
312 }
314 }
316 return false;
317 }
323 /**
324 \return none
325 \brief This function 'loads' an extention, basically it determines
326 the full command for the extention and stores that.
327 \param module The extention to be loaded.
329 The most difficult part about this function is finding the actual
330 command through all of the Reprs. Basically it is hidden down a
331 couple of layers, and so the code has to move down too. When
332 the command is actually found, it has its relative directory
333 solved.
335 At that point all of the loops are exited, and there is an
336 if statement to make sure they didn't exit because of not finding
337 the command. If that's the case, the extention doesn't get loaded
338 and should error out at a higher level.
339 */
341 bool
342 Script::load(Inkscape::Extension::Extension *module)
343 {
344 if (module->loaded())
345 return true;
347 helper_extension = "";
349 /* This should probably check to find the executable... */
350 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
351 while (child_repr != NULL) {
352 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
353 child_repr = sp_repr_children(child_repr);
354 while (child_repr != NULL) {
355 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
356 const gchar *interpretstr = child_repr->attribute("interpreter");
357 if (interpretstr != NULL) {
358 Glib::ustring interpString =
359 resolveInterpreterExecutable(interpretstr);
360 //g_message("Found: %s and %s",interpString.c_str(),interpretstr);
361 command.insert(command.end(), interpretstr);
362 }
363 Glib::ustring tmp = "\"";
364 tmp += solve_reldir(child_repr);
365 tmp += "\"";
367 command.insert(command.end(), tmp);
368 }
369 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
370 helper_extension = sp_repr_children(child_repr)->content();
371 }
372 child_repr = sp_repr_next(child_repr);
373 }
375 break;
376 }
377 child_repr = sp_repr_next(child_repr);
378 }
380 //g_return_val_if_fail(command.length() > 0, false);
382 return true;
383 }
386 /**
387 \return None.
388 \brief Unload this puppy!
389 \param module Extension to be unloaded.
391 This function just sets the module to unloaded. It free's the
392 command if it has been allocated.
393 */
394 void
395 Script::unload(Inkscape::Extension::Extension */*module*/)
396 {
397 command.clear();
398 helper_extension = "";
399 }
404 /**
405 \return Whether the check passed or not
406 \brief Check every dependency that was given to make sure we should keep this extension
407 \param module The Extension in question
409 */
410 bool
411 Script::check(Inkscape::Extension::Extension *module)
412 {
413 int script_count = 0;
414 Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
415 while (child_repr != NULL) {
416 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
417 script_count++;
418 child_repr = sp_repr_children(child_repr);
419 while (child_repr != NULL) {
420 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
421 Glib::ustring command_text = solve_reldir(child_repr);
422 if (command_text.size() > 0) {
423 /* I've got the command */
424 bool existance = check_existance(command_text);
425 if (!existance)
426 return false;
427 }
428 }
430 if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
431 gchar const *helper = sp_repr_children(child_repr)->content();
432 if (Inkscape::Extension::db.get(helper) == NULL) {
433 return false;
434 }
435 }
437 child_repr = sp_repr_next(child_repr);
438 }
440 break;
441 }
442 child_repr = sp_repr_next(child_repr);
443 }
445 if (script_count == 0) {
446 return false;
447 }
449 return true;
450 }
452 class ScriptDocCache : public ImplementationDocumentCache {
453 friend class Script;
454 protected:
455 std::string _filename;
456 int _tempfd;
457 public:
458 ScriptDocCache (Inkscape::UI::View::View * view);
459 ~ScriptDocCache ( );
460 };
462 ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
463 ImplementationDocumentCache(view),
464 _filename(""),
465 _tempfd(0)
466 {
467 try {
468 _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
469 } catch (...) {
470 /// \todo Popup dialog here
471 return;
472 }
474 SPDesktop *desktop = (SPDesktop *) view;
475 sp_namedview_document_from_window(desktop);
477 Inkscape::Extension::save(
478 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
479 view->doc(), _filename.c_str(), false, false, false);
481 return;
482 }
484 ScriptDocCache::~ScriptDocCache ( )
485 {
486 close(_tempfd);
487 unlink(_filename.c_str());
488 }
490 ImplementationDocumentCache *
491 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
492 return new ScriptDocCache(view);
493 }
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 *
505 Script::prefs_input(Inkscape::Extension::Input *module,
506 const gchar */*filename*/)
507 {
508 return module->autogui(NULL, NULL);
509 }
513 /**
514 \return A dialog for preferences
515 \brief A stub funtion right now
516 \param module Module whose preferences need getting
518 This function should really do something, right now it doesn't.
519 */
520 Gtk::Widget *
521 Script::prefs_output(Inkscape::Extension::Output *module)
522 {
523 return module->autogui(NULL, NULL);
524 }
526 /**
527 \return A new document that has been opened
528 \brief This function uses a filename that is put in, and calls
529 the extension's command to create an SVG file which is
530 returned.
531 \param module Extension to use.
532 \param filename File to open.
534 First things first, this function needs a temporary file name. To
535 create on of those the function g_file_open_tmp is used with
536 the header of ink_ext_.
538 The extension is then executed using the 'execute' function
539 with the filname coming in, and the temporary filename. After
540 That executing, the SVG should be in the temporary file.
542 Finally, the temporary file is opened using the SVG input module and
543 a document is returned. That document has its filename set to
544 the incoming filename (so that it's not the temporary filename).
545 That document is then returned from this function.
546 */
547 SPDocument *
548 Script::open(Inkscape::Extension::Input *module,
549 const gchar *filenameArg)
550 {
551 std::list<std::string> params;
552 module->paramListString(params);
554 std::string tempfilename_out;
555 int tempfd_out = 0;
556 try {
557 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
558 } catch (...) {
559 /// \todo Popup dialog here
560 return NULL;
561 }
563 std::string lfilename = Glib::filename_from_utf8(filenameArg);
565 file_listener fileout;
566 int data_read = execute(command, params, lfilename, fileout);
567 fileout.toFile(tempfilename_out);
569 SPDocument * mydoc = NULL;
570 if (data_read > 10) {
571 if (helper_extension.size()==0) {
572 mydoc = Inkscape::Extension::open(
573 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
574 tempfilename_out.c_str());
575 } else {
576 mydoc = Inkscape::Extension::open(
577 Inkscape::Extension::db.get(helper_extension.c_str()),
578 tempfilename_out.c_str());
579 }
580 } // data_read
582 if (mydoc != NULL) {
583 sp_document_set_uri(mydoc, filenameArg);
584 }
586 // make sure we don't leak file descriptors from g_file_open_tmp
587 close(tempfd_out);
589 unlink(tempfilename_out.c_str());
591 return mydoc;
592 } // open
596 /**
597 \return none
598 \brief This function uses an extention to save a document. It first
599 creates an SVG file of the document, and then runs it through
600 the script.
601 \param module Extention to be used
602 \param doc Document to be saved
603 \param filename The name to save the final file as
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
621 Script::save(Inkscape::Extension::Output *module,
622 SPDocument *doc,
623 const gchar *filenameArg)
624 {
625 std::list<std::string> params;
626 module->paramListString(params);
628 std::string tempfilename_in;
629 int tempfd_in = 0;
630 try {
631 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
632 } catch (...) {
633 /// \todo Popup dialog here
634 return;
635 }
637 if (helper_extension.size() == 0) {
638 Inkscape::Extension::save(
639 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
640 doc, tempfilename_in.c_str(), false, false, false);
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 }
648 file_listener fileout;
649 execute(command, params, tempfilename_in, fileout);
651 std::string lfilename = Glib::filename_from_utf8(filenameArg);
652 fileout.toFile(lfilename);
654 // make sure we don't leak file descriptors from g_file_open_tmp
655 close(tempfd_in);
656 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
657 unlink(tempfilename_in.c_str());
659 return;
660 }
664 /**
665 \return none
666 \brief This function uses an extention as a effect on a document.
667 \param module Extention to effect with.
668 \param doc Document to run through the effect.
670 This function is a little bit trickier than the previous two. It
671 needs two temporary files to get it's work done. Both of these
672 files have random names created for them using the g_file_open_temp function
673 with the ink_ext_ prefix in the temporary directory. Like the other
674 functions, the temporary files are deleted at the end.
676 To save/load the two temporary documents (both are SVG) the internal
677 modules for SVG load and save are used. They are both used through
678 the module system function by passing their keys into the functions.
680 The command itself is built a little bit differently than in other
681 functions because the effect support selections. So on the command
682 line a list of all the ids that are selected is included. Currently,
683 this only works for a single selected object, but there will be more.
684 The command string is filled with the data, and then after the execution
685 it is freed.
687 The execute function is used at the core of this function
688 to execute the Script on the two SVG documents (actually only one
689 exists at the time, the other is created by that script). At that
690 point both should be full, and the second one is loaded.
691 */
692 void
693 Script::effect(Inkscape::Extension::Effect *module,
694 Inkscape::UI::View::View *doc,
695 ImplementationDocumentCache * docCache)
696 {
697 if (docCache == NULL) {
698 docCache = newDocCache(module, doc);
699 }
700 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
701 if (dc == NULL) {
702 printf("TOO BAD TO LIVE!!!");
703 exit(1);
704 }
706 SPDesktop *desktop = (SPDesktop *)doc;
707 sp_namedview_document_from_window(desktop);
709 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
711 std::list<std::string> params;
712 module->paramListString(params);
714 if (module->no_doc) {
715 // this is a no-doc extension, e.g. a Help menu command;
716 // just run the command without any files, ignoring errors
718 Glib::ustring empty;
719 file_listener outfile;
720 execute(command, params, empty, outfile);
722 return;
723 }
725 std::string tempfilename_out;
726 int tempfd_out = 0;
727 try {
728 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
729 } catch (...) {
730 /// \todo Popup dialog here
731 return;
732 }
734 if (desktop != NULL) {
735 Inkscape::Util::GSListConstIterator<SPItem *> selected =
736 sp_desktop_selection(desktop)->itemList();
737 while ( selected != NULL ) {
738 Glib::ustring selected_id;
739 selected_id += "--id=";
740 selected_id += SP_OBJECT_ID(*selected);
741 params.insert(params.begin(), selected_id);
742 ++selected;
743 }
744 }
746 file_listener fileout;
747 int data_read = execute(command, params, dc->_filename, fileout);
748 fileout.toFile(tempfilename_out);
750 pump_events();
752 SPDocument * mydoc = NULL;
753 if (data_read > 10) {
754 mydoc = Inkscape::Extension::open(
755 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
756 tempfilename_out.c_str());
757 } // data_read
759 pump_events();
761 // make sure we don't leak file descriptors from g_file_open_tmp
762 close(tempfd_out);
764 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
765 unlink(tempfilename_out.c_str());
767 /* Do something with mydoc.... */
768 if (mydoc) {
769 doc->doc()->emitReconstructionStart();
770 copy_doc(doc->doc()->rroot, mydoc->rroot);
771 doc->doc()->emitReconstructionFinish();
772 mydoc->release();
773 sp_namedview_update_layers_from_document(desktop);
775 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
776 }
777 g_free(orig_output_extension);
779 return;
780 }
784 /**
785 \brief A function to take all the svg elements from one document
786 and put them in another.
787 \param oldroot The root node of the document to be replaced
788 \param newroot The root node of the document to replace it with
790 This function first deletes all of the data in the old document. It
791 does this by creating a list of what needs to be deleted, and then
792 goes through the list. This two pass approach removes issues with
793 the list being change while parsing through it. Lots of nasty bugs.
795 Then, it goes through the new document, duplicating all of the
796 elements and putting them into the old document. The copy
797 is then complete.
798 */
799 void
800 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
801 {
802 std::vector<Inkscape::XML::Node *> delete_list;
803 Inkscape::XML::Node * oldroot_namedview = NULL;
805 for (Inkscape::XML::Node * child = oldroot->firstChild();
806 child != NULL;
807 child = child->next()) {
808 if (!strcmp("sodipodi:namedview", child->name())) {
809 oldroot_namedview = child;
810 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
811 oldroot_namedview_child != NULL;
812 oldroot_namedview_child = oldroot_namedview_child->next()) {
813 delete_list.push_back(oldroot_namedview_child);
814 }
815 } else {
816 delete_list.push_back(child);
817 }
818 }
819 for (unsigned int i = 0; i < delete_list.size(); i++)
820 sp_repr_unparent(delete_list[i]);
822 for (Inkscape::XML::Node * child = newroot->firstChild();
823 child != NULL;
824 child = child->next()) {
825 if (!strcmp("sodipodi:namedview", child->name())) {
826 if (oldroot_namedview != NULL) {
827 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
828 newroot_namedview_child != NULL;
829 newroot_namedview_child = newroot_namedview_child->next()) {
830 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
831 }
832 }
833 } else {
834 oldroot->appendChild(child->duplicate(oldroot->document()));
835 }
836 }
838 {
839 using Inkscape::Util::List;
840 using Inkscape::XML::AttributeRecord;
841 std::vector<gchar const *> attribs;
843 // Make a list of all attributes of the old root node.
844 for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
845 attribs.push_back(g_quark_to_string(iter->key));
846 }
848 // Delete the attributes of the old root nodes.
849 for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++)
850 oldroot->setAttribute(*it, NULL);
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 */
861 }
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
868 Script::checkStderr (const Glib::ustring &data,
869 Gtk::MessageType type,
870 const Glib::ustring &message)
871 {
872 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
873 warning.set_resizable(true);
874 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
875 sp_transientize(dlg);
877 Gtk::VBox * vbox = warning.get_vbox();
879 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
880 Gtk::TextView * textview = new Gtk::TextView();
881 textview->set_editable(false);
882 textview->set_wrap_mode(Gtk::WRAP_WORD);
883 textview->show();
885 textview->get_buffer()->set_text(data.c_str());
887 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
888 scrollwindow->add(*textview);
889 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
890 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
891 scrollwindow->show();
893 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
895 warning.run();
897 return;
898 }
900 bool
901 Script::cancelProcessing (void) {
902 _canceled = true;
903 _main_loop->quit();
904 Glib::spawn_close_pid(_pid);
906 return true;
907 }
910 /** \brief This is the core of the extension file as it actually does
911 the execution of the extension.
912 \param in_command The command to be executed
913 \param filein Filename coming in
914 \param fileout Filename of the out file
915 \return Number of bytes that were read into the output file.
917 The first thing that this function does is build the command to be
918 executed. This consists of the first string (in_command) and then
919 the filename for input (filein). This file is put on the command
920 line.
922 The next thing is that this function does is open a pipe to the
923 command and get the file handle in the ppipe variable. It then
924 opens the output file with the output file handle. Both of these
925 operations are checked extensively for errors.
927 After both are opened, then the data is copied from the output
928 of the pipe into the file out using fread and fwrite. These two
929 functions are used because of their primitive nature they make
930 no assumptions about the data. A buffer is used in the transfer,
931 but the output of fread is stored so the exact number of bytes
932 is handled gracefully.
934 At the very end (after the data has been copied) both of the files
935 are closed, and we return to what we were doing.
936 */
937 int
938 Script::execute (const std::list<std::string> &in_command,
939 const std::list<std::string> &in_params,
940 const Glib::ustring &filein,
941 file_listener &fileout)
942 {
943 g_return_val_if_fail(in_command.size() > 0, 0);
944 // printf("Executing\n");
946 std::vector <std::string> argv;
948 /*
949 for (std::list<std::string>::const_iterator i = in_command.begin();
950 i != in_command.end(); i++) {
951 argv.push_back(*i);
952 }
953 */
954 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
955 // we tokenize so that spwan does not need to quote over all params
956 for (std::list<std::string>::const_iterator i = in_command.begin();
957 i != in_command.end(); i++) {
958 std::string param_str = *i;
959 do {
960 //g_message("param: %s", param_str.c_str());
961 size_t first_space = param_str.find_first_of(' ');
962 size_t first_quote = param_str.find_first_of('"');
963 //std::cout << "first space " << first_space << std::endl;
964 //std::cout << "first quote " << first_quote << std::endl;
966 if((first_quote != std::string::npos) && (first_quote == 0)) {
967 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
968 //std::cout << "next quote " << next_quote << std::endl;
970 if(next_quote != std::string::npos) {
971 //std::cout << "now split " << next_quote << std::endl;
972 //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
973 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
974 std::string part_str = param_str.substr(1, next_quote - 1);
975 if(part_str.size() > 0)
976 argv.push_back(part_str);
977 param_str = param_str.substr(next_quote + 1);
979 }
980 else {
981 if(param_str.size() > 0)
982 argv.push_back(param_str);
983 param_str = "";
984 }
986 }
987 else if(first_space != std::string::npos) {
988 //std::cout << "now split " << first_space << std::endl;
989 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
990 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
991 std::string part_str = param_str.substr(0, first_space);
992 if(part_str.size() > 0)
993 argv.push_back(part_str);
994 param_str = param_str.substr(first_space + 1);
995 }
996 else {
997 if(param_str.size() > 0)
998 argv.push_back(param_str);
999 param_str = "";
1000 }
1001 } while(param_str.size() > 0);
1002 }
1004 for (std::list<std::string>::const_iterator i = in_params.begin();
1005 i != in_params.end(); i++) {
1006 //g_message("Script parameter: %s",(*i)g.c_str());
1007 argv.push_back(*i);
1008 }
1010 if (!(filein.empty())) {
1011 argv.push_back(filein);
1012 }
1014 int stdout_pipe, stderr_pipe;
1016 try {
1017 Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1018 argv, // arg v
1019 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1020 sigc::slot<void>(),
1021 &_pid, // Pid
1022 NULL, // STDIN
1023 &stdout_pipe, // STDOUT
1024 &stderr_pipe); // STDERR
1025 } catch (Glib::SpawnError e) {
1026 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1027 return 0;
1028 }
1030 _main_loop = Glib::MainLoop::create(false);
1032 file_listener fileerr;
1033 fileout.init(stdout_pipe, _main_loop);
1034 fileerr.init(stderr_pipe, _main_loop);
1036 _canceled = false;
1037 _main_loop->run();
1039 // Ensure all the data is out of the pipe
1040 while (!fileout.isDead())
1041 fileout.read(Glib::IO_IN);
1042 while (!fileerr.isDead())
1043 fileerr.read(Glib::IO_IN);
1045 if (_canceled) {
1046 // std::cout << "Script Canceled" << std::endl;
1047 return 0;
1048 }
1050 Glib::ustring stderr_data = fileerr.string();
1051 if (stderr_data.length() != 0 &&
1052 Inkscape::NSApplication::Application::getUseGui()
1053 ) {
1054 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1055 _("Inkscape has received additional data from the script executed. "
1056 "The script did not return an error, but this may indicate the results will not be as expected."));
1057 }
1059 Glib::ustring stdout_data = fileout.string();
1060 if (stdout_data.length() == 0) {
1061 return 0;
1062 }
1064 // std::cout << "Finishing Execution." << std::endl;
1065 return stdout_data.length();
1066 }
1071 } // namespace Implementation
1072 } // namespace Extension
1073 } // namespace Inkscape
1075 /*
1076 Local Variables:
1077 mode:c++
1078 c-file-style:"stroustrup"
1079 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1080 indent-tabs-mode:nil
1081 fill-column:99
1082 End:
1083 */
1084 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :