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)
105 {
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;
170 }
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()
182 {
183 }
185 /**
186 * brief Destructor
187 */
188 Script::~Script()
189 {
190 }
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 "";
244 }
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)
266 {
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;
318 }
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)
344 {
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;
384 }
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*/)
397 {
398 command.clear();
399 helper_extension = "";
400 }
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)
413 {
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;
451 }
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)
467 {
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);
482 return;
483 }
485 ScriptDocCache::~ScriptDocCache ( )
486 {
487 close(_tempfd);
488 unlink(_filename.c_str());
489 }
491 ImplementationDocumentCache *
492 Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
493 return new ScriptDocCache(view);
494 }
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*/)
508 {
509 return module->autogui(NULL, NULL);
510 }
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)
523 {
524 return module->autogui(NULL, NULL);
525 }
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)
551 {
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
608 Well, at some point people need to save - it is really what makes
609 the entire application useful. And, it is possible that someone
610 would want to use an extetion for this, so we need a function to
611 do that eh?
613 First things first, the document is saved to a temporary file that
614 is an SVG file. To get the temporary filename g_file_open_tmp is used with
615 ink_ext_ as a prefix. Don't worry, this file gets deleted at the
616 end of the function.
618 After we have the SVG file, then extention_execute is called with
619 the temporary file name and the final output filename. This should
620 put the output of the script into the final output file. We then
621 delete the temporary file.
622 */
623 void
624 Script::save(Inkscape::Extension::Output *module,
625 SPDocument *doc,
626 const gchar *filenameArg)
627 {
628 std::list<std::string> params;
629 module->paramListString(params);
631 std::string tempfilename_in;
632 int tempfd_in = 0;
633 try {
634 tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
635 } catch (...) {
636 /// \todo Popup dialog here
637 return;
638 }
640 if (helper_extension.size() == 0) {
641 Inkscape::Extension::save(
642 Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
643 doc, tempfilename_in.c_str(), false, false, false);
644 } else {
645 Inkscape::Extension::save(
646 Inkscape::Extension::db.get(helper_extension.c_str()),
647 doc, tempfilename_in.c_str(), false, false, false);
648 }
651 file_listener fileout;
652 execute(command, params, tempfilename_in, fileout);
654 std::string lfilename = Glib::filename_from_utf8(filenameArg);
655 fileout.toFile(lfilename);
657 // make sure we don't leak file descriptors from g_file_open_tmp
658 close(tempfd_in);
659 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
660 unlink(tempfilename_in.c_str());
662 return;
663 }
667 /**
668 \return none
669 \brief This function uses an extention as a effect on a document.
670 \param module Extention to effect with.
671 \param doc Document to run through the effect.
673 This function is a little bit trickier than the previous two. It
674 needs two temporary files to get it's work done. Both of these
675 files have random names created for them using the g_file_open_temp function
676 with the ink_ext_ prefix in the temporary directory. Like the other
677 functions, the temporary files are deleted at the end.
679 To save/load the two temporary documents (both are SVG) the internal
680 modules for SVG load and save are used. They are both used through
681 the module system function by passing their keys into the functions.
683 The command itself is built a little bit differently than in other
684 functions because the effect support selections. So on the command
685 line a list of all the ids that are selected is included. Currently,
686 this only works for a single selected object, but there will be more.
687 The command string is filled with the data, and then after the execution
688 it is freed.
690 The execute function is used at the core of this function
691 to execute the Script on the two SVG documents (actually only one
692 exists at the time, the other is created by that script). At that
693 point both should be full, and the second one is loaded.
694 */
695 void
696 Script::effect(Inkscape::Extension::Effect *module,
697 Inkscape::UI::View::View *doc,
698 ImplementationDocumentCache * docCache)
699 {
700 if (docCache == NULL) {
701 docCache = newDocCache(module, doc);
702 }
703 ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
704 if (dc == NULL) {
705 printf("TOO BAD TO LIVE!!!");
706 exit(1);
707 }
709 SPDesktop *desktop = (SPDesktop *)doc;
710 sp_namedview_document_from_window(desktop);
712 gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
714 std::list<std::string> params;
715 module->paramListString(params);
717 if (module->no_doc) {
718 // this is a no-doc extension, e.g. a Help menu command;
719 // just run the command without any files, ignoring errors
721 Glib::ustring empty;
722 file_listener outfile;
723 execute(command, params, empty, outfile);
725 return;
726 }
728 std::string tempfilename_out;
729 int tempfd_out = 0;
730 try {
731 tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
732 } catch (...) {
733 /// \todo Popup dialog here
734 return;
735 }
737 if (desktop != NULL) {
738 Inkscape::Util::GSListConstIterator<SPItem *> selected =
739 sp_desktop_selection(desktop)->itemList();
740 while ( selected != NULL ) {
741 Glib::ustring selected_id;
742 selected_id += "--id=";
743 selected_id += SP_OBJECT_ID(*selected);
744 params.insert(params.begin(), selected_id);
745 ++selected;
746 }
747 }
749 file_listener fileout;
750 int data_read = execute(command, params, dc->_filename, fileout);
751 fileout.toFile(tempfilename_out);
753 pump_events();
755 SPDocument * mydoc = NULL;
756 if (data_read > 10) {
757 mydoc = Inkscape::Extension::open(
758 Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
759 tempfilename_out.c_str());
760 } // data_read
762 pump_events();
764 // make sure we don't leak file descriptors from g_file_open_tmp
765 close(tempfd_out);
767 // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
768 unlink(tempfilename_out.c_str());
770 /* Do something with mydoc.... */
771 if (mydoc) {
772 doc->doc()->emitReconstructionStart();
773 copy_doc(doc->doc()->rroot, mydoc->rroot);
774 doc->doc()->emitReconstructionFinish();
775 mydoc->release();
776 sp_namedview_update_layers_from_document(desktop);
778 sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
779 }
780 g_free(orig_output_extension);
782 return;
783 }
787 /**
788 \brief A function to take all the svg elements from one document
789 and put them in another.
790 \param oldroot The root node of the document to be replaced
791 \param newroot The root node of the document to replace it with
793 This function first deletes all of the data in the old document. It
794 does this by creating a list of what needs to be deleted, and then
795 goes through the list. This two pass approach removes issues with
796 the list being change while parsing through it. Lots of nasty bugs.
798 Then, it goes through the new document, duplicating all of the
799 elements and putting them into the old document. The copy
800 is then complete.
801 */
802 void
803 Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
804 {
805 std::vector<Inkscape::XML::Node *> delete_list;
806 Inkscape::XML::Node * oldroot_namedview = NULL;
808 for (Inkscape::XML::Node * child = oldroot->firstChild();
809 child != NULL;
810 child = child->next()) {
811 if (!strcmp("sodipodi:namedview", child->name())) {
812 oldroot_namedview = child;
813 for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
814 oldroot_namedview_child != NULL;
815 oldroot_namedview_child = oldroot_namedview_child->next()) {
816 delete_list.push_back(oldroot_namedview_child);
817 }
818 } else {
819 delete_list.push_back(child);
820 }
821 }
822 for (unsigned int i = 0; i < delete_list.size(); i++)
823 sp_repr_unparent(delete_list[i]);
825 for (Inkscape::XML::Node * child = newroot->firstChild();
826 child != NULL;
827 child = child->next()) {
828 if (!strcmp("sodipodi:namedview", child->name())) {
829 if (oldroot_namedview != NULL) {
830 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
831 newroot_namedview_child != NULL;
832 newroot_namedview_child = newroot_namedview_child->next()) {
833 oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
834 }
835 }
836 } else {
837 oldroot->appendChild(child->duplicate(oldroot->document()));
838 }
839 }
841 {
842 using Inkscape::Util::List;
843 using Inkscape::XML::AttributeRecord;
844 std::vector<gchar const *> attribs;
846 // Make a list of all attributes of the old root node.
847 for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
848 attribs.push_back(g_quark_to_string(iter->key));
849 }
851 // Delete the attributes of the old root nodes.
852 for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++)
853 oldroot->setAttribute(*it, NULL);
855 // Set the new attributes.
856 for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
857 gchar const *name = g_quark_to_string(iter->key);
858 oldroot->setAttribute(name, newroot->attribute(name));
859 }
860 }
862 /** \todo Restore correct layer */
863 /** \todo Restore correct selection */
864 }
866 /** \brief This function checks the stderr file, and if it has data,
867 shows it in a warning dialog to the user
868 \param filename Filename of the stderr file
869 */
870 void
871 Script::checkStderr (const Glib::ustring &data,
872 Gtk::MessageType type,
873 const Glib::ustring &message)
874 {
875 Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
876 warning.set_resizable(true);
877 GtkWidget *dlg = GTK_WIDGET(warning.gobj());
878 sp_transientize(dlg);
880 Gtk::VBox * vbox = warning.get_vbox();
882 /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
883 Gtk::TextView * textview = new Gtk::TextView();
884 textview->set_editable(false);
885 textview->set_wrap_mode(Gtk::WRAP_WORD);
886 textview->show();
888 textview->get_buffer()->set_text(data.c_str());
890 Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
891 scrollwindow->add(*textview);
892 scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
893 scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
894 scrollwindow->show();
896 vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
898 warning.run();
900 return;
901 }
903 bool
904 Script::cancelProcessing (void) {
905 _canceled = true;
906 _main_loop->quit();
907 Glib::spawn_close_pid(_pid);
909 return true;
910 }
913 /** \brief This is the core of the extension file as it actually does
914 the execution of the extension.
915 \param in_command The command to be executed
916 \param filein Filename coming in
917 \param fileout Filename of the out file
918 \return Number of bytes that were read into the output file.
920 The first thing that this function does is build the command to be
921 executed. This consists of the first string (in_command) and then
922 the filename for input (filein). This file is put on the command
923 line.
925 The next thing is that this function does is open a pipe to the
926 command and get the file handle in the ppipe variable. It then
927 opens the output file with the output file handle. Both of these
928 operations are checked extensively for errors.
930 After both are opened, then the data is copied from the output
931 of the pipe into the file out using fread and fwrite. These two
932 functions are used because of their primitive nature they make
933 no assumptions about the data. A buffer is used in the transfer,
934 but the output of fread is stored so the exact number of bytes
935 is handled gracefully.
937 At the very end (after the data has been copied) both of the files
938 are closed, and we return to what we were doing.
939 */
940 int
941 Script::execute (const std::list<std::string> &in_command,
942 const std::list<std::string> &in_params,
943 const Glib::ustring &filein,
944 file_listener &fileout)
945 {
946 g_return_val_if_fail(in_command.size() > 0, 0);
947 // printf("Executing\n");
949 std::vector <std::string> argv;
951 /*
952 for (std::list<std::string>::const_iterator i = in_command.begin();
953 i != in_command.end(); i++) {
954 argv.push_back(*i);
955 }
956 */
957 // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
958 // we tokenize so that spwan does not need to quote over all params
959 for (std::list<std::string>::const_iterator i = in_command.begin();
960 i != in_command.end(); i++) {
961 std::string param_str = *i;
962 do {
963 //g_message("param: %s", param_str.c_str());
964 size_t first_space = param_str.find_first_of(' ');
965 size_t first_quote = param_str.find_first_of('"');
966 //std::cout << "first space " << first_space << std::endl;
967 //std::cout << "first quote " << first_quote << std::endl;
969 if((first_quote != std::string::npos) && (first_quote == 0)) {
970 size_t next_quote = param_str.find_first_of('"', first_quote + 1);
971 //std::cout << "next quote " << next_quote << std::endl;
973 if(next_quote != std::string::npos) {
974 //std::cout << "now split " << next_quote << std::endl;
975 //std::cout << "now split " << param_str.substr(1, next_quote - 1) << std::endl;
976 //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
977 std::string part_str = param_str.substr(1, next_quote - 1);
978 if(part_str.size() > 0)
979 argv.push_back(part_str);
980 param_str = param_str.substr(next_quote + 1);
982 }
983 else {
984 if(param_str.size() > 0)
985 argv.push_back(param_str);
986 param_str = "";
987 }
989 }
990 else if(first_space != std::string::npos) {
991 //std::cout << "now split " << first_space << std::endl;
992 //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
993 //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
994 std::string part_str = param_str.substr(0, first_space);
995 if(part_str.size() > 0)
996 argv.push_back(part_str);
997 param_str = param_str.substr(first_space + 1);
998 }
999 else {
1000 if(param_str.size() > 0)
1001 argv.push_back(param_str);
1002 param_str = "";
1003 }
1004 } while(param_str.size() > 0);
1005 }
1007 for (std::list<std::string>::const_iterator i = in_params.begin();
1008 i != in_params.end(); i++) {
1009 //g_message("Script parameter: %s",(*i)g.c_str());
1010 argv.push_back(*i);
1011 }
1013 if (!(filein.empty())) {
1014 argv.push_back(filein);
1015 }
1017 int stdout_pipe, stderr_pipe;
1019 try {
1020 Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
1021 argv, // arg v
1022 Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
1023 sigc::slot<void>(),
1024 &_pid, // Pid
1025 NULL, // STDIN
1026 &stdout_pipe, // STDOUT
1027 &stderr_pipe); // STDERR
1028 } catch (Glib::SpawnError e) {
1029 printf("Can't Spawn!!! spawn returns: %d\n", e.code());
1030 return 0;
1031 }
1033 _main_loop = Glib::MainLoop::create(false);
1035 file_listener fileerr;
1036 fileout.init(stdout_pipe, _main_loop);
1037 fileerr.init(stderr_pipe, _main_loop);
1039 _canceled = false;
1040 _main_loop->run();
1042 // Ensure all the data is out of the pipe
1043 while (!fileout.isDead())
1044 fileout.read(Glib::IO_IN);
1045 while (!fileerr.isDead())
1046 fileerr.read(Glib::IO_IN);
1048 if (_canceled) {
1049 // std::cout << "Script Canceled" << std::endl;
1050 return 0;
1051 }
1053 Glib::ustring stderr_data = fileerr.string();
1054 if (stderr_data.length() != 0 &&
1055 Inkscape::NSApplication::Application::getUseGui()
1056 ) {
1057 checkStderr(stderr_data, Gtk::MESSAGE_INFO,
1058 _("Inkscape has received additional data from the script executed. "
1059 "The script did not return an error, but this may indicate the results will not be as expected."));
1060 }
1062 Glib::ustring stdout_data = fileout.string();
1063 if (stdout_data.length() == 0) {
1064 return 0;
1065 }
1067 // std::cout << "Finishing Execution." << std::endl;
1068 return stdout_data.length();
1069 }
1074 } // namespace Implementation
1075 } // namespace Extension
1076 } // namespace Inkscape
1078 /*
1079 Local Variables:
1080 mode:c++
1081 c-file-style:"stroustrup"
1082 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1083 indent-tabs-mode:nil
1084 fill-column:99
1085 End:
1086 */
1087 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :