From c853ffb5b2f75f5a889ed2e3ef89b818a736e87a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:50:49 +0200 Subject: Adding upstream version 1.3+ds. Signed-off-by: Daniel Baumann --- src/extension/implementation/implementation.cpp | 60 ++ src/extension/implementation/implementation.h | 215 ++++++ src/extension/implementation/script.cpp | 911 ++++++++++++++++++++++++ src/extension/implementation/script.h | 140 ++++ src/extension/implementation/xslt.cpp | 253 +++++++ src/extension/implementation/xslt.h | 66 ++ 6 files changed, 1645 insertions(+) create mode 100644 src/extension/implementation/implementation.cpp create mode 100644 src/extension/implementation/implementation.h create mode 100644 src/extension/implementation/script.cpp create mode 100644 src/extension/implementation/script.h create mode 100644 src/extension/implementation/xslt.cpp create mode 100644 src/extension/implementation/xslt.h (limited to 'src/extension/implementation') 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 + 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 +#include +#include + +#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 * 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(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 + 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 +#include +#include +#include +#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> 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 *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 + * Ted Gould + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2002-2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "script.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 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 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 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 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 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 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(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 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 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 ¶ms, 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 &in_command, + const std::list &in_params, + const Glib::ustring &filein, + file_listener &fileout, + bool ignore_stderr) +{ + g_return_val_if_fail(!in_command.empty(), 0); + + std::vector 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(0), // no flags + sigc::slot(), + &_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 _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 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 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 + * Ted Gould + * + * 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 +#include +#include +#include +#include + +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 _main_loop; + + void _change_extension(Inkscape::Extension::Extension *mod, SPDocument *doc, std::list ¶ms, bool ignore_stderr); + + /** + * The command that has been derived from + * the configuration file with appropriate directories + */ + std::list 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 _channel; + Glib::RefPtr _main_loop; + bool _dead; + + public: + file_listener () : _dead(false) { }; + virtual ~file_listener () { + _conn.disconnect(); + }; + + bool isDead () { return _dead; } + void init(int fd, Glib::RefPtr 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 &in_command, + const std::list &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 defaultvals; /**< The default values to check if the preferences are wrong */ + }; + static const std::map 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 + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2006-2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "xslt.h" + +#include +#include + +#include +#include +#include + +#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 + +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 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 + * + * 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 : -- cgit v1.2.3