summaryrefslogtreecommitdiffstats
path: root/src/extension/implementation
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/extension/implementation
parentInitial commit. (diff)
downloadinkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz
inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/extension/implementation')
-rw-r--r--src/extension/implementation/implementation.cpp60
-rw-r--r--src/extension/implementation/implementation.h215
-rw-r--r--src/extension/implementation/script.cpp911
-rw-r--r--src/extension/implementation/script.h140
-rw-r--r--src/extension/implementation/xslt.cpp253
-rw-r--r--src/extension/implementation/xslt.h66
6 files changed, 1645 insertions, 0 deletions
diff --git a/src/extension/implementation/implementation.cpp b/src/extension/implementation/implementation.cpp
new file mode 100644
index 0000000..b24df56
--- /dev/null
+++ b/src/extension/implementation/implementation.cpp
@@ -0,0 +1,60 @@
+// 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_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..6c00931
--- /dev/null
+++ b/src/extension/implementation/implementation.h
@@ -0,0 +1,215 @@
+// 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 <memory>
+#include <sigc++/signal.h>
+#include <glibmm/value.h>
+#include <2geom/forward.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+class SPDocument;
+class SPPage;
+class SPStyle;
+
+namespace Inkscape {
+
+namespace UI {
+namespace View {
+class View;
+}
+}
+
+namespace XML {
+ class Node;
+}
+
+namespace Extension {
+
+class Effect;
+class Extension;
+class Template;
+class TemplatePreset;
+class Input;
+class Output;
+class Print;
+
+typedef std::vector<std::shared_ptr<TemplatePreset>> TemplatePresets;
+
+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 () {}
+
+ // ---- Template and Page functions -----
+ virtual SPDocument *new_from_template(Inkscape::Extension::Template *) { return nullptr; }
+ virtual void get_template_presets(const Template *tmod, TemplatePresets &presets) const {};
+ virtual void resize_to_template(Inkscape::Extension::Template *tmod, SPDocument *doc, SPPage *page){};
+ virtual bool match_template_size(Inkscape::Extension::Template *tmod, double width, double height){ return false; }
+
+ // ----- Input functions -----
+ virtual SPDocument *open(Inkscape::Extension::Input * /*module*/,
+ gchar const * /*filename*/) { return nullptr; }
+
+ // ----- Output functions -----
+ /** Find out information about the file. */
+ 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..40357f7
--- /dev/null
+++ b/src/extension/implementation/script.cpp
@@ -0,0 +1,911 @@
+// 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 "script.h"
+
+#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 "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 "extension/template.h"
+#include "inkscape-window.h"
+#include "inkscape.h"
+#include "io/resource.h"
+#include "io/file.h"
+#include "layer-manager.h"
+#include "object/sp-namedview.h"
+#include "object/sp-page.h"
+#include "object/sp-path.h"
+#include "object/sp-root.h"
+#include "path-prefix.h"
+#include "preferences.h"
+#include "selection.h"
+#include "io/dir-util.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/util.h"
+#include "ui/view/view.h"
+#include "widgets/desktop-widget.h"
+#include "xml/attribute-record.h"
+#include "xml/rebase-hrefs.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;
+}
+
+/**
+ * Create a new document based on the given template.
+ */
+SPDocument *Script::new_from_template(Inkscape::Extension::Template *module)
+{
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment();
+
+ if (auto in_file = module->get_template_filename()) {
+ file_listener fileout;
+ execute(command, params, in_file->get_path(), fileout);
+ auto svg = fileout.string();
+ auto rdoc = sp_repr_read_mem(svg.c_str(), svg.length(), SP_SVG_NS_URI);
+ if (rdoc) {
+ auto name = g_strdup_printf(_("New document %d"), SPDocument::get_new_doc_number());
+ return SPDocument::createDoc(rdoc, nullptr, nullptr, name, false, nullptr);
+ }
+ }
+
+ return nullptr;
+}
+
+/**
+ * Take an existing document and selected page and resize or add items as needed.
+ */
+void Script::resize_to_template(Inkscape::Extension::Template *tmod, SPDocument *doc, SPPage *page)
+{
+ std::list<std::string> params;
+ {
+ std::string param = "--page=";
+ if (page) {
+ param += page->getId();
+ } else {
+ // This means 'resize the svg document'
+ param += doc->getRoot()->getId();
+ }
+ params.push_back(param);
+ }
+ _change_extension(tmod, doc, params, true);
+}
+
+/**
+ \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 (doc == nullptr)
+ {
+ g_warning("Script::effect: View not defined");
+ return;
+ }
+
+ SPDesktop *desktop = reinterpret_cast<SPDesktop *>(doc);
+ sp_namedview_document_from_window(desktop);
+
+ 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
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(desktop->getDocument());
+
+ Glib::ustring empty;
+ file_listener outfile;
+ execute(command, {}, 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::list<std::string> params;
+ if (desktop) {
+ Inkscape::Selection * selection = desktop->getSelection();
+ if (selection) {
+ params = selection->params;
+ selection->clear();
+ }
+ }
+ _change_extension(module, desktop->getDocument(), params, module->ignore_stderr);
+}
+
+//uncomment if issues on ref extensions links
+/* void sp_change_hrefs(Inkscape::XML::Node *repr, gchar const *const oldfilename, gchar const *const filename)
+{
+ gchar *new_document_base = nullptr;
+ gchar *new_document_filename = nullptr;
+ gchar *old_document_base = nullptr;
+ gchar *old_document_filename = nullptr;
+ if (filename) {
+
+#ifndef _WIN32
+ new_document_filename = prepend_current_dir_if_relative(filename);
+ old_document_filename = prepend_current_dir_if_relative(oldfilename);
+#else
+ // FIXME: it may be that prepend_current_dir_if_relative works OK on windows too, test!
+ new_document_filename = g_strdup(filename);
+ old_document_filename = g_strdup(oldfilename);
+#endif
+
+ new_document_base = g_path_get_dirname(new_document_filename);
+ old_document_base = g_path_get_dirname(old_document_filename);
+ } else {
+ new_document_base = nullptr;
+ old_document_base = nullptr;
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool use_sodipodi_absref = prefs->getBool("/options/svgoutput/usesodipodiabsref", false);
+ Inkscape::XML::rebase_hrefs(repr, old_document_base, new_document_base, use_sodipodi_absref);
+ g_free(new_document_base);
+ g_free(old_document_base);
+ g_free(new_document_filename);
+ g_free(old_document_filename);
+} */
+
+/**
+ * Internally, any modification of an existing document, used by effect and resize_page extensions.
+ */
+void Script::_change_extension(Inkscape::Extension::Extension *module, SPDocument *doc, std::list<std::string> &params, bool ignore_stderr)
+{
+ module->paramListString(params);
+ module->set_environment(doc);
+
+ if (auto env = module->get_execution_env()) {
+ parent_window = env->get_working_dialog();
+ }
+
+ auto tempfile_out = Inkscape::IO::TempFilename("ink_ext_XXXXXX.svg");
+ auto tempfile_in = Inkscape::IO::TempFilename("ink_ext_XXXXXX.svg");
+
+ // Save current document to a temporary file we can send to the extension
+ 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),
+ doc, tempfile_in.get_filename().c_str(), false, false,
+ Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ prefs->setBool("/options/svgoutput/disable_optimizations", false);
+
+ file_listener fileout;
+ int data_read = execute(command, params, tempfile_in.get_filename(), fileout, ignore_stderr);
+ if (data_read == 0) {
+ return;
+ }
+ fileout.toFile(tempfile_out.get_filename());
+
+ pump_events();
+ Inkscape::XML::Document *new_xmldoc = nullptr;
+ if (data_read > 10) {
+ new_xmldoc = sp_repr_read_file(tempfile_out.get_filename().c_str(), SP_SVG_NS_URI);
+ } // data_read
+
+ pump_events();
+
+ if (new_xmldoc) {
+ //uncomment if issues on ref extensions links (with previous function)
+ //sp_change_hrefs(new_xmldoc, tempfile_out.get_filename().c_str(), doc->getDocumentFilename());
+ doc->rebase(new_xmldoc);
+ } else {
+ Inkscape::UI::gui_warning(_("The output from the extension could not be parsed."), parent_window);
+ }
+
+ return;
+}
+
+/** \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::showPopupError (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,
+ bool ignore_stderr)
+{
+ 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.empty() && !ignore_stderr) {
+ if (INKSCAPE.use_gui()) {
+ showPopupError(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."));
+ } else {
+ std::cerr << "Script Error\n----\n" << stderr_data.c_str() << "\n----\n";
+ }
+ }
+
+ Glib::ustring stdout_data = fileout.string();
+ 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..d825ae4
--- /dev/null
+++ b/src/extension/implementation/script.h
@@ -0,0 +1,140 @@
+// 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 "xml/node.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;
+
+ SPDocument *new_from_template(Inkscape::Extension::Template *module) override;
+ void resize_to_template(Inkscape::Extension::Template *tmod, SPDocument *doc, SPPage *page) override;
+
+ SPDocument *open(Inkscape::Extension::Input *module, gchar const *filename) 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;
+
+ void _change_extension(Inkscape::Extension::Extension *mod, SPDocument *doc, std::list<std::string> &params, bool ignore_stderr);
+
+ /**
+ * 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 showPopupError (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,
+ bool ignore_stderr = false);
+
+ 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 :