summaryrefslogtreecommitdiffstats
path: root/src/extension/implementation
diff options
context:
space:
mode:
Diffstat (limited to 'src/extension/implementation')
-rw-r--r--src/extension/implementation/implementation.cpp70
-rw-r--r--src/extension/implementation/implementation.h208
-rw-r--r--src/extension/implementation/script.cpp1022
-rw-r--r--src/extension/implementation/script.h138
-rw-r--r--src/extension/implementation/xslt.cpp253
-rw-r--r--src/extension/implementation/xslt.h66
6 files changed, 1757 insertions, 0 deletions
diff --git a/src/extension/implementation/implementation.cpp b/src/extension/implementation/implementation.cpp
new file mode 100644
index 0000000..36cd299
--- /dev/null
+++ b/src/extension/implementation/implementation.cpp
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ Author: Ted Gould <ted@gould.cx>
+ Copyright (c) 2003-2005,2007
+
+ Released under GNU GPL v2+, read the file 'COPYING' for more information.
+
+ This file is the backend to the extensions system. These are
+ the parts of the system that most users will never see, but are
+ important for implementing the extensions themselves. This file
+ contains the base class for all of that.
+*/
+
+#include "implementation.h"
+
+#include <extension/output.h>
+#include <extension/input.h>
+#include <extension/effect.h>
+
+#include "selection.h"
+#include "desktop.h"
+
+
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+Gtk::Widget *
+Implementation::prefs_input(Inkscape::Extension::Input *module, gchar const */*filename*/) {
+ return module->autogui(nullptr, nullptr);
+}
+
+Gtk::Widget *
+Implementation::prefs_output(Inkscape::Extension::Output *module) {
+ return module->autogui(nullptr, nullptr);
+}
+
+Gtk::Widget *Implementation::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, ImplementationDocumentCache * /*docCache*/)
+{
+ if (module->widget_visible_count() == 0) {
+ return nullptr;
+ }
+
+ SPDocument * current_document = view->doc();
+
+ auto selected = ((SPDesktop *) view)->getSelection()->items();
+ Inkscape::XML::Node const* first_select = nullptr;
+ if (!selected.empty()) {
+ const SPItem * item = selected.front();
+ first_select = item->getRepr();
+ }
+
+ // TODO deal with this broken const correctness:
+ return module->autogui(current_document, const_cast<Inkscape::XML::Node *>(first_select), changeSignal);
+} // Implementation::prefs_effect
+
+} /* namespace Implementation */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/implementation/implementation.h b/src/extension/implementation/implementation.h
new file mode 100644
index 0000000..edd9e72
--- /dev/null
+++ b/src/extension/implementation/implementation.h
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ Author: Ted Gould <ted@gould.cx>
+ Copyright (c) 2003-2005,2007
+
+ Released under GNU GPL v2+, read the file 'COPYING' for more information.
+
+ This file is the backend to the extensions system. These are
+ the parts of the system that most users will never see, but are
+ important for implementing the extensions themselves. This file
+ contains the base class for all of that.
+*/
+#ifndef SEEN_INKSCAPE_EXTENSION_IMPLEMENTATION_H
+#define SEEN_INKSCAPE_EXTENSION_IMPLEMENTATION_H
+
+#include <vector>
+#include <sigc++/signal.h>
+#include <glibmm/value.h>
+#include <2geom/forward.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+class SPDocument;
+class SPStyle;
+
+namespace Inkscape {
+
+namespace UI {
+namespace View {
+class View;
+}
+}
+
+namespace XML {
+ class Node;
+}
+
+namespace Extension {
+
+class Effect;
+class Extension;
+class Input;
+class Output;
+class Print;
+
+namespace Implementation {
+
+/**
+ * A cache for the document and this implementation.
+ */
+class ImplementationDocumentCache {
+
+ /**
+ * The document that this instance is working on.
+ */
+ Inkscape::UI::View::View * _view;
+public:
+ explicit ImplementationDocumentCache (Inkscape::UI::View::View * view) { _view = view;};
+
+ virtual ~ImplementationDocumentCache ( ) { return; };
+ Inkscape::UI::View::View const * view ( ) { return _view; };
+};
+
+/**
+ * Base class for all implementations of modules. This is whether they are done systematically by
+ * having something like the scripting system, or they are implemented internally they all derive
+ * from this class.
+ */
+class Implementation {
+public:
+ // ----- Constructor / destructor -----
+ Implementation() = default;
+
+ virtual ~Implementation() = default;
+
+ // ----- Basic functions for all Extension -----
+ virtual bool load(Inkscape::Extension::Extension * /*module*/) { return true; }
+
+ virtual void unload(Inkscape::Extension::Extension * /*module*/) {}
+
+ /**
+ * Create a new document cache object.
+ * This function just returns \c NULL. Subclasses are likely
+ * to reimplement it to do something useful.
+ * @param ext The extension that is referencing us
+ * @param doc The document to create the cache of
+ * @return A new document cache that is valid as long as the document
+ * is not changed.
+ */
+ virtual ImplementationDocumentCache * newDocCache (Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * /*doc*/) { return nullptr; }
+
+ /** Verify any dependencies. */
+ virtual bool check(Inkscape::Extension::Extension * /*module*/) { return true; }
+
+ virtual bool cancelProcessing () { return true; }
+ virtual void commitDocument () {}
+
+ // ----- Input functions -----
+ /** Find out information about the file. */
+ virtual Gtk::Widget *prefs_input(Inkscape::Extension::Input *module,
+ gchar const *filename);
+
+ virtual SPDocument *open(Inkscape::Extension::Input * /*module*/,
+ gchar const * /*filename*/) { return nullptr; }
+
+ // ----- Output functions -----
+ /** Find out information about the file. */
+ virtual Gtk::Widget *prefs_output(Inkscape::Extension::Output *module);
+ virtual void save(Inkscape::Extension::Output * /*module*/, SPDocument * /*doc*/, gchar const * /*filename*/) {}
+ virtual void export_raster(
+ Inkscape::Extension::Output * /*module*/,
+ const SPDocument * /*doc*/,
+ std::string const &/*png_file*/,
+ gchar const * /*filename*/) {}
+
+ // ----- Effect functions -----
+ /** Find out information about the file. */
+ virtual Gtk::Widget * prefs_effect(Inkscape::Extension::Effect *module,
+ Inkscape::UI::View::View *view,
+ sigc::signal<void> *changeSignal,
+ ImplementationDocumentCache *docCache);
+ virtual void effect(Inkscape::Extension::Effect * /*module*/,
+ Inkscape::UI::View::View * /*document*/,
+ ImplementationDocumentCache * /*docCache*/) {}
+
+ // ----- Print functions -----
+ virtual unsigned setup(Inkscape::Extension::Print * /*module*/) { return 0; }
+ virtual unsigned set_preview(Inkscape::Extension::Print * /*module*/) { return 0; }
+
+ virtual unsigned begin(Inkscape::Extension::Print * /*module*/,
+ SPDocument * /*doc*/) { return 0; }
+ virtual unsigned finish(Inkscape::Extension::Print * /*module*/) { return 0; }
+
+ /**
+ * Tell the printing engine whether text should be text or path.
+ * Default value is false because most printing engines will support
+ * paths more than they'll support text. (at least they do today)
+ * \retval true Render the text as a path
+ * \retval false Render text using the text function (above)
+ */
+ virtual bool textToPath(Inkscape::Extension::Print * /*ext*/) { return false; }
+
+ /**
+ * Get "fontEmbedded" param, i.e. tell the printing engine whether fonts should be embedded.
+ * Only available for Adobe Type 1 fonts in EPS output as of now
+ * \retval true Fonts have to be embedded in the output so that the user might not need
+ * to install fonts to have the interpreter read the document correctly
+ * \retval false Do not embed fonts
+ */
+ virtual bool fontEmbedded(Inkscape::Extension::Print * /*ext*/) { return false; }
+
+ // ----- Rendering methods -----
+ virtual unsigned bind(Inkscape::Extension::Print * /*module*/,
+ Geom::Affine const & /*transform*/,
+ float /*opacity*/) { return 0; }
+ virtual unsigned release(Inkscape::Extension::Print * /*module*/) { return 0; }
+ virtual unsigned fill(Inkscape::Extension::Print * /*module*/,
+ Geom::PathVector const & /*pathv*/,
+ Geom::Affine const & /*ctm*/,
+ SPStyle const * /*style*/,
+ Geom::OptRect const & /*pbox*/,
+ Geom::OptRect const & /*dbox*/,
+ Geom::OptRect const & /*bbox*/) { return 0; }
+ virtual unsigned stroke(Inkscape::Extension::Print * /*module*/,
+ Geom::PathVector const & /*pathv*/,
+ Geom::Affine const & /*transform*/,
+ SPStyle const * /*style*/,
+ Geom::OptRect const & /*pbox*/,
+ Geom::OptRect const & /*dbox*/,
+ Geom::OptRect const & /*bbox*/) { return 0; }
+ virtual unsigned image(Inkscape::Extension::Print * /*module*/,
+ unsigned char * /*px*/,
+ unsigned int /*w*/,
+ unsigned int /*h*/,
+ unsigned int /*rs*/,
+ Geom::Affine const & /*transform*/,
+ SPStyle const * /*style*/) { return 0; }
+ virtual unsigned text(Inkscape::Extension::Print * /*module*/,
+ char const * /*text*/,
+ Geom::Point const & /*p*/,
+ SPStyle const * /*style*/) { return 0; }
+ virtual void processPath(Inkscape::XML::Node * /*node*/) {}
+
+ /**
+ * If detach = true, when saving to a file, don't store URIs relative to the filename
+ */
+ virtual void setDetachBase(bool detach) {}
+};
+
+
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // __INKSCAPE_EXTENSION_IMPLEMENTATION_H__
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp
new file mode 100644
index 0000000..0d0a07e
--- /dev/null
+++ b/src/extension/implementation/script.cpp
@@ -0,0 +1,1022 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * Code for handling extensions (i.e. scripts).
+ *
+ * Authors:
+ * Bryce Harrington <bryce@osdl.org>
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2002-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib/gstdio.h>
+#include <glibmm.h>
+#include <glibmm/convert.h>
+#include <glibmm/miscutils.h>
+#include <gtkmm/main.h>
+#include <gtkmm/messagedialog.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/textview.h>
+
+#include "desktop.h"
+#include "inkscape.h"
+#include "inkscape-window.h"
+#include "path-prefix.h"
+#include "preferences.h"
+#include "script.h"
+#include "selection.h"
+
+#include "extension/db.h"
+#include "extension/effect.h"
+#include "extension/execution-env.h"
+#include "extension/init.h"
+#include "extension/input.h"
+#include "extension/output.h"
+#include "extension/system.h"
+#include "io/resource.h"
+#include "layer-manager.h"
+#include "object/sp-namedview.h"
+#include "object/sp-path.h"
+#include "ui/desktop/menubar.h"
+#include "ui/dialog-events.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+#include "ui/tools/node-tool.h"
+#include "ui/view/view.h"
+#include "widgets/desktop-widget.h"
+#include "xml/attribute-record.h"
+#include "xml/node.h"
+
+/* Namespaces */
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+/** \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 () {
+ while ( Gtk::Main::events_pending() ) {
+ Gtk::Main::iteration();
+ }
+ return;
+}
+
+
+/** \brief A table of what interpreters to call for a given language
+
+ This table is used to keep track of all the programs to execute a
+ given script. It also tracks the preference to use to overwrite
+ the given interpreter to a custom one per user.
+*/
+const std::map<std::string, Script::interpreter_t> Script::interpreterTab = {
+ // clang-format off
+#ifdef _WIN32
+ { "perl", {"perl-interpreter", {"wperl" }}},
+ { "python", {"python-interpreter", {"pythonw" }}},
+#elif defined __APPLE__
+ { "perl", {"perl-interpreter", {"perl" }}},
+ { "python", {"python-interpreter", {"python3" }}},
+#else
+ { "perl", {"perl-interpreter", {"perl" }}},
+ { "python", {"python-interpreter", {"python3", "python" }}},
+#endif
+ { "python2", {"python2-interpreter", {"python2", "python" }}},
+ { "ruby", {"ruby-interpreter", {"ruby" }}},
+ { "shell", {"shell-interpreter", {"sh" }}},
+ // clang-format on
+};
+
+
+
+/** \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)
+{
+ // 0. Do we have a supported interpreter type?
+ auto interp = interpreterTab.find(interpNameArg);
+ if (interp == interpreterTab.end()) {
+ g_critical("Script::resolveInterpreterExecutable(): unknown script interpreter '%s'", interpNameArg.c_str());
+ return "";
+ }
+
+ std::list<Glib::ustring> searchList;
+ std::copy(interp->second.defaultvals.begin(), interp->second.defaultvals.end(), std::back_inserter(searchList));
+
+ // 1. Check preferences for an override.
+ auto prefs = Inkscape::Preferences::get();
+ auto prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->second.prefstring));
+
+ if (!prefInterp.empty()) {
+ searchList.push_front(prefInterp);
+ }
+
+ // 2. Search for things in the path if they're there or an absolute
+ for (const auto& binname : searchList) {
+ auto interpreter_path = Glib::filename_from_utf8(binname);
+
+ if (!Glib::path_is_absolute(interpreter_path)) {
+ auto found_path = Glib::find_program_in_path(interpreter_path);
+ if (!found_path.empty()) {
+ return found_path;
+ }
+ } else {
+ return interpreter_path;
+ }
+ }
+
+ // 3. Error
+ g_critical("Script::resolveInterpreterExecutable(): failed to locate script interpreter '%s'", interpNameArg.c_str());
+ return "";
+}
+
+/** \brief This function creates a script object and sets up the
+ variables.
+ \return A script object
+
+ This function just sets the command to NULL. It should get built
+ officially in the load function. This allows for less allocation
+ of memory in the unloaded state.
+*/
+Script::Script()
+ : Implementation()
+ , _canceled(false)
+ , parent_window(nullptr)
+{
+}
+
+/**
+ * \brief Destructor
+ */
+Script::~Script()
+= default;
+
+
+/**
+ \return none
+ \brief This function 'loads' an extension, basically it determines
+ the full command for the extension and stores that.
+ \param module The extension to be loaded.
+
+ The most difficult part about this function is finding the actual
+ command through all of the Reprs. Basically it is hidden down a
+ couple of layers, and so the code has to move down too. When
+ the command is actually found, it has its relative directory
+ solved.
+
+ At that point all of the loops are exited, and there is an
+ if statement to make sure they didn't exit because of not finding
+ the command. If that's the case, the extension doesn't get loaded
+ and should error out at a higher level.
+*/
+
+bool Script::load(Inkscape::Extension::Extension *module)
+{
+ if (module->loaded()) {
+ return true;
+ }
+
+ helper_extension = "";
+
+ /* This should probably check to find the executable... */
+ Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
+ for (child_repr = child_repr->firstChild(); child_repr != nullptr; child_repr = child_repr->next()) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
+ const gchar *interpretstr = child_repr->attribute("interpreter");
+ if (interpretstr != nullptr) {
+ std::string interpString = resolveInterpreterExecutable(interpretstr);
+ if (interpString.empty()) {
+ continue; // can't have a script extension with empty interpreter
+ }
+ command.push_back(interpString);
+ }
+ // TODO: we already parse commands as dependencies in extension.cpp
+ // can can we optimize this to be less indirect?
+ const char *script_name = child_repr->firstChild()->content();
+ std::string script_location = module->get_dependency_location(script_name);
+ command.push_back(std::move(script_location));
+ } else if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
+ helper_extension = child_repr->firstChild()->content();
+ }
+ }
+
+ break;
+ }
+ child_repr = child_repr->next();
+ }
+
+ // TODO: Currently this causes extensions to fail silently, see comment in Extension::set_state()
+ g_return_val_if_fail(command.size() > 0, false);
+
+ return true;
+}
+
+
+/**
+ \return None.
+ \brief Unload this puppy!
+ \param module Extension to be unloaded.
+
+ 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*/)
+{
+ command.clear();
+ helper_extension = "";
+}
+
+
+
+
+/**
+ \return Whether the check passed or not
+ \brief Check every dependency that was given to make sure we should keep this extension
+ \param module The Extension in question
+
+*/
+bool Script::check(Inkscape::Extension::Extension *module)
+{
+ int script_count = 0;
+ Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
+ script_count++;
+
+ // check if all helper_extensions attached to this script were registered
+ child_repr = child_repr->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
+ gchar const *helper = child_repr->firstChild()->content();
+ if (Inkscape::Extension::db.get(helper) == nullptr) {
+ return false;
+ }
+ }
+
+ child_repr = child_repr->next();
+ }
+
+ break;
+ }
+ child_repr = child_repr->next();
+ }
+
+ if (script_count == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+class ScriptDocCache : public ImplementationDocumentCache {
+ friend class Script;
+protected:
+ std::string _filename;
+ int _tempfd;
+public:
+ ScriptDocCache (Inkscape::UI::View::View * view);
+ ~ScriptDocCache ( ) override;
+};
+
+ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
+ ImplementationDocumentCache(view),
+ _filename(""),
+ _tempfd(0)
+{
+ try {
+ _tempfd = Glib::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return;
+ }
+
+ SPDesktop *desktop = (SPDesktop *) view;
+ sp_namedview_document_from_window(desktop);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/options/svgoutput/disable_optimizations", true);
+ Inkscape::Extension::save(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
+ view->doc(), _filename.c_str(), false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ prefs->setBool("/options/svgoutput/disable_optimizations", false);
+ return;
+}
+
+ScriptDocCache::~ScriptDocCache ( )
+{
+ close(_tempfd);
+ unlink(_filename.c_str());
+}
+
+ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
+ return new ScriptDocCache(view);
+}
+
+
+/**
+ \return A dialog for preferences
+ \brief A stub function right now
+ \param module Module who's preferences need getting
+ \param filename Hey, the file you're getting might be important
+
+ This function should really do something, right now it doesn't.
+*/
+Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
+ const gchar */*filename*/)
+{
+ return module->autogui(nullptr, nullptr);
+}
+
+
+
+/**
+ \return A dialog for preferences
+ \brief A stub function right now
+ \param module Module whose preferences need getting
+
+ This function should really do something, right now it doesn't.
+*/
+Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
+{
+ return module->autogui(nullptr, nullptr);
+}
+
+/**
+ \return A new document that has been opened
+ \brief This function uses a filename that is put in, and calls
+ the extension's command to create an SVG file which is
+ returned.
+ \param module Extension to use.
+ \param filename File to open.
+
+ First things first, this function needs a temporary file name. To
+ create one of those the function Glib::file_open_tmp is used with
+ the header of ink_ext_.
+
+ The extension is then executed using the 'execute' function
+ with the filename assigned and then the temporary filename.
+ After execution the SVG should be in the temporary file.
+
+ Finally, the temporary file is opened using the SVG input module and
+ a document is returned. That document has its filename set to
+ 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,
+ const gchar *filenameArg)
+{
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment();
+
+ std::string tempfilename_out;
+ int tempfd_out = 0;
+ try {
+ tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return nullptr;
+ }
+
+ std::string lfilename = Glib::filename_from_utf8(filenameArg);
+
+ file_listener fileout;
+ int data_read = execute(command, params, lfilename, fileout);
+ fileout.toFile(tempfilename_out);
+
+ SPDocument * mydoc = nullptr;
+ if (data_read > 10) {
+ if (helper_extension.size()==0) {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
+ tempfilename_out.c_str());
+ } else {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(helper_extension.c_str()),
+ tempfilename_out.c_str());
+ }
+ } // data_read
+
+ if (mydoc != nullptr) {
+ mydoc->setDocumentBase(nullptr);
+ mydoc->changeFilenameAndHrefs(filenameArg);
+ }
+
+ // make sure we don't leak file descriptors from Glib::file_open_tmp
+ close(tempfd_out);
+
+ unlink(tempfilename_out.c_str());
+
+ return mydoc;
+} // open
+
+
+
+/**
+ \return none
+ \brief This function uses an extension to save a document. It first
+ creates an SVG file of the document, and then runs it through
+ the script.
+ \param module Extension 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
+ would want to use an extension for this, so we need a function to
+ do that, eh?
+
+ First things first, the document is saved to a temporary file that
+ is an SVG file. To get the temporary filename Glib::file_open_tmp is used with
+ ink_ext_ as a prefix. Don't worry, this file gets deleted at the
+ end of the function.
+
+ After we have the SVG file, then Script::execute is called with
+ the temporary file name and the final output filename. This should
+ put the output of the script into the final output file. We then
+ delete the temporary file.
+*/
+void Script::save(Inkscape::Extension::Output *module,
+ SPDocument *doc,
+ const gchar *filenameArg)
+{
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(doc);
+
+ std::string tempfilename_in;
+ int tempfd_in = 0;
+ try {
+ tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ 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,
+ 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,
+ Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ }
+
+
+ file_listener fileout;
+ int data_read = execute(command, params, tempfilename_in, fileout);
+
+ bool success = false;
+
+ 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 Glib::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;
+}
+
+
+void Script::export_raster(Inkscape::Extension::Output *module,
+ const SPDocument *doc,
+ const std::string &png_file,
+ const gchar *filenameArg)
+{
+ if(!module->is_raster()) {
+ g_error("Can not export raster to non-raster extension.");
+ return;
+ }
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(doc);
+
+ file_listener fileout;
+ int data_read = execute(command, params, png_file, fileout);
+
+ bool success = false;
+ if (data_read > 0) {
+ std::string lfilename = Glib::filename_from_utf8(filenameArg);
+ success = fileout.toFile(lfilename);
+ }
+ if (success == false) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+ return;
+}
+
+/**
+ \return none
+ \brief This function uses an extension as an effect on a document.
+ \param module Extension to effect with.
+ \param doc Document to run through the effect.
+
+ This function is a little bit trickier than the previous two. It
+ needs two temporary files to get its work done. Both of these
+ files have random names created for them using the Glib::file_open_temp function
+ with the ink_ext_ prefix in the temporary directory. Like the other
+ functions, the temporary files are deleted at the end.
+
+ To save/load the two temporary documents (both are SVG) the internal
+ modules for SVG load and save are used. They are both used through
+ the module system function by passing their keys into the functions.
+
+ The command itself is built a little bit differently than in other
+ functions because the effect support selections. So on the command
+ line a list of all the ids that are selected is included. Currently,
+ this only works for a single selected object, but there will be more.
+ The command string is filled with the data, and then after the execution
+ it is freed.
+
+ The execute function is used at the core of this function
+ to execute the Script on the two SVG documents (actually only one
+ 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,
+ Inkscape::UI::View::View *doc,
+ ImplementationDocumentCache * docCache)
+{
+ if (docCache == nullptr) {
+ docCache = newDocCache(module, doc);
+ }
+ ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
+ if (dc == nullptr) {
+ printf("TOO BAD TO LIVE!!!");
+ exit(1);
+ }
+ if (doc == nullptr)
+ {
+ g_warning("Script::effect: View not defined");
+ return;
+ }
+
+ SPDesktop *desktop = reinterpret_cast<SPDesktop *>(doc);
+ sp_namedview_document_from_window(desktop);
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(desktop->getDocument());
+
+ parent_window = module->get_execution_env()->get_working_dialog();
+
+ if (module->no_doc) {
+ // this is a no-doc extension, e.g. a Help menu command;
+ // just run the command without any files, ignoring errors
+
+ Glib::ustring empty;
+ file_listener outfile;
+ execute(command, params, empty, outfile);
+
+ // Hack to allow for extension manager to reload extensions
+ // TODO: Find a better way to do this, e.g. implement an action and have extensions (or users)
+ // call that instead when there's a change that requires extensions to reload
+ if (!g_strcmp0(module->get_id(), "org.inkscape.extension.manager")) {
+ Inkscape::Extension::refresh_user_extensions();
+ build_menu(); // Rebuild main menubar.
+ }
+
+ return;
+ }
+
+ std::string tempfilename_out;
+ int tempfd_out = 0;
+ try {
+ tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return;
+ }
+
+ if (desktop) {
+ Inkscape::Selection * selection = desktop->getSelection();
+ if (selection) {
+ params = selection->params;
+ module->paramListString(params);
+ selection->clear();
+ }
+ }
+
+ file_listener fileout;
+ int data_read = execute(command, params, dc->_filename, fileout);
+ fileout.toFile(tempfilename_out);
+
+ pump_events();
+
+ SPDocument * mydoc = nullptr;
+ if (data_read > 10) {
+ try {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
+ tempfilename_out.c_str());
+ } catch (const Inkscape::Extension::Input::open_failed &e) {
+ g_warning("Extension returned output that could not be parsed: %s", e.what());
+ Gtk::MessageDialog warning(
+ _("The output from the extension could not be parsed."),
+ false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
+ warning.set_transient_for( parent_window ? *parent_window : *(INKSCAPE.active_desktop()->getToplevel()) );
+ warning.run();
+ }
+ } // data_read
+
+ pump_events();
+
+ // make sure we don't leak file descriptors from Glib::file_open_tmp
+ close(tempfd_out);
+
+ g_unlink(tempfilename_out.c_str());
+
+ if (mydoc) {
+ SPDocument* vd=doc->doc();
+ if (vd != nullptr)
+ {
+ mydoc->changeFilenameAndHrefs(vd->getDocumentFilename());
+
+ vd->emitReconstructionStart();
+ copy_doc(vd->getReprRoot(), mydoc->getReprRoot());
+ vd->emitReconstructionFinish();
+
+ // Getting the named view from the document generated by the extension
+ SPNamedView *nv = mydoc->getNamedView();
+
+ //Check if it has a default layer set up
+ SPObject *layer = nullptr;
+ if ( nv != nullptr)
+ {
+ SPDocument *document = desktop->doc();
+ if (document != nullptr) {
+ //If so, get that layer
+ if( nv->default_layer_id != 0 ) {
+ layer = document->getObjectById(g_quark_to_string(nv->default_layer_id));
+ }
+ desktop->showGrids(nv->grids_visible);
+ }
+ }
+
+ sp_namedview_update_layers_from_document(desktop);
+ //If that layer exists,
+ if (layer) {
+ //set the current layer
+ desktop->layerManager().setCurrentLayer(layer);
+ }
+ }
+ mydoc->release();
+ }
+
+ return;
+}
+
+
+
+/**
+ \brief A function to replace all the elements in an old document
+ by those from a new document.
+ document and repinserts them into an emptied old document.
+ \param oldroot The root node of the old (destination) document.
+ \param newroot The root node of the new (source) document.
+
+ This function first deletes all the root attributes in the old document followed
+ by copying all the root attributes from the new document to the old document.
+
+ It then deletes all the elements in the old document by
+ making two passes, the first to create a list of the old elements and
+ the second to actually delete them. This two pass approach removes issues
+ with the list being changed while parsing through it... lots of nasty bugs.
+
+ Then, it copies all the element in the new document into the old document.
+
+ Finally, it copies the attributes in namedview.
+*/
+void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
+{
+ if ((oldroot == nullptr) ||(newroot == nullptr))
+ {
+ g_warning("Error on copy_doc: NULL pointer input.");
+ return;
+ }
+
+ // For copying attributes in root and in namedview
+ std::vector<gchar const *> attribs;
+
+ // Must explicitly copy root attributes. This must be done first since
+ // copying grid lines calls "SPGuide::set()" which needs to know the
+ // width, height, and viewBox of the root element.
+
+ // Make a list of all attributes of the old root node.
+ for (const auto & iter : oldroot->attributeList()) {
+ attribs.push_back(g_quark_to_string(iter.key));
+ }
+
+ // Delete the attributes of the old root node.
+ for (auto attrib : attribs) {
+ oldroot->removeAttribute(attrib);
+ }
+
+ // Set the new attributes.
+ for (const auto & iter : newroot->attributeList()) {
+ gchar const *name = g_quark_to_string(iter.key);
+ oldroot->setAttribute(name, newroot->attribute(name));
+ }
+
+ // Question: Why is the "sodipodi:namedview" special? Treating it as a normal
+ // element results in crashes.
+ // Seems to be a bug:
+ // http://inkscape.13.x6.nabble.com/Effect-that-modifies-the-document-properties-tt2822126.html
+
+ std::vector<Inkscape::XML::Node *> delete_list;
+
+ // Make list
+ for (Inkscape::XML::Node * child = oldroot->firstChild();
+ child != nullptr;
+ child = child->next()) {
+ if (!strcmp("sodipodi:namedview", child->name())) {
+ for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
+ oldroot_namedview_child != nullptr;
+ oldroot_namedview_child = oldroot_namedview_child->next()) {
+ delete_list.push_back(oldroot_namedview_child);
+ }
+ break;
+ }
+ }
+
+ // Unparent (delete)
+ for (auto & i : delete_list) {
+ sp_repr_unparent(i);
+ }
+ attribs.clear();
+ oldroot->mergeFrom(newroot, "id", true, true);
+}
+
+/** \brief This function checks the stderr file, and if it has data,
+ shows it in a warning dialog to the user
+ \param filename Filename of the stderr file
+*/
+void Script::checkStderr (const Glib::ustring &data,
+ Gtk::MessageType type,
+ const Glib::ustring &message)
+{
+ Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
+ warning.set_resizable(true);
+ GtkWidget *dlg = GTK_WIDGET(warning.gobj());
+ if (parent_window) {
+ warning.set_transient_for(*parent_window);
+ } else {
+ sp_transientize(dlg);
+ }
+
+ auto vbox = warning.get_content_area();
+
+ /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
+ Gtk::TextView * textview = new Gtk::TextView();
+ textview->set_editable(false);
+ textview->set_wrap_mode(Gtk::WRAP_WORD);
+ textview->show();
+
+ textview->get_buffer()->set_text(data.c_str());
+
+ Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
+ scrollwindow->add(*textview);
+ scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
+ scrollwindow->show();
+ scrollwindow->set_size_request(0, 60);
+
+ vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
+
+ warning.run();
+
+ delete textview;
+ delete scrollwindow;
+
+ return;
+}
+
+bool Script::cancelProcessing () {
+ _canceled = true;
+ if (_main_loop) {
+ _main_loop->quit();
+ }
+ Glib::spawn_close_pid(_pid);
+
+ return true;
+}
+
+
+/** \brief This is the core of the extension file as it actually does
+ the execution of the extension.
+ \param in_command The command to be executed
+ \param filein Filename coming in
+ \param fileout Filename of the out file
+ \return Number of bytes that were read into the output file.
+
+ The first thing that this function does is build the command to be
+ executed. This consists of the first string (in_command) and then
+ the filename for input (filein). This file is put on the command
+ line.
+
+ The next thing that this function does is open a pipe to the
+ command and get the file handle in the ppipe variable. It then
+ opens the output file with the output file handle. Both of these
+ operations are checked extensively for errors.
+
+ After both are opened, then the data is copied from the output
+ of the pipe into the file out using \a fread and \a fwrite. These two
+ functions are used because of their primitive nature - they make
+ no assumptions about the data. A buffer is used in the transfer,
+ but the output of \a fread is stored so the exact number of bytes
+ is handled gracefully.
+
+ 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,
+ const std::list<std::string> &in_params,
+ const Glib::ustring &filein,
+ file_listener &fileout)
+{
+ g_return_val_if_fail(!in_command.empty(), 0);
+
+ std::vector<std::string> argv;
+
+ bool interpreted = (in_command.size() == 2);
+ std::string program = in_command.front();
+ std::string script = interpreted ? in_command.back() : "";
+ std::string working_directory = "";
+
+ // We should always have an absolute path here:
+ // - For interpreted scripts, see Script::resolveInterpreterExecutable()
+ // - For "normal" scripts this should be done as part of the dependency checking, see Dependency::check()
+ if (!Glib::path_is_absolute(program)) {
+ g_critical("Script::execute(): Got unexpected relative path '%s'. Please report a bug.", program.c_str());
+ return 0;
+ }
+ 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);
+ argv.push_back(script);
+ }
+
+ // assemble the rest of argv
+ std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
+ if (!filein.empty()) {
+ auto filein_native = Glib::filename_from_utf8(filein);
+ if (!Glib::path_is_absolute(filein_native))
+ filein_native = Glib::build_filename(Glib::get_current_dir(), filein_native);
+ argv.push_back(filein_native);
+ }
+
+ //for(int i=0;i<argv.size(); ++i){printf("%s ",argv[i].c_str());}printf("\n");
+
+ int stdout_pipe, stderr_pipe;
+
+ try {
+ Glib::spawn_async_with_pipes(working_directory, // working directory
+ argv, // arg v
+ static_cast<Glib::SpawnFlags>(0), // no flags
+ sigc::slot<void>(),
+ &_pid, // Pid
+ nullptr, // STDIN
+ &stdout_pipe, // STDOUT
+ &stderr_pipe); // STDERR
+ } catch (Glib::Error &e) {
+ g_critical("Script::execute(): failed to execute program '%s'.\n\tReason: %s", program.c_str(), e.what().data());
+ return 0;
+ }
+
+ // Create a new MainContext for the loop so that the original context sources are not run here,
+ // this enforces that only the file_listeners should be read in this new MainLoop
+ Glib::RefPtr<Glib::MainContext> _main_context = Glib::MainContext::create();
+ _main_loop = Glib::MainLoop::create(_main_context, false);
+
+ file_listener fileerr;
+ fileout.init(stdout_pipe, _main_loop);
+ fileerr.init(stderr_pipe, _main_loop);
+
+ _canceled = false;
+ _main_loop->run();
+
+ // Ensure all the data is out of the pipe
+ while (!fileout.isDead()) {
+ fileout.read(Glib::IO_IN);
+ }
+ while (!fileerr.isDead()) {
+ fileerr.read(Glib::IO_IN);
+ }
+
+ _main_loop.reset();
+
+ if (_canceled) {
+ // std::cout << "Script Canceled" << std::endl;
+ return 0;
+ }
+
+ Glib::ustring stderr_data = fileerr.string();
+ 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."));
+ }
+
+ Glib::ustring stdout_data = fileout.string();
+ if (stdout_data.length() == 0) {
+ return 0;
+ }
+
+ // std::cout << "Finishing Execution." << std::endl;
+ return stdout_data.length();
+}
+
+
+void Script::file_listener::init(int fd, Glib::RefPtr<Glib::MainLoop> main) {
+ _channel = Glib::IOChannel::create_from_fd(fd);
+ _channel->set_close_on_unref(true);
+ _channel->set_encoding();
+ _conn = main->get_context()->signal_io().connect(sigc::mem_fun(*this, &file_listener::read), _channel, Glib::IO_IN | Glib::IO_HUP | Glib::IO_ERR);
+ _main_loop = main;
+
+ return;
+}
+
+bool Script::file_listener::read(Glib::IOCondition condition) {
+ if (condition != Glib::IO_IN) {
+ _main_loop->quit();
+ return false;
+ }
+
+ Glib::IOStatus status;
+ Glib::ustring out;
+ status = _channel->read_line(out);
+ _string += out;
+
+ if (status != Glib::IO_STATUS_NORMAL) {
+ _main_loop->quit();
+ _dead = true;
+ return false;
+ }
+
+ return true;
+}
+
+bool Script::file_listener::toFile(const Glib::ustring &name) {
+ return toFile(Glib::filename_from_utf8(name));
+}
+
+bool Script::file_listener::toFile(const std::string &name) {
+ try {
+ Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w");
+ stdout_file->set_encoding();
+ stdout_file->write(_string);
+ } catch (Glib::FileError &e) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/extension/implementation/script.h b/src/extension/implementation/script.h
new file mode 100644
index 0000000..bc865f4
--- /dev/null
+++ b/src/extension/implementation/script.h
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Code for handling extensions (i.e., scripts)
+ *
+ * Authors:
+ * Bryce Harrington <bryce@osdl.org>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN
+#define INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN
+
+#include "implementation.h"
+#include <gtkmm/enums.h>
+#include <gtkmm/window.h>
+#include <glibmm/main.h>
+#include <glibmm/spawn.h>
+#include <glibmm/fileutils.h>
+
+namespace Inkscape {
+namespace XML {
+class Node;
+} // namespace XML
+
+namespace Extension {
+namespace Implementation {
+
+/**
+ * Utility class used for loading and launching script extensions
+ */
+class Script : public Implementation {
+public:
+
+ Script();
+ ~Script() override;
+ bool load(Inkscape::Extension::Extension *module) override;
+ void unload(Inkscape::Extension::Extension *module) override;
+ bool check(Inkscape::Extension::Extension *module) override;
+
+ ImplementationDocumentCache * newDocCache(Inkscape::Extension::Extension * ext, Inkscape::UI::View::View * view) override;
+
+ Gtk::Widget *prefs_input(Inkscape::Extension::Input *module, gchar const *filename) override;
+ SPDocument *open(Inkscape::Extension::Input *module, gchar const *filename) override;
+ Gtk::Widget *prefs_output(Inkscape::Extension::Output *module) override;
+ void save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) override;
+ void export_raster(Inkscape::Extension::Output *module,
+ const SPDocument *doc, std::string const &png_file, gchar const *filename) override;
+ void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc, ImplementationDocumentCache * docCache) override;
+ bool cancelProcessing () override;
+
+private:
+ bool _canceled;
+ Glib::Pid _pid;
+ Glib::RefPtr<Glib::MainLoop> _main_loop;
+
+ /**
+ * The command that has been derived from
+ * the configuration file with appropriate directories
+ */
+ std::list<std::string> command;
+
+ /**
+ * This is the extension that will be used
+ * as the helper to read in or write out the
+ * data
+ */
+ Glib::ustring helper_extension;
+
+ /**
+ * The window which should be considered as "parent window" of the script execution,
+ * e.g. when showin warning messages
+ *
+ * If set to NULL the main window of the currently active document is used.
+ */
+ Gtk::Window *parent_window;
+
+ void copy_doc(Inkscape::XML::Node * olddoc, Inkscape::XML::Node * newdoc);
+ void checkStderr (Glib::ustring const& filename, Gtk::MessageType type, Glib::ustring const& message);
+
+ class file_listener {
+ Glib::ustring _string;
+ sigc::connection _conn;
+ Glib::RefPtr<Glib::IOChannel> _channel;
+ Glib::RefPtr<Glib::MainLoop> _main_loop;
+ bool _dead;
+
+ public:
+ file_listener () : _dead(false) { };
+ virtual ~file_listener () {
+ _conn.disconnect();
+ };
+
+ bool isDead () { return _dead; }
+ void init(int fd, Glib::RefPtr<Glib::MainLoop> main);
+ bool read(Glib::IOCondition condition);
+ Glib::ustring string () { return _string; };
+ bool toFile(const Glib::ustring &name);
+ bool toFile(const std::string &name);
+ };
+
+ int execute (const std::list<std::string> &in_command,
+ const std::list<std::string> &in_params,
+ const Glib::ustring &filein,
+ file_listener &fileout);
+
+ void pump_events();
+
+ /** \brief A definition of an interpreter, which can be specified
+ in the INX file, but we need to know what to call */
+ struct interpreter_t {
+ std::string prefstring; /**< The preferences key that can override the default */
+ std::vector<std::string> defaultvals; /**< The default values to check if the preferences are wrong */
+ };
+ static const std::map<std::string, interpreter_t> interpreterTab;
+
+ std::string resolveInterpreterExecutable(const Glib::ustring &interpNameArg);
+
+}; // class Script
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/implementation/xslt.cpp b/src/extension/implementation/xslt.cpp
new file mode 100644
index 0000000..dced1dd
--- /dev/null
+++ b/src/extension/implementation/xslt.cpp
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Code for handling XSLT extensions.
+ */
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "xslt.h"
+
+#include <unistd.h>
+#include <cstring>
+
+#include <glibmm/fileutils.h>
+#include <libxslt/transform.h>
+#include <libxslt/xsltutils.h>
+
+#include "document.h"
+#include "file.h"
+
+#include "extension/extension.h"
+#include "extension/output.h"
+#include "extension/input.h"
+
+#include "io/resource.h"
+
+#include "xml/node.h"
+#include "xml/repr.h"
+
+#include <clocale>
+
+Inkscape::XML::Document * sp_repr_do_read (xmlDocPtr doc, const gchar * default_ns);
+
+/* Namespaces */
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+/* Real functions */
+/**
+ \return A XSLT object
+ \brief This function creates a XSLT object and sets up the
+ variables.
+
+*/
+XSLT::XSLT() :
+ Implementation(),
+ _filename(""),
+ _parsedDoc(nullptr),
+ _stylesheet(nullptr)
+{
+}
+
+bool XSLT::check(Inkscape::Extension::Extension *module)
+{
+ if (load(module)) {
+ unload(module);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool XSLT::load(Inkscape::Extension::Extension *module)
+{
+ if (module->loaded()) { return true; }
+
+ Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "xslt")) {
+ child_repr = child_repr->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "file")) {
+ // TODO: we already parse xslt files as dependencies in extension.cpp
+ // can can we optimize this to be less indirect?
+ const char *filename = child_repr->firstChild()->content();
+ _filename = module->get_dependency_location(filename);
+ }
+ child_repr = child_repr->next();
+ }
+
+ break;
+ }
+ child_repr = child_repr->next();
+ }
+
+ _parsedDoc = xmlParseFile(_filename.c_str());
+ if (_parsedDoc == nullptr) { return false; }
+
+ _stylesheet = xsltParseStylesheetDoc(_parsedDoc);
+
+ return true;
+}
+
+void XSLT::unload(Inkscape::Extension::Extension *module)
+{
+ if (!module->loaded()) { return; }
+ xsltFreeStylesheet(_stylesheet);
+ // No need to use xmlfreedoc(_parsedDoc), it's handled by xsltFreeStylesheet(_stylesheet);
+ return;
+}
+
+SPDocument * XSLT::open(Inkscape::Extension::Input */*module*/,
+ gchar const *filename)
+{
+ xmlDocPtr filein = xmlParseFile(filename);
+ if (filein == nullptr) { return nullptr; }
+
+ const char * params[1];
+ params[0] = nullptr;
+ char *oldlocale = g_strdup(std::setlocale(LC_NUMERIC, nullptr));
+ std::setlocale(LC_NUMERIC, "C");
+
+ xmlDocPtr result = xsltApplyStylesheet(_stylesheet, filein, params);
+ xmlFreeDoc(filein);
+
+ Inkscape::XML::Document * rdoc = sp_repr_do_read( result, SP_SVG_NS_URI);
+ xmlFreeDoc(result);
+ std::setlocale(LC_NUMERIC, oldlocale);
+ g_free(oldlocale);
+
+ if (rdoc == nullptr) {
+ return nullptr;
+ }
+
+ if (strcmp(rdoc->root()->name(), "svg:svg") != 0) {
+ return nullptr;
+ }
+
+ gchar * base = nullptr;
+ gchar * name = nullptr;
+ gchar * s = nullptr, * p = nullptr;
+ s = g_strdup(filename);
+ p = strrchr(s, '/');
+ if (p) {
+ name = g_strdup(p + 1);
+ p[1] = '\0';
+ base = g_strdup(s);
+ } else {
+ base = nullptr;
+ name = g_strdup(filename);
+ }
+ g_free(s);
+
+ SPDocument * doc = SPDocument::createDoc(rdoc, filename, base, name, true, nullptr);
+
+ g_free(base); g_free(name);
+
+ return doc;
+}
+
+void XSLT::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename)
+{
+ /* TODO: Should we assume filename to be in utf8 or to be a raw filename?
+ * See JavaFXOutput::save for discussion.
+ *
+ * From JavaFXOutput::save (now removed):
+ * ---
+ * N.B. The name `filename_utf8' represents the fact that we want it to be in utf8; whereas in
+ * fact we know that some callers of Extension::save pass something in the filesystem's
+ * encoding, while others do g_filename_to_utf8 before calling.
+ *
+ * In terms of safety, it's best to make all callers pass actual filenames, since in general
+ * one can't round-trip from filename to utf8 back to the same filename. Whereas the argument
+ * for passing utf8 filenames is one of convenience: we often want to pass to g_warning or
+ * store as a string (rather than a byte stream) in XML, or the like. */
+ g_return_if_fail(doc != nullptr);
+ g_return_if_fail(filename != nullptr);
+
+ Inkscape::XML::Node *repr = doc->getReprRoot();
+
+ std::string tempfilename_out;
+ int tempfd_out = 0;
+ try {
+ tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return;
+ }
+
+ if (!sp_repr_save_rebased_file(repr->document(), tempfilename_out.c_str(), SP_SVG_NS_URI,
+ doc->getDocumentBase(), filename)) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ xmlDocPtr svgdoc = xmlParseFile(tempfilename_out.c_str());
+ close(tempfd_out);
+ if (svgdoc == nullptr) {
+ return;
+ }
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ const int max_parameters = params.size() * 2;
+ const char * xslt_params[max_parameters+1] ;
+
+ int count = 0;
+ for(auto & param : params) {
+ std::size_t pos = param.find("=");
+ std::ostringstream parameter;
+ std::ostringstream value;
+ parameter << param.substr(2,pos-2);
+ value << param.substr(pos+1);
+ xslt_params[count++] = g_strdup_printf("%s", parameter.str().c_str());
+ xslt_params[count++] = g_strdup_printf("'%s'", value.str().c_str());
+ }
+ xslt_params[count] = nullptr;
+
+ // workaround for inbox#2208
+ char *oldlocale = g_strdup(std::setlocale(LC_NUMERIC, nullptr));
+ std::setlocale(LC_NUMERIC, "C");
+ xmlDocPtr newdoc = xsltApplyStylesheet(_stylesheet, svgdoc, xslt_params);
+ //xmlSaveFile(filename, newdoc);
+ int success = xsltSaveResultToFilename(filename, newdoc, _stylesheet, 0);
+ std::setlocale(LC_NUMERIC, oldlocale);
+ g_free(oldlocale);
+
+ xmlFreeDoc(newdoc);
+ xmlFreeDoc(svgdoc);
+
+ xsltCleanupGlobals();
+ xmlCleanupParser();
+
+ if (success < 1) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ return;
+}
+
+
+} /* Implementation */
+} /* module */
+} /* Inkscape */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/implementation/xslt.h b/src/extension/implementation/xslt.h
new file mode 100644
index 0000000..745d7f5
--- /dev/null
+++ b/src/extension/implementation/xslt.h
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Code for handling XSLT extensions
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_H__
+#define __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_H__
+
+#include "implementation.h"
+
+#include "libxml/tree.h"
+#include "libxslt/xslt.h"
+#include "libxslt/xsltInternals.h"
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+}
+
+
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+class XSLT : public Implementation {
+private:
+ std::string _filename;
+ xmlDocPtr _parsedDoc;
+ xsltStylesheetPtr _stylesheet;
+
+public:
+ XSLT ();
+
+ bool load(Inkscape::Extension::Extension *module) override;
+ void unload(Inkscape::Extension::Extension *module) override;
+
+ bool check(Inkscape::Extension::Extension *module) override;
+
+ SPDocument *open(Inkscape::Extension::Input *module,
+ gchar const *filename) override;
+ void save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) override;
+};
+
+} /* Inkscape */
+} /* Extension */
+} /* Implementation */
+#endif /* __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :