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, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
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
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)
628 {
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;
670 }
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)
706 {
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;
785 }
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)
806 {
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 */
866 }
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)
876 {
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;
903 }
905 bool
906 Script::cancelProcessing (void) {
907 _canceled = true;
908 _main_loop->quit();
909 Glib::spawn_close_pid(_pid);
911 return true;
912 }
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)
947 {
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();
1071 }
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 :