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