summaryrefslogtreecommitdiffstats
path: root/src/attribute-rel-util.cpp
blob: 4c54297613fd3712154a86513c47f850c57557e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
// 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 <fstream>
#include <sstream>
#include <iostream>

#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 <defs> or is a <symbol>
    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<Glib::ustring> 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 <svg:rect>
 * 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<Glib::ustring> 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 <rect>?).
    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<Glib::ustring> 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 <element>
            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 <element>
            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 :