// SPDX-License-Identifier: GPL-2.0-or-later /** @file * TODO: insert short description here *//* * Authors: see git history * * Copyright (C) 2018 Authors * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ /* * attribute-rel-util.h * * Created on: Sep 8, 2011 * Author: tavmjong */ /** * Utility functions for cleaning SVG tree of unneeded attributes and style properties. */ #include #include #include #include "preferences.h" #include "xml/attribute-record.h" #include "attribute-rel-css.h" #include "attribute-rel-svg.h" #include "attribute-rel-util.h" using Inkscape::XML::Node; /** * Get preferences */ unsigned int sp_attribute_clean_get_prefs() { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); unsigned int flags = 0; if( prefs->getBool("/options/svgoutput/incorrect_attributes_warn") ) flags += SP_ATTRCLEAN_ATTR_WARN; if( prefs->getBool("/options/svgoutput/incorrect_attributes_remove") && !prefs->getBool("/options/svgoutput/disable_optimizations" ) ) flags += SP_ATTRCLEAN_ATTR_REMOVE; if( prefs->getBool("/options/svgoutput/incorrect_style_properties_warn") ) flags += SP_ATTRCLEAN_STYLE_WARN; if( prefs->getBool("/options/svgoutput/incorrect_style_properties_remove" ) && !prefs->getBool("/options/svgoutput/disable_optimizations" ) ) flags += SP_ATTRCLEAN_STYLE_REMOVE; if( prefs->getBool("/options/svgoutput/style_defaults_warn") ) flags += SP_ATTRCLEAN_DEFAULT_WARN; if( prefs->getBool("/options/svgoutput/style_defaults_remove") && !prefs->getBool("/options/svgoutput/disable_optimizations" ) ) flags += SP_ATTRCLEAN_DEFAULT_REMOVE; return flags; } /** * Remove or warn about inappropriate attributes and useless stype properties. * repr: the root node in a document or any other node. */ void sp_attribute_clean_tree(Node *repr) { g_return_if_fail (repr != nullptr); unsigned int flags = sp_attribute_clean_get_prefs(); if( flags ) { sp_attribute_clean_recursive( repr, flags ); } } /** * Clean recursively over all elements. */ void sp_attribute_clean_recursive(Node *repr, unsigned int flags) { g_return_if_fail (repr != nullptr); if( repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE ) { Glib::ustring element = repr->name(); // Only clean elements in svg namespace if( element.substr(0,4) == "svg:" ) { sp_attribute_clean_element(repr, flags ); } } for(Node *child=repr->firstChild() ; child ; child = child->next()) { // Don't remove default css values if element is in or is a Glib::ustring element = child->name(); unsigned int flags_temp = flags; if( element.compare( "svg:defs" ) == 0 || element.compare( "svg:symbol" ) == 0 ) { flags_temp &= ~(SP_ATTRCLEAN_DEFAULT_WARN|SP_ATTRCLEAN_DEFAULT_REMOVE); } sp_attribute_clean_recursive( child, flags_temp ); } } /** * Clean attributes on an element */ void sp_attribute_clean_element(Node *repr, unsigned int flags) { g_return_if_fail (repr != nullptr); g_return_if_fail (repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE); Glib::ustring element = repr->name(); Glib::ustring id = (repr->attribute( "id" )==nullptr ? "" : repr->attribute( "id" )); // Clean style: this attribute is unique in that normally we want to change it and not simply // delete it. sp_attribute_clean_style(repr, flags ); // Clean attributes std::set attributesToDelete; for ( const auto & iter : repr->attributeList()) { Glib::ustring attribute = g_quark_to_string(iter.key); //Glib::ustring value = (const char*)iter->value; bool is_useful = sp_attribute_check_attribute( element, id, attribute, flags & SP_ATTRCLEAN_ATTR_WARN ); if( !is_useful && (flags & SP_ATTRCLEAN_ATTR_REMOVE) ) { attributesToDelete.insert( attribute ); } } // Do actual deleting (done after so as not to perturb List iterator). for(const auto & iter_d : attributesToDelete) { repr->removeAttribute(iter_d); } } /** * Clean CSS style on an element. */ void sp_attribute_clean_style(Node *repr, unsigned int flags) { g_return_if_fail (repr != nullptr); g_return_if_fail (repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE); // Find element's style SPCSSAttr *css = sp_repr_css_attr( repr, "style" ); sp_attribute_clean_style(repr, css, flags); // Convert css node's properties data to string and set repr node's attribute "style" to that string. // sp_repr_css_set( repr, css, "style"); // Don't use as it will cause loop. Glib::ustring value; sp_repr_css_write_string(css, value); repr->setAttributeOrRemoveIfEmpty("style", value); sp_repr_css_attr_unref( css ); } /** * Clean CSS style on an element. */ Glib::ustring sp_attribute_clean_style(Node *repr, gchar const *string, unsigned int flags) { g_return_val_if_fail (repr != nullptr, ""); g_return_val_if_fail (repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE, ""); SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_attr_add_from_string( css, string ); sp_attribute_clean_style(repr, css, flags); Glib::ustring string_cleaned; sp_repr_css_write_string (css, string_cleaned); sp_repr_css_attr_unref( css ); return string_cleaned; } /** * Clean CSS style on an element. * * 1. Is a style property appropriate on the given element? * e.g, font-size is useless on * 2. Is the value of the style property useful? * Is it the same as the parent and it inherits? * Is it the default value (and the property on the parent is not set or does not inherit)? */ void sp_attribute_clean_style(Node* repr, SPCSSAttr *css, unsigned int flags) { g_return_if_fail (repr != nullptr); g_return_if_fail (css != nullptr); Glib::ustring element = repr->name(); Glib::ustring id = (repr->attribute( "id" )==nullptr ? "" : repr->attribute( "id" )); // Find parent's style, including properties that are inherited. // Note, a node may not have a parent if it has not yet been added to tree. SPCSSAttr *css_parent = nullptr; if( repr->parent() ) css_parent = sp_repr_css_attr_inherited( repr->parent(), "style" ); // Loop over all properties in "style" node, keeping track of which to delete. std::set toDelete; for ( const auto & iter : css->attributeList()) { gchar const * property = g_quark_to_string(iter.key); gchar const * value = iter.value; // Check if a property is applicable to an element (i.e. is font-family useful for a ?). if( !SPAttributeRelCSS::findIfValid( property, element ) ) { if( flags & SP_ATTRCLEAN_STYLE_WARN ) { g_warning( "<%s id=\"%s\">: CSS Style property: \"%s\" is inappropriate.", element.c_str(), id.c_str(), property ); } if( flags & SP_ATTRCLEAN_STYLE_REMOVE ) { toDelete.insert(property); } continue; } // Find parent value for same property (property) gchar const * value_p = nullptr; if( css_parent != nullptr ) { for ( const auto & iter_p : css_parent->attributeList()) { gchar const * property_p = g_quark_to_string(iter_p.key); if( !g_strcmp0( property, property_p ) ) { value_p = iter_p.value; break; } } } // If parent has same property value and property is inherited, mark for deletion. if ( !g_strcmp0( value, value_p ) && SPAttributeRelCSS::findIfInherit( property ) ) { if ( flags & SP_ATTRCLEAN_DEFAULT_WARN ) { g_warning( "<%s id=\"%s\">: CSS Style property: \"%s\" has same value as parent (%s).", element.c_str(), id.c_str(), property, value ); } if ( flags & SP_ATTRCLEAN_DEFAULT_REMOVE ) { toDelete.insert( property ); } continue; } // If property value is same as default and the parent value not set or property is not inherited, // mark for deletion. if ( SPAttributeRelCSS::findIfDefault( property, value ) && ((value_p == nullptr) || !SPAttributeRelCSS::findIfInherit( property ) ) ) { if ( flags & SP_ATTRCLEAN_DEFAULT_WARN ) { g_warning( "<%s id=\"%s\">: CSS Style property: \"%s\" with default value (%s) not needed.", element.c_str(), id.c_str(), property, value ); } if ( flags & SP_ATTRCLEAN_DEFAULT_REMOVE ) { toDelete.insert( property ); } continue; } } // End loop over style properties // Delete unneeded style properties. Do this at the end so as to not perturb List iterator. for(const auto & iter_d : toDelete) { sp_repr_css_set_property( css, iter_d.c_str(), nullptr ); } } /** * Remove CSS style properties with default values. */ void sp_attribute_purge_default_style(SPCSSAttr *css, unsigned int flags) { g_return_if_fail (css != nullptr); // Loop over all properties in "style" node, keeping track of which to delete. std::set toDelete; for ( const auto & iter : css->attributeList()) { gchar const * property = g_quark_to_string(iter.key); gchar const * value = iter.value; // If property value is same as default mark for deletion. if ( SPAttributeRelCSS::findIfDefault( property, value ) ) { if ( flags & SP_ATTRCLEAN_DEFAULT_WARN ) { g_warning( "Preferences CSS Style property: \"%s\" with default value (%s) not needed.", property, value ); } if ( flags & SP_ATTRCLEAN_DEFAULT_REMOVE ) { toDelete.insert( property ); } continue; } } // End loop over style properties // Delete unneeded style properties. Do this at the end so as to not perturb List iterator. for(const auto & iter_d : toDelete) { sp_repr_css_set_property( css, iter_d.c_str(), nullptr ); } } /** * Check one attribute on an element */ bool sp_attribute_check_attribute(Glib::ustring element, Glib::ustring id, Glib::ustring attribute, bool warn) { bool is_useful = true; if( SPAttributeRelCSS::findIfProperty( attribute ) ) { // First check if it is a presentation attribute. Presentation attributes can be applied to // any element. At the moment, we are only going to check if it is a possibly useful // attribute. Note, we don't explicitly check against the list of elements where presentation // attributes are allowed (See SVG1.1 spec, Appendix M.2). if( !SPAttributeRelCSS::findIfValid( attribute, element ) ) { // Non-useful presentation attribute on SVG if( warn ) { g_warning( "<%s id=\"%s\">: Non-useful presentation attribute: \"%s\" found.", element.c_str(), id.c_str(), attribute.c_str() ); } is_useful = false; } } else { // Second check if it is a valid attribute if ( !SPAttributeRelSVG::findIfValid( attribute, element ) ) { // Invalid attribute on SVG if( warn ) { g_warning( "<%s id=\"%s\">: Invalid attribute: \"%s\" found.", element.c_str(), id.c_str(), attribute.c_str() ); } is_useful = false; } } return is_useful; } /* 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 :