Code

Extensions. Fix for Bug #668895 (Extensions with <check> tags fail to load).
[inkscape.git] / src / extension / implementation / script.cpp
index 69f8cec533db017e52bddfbdb011c4888dfdf29b..428ee626fd361f79342dc7624d67f192e62da165 100644 (file)
@@ -5,29 +5,14 @@
  * Authors:
  *   Bryce Harrington <bryce@osdl.org>
  *   Ted Gould <ted@gould.cx>
+ *   Jon A. Cruz <jon@joncruz.org>
+ *   Abhishek Sharma
  *
  * Copyright (C) 2002-2005,2007 Authors
  *
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
-/*
-TODO:
-FIXME:
-  After Inkscape makes a formal requirement for a GTK version above 2.11.4, please
-  replace all the instances of ink_ext_XXXXXX in this file that represent
-  svg files with ink_ext_XXXXXX.svg . Doing so will prevent errors in extensions
-  that call inkscape to manipulate the file.
-
-  "** (inkscape:5848): WARNING **: Format autodetect failed. The file is being opened as SVG."
-
-  references:
-  http://www.gtk.org/api/2.6/glib/glib-File-Utilities.html#g-mkstemp
-  http://ftp.gnome.org/pub/gnome/sources/glib/2.11/glib-2.11.4.changes
-  http://developer.gnome.org/doc/API/2.0/glib/glib-File-Utilities.html#g-mkstemp
-
-  --Aaron Spike
-*/
 #define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__
 
 #ifdef HAVE_CONFIG_H
@@ -37,6 +22,8 @@ FIXME:
 #include <unistd.h>
 
 #include <errno.h>
+#include <glib.h>
+#include <glib/gstdio.h>
 #include <gtkmm.h>
 
 #include "ui/view/view.h"
@@ -45,7 +32,7 @@ FIXME:
 #include "selection.h"
 #include "sp-namedview.h"
 #include "io/sys.h"
-#include "prefs-utils.h"
+#include "preferences.h"
 #include "../system.h"
 #include "extension/effect.h"
 #include "extension/output.h"
@@ -53,6 +40,9 @@ FIXME:
 #include "extension/db.h"
 #include "script.h"
 #include "dialogs/dialog-events.h"
+#include "inkscape.h"
+#include "xml/node.h"
+#include "xml/attribute-record.h"
 
 #include "util/glib-list-iterators.h"
 
@@ -76,19 +66,18 @@ namespace Inkscape {
 namespace Extension {
 namespace Implementation {
 
-void pump_events (void) {
-    while( Gtk::Main::events_pending() )
+/** \brief  Make GTK+ events continue to come through a little bit
+
+    This just keeps coming the events through so that we'll make the GUI
+    update and look pretty.
+*/
+void Script::pump_events (void) {
+    while ( Gtk::Main::events_pending() ) {
         Gtk::Main::iteration();
+    }
     return;
 }
 
-//Interpreter lookup table
-struct interpreter_t {
-        gchar const *identity;
-        gchar const *prefstring;
-        gchar const *defaultval;
-};
-
 
 /** \brief  A table of what interpreters to call for a given language
 
@@ -96,9 +85,9 @@ struct interpreter_t {
     given script.  It also tracks the preference to use to overwrite
     the given interpreter to a custom one per user.
 */
-static interpreter_t const interpreterTab[] = {
+Script::interpreter_t const Script::interpreterTab[] = {
         {"perl",   "perl-interpreter",   "perl"   },
-#ifdef _WIN32
+#ifdef WIN32
         {"python", "python-interpreter", "pythonw" },
 #else
         {"python", "python-interpreter", "python" },
@@ -110,80 +99,47 @@ static interpreter_t const interpreterTab[] = {
 
 
 
-/**
- * Look up an interpreter name, and translate to something that
- * is executable
- */
-static Glib::ustring
-resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
+/** \brief Look up an interpreter name, and translate to something that
+    is executable
+    \param interpNameArg  The name of the interpreter that we're looking
+    for, should be an entry in interpreterTab
+*/
+std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
 {
-
-    Glib::ustring interpName = interpNameArg;
-
-    interpreter_t const *interp;
+    interpreter_t const *interp = 0;
     bool foundInterp = false;
     for (interp =  interpreterTab ; interp->identity ; interp++ ){
-        if (interpName == interp->identity) {
+        if (interpNameArg == interp->identity) {
             foundInterp = true;
             break;
         }
     }
 
     // Do we have a supported interpreter type?
-    if (!foundInterp)
+    if (!foundInterp) {
         return "";
-    interpName = interp->defaultval;
-
-    // 1.  Check preferences
-    gchar const *prefInterp = prefs_get_string_attribute("extensions", interp->prefstring);
-
-    if (prefInterp) {
-        interpName = prefInterp;
-        return interpName;
     }
+    std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval);
 
-#ifdef _WIN32
-
-    // 2.  Windows.  Try looking relative to inkscape.exe
-    RegistryTool rt;
-    Glib::ustring fullPath;
-    Glib::ustring path;
-    Glib::ustring exeName;
-    if (rt.getExeInfo(fullPath, path, exeName)) {
-        Glib::ustring interpPath = path;
-        interpPath.append("\\");
-        interpPath.append(interpNameArg);
-        interpPath.append("\\");
-        interpPath.append(interpName);
-        interpPath.append(".exe");
-        struct stat finfo;
-        if (stat(interpPath .c_str(), &finfo) ==0) {
-            g_message("Found local interpreter, '%s',  Size: %d",
-                      interpPath .c_str(),
-                      (int)finfo.st_size);
-            return interpPath;
-        }
-    }
+    // 1.  Check preferences for an override.
+    // Note: this must be an absolute path.
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring));
 
-    // 3. Try searching the path
-    char szExePath[MAX_PATH];
-    char szCurrentDir[MAX_PATH];
-    GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
-    unsigned int ret = (unsigned int)FindExecutable(
-                  interpName.c_str(), szCurrentDir, szExePath);
-    if (ret > 32) {
-        interpName = szExePath;
-        return interpName;
+    if (!prefInterp.empty()) {
+        interpreter_path = Glib::filename_from_utf8(prefInterp);
     }
 
-#endif // win32
-
-
-    return interpName;
+    // 2. Search the path.
+    // Do this on all systems, for consistency.
+    // PATH is set up to contain the Python and Perl binary directories
+    // on Windows, so no extra code is necessary.
+    if (!Glib::path_is_absolute(interpreter_path)) {
+        interpreter_path = Glib::find_program_in_path(interpreter_path);
+    }
+    return interpreter_path;
 }
 
-
-
 /** \brief     This function creates a script object and sets up the
                variables.
     \return    A script object
@@ -197,7 +153,6 @@ Script::Script() :
 {
 }
 
-
 /**
  *   brief     Destructor
  */
@@ -222,38 +177,33 @@ Script::~Script()
     string.  This means that the caller of this function can always
     free what they are given (and should do it too!).
 */
-Glib::ustring
+std::string
 Script::solve_reldir(Inkscape::XML::Node *reprin) {
 
     gchar const *s = reprin->attribute("reldir");
 
-    if (!s) {
+    // right now the only recognized relative directory is "extensions"
+    if (!s || Glib::ustring(s) != "extensions") {
         Glib::ustring str = sp_repr_children(reprin)->content();
         return str;
     }
 
     Glib::ustring reldir = s;
 
-    if (reldir == "extensions") {
+    for (unsigned int i=0;
+        i < Inkscape::Extension::Extension::search_path.size();
+        i++) {
 
-        for (unsigned int i=0;
-            i < Inkscape::Extension::Extension::search_path.size();
-            i++) {
-
-            gchar * fname = g_build_filename(
-               Inkscape::Extension::Extension::search_path[i],
-               sp_repr_children(reprin)->content(),
-               NULL);
-            Glib::ustring filename = fname;
-            g_free(fname);
-
-            if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) )
-                return filename;
+        gchar * fname = g_build_filename(
+           Inkscape::Extension::Extension::search_path[i],
+           sp_repr_children(reprin)->content(),
+           NULL);
+        Glib::ustring filename = fname;
+        g_free(fname);
 
+        if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) ) {
+            return Glib::filename_from_utf8(filename);
         }
-    } else {
-        Glib::ustring str = sp_repr_children(reprin)->content();
-        return str;
     }
 
     return "";
@@ -277,38 +227,35 @@ Script::solve_reldir(Inkscape::XML::Node *reprin) {
     then a TRUE is returned.  If we get all the way through the path
     then a FALSE is returned, the command could not be found.
 */
-bool
-Script::check_existance(const Glib::ustring &command)
+bool Script::check_existence(const std::string &command)
 {
 
     // Check the simple case first
-    if (command.size() == 0) {
+    if (command.empty()) {
         return false;
     }
 
-    //Don't search when it contains a slash. */
-    if (command.find(G_DIR_SEPARATOR) != command.npos) {
-        if (Inkscape::IO::file_test(command.c_str(), G_FILE_TEST_EXISTS))
+    //Don't search when it is an absolute path. */
+    if (Glib::path_is_absolute(command)) {
+        if (Glib::file_test(command, Glib::FILE_TEST_EXISTS)) {
             return true;
-        else
+        } else {
             return false;
+        }
     }
 
-
-    Glib::ustring path;
-    gchar *s = (gchar *) g_getenv("PATH");
-    if (s)
-        path = s;
-    else
+    std::string path = Glib::getenv("PATH");
+    if (path.empty()) {
        /* There is no `PATH' in the environment.
            The default search path is the current directory */
         path = G_SEARCHPATH_SEPARATOR_S;
+    }
 
     std::string::size_type pos  = 0;
     std::string::size_type pos2 = 0;
     while ( pos < path.size() ) {
 
-        Glib::ustring localPath;
+        std::string localPath;
 
         pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos);
         if (pos2 == path.npos) {
@@ -320,11 +267,11 @@ Script::check_existance(const Glib::ustring &command)
         }
 
         //printf("### %s\n", localPath.c_str());
-        Glib::ustring candidatePath =
+        std::string candidatePath =
                       Glib::build_filename(localPath, command);
 
-        if (Inkscape::IO::file_test(candidatePath .c_str(),
-                      G_FILE_TEST_EXISTS)) {
+        if (Glib::file_test(candidatePath,
+                      Glib::FILE_TEST_EXISTS)) {
             return true;
         }
 
@@ -355,31 +302,29 @@ Script::check_existance(const Glib::ustring &command)
     and should error out at a higher level.
 */
 
-bool
-Script::load(Inkscape::Extension::Extension *module)
+bool Script::load(Inkscape::Extension::Extension *module)
 {
-    if (module->loaded())
-        return TRUE;
+    if (module->loaded()) {
+        return true;
+    }
 
     helper_extension = "";
 
     /* This should probably check to find the executable... */
     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
     while (child_repr != NULL) {
-        if (!strcmp(child_repr->name(), "script")) {
+        if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
             child_repr = sp_repr_children(child_repr);
             while (child_repr != NULL) {
-                if (!strcmp(child_repr->name(), "command")) {
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
                     const gchar *interpretstr = child_repr->attribute("interpreter");
                     if (interpretstr != NULL) {
-                        Glib::ustring interpString =
-                            resolveInterpreterExecutable(interpretstr);
-                        command.insert(command.end(), interpretstr);
+                        std::string interpString = resolveInterpreterExecutable(interpretstr);
+                        command.insert(command.end(), interpString);
                     }
-
                     command.insert(command.end(), solve_reldir(child_repr));
                 }
-                if (!strcmp(child_repr->name(), "helper_extension")) {
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
                     helper_extension = sp_repr_children(child_repr)->content();
                 }
                 child_repr = sp_repr_next(child_repr);
@@ -390,7 +335,7 @@ Script::load(Inkscape::Extension::Extension *module)
         child_repr = sp_repr_next(child_repr);
     }
 
-    //g_return_val_if_fail(command.length() > 0, FALSE);
+    //g_return_val_if_fail(command.length() > 0, false);
 
     return true;
 }
@@ -404,8 +349,7 @@ Script::load(Inkscape::Extension::Extension *module)
     This function just sets the module to unloaded.  It free's the
     command if it has been allocated.
 */
-void
-Script::unload(Inkscape::Extension::Extension */*module*/)
+void Script::unload(Inkscape::Extension::Extension */*module*/)
 {
     command.clear();
     helper_extension = "";
@@ -423,25 +367,27 @@ Script::unload(Inkscape::Extension::Extension */*module*/)
 bool
 Script::check(Inkscape::Extension::Extension *module)
 {
+    int script_count = 0;
     Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr());
     while (child_repr != NULL) {
-        if (!strcmp(child_repr->name(), "script")) {
+        if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
+            script_count++;
             child_repr = sp_repr_children(child_repr);
             while (child_repr != NULL) {
-                if (!strcmp(child_repr->name(), "check")) {
-                    Glib::ustring command_text = solve_reldir(child_repr);
-                    if (command_text.size() > 0) {
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) {
+                    std::string command_text = solve_reldir(child_repr);
+                    if (!command_text.empty()) {
                         /* I've got the command */
-                        bool existance = check_existance(command_text);
+                        bool existance = check_existence(command_text);
                         if (!existance)
-                            return FALSE;
+                            return false;
                     }
                 }
 
-                if (!strcmp(child_repr->name(), "helper_extension")) {
+                if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
                     gchar const *helper = sp_repr_children(child_repr)->content();
                     if (Inkscape::Extension::db.get(helper) == NULL) {
-                        return FALSE;
+                        return false;
                     }
                 }
 
@@ -453,6 +399,10 @@ Script::check(Inkscape::Extension::Extension *module)
         child_repr = sp_repr_next(child_repr);
     }
 
+    if (script_count == 0) {
+        return false;
+    }
+
     return true;
 }
 
@@ -483,7 +433,7 @@ ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
 
     Inkscape::Extension::save(
               Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
-              view->doc(), _filename.c_str(), FALSE, FALSE, FALSE);
+              view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
 
     return;
 }
@@ -494,8 +444,7 @@ ScriptDocCache::~ScriptDocCache ( )
     unlink(_filename.c_str());
 }
 
-ImplementationDocumentCache *
-Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
+ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
     return new ScriptDocCache(view);
 }
 
@@ -508,8 +457,7 @@ Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::Vie
 
     This function should really do something, right now it doesn't.
 */
-Gtk::Widget *
-Script::prefs_input(Inkscape::Extension::Input *module,
+Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
                     const gchar */*filename*/)
 {
     return module->autogui(NULL, NULL);
@@ -524,44 +472,11 @@ Script::prefs_input(Inkscape::Extension::Input *module,
 
     This function should really do something, right now it doesn't.
 */
-Gtk::Widget *
-Script::prefs_output(Inkscape::Extension::Output *module)
+Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
 {
     return module->autogui(NULL, NULL);
 }
 
-
-
-/**
-    \return   A dialog for preferences
-    \brief    A stub funtion right now
-    \param    module    Module who's preferences need getting
-
-    This function should really do something, right now it doesn't.
-*/
-Gtk::Widget *
-Script::prefs_effect( Inkscape::Extension::Effect *module,
-                      Inkscape::UI::View::View *view,
-                      sigc::signal<void> * changeSignal,
-                      ImplementationDocumentCache * /*docCache*/ )
-{
-    SPDocument * current_document = view->doc();
-
-    using Inkscape::Util::GSListConstIterator;
-    GSListConstIterator<SPItem *> selected =
-           sp_desktop_selection((SPDesktop *)view)->itemList();
-    Inkscape::XML::Node * first_select = NULL;
-    if (selected != NULL) {
-        const SPItem * item = *selected;
-        first_select = SP_OBJECT_REPR(item);
-    }
-
-    return module->autogui(current_document, first_select, changeSignal);
-}
-
-
-
-
 /**
     \return  A new document that has been opened
     \brief   This function uses a filename that is put in, and calls
@@ -583,8 +498,7 @@ Script::prefs_effect( Inkscape::Extension::Effect *module,
     the incoming filename (so that it's not the temporary filename).
     That document is then returned from this function.
 */
-SPDocument *
-Script::open(Inkscape::Extension::Input *module,
+SPDocument *Script::open(Inkscape::Extension::Input *module,
              const gchar *filenameArg)
 {
     std::list<std::string> params;
@@ -593,7 +507,7 @@ Script::open(Inkscape::Extension::Input *module,
     std::string tempfilename_out;
     int tempfd_out = 0;
     try {
-        tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
+        tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
     } catch (...) {
         /// \todo Popup dialog here
         return NULL;
@@ -619,7 +533,8 @@ Script::open(Inkscape::Extension::Input *module,
     } // data_read
 
     if (mydoc != NULL) {
-        sp_document_set_uri(mydoc, filenameArg);
+        mydoc->setBase(0);
+        mydoc->changeUriAndHrefs(filenameArg);
     }
 
     // make sure we don't leak file descriptors from g_file_open_tmp
@@ -640,6 +555,7 @@ Script::open(Inkscape::Extension::Input *module,
     \param    module    Extention to be used
     \param    doc       Document to be saved
     \param    filename  The name to save the final file as
+    \return   false in case of any failure writing the file, otherwise true
 
     Well, at some point people need to save - it is really what makes
     the entire application useful.  And, it is possible that someone
@@ -656,8 +572,7 @@ Script::open(Inkscape::Extension::Input *module,
     put the output of the script into the final output file.  We then
     delete the temporary file.
 */
-void
-Script::save(Inkscape::Extension::Output *module,
+void Script::save(Inkscape::Extension::Output *module,
              SPDocument *doc,
              const gchar *filenameArg)
 {
@@ -667,34 +582,44 @@ Script::save(Inkscape::Extension::Output *module,
     std::string tempfilename_in;
     int tempfd_in = 0;
     try {
-        tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX");
+        tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
     } catch (...) {
         /// \todo Popup dialog here
-        return;
+        throw Inkscape::Extension::Output::save_failed();
     }
 
     if (helper_extension.size() == 0) {
         Inkscape::Extension::save(
                    Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
-                   doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
+                   doc, tempfilename_in.c_str(), false, false, false,
+                   Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
     } else {
         Inkscape::Extension::save(
                    Inkscape::Extension::db.get(helper_extension.c_str()),
-                   doc, tempfilename_in.c_str(), FALSE, FALSE, FALSE);
+                   doc, tempfilename_in.c_str(), false, false, false,
+                   Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
     }
 
 
     file_listener fileout;
-    execute(command, params, tempfilename_in, fileout);
+    int data_read = execute(command, params, tempfilename_in, fileout);
+    
+    bool success = false;
 
-    std::string lfilename = Glib::filename_from_utf8(filenameArg);
-    fileout.toFile(lfilename);
+    if (data_read > 0) {
+        std::string lfilename = Glib::filename_from_utf8(filenameArg);
+        success = fileout.toFile(lfilename);
+    }
 
     // make sure we don't leak file descriptors from g_file_open_tmp
     close(tempfd_in);
     // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
     unlink(tempfilename_in.c_str());
 
+    if (success == false) {
+        throw Inkscape::Extension::Output::save_failed();
+    }
+
     return;
 }
 
@@ -728,8 +653,7 @@ Script::save(Inkscape::Extension::Output *module,
     exists at the time, the other is created by that script).  At that
     point both should be full, and the second one is loaded.
 */
-void
-Script::effect(Inkscape::Extension::Effect *module,
+void Script::effect(Inkscape::Extension::Effect *module,
                Inkscape::UI::View::View *doc,
                ImplementationDocumentCache * docCache)
 {
@@ -745,8 +669,6 @@ Script::effect(Inkscape::Extension::Effect *module,
     SPDesktop *desktop = (SPDesktop *)doc;
     sp_namedview_document_from_window(desktop);
 
-    gchar * orig_output_extension = g_strdup(sp_document_repr_root(desktop->doc())->attribute("inkscape:output_extension"));
-
     std::list<std::string> params;
     module->paramListString(params);
 
@@ -776,7 +698,7 @@ Script::effect(Inkscape::Extension::Effect *module,
         while ( selected != NULL ) {
             Glib::ustring selected_id;
             selected_id += "--id=";
-            selected_id += SP_OBJECT_ID(*selected);
+            selected_id += (*selected)->getId();
             params.insert(params.begin(), selected_id);
             ++selected;
         }
@@ -800,8 +722,7 @@ Script::effect(Inkscape::Extension::Effect *module,
     // make sure we don't leak file descriptors from g_file_open_tmp
     close(tempfd_out);
 
-    // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
-    unlink(tempfilename_out.c_str());
+    g_unlink(tempfilename_out.c_str());
 
     /* Do something with mydoc.... */
     if (mydoc) {
@@ -810,10 +731,7 @@ Script::effect(Inkscape::Extension::Effect *module,
         doc->doc()->emitReconstructionFinish();
         mydoc->release();
         sp_namedview_update_layers_from_document(desktop);
-
-        sp_document_repr_root(desktop->doc())->setAttribute("inkscape:output_extension", orig_output_extension);
     }
-    g_free(orig_output_extension);
 
     return;
 }
@@ -835,8 +753,7 @@ Script::effect(Inkscape::Extension::Effect *module,
     elements and putting them into the old document.  The copy
     is then complete.
 */
-void
-Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
+void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
 {
     std::vector<Inkscape::XML::Node *> delete_list;
     Inkscape::XML::Node * oldroot_namedview = NULL;
@@ -855,8 +772,9 @@ Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
             delete_list.push_back(child);
         }
     }
-    for (unsigned int i = 0; i < delete_list.size(); i++)
+    for (unsigned int i = 0; i < delete_list.size(); i++) {
         sp_repr_unparent(delete_list[i]);
+    }
 
     for (Inkscape::XML::Node * child = newroot->firstChild();
             child != NULL;
@@ -866,16 +784,35 @@ Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
                 for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild();
                         newroot_namedview_child != NULL;
                         newroot_namedview_child = newroot_namedview_child->next()) {
-                    oldroot_namedview->appendChild(newroot_namedview_child->duplicate(child->document()));
+                    oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document()));
                 }
             }
         } else {
-            oldroot->appendChild(child->duplicate(newroot->document()));
+            oldroot->appendChild(child->duplicate(oldroot->document()));
         }
     }
 
-    oldroot->setAttribute("width", newroot->attribute("width"));
-    oldroot->setAttribute("height", newroot->attribute("height"));
+    {
+        using Inkscape::Util::List;
+        using Inkscape::XML::AttributeRecord;
+        std::vector<gchar const *> attribs;
+
+        // Make a list of all attributes of the old root node.
+        for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) {
+            attribs.push_back(g_quark_to_string(iter->key));
+        }
+
+        // Delete the attributes of the old root nodes.
+        for (std::vector<gchar const *>::const_iterator it = attribs.begin(); it != attribs.end(); it++) {
+            oldroot->setAttribute(*it, NULL);
+        }
+
+        // Set the new attributes.
+        for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) {
+            gchar const *name = g_quark_to_string(iter->key);
+            oldroot->setAttribute(name, newroot->attribute(name));
+        }
+    }
 
     /** \todo  Restore correct layer */
     /** \todo  Restore correct selection */
@@ -885,8 +822,7 @@ Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
              shows it in a warning dialog to the user
      \param  filename  Filename of the stderr file
 */
-void
-Script::checkStderr (const Glib::ustring &data,
+void Script::checkStderr (const Glib::ustring &data,
                            Gtk::MessageType type,
                      const Glib::ustring &message)
 {
@@ -903,7 +839,13 @@ Script::checkStderr (const Glib::ustring &data,
     textview->set_wrap_mode(Gtk::WRAP_WORD);
     textview->show();
 
-    textview->get_buffer()->set_text(data.c_str());
+    // Remove the last character
+    char *errormsg = (char*) data.c_str();
+    while (*errormsg != '\0') errormsg++;
+    errormsg -= 1;
+    *errormsg = '\0';
+
+    textview->get_buffer()->set_text(_(data.c_str()));
 
     Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
     scrollwindow->add(*textview);
@@ -918,8 +860,7 @@ Script::checkStderr (const Glib::ustring &data,
     return;
 }
 
-bool
-Script::cancelProcessing (void) {
+bool Script::cancelProcessing (void) {
     _canceled = true;
     _main_loop->quit();
     Glib::spawn_close_pid(_pid);
@@ -955,90 +896,66 @@ Script::cancelProcessing (void) {
     At the very end (after the data has been copied) both of the files
     are closed, and we return to what we were doing.
 */
-int
-Script::execute (const std::list<std::string> &in_command,
+int Script::execute (const std::list<std::string> &in_command,
                  const std::list<std::string> &in_params,
                  const Glib::ustring &filein,
                  file_listener &fileout)
 {
-    g_return_val_if_fail(in_command.size() > 0, 0);
+    g_return_val_if_fail(!in_command.empty(), 0);
     // printf("Executing\n");
 
-    std::vector <std::string> argv;
+    std::vector<std::string> argv;
 
-/*
-    for (std::list<std::string>::const_iterator i = in_command.begin();
-            i != in_command.end(); i++) {
-        argv.push_back(*i);
-    }
-*/
-    // according to http://www.gtk.org/api/2.6/glib/glib-Spawning-Processes.html spawn quotes parameter containing spaces
-    // we tokenize so that spwan does not need to quote over all params
-    for (std::list<std::string>::const_iterator i = in_command.begin();
-            i != in_command.end(); i++) {
-        std::string param_str = *i;
-        //std::cout << "params " << param_str << std::endl;
-        do {
-            //std::cout << "param " << param_str << std::endl;
-            size_t first_space = param_str.find_first_of(' ');
-            size_t first_quote = param_str.find_first_of('"');
-            //std::cout << "first space " << first_space << std::endl;
-            //std::cout << "first quote " << first_quote << std::endl;
-
-            if((first_quote != std::string::npos) && (first_quote == 0)) {
-                size_t next_quote = param_str.find_first_of('"', first_quote);
-                //std::cout << "next quote " << next_quote << std::endl;
-
-                if(next_quote != std::string::npos) {
-                    //std::cout << "now split " << next_quote << std::endl;
-                    //std::cout << "now split " << param_str.substr(1, next_quote) << std::endl;
-                    //std::cout << "now split " << param_str.substr(next_quote + 1) << std::endl;
-                    std::string part_str = param_str.substr(1, next_quote);
-                    if(part_str.size() > 0)
-                        argv.push_back(part_str);
-                    param_str = param_str.substr(next_quote + 1);
+    bool interpreted = (in_command.size() == 2);
+    std::string program = in_command.front();
+    std::string script = interpreted ? in_command.back() : "";
+    std::string working_directory = "";
 
-                }
-            }
-            else if(first_space != std::string::npos) {
-                //std::cout << "now split " << first_space << std::endl;
-                //std::cout << "now split " << param_str.substr(0, first_space) << std::endl;
-                //std::cout << "now split " << param_str.substr(first_space + 1) << std::endl;
-                std::string part_str = param_str.substr(0, first_space);
-                if(part_str.size() > 0)
-                    argv.push_back(part_str);
-                param_str = param_str.substr(first_space + 1);
-            }
-            else {
-                if(param_str.size() > 0)
-                    argv.push_back(param_str);
-                param_str = "";
-            }
-        } while(param_str.size() > 0);
+    // Use Glib::find_program_in_path instead of the equivalent
+    // Glib::spawn_* functionality, because _wspawnp is broken on Windows:
+    // it doesn't work when PATH contains Unicode directories
+    if (!Glib::path_is_absolute(program)) {
+        program = Glib::find_program_in_path(program);
     }
+    argv.push_back(program);
+
+    if (interpreted) {
+        // On Windows, Python garbles Unicode command line parameters
+        // in an useless way. This means extensions fail when Inkscape
+        // is run from an Unicode directory.
+        // As a workaround, we set the working directory to the one
+        // containing the script.
+        working_directory = Glib::path_get_dirname(script);
+        script = Glib::path_get_basename(script);
+        #ifdef G_OS_WIN32
+        // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8
+        gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data());
+        working_directory = workdir_s;
+        g_free(workdir_s);
+        #endif
 
-    for (std::list<std::string>::const_iterator i = in_params.begin();
-            i != in_params.end(); i++) {
-        argv.push_back(*i);
+        argv.push_back(script);
     }
 
-    if (!(filein.empty())) {
-               argv.push_back(filein);
+    // assemble the rest of argv
+    std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
+    if (!filein.empty()) {
+        argv.push_back(filein);
     }
 
     int stdout_pipe, stderr_pipe;
 
     try {
-        Inkscape::IO::spawn_async_with_pipes(Glib::get_current_dir(), // working directory
+        Glib::spawn_async_with_pipes(working_directory, // working directory
                                      argv,  // arg v
-                                     Glib::SPAWN_SEARCH_PATH /*| Glib::SPAWN_DO_NOT_REAP_CHILD*/,
+                                     static_cast<Glib::SpawnFlags>(0), // no flags
                                      sigc::slot<void>(),
                                      &_pid,          // Pid
                                      NULL,           // STDIN
                                      &stdout_pipe,   // STDOUT
                                      &stderr_pipe);  // STDERR
-    } catch (Glib::SpawnError e) {
-        printf("Can't Spawn!!! spawn returns: %d\n", e.code());
+    } catch (Glib::Error e) {
+        printf("Can't Spawn!!! spawn returns: %s\n", e.what().data());
         return 0;
     }
 
@@ -1052,10 +969,12 @@ Script::execute (const std::list<std::string> &in_command,
     _main_loop->run();
 
     // Ensure all the data is out of the pipe
-    while (!fileout.isDead())
+    while (!fileout.isDead()) {
         fileout.read(Glib::IO_IN);
-    while (!fileerr.isDead())
+    }
+    while (!fileerr.isDead()) {
         fileerr.read(Glib::IO_IN);
+    }
 
     if (_canceled) {
         // std::cout << "Script Canceled" << std::endl;
@@ -1063,7 +982,9 @@ Script::execute (const std::list<std::string> &in_command,
     }
 
     Glib::ustring stderr_data = fileerr.string();
-    if (stderr_data.length() != 0) {
+    if (stderr_data.length() != 0 &&
+        inkscape_use_gui()
+       ) {
         checkStderr(stderr_data, Gtk::MESSAGE_INFO,
                                  _("Inkscape has received additional data from the script executed.  "
                                    "The script did not return an error, but this may indicate the results will not be as expected."));
@@ -1094,4 +1015,4 @@ Script::execute (const std::list<std::string> &in_command,
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :