Repository: svg/svgo
Pull request #906 (2018-02-16 11:34:25)

Allow to forceAbsolutePath output for convertPathData plugin.
Added parameter forceAbsolutePath to get absolute SVG path (even when the relative path is shorter).

This is useful for generating mobile assets, android vector drawables etc.


Patch:
diff --git a/.gitignore b/.gitignore
index b7c204f..7ab3c06 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ bin/svgo-profiling
 *.sublime-*
 *.log
 .DS_Store
+.idea
diff --git a/.npmignore b/.npmignore
index a99468b..fa34277 100644
--- a/.npmignore
+++ b/.npmignore
@@ -3,6 +3,7 @@ examples
 test
 lib-cov
 .editorconfig
+.gitignore
 .jshintignore
 .jshintrc
 .travis.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8d22cf..2ddc48d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+### [ [>](https://github.com/svg/svgo/tree/v1.0.5) ] 1.0.5 / 26.02.2018
+* Fixed issue with prefixIDs plugin not replacing url() values correctly (by @harrisjose).
+
 ### [ [>](https://github.com/svg/svgo/tree/v1.0.4) ] 1.0.4 / 30.01.2018
 * Fixed bug with removing groups that are direct child of "<switch>".
 * Fixed bug with shorthand path points counting (thanks @alexjlockwood for noticing).
@@ -32,7 +35,7 @@
 * New option `keepAriaAttrs` in `removeUnknownsAndDefaults` (by @davidtheclark).
 * Corrected parsing in `cleanupIDs` to account animation syntax (by @caub).
 * `#ff0000` now converts to `red` as well as `#f00` (by @davidleston).
-* Added “gray” variation to colors list per CSS Color Module Level 4 (by @JoshyPHP).
+* Added “gray” variation to colors list per CSS Color Module Level 4 (by @ydaniv).
 * Fixed error on empty files.
 * A separator character in `removeAttrs` now can be changed per `elemSeparator` option (by @mikestreety).
 * `addAttributesToSVGElement` now can add values to attributes.
diff --git a/README.md b/README.md
index baab209..b7a6a9b 100644
--- a/README.md
+++ b/README.md
@@ -3,14 +3,14 @@
 
 <img src="https://svg.github.io/svgo-logo.svg" width="200" height="200" alt="logo"/>
 
-## SVGO [![NPM version](https://badge.fury.io/js/svgo.svg)](https://npmjs.org/package/svgo) [![Dependency Status](https://gemnasium.com/svg/svgo.svg)](https://gemnasium.com/svg/svgo) [![Build Status](https://secure.travis-ci.org/svg/svgo.svg)](https://travis-ci.org/svg/svgo) [![Coverage Status](https://img.shields.io/coveralls/svg/svgo.svg)](https://coveralls.io/r/svg/svgo?branch=master)
+## SVGO [![NPM version](https://badge.fury.io/js/svgo.svg)](https://npmjs.org/package/svgo) [![Build Status](https://secure.travis-ci.org/svg/svgo.svg)](https://travis-ci.org/svg/svgo) [![Coverage Status](https://img.shields.io/coveralls/svg/svgo.svg)](https://coveralls.io/r/svg/svgo?branch=master)
 
 **SVG O**ptimizer is a Nodejs-based tool for optimizing SVG vector graphics files.
 ![](https://mc.yandex.ru/watch/18431326)
 
 ## Why?
 
-SVG files, especially exported from various editors, usually contain a lot of redundant and useless information such as editor metadata, comments, hidden elements, default or non-optimal values and other stuff that can be safely removed or converted without affecting SVG rendering result.
+SVG files, especially those exported from various editors, usually contain a lot of redundant and useless information. This can include editor metadata, comments, hidden elements, default or non-optimal values and other stuff that can be safely removed or converted without affecting the SVG rendering result.
 
 ## What it can do
 
@@ -21,6 +21,7 @@ Today we have:
 | Plugin | Description |
 | ------ | ----------- |
 | [cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) | cleanup attributes from newlines, trailing, and repeating spaces |
+| [inlineStyles](https://github.com/svg/svgo/blob/master/plugins/inlineStyles.js) | move and merge styles from `<style>` elements to element `style` attributes |
 | [removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) | remove doctype declaration |
 | [removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) | remove XML processing instructions |
 | [removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) | remove comments |
@@ -176,7 +177,7 @@ Arguments:
 
 ### Other Ways to Use SVGO
 
-* as a web app - [SVGOMG](https://jakearchibald.github.io/svgomg/)
+* as a web app – [SVGOMG](https://jakearchibald.github.io/svgomg/)
 * as a Nodejs module – [examples](https://github.com/svg/svgo/tree/master/examples)
 * as a Grunt task – [grunt-svgmin](https://github.com/sindresorhus/grunt-svgmin)
 * as a Gulp task – [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin)
@@ -184,8 +185,10 @@ Arguments:
 * as an OSX Folder Action – [svgo-osx-folder-action](https://github.com/svg/svgo-osx-folder-action)
 * as a webpack loader – [image-webpack-loader](https://github.com/tcoopman/image-webpack-loader)
 * as a Telegram Bot – [svgo_bot](https://github.com/maksugr/svgo_bot)
-* as a PostCSS plugin - [postcss-svgo](https://github.com/ben-eb/postcss-svgo)
-* as an Inkscape plugin - [inkscape-svgo](https://github.com/konsumer/inkscape-svgo)
+* as a PostCSS plugin – [postcss-svgo](https://github.com/ben-eb/postcss-svgo)
+* as an Inkscape plugin – [inkscape-svgo](https://github.com/konsumer/inkscape-svgo)
+* as a Sketch plugin - [svgo-compressor](https://github.com/BohemianCoding/svgo-compressor)
+* as macOS app - [Image Shrinker](https://image-shrinker.com)
 
 ## License and Copyright
 
diff --git a/README.ru.md b/README.ru.md
index 26f1499..8b939d0 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -3,24 +3,25 @@
 
 <img src="https://svg.github.io/svgo-logo.svg" width="200" height="200" alt="logo"/>
 
-## SVGO [![NPM version](https://badge.fury.io/js/svgo.svg)](https://npmjs.org/package/svgo) [![Dependency Status](https://gemnasium.com/svg/svgo.svg)](https://gemnasium.com/svg/svgo) [![Build Status](https://secure.travis-ci.org/svg/svgo.svg)](https://travis-ci.org/svg/svgo) [![Coverage Status](https://img.shields.io/coveralls/svg/svgo.svg)](https://coveralls.io/r/svg/svgo?branch=master)
+## SVGO [![NPM version](https://badge.fury.io/js/svgo.svg)](https://npmjs.org/package/svgo) [![Build Status](https://secure.travis-ci.org/svg/svgo.svg)](https://travis-ci.org/svg/svgo) [![Coverage Status](https://img.shields.io/coveralls/svg/svgo.svg)](https://coveralls.io/r/svg/svgo?branch=master)
 
 **SVG** **O**ptimizer – это инструмент для оптимизации векторной графики в формате SVG, написанный на Node.js.
 ![](https://mc.yandex.ru/watch/18431326)
 
 ## Зачем?
 
-SVG-файлы, особенно – экспортированные из различных редакторов, содержат много избыточной и бесполезной информации, комментариев, скрытых элементов, неоптимальные или стандартные значения и другой мусор, удаление которого безопасно и не влияет на конечный результат отрисовки.
+SVG-файлы, особенно экспортированные из редакторов, содержат много избыточной и бесполезной информации, комментариев, скрытых элементов, неоптимальные или стандартные значения и другой мусор, удаление которого безопасно и не влияет на конечный вид изображения.
 
 ## Возможности
 
 SVGO имеет расширяемую архитектуру, в которой почти каждая оптимизация является отдельным расширением.
 
-Сегодня у нас есть:
+Что у нас есть:
 
 | Plugin | Description |
 | ------ | ----------- |
 | [cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) | удаление переносов строк и лишних пробелов |
+| [inlineStyles](https://github.com/svg/svgo/blob/master/plugins/inlineStyles.js) | перенос стилей из элементов `<style>` в атрибуты `style` |
 | [removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) | удаление doctype |
 | [removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) | удаление XML-инструкций |
 | [removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) | удаление комментариев |
@@ -63,7 +64,7 @@ SVGO имеет расширяемую архитектуру, в которой
 | [removeStyleElement](https://github.com/svg/svgo/blob/master/plugins/removeStyleElement.js) | удаляет элементы `<style>` (выключено по умолчанию) |
 | [removeScriptElement](https://github.com/svg/svgo/blob/master/plugins/removeScriptElement.js) | удаляет элементы `<script>` (выключено по умолчанию) |
 
-Хотите узнать, как это работает и как написать свой плагин? [Конечно же, да!](https://github.com/svg/svgo/blob/master/docs/how-it-works/ru.md).
+Хотите узнать принципы работы и как написать свой плагин? [Конечно же, да!](https://github.com/svg/svgo/blob/master/docs/how-it-works/ru.md)
 
 
 ## Как использовать
@@ -185,6 +186,9 @@ $ [sudo] npm install -g svgo
 * через загрузчик webpack – [image-webpack-loader](https://github.com/tcoopman/image-webpack-loader)
 * с помощью бота в Telegram – [svgo_bot](https://github.com/maksugr/svgo_bot)
 * как плагин PostCSS - [postcss-svgo](https://github.com/ben-eb/postcss-svgo)
+* как плагин для Inkscape – [inkscape-svgo](https://github.com/konsumer/inkscape-svgo)
+* как плагин для Sketch - [svgo-compressor](https://github.com/BohemianCoding/svgo-compressor)
+* в виде приложения macOS - [Image Shrinker](https://image-shrinker.com)
 
 ## Лицензия и копирайты
 
diff --git a/docs/how-it-works/en.md b/docs/how-it-works/en.md
index 47b19d9..3b794b3 100644
--- a/docs/how-it-works/en.md
+++ b/docs/how-it-works/en.md
@@ -22,7 +22,7 @@ SVGO reads, parses and/or extends the [default config](https://github.com/svg/sv
 
 ```yaml
 plugins:
-  - myTestPlugi
+  - myTestPlugin
   - myTestPlugin2: false
   - myTestPlugin3:
       param1: 1
diff --git a/docs/how-it-works/ko.md b/docs/how-it-works/ko.md
index 0033a10..57de896 100644
--- a/docs/how-it-works/ko.md
+++ b/docs/how-it-works/ko.md
@@ -201,11 +201,130 @@ plugins
   * @param {Object} [context] callback context
   * @return {Boolean} false if there are no any attributes
 
+##### querySelectorAll(selectors)
+  * 요소에 대해 CSS selector들의 문자열을 평가하고 일치하는 요소를 리턴한다.
+  * @param {String} selectors CSS selector(s) string
+  * @return {Array} null if no elements matched
+
+##### querySelector(selectors)
+  * 요소에 대해 CSS selector들의 문자열을 평가하고 첫 번째로 일치하는 element만 리턴한다.
+  * @param {String} selectors CSS selector(s) string
+  * @return {Array} null if no element matched
+
+##### matches(selector)
+  * selector가 주어진 요소와 일치하는지 테스트한다.
+  * @param {String} selector CSS selector string
+  * @return {Boolean} true if element would be selected by selector string, false if it does not
+
+
+##### style.getCssText()
+  * 선언 블록의 텍스트 표현을 가져온다. (cssText 특성과 동일)
+  * @return {String} Textual representation of the declaration block (empty string for no properties)
+
+##### style.getPropertyPriority(propertyName)
+  * "중요한"으로 속성값이 부여된 선택적 요소를 리턴한다.
+  * @param {String} propertyName representing the property name to be checked.
+  * @return {String} priority that represents the priority (e.g. "important") if one exists. If none exists, returns the empty string.
+
+##### style.getPropertyValue(propertyName)
+  * 프로퍼티 이름으로 주어진 프로퍼티 값을 리턴한다.
+  * @param {String} propertyName representing the property name to be checked.
+  * @return {String} value containing the value of the property. If not set, returns the empty string.
+
+##### style.item(index)
+  * 프로퍼티 이름을 리턴한다.
+  * @param {Number} index of the node to be fetched. The index is zero-based.
+  * @return {String} propertyName that is the name of the CSS property at the specified index.
+
+##### style.getProperties()
+  * 노드의 모든 프로퍼티를 리턴한다.
+  * @return {Map} properties that is a Map with propertyName as key and property (propertyValue + propertyPriority) as value.
+
+##### style.removeProperty(propertyName)
+  * CSS 선언 블록에서 속성을 제거한다.
+  * @param {String} propertyName representing the property name to be removed.
+  * @return {String} oldValue equal to the value of the CSS property before it was removed.
+
+##### style.setProperty(propertyName, value, priority)
+  * 기존 CSS 속성을 수정하거나 선언 블록에 새 CSS 속성을 만든다.
+  * @param {String} propertyName representing the CSS property name to be modified.
+  * @param {String} [value] containing the new property value. If not specified, treated as the empty string. value must not contain "!important" -- that should be set using the priority parameter.
+  * @param {String} [priority] allowing the "important" CSS priority to be set. If not specified, treated as the empty string.
+  * @return {undefined}
+
+##### css-tools.flattenToSelectors(cssAst)
+  * CSS AST를 Select List로 만든다.
+  * @param {Object} CSS AST to flatten
+  * @return {Array} selectors
+
+##### css-tools.filterByMqs(selectors, useMqs)
+  * Filter selectors by Media Query.
+  * @param {Array} Selectors to filter
+  * @param {Array} Strings of media queries that should pass (<name> <expression>)
+  * @return {Array} Filtered selectors that match the passed media queries
+
+##### css-tools.filterByPseudos(selectors, useMqs)
+  * Filter selectors by the pseudo-elements and/or -classes they contain.
+  * @param {Array} Selectors to filter
+  * @param {Array} Strings of single or sequence of pseudo-elements and/or -classes that should pass
+  * @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes
+
+##### css-tools.cleanPseudos(selectors)
+  * 정당히 매칭된 selector로 부터 pseudo element와 class들을 제거한다.
+  * @param {Array} Selectors to clean
+  * @return {Array} Selectors without pseudo-elements and/or -classes
+
+##### css-tools.compareSpecificity(aSpecificity, bSpecificity)
+  * 두 개의 Selector 특성을 비교한다.
+  * @param {Array} Specificity of selector A
+  * @param {Array} Specificity of selector B
+  * @return {Number} Score of selector specificity A compared to selector specificity B
+
+##### css-tools.compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode)
+  * 두 개의 간단한 Selector를 비교한다.
+  * @param {Object} Simple selector A
+  * @param {Object} Simple selector B
+  * @return {Number} Score of selector A compared to selector B
+
+##### css-tools.sortSelectors(selectors)
+  * Selector들을 안정적으로 정렬한다.
+  * @param {Array} Selectors to be sorted
+  * @return {Array} Stable sorted selectors
+
+##### css-tools.csstreeToStyleDeclaration(declaration)
+  * AST style의 css-tree 선언을 CSSStyleDeclaration property로 바꾼다.
+  * @param {Object} css-tree style declaration
+  * @return {Object} CSSStyleDeclaration property
+
+##### css-tools.getCssStr(elem)
+  * style element로 부터 CSS string을 가져온다.
+  * @param {Object} element style element
+  * @return {String|Array} CSS string or empty array if no styles are set
+
+##### css-tools.csstreeToStyleDeclaration(elem, css)
+  * @param {Object} element style element
+  * @param {String} CSS string to be set
+  * @return {Object} reference to field with CSS
+
+
 #### 3.3 tests
 
 당신의 플러그인을 테스팅 하는 것보다 쉬운 일은 없다.
 
 1. `test/plugins` 안에 `myPlugin.01.orig.svg` 와 `myPlugin.01.should.svg` 를 넣는다
+
+```
+[original svg]
+
+@@@
+
+[how svg should look afterwards]
+
+@@@
+
+attributes if your plugin needs them
+```
+
 2. `npm test` 를 실행한다
 3. PROFIT!
 
diff --git a/lib/svgo/config.js b/lib/svgo/config.js
index 786c37a..2f4abdc 100644
--- a/lib/svgo/config.js
+++ b/lib/svgo/config.js
@@ -27,7 +27,7 @@ module.exports = function(config) {
         }
     } else {
         defaults = Object.assign({}, yaml.safeLoad(FS.readFileSync(__dirname + '/../../.svgo.yml', 'utf8')));
-        defaults.plugins = preparePluginsArray(defaults.plugins);
+        defaults.plugins = preparePluginsArray(defaults.plugins || []);
         defaults = extendConfig(defaults, config);
     }
 
diff --git a/lib/svgo/css-class-list.js b/lib/svgo/css-class-list.js
index 28e5816..8401a9f 100644
--- a/lib/svgo/css-class-list.js
+++ b/lib/svgo/css-class-list.js
@@ -13,6 +13,28 @@ var CSSClassList = function(node) {
     //this.classValue = null;
 };
 
+/**
+ * Performs a deep clone of this object.
+ *
+ * @param parentNode the parentNode to assign to the cloned result
+ */
+CSSClassList.prototype.clone = function(parentNode) {
+    var node = this;
+    var nodeData = {};
+
+    Object.keys(node).forEach(function(key) {
+        if (key !== 'parentNode') {
+            nodeData[key] = node[key];
+        }
+    });
+
+    // Deep-clone node data.
+    nodeData = JSON.parse(JSON.stringify(nodeData));
+
+    var clone = new CSSClassList(parentNode);
+   Object.assign(clone, nodeData);
+    return clone;
+};
 
 CSSClassList.prototype.hasClass = function() {
     this.classAttr = { // empty class attr
diff --git a/lib/svgo/css-style-declaration.js b/lib/svgo/css-style-declaration.js
index 5b2300b..4bb987c 100644
--- a/lib/svgo/css-style-declaration.js
+++ b/lib/svgo/css-style-declaration.js
@@ -16,6 +16,28 @@ var CSSStyleDeclaration = function(node) {
     this.parseError = false;
 };
 
+/**
+ * Performs a deep clone of this object.
+ *
+ * @param parentNode the parentNode to assign to the cloned result
+ */
+CSSStyleDeclaration.prototype.clone = function(parentNode) {
+    var node = this;
+    var nodeData = {};
+
+    Object.keys(node).forEach(function(key) {
+        if (key !== 'parentNode') {
+            nodeData[key] = node[key];
+        }
+    });
+
+    // Deep-clone node data.
+    nodeData = JSON.parse(JSON.stringify(nodeData));
+
+    var clone = new CSSStyleDeclaration(parentNode);
+    Object.assign(clone, nodeData);
+    return clone;
+};
 
 CSSStyleDeclaration.prototype.hasStyle = function() {
     this.addStyleHandler();
diff --git a/lib/svgo/jsAPI.js b/lib/svgo/jsAPI.js
index b4d4d1b..4cf958c 100644
--- a/lib/svgo/jsAPI.js
+++ b/lib/svgo/jsAPI.js
@@ -28,12 +28,12 @@ JSAPI.prototype.clone = function() {
     var nodeData = {};
 
     Object.keys(node).forEach(function(key) {
-        if (key !== 'content') {
+        if (key !== 'class' && key !== 'style' && key !== 'content') {
             nodeData[key] = node[key];
         }
     });
 
-    // Deep-clone node data
+    // Deep-clone node data.
     nodeData = JSON.parse(JSON.stringify(nodeData));
 
     // parentNode gets set to a proper object by the parent clone,
@@ -41,6 +41,12 @@ JSAPI.prototype.clone = function() {
     // in the constructor.
     var clonedNode = new JSAPI(nodeData, !!node.parentNode);
 
+    if (node.class) {
+        clonedNode.class = node.class.clone(clonedNode);
+    }
+    if (node.style) {
+        clonedNode.style = node.style.clone(clonedNode);
+    }
     if (node.content) {
         clonedNode.content = node.content.map(function(childNode) {
             var clonedChild = childNode.clone();
@@ -243,7 +249,10 @@ JSAPI.prototype.renameElem = function(name) {
 
     if (!arguments.length) return false;
 
-    if (Array.isArray(name)) name.forEach(this.removeAttr, this);
+    if (Array.isArray(name)) {
+        name.forEach(this.removeAttr, this);
+        return false;
+    }
 
     if (!this.hasAttr(name)) return false;
 
diff --git a/lib/svgo/tools.js b/lib/svgo/tools.js
index 7dd33c0..fb52c9d 100644
--- a/lib/svgo/tools.js
+++ b/lib/svgo/tools.js
@@ -15,9 +15,12 @@ exports.encodeSVGDatauri = function(str, type) {
     if (!type || type === 'base64') {
 
         prefix += ';base64,';
-
-        str = prefix + new Buffer(str).toString('base64');
-
+        if (Buffer.from) {
+            str = prefix + Buffer.from(str).toString('base64');
+        } else {
+            str = prefix + new Buffer(str).toString('base64');
+        }
+        
     // URI encoded
     } else if (type === 'enc') {
 
diff --git a/package.json b/package.json
index 175f697..8b689f6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "svgo",
-  "version": "1.0.4",
+  "version": "1.0.5",
   "description": "Nodejs-based tool for optimizing SVG vector graphics files",
   "keywords": [
     "svgo",
diff --git a/plugins/_collections.js b/plugins/_collections.js
index 3348dcb..fab9287 100644
--- a/plugins/_collections.js
+++ b/plugins/_collections.js
@@ -2291,7 +2291,8 @@ exports.editorNamespaces = [
     'http://ns.adobe.com/Flows/1.0/',
     'http://ns.adobe.com/ImageReplacement/1.0/',
     'http://ns.adobe.com/GenericCustomNamespace/1.0/',
-    'http://ns.adobe.com/XPath/1.0/'
+    'http://ns.adobe.com/XPath/1.0/',
+    'http://schemas.microsoft.com/visio/2003/SVGExtensions/'
 ];
 
 // http://www.w3.org/TR/SVG/linking.html#processingIRI
diff --git a/plugins/_path.js b/plugins/_path.js
index 5222d35..47fe69d 100644
--- a/plugins/_path.js
+++ b/plugins/_path.js
@@ -208,16 +208,22 @@ exports.applyTransforms = function(elem, path, params) {
         if (scale !== 1) {
             var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth;
 
-            if (elem.hasAttr('stroke-width')) {
-                elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value.trim()
-                    .replace(regNumericValues, function(num) { return removeLeadingZero(num * scale) });
-            } else {
-                elem.addAttr({
-                    name: 'stroke-width',
-                    prefix: '',
-                    local: 'stroke-width',
-                    value: strokeWidth.replace(regNumericValues, function(num) { return removeLeadingZero(num * scale) })
-                });
+            if (!elem.hasAttr('vector-effect') || elem.attr('vector-effect').value !== 'non-scaling-stroke') {
+                if (elem.hasAttr('stroke-width')) {
+                    elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value.trim()
+                        .replace(regNumericValues, function(num) {
+                            return removeLeadingZero(num * scale);
+                        });
+                } else {
+                    elem.addAttr({
+                        name: 'stroke-width',
+                        prefix: '',
+                        local: 'stroke-width',
+                        value: strokeWidth.replace(regNumericValues, function(num) {
+                            return removeLeadingZero(num * scale);
+                        })
+                    });
+                }
             }
         }
     } else if (id) { // Stroke and stroke-width can be redefined with <use>
diff --git a/plugins/cleanupNumericValues.js b/plugins/cleanupNumericValues.js
index e91fdf2..f7c8a1e 100644
--- a/plugins/cleanupNumericValues.js
+++ b/plugins/cleanupNumericValues.js
@@ -48,6 +48,9 @@ exports.fn = function(item, params) {
         }
 
         item.eachAttr(function(attr) {
+            // The `version` attribute is a text string and cannot be rounded
+            if (attr.name === 'version') { return }
+            
             var match = attr.value.match(regNumericValues);
 
             // if attribute value matches regNumericValues
diff --git a/plugins/collapseGroups.js b/plugins/collapseGroups.js
index f0a1343..6e38651 100644
--- a/plugins/collapseGroups.js
+++ b/plugins/collapseGroups.js
@@ -63,6 +63,8 @@ exports.fn = function(item) {
                                 inner.addAttr(attr);
                             } else if (attr.name == 'transform') {
                                 inner.attr(attr.name).value = attr.value + ' ' + inner.attr(attr.name).value;
+                            } else if (inner.hasAttr(attr.name, 'inherit')) {
+                                inner.attr(attr.name).value = attr.value;
                             } else if (
                                 attrsInheritable.indexOf(attr.name) < 0 &&
                                 !inner.hasAttr(attr.name, attr.value)
diff --git a/plugins/convertPathData.js b/plugins/convertPathData.js
index fe8e475..789bb30 100644
--- a/plugins/convertPathData.js
+++ b/plugins/convertPathData.js
@@ -22,7 +22,8 @@ exports.params = {
     collapseRepeated: true,
     utilizeAbsolute: true,
     leadingZero: true,
-    negativeExtraSpace: true
+    negativeExtraSpace: true,
+    forceAbsolutePath: false
 };
 
 var pathElems = require('./_collections.js').pathElems,
@@ -671,11 +672,12 @@ function convertToMixed(path, params) {
         var absoluteDataStr = cleanupOutData(adata, params),
             relativeDataStr = cleanupOutData(data, params);
 
-        // Convert to absolute coordinates if it's shorter.
+        // Convert to absolute coordinates if it's shorter or forceAbsolutePath is true.
         // v-20 -> V0
         // Don't convert if it fits following previous instruction.
         // l20 30-10-50 instead of l20 30L20 30
         if (
+            params.forceAbsolutePath || (
             absoluteDataStr.length < relativeDataStr.length &&
             !(
                 params.negativeExtraSpace &&
@@ -683,7 +685,7 @@ function convertToMixed(path, params) {
                 prev.instruction.charCodeAt(0) > 96 &&
                 absoluteDataStr.length == relativeDataStr.length - 1 &&
                 (data[0] < 0 || /^0\./.test(data[0]) && prev.data[prev.data.length - 1] % 1)
-            )
+            ))
         ) {
             item.instruction = instruction.toUpperCase();
             item.data = adata;
diff --git a/plugins/prefixIds.js b/plugins/prefixIds.js
index ddda078..7d2b8c8 100644
--- a/plugins/prefixIds.js
+++ b/plugins/prefixIds.js
@@ -13,7 +13,7 @@ exports.description = 'prefix IDs';
 
 var path = require('path'),
     csstree = require('css-tree'),
-    cssRx = require('css-url-regex')(),
+    cssRx = require('css-url-regex'),
     unquote = require('unquote'),
     collections = require('./_collections.js'),
     referencesProps = collections.referencesProps,
@@ -37,7 +37,7 @@ var matchId = function(urlVal) {
 
 // Matches an url(...) value, captures the URL
 var matchUrl = function(val) {
-    var urlMatches = cssRx.exec(val);
+    var urlMatches = cssRx().exec(val);
     if (urlMatches === null) {
         return false;
     }
@@ -61,13 +61,13 @@ var prefixId = function(val) {
 
 // attr.value helper methods
 
-// prefixes a normal attribute value
-var addPrefixToAttr = function(attr) {
+// prefixes a class attribute value
+var addPrefixToClassAttr = function(attr) {
     if (!attrNotEmpty(attr)) {
         return;
     }
 
-    attr.value = addPrefix(attr.value);
+    attr.value = attr.value.split(/\s+/).map(addPrefix).join(' ');
 };
 
 // prefixes an ID attribute value
@@ -76,6 +76,15 @@ var addPrefixToIdAttr = function(attr) {
         return;
     }
 
+    attr.value = addPrefix(attr.value);
+};
+
+// prefixes a href attribute value
+var addPrefixToHrefAttr = function(attr) {
+    if (!attrNotEmpty(attr)) {
+        return;
+    }
+
     var idPrefixed = prefixId(attr.value);
     if (!idPrefixed) {
         return;
@@ -123,6 +132,8 @@ exports.fn = function(node, opts, extra) {
         } else {
             prefix = opts.prefix;
         }
+    } else if (opts.prefix === false) {
+        prefix = false;
     } else if (extra && extra.path && extra.path.length > 0) {
         var filename = path.basename(extra.path);
         prefix = filename;
@@ -131,6 +142,9 @@ exports.fn = function(node, opts, extra) {
 
     // prefixes a normal value
     addPrefix = function(name) {
+        if(prefix === false){
+            return escapeIdentifierName(name);
+        }
         return escapeIdentifierName(prefix + opts.delim + name);
     };
 
@@ -192,14 +206,16 @@ exports.fn = function(node, opts, extra) {
     }
 
     // ID
-    addPrefixToAttr(node.attrs.id);
+    addPrefixToIdAttr(node.attrs.id);
+
     // Class
-    addPrefixToAttr(node.attrs.class);
+    addPrefixToClassAttr(node.attrs.class);
 
     // href
-    addPrefixToIdAttr(node.attrs.href);
+    addPrefixToHrefAttr(node.attrs.href);
+
     // (xlink:)href (deprecated, must be still supported)
-    addPrefixToIdAttr(node.attrs['xlink:href']);
+    addPrefixToHrefAttr(node.attrs['xlink:href']);
 
     // referenceable properties
     for (var referencesProp of referencesProps) {
diff --git a/plugins/removeHiddenElems.js b/plugins/removeHiddenElems.js
index 231a2bd..7379125 100644
--- a/plugins/removeHiddenElems.js
+++ b/plugins/removeHiddenElems.js
@@ -7,6 +7,7 @@ exports.active = true;
 exports.description = 'removes hidden elements (zero sized, with absent attributes)';
 
 exports.params = {
+    isHidden: true,
     displayNone: true,
     opacity0: true,
     circleR0: true,
@@ -44,9 +45,15 @@ var regValidPath = /M\s*(?:[-+]?(?:\d*\.\d+|\d+(?:\.|(?!\.)))([eE][-+]?\d+)?(?!\
  *
  * @author Kir Belevich
  */
-exports.fn = function(item, params) {
+exports.fn = function (item, params) {
 
     if (item.elem) {
+        // Removes hidden elements
+        // https://www.w3schools.com/cssref/pr_class_visibility.asp
+        if (
+            params.isHidden &&
+            item.hasAttr('visibility', 'hidden')
+        ) return false;
 
         // display="none"
         //
diff --git a/plugins/sortAttrs.js b/plugins/sortAttrs.js
index 221a4bf..cba5c1d 100644
--- a/plugins/sortAttrs.js
+++ b/plugins/sortAttrs.js
@@ -30,7 +30,8 @@ exports.fn = function(item, params) {
 
 	var attrs = [],
 		sorted = {},
-		orderlen = params.order.length + 1;
+		orderlen = params.order.length + 1,
+		xmlnsOrder = params.xmlnsOrder || 'front';
 
 	if (item.elem) {
 
@@ -41,10 +42,12 @@ exports.fn = function(item, params) {
 		attrs.sort(function(a, b) {
 			if (a.prefix != b.prefix) {
 				// xmlns attributes implicitly have the prefix xmlns
-				if (a.prefix == 'xmlns')
-					return -1;
-				if (b.prefix == 'xmlns')
-					return 1;
+				if (xmlnsOrder == 'front') {
+                    if (a.prefix == 'xmlns')
+                        return -1;
+                    if (b.prefix == 'xmlns')
+                        return 1;
+                }
 				return a.prefix < b.prefix ? -1 : 1;
 			}
 


Test patch:
diff --git a/examples/test.js b/examples/test.js
index b6fc31a..e4f4fa4 100644
--- a/examples/test.js
+++ b/examples/test.js
@@ -34,7 +34,7 @@ var FS = require('fs'),
         },{
           removeViewBox: false,
         },{
-          cleanUpEnableBackground: true,
+          cleanupEnableBackground: true,
         },{
           convertStyleToAttrs: true,
         },{
@@ -69,8 +69,6 @@ var FS = require('fs'),
           convertShapeToPath: true,
         },{
           sortAttrs: true,
-        },{
-          transformsWithOnePath: false,
         },{
           removeDimensions: true,
         },{
diff --git a/test/plugins/collapseGroups.15.svg b/test/plugins/collapseGroups.15.svg
new file mode 100644
index 0000000..411020c
--- /dev/null
+++ b/test/plugins/collapseGroups.15.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+	<g color="red">
+		<g color="inherit" fill="none" stroke="none">
+			<circle cx="130" cy="80" r="60" fill="currentColor"/>
+			<circle cx="350" cy="80" r="60" stroke="currentColor" stroke-width="4"/>
+		</g>
+	</g>
+</svg>
+
+@@@
+
+<svg xmlns="http://www.w3.org/2000/svg">
+    <g color="red" fill="none" stroke="none">
+        <circle cx="130" cy="80" r="60" fill="currentColor"/>
+        <circle cx="350" cy="80" r="60" stroke="currentColor" stroke-width="4"/>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/test/plugins/convertPathData.19.svg b/test/plugins/convertPathData.19.svg
new file mode 100644
index 0000000..c57c8e2
--- /dev/null
+++ b/test/plugins/convertPathData.19.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
+    <path transform="translate(250, 250) scale(1.5, 1.5) translate(-250, -250)" fill="#7ED321" stroke="#000" stroke-width="15" vector-effect="non-scaling-stroke" d="M125 125h250v250h-250v-250z"/>
+</svg>
+
+@@@
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
+    <path fill="#7ED321" stroke="#000" stroke-width="15" vector-effect="non-scaling-stroke" d="M62.5 62.5h375v375h-375v-375z"/>
+</svg>
diff --git a/test/plugins/prefixIds.01.svg b/test/plugins/prefixIds.01.svg
index ea23b2f..e99c2ea 100644
--- a/test/plugins/prefixIds.01.svg
+++ b/test/plugins/prefixIds.01.svg
@@ -1,6 +1,6 @@
 <svg width="120" height="120" xmlns="http://www.w3.org/2000/svg">
     <style>
-        .test2 {
+        .test {
             color: blue;
         }
         #test {
@@ -16,7 +16,7 @@
 
 <svg width="120" height="120" xmlns="http://www.w3.org/2000/svg">
     <style>
-        .prefixIds_01_svg__test2{color:blue}#prefixIds_01_svg__test{color:red}
+        .prefixIds_01_svg__test{color:blue}#prefixIds_01_svg__test{color:red}
     </style>
     <rect class="prefixIds_01_svg__test" x="10" y="10" width="100" height="100"/>
     <rect class="" id="prefixIds_01_svg__test" x="10" y="10" width="100" height="100"/>
diff --git a/test/plugins/prefixIds.05.svg b/test/plugins/prefixIds.05.svg
new file mode 100644
index 0000000..d7805dd
--- /dev/null
+++ b/test/plugins/prefixIds.05.svg
@@ -0,0 +1,41 @@
+<svg width="340" height="120" xmlns="http://www.w3.org/2000/svg">
+    <defs>
+        <linearGradient id="gradient_1">
+            <stop offset="5%" stop-color="green"/>
+            <stop offset="95%" stop-color="gold"/>
+        </linearGradient>
+        <linearGradient id="gradient_2">
+            <stop offset="5%" stop-color="red"/>
+            <stop offset="95%" stop-color="black"/>
+        </linearGradient>
+        <linearGradient id="gradient_3">
+            <stop offset="5%" stop-color="blue"/>
+            <stop offset="95%" stop-color="orange"/>
+        </linearGradient>
+    </defs>
+    <rect fill="url(#gradient_1)" x="10" y="10" width="100" height="100"/>
+    <rect fill="url(#gradient_2)" x="120" y="10" width="100" height="100"/>
+    <rect fill="url(#gradient_3)" x="230" y="10" width="100" height="100"/>
+</svg>
+
+@@@
+
+<svg width="340" height="120" xmlns="http://www.w3.org/2000/svg">
+    <defs>
+        <linearGradient id="prefixIds_05_svg__gradient_1">
+            <stop offset="5%" stop-color="green"/>
+            <stop offset="95%" stop-color="gold"/>
+        </linearGradient>
+        <linearGradient id="prefixIds_05_svg__gradient_2">
+            <stop offset="5%" stop-color="red"/>
+            <stop offset="95%" stop-color="black"/>
+        </linearGradient>
+        <linearGradient id="prefixIds_05_svg__gradient_3">
+            <stop offset="5%" stop-color="blue"/>
+            <stop offset="95%" stop-color="orange"/>
+        </linearGradient>
+    </defs>
+    <rect fill="url(#prefixIds_05_svg__gradient_1)" x="10" y="10" width="100" height="100"/>
+    <rect fill="url(#prefixIds_05_svg__gradient_2)" x="120" y="10" width="100" height="100"/>
+    <rect fill="url(#prefixIds_05_svg__gradient_3)" x="230" y="10" width="100" height="100"/>
+</svg>
diff --git a/test/plugins/prefixIds.06.svg b/test/plugins/prefixIds.06.svg
new file mode 100644
index 0000000..9bd4f76
--- /dev/null
+++ b/test/plugins/prefixIds.06.svg
@@ -0,0 +1,38 @@
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg">
+    <style>
+        .test {
+            color: blue;
+        }
+        .test2 {
+            color: green;
+        }
+        #test {
+            color: red;
+        }
+        .test3 .test4 {
+            color: black;
+        }
+        .test5.test6 {
+            color: brown;
+        }
+        .test5.test6 #test7 {
+            color: yellow;
+        }
+    </style>
+    <rect class="test" x="10" y="10" width="100" height="100"/>
+    <rect class="test test2" x="10" y="10" width="100" height="100"/>
+    <rect class="test  test2" x="10" y="10" width="100" height="100"/>
+    <rect class="" id="test" x="10" y="10" width="100" height="100"/>
+</svg>
+
+@@@
+
+<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg">
+    <style>
+        .prefixIds_06_svg__test{color:blue}.prefixIds_06_svg__test2{color:green}#prefixIds_06_svg__test{color:red}.prefixIds_06_svg__test3 .prefixIds_06_svg__test4{color:black}.prefixIds_06_svg__test5.prefixIds_06_svg__test6{color:brown}.prefixIds_06_svg__test5.prefixIds_06_svg__test6 #prefixIds_06_svg__test7{color:yellow}
+    </style>
+    <rect class="prefixIds_06_svg__test" x="10" y="10" width="100" height="100"/>
+    <rect class="prefixIds_06_svg__test prefixIds_06_svg__test2" x="10" y="10" width="100" height="100"/>
+    <rect class="prefixIds_06_svg__test prefixIds_06_svg__test2" x="10" y="10" width="100" height="100"/>
+    <rect class="" id="prefixIds_06_svg__test" x="10" y="10" width="100" height="100"/>
+</svg>
diff --git a/test/plugins/sortAttrs.02.svg b/test/plugins/sortAttrs.02.svg
new file mode 100644
index 0000000..3237551
--- /dev/null
+++ b/test/plugins/sortAttrs.02.svg
@@ -0,0 +1,15 @@
+<svg foo="bar" xmlns="http://www.w3.org/2000/svg" height="10" baz="quux" width="10" hello="world">
+    <rect x="0" y="0" width="100" height="100" stroke-width="1" stroke-linejoin="round" fill="red" stroke="orange" xmlns="http://www.w3.org/2000/svg"/>
+    test
+</svg>
+
+@@@
+
+<svg width="10" height="10" baz="quux" foo="bar" hello="world" xmlns="http://www.w3.org/2000/svg">
+    <rect width="100" height="100" x="0" y="0" fill="red" stroke="orange" stroke-linejoin="round" stroke-width="1" xmlns="http://www.w3.org/2000/svg"/>
+    test
+</svg>
+
+@@@
+
+{ "xmlnsOrder": "alphabetical" }
diff --git a/test/svg2js/_index.js b/test/svg2js/_index.js
index 4a791bc..7528d34 100644
--- a/test/svg2js/_index.js
+++ b/test/svg2js/_index.js
@@ -3,6 +3,15 @@
 var SHOULD = require('should'),
     FS = require('fs'),
     PATH = require('path'),
+    JSAPI = require(process.env.COVERAGE ?
+                     '../../lib-cov/svgo/jsAPI' :
+                     '../../lib/svgo/jsAPI'),
+    CSSClassList = require(process.env.COVERAGE ?
+                     '../../lib-cov/svgo/css-class-list' :
+                     '../../lib/svgo/css-class-list'),
+    CSSStyleDeclaration = require(process.env.COVERAGE ?
+                     '../../lib-cov/svgo/css-style-declaration' :
+                     '../../lib/svgo/css-style-declaration'),
     SVG2JS = require(process.env.COVERAGE ?
                      '../../lib-cov/svgo/svg2js' :
                      '../../lib/svgo/svg2js');
@@ -179,6 +188,26 @@ describe('svg2js', function() {
 
         describe('API', function() {
 
+            describe('clone()', function() {
+
+                it('svg should have property "clone"', function() {
+                    return root.content[3].should.have.property('clone');
+                });
+
+                it('svg.clone() should be an instance of JSAPI', function() {
+                    return root.content[3].clone().should.be.instanceOf(JSAPI);
+                });
+
+                it('root.content[3].content[0].clone() has a valid style property', function() {
+                    return root.content[3].content[0].clone().style.should.be.instanceof(CSSStyleDeclaration);
+                });
+
+                it('root.content[3].content[2].clone() has a valid class property', function() {
+                    return root.content[3].content[2].clone().class.should.be.instanceof(CSSClassList);
+                });
+
+            });
+
             describe('isElem()', function() {
 
                 it('svg should have property "isElem"', function() {
diff --git a/test/svg2js/test.svg b/test/svg2js/test.svg
index 4069162..f8e741d 100644
--- a/test/svg2js/test.svg
+++ b/test/svg2js/test.svg
@@ -15,5 +15,5 @@
             <text>test</text>
         </g>
     </g>
-    <g style="color: black"></g>
+    <g style="color: black" class="unknown-class"></g>
 </svg>