From c648add9631b999db953c666aeb83a54140252be Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Jan 2021 19:11:22 +0100
Subject: [PATCH] :tada: Add svgclean (svgo graalvm/browser ready fork).

---
 vendor/svgclean/.gitignore                    |   11 +
 vendor/svgclean/LICENSE                       |   25 +
 vendor/svgclean/examples/test.js              |  101 +
 vendor/svgclean/examples/test.svg             |    3 +
 vendor/svgclean/main.js                       |   43 +
 vendor/svgclean/package-lock.json             | 3488 +++++++++++++++++
 vendor/svgclean/package.json                  |   34 +
 vendor/svgclean/src/svgclean.js               |   59 +
 vendor/svgclean/src/svgclean/config.js        |  269 ++
 .../svgclean/src/svgclean/css-class-list.js   |  138 +
 .../src/svgclean/css-select-adapter.js        |   53 +
 .../src/svgclean/css-style-declaration.js     |  285 ++
 vendor/svgclean/src/svgclean/css-tools.js     |  222 ++
 vendor/svgclean/src/svgclean/js2svg.js        |  346 ++
 vendor/svgclean/src/svgclean/jsAPI.js         |  372 ++
 .../src/svgclean/plugins/_collections.js      | 2558 ++++++++++++
 vendor/svgclean/src/svgclean/plugins/_path.js |  988 +++++
 .../src/svgclean/plugins/_transforms.js       |  310 ++
 .../plugins/addAttributesToSVGElement.js      |   82 +
 .../plugins/addClassesToSVGElement.js         |   50 +
 .../src/svgclean/plugins/cleanupAttrs.js      |   56 +
 .../plugins/cleanupEnableBackground.js        |   84 +
 .../src/svgclean/plugins/cleanupIDs.js        |  212 +
 .../svgclean/plugins/cleanupListOfValues.js   |  139 +
 .../svgclean/plugins/cleanupNumericValues.js  |   88 +
 .../src/svgclean/plugins/collapseGroups.js    |   87 +
 .../src/svgclean/plugins/convertColors.js     |  130 +
 .../plugins/convertEllipseToCircle.js         |   39 +
 .../src/svgclean/plugins/convertPathData.js   |  971 +++++
 .../svgclean/plugins/convertShapeToPath.js    |  149 +
 .../svgclean/plugins/convertStyleToAttrs.js   |  125 +
 .../src/svgclean/plugins/convertTransform.js  |  363 ++
 .../src/svgclean/plugins/inlineStyles.js      |  245 ++
 .../src/svgclean/plugins/mergePaths.js        |   73 +
 .../src/svgclean/plugins/minifyStyles.js      |  160 +
 .../svgclean/plugins/moveElemsAttrsToGroup.js |  126 +
 .../svgclean/plugins/moveGroupAttrsToElems.js |   63 +
 .../src/svgclean/plugins/prefixIds.js         |  274 ++
 .../plugins/removeAttributesBySelector.js     |   70 +
 .../src/svgclean/plugins/removeAttrs.js       |  150 +
 .../src/svgclean/plugins/removeComments.js    |   27 +
 .../src/svgclean/plugins/removeDesc.js        |   32 +
 .../src/svgclean/plugins/removeDimensions.js  |   49 +
 .../src/svgclean/plugins/removeDoctype.js     |   40 +
 .../svgclean/plugins/removeEditorsNSData.js   |   65 +
 .../svgclean/plugins/removeElementsByAttr.js  |   80 +
 .../src/svgclean/plugins/removeEmptyAttrs.js  |   29 +
 .../svgclean/plugins/removeEmptyContainers.js |   32 +
 .../src/svgclean/plugins/removeEmptyText.js   |   59 +
 .../src/svgclean/plugins/removeHiddenElems.js |  225 ++
 .../src/svgclean/plugins/removeMetadata.js    |   23 +
 .../plugins/removeNonInheritableGroupAttrs.js |   37 +
 .../svgclean/plugins/removeOffCanvasPaths.js  |  134 +
 .../svgclean/plugins/removeRasterImages.js    |   28 +
 .../svgclean/plugins/removeScriptElement.js   |   23 +
 .../svgclean/plugins/removeStyleElement.js    |   23 +
 .../src/svgclean/plugins/removeTitle.js       |   23 +
 .../plugins/removeUnknownsAndDefaults.js      |  150 +
 .../src/svgclean/plugins/removeUnusedNS.js    |  109 +
 .../src/svgclean/plugins/removeUselessDefs.js |   53 +
 .../plugins/removeUselessStrokeAndFill.js     |  100 +
 .../src/svgclean/plugins/removeViewBox.js     |   48 +
 .../src/svgclean/plugins/removeXMLNS.js       |   28 +
 .../src/svgclean/plugins/removeXMLProcInst.js |   24 +
 .../src/svgclean/plugins/reusePaths.js        |  169 +
 .../src/svgclean/plugins/sortAttrs.js         |   84 +
 .../src/svgclean/plugins/sortDefsChildren.js  |   47 +
 vendor/svgclean/src/svgclean/svg2js.js        |  190 +
 vendor/svgclean/src/svgclean/tools.js         |  152 +
 vendor/svgclean/test.js                       |    8 +
 vendor/svgclean/test.svg                      |    3 +
 71 files changed, 15135 insertions(+)
 create mode 100644 vendor/svgclean/.gitignore
 create mode 100644 vendor/svgclean/LICENSE
 create mode 100644 vendor/svgclean/examples/test.js
 create mode 100644 vendor/svgclean/examples/test.svg
 create mode 100644 vendor/svgclean/main.js
 create mode 100644 vendor/svgclean/package-lock.json
 create mode 100644 vendor/svgclean/package.json
 create mode 100755 vendor/svgclean/src/svgclean.js
 create mode 100644 vendor/svgclean/src/svgclean/config.js
 create mode 100644 vendor/svgclean/src/svgclean/css-class-list.js
 create mode 100644 vendor/svgclean/src/svgclean/css-select-adapter.js
 create mode 100644 vendor/svgclean/src/svgclean/css-style-declaration.js
 create mode 100644 vendor/svgclean/src/svgclean/css-tools.js
 create mode 100644 vendor/svgclean/src/svgclean/js2svg.js
 create mode 100644 vendor/svgclean/src/svgclean/jsAPI.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/_collections.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/_path.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/_transforms.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/addAttributesToSVGElement.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/addClassesToSVGElement.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/cleanupAttrs.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/cleanupEnableBackground.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/cleanupIDs.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/cleanupListOfValues.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/cleanupNumericValues.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/collapseGroups.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/convertColors.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/convertEllipseToCircle.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/convertPathData.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/convertShapeToPath.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/convertStyleToAttrs.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/convertTransform.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/inlineStyles.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/mergePaths.js
 create mode 100755 vendor/svgclean/src/svgclean/plugins/minifyStyles.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/moveElemsAttrsToGroup.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/moveGroupAttrsToElems.js
 create mode 100755 vendor/svgclean/src/svgclean/plugins/prefixIds.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeAttributesBySelector.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeAttrs.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeComments.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeDesc.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeDimensions.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeDoctype.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeEditorsNSData.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeElementsByAttr.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeEmptyAttrs.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeEmptyContainers.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeEmptyText.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeHiddenElems.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeMetadata.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeNonInheritableGroupAttrs.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeOffCanvasPaths.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeRasterImages.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeScriptElement.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeStyleElement.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeTitle.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeUnknownsAndDefaults.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeUnusedNS.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeUselessDefs.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeUselessStrokeAndFill.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeViewBox.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeXMLNS.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/removeXMLProcInst.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/reusePaths.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/sortAttrs.js
 create mode 100644 vendor/svgclean/src/svgclean/plugins/sortDefsChildren.js
 create mode 100644 vendor/svgclean/src/svgclean/svg2js.js
 create mode 100644 vendor/svgclean/src/svgclean/tools.js
 create mode 100644 vendor/svgclean/test.js
 create mode 100644 vendor/svgclean/test.svg

diff --git a/vendor/svgclean/.gitignore b/vendor/svgclean/.gitignore
new file mode 100644
index 000000000..285888763
--- /dev/null
+++ b/vendor/svgclean/.gitignore
@@ -0,0 +1,11 @@
+node_modules/
+lib-cov/
+html-report/
+lcov.info
+bin/svgo-profiling
+*.sublime-*
+*.log
+.DS_Store
+.idea
+.vscode
+.npmrc
diff --git a/vendor/svgclean/LICENSE b/vendor/svgclean/LICENSE
new file mode 100644
index 000000000..10ba902be
--- /dev/null
+++ b/vendor/svgclean/LICENSE
@@ -0,0 +1,25 @@
+The MIT License
+
+Copyright © 2021 Andrey Antukh <niwi@niwi.nz>
+Copyright © 2012–2016 Kir Belevich
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/svgclean/examples/test.js b/vendor/svgclean/examples/test.js
new file mode 100644
index 000000000..e4f4fa43b
--- /dev/null
+++ b/vendor/svgclean/examples/test.js
@@ -0,0 +1,101 @@
+'use strict';
+
+var FS = require('fs'),
+    PATH = require('path'),
+    SVGO = require('../lib/svgo'),
+    filepath = PATH.resolve(__dirname, 'test.svg'),
+    svgo = new SVGO({
+        plugins: [{
+          cleanupAttrs: true,
+        }, {
+          removeDoctype: true,
+        },{
+          removeXMLProcInst: true,
+        },{
+          removeComments: true,
+        },{
+          removeMetadata: true,
+        },{
+          removeTitle: true,
+        },{
+          removeDesc: true,
+        },{
+          removeUselessDefs: true,
+        },{
+          removeEditorsNSData: true,
+        },{
+          removeEmptyAttrs: true,
+        },{
+          removeHiddenElems: true,
+        },{
+          removeEmptyText: true,
+        },{
+          removeEmptyContainers: true,
+        },{
+          removeViewBox: false,
+        },{
+          cleanupEnableBackground: true,
+        },{
+          convertStyleToAttrs: true,
+        },{
+          convertColors: true,
+        },{
+          convertPathData: true,
+        },{
+          convertTransform: true,
+        },{
+          removeUnknownsAndDefaults: true,
+        },{
+          removeNonInheritableGroupAttrs: true,
+        },{
+          removeUselessStrokeAndFill: true,
+        },{
+          removeUnusedNS: true,
+        },{
+          cleanupIDs: true,
+        },{
+          cleanupNumericValues: true,
+        },{
+          moveElemsAttrsToGroup: true,
+        },{
+          moveGroupAttrsToElems: true,
+        },{
+          collapseGroups: true,
+        },{
+          removeRasterImages: false,
+        },{
+          mergePaths: true,
+        },{
+          convertShapeToPath: true,
+        },{
+          sortAttrs: true,
+        },{
+          removeDimensions: true,
+        },{
+          removeAttrs: {attrs: '(stroke|fill)'},
+        }]
+      });
+
+FS.readFile(filepath, 'utf8', function(err, data) {
+
+    if (err) {
+        throw err;
+    }
+
+    svgo.optimize(data, {path: filepath}).then(function(result) {
+
+        console.log(result);
+
+        // {
+        //     // optimized SVG data string
+        //     data: '<svg width="10" height="20">test</svg>'
+        //     // additional info such as width/height
+        //     info: {
+        //         width: '10',
+        //         height: '20'
+        //     }
+        // }
+
+    });
+
+});
diff --git a/vendor/svgclean/examples/test.svg b/vendor/svgclean/examples/test.svg
new file mode 100644
index 000000000..c539cd408
--- /dev/null
+++ b/vendor/svgclean/examples/test.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" width="10" height="20">
+    test
+</svg>
diff --git a/vendor/svgclean/main.js b/vendor/svgclean/main.js
new file mode 100644
index 000000000..48a9198b0
--- /dev/null
+++ b/vendor/svgclean/main.js
@@ -0,0 +1,43 @@
+const plugins = {
+  prefixIds: true,
+  cleanupAttrs: true,
+  cleanupEnableBackground: true,
+  cleanupIDs: true,
+  cleanupNumericValues: true,
+  collapseGroups: true,
+  convertColors: true,
+  convertEllipseToCircle: true,
+  convertPathData: true,
+  convertShapeToPath: true,
+  convertStyleToAttrs: true,
+  convertTransform: true,
+  inlineStyles: true,
+  mergePaths: false,
+  minifyStyles: true,
+  removeComments: true,
+  removeDesc: true,
+  removeDimensions: false,
+  removeDoctype: true,
+  removeEditorsNSData: true,
+  removeEmptyAttrs: true,
+  removeEmptyContainers: true,
+  removeEmptyText: true,
+  removeHiddenElems: true,
+  removeNonInheritableGroupAttrs: true,
+  removeRasterImages: true,
+  removeTitle: true,
+  removeUnknownsAndDefaults: true,
+  removeUnusedNS: true,
+  removeUselessDefs: true,
+  removeUselessStrokeAndFill: true,
+  removeXMLNS: true,
+  removeXMLProcInst: true
+};
+
+const svgc = require("./src/svgclean.js");
+const inst = svgc.configure({plugins});
+
+exports.optimize = function(data) {
+  return svgc.optimize(inst, data)
+    .then((result) => result.data);
+};
diff --git a/vendor/svgclean/package-lock.json b/vendor/svgclean/package-lock.json
new file mode 100644
index 000000000..54f08f180
--- /dev/null
+++ b/vendor/svgclean/package-lock.json
@@ -0,0 +1,3488 @@
+{
+  "name": "svgclean",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@ungap/promise-all-settled": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
+      "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
+      "dev": true
+    },
+    "JSONStream": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+      "requires": {
+        "jsonparse": "^1.2.0",
+        "through": ">=2.2.7 <3"
+      }
+    },
+    "abbrev": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz",
+      "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=",
+      "dev": true
+    },
+    "acorn": {
+      "version": "7.4.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
+    },
+    "acorn-node": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+      "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+      "requires": {
+        "acorn": "^7.0.0",
+        "acorn-walk": "^7.0.0",
+        "xtend": "^4.0.2"
+      }
+    },
+    "acorn-walk": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+      "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
+    },
+    "ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "amdefine": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+      "dev": true,
+      "optional": true
+    },
+    "ansi-colors": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+      "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+      "dev": true
+    },
+    "ansi-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+      "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "requires": {
+        "color-convert": "^2.0.1"
+      }
+    },
+    "anymatch": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+      "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+      "dev": true,
+      "requires": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      }
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dev": true,
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "array-filter": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz",
+      "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM="
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "asn1.js": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+      "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+      "requires": {
+        "bn.js": "^4.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "safer-buffer": "^2.1.0"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
+        }
+      }
+    },
+    "assert": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+      "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+      "requires": {
+        "object-assign": "^4.1.1",
+        "util": "0.10.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
+        },
+        "util": {
+          "version": "0.10.3",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+          "requires": {
+            "inherits": "2.0.1"
+          }
+        }
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+      "dev": true
+    },
+    "async": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+      "dev": true
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+      "dev": true
+    },
+    "at-least-node": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+      "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+      "dev": true
+    },
+    "available-typed-arrays": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz",
+      "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==",
+      "requires": {
+        "array-filter": "^1.0.0"
+      }
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+      "dev": true
+    },
+    "aws4": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+      "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+    },
+    "base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "dev": true,
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "binary-extensions": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
+      "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+      "dev": true
+    },
+    "bn.js": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
+      "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ=="
+    },
+    "boolbase": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+      "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "requires": {
+        "fill-range": "^7.0.1"
+      }
+    },
+    "brorand": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
+    },
+    "browser-pack": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
+      "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==",
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "combine-source-map": "~0.8.0",
+        "defined": "^1.0.0",
+        "safe-buffer": "^5.1.1",
+        "through2": "^2.0.0",
+        "umd": "^3.0.0"
+      }
+    },
+    "browser-resolve": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz",
+      "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==",
+      "requires": {
+        "resolve": "^1.17.0"
+      },
+      "dependencies": {
+        "resolve": {
+          "version": "1.19.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
+          "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
+          "requires": {
+            "is-core-module": "^2.1.0",
+            "path-parse": "^1.0.6"
+          }
+        }
+      }
+    },
+    "browser-stdout": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+      "dev": true
+    },
+    "browserify": {
+      "version": "17.0.0",
+      "resolved": "https://registry.npmjs.org/browserify/-/browserify-17.0.0.tgz",
+      "integrity": "sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w==",
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "assert": "^1.4.0",
+        "browser-pack": "^6.0.1",
+        "browser-resolve": "^2.0.0",
+        "browserify-zlib": "~0.2.0",
+        "buffer": "~5.2.1",
+        "cached-path-relative": "^1.0.0",
+        "concat-stream": "^1.6.0",
+        "console-browserify": "^1.1.0",
+        "constants-browserify": "~1.0.0",
+        "crypto-browserify": "^3.0.0",
+        "defined": "^1.0.0",
+        "deps-sort": "^2.0.1",
+        "domain-browser": "^1.2.0",
+        "duplexer2": "~0.1.2",
+        "events": "^3.0.0",
+        "glob": "^7.1.0",
+        "has": "^1.0.0",
+        "htmlescape": "^1.1.0",
+        "https-browserify": "^1.0.0",
+        "inherits": "~2.0.1",
+        "insert-module-globals": "^7.2.1",
+        "labeled-stream-splicer": "^2.0.0",
+        "mkdirp-classic": "^0.5.2",
+        "module-deps": "^6.2.3",
+        "os-browserify": "~0.3.0",
+        "parents": "^1.0.1",
+        "path-browserify": "^1.0.0",
+        "process": "~0.11.0",
+        "punycode": "^1.3.2",
+        "querystring-es3": "~0.2.0",
+        "read-only-stream": "^2.0.0",
+        "readable-stream": "^2.0.2",
+        "resolve": "^1.1.4",
+        "shasum-object": "^1.0.0",
+        "shell-quote": "^1.6.1",
+        "stream-browserify": "^3.0.0",
+        "stream-http": "^3.0.0",
+        "string_decoder": "^1.1.1",
+        "subarg": "^1.0.0",
+        "syntax-error": "^1.1.1",
+        "through2": "^2.0.0",
+        "timers-browserify": "^1.0.1",
+        "tty-browserify": "0.0.1",
+        "url": "~0.11.0",
+        "util": "~0.12.0",
+        "vm-browserify": "^1.0.0",
+        "xtend": "^4.0.0"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "7.1.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+          "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          },
+          "dependencies": {
+            "string_decoder": {
+              "version": "1.1.1",
+              "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+              "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+              "requires": {
+                "safe-buffer": "~5.1.0"
+              }
+            }
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "string_decoder": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+          "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+          "requires": {
+            "safe-buffer": "~5.2.0"
+          },
+          "dependencies": {
+            "safe-buffer": {
+              "version": "5.2.1",
+              "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+              "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+            }
+          }
+        }
+      }
+    },
+    "browserify-aes": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "requires": {
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "browserify-cipher": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+      "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+      "requires": {
+        "browserify-aes": "^1.0.4",
+        "browserify-des": "^1.0.0",
+        "evp_bytestokey": "^1.0.0"
+      }
+    },
+    "browserify-des": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+      "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "des.js": "^1.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "browserify-rsa": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
+      "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
+      "requires": {
+        "bn.js": "^5.0.0",
+        "randombytes": "^2.0.1"
+      }
+    },
+    "browserify-sign": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
+      "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
+      "requires": {
+        "bn.js": "^5.1.1",
+        "browserify-rsa": "^4.0.1",
+        "create-hash": "^1.2.0",
+        "create-hmac": "^1.1.7",
+        "elliptic": "^6.5.3",
+        "inherits": "^2.0.4",
+        "parse-asn1": "^5.1.5",
+        "readable-stream": "^3.6.0",
+        "safe-buffer": "^5.2.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+          "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+          "requires": {
+            "safe-buffer": "~5.2.0"
+          }
+        }
+      }
+    },
+    "browserify-zlib": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+      "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+      "requires": {
+        "pako": "~1.0.5"
+      }
+    },
+    "buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz",
+      "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==",
+      "requires": {
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4"
+      }
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+    },
+    "buffer-xor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+    },
+    "builtin-status-codes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug="
+    },
+    "cached-path-relative": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz",
+      "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg=="
+    },
+    "call-bind": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.1.tgz",
+      "integrity": "sha512-tvAvUwNcRikl3RVF20X9lsYmmepsovzTWeJiXjO0PkJp15uy/6xKFZOQtuiSULwYW+6ToZBprphCgWXC2dSgcQ==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "get-intrinsic": "^1.0.2"
+      }
+    },
+    "camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "dev": true
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+      "dev": true
+    },
+    "chalk": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+      "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+      "requires": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      }
+    },
+    "chokidar": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
+      "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
+      "dev": true,
+      "requires": {
+        "anymatch": "~3.1.1",
+        "braces": "~3.0.2",
+        "fsevents": "~2.1.2",
+        "glob-parent": "~5.1.0",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.5.0"
+      }
+    },
+    "cipher-base": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "cli": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
+      "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=",
+      "dev": true,
+      "requires": {
+        "exit": "0.1.2",
+        "glob": "^7.1.1"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "7.1.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+          "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        }
+      }
+    },
+    "cliui": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+      "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^3.1.0",
+        "strip-ansi": "^5.2.0",
+        "wrap-ansi": "^5.1.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "requires": {
+        "color-name": "~1.1.4"
+      }
+    },
+    "color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
+    "combine-source-map": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
+      "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=",
+      "requires": {
+        "convert-source-map": "~1.1.0",
+        "inline-source-map": "~0.6.0",
+        "lodash.memoize": "~3.0.3",
+        "source-map": "~0.5.3"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dev": true,
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "console-browserify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+      "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+      "requires": {
+        "date-now": "^0.1.4"
+      }
+    },
+    "constants-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+      "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U="
+    },
+    "convert-source-map": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
+      "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA="
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "coveralls": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz",
+      "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==",
+      "dev": true,
+      "requires": {
+        "js-yaml": "^3.13.1",
+        "lcov-parse": "^1.0.0",
+        "log-driver": "^1.2.7",
+        "minimist": "^1.2.5",
+        "request": "^2.88.2"
+      }
+    },
+    "create-ecdh": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
+      "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
+      "requires": {
+        "bn.js": "^4.1.0",
+        "elliptic": "^6.5.3"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
+        }
+      }
+    },
+    "create-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "md5.js": "^1.3.4",
+        "ripemd160": "^2.0.1",
+        "sha.js": "^2.4.0"
+      }
+    },
+    "create-hmac": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "requires": {
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "crypto-browserify": {
+      "version": "3.12.0",
+      "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+      "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+      "requires": {
+        "browserify-cipher": "^1.0.0",
+        "browserify-sign": "^4.0.0",
+        "create-ecdh": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.0",
+        "diffie-hellman": "^5.0.0",
+        "inherits": "^2.0.1",
+        "pbkdf2": "^3.0.3",
+        "public-encrypt": "^4.0.0",
+        "randombytes": "^2.0.0",
+        "randomfill": "^1.0.3"
+      }
+    },
+    "css-select": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz",
+      "integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==",
+      "requires": {
+        "boolbase": "^1.0.0",
+        "css-what": "^4.0.0",
+        "domhandler": "^4.0.0",
+        "domutils": "^2.4.3",
+        "nth-check": "^2.0.0"
+      }
+    },
+    "css-select-base-adapter": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
+      "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
+    },
+    "css-tree": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz",
+      "integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==",
+      "requires": {
+        "mdn-data": "2.0.14",
+        "source-map": "^0.6.1"
+      }
+    },
+    "css-what": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz",
+      "integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A=="
+    },
+    "csso": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz",
+      "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==",
+      "requires": {
+        "css-tree": "^1.1.2"
+      }
+    },
+    "dash-ast": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz",
+      "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA=="
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "date-now": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+      "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs="
+    },
+    "debug": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
+      "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
+      "dev": true,
+      "requires": {
+        "ms": "2.1.2"
+      }
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "dev": true
+    },
+    "deep-is": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+      "dev": true
+    },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
+    "defined": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+      "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "dev": true
+    },
+    "deps-sort": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz",
+      "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==",
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "shasum-object": "^1.0.0",
+        "subarg": "^1.0.0",
+        "through2": "^2.0.0"
+      }
+    },
+    "des.js": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+      "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "detective": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
+      "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
+      "requires": {
+        "acorn-node": "^1.6.1",
+        "defined": "^1.0.0",
+        "minimist": "^1.1.1"
+      }
+    },
+    "diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true
+    },
+    "diffie-hellman": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+      "requires": {
+        "bn.js": "^4.1.0",
+        "miller-rabin": "^4.0.0",
+        "randombytes": "^2.0.0"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
+        }
+      }
+    },
+    "dom-serializer": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
+      "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
+      "requires": {
+        "domelementtype": "^2.0.1",
+        "domhandler": "^4.0.0",
+        "entities": "^2.0.0"
+      }
+    },
+    "domain-browser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA=="
+    },
+    "domelementtype": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
+      "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w=="
+    },
+    "domhandler": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
+      "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
+      "requires": {
+        "domelementtype": "^2.1.0"
+      }
+    },
+    "domutils": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",
+      "integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==",
+      "requires": {
+        "dom-serializer": "^1.0.1",
+        "domelementtype": "^2.0.1",
+        "domhandler": "^4.0.0"
+      }
+    },
+    "duplexer2": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+      "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
+      "requires": {
+        "readable-stream": "^2.0.2"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "dev": true,
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "elliptic": {
+      "version": "6.5.3",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+      "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
+      "requires": {
+        "bn.js": "^4.4.0",
+        "brorand": "^1.0.1",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.0"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
+        }
+      }
+    },
+    "emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
+    "entities": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
+      "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
+    },
+    "es-abstract": {
+      "version": "1.18.0-next.1",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz",
+      "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==",
+      "requires": {
+        "es-to-primitive": "^1.2.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1",
+        "is-callable": "^1.2.2",
+        "is-negative-zero": "^2.0.0",
+        "is-regex": "^1.1.1",
+        "object-inspect": "^1.8.0",
+        "object-keys": "^1.1.1",
+        "object.assign": "^4.1.1",
+        "string.prototype.trimend": "^1.0.1",
+        "string.prototype.trimstart": "^1.0.1"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true
+    },
+    "escodegen": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
+      "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
+      "dev": true,
+      "requires": {
+        "esprima": "^2.7.1",
+        "estraverse": "^1.9.1",
+        "esutils": "^2.0.2",
+        "optionator": "^0.8.1",
+        "source-map": "~0.2.0"
+      },
+      "dependencies": {
+        "esprima": {
+          "version": "2.7.3",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+          "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+          "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "amdefine": ">=0.0.4"
+          }
+        }
+      }
+    },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "dev": true
+    },
+    "estraverse": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+      "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "events": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
+      "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg=="
+    },
+    "evp_bytestokey": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "requires": {
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "exit": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+      "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+      "dev": true
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+      "dev": true
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+      "dev": true
+    },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+      "dev": true
+    },
+    "fast-safe-stringify": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
+      "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
+    },
+    "fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "requires": {
+        "to-regex-range": "^5.0.1"
+      }
+    },
+    "find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "requires": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      }
+    },
+    "flat": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+      "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+      "dev": true
+    },
+    "for-each": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+      "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+      "requires": {
+        "is-callable": "^1.1.3"
+      }
+    },
+    "foreach": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
+      "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+      "dev": true
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "dev": true,
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "fs-extra": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
+      "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
+      "dev": true,
+      "requires": {
+        "at-least-node": "^1.0.0",
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^1.0.0"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "fsevents": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+      "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+      "dev": true,
+      "optional": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "get-assigned-identifiers": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
+      "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ=="
+    },
+    "get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "dev": true
+    },
+    "get-intrinsic": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz",
+      "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==",
+      "requires": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "glob": {
+      "version": "5.0.15",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+      "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+      "dev": true,
+      "requires": {
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "2 || 3",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+      "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+      "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+      "dev": true
+    },
+    "growl": {
+      "version": "1.10.5",
+      "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+      "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+      "dev": true
+    },
+    "handlebars": {
+      "version": "4.7.6",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz",
+      "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.5",
+        "neo-async": "^2.6.0",
+        "source-map": "^0.6.1",
+        "uglify-js": "^3.1.4",
+        "wordwrap": "^1.0.0"
+      }
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+      "dev": true
+    },
+    "har-validator": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+      "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.12.3",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+    },
+    "has-symbols": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
+    },
+    "hash-base": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+      "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+      "requires": {
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.6.0",
+        "safe-buffer": "^5.2.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+          "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+          "requires": {
+            "safe-buffer": "~5.2.0"
+          }
+        }
+      }
+    },
+    "hash.js": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "requires": {
+        "inherits": "^2.0.3",
+        "minimalistic-assert": "^1.0.1"
+      }
+    },
+    "he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true
+    },
+    "hmac-drbg": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "requires": {
+        "hash.js": "^1.0.3",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "htmlescape": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
+      "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E="
+    },
+    "htmlparser2": {
+      "version": "3.8.3",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
+      "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
+      "dev": true,
+      "requires": {
+        "domelementtype": "1",
+        "domhandler": "2.3",
+        "domutils": "1.5",
+        "entities": "1.0",
+        "readable-stream": "1.1"
+      },
+      "dependencies": {
+        "dom-serializer": {
+          "version": "0.2.2",
+          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
+          "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
+          "dev": true,
+          "requires": {
+            "domelementtype": "^2.0.1",
+            "entities": "^2.0.0"
+          },
+          "dependencies": {
+            "domelementtype": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
+              "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==",
+              "dev": true
+            },
+            "entities": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
+              "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
+              "dev": true
+            }
+          }
+        },
+        "domelementtype": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+          "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
+          "dev": true
+        },
+        "domhandler": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
+          "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
+          "dev": true,
+          "requires": {
+            "domelementtype": "1"
+          }
+        },
+        "domutils": {
+          "version": "1.5.1",
+          "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+          "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+          "dev": true,
+          "requires": {
+            "dom-serializer": "0",
+            "domelementtype": "1"
+          }
+        },
+        "entities": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+          "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=",
+          "dev": true
+        }
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "https-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
+    },
+    "ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "inline-source-map": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
+      "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
+      "requires": {
+        "source-map": "~0.5.3"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "insert-module-globals": {
+      "version": "7.2.1",
+      "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz",
+      "integrity": "sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==",
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "acorn-node": "^1.5.2",
+        "combine-source-map": "^0.8.0",
+        "concat-stream": "^1.6.1",
+        "is-buffer": "^1.1.0",
+        "path-is-absolute": "^1.0.1",
+        "process": "~0.11.0",
+        "through2": "^2.0.0",
+        "undeclared-identifiers": "^1.1.2",
+        "xtend": "^4.0.0"
+      }
+    },
+    "is-arguments": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz",
+      "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==",
+      "requires": {
+        "call-bind": "^1.0.0"
+      }
+    },
+    "is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "requires": {
+        "binary-extensions": "^2.0.0"
+      }
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+    },
+    "is-callable": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz",
+      "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA=="
+    },
+    "is-core-module": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
+      "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "is-date-object": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
+      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true
+    },
+    "is-generator-function": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz",
+      "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ=="
+    },
+    "is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-negative-zero": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
+      "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w=="
+    },
+    "is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true
+    },
+    "is-plain-obj": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+      "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+      "dev": true
+    },
+    "is-regex": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+      "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+      "requires": {
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "is-symbol": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
+      "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
+      "requires": {
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "is-typed-array": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.4.tgz",
+      "integrity": "sha512-ILaRgn4zaSrVNXNGtON6iFNotXW3hAPF3+0fB1usg2jFlWqo5fEDdmJkz0zBfoi7Dgskr8Khi2xZ8cXqZEfXNA==",
+      "requires": {
+        "available-typed-arrays": "^1.0.2",
+        "call-bind": "^1.0.0",
+        "es-abstract": "^1.18.0-next.1",
+        "foreach": "^2.0.5",
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+      "dev": true
+    },
+    "isarray": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+      "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+      "dev": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+      "dev": true
+    },
+    "istanbul": {
+      "version": "0.4.5",
+      "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz",
+      "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=",
+      "dev": true,
+      "requires": {
+        "abbrev": "1.0.x",
+        "async": "1.x",
+        "escodegen": "1.8.x",
+        "esprima": "2.7.x",
+        "glob": "^5.0.15",
+        "handlebars": "^4.0.1",
+        "js-yaml": "3.x",
+        "mkdirp": "0.5.x",
+        "nopt": "3.x",
+        "once": "1.x",
+        "resolve": "1.1.x",
+        "supports-color": "^3.1.0",
+        "which": "^1.1.1",
+        "wordwrap": "^1.0.0"
+      },
+      "dependencies": {
+        "esprima": {
+          "version": "2.7.3",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+          "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+          "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "3.2.3",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+          "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+          "dev": true,
+          "requires": {
+            "has-flag": "^1.0.0"
+          }
+        }
+      }
+    },
+    "js-yaml": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+      "dev": true,
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+      "dev": true
+    },
+    "jshint": {
+      "version": "2.12.0",
+      "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.12.0.tgz",
+      "integrity": "sha512-TwuuaUDmra0JMkuqvqy+WGo2xGHSNjv1BA1nTIgtH2K5z1jHuAEeAgp7laaR+hLRmajRjcrM71+vByBDanCyYA==",
+      "dev": true,
+      "requires": {
+        "cli": "~1.0.0",
+        "console-browserify": "1.1.x",
+        "exit": "0.1.x",
+        "htmlparser2": "3.8.x",
+        "lodash": "~4.17.19",
+        "minimatch": "~3.0.2",
+        "shelljs": "0.3.x",
+        "strip-json-comments": "1.0.x"
+      }
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+      "dev": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+      "dev": true
+    },
+    "jsonfile": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+      "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.6",
+        "universalify": "^2.0.0"
+      },
+      "dependencies": {
+        "universalify": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+          "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+          "dev": true
+        }
+      }
+    },
+    "jsonparse": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+      "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA="
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "labeled-stream-splicer": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz",
+      "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "stream-splicer": "^2.0.0"
+      }
+    },
+    "lcov-parse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz",
+      "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=",
+      "dev": true
+    },
+    "levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2"
+      }
+    },
+    "locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "requires": {
+        "p-locate": "^5.0.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.20",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+      "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+      "dev": true
+    },
+    "lodash.memoize": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
+      "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8="
+    },
+    "log-driver": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz",
+      "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==",
+      "dev": true
+    },
+    "log-symbols": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
+      "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
+      "dev": true,
+      "requires": {
+        "chalk": "^4.0.0"
+      }
+    },
+    "md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "mdn-data": {
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
+      "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="
+    },
+    "miller-rabin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+      "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+      "requires": {
+        "bn.js": "^4.0.0",
+        "brorand": "^1.0.1"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
+        }
+      }
+    },
+    "mime-db": {
+      "version": "1.45.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
+      "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==",
+      "dev": true
+    },
+    "mime-types": {
+      "version": "2.1.28",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz",
+      "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==",
+      "dev": true,
+      "requires": {
+        "mime-db": "1.45.0"
+      }
+    },
+    "minimalistic-assert": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
+    },
+    "minimalistic-crypto-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+    },
+    "mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "mkdirp-classic": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
+    },
+    "mocha": {
+      "version": "8.2.1",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz",
+      "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==",
+      "dev": true,
+      "requires": {
+        "@ungap/promise-all-settled": "1.1.2",
+        "ansi-colors": "4.1.1",
+        "browser-stdout": "1.3.1",
+        "chokidar": "3.4.3",
+        "debug": "4.2.0",
+        "diff": "4.0.2",
+        "escape-string-regexp": "4.0.0",
+        "find-up": "5.0.0",
+        "glob": "7.1.6",
+        "growl": "1.10.5",
+        "he": "1.2.0",
+        "js-yaml": "3.14.0",
+        "log-symbols": "4.0.0",
+        "minimatch": "3.0.4",
+        "ms": "2.1.2",
+        "nanoid": "3.1.12",
+        "serialize-javascript": "5.0.1",
+        "strip-json-comments": "3.1.1",
+        "supports-color": "7.2.0",
+        "which": "2.0.2",
+        "wide-align": "1.1.3",
+        "workerpool": "6.0.2",
+        "yargs": "13.3.2",
+        "yargs-parser": "13.1.2",
+        "yargs-unparser": "2.0.0"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "7.1.6",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+          "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+          "dev": true,
+          "requires": {
+            "fs.realpath": "^1.0.0",
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "^3.0.4",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        },
+        "js-yaml": {
+          "version": "3.14.0",
+          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
+          "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
+          "dev": true,
+          "requires": {
+            "argparse": "^1.0.7",
+            "esprima": "^4.0.0"
+          }
+        },
+        "strip-json-comments": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+          "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+          "dev": true
+        },
+        "which": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+          "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+          "dev": true,
+          "requires": {
+            "isexe": "^2.0.0"
+          }
+        }
+      }
+    },
+    "mocha-istanbul": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/mocha-istanbul/-/mocha-istanbul-0.3.0.tgz",
+      "integrity": "sha1-U8dNPoX/bix9EHWw5X4xdKvqZEY=",
+      "dev": true,
+      "requires": {
+        "istanbul": "*"
+      }
+    },
+    "mock-stdin": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/mock-stdin/-/mock-stdin-1.0.0.tgz",
+      "integrity": "sha512-tukRdb9Beu27t6dN+XztSRHq9J0B/CoAOySGzHfn8UTfmqipA5yNT/sDUEyYdAV3Hpka6Wx6kOMxuObdOex60Q==",
+      "dev": true
+    },
+    "module-deps": {
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz",
+      "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==",
+      "requires": {
+        "JSONStream": "^1.0.3",
+        "browser-resolve": "^2.0.0",
+        "cached-path-relative": "^1.0.2",
+        "concat-stream": "~1.6.0",
+        "defined": "^1.0.0",
+        "detective": "^5.2.0",
+        "duplexer2": "^0.1.2",
+        "inherits": "^2.0.1",
+        "parents": "^1.0.0",
+        "readable-stream": "^2.0.2",
+        "resolve": "^1.4.0",
+        "stream-combiner2": "^1.1.1",
+        "subarg": "^1.0.0",
+        "through2": "^2.0.0",
+        "xtend": "^4.0.0"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "resolve": {
+          "version": "1.19.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
+          "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
+          "requires": {
+            "is-core-module": "^2.1.0",
+            "path-parse": "^1.0.6"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "nanoid": {
+      "version": "3.1.12",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz",
+      "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==",
+      "dev": true
+    },
+    "neo-async": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+      "dev": true
+    },
+    "nopt": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+      "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+      "dev": true,
+      "requires": {
+        "abbrev": "1"
+      }
+    },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true
+    },
+    "nth-check": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
+      "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
+      "requires": {
+        "boolbase": "^1.0.0"
+      }
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+      "dev": true
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "object-inspect": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
+      "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw=="
+    },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+    },
+    "object.assign": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
+      "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "define-properties": "^1.1.3",
+        "has-symbols": "^1.0.1",
+        "object-keys": "^1.1.1"
+      }
+    },
+    "object.getownpropertydescriptors": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz",
+      "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.18.0-next.1"
+      }
+    },
+    "object.values": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz",
+      "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.18.0-next.1",
+        "has": "^1.0.3"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "optionator": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+      "dev": true,
+      "requires": {
+        "deep-is": "~0.1.3",
+        "fast-levenshtein": "~2.0.6",
+        "levn": "~0.3.0",
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2",
+        "word-wrap": "~1.2.3"
+      }
+    },
+    "os-browserify": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+      "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc="
+    },
+    "p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "requires": {
+        "yocto-queue": "^0.1.0"
+      }
+    },
+    "p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "requires": {
+        "p-limit": "^3.0.2"
+      }
+    },
+    "p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+      "dev": true
+    },
+    "pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+    },
+    "parents": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
+      "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=",
+      "requires": {
+        "path-platform": "~0.11.15"
+      }
+    },
+    "parse-asn1": {
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
+      "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
+      "requires": {
+        "asn1.js": "^5.2.0",
+        "browserify-aes": "^1.0.0",
+        "evp_bytestokey": "^1.0.0",
+        "pbkdf2": "^3.0.3",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "path-browserify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
+    },
+    "path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
+    },
+    "path-platform": {
+      "version": "0.11.15",
+      "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
+      "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I="
+    },
+    "pbkdf2": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
+      "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
+      "requires": {
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4",
+        "ripemd160": "^2.0.1",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+      "dev": true
+    },
+    "picomatch": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+      "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+      "dev": true
+    },
+    "prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+      "dev": true
+    },
+    "process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+      "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
+    "psl": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+      "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
+      "dev": true
+    },
+    "public-encrypt": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+      "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+      "requires": {
+        "bn.js": "^4.1.0",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "parse-asn1": "^5.0.0",
+        "randombytes": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      },
+      "dependencies": {
+        "bn.js": {
+          "version": "4.11.9",
+          "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+          "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
+        }
+      }
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+      "dev": true
+    },
+    "querystring": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
+    },
+    "querystring-es3": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+      "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
+    },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "randomfill": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+      "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+      "requires": {
+        "randombytes": "^2.0.5",
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "read-only-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
+      "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=",
+      "requires": {
+        "readable-stream": "^2.0.2"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "readable-stream": {
+      "version": "1.1.14",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+      "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+      "dev": true,
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.1",
+        "isarray": "0.0.1",
+        "string_decoder": "~0.10.x"
+      }
+    },
+    "readdirp": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+      "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+      "dev": true,
+      "requires": {
+        "picomatch": "^2.2.1"
+      }
+    },
+    "request": {
+      "version": "2.88.2",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+      "dev": true,
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.5.0",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+      "dev": true
+    },
+    "require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+      "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs="
+    },
+    "ripemd160": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+    },
+    "serialize-javascript": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
+      "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
+      "dev": true,
+      "requires": {
+        "randombytes": "^2.1.0"
+      }
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+      "dev": true
+    },
+    "sha.js": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "shasum-object": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz",
+      "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==",
+      "requires": {
+        "fast-safe-stringify": "^2.0.7"
+      }
+    },
+    "shell-quote": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
+      "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
+    },
+    "shelljs": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
+      "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=",
+      "dev": true
+    },
+    "should": {
+      "version": "13.2.3",
+      "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz",
+      "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==",
+      "dev": true,
+      "requires": {
+        "should-equal": "^2.0.0",
+        "should-format": "^3.0.3",
+        "should-type": "^1.4.0",
+        "should-type-adaptors": "^1.0.1",
+        "should-util": "^1.0.0"
+      }
+    },
+    "should-equal": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
+      "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
+      "dev": true,
+      "requires": {
+        "should-type": "^1.4.0"
+      }
+    },
+    "should-format": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
+      "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
+      "dev": true,
+      "requires": {
+        "should-type": "^1.3.0",
+        "should-type-adaptors": "^1.0.1"
+      }
+    },
+    "should-type": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
+      "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
+      "dev": true
+    },
+    "should-type-adaptors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
+      "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
+      "dev": true,
+      "requires": {
+        "should-type": "^1.3.0",
+        "should-util": "^1.0.0"
+      }
+    },
+    "should-util": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz",
+      "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==",
+      "dev": true
+    },
+    "simple-concat": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+      "dev": true
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "dev": true,
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "stable": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
+      "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w=="
+    },
+    "stream-browserify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
+      "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
+      "requires": {
+        "inherits": "~2.0.4",
+        "readable-stream": "^3.5.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+          "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+          "requires": {
+            "safe-buffer": "~5.2.0"
+          }
+        }
+      }
+    },
+    "stream-combiner2": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
+      "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=",
+      "requires": {
+        "duplexer2": "~0.1.0",
+        "readable-stream": "^2.0.2"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "stream-http": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz",
+      "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==",
+      "requires": {
+        "builtin-status-codes": "^3.0.0",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.6.0",
+        "xtend": "^4.0.2"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.6.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        },
+        "string_decoder": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+          "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+          "requires": {
+            "safe-buffer": "~5.2.0"
+          }
+        }
+      }
+    },
+    "stream-splicer": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz",
+      "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==",
+      "requires": {
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.2"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "string-width": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+      "dev": true,
+      "requires": {
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^4.0.0"
+      }
+    },
+    "string.prototype.trimend": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz",
+      "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "define-properties": "^1.1.3"
+      }
+    },
+    "string.prototype.trimstart": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz",
+      "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "define-properties": "^1.1.3"
+      }
+    },
+    "string_decoder": {
+      "version": "0.10.31",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+      "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+      "dev": true
+    },
+    "strip-ansi": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+      "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^3.0.0"
+      }
+    },
+    "strip-json-comments": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
+      "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=",
+      "dev": true
+    },
+    "subarg": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
+      "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
+      "requires": {
+        "minimist": "^1.1.0"
+      }
+    },
+    "supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "requires": {
+        "has-flag": "^4.0.0"
+      }
+    },
+    "syntax-error": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
+      "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==",
+      "requires": {
+        "acorn-node": "^1.2.0"
+      }
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+    },
+    "through2": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "requires": {
+        "readable-stream": "~2.3.6",
+        "xtend": "~4.0.1"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "timers-browserify": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
+      "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
+      "requires": {
+        "process": "~0.11.0"
+      }
+    },
+    "to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "requires": {
+        "is-number": "^7.0.0"
+      }
+    },
+    "tough-cookie": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+      "dev": true,
+      "requires": {
+        "psl": "^1.1.28",
+        "punycode": "^2.1.1"
+      }
+    },
+    "tty-browserify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
+      "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw=="
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+      "dev": true
+    },
+    "type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2"
+      }
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+    },
+    "uglify-js": {
+      "version": "3.12.4",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.4.tgz",
+      "integrity": "sha512-L5i5jg/SHkEqzN18gQMTWsZk3KelRsfD1wUVNqtq0kzqWQqcJjyL8yc1o8hJgRrWqrAl2mUFbhfznEIoi7zi2A==",
+      "dev": true,
+      "optional": true
+    },
+    "umd": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz",
+      "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow=="
+    },
+    "undeclared-identifiers": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz",
+      "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==",
+      "requires": {
+        "acorn-node": "^1.3.0",
+        "dash-ast": "^1.0.0",
+        "get-assigned-identifiers": "^1.2.0",
+        "simple-concat": "^1.0.0",
+        "xtend": "^4.0.1"
+      }
+    },
+    "universalify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
+      "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
+      "dev": true
+    },
+    "unquote": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
+      "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ="
+    },
+    "uri-js": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+      "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "url": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+      "requires": {
+        "punycode": "1.3.2",
+        "querystring": "0.2.0"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+        }
+      }
+    },
+    "util": {
+      "version": "0.12.3",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz",
+      "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==",
+      "requires": {
+        "inherits": "^2.0.3",
+        "is-arguments": "^1.0.4",
+        "is-generator-function": "^1.0.7",
+        "is-typed-array": "^1.1.3",
+        "safe-buffer": "^5.1.2",
+        "which-typed-array": "^1.1.2"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "util.promisify": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz",
+      "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==",
+      "requires": {
+        "call-bind": "^1.0.0",
+        "define-properties": "^1.1.3",
+        "for-each": "^0.3.3",
+        "has-symbols": "^1.0.1",
+        "object.getownpropertydescriptors": "^2.1.1"
+      }
+    },
+    "uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+      "dev": true
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "vm-browserify": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+      "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+      "dev": true
+    },
+    "which-typed-array": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz",
+      "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==",
+      "requires": {
+        "available-typed-arrays": "^1.0.2",
+        "call-bind": "^1.0.0",
+        "es-abstract": "^1.18.0-next.1",
+        "foreach": "^2.0.5",
+        "function-bind": "^1.1.1",
+        "has-symbols": "^1.0.1",
+        "is-typed-array": "^1.1.3"
+      }
+    },
+    "wide-align": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^1.0.2 || 2"
+      }
+    },
+    "word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true
+    },
+    "wordwrap": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+      "dev": true
+    },
+    "workerpool": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz",
+      "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==",
+      "dev": true
+    },
+    "wrap-ansi": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+      "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.0",
+        "string-width": "^3.0.0",
+        "strip-ansi": "^5.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "color-convert": {
+          "version": "1.9.3",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+          "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+    },
+    "y18n": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
+      "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
+      "dev": true
+    },
+    "yargs": {
+      "version": "13.3.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+      "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+      "dev": true,
+      "requires": {
+        "cliui": "^5.0.0",
+        "find-up": "^3.0.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^3.0.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^13.1.2"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "find-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^3.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^3.0.0",
+            "path-exists": "^3.0.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.0.0"
+          }
+        },
+        "path-exists": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "yargs-parser": {
+      "version": "13.1.2",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+      "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+      "dev": true,
+      "requires": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
+    },
+    "yargs-unparser": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+      "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+      "dev": true,
+      "requires": {
+        "camelcase": "^6.0.0",
+        "decamelize": "^4.0.0",
+        "flat": "^5.0.2",
+        "is-plain-obj": "^2.1.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "6.2.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
+          "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
+          "dev": true
+        },
+        "decamelize": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+          "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+          "dev": true
+        }
+      }
+    },
+    "yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true
+    }
+  }
+}
diff --git a/vendor/svgclean/package.json b/vendor/svgclean/package.json
new file mode 100644
index 000000000..af4cf28d5
--- /dev/null
+++ b/vendor/svgclean/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "svgclean",
+  "version": "1.0.0",
+  "description": "SVG Cleaner (SVGO Fork)",
+  "main": "./src/svgo.js",
+  "scripts": {
+    "test": "set NODE_ENV=test && mocha",
+    "lint": "jshint --show-non-errors ."
+  },
+  "dependencies": {
+    "chalk": "^4.1.0",
+    "css-select": "^3.1.2",
+    "css-select-base-adapter": "^0.1.1",
+    "css-tree": "^1.1.2",
+    "csso": "^4.2.0",
+    "object.values": "^1.1.2",
+    "sax": "^1.2.4",
+    "stable": "^0.1.8",
+    "unquote": "^1.1.1",
+    "util.promisify": "^1.1.1"
+  },
+  "devDependencies": {
+    "browserify": "^17.0.0",
+    "coveralls": "^3.1.0",
+    "fs-extra": "^9.0.1",
+    "istanbul": "^0.4.5",
+    "jshint": "^2.12.0",
+    "mocha": "^8.2.1",
+    "mocha-istanbul": "^0.3.0",
+    "mock-stdin": "^1.0.0",
+    "should": "~13.2.3"
+  },
+  "license": "MIT"
+}
diff --git a/vendor/svgclean/src/svgclean.js b/vendor/svgclean/src/svgclean.js
new file mode 100755
index 000000000..62582d616
--- /dev/null
+++ b/vendor/svgclean/src/svgclean.js
@@ -0,0 +1,59 @@
+'use strict';
+
+// encodeSVGDatauri = require('./svgclean/tools.js').encodeSVGDatauri,
+
+const cfg     = require("./svgclean/config.js");
+const svgToJs = require('./svgclean/svg2js.js');
+const jsToSvg = require('./svgclean/js2svg.js');
+
+exports.configure = function(config={}) {
+  const plugins = cfg.loadPlugins(config);
+  return Object.assign({}, config, {plugins});
+};
+
+exports.optimize = function(config, svgstr) {
+  const info = {};
+
+  return new Promise((resolve, reject) => {
+    let maxPassCount = config.multipass ? 10 : 1;
+    let counter = 0;
+    let prevResultSize = Number.POSITIVE_INFINITY;
+
+    function optimize(root) {
+      if (root.error) {
+        reject(root.error);
+        return;
+      }
+
+      info.multipassCount = counter;
+      if (++counter < maxPassCount && root.data.length < prevResultSize) {
+        prevResultSize = root.data.length;
+        runOptimizations(config, root.data, info, optimize);
+      } else {
+        // if (config.datauri) {
+        //   root.data = encodeSVGDatauri(root.data, config.datauri);
+        // }
+        if (info && info.path) {
+          root.path = info.path;
+        }
+
+        resolve(root);
+      }
+    };
+
+    runOptimizations(config, svgstr, info, optimize);
+  });
+};
+
+function runOptimizations(config, svgstr, info, callback) {
+  const plugins = config.plugins;
+
+  svgToJs(svgstr).then(function(doc) {
+    doc = cfg.executePlugins(plugins, doc, info);
+    // TODO: pass formating (js2svg) config
+    callback(jsToSvg(doc, config.format || {}));
+  }, function(error) {
+    callback({error: error});
+  });
+};
+
diff --git a/vendor/svgclean/src/svgclean/config.js b/vendor/svgclean/src/svgclean/config.js
new file mode 100644
index 000000000..ce0652842
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/config.js
@@ -0,0 +1,269 @@
+'use strict';
+
+const _collections = require('./plugins/_collections');
+const _path = require('./plugins/_path');
+const _transforms = require('./plugins/_transforms');
+const addAttributesToSVGElement = require('./plugins/addAttributesToSVGElement');
+const addClassesToSVGElement = require('./plugins/addClassesToSVGElement');
+const cleanupAttrs = require('./plugins/cleanupAttrs');
+const cleanupEnableBackground = require('./plugins/cleanupEnableBackground');
+const cleanupIDs = require('./plugins/cleanupIDs');
+const cleanupListOfValues = require('./plugins/cleanupListOfValues');
+const cleanupNumericValues = require('./plugins/cleanupNumericValues');
+const collapseGroups = require('./plugins/collapseGroups');
+const convertColors = require('./plugins/convertColors');
+const convertEllipseToCircle = require('./plugins/convertEllipseToCircle');
+const convertPathData = require('./plugins/convertPathData');
+const convertShapeToPath = require('./plugins/convertShapeToPath');
+const convertStyleToAttrs = require('./plugins/convertStyleToAttrs');
+const convertTransform = require('./plugins/convertTransform');
+const inlineStyles = require('./plugins/inlineStyles');
+const mergePaths = require('./plugins/mergePaths');
+const minifyStyles = require('./plugins/minifyStyles');
+const moveElemsAttrsToGroup = require('./plugins/moveElemsAttrsToGroup');
+const moveGroupAttrsToElems = require('./plugins/moveGroupAttrsToElems');
+const prefixIds = require('./plugins/prefixIds');
+const removeAttributesBySelector = require('./plugins/removeAttributesBySelector');
+const removeAttrs = require('./plugins/removeAttrs');
+const removeComments = require('./plugins/removeComments');
+const removeDesc = require('./plugins/removeDesc');
+const removeDimensions = require('./plugins/removeDimensions');
+const removeDoctype = require('./plugins/removeDoctype');
+const removeEditorsNSData = require('./plugins/removeEditorsNSData');
+const removeElementsByAttr = require('./plugins/removeElementsByAttr');
+const removeEmptyAttrs = require('./plugins/removeEmptyAttrs');
+const removeEmptyContainers = require('./plugins/removeEmptyContainers');
+const removeEmptyText = require('./plugins/removeEmptyText');
+const removeHiddenElems = require('./plugins/removeHiddenElems');
+const removeMetadata = require('./plugins/removeMetadata');
+const removeNonInheritableGroupAttrs = require('./plugins/removeNonInheritableGroupAttrs');
+const removeOffCanvasPaths = require('./plugins/removeOffCanvasPaths');
+const removeRasterImages = require('./plugins/removeRasterImages');
+const removeScriptElement = require('./plugins/removeScriptElement');
+const removeStyleElement = require('./plugins/removeStyleElement');
+const removeTitle = require('./plugins/removeTitle');
+const removeUnknownsAndDefaults = require('./plugins/removeUnknownsAndDefaults');
+const removeUnusedNS = require('./plugins/removeUnusedNS');
+const removeUselessDefs = require('./plugins/removeUselessDefs');
+const removeUselessStrokeAndFill = require('./plugins/removeUselessStrokeAndFill');
+const removeViewBox = require('./plugins/removeViewBox');
+const removeXMLNS = require('./plugins/removeXMLNS');
+const removeXMLProcInst = require('./plugins/removeXMLProcInst');
+const reusePaths = require('./plugins/reusePaths');
+const sortAttrs = require('./plugins/sortAttrs');
+const sortDefsChildren = require('./plugins/sortDefsChildren');
+
+const builtinPlugins = {
+  removeOffCanvasPaths,
+  _collections,
+  _path,
+  _transforms,
+  addAttributesToSVGElement,
+  addClassesToSVGElement,
+  cleanupAttrs,
+  cleanupEnableBackground,
+  cleanupIDs,
+  cleanupListOfValues,
+  cleanupNumericValues,
+  collapseGroups,
+  convertColors,
+  convertEllipseToCircle,
+  convertPathData,
+  convertShapeToPath,
+  convertStyleToAttrs,
+  convertTransform,
+  inlineStyles,
+  mergePaths,
+  minifyStyles,
+  moveElemsAttrsToGroup,
+  moveGroupAttrsToElems,
+  prefixIds,
+  removeAttributesBySelector,
+  removeAttrs,
+  removeComments,
+  removeDesc,
+  removeDimensions,
+  removeDoctype,
+  removeEditorsNSData,
+  removeElementsByAttr,
+  removeEmptyAttrs,
+  removeEmptyContainers,
+  removeEmptyText,
+  removeHiddenElems,
+  removeMetadata,
+  removeNonInheritableGroupAttrs,
+  removeRasterImages,
+  removeScriptElement,
+  removeStyleElement,
+  removeTitle,
+  removeUnknownsAndDefaults,
+  removeUnusedNS,
+  removeUselessDefs,
+  removeUselessStrokeAndFill,
+  removeViewBox,
+  removeXMLNS,
+  removeXMLProcInst,
+  reusePaths,
+  sortAttrs,
+  sortDefsChildren,
+};
+
+const defaultPlugins = {
+  prefixIds: true,
+  cleanupAttrs: true,
+  cleanupEnableBackground: true,
+  cleanupIDs: true,
+  cleanupNumericValues: true,
+  collapseGroups: true,
+  convertColors: true,
+  convertEllipseToCircle: true,
+  convertPathData: true,
+  convertShapeToPath: true,
+  convertStyleToAttrs: true,
+  convertTransform: true,
+  inlineStyles: true,
+  mergePaths: false,
+  minifyStyles: true,
+  moveElemsAttrsToGroup: false,
+  moveGroupAttrsToElems: false,
+  removeComments: true,
+  removeDesc: true,
+  removeDimensions: false,
+  removeDoctype: true,
+  removeEditorsNSData: true,
+  removeEmptyAttrs: true,
+  removeEmptyContainers: true,
+  removeEmptyText: true,
+  removeHiddenElems: true,
+  removeNonInheritableGroupAttrs: true,
+  removeRasterImages: true,
+  removeTitle: true,
+  removeUnknownsAndDefaults: true,
+  removeUnusedNS: true,
+  removeUselessDefs: true,
+  removeUselessStrokeAndFill: true,
+  removeViewBox: false,
+  removeXMLNS: true,
+  removeXMLProcInst: true,
+  sortAttrs: false,
+  sortDefsChildren: true
+};
+
+function optimizePlugins(plugins) {
+  let prev;
+
+  return plugins.reduce(function(plugins, item) {
+    if (prev && item.type == prev[0].type) {
+      prev.push(item);
+    } else {
+      plugins.push(prev = [item]);
+    }
+    return plugins;
+  }, []);
+}
+
+exports.loadPlugins = function(config={}) {
+  let plugins = Object.assign({}, config.plugins || defaultPlugins);
+  let configuredPlugins = [];
+
+  for (let key of Object.keys(plugins)) {
+    let pluginOpt = plugins[key];
+    let plugin = null;
+
+    if (typeof pluginOpt === "object") {
+      plugin = Object.assign({}, builtinPlugins[key]);
+      plugin.params = Object.assign({}, pluginOpt);
+    } else if (pluginOpt === true) {
+      plugin = Object.assign({}, builtinPlugins[key]);
+    } else {
+      continue;
+    }
+
+    plugin.active = true;
+    configuredPlugins.push(plugin);
+  }
+
+  return optimizePlugins(configuredPlugins);
+};
+
+exports.executePlugins = function(plugins, data, info) {
+  plugins.forEach(function(group) {
+    switch(group[0].type) {
+    case 'perItem':
+      data = perItem(data, info, group);
+      break;
+    case 'perItemReverse':
+      data = perItem(data, info, group, true);
+      break;
+    case 'full':
+      data = full(data, info, group);
+      break;
+    }
+
+  });
+
+  return data;
+}
+
+/**
+ * Direct or reverse per-item loop.
+ *
+ * @param {Object} data input data
+ * @param {Object} info extra information
+ * @param {Array} plugins plugins list to process
+ * @param {Boolean} [reverse] reverse pass?
+ * @return {Object} output data
+ */
+function perItem(data, info, plugins, reverse) {
+
+  function monkeys(items) {
+    items.content = items.content.filter(function(item) {
+
+      // reverse pass
+      if (reverse && item.content) {
+        monkeys(item);
+      }
+
+      // main filter
+      let filter = true;
+
+      for (let i = 0; filter && i < plugins.length; i++) {
+        let plugin = plugins[i];
+
+        if (plugin.active && plugin.fn(item, plugin.params, info) === false) {
+          filter = false;
+        }
+      }
+
+      // direct pass
+      if (!reverse && item.content) {
+        monkeys(item);
+      }
+
+      return filter;
+
+    });
+
+    return items;
+
+  }
+
+  return monkeys(data);
+}
+
+/**
+ * "Full" plugins.
+ *
+ * @param {Object} data input data
+ * @param {Object} info extra information
+ * @param {Array} plugins plugins list to process
+ * @return {Object} output data
+ */
+function full(data, info, plugins) {
+  plugins.forEach(function(plugin) {
+    if (plugin.active) {
+      data = plugin.fn(data, plugin.params, info);
+    }
+  });
+  return data;
+}
diff --git a/vendor/svgclean/src/svgclean/css-class-list.js b/vendor/svgclean/src/svgclean/css-class-list.js
new file mode 100644
index 000000000..8401a9f84
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/css-class-list.js
@@ -0,0 +1,138 @@
+'use strict';
+
+var values = require('object.values');
+if (!Object.values) {
+    values.shim();
+}
+
+
+var CSSClassList = function(node) {
+    this.parentNode = node;
+    this.classNames = new Set();
+    this.classAttr = null;
+    //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
+        'name': 'class',
+        'value': null
+    };
+
+    this.addClassHandler();
+};
+
+
+// attr.class
+
+CSSClassList.prototype.addClassHandler = function() {
+
+    Object.defineProperty(this.parentNode.attrs, 'class', {
+        get: this.getClassAttr.bind(this),
+        set: this.setClassAttr.bind(this),
+        enumerable: true,
+        configurable: true
+    });
+
+    this.addClassValueHandler();
+};
+
+// attr.class.value
+
+CSSClassList.prototype.addClassValueHandler = function() {
+
+    Object.defineProperty(this.classAttr, 'value', {
+        get: this.getClassValue.bind(this),
+        set: this.setClassValue.bind(this),
+        enumerable: true,
+        configurable: true
+    });
+};
+
+CSSClassList.prototype.getClassAttr = function() {
+    return this.classAttr;
+};
+
+CSSClassList.prototype.setClassAttr = function(newClassAttr) {
+    this.setClassValue(newClassAttr.value); // must before applying value handler!
+
+    this.classAttr = newClassAttr;
+    this.addClassValueHandler();
+};
+
+CSSClassList.prototype.getClassValue = function() {
+    var arrClassNames = Array.from(this.classNames);
+    return arrClassNames.join(' ');
+};
+
+CSSClassList.prototype.setClassValue = function(newValue) {
+    if(typeof newValue === 'undefined') {
+      this.classNames.clear();
+      return;
+    }
+    var arrClassNames = newValue.split(' ');
+    this.classNames = new Set(arrClassNames);
+};
+
+
+CSSClassList.prototype.add = function(/* variadic */) {
+    this.hasClass();
+    Object.values(arguments).forEach(this._addSingle.bind(this));
+};
+
+CSSClassList.prototype._addSingle = function(className) {
+    this.classNames.add(className);
+};
+
+
+CSSClassList.prototype.remove = function(/* variadic */) {
+    this.hasClass();
+    Object.values(arguments).forEach(this._removeSingle.bind(this));
+};
+
+CSSClassList.prototype._removeSingle = function(className) {
+    this.classNames.delete(className);
+};
+
+
+CSSClassList.prototype.item = function(index) {
+    var arrClassNames = Array.from(this.classNames);
+    return arrClassNames[index];
+};
+
+CSSClassList.prototype.toggle = function(className, force) {
+    if(this.contains(className) || force === false) {
+        this.classNames.delete(className);
+    }
+    this.classNames.add(className);
+};
+
+CSSClassList.prototype.contains = function(className) {
+    return this.classNames.has(className);
+};
+
+
+module.exports = CSSClassList;
\ No newline at end of file
diff --git a/vendor/svgclean/src/svgclean/css-select-adapter.js b/vendor/svgclean/src/svgclean/css-select-adapter.js
new file mode 100644
index 000000000..c37678cbf
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/css-select-adapter.js
@@ -0,0 +1,53 @@
+'use strict';
+
+var baseCssAdapter = require('css-select-base-adapter');
+
+/**
+ * DOMUtils API for SVGO AST (used by css-select)
+ */
+var svgoCssSelectAdapterMin = {
+
+    // is the node a tag?
+    // isTag: ( node:Node ) => isTag:Boolean
+    isTag: function(node) {
+        return node.isElem();
+    },
+
+    // get the parent of the node
+    // getParent: ( node:Node ) => parentNode:Node
+    // returns null when no parent exists
+    getParent: function(node) {
+        return node.parentNode || null;
+    },
+
+    // get the node's children
+    // getChildren: ( node:Node ) => children:[Node]
+    getChildren: function(node) {
+        return node.content || [];
+    },
+
+    // get the name of the tag
+    // getName: ( elem:ElementNode ) => tagName:String
+    getName: function(elemAst) {
+        return elemAst.elem;
+    },
+
+    // get the text content of the node, and its children if it has any
+    // getText: ( node:Node ) => text:String
+    // returns empty string when there is no text
+    getText: function(node) {
+        return node.content[0].text || node.content[0].cdata || '';
+    },
+
+    // get the attribute value
+    // getAttributeValue: ( elem:ElementNode, name:String ) => value:String
+    // returns null when attribute doesn't exist
+    getAttributeValue: function(elem, name) {
+        return elem.hasAttr(name) ? elem.attr(name).value : null;
+    }
+};
+
+// use base adapter for default implementation
+var svgoCssSelectAdapter = baseCssAdapter(svgoCssSelectAdapterMin);
+
+module.exports = svgoCssSelectAdapter;
diff --git a/vendor/svgclean/src/svgclean/css-style-declaration.js b/vendor/svgclean/src/svgclean/css-style-declaration.js
new file mode 100644
index 000000000..ea389114c
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/css-style-declaration.js
@@ -0,0 +1,285 @@
+'use strict';
+
+var csstree = require('css-tree'),
+    csstools = require('./css-tools');
+
+
+var CSSStyleDeclaration = function(node) {
+    this.parentNode = node;
+
+    this.properties = new Map();
+    this.hasSynced = false;
+
+    this.styleAttr = null;
+    this.styleValue = null;
+
+    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();
+};
+
+
+
+
+// attr.style
+
+CSSStyleDeclaration.prototype.addStyleHandler = function() {
+
+    this.styleAttr = { // empty style attr
+        'name': 'style',
+        'value': null
+    };
+
+    Object.defineProperty(this.parentNode.attrs, 'style', {
+        get: this.getStyleAttr.bind(this),
+        set: this.setStyleAttr.bind(this),
+        enumerable: true,
+        configurable: true
+    });
+
+    this.addStyleValueHandler();
+};
+
+// attr.style.value
+
+CSSStyleDeclaration.prototype.addStyleValueHandler = function() {
+
+    Object.defineProperty(this.styleAttr, 'value', {
+        get: this.getStyleValue.bind(this),
+        set: this.setStyleValue.bind(this),
+        enumerable: true,
+        configurable: true
+    });
+};
+
+CSSStyleDeclaration.prototype.getStyleAttr = function() {
+    return this.styleAttr;
+};
+
+CSSStyleDeclaration.prototype.setStyleAttr = function(newStyleAttr) {
+    this.setStyleValue(newStyleAttr.value); // must before applying value handler!
+
+    this.styleAttr = newStyleAttr;
+    this.addStyleValueHandler();
+    this.hasSynced = false; // raw css changed
+};
+
+CSSStyleDeclaration.prototype.getStyleValue = function() {
+    return this.getCssText();
+};
+
+CSSStyleDeclaration.prototype.setStyleValue = function(newValue) {
+    this.properties.clear(); // reset all existing properties
+    this.styleValue = newValue;
+    this.hasSynced = false; // raw css changed
+};
+
+
+
+
+CSSStyleDeclaration.prototype._loadCssText = function() {
+    if (this.hasSynced) {
+        return;
+    }
+    this.hasSynced = true; // must be set here to prevent loop in setProperty(...)
+
+    if (!this.styleValue || this.styleValue.length === 0) {
+        return;
+    }
+    var inlineCssStr = this.styleValue;
+
+    var declarations = {};
+    try {
+        declarations = csstree.parse(inlineCssStr, {
+            context: 'declarationList',
+            parseValue: false
+        });
+    } catch (parseError) {
+        this.parseError = parseError;
+        return;
+    }
+    this.parseError = false;
+
+    var self = this;
+    declarations.children.each(function(declaration) {
+        try {
+          var styleDeclaration = csstools.csstreeToStyleDeclaration(declaration);
+          self.setProperty(styleDeclaration.name, styleDeclaration.value, styleDeclaration.priority);
+        } catch(styleError) {
+            if(styleError.message !== 'Unknown node type: undefined') {
+                self.parseError = styleError;
+            }
+        }
+    });
+};
+
+
+// only reads from properties
+
+/**
+ * Get the textual representation of the declaration block (equivalent to .cssText attribute).
+ *
+ * @return {String} Textual representation of the declaration block (empty string for no properties)
+ */
+CSSStyleDeclaration.prototype.getCssText = function() {
+    var properties = this.getProperties();
+
+    if (this.parseError) {
+        // in case of a parse error, pass through original styles
+        return this.styleValue;
+    }
+
+    var cssText = [];
+    properties.forEach(function(property, propertyName) {
+        var strImportant = property.priority === 'important' ? '!important' : '';
+        cssText.push(propertyName.trim() + ':' + property.value.trim() + strImportant);
+    });
+    return cssText.join(';');
+};
+
+CSSStyleDeclaration.prototype._handleParseError = function() {
+    if (this.parseError) {
+        console.warn('Warning: Parse error when parsing inline styles, style properties of this element cannot be used. The raw styles can still be get/set using .attr(\'style\').value. Error details: ' + this.parseError);
+    }
+};
+
+
+CSSStyleDeclaration.prototype._getProperty = function(propertyName) {
+    if(typeof propertyName === 'undefined') {
+        throw Error('1 argument required, but only 0 present.');
+    }
+
+    var properties = this.getProperties();
+    this._handleParseError();
+
+    var property = properties.get(propertyName.trim());
+    return property;
+};
+
+/**
+ * Return the optional priority, "important".
+ *
+ * @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.
+ */
+CSSStyleDeclaration.prototype.getPropertyPriority = function(propertyName) {
+    var property = this._getProperty(propertyName);
+    return property ? property.priority : '';
+};
+
+/**
+ * Return the property value given a property name.
+ *
+ * @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.
+ */
+CSSStyleDeclaration.prototype.getPropertyValue = function(propertyName) {
+    var property = this._getProperty(propertyName);
+    return property ? property.value : null;
+};
+
+/**
+ * Return a property name.
+ *
+ * @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.
+ */
+CSSStyleDeclaration.prototype.item = function(index) {
+    if(typeof index === 'undefined') {
+        throw Error('1 argument required, but only 0 present.');
+    }
+
+    var properties = this.getProperties();
+    this._handleParseError();
+
+    return Array.from(properties.keys())[index];
+};
+
+/**
+ * Return all properties of the node.
+ *
+ * @return {Map} properties that is a Map with propertyName as key and property (propertyValue + propertyPriority) as value.
+ */
+CSSStyleDeclaration.prototype.getProperties = function() {
+    this._loadCssText();
+    return this.properties;
+};
+
+
+// writes to properties
+
+/**
+ * Remove a property from the CSS declaration block.
+ *
+ * @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.
+ */
+CSSStyleDeclaration.prototype.removeProperty = function(propertyName) {
+    if(typeof propertyName === 'undefined') {
+        throw Error('1 argument required, but only 0 present.');
+    }
+
+    this.hasStyle();
+
+    var properties = this.getProperties();
+    this._handleParseError();
+
+    var oldValue = this.getPropertyValue(propertyName);
+    properties.delete(propertyName.trim());
+    return oldValue;
+};
+
+/**
+ * Modify an existing CSS property or creates a new CSS property in the declaration block.
+ *
+ * @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}
+ */
+CSSStyleDeclaration.prototype.setProperty = function(propertyName, value, priority) {
+    if(typeof propertyName === 'undefined') {
+        throw Error('propertyName argument required, but only not present.');
+    }
+
+    this.hasStyle();
+
+    var properties = this.getProperties();
+    this._handleParseError();
+
+    var property = {
+        value: value.trim(),
+        priority: priority.trim()
+    };
+    properties.set(propertyName.trim(), property);
+
+    return property;
+};
+
+
+module.exports = CSSStyleDeclaration;
diff --git a/vendor/svgclean/src/svgclean/css-tools.js b/vendor/svgclean/src/svgclean/css-tools.js
new file mode 100644
index 000000000..6517892b8
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/css-tools.js
@@ -0,0 +1,222 @@
+'use strict';
+
+var csstree     = require('css-tree'),
+    List        = csstree.List,
+    stable      = require('stable'),
+    specificity = require('csso/lib/restructure/prepare/specificity');
+
+
+/**
+ * Flatten a CSS AST to a selectors list.
+ *
+ * @param {Object} cssAst css-tree AST to flatten
+ * @return {Array} selectors
+ */
+function flattenToSelectors(cssAst) {
+    var selectors = [];
+
+    csstree.walk(cssAst, {visit: 'Rule', enter: function(node) {
+        if (node.type !== 'Rule') {
+            return;
+        }
+
+        var atrule = this.atrule;
+        var rule = node;
+
+        node.prelude.children.each(function(selectorNode, selectorItem) {
+            var selector = {
+                item: selectorItem,
+                atrule: atrule,
+                rule: rule,
+                pseudos: []
+            };
+
+            selectorNode.children.each(function(selectorChildNode, selectorChildItem, selectorChildList) {
+                if (selectorChildNode.type === 'PseudoClassSelector' ||
+                    selectorChildNode.type === 'PseudoElementSelector') {
+                    selector.pseudos.push({
+                        item: selectorChildItem,
+                        list: selectorChildList
+                    });
+                }
+            });
+
+            selectors.push(selector);
+        });
+    }});
+
+    return selectors;
+}
+
+/**
+ * Filter selectors by Media Query.
+ *
+ * @param {Array} selectors to filter
+ * @param {Array} useMqs Array with strings of media queries that should pass (<name> <expression>)
+ * @return {Array} Filtered selectors that match the passed media queries
+ */
+function filterByMqs(selectors, useMqs) {
+    return selectors.filter(function(selector) {
+        if (selector.atrule === null) {
+            return ~useMqs.indexOf('');
+        }
+
+        var mqName = selector.atrule.name;
+        var mqStr = mqName;
+        if (selector.atrule.expression &&
+            selector.atrule.expression.children.first().type === 'MediaQueryList') {
+            var mqExpr = csstree.generate(selector.atrule.expression);
+            mqStr = [mqName, mqExpr].join(' ');
+        }
+
+        return ~useMqs.indexOf(mqStr);
+    });
+}
+
+/**
+ * Filter selectors by the pseudo-elements and/or -classes they contain.
+ *
+ * @param {Array} selectors to filter
+ * @param {Array} usePseudos Array with 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
+ */
+function filterByPseudos(selectors, usePseudos) {
+    return selectors.filter(function(selector) {
+        var pseudoSelectorsStr = csstree.generate({
+            type: 'Selector',
+            children: new List().fromArray(selector.pseudos.map(function(pseudo) {
+                return pseudo.item.data;
+            }))
+        });
+        return ~usePseudos.indexOf(pseudoSelectorsStr);
+    });
+}
+
+/**
+ * Remove pseudo-elements and/or -classes from the selectors for proper matching.
+ *
+ * @param {Array} selectors to clean
+ * @return {Array} Selectors without pseudo-elements and/or -classes
+ */
+function cleanPseudos(selectors) {
+    selectors.forEach(function(selector) {
+        selector.pseudos.forEach(function(pseudo) {
+            pseudo.list.remove(pseudo.item);
+        });
+    });
+}
+
+
+/**
+ * Compares two selector specificities.
+ * extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
+ *
+ * @param {Array} aSpecificity Specificity of selector A
+ * @param {Array} bSpecificity Specificity of selector B
+ * @return {Number} Score of selector specificity A compared to selector specificity B
+ */
+function compareSpecificity(aSpecificity, bSpecificity) {
+    for (var i = 0; i < 4; i += 1) {
+        if (aSpecificity[i] < bSpecificity[i]) {
+            return -1;
+        } else if (aSpecificity[i] > bSpecificity[i]) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+
+/**
+ * Compare two simple selectors.
+ *
+ * @param {Object} aSimpleSelectorNode Simple selector A
+ * @param {Object} bSimpleSelectorNode Simple selector B
+ * @return {Number} Score of selector A compared to selector B
+ */
+function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) {
+    var aSpecificity = specificity(aSimpleSelectorNode),
+        bSpecificity = specificity(bSimpleSelectorNode);
+    return compareSpecificity(aSpecificity, bSpecificity);
+}
+
+function _bySelectorSpecificity(selectorA, selectorB) {
+    return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data);
+}
+
+
+/**
+ * Sort selectors stably by their specificity.
+ *
+ * @param {Array} selectors to be sorted
+ * @return {Array} Stable sorted selectors
+ */
+function sortSelectors(selectors) {
+    return stable(selectors, _bySelectorSpecificity);
+}
+
+
+/**
+ * Convert a css-tree AST style declaration to CSSStyleDeclaration property.
+ *
+ * @param {Object} declaration css-tree style declaration
+ * @return {Object} CSSStyleDeclaration property
+ */
+function csstreeToStyleDeclaration(declaration) {
+    var propertyName = declaration.property,
+        propertyValue = csstree.generate(declaration.value),
+        propertyPriority = (declaration.important ? 'important' : '');
+    return {
+        name: propertyName,
+        value: propertyValue,
+        priority: propertyPriority
+    };
+}
+
+
+/**
+ * Gets the CSS string of a style element
+ *
+ * @param {Object} element style element
+ * @return {String|Array} CSS string or empty array if no styles are set
+ */
+function getCssStr(elem) {
+    return elem.content[0].text || elem.content[0].cdata || [];
+}
+
+/**
+ * Sets the CSS string of a style element
+ *
+ * @param {Object} element style element
+ * @param {String} CSS string to be set
+ * @return {Object} reference to field with CSS
+ */
+function setCssStr(elem, css) {
+    // in case of cdata field
+    if(elem.content[0].cdata) {
+        elem.content[0].cdata = css;
+        return elem.content[0].cdata;
+    }
+
+    // in case of text field + if nothing was set yet
+    elem.content[0].text  = css;
+    return elem.content[0].text;
+}
+
+
+module.exports.flattenToSelectors = flattenToSelectors;
+
+module.exports.filterByMqs = filterByMqs;
+module.exports.filterByPseudos = filterByPseudos;
+module.exports.cleanPseudos = cleanPseudos;
+
+module.exports.compareSpecificity = compareSpecificity;
+module.exports.compareSimpleSelectorNode = compareSimpleSelectorNode;
+
+module.exports.sortSelectors = sortSelectors;
+
+module.exports.csstreeToStyleDeclaration = csstreeToStyleDeclaration;
+
+module.exports.getCssStr = getCssStr;
+module.exports.setCssStr = setCssStr;
diff --git a/vendor/svgclean/src/svgclean/js2svg.js b/vendor/svgclean/src/svgclean/js2svg.js
new file mode 100644
index 000000000..6ed123f9a
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/js2svg.js
@@ -0,0 +1,346 @@
+'use strict';
+
+const EOL = "\n";
+const textElem = require('./plugins/_collections.js').elemsGroups.textContent.concat('title');
+
+var defaults = {
+  doctypeStart: '<!DOCTYPE',
+  doctypeEnd: '>',
+  procInstStart: '<?',
+  procInstEnd: '?>',
+  tagOpenStart: '<',
+  tagOpenEnd: '>',
+  tagCloseStart: '</',
+  tagCloseEnd: '>',
+  tagShortStart: '<',
+  tagShortEnd: '/>',
+  attrStart: '="',
+  attrEnd: '"',
+  commentStart: '<!--',
+  commentEnd: '-->',
+  cdataStart: '<![CDATA[',
+  cdataEnd: ']]>',
+  textStart: '',
+  textEnd: '',
+  indent: 4,
+  regEntities: /[&'"<>]/g,
+  regValEntities: /[&"<>]/g,
+  encodeEntity: encodeEntity,
+  pretty: false,
+  useShortTags: true
+};
+
+var entities = {
+  '&': '&amp;',
+  '\'': '&apos;',
+  '"': '&quot;',
+  '>': '&gt;',
+  '<': '&lt;',
+};
+
+/**
+ * Convert SVG-as-JS object to SVG (XML) string.
+ *
+ * @param {Object} data input data
+ * @param {Object} config config
+ *
+ * @return {Object} output data
+ */
+module.exports = function(data, config) {
+  return new JS2SVG(config).convert(data);
+
+};
+
+function JS2SVG(config) {
+  if (config) {
+    this.config = Object.assign({}, defaults, config);
+  } else {
+    this.config = Object.assign({}, defaults);
+  }
+
+  var indent = this.config.indent;
+  if (typeof indent == 'number' && !isNaN(indent)) {
+    this.config.indent = (indent < 0) ? '\t' : ' '.repeat(indent);
+  } else if (typeof indent != 'string') {
+    this.config.indent = '    ';
+  }
+
+  if (this.config.pretty) {
+    this.config.doctypeEnd += EOL;
+    this.config.procInstEnd += EOL;
+    this.config.commentEnd += EOL;
+    this.config.cdataEnd += EOL;
+    this.config.tagShortEnd += EOL;
+    this.config.tagOpenEnd += EOL;
+    this.config.tagCloseEnd += EOL;
+    this.config.textEnd += EOL;
+  }
+
+  this.indentLevel = 0;
+  this.textContext = null;
+
+}
+
+function encodeEntity(char) {
+  return entities[char];
+}
+
+/**
+ * Start conversion.
+ *
+ * @param {Object} data input data
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.convert = function(data) {
+
+  var svg = '';
+
+  if (data.content) {
+
+    this.indentLevel++;
+
+    data.content.forEach(function(item) {
+
+      if (item.elem) {
+        svg += this.createElem(item);
+      } else if (item.text) {
+        svg += this.createText(item.text);
+      } else if (item.doctype) {
+        svg += this.createDoctype(item.doctype);
+      } else if (item.processinginstruction) {
+        svg += this.createProcInst(item.processinginstruction);
+      } else if (item.comment) {
+        svg += this.createComment(item.comment);
+      } else if (item.cdata) {
+        svg += this.createCDATA(item.cdata);
+      }
+
+    }, this);
+
+  }
+
+  this.indentLevel--;
+
+  return {
+    data: svg,
+    info: {
+      width: this.width,
+      height: this.height
+    }
+  };
+
+};
+
+/**
+ * Create indent string in accordance with the current node level.
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createIndent = function() {
+
+  var indent = '';
+
+  if (this.config.pretty && !this.textContext) {
+    indent = this.config.indent.repeat(this.indentLevel - 1);
+  }
+
+  return indent;
+
+};
+
+/**
+ * Create doctype tag.
+ *
+ * @param {String} doctype doctype body string
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createDoctype = function(doctype) {
+
+  return  this.config.doctypeStart +
+    doctype +
+    this.config.doctypeEnd;
+
+};
+
+/**
+ * Create XML Processing Instruction tag.
+ *
+ * @param {Object} instruction instruction object
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createProcInst = function(instruction) {
+
+  return  this.config.procInstStart +
+    instruction.name +
+    ' ' +
+    instruction.body +
+    this.config.procInstEnd;
+
+};
+
+/**
+ * Create comment tag.
+ *
+ * @param {String} comment comment body
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createComment = function(comment) {
+
+  return  this.config.commentStart +
+    comment +
+    this.config.commentEnd;
+
+};
+
+/**
+ * Create CDATA section.
+ *
+ * @param {String} cdata CDATA body
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createCDATA = function(cdata) {
+
+  return  this.createIndent() +
+    this.config.cdataStart +
+    cdata +
+    this.config.cdataEnd;
+
+};
+
+/**
+ * Create element tag.
+ *
+ * @param {Object} data element object
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createElem = function(data) {
+
+  // beautiful injection for obtaining SVG information :)
+  if (
+    data.isElem('svg') &&
+      data.hasAttr('width') &&
+      data.hasAttr('height')
+  ) {
+    this.width = data.attr('width').value;
+    this.height = data.attr('height').value;
+  }
+
+  // empty element and short tag
+  if (data.isEmpty()) {
+    if (this.config.useShortTags) {
+      return this.createIndent() +
+        this.config.tagShortStart +
+        data.elem +
+        this.createAttrs(data) +
+        this.config.tagShortEnd;
+    } else {
+      return this.createIndent() +
+        this.config.tagShortStart +
+        data.elem +
+        this.createAttrs(data) +
+        this.config.tagOpenEnd +
+        this.config.tagCloseStart +
+        data.elem +
+        this.config.tagCloseEnd;
+    }
+    // non-empty element
+  } else {
+    var tagOpenStart = this.config.tagOpenStart,
+        tagOpenEnd = this.config.tagOpenEnd,
+        tagCloseStart = this.config.tagCloseStart,
+        tagCloseEnd = this.config.tagCloseEnd,
+        openIndent = this.createIndent(),
+        textIndent = '',
+        processedData = '',
+        dataEnd = '';
+
+    if (this.textContext) {
+      tagOpenStart = defaults.tagOpenStart;
+      tagOpenEnd = defaults.tagOpenEnd;
+      tagCloseStart = defaults.tagCloseStart;
+      tagCloseEnd = defaults.tagCloseEnd;
+      openIndent = '';
+    } else if (data.isElem(textElem)) {
+      if (this.config.pretty) {
+        textIndent += openIndent + this.config.indent;
+      }
+      this.textContext = data;
+    }
+
+    processedData += this.convert(data).data;
+
+    if (this.textContext == data) {
+      this.textContext = null;
+      if (this.config.pretty) dataEnd = EOL;
+    }
+
+    return  openIndent +
+      tagOpenStart +
+      data.elem +
+      this.createAttrs(data) +
+      tagOpenEnd +
+      textIndent +
+      processedData +
+      dataEnd +
+      this.createIndent() +
+      tagCloseStart +
+      data.elem +
+      tagCloseEnd;
+
+  }
+
+};
+
+/**
+ * Create element attributes.
+ *
+ * @param {Object} elem attributes object
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createAttrs = function(elem) {
+
+  var attrs = '';
+
+  elem.eachAttr(function(attr) {
+
+    if (attr.value !== undefined) {
+      attrs +=    ' ' +
+        attr.name +
+        this.config.attrStart +
+        String(attr.value).replace(this.config.regValEntities, this.config.encodeEntity) +
+        this.config.attrEnd;
+    }
+    else {
+      attrs +=    ' ' +
+        attr.name;
+    }
+
+
+  }, this);
+
+  return attrs;
+
+};
+
+/**
+ * Create text node.
+ *
+ * @param {String} text text
+ *
+ * @return {String}
+ */
+JS2SVG.prototype.createText = function(text) {
+
+  return  this.createIndent() +
+    this.config.textStart +
+    text.replace(this.config.regEntities, this.config.encodeEntity) +
+    (this.textContext ? '' : this.config.textEnd);
+
+};
diff --git a/vendor/svgclean/src/svgclean/jsAPI.js b/vendor/svgclean/src/svgclean/jsAPI.js
new file mode 100644
index 000000000..354e4672e
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/jsAPI.js
@@ -0,0 +1,372 @@
+'use strict';
+
+var cssSelect = require('css-select');
+
+var svgoCssSelectAdapter = require('./css-select-adapter');
+var cssSelectOpts = {
+  xmlMode: true,
+  adapter: svgoCssSelectAdapter
+};
+
+var JSAPI = module.exports = function(data, parentNode) {
+    Object.assign(this, data);
+    if (parentNode) {
+        Object.defineProperty(this, 'parentNode', {
+            writable: true,
+            value: parentNode
+        });
+    }
+};
+
+/**
+ * Perform a deep clone of this node.
+ *
+ * @return {Object} element
+ */
+JSAPI.prototype.clone = function() {
+    var node = this;
+    var nodeData = {};
+
+    Object.keys(node).forEach(function(key) {
+        if (key !== 'class' && key !== 'style' && key !== 'content') {
+            nodeData[key] = node[key];
+        }
+    });
+
+    // Deep-clone node data.
+    nodeData = JSON.parse(JSON.stringify(nodeData));
+
+    // parentNode gets set to a proper object by the parent clone,
+    // but it needs to be true/false now to do the right thing
+    // 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();
+            clonedChild.parentNode = clonedNode;
+            return clonedChild;
+        });
+    }
+
+    return clonedNode;
+};
+
+/**
+ * Determine if item is an element
+ * (any, with a specific name or in a names array).
+ *
+ * @param {String|Array} [param] element name or names arrays
+ * @return {Boolean}
+ */
+JSAPI.prototype.isElem = function(param) {
+
+    if (!param) return !!this.elem;
+
+    if (Array.isArray(param)) return !!this.elem && (param.indexOf(this.elem) > -1);
+
+    return !!this.elem && this.elem === param;
+
+};
+
+/**
+ * Renames an element
+ *
+ * @param {String} name new element name
+ * @return {Object} element
+ */
+JSAPI.prototype.renameElem = function(name) {
+
+    if (name && typeof name === 'string')
+        this.elem = this.local = name;
+
+    return this;
+
+};
+
+/**
+ * Determine if element is empty.
+ *
+ * @return {Boolean}
+ */
+ JSAPI.prototype.isEmpty = function() {
+
+    return !this.content || !this.content.length;
+
+};
+
+/**
+ * Find the closest ancestor of the current element.
+ * @param elemName
+ *
+ * @return {?Object}
+ */
+ JSAPI.prototype.closestElem = function(elemName) {
+    var elem = this;
+
+    while ((elem = elem.parentNode) && !elem.isElem(elemName));
+
+    return elem;
+};
+
+/**
+ * Changes content by removing elements and/or adding new elements.
+ *
+ * @param {Number} start Index at which to start changing the content.
+ * @param {Number} n Number of elements to remove.
+ * @param {Array|Object} [insertion] Elements to add to the content.
+ * @return {Array} Removed elements.
+ */
+ JSAPI.prototype.spliceContent = function(start, n, insertion) {
+
+    if (arguments.length < 2) return [];
+
+    if (!Array.isArray(insertion))
+        insertion = Array.apply(null, arguments).slice(2);
+
+    insertion.forEach(function(inner) { inner.parentNode = this }, this);
+
+    return this.content.splice.apply(this.content, [start, n].concat(insertion));
+
+
+};
+
+/**
+ * Determine if element has an attribute
+ * (any, or by name or by name + value).
+ *
+ * @param {String} [name] attribute name
+ * @param {String} [val] attribute value (will be toString()'ed)
+ * @return {Boolean}
+ */
+ JSAPI.prototype.hasAttr = function(name, val) {
+
+    if (!this.attrs || !Object.keys(this.attrs).length) return false;
+
+    if (!arguments.length) return !!this.attrs;
+
+    if (val !== undefined) return !!this.attrs[name] && this.attrs[name].value === val.toString();
+
+    return !!this.attrs[name];
+
+};
+
+/**
+ * Determine if element has an attribute by local name
+ * (any, or by name or by name + value).
+ *
+ * @param {String} [localName] local attribute name
+ * @param {Number|String|RegExp|Function} [val] attribute value (will be toString()'ed or executed, otherwise ignored)
+ * @return {Boolean}
+ */
+ JSAPI.prototype.hasAttrLocal = function(localName, val) {
+
+    if (!this.attrs || !Object.keys(this.attrs).length) return false;
+
+    if (!arguments.length) return !!this.attrs;
+
+    var callback;
+
+    switch (val != null && val.constructor && val.constructor.name) {
+        case 'Number':   // same as String
+        case 'String':   callback = stringValueTest; break;
+        case 'RegExp':   callback = regexpValueTest; break;
+        case 'Function': callback = funcValueTest; break;
+        default:         callback = nameTest;
+    }
+    return this.someAttr(callback);
+
+    function nameTest(attr) {
+        return attr.local === localName;
+    }
+
+    function stringValueTest(attr) {
+        return attr.local === localName && val == attr.value;
+    }
+
+    function regexpValueTest(attr) {
+        return attr.local === localName && val.test(attr.value);
+    }
+
+    function funcValueTest(attr) {
+        return attr.local === localName && val(attr.value);
+    }
+
+};
+
+/**
+ * Get a specific attribute from an element
+ * (by name or name + value).
+ *
+ * @param {String} name attribute name
+ * @param {String} [val] attribute value (will be toString()'ed)
+ * @return {Object|Undefined}
+ */
+ JSAPI.prototype.attr = function(name, val) {
+
+    if (!this.hasAttr() || !arguments.length) return undefined;
+
+    if (val !== undefined) return this.hasAttr(name, val) ? this.attrs[name] : undefined;
+
+    return this.attrs[name];
+
+};
+
+/**
+ * Get computed attribute value from an element
+ *
+ * @param {String} name attribute name
+ * @return {Object|Undefined}
+ */
+ JSAPI.prototype.computedAttr = function(name, val) {
+    /* jshint eqnull: true */
+    if (!arguments.length) return;
+
+    for (var elem = this; elem && (!elem.hasAttr(name) || !elem.attr(name).value); elem = elem.parentNode);
+
+    if (val != null) {
+        return elem ? elem.hasAttr(name, val) : false;
+    } else if (elem && elem.hasAttr(name)) {
+        return elem.attrs[name].value;
+    }
+
+};
+
+/**
+ * Remove a specific attribute.
+ *
+ * @param {String|Array} name attribute name
+ * @param {String} [val] attribute value
+ * @return {Boolean}
+ */
+ JSAPI.prototype.removeAttr = function(name, val, recursive) {
+
+    if (!arguments.length) return false;
+
+    if (Array.isArray(name)) {
+        name.forEach(this.removeAttr, this);
+        return false;
+    }
+
+    if (!this.hasAttr(name)) return false;
+
+    if (!recursive && val && this.attrs[name].value !== val) return false;
+
+    delete this.attrs[name];
+
+    if (!Object.keys(this.attrs).length) delete this.attrs;
+
+    return true;
+
+};
+
+/**
+ * Add attribute.
+ *
+ * @param {Object} [attr={}] attribute object
+ * @return {Object|Boolean} created attribute or false if no attr was passed in
+ */
+ JSAPI.prototype.addAttr = function(attr) {
+    attr = attr || {};
+
+    if (attr.name === undefined ||
+        attr.prefix === undefined ||
+        attr.local === undefined
+    ) return false;
+
+    this.attrs = this.attrs || {};
+    this.attrs[attr.name] = attr;
+
+    if(attr.name === 'class') { // newly added class attribute
+        this.class.hasClass();
+    }
+
+    if(attr.name === 'style') { // newly added style attribute
+        this.style.hasStyle();
+    }
+
+    return this.attrs[attr.name];
+
+};
+
+/**
+ * Iterates over all attributes.
+ *
+ * @param {Function} callback callback
+ * @param {Object} [context] callback context
+ * @return {Boolean} false if there are no any attributes
+ */
+ JSAPI.prototype.eachAttr = function(callback, context) {
+
+    if (!this.hasAttr()) return false;
+
+    for (var name in this.attrs) {
+        callback.call(context, this.attrs[name]);
+    }
+
+    return true;
+
+};
+
+/**
+ * Tests whether some attribute passes the test.
+ *
+ * @param {Function} callback callback
+ * @param {Object} [context] callback context
+ * @return {Boolean} false if there are no any attributes
+ */
+ JSAPI.prototype.someAttr = function(callback, context) {
+
+    if (!this.hasAttr()) return false;
+
+    for (var name in this.attrs) {
+        if (callback.call(context, this.attrs[name])) return true;
+    }
+
+    return false;
+
+};
+
+/**
+ * Evaluate a string of CSS selectors against the element and returns matched elements.
+ *
+ * @param {String} selectors CSS selector(s) string
+ * @return {Array} null if no elements matched
+ */
+ JSAPI.prototype.querySelectorAll = function(selectors) {
+
+   var matchedEls = cssSelect.selectAll(selectors, this, cssSelectOpts);
+
+   return matchedEls.length > 0 ? matchedEls : null;
+
+};
+
+/**
+ * Evaluate a string of CSS selectors against the element and returns only the first matched element.
+ *
+ * @param {String} selectors CSS selector(s) string
+ * @return {Array} null if no element matched
+ */
+ JSAPI.prototype.querySelector = function(selectors) {
+
+   return cssSelect.selectOne(selectors, this, cssSelectOpts);
+
+};
+
+/**
+ * Test if a selector matches a given element.
+ *
+ * @param {String} selector CSS selector string
+ * @return {Boolean} true if element would be selected by selector string, false if it does not
+ */
+ JSAPI.prototype.matches = function(selector) {
+
+   return cssSelect.is(this, selector, cssSelectOpts);
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/_collections.js b/vendor/svgclean/src/svgclean/plugins/_collections.js
new file mode 100644
index 000000000..8179f3050
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/_collections.js
@@ -0,0 +1,2558 @@
+'use strict';
+
+// http://www.w3.org/TR/SVG11/intro.html#Definitions
+exports.elemsGroups = {
+    animation: ['animate', 'animateColor', 'animateMotion', 'animateTransform', 'set'],
+    descriptive: ['desc', 'metadata', 'title'],
+    shape: ['circle', 'ellipse', 'line', 'path', 'polygon', 'polyline', 'rect'],
+    structural: ['defs', 'g', 'svg', 'symbol', 'use'],
+    paintServer: ['solidColor', 'linearGradient', 'radialGradient', 'meshGradient', 'pattern', 'hatch'],
+    nonRendering: ['linearGradient', 'radialGradient', 'pattern', 'clipPath', 'mask', 'marker', 'symbol', 'filter', 'solidColor'],
+    container: ['a', 'defs', 'g', 'marker', 'mask', 'missing-glyph', 'pattern', 'svg', 'switch', 'symbol', 'foreignObject'],
+    textContent: ['altGlyph', 'altGlyphDef', 'altGlyphItem', 'glyph', 'glyphRef', 'textPath', 'text', 'tref', 'tspan'],
+    textContentChild: ['altGlyph', 'textPath', 'tref', 'tspan'],
+    lightSource: ['feDiffuseLighting', 'feSpecularLighting', 'feDistantLight', 'fePointLight', 'feSpotLight'],
+    filterPrimitive: ['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage', 'feMerge', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile', 'feTurbulence']
+};
+
+exports.pathElems = ['path', 'glyph', 'missing-glyph'];
+
+// http://www.w3.org/TR/SVG11/intro.html#Definitions
+exports.attrsGroups = {
+    animationAddition: ['additive', 'accumulate'],
+    animationAttributeTarget: ['attributeType', 'attributeName'],
+    animationEvent: ['onbegin', 'onend', 'onrepeat', 'onload'],
+    animationTiming: ['begin', 'dur', 'end', 'min', 'max', 'restart', 'repeatCount', 'repeatDur', 'fill'],
+    animationValue: ['calcMode', 'values', 'keyTimes', 'keySplines', 'from', 'to', 'by'],
+    conditionalProcessing: ['requiredFeatures', 'requiredExtensions', 'systemLanguage'],
+    core: ['id', 'tabindex', 'xml:base', 'xml:lang', 'xml:space'],
+    graphicalEvent: ['onfocusin', 'onfocusout', 'onactivate', 'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onload'],
+    presentation: [
+        'alignment-baseline',
+        'baseline-shift',
+        'clip',
+        'clip-path',
+        'clip-rule',
+        'color',
+        'color-interpolation',
+        'color-interpolation-filters',
+        'color-profile',
+        'color-rendering',
+        'cursor',
+        'direction',
+        'display',
+        'dominant-baseline',
+        'enable-background',
+        'fill',
+        'fill-opacity',
+        'fill-rule',
+        'filter',
+        'flood-color',
+        'flood-opacity',
+        'font-family',
+        'font-size',
+        'font-size-adjust',
+        'font-stretch',
+        'font-style',
+        'font-variant',
+        'font-weight',
+        'glyph-orientation-horizontal',
+        'glyph-orientation-vertical',
+        'image-rendering',
+        'letter-spacing',
+        'lighting-color',
+        'marker-end',
+        'marker-mid',
+        'marker-start',
+        'mask',
+        'opacity',
+        'overflow',
+        'paint-order',
+        'pointer-events',
+        'shape-rendering',
+        'stop-color',
+        'stop-opacity',
+        'stroke',
+        'stroke-dasharray',
+        'stroke-dashoffset',
+        'stroke-linecap',
+        'stroke-linejoin',
+        'stroke-miterlimit',
+        'stroke-opacity',
+        'stroke-width',
+        'text-anchor',
+        'text-decoration',
+        'text-overflow',
+        'text-rendering',
+        'transform',
+        'unicode-bidi',
+        'vector-effect',
+        'visibility',
+        'word-spacing',
+        'writing-mode'
+    ],
+    xlink: ['xlink:href', 'xlink:show', 'xlink:actuate', 'xlink:type', 'xlink:role', 'xlink:arcrole', 'xlink:title'],
+    documentEvent: ['onunload', 'onabort', 'onerror', 'onresize', 'onscroll', 'onzoom'],
+    filterPrimitive: ['x', 'y', 'width', 'height', 'result'],
+    transferFunction: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset']
+};
+
+exports.attrsGroupsDefaults = {
+    core: {'xml:space': 'preserve'},
+    filterPrimitive: {x: '0', y: '0', width: '100%', height: '100%'},
+    presentation: {
+        clip: 'auto',
+        'clip-path': 'none',
+        'clip-rule': 'nonzero',
+        mask: 'none',
+        opacity: '1',
+        'stop-color': '#000',
+        'stop-opacity': '1',
+        'fill-opacity': '1',
+        'fill-rule': 'nonzero',
+        fill: '#000',
+        stroke: 'none',
+        'stroke-width': '1',
+        'stroke-linecap': 'butt',
+        'stroke-linejoin': 'miter',
+        'stroke-miterlimit': '4',
+        'stroke-dasharray': 'none',
+        'stroke-dashoffset': '0',
+        'stroke-opacity': '1',
+        'paint-order': 'normal',
+        'vector-effect': 'none',
+        display: 'inline',
+        visibility: 'visible',
+        'marker-start': 'none',
+        'marker-mid': 'none',
+        'marker-end': 'none',
+        'color-interpolation': 'sRGB',
+        'color-interpolation-filters': 'linearRGB',
+        'color-rendering': 'auto',
+        'shape-rendering': 'auto',
+        'text-rendering': 'auto',
+        'image-rendering': 'auto',
+        'font-style': 'normal',
+        'font-variant': 'normal',
+        'font-weight': 'normal',
+        'font-stretch': 'normal',
+        'font-size': 'medium',
+        'font-size-adjust': 'none',
+        kerning: 'auto',
+        'letter-spacing': 'normal',
+        'word-spacing': 'normal',
+        'text-decoration': 'none',
+        'text-anchor': 'start',
+        'text-overflow': 'clip',
+        'writing-mode': 'lr-tb',
+        'glyph-orientation-vertical': 'auto',
+        'glyph-orientation-horizontal': '0deg',
+        direction: 'ltr',
+        'unicode-bidi': 'normal',
+        'dominant-baseline': 'auto',
+        'alignment-baseline': 'baseline',
+        'baseline-shift': 'baseline'
+    },
+    transferFunction: {slope: '1', intercept: '0', amplitude: '1', exponent: '1', offset: '0'}
+};
+
+// http://www.w3.org/TR/SVG11/eltindex.html
+exports.elems = {
+    a: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'target'
+        ],
+        defaults: {
+            target: '_self'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    altGlyph: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x',
+            'y',
+            'dx',
+            'dy',
+            'glyphRef',
+            'format',
+            'rotate'
+        ]
+    },
+    altGlyphDef: {
+        attrsGroups: [
+            'core'
+        ],
+        content: [
+            'glyphRef'
+        ]
+    },
+    altGlyphItem: {
+        attrsGroups: [
+            'core'
+        ],
+        content: [
+            'glyphRef',
+            'altGlyphItem'
+        ]
+    },
+    animate: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animationAddition',
+            'animationAttributeTarget',
+            'animationEvent',
+            'animationTiming',
+            'animationValue',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'externalResourcesRequired'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    animateColor: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animationEvent',
+            'xlink',
+            'animationAttributeTarget',
+            'animationTiming',
+            'animationValue',
+            'animationAddition',
+            'presentation'
+        ],
+        attrs: [
+            'externalResourcesRequired'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    animateMotion: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animationEvent',
+            'xlink',
+            'animationTiming',
+            'animationValue',
+            'animationAddition'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'path',
+            'keyPoints',
+            'rotate',
+            'origin'
+        ],
+        defaults: {
+            'rotate': '0'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'mpath'
+        ]
+    },
+    animateTransform: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animationEvent',
+            'xlink',
+            'animationAttributeTarget',
+            'animationTiming',
+            'animationValue',
+            'animationAddition'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'type'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    circle: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'cx',
+            'cy',
+            'r'
+        ],
+        defaults: {
+            cx: '0',
+            cy: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    clipPath: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'clipPathUnits'
+        ],
+        defaults: {
+            clipPathUnits: 'userSpaceOnUse'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape'
+        ],
+        content: [
+            'text',
+            'use'
+        ]
+    },
+    'color-profile': {
+        attrsGroups: [
+            'core',
+            'xlink'
+        ],
+        attrs: [
+            'local',
+            'name',
+            'rendering-intent'
+        ],
+        defaults: {
+            name: 'sRGB',
+            'rendering-intent': 'auto'
+        },
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    cursor: {
+        attrsGroups: [
+            'core',
+            'conditionalProcessing',
+            'xlink'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'x',
+            'y'
+        ],
+        defaults: {
+            x: '0',
+            y: '0'
+        },
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    defs: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    desc: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ]
+    },
+    ellipse: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'cx',
+            'cy',
+            'rx',
+            'ry'
+        ],
+        defaults: {
+            cx: '0',
+            cy: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    feBlend: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            // TODO: in - 'If no value is provided and this is the first filter primitive,
+            // then this filter primitive will use SourceGraphic as its input'
+            'in',
+            'in2',
+            'mode'
+        ],
+        defaults: {
+            mode: 'normal'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feColorMatrix: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'type',
+            'values'
+        ],
+        defaults: {
+            type: 'matrix'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feComponentTransfer: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in'
+        ],
+        content: [
+            'feFuncA',
+            'feFuncB',
+            'feFuncG',
+            'feFuncR'
+        ]
+    },
+    feComposite: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'in2',
+            'operator',
+            'k1',
+            'k2',
+            'k3',
+            'k4'
+        ],
+        defaults: {
+            operator: 'over',
+            k1: '0',
+            k2: '0',
+            k3: '0',
+            k4: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feConvolveMatrix: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'order',
+            'kernelMatrix',
+            // TODO: divisor - 'The default value is the sum of all values in kernelMatrix,
+            // with the exception that if the sum is zero, then the divisor is set to 1'
+            'divisor',
+            'bias',
+            // TODO: targetX - 'By default, the convolution matrix is centered in X over each
+            // pixel of the input image (i.e., targetX = floor ( orderX / 2 ))'
+            'targetX',
+            'targetY',
+            'edgeMode',
+            // TODO: kernelUnitLength - 'The first number is the <dx> value. The second number
+            // is the <dy> value. If the <dy> value is not specified, it defaults to the same value as <dx>'
+            'kernelUnitLength',
+            'preserveAlpha'
+        ],
+        defaults: {
+            order: '3',
+            bias: '0',
+            edgeMode: 'duplicate',
+            preserveAlpha: 'false'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feDiffuseLighting: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'surfaceScale',
+            'diffuseConstant',
+            'kernelUnitLength'
+        ],
+        defaults: {
+            surfaceScale: '1',
+            diffuseConstant: '1'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            // TODO: 'exactly one light source element, in any order'
+            'feDistantLight',
+            'fePointLight',
+            'feSpotLight'
+        ]
+    },
+    feDisplacementMap: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'in2',
+            'scale',
+            'xChannelSelector',
+            'yChannelSelector'
+        ],
+        defaults: {
+            scale: '0',
+            xChannelSelector: 'A',
+            yChannelSelector: 'A'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feDistantLight: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'azimuth',
+            'elevation'
+        ],
+        defaults: {
+            azimuth: '0',
+            elevation: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feFlood: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        content: [
+            'animate',
+            'animateColor',
+            'set'
+        ]
+    },
+    feFuncA: {
+        attrsGroups: [
+            'core',
+            'transferFunction'
+        ],
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feFuncB: {
+        attrsGroups: [
+            'core',
+            'transferFunction'
+        ],
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feFuncG: {
+        attrsGroups: [
+            'core',
+            'transferFunction'
+        ],
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feFuncR: {
+        attrsGroups: [
+            'core',
+            'transferFunction'
+        ],
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feGaussianBlur: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'stdDeviation'
+        ],
+        defaults: {
+            stdDeviation: '0'
+        },
+        content: [
+            'set',
+            'animate'
+        ]
+    },
+    feImage: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'preserveAspectRatio',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            preserveAspectRatio: 'xMidYMid meet'
+        },
+        content: [
+            'animate',
+            'animateTransform',
+            'set'
+        ]
+    },
+    feMerge: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        content: [
+            'feMergeNode'
+        ]
+    },
+    feMergeNode: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'in'
+        ],
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feMorphology: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'operator',
+            'radius'
+        ],
+        defaults: {
+            operator: 'erode',
+            radius: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feOffset: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'dx',
+            'dy'
+        ],
+        defaults: {
+            dx: '0',
+            dy: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    fePointLight: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'x',
+            'y',
+            'z'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            z: '0'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feSpecularLighting: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in',
+            'surfaceScale',
+            'specularConstant',
+            'specularExponent',
+            'kernelUnitLength'
+        ],
+        defaults: {
+            surfaceScale: '1',
+            specularConstant: '1',
+            specularExponent: '1'
+        },
+        contentGroups: [
+            'descriptive',
+            // TODO: exactly one 'light source element'
+            'lightSource'
+        ]
+    },
+    feSpotLight: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'x',
+            'y',
+            'z',
+            'pointsAtX',
+            'pointsAtY',
+            'pointsAtZ',
+            'specularExponent',
+            'limitingConeAngle'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            z: '0',
+            pointsAtX: '0',
+            pointsAtY: '0',
+            pointsAtZ: '0',
+            specularExponent: '1'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feTile: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'in'
+        ],
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    feTurbulence: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'filterPrimitive'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'baseFrequency',
+            'numOctaves',
+            'seed',
+            'stitchTiles',
+            'type'
+        ],
+        defaults: {
+            baseFrequency: '0',
+            numOctaves: '1',
+            seed: '0',
+            stitchTiles: 'noStitch',
+            type: 'turbulence'
+        },
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    filter: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x',
+            'y',
+            'width',
+            'height',
+            'filterRes',
+            'filterUnits',
+            'primitiveUnits',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            primitiveUnits: 'userSpaceOnUse',
+            x: '-10%',
+            y: '-10%',
+            width: '120%',
+            height: '120%'
+        },
+        contentGroups: [
+            'descriptive',
+            'filterPrimitive'
+        ],
+        content: [
+            'animate',
+            'set'
+        ]
+    },
+    font: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'horiz-origin-x',
+            'horiz-origin-y',
+            'horiz-adv-x',
+            'vert-origin-x',
+            'vert-origin-y',
+            'vert-adv-y'
+        ],
+        defaults: {
+            'horiz-origin-x': '0',
+            'horiz-origin-y': '0'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'font-face',
+            'glyph',
+            'hkern',
+            'missing-glyph',
+            'vkern'
+        ]
+    },
+    'font-face': {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'font-family',
+            'font-style',
+            'font-variant',
+            'font-weight',
+            'font-stretch',
+            'font-size',
+            'unicode-range',
+            'units-per-em',
+            'panose-1',
+            'stemv',
+            'stemh',
+            'slope',
+            'cap-height',
+            'x-height',
+            'accent-height',
+            'ascent',
+            'descent',
+            'widths',
+            'bbox',
+            'ideographic',
+            'alphabetic',
+            'mathematical',
+            'hanging',
+            'v-ideographic',
+            'v-alphabetic',
+            'v-mathematical',
+            'v-hanging',
+            'underline-position',
+            'underline-thickness',
+            'strikethrough-position',
+            'strikethrough-thickness',
+            'overline-position',
+            'overline-thickness'
+        ],
+        defaults: {
+            'font-style': 'all',
+            'font-variant': 'normal',
+            'font-weight': 'all',
+            'font-stretch': 'normal',
+            'unicode-range': 'U+0-10FFFF',
+            'units-per-em': '1000',
+            'panose-1': '0 0 0 0 0 0 0 0 0 0',
+            'slope': '0'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            // TODO: "at most one 'font-face-src' element"
+            'font-face-src'
+        ]
+    },
+    // TODO: empty content
+    'font-face-format': {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'string'
+        ]
+    },
+    'font-face-name': {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'name'
+        ]
+    },
+    'font-face-src': {
+        attrsGroups: [
+            'core'
+        ],
+        content: [
+            'font-face-name',
+            'font-face-uri'
+        ]
+    },
+    'font-face-uri': {
+        attrsGroups: [
+            'core',
+            'xlink'
+        ],
+        attrs: [
+            'href',
+            'xlink:href'
+        ],
+        content: [
+            'font-face-format'
+        ]
+    },
+    foreignObject: {
+        attrsGroups: [
+            'core',
+            'conditionalProcessing',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'x',
+            'y',
+            'width',
+            'height'
+        ],
+        defaults: {
+            x: 0,
+            y: 0
+        }
+    },
+    g: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    glyph: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'd',
+            'horiz-adv-x',
+            'vert-origin-x',
+            'vert-origin-y',
+            'vert-adv-y',
+            'unicode',
+            'glyph-name',
+            'orientation',
+            'arabic-form',
+            'lang'
+        ],
+        defaults: {
+            'arabic-form': 'initial'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ],
+    },
+    glyphRef: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'd',
+            'horiz-adv-x',
+            'vert-origin-x',
+            'vert-origin-y',
+            'vert-adv-y'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    hatch: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'x',
+            'y',
+            'pitch',
+            'rotate',
+            'hatchUnits',
+            'hatchContentUnits',
+            'transform'
+        ],
+        defaults: {
+            hatchUnits: 'objectBoundingBox',
+            hatchContentUnits: 'userSpaceOnUse',
+            x: '0',
+            y: '0',
+            pitch: '0',
+            rotate: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ],
+        content: [
+            'hatchPath'
+        ]
+    },
+    hatchPath: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'd',
+            'offset'
+        ],
+        defaults: {
+            offset: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    hkern: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'u1',
+            'g1',
+            'u2',
+            'g2',
+            'k'
+        ]
+    },
+    image: {
+        attrsGroups: [
+            'core',
+            'conditionalProcessing',
+            'graphicalEvent',
+            'xlink',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'preserveAspectRatio',
+            'transform',
+            'x',
+            'y',
+            'width',
+            'height',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            preserveAspectRatio: 'xMidYMid meet'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    line: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'x1',
+            'y1',
+            'x2',
+            'y2'
+        ],
+        defaults: {
+            x1: '0',
+            y1: '0',
+            x2: '0',
+            y2: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    linearGradient: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x1',
+            'y1',
+            'x2',
+            'y2',
+            'gradientUnits',
+            'gradientTransform',
+            'spreadMethod',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            x1: '0',
+            y1: '0',
+            x2: '100%',
+            y2: '0',
+            spreadMethod: 'pad'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'animate',
+            'animateTransform',
+            'set',
+            'stop'
+        ]
+    },
+    marker: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'viewBox',
+            'preserveAspectRatio',
+            'refX',
+            'refY',
+            'markerUnits',
+            'markerWidth',
+            'markerHeight',
+            'orient'
+        ],
+        defaults: {
+            markerUnits: 'strokeWidth',
+            refX: '0',
+            refY: '0',
+            markerWidth: '3',
+            markerHeight: '3'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    mask: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x',
+            'y',
+            'width',
+            'height',
+            'maskUnits',
+            'maskContentUnits'
+        ],
+        defaults: {
+            maskUnits: 'objectBoundingBox',
+            maskContentUnits: 'userSpaceOnUse',
+            x: '-10%',
+            y: '-10%',
+            width: '120%',
+            height: '120%'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    metadata: {
+        attrsGroups: [
+            'core'
+        ]
+    },
+    'missing-glyph': {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'd',
+            'horiz-adv-x',
+            'vert-origin-x',
+            'vert-origin-y',
+            'vert-adv-y'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    mpath: {
+        attrsGroups: [
+            'core',
+            'xlink'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'href',
+            'xlink:href'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    path: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'd',
+            'pathLength'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    pattern: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'viewBox',
+            'preserveAspectRatio',
+            'x',
+            'y',
+            'width',
+            'height',
+            'patternUnits',
+            'patternContentUnits',
+            'patternTransform',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            patternUnits: 'objectBoundingBox',
+            patternContentUnits: 'userSpaceOnUse',
+            x: '0',
+            y: '0',
+            width: '0',
+            height: '0',
+            preserveAspectRatio: 'xMidYMid meet'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'paintServer',
+            'shape',
+            'structural'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    polygon: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'points'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    polyline: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'points'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    radialGradient: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'cx',
+            'cy',
+            'r',
+            'fx',
+            'fy',
+            'fr',
+            'gradientUnits',
+            'gradientTransform',
+            'spreadMethod',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            gradientUnits: 'objectBoundingBox',
+            cx: '50%',
+            cy: '50%',
+            r: '50%'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'animate',
+            'animateTransform',
+            'set',
+            'stop'
+        ]
+    },
+    meshGradient: {
+        attrsGroups: [
+            'core',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'x',
+            'y',
+            'gradientUnits',
+            'transform'
+        ],
+        contentGroups: [
+            'descriptive',
+            'paintServer',
+            'animation',
+        ],
+        content: [
+            'meshRow'
+        ]
+    },
+    meshRow: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'meshPatch'
+        ]
+    },
+    meshPatch: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'stop'
+        ]
+    },
+    rect: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'x',
+            'y',
+            'width',
+            'height',
+            'rx',
+            'ry'
+        ],
+        defaults: {
+            x: '0',
+            y: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    script: {
+        attrsGroups: [
+            'core',
+            'xlink'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'type',
+            'href',
+            'xlink:href'
+        ]
+    },
+    set: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'animation',
+            'xlink',
+            'animationAttributeTarget',
+            'animationTiming',
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'to'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    solidColor: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ],
+        contentGroups: [
+            'paintServer'
+        ]
+    },
+    stop: {
+        attrsGroups: [
+            'core',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'offset',
+            'path'
+        ],
+        content: [
+            'animate',
+            'animateColor',
+            'set'
+        ]
+    },
+    style: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'type',
+            'media',
+            'title'
+        ],
+        defaults: {
+            type: 'text/css'
+        }
+    },
+    svg: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'documentEvent',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'x',
+            'y',
+            'width',
+            'height',
+            'viewBox',
+            'preserveAspectRatio',
+            'zoomAndPan',
+            'version',
+            'baseProfile',
+            'contentScriptType',
+            'contentStyleType'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            width: '100%',
+            height: '100%',
+            preserveAspectRatio: 'xMidYMid meet',
+            zoomAndPan: 'magnify',
+            version: '1.1',
+            baseProfile: 'none',
+            contentScriptType: 'application/ecmascript',
+            contentStyleType: 'text/css'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    switch: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform'
+        ],
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape'
+        ],
+        content: [
+            'a',
+            'foreignObject',
+            'g',
+            'image',
+            'svg',
+            'switch',
+            'text',
+            'use'
+        ]
+    },
+    symbol: {
+        attrsGroups: [
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'preserveAspectRatio',
+            'viewBox',
+            'refX',
+            'refY'
+        ],
+        defaults: {
+            refX: 0,
+            refY: 0
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'shape',
+            'structural',
+            'paintServer'
+        ],
+        content: [
+            'a',
+            'altGlyphDef',
+            'clipPath',
+            'color-profile',
+            'cursor',
+            'filter',
+            'font',
+            'font-face',
+            'foreignObject',
+            'image',
+            'marker',
+            'mask',
+            'pattern',
+            'script',
+            'style',
+            'switch',
+            'text',
+            'view'
+        ]
+    },
+    text: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'lengthAdjust',
+            'x',
+            'y',
+            'dx',
+            'dy',
+            'rotate',
+            'textLength'
+        ],
+        defaults: {
+            x: '0',
+            y: '0',
+            lengthAdjust: 'spacing'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive',
+            'textContentChild'
+        ],
+        content: [
+            'a'
+        ]
+    },
+    textPath: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'href',
+            'xlink:href',
+            'startOffset',
+            'method',
+            'spacing',
+            'd'
+        ],
+        defaults: {
+            startOffset: '0',
+            method: 'align',
+            spacing: 'exact'
+        },
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'a',
+            'altGlyph',
+            'animate',
+            'animateColor',
+            'set',
+            'tref',
+            'tspan'
+        ]
+    },
+    title: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'class',
+            'style'
+        ]
+    },
+    tref: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'href',
+            'xlink:href'
+        ],
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'animate',
+            'animateColor',
+            'set'
+        ]
+    },
+    tspan: {
+        attrsGroups: [
+            'conditionalProcessing',
+            'core',
+            'graphicalEvent',
+            'presentation'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'x',
+            'y',
+            'dx',
+            'dy',
+            'rotate',
+            'textLength',
+            'lengthAdjust'
+        ],
+        contentGroups: [
+            'descriptive'
+        ],
+        content: [
+            'a',
+            'altGlyph',
+            'animate',
+            'animateColor',
+            'set',
+            'tref',
+            'tspan'
+        ]
+    },
+    use: {
+        attrsGroups: [
+            'core',
+            'conditionalProcessing',
+            'graphicalEvent',
+            'presentation',
+            'xlink'
+        ],
+        attrs: [
+            'class',
+            'style',
+            'externalResourcesRequired',
+            'transform',
+            'x',
+            'y',
+            'width',
+            'height',
+            'href',
+            'xlink:href'
+        ],
+        defaults: {
+            x: '0',
+            y: '0'
+        },
+        contentGroups: [
+            'animation',
+            'descriptive'
+        ]
+    },
+    view: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'externalResourcesRequired',
+            'viewBox',
+            'preserveAspectRatio',
+            'zoomAndPan',
+            'viewTarget'
+        ],
+        contentGroups: [
+            'descriptive'
+        ]
+    },
+    vkern: {
+        attrsGroups: [
+            'core'
+        ],
+        attrs: [
+            'u1',
+            'g1',
+            'u2',
+            'g2',
+            'k'
+        ]
+    }
+};
+
+// http://wiki.inkscape.org/wiki/index.php/Inkscape-specific_XML_attributes
+exports.editorNamespaces = [
+    'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
+    'http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd',
+    'http://www.inkscape.org/namespaces/inkscape',
+    'http://www.bohemiancoding.com/sketch/ns',
+    'http://ns.adobe.com/AdobeIllustrator/10.0/',
+    'http://ns.adobe.com/Graphs/1.0/',
+    'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/',
+    'http://ns.adobe.com/Variables/1.0/',
+    'http://ns.adobe.com/SaveForWeb/1.0/',
+    'http://ns.adobe.com/Extensibility/1.0/',
+    '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://schemas.microsoft.com/visio/2003/SVGExtensions/',
+    'http://taptrix.com/vectorillustrator/svg_extensions',
+    'http://www.figma.com/figma/ns',
+    'http://purl.org/dc/elements/1.1/',
+    'http://creativecommons.org/ns#',
+    'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+    'http://www.serif.com/',
+    'http://www.vector.evaxdesign.sk'
+];
+
+// http://www.w3.org/TR/SVG11/linking.html#processingIRI
+exports.referencesProps = [
+    'clip-path',
+    'color-profile',
+    'fill',
+    'filter',
+    'marker-start',
+    'marker-mid',
+    'marker-end',
+    'mask',
+    'stroke',
+    'style'
+];
+
+// http://www.w3.org/TR/SVG11/propidx.html
+exports.inheritableAttrs = [
+    'clip-rule',
+    'color',
+    'color-interpolation',
+    'color-interpolation-filters',
+    'color-profile',
+    'color-rendering',
+    'cursor',
+    'direction',
+    'dominant-baseline',
+    'fill',
+    'fill-opacity',
+    'fill-rule',
+    'font',
+    'font-family',
+    'font-size',
+    'font-size-adjust',
+    'font-stretch',
+    'font-style',
+    'font-variant',
+    'font-weight',
+    'glyph-orientation-horizontal',
+    'glyph-orientation-vertical',
+    'image-rendering',
+    'letter-spacing',
+    'marker',
+    'marker-end',
+    'marker-mid',
+    'marker-start',
+    'paint-order',
+    'pointer-events',
+    'shape-rendering',
+    'stroke',
+    'stroke-dasharray',
+    'stroke-dashoffset',
+    'stroke-linecap',
+    'stroke-linejoin',
+    'stroke-miterlimit',
+    'stroke-opacity',
+    'stroke-width',
+    'text-anchor',
+    'text-rendering',
+    'transform',
+    'visibility',
+    'word-spacing',
+    'writing-mode'
+];
+
+exports.presentationNonInheritableGroupAttrs = [
+    'display',
+    'clip-path',
+    'filter',
+    'mask',
+    'opacity',
+    'text-decoration',
+    'transform',
+    'unicode-bidi',
+    'visibility'
+];
+
+// http://www.w3.org/TR/SVG11/single-page.html#types-ColorKeywords
+exports.colorsNames = {
+    'aliceblue': '#f0f8ff',
+    'antiquewhite': '#faebd7',
+    'aqua': '#0ff',
+    'aquamarine': '#7fffd4',
+    'azure': '#f0ffff',
+    'beige': '#f5f5dc',
+    'bisque': '#ffe4c4',
+    'black': '#000',
+    'blanchedalmond': '#ffebcd',
+    'blue': '#00f',
+    'blueviolet': '#8a2be2',
+    'brown': '#a52a2a',
+    'burlywood': '#deb887',
+    'cadetblue': '#5f9ea0',
+    'chartreuse': '#7fff00',
+    'chocolate': '#d2691e',
+    'coral': '#ff7f50',
+    'cornflowerblue': '#6495ed',
+    'cornsilk': '#fff8dc',
+    'crimson': '#dc143c',
+    'cyan': '#0ff',
+    'darkblue': '#00008b',
+    'darkcyan': '#008b8b',
+    'darkgoldenrod': '#b8860b',
+    'darkgray': '#a9a9a9',
+    'darkgreen': '#006400',
+    'darkgrey': '#a9a9a9',
+    'darkkhaki': '#bdb76b',
+    'darkmagenta': '#8b008b',
+    'darkolivegreen': '#556b2f',
+    'darkorange': '#ff8c00',
+    'darkorchid': '#9932cc',
+    'darkred': '#8b0000',
+    'darksalmon': '#e9967a',
+    'darkseagreen': '#8fbc8f',
+    'darkslateblue': '#483d8b',
+    'darkslategray': '#2f4f4f',
+    'darkslategrey': '#2f4f4f',
+    'darkturquoise': '#00ced1',
+    'darkviolet': '#9400d3',
+    'deeppink': '#ff1493',
+    'deepskyblue': '#00bfff',
+    'dimgray': '#696969',
+    'dimgrey': '#696969',
+    'dodgerblue': '#1e90ff',
+    'firebrick': '#b22222',
+    'floralwhite': '#fffaf0',
+    'forestgreen': '#228b22',
+    'fuchsia': '#f0f',
+    'gainsboro': '#dcdcdc',
+    'ghostwhite': '#f8f8ff',
+    'gold': '#ffd700',
+    'goldenrod': '#daa520',
+    'gray': '#808080',
+    'green': '#008000',
+    'greenyellow': '#adff2f',
+    'grey': '#808080',
+    'honeydew': '#f0fff0',
+    'hotpink': '#ff69b4',
+    'indianred': '#cd5c5c',
+    'indigo': '#4b0082',
+    'ivory': '#fffff0',
+    'khaki': '#f0e68c',
+    'lavender': '#e6e6fa',
+    'lavenderblush': '#fff0f5',
+    'lawngreen': '#7cfc00',
+    'lemonchiffon': '#fffacd',
+    'lightblue': '#add8e6',
+    'lightcoral': '#f08080',
+    'lightcyan': '#e0ffff',
+    'lightgoldenrodyellow': '#fafad2',
+    'lightgray': '#d3d3d3',
+    'lightgreen': '#90ee90',
+    'lightgrey': '#d3d3d3',
+    'lightpink': '#ffb6c1',
+    'lightsalmon': '#ffa07a',
+    'lightseagreen': '#20b2aa',
+    'lightskyblue': '#87cefa',
+    'lightslategray': '#789',
+    'lightslategrey': '#789',
+    'lightsteelblue': '#b0c4de',
+    'lightyellow': '#ffffe0',
+    'lime': '#0f0',
+    'limegreen': '#32cd32',
+    'linen': '#faf0e6',
+    'magenta': '#f0f',
+    'maroon': '#800000',
+    'mediumaquamarine': '#66cdaa',
+    'mediumblue': '#0000cd',
+    'mediumorchid': '#ba55d3',
+    'mediumpurple': '#9370db',
+    'mediumseagreen': '#3cb371',
+    'mediumslateblue': '#7b68ee',
+    'mediumspringgreen': '#00fa9a',
+    'mediumturquoise': '#48d1cc',
+    'mediumvioletred': '#c71585',
+    'midnightblue': '#191970',
+    'mintcream': '#f5fffa',
+    'mistyrose': '#ffe4e1',
+    'moccasin': '#ffe4b5',
+    'navajowhite': '#ffdead',
+    'navy': '#000080',
+    'oldlace': '#fdf5e6',
+    'olive': '#808000',
+    'olivedrab': '#6b8e23',
+    'orange': '#ffa500',
+    'orangered': '#ff4500',
+    'orchid': '#da70d6',
+    'palegoldenrod': '#eee8aa',
+    'palegreen': '#98fb98',
+    'paleturquoise': '#afeeee',
+    'palevioletred': '#db7093',
+    'papayawhip': '#ffefd5',
+    'peachpuff': '#ffdab9',
+    'peru': '#cd853f',
+    'pink': '#ffc0cb',
+    'plum': '#dda0dd',
+    'powderblue': '#b0e0e6',
+    'purple': '#800080',
+    'rebeccapurple': '#639',
+    'red': '#f00',
+    'rosybrown': '#bc8f8f',
+    'royalblue': '#4169e1',
+    'saddlebrown': '#8b4513',
+    'salmon': '#fa8072',
+    'sandybrown': '#f4a460',
+    'seagreen': '#2e8b57',
+    'seashell': '#fff5ee',
+    'sienna': '#a0522d',
+    'silver': '#c0c0c0',
+    'skyblue': '#87ceeb',
+    'slateblue': '#6a5acd',
+    'slategray': '#708090',
+    'slategrey': '#708090',
+    'snow': '#fffafa',
+    'springgreen': '#00ff7f',
+    'steelblue': '#4682b4',
+    'tan': '#d2b48c',
+    'teal': '#008080',
+    'thistle': '#d8bfd8',
+    'tomato': '#ff6347',
+    'turquoise': '#40e0d0',
+    'violet': '#ee82ee',
+    'wheat': '#f5deb3',
+    'white': '#fff',
+    'whitesmoke': '#f5f5f5',
+    'yellow': '#ff0',
+    'yellowgreen': '#9acd32'
+};
+
+exports.colorsShortNames = {
+  '#f0ffff': 'azure',
+  '#f5f5dc': 'beige',
+  '#ffe4c4': 'bisque',
+  '#a52a2a': 'brown',
+  '#ff7f50': 'coral',
+  '#ffd700': 'gold',
+  '#808080': 'gray',
+  '#008000': 'green',
+  '#4b0082': 'indigo',
+  '#fffff0': 'ivory',
+  '#f0e68c': 'khaki',
+  '#faf0e6': 'linen',
+  '#800000': 'maroon',
+  '#000080': 'navy',
+  '#808000': 'olive',
+  '#ffa500': 'orange',
+  '#da70d6': 'orchid',
+  '#cd853f': 'peru',
+  '#ffc0cb': 'pink',
+  '#dda0dd': 'plum',
+  '#800080': 'purple',
+  '#f00': 'red',
+  '#ff0000': 'red',
+  '#fa8072': 'salmon',
+  '#a0522d': 'sienna',
+  '#c0c0c0': 'silver',
+  '#fffafa': 'snow',
+  '#d2b48c': 'tan',
+  '#008080': 'teal',
+  '#ff6347': 'tomato',
+  '#ee82ee': 'violet',
+  '#f5deb3': 'wheat'
+};
+
+// http://www.w3.org/TR/SVG11/single-page.html#types-DataTypeColor
+exports.colorsProps = [
+    'color', 'fill', 'stroke', 'stop-color', 'flood-color', 'lighting-color'
+];
diff --git a/vendor/svgclean/src/svgclean/plugins/_path.js b/vendor/svgclean/src/svgclean/plugins/_path.js
new file mode 100644
index 000000000..89244d83a
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/_path.js
@@ -0,0 +1,988 @@
+/* global a2c */
+'use strict';
+
+var rNumber = String.raw`[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?\s*`,
+    rCommaWsp = String.raw`(?:\s,?\s*|,\s*)`,
+    rNumberCommaWsp = `(${rNumber})` + rCommaWsp,
+    rFlagCommaWsp = `([01])${rCommaWsp}?`,
+    rCoordinatePair = String.raw`(${rNumber})${rCommaWsp}?(${rNumber})`,
+    rArcSeq = (rNumberCommaWsp + '?').repeat(2) + rNumberCommaWsp + rFlagCommaWsp.repeat(2) + rCoordinatePair;
+
+var regPathInstructions = /([MmLlHhVvCcSsQqTtAaZz])\s*/,
+    regCoordinateSequence = new RegExp(rNumber, 'g'),
+    regArcArgumentSequence = new RegExp(rArcSeq, 'g'),
+    regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/,
+    transform2js = require('./_transforms').transform2js,
+    transformsMultiply = require('./_transforms').transformsMultiply,
+    transformArc = require('./_transforms').transformArc,
+    collections = require('./_collections.js'),
+    referencesProps = collections.referencesProps,
+    defaultStrokeWidth = collections.attrsGroupsDefaults.presentation['stroke-width'],
+    cleanupOutData = require('../tools').cleanupOutData,
+    removeLeadingZero = require('../tools').removeLeadingZero,
+    prevCtrlPoint;
+
+/**
+ * Convert path string to JS representation.
+ *
+ * @param {String} pathString input string
+ * @param {Object} params plugin params
+ * @return {Array} output array
+ */
+exports.path2js = function(path) {
+    if (path.pathJS) return path.pathJS;
+
+    var paramsLength = { // Number of parameters of every path command
+            H: 1, V: 1, M: 2, L: 2, T: 2, Q: 4, S: 4, C: 6, A: 7,
+            h: 1, v: 1, m: 2, l: 2, t: 2, q: 4, s: 4, c: 6, a: 7
+        },
+        pathData = [],   // JS representation of the path data
+        instruction, // current instruction context
+        startMoveto = false;
+
+    // splitting path string into array like ['M', '10 50', 'L', '20 30']
+    path.attr('d').value.split(regPathInstructions).forEach(function(data) {
+        if (!data) return;
+        if (!startMoveto) {
+            if (data == 'M' || data == 'm') {
+                startMoveto = true;
+            } else return;
+        }
+
+        // instruction item
+        if (regPathInstructions.test(data)) {
+            instruction = data;
+
+            // z - instruction w/o data
+            if (instruction == 'Z' || instruction == 'z') {
+                pathData.push({
+                    instruction: 'z'
+                });
+            }
+        // data item
+        } else {
+            /* jshint boss: true */
+            if (instruction == 'A' || instruction == 'a') {
+                var newData = [];
+                for (var args; (args = regArcArgumentSequence.exec(data));) {
+                    for (var i = 1; i < args.length; i++) {
+                        newData.push(args[i]);
+                    }
+                }
+                data = newData;
+            } else {
+                data = data.match(regCoordinateSequence);
+            }
+            if (!data) return;
+
+            data = data.map(Number);
+            // Subsequent moveto pairs of coordinates are threated as implicit lineto commands
+            // http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
+            if (instruction == 'M' || instruction == 'm') {
+                pathData.push({
+                    instruction: pathData.length == 0 ? 'M' : instruction,
+                    data: data.splice(0, 2)
+                });
+                instruction = instruction == 'M' ? 'L' : 'l';
+            }
+
+            for (var pair = paramsLength[instruction]; data.length;) {
+                pathData.push({
+                    instruction: instruction,
+                    data: data.splice(0, pair)
+                });
+            }
+        }
+    });
+
+    // First moveto is actually absolute. Subsequent coordinates were separated above.
+    if (pathData.length && pathData[0].instruction == 'm') {
+        pathData[0].instruction = 'M';
+    }
+    path.pathJS = pathData;
+
+    return pathData;
+};
+
+/**
+ * Convert relative Path data to absolute.
+ *
+ * @param {Array} data input data
+ * @return {Array} output data
+ */
+var relative2absolute = exports.relative2absolute = function(data) {
+    var currentPoint = [0, 0],
+        subpathPoint = [0, 0],
+        i;
+
+    return data.map(function(item) {
+
+        var instruction = item.instruction,
+            itemData = item.data && item.data.slice();
+
+        if (instruction == 'M') {
+
+            set(currentPoint, itemData);
+            set(subpathPoint, itemData);
+
+        } else if ('mlcsqt'.indexOf(instruction) > -1) {
+
+            for (i = 0; i < itemData.length; i++) {
+                itemData[i] += currentPoint[i % 2];
+            }
+            set(currentPoint, itemData);
+
+            if (instruction == 'm') {
+                set(subpathPoint, itemData);
+            }
+
+        } else if (instruction == 'a') {
+
+            itemData[5] += currentPoint[0];
+            itemData[6] += currentPoint[1];
+            set(currentPoint, itemData);
+
+        } else if (instruction == 'h') {
+
+            itemData[0] += currentPoint[0];
+            currentPoint[0] = itemData[0];
+
+        } else if (instruction == 'v') {
+
+            itemData[0] += currentPoint[1];
+            currentPoint[1] = itemData[0];
+
+        } else if ('MZLCSQTA'.indexOf(instruction) > -1) {
+
+            set(currentPoint, itemData);
+
+        } else if (instruction == 'H') {
+
+            currentPoint[0] = itemData[0];
+
+        } else if (instruction == 'V') {
+
+            currentPoint[1] = itemData[0];
+
+        } else if (instruction == 'z') {
+
+            set(currentPoint, subpathPoint);
+
+        }
+
+        return instruction == 'z' ?
+            { instruction: 'z' } :
+            {
+                instruction: instruction.toUpperCase(),
+                data: itemData
+            };
+
+    });
+};
+
+/**
+ * Apply transformation(s) to the Path data.
+ *
+ * @param {Object} elem current element
+ * @param {Array} path input path data
+ * @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width)
+ * @return {Array} output path data
+ */
+exports.applyTransforms = function(elem, path, params) {
+    // if there are no 'stroke' attr and references to other objects such as
+    // gradiends or clip-path which are also subjects to transform.
+    if (!elem.hasAttr('transform') || !elem.attr('transform').value ||
+        elem.someAttr(function(attr) {
+            return ~referencesProps.indexOf(attr.name) && ~attr.value.indexOf('url(');
+        }))
+        return path;
+
+    var matrix = transformsMultiply(transform2js(elem.attr('transform').value)),
+        stroke = elem.computedAttr('stroke'),
+        id = elem.computedAttr('id'),
+        transformPrecision = params.transformPrecision,
+        newPoint, scale;
+
+    if (stroke && stroke != 'none') {
+        if (!params.applyTransformsStroked ||
+            (matrix.data[0] != matrix.data[3] || matrix.data[1] != -matrix.data[2]) &&
+            (matrix.data[0] != -matrix.data[3] || matrix.data[1] != matrix.data[2]))
+            return path;
+
+        // "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
+        if (id) {
+            var idElem = elem,
+                hasStrokeWidth = false;
+
+            do {
+                if (idElem.hasAttr('stroke-width')) hasStrokeWidth = true;
+            } while (!idElem.hasAttr('id', id) && !hasStrokeWidth && (idElem = idElem.parentNode));
+
+            if (!hasStrokeWidth) return path;
+        }
+
+        scale = +Math.sqrt(matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]).toFixed(transformPrecision);
+
+        if (scale !== 1) {
+            var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth;
+
+            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>
+        return path;
+    }
+
+    path.forEach(function(pathItem) {
+
+        if (pathItem.data) {
+
+            // h -> l
+            if (pathItem.instruction === 'h') {
+
+                pathItem.instruction = 'l';
+                pathItem.data[1] = 0;
+
+            // v -> l
+            } else if (pathItem.instruction === 'v') {
+
+                pathItem.instruction = 'l';
+                pathItem.data[1] = pathItem.data[0];
+                pathItem.data[0] = 0;
+
+            }
+
+            // if there is a translate() transform
+            if (pathItem.instruction === 'M' &&
+                (matrix.data[4] !== 0 ||
+                matrix.data[5] !== 0)
+            ) {
+
+                // then apply it only to the first absoluted M
+                newPoint = transformPoint(matrix.data, pathItem.data[0], pathItem.data[1]);
+                set(pathItem.data, newPoint);
+                set(pathItem.coords, newPoint);
+
+                // clear translate() data from transform matrix
+                matrix.data[4] = 0;
+                matrix.data[5] = 0;
+
+            } else {
+
+                if (pathItem.instruction == 'a') {
+
+                    transformArc(pathItem.data, matrix.data);
+
+                    // reduce number of digits in rotation angle
+                    if (Math.abs(pathItem.data[2]) > 80) {
+                        var a = pathItem.data[0],
+                            rotation = pathItem.data[2];
+                        pathItem.data[0] = pathItem.data[1];
+                        pathItem.data[1] = a;
+                        pathItem.data[2] = rotation + (rotation > 0 ? -90 : 90);
+                    }
+
+                    newPoint = transformPoint(matrix.data, pathItem.data[5], pathItem.data[6]);
+                    pathItem.data[5] = newPoint[0];
+                    pathItem.data[6] = newPoint[1];
+
+                } else {
+
+                    for (var i = 0; i < pathItem.data.length; i += 2) {
+                        newPoint = transformPoint(matrix.data, pathItem.data[i], pathItem.data[i + 1]);
+                        pathItem.data[i] = newPoint[0];
+                        pathItem.data[i + 1] = newPoint[1];
+                    }
+                }
+
+                pathItem.coords[0] = pathItem.base[0] + pathItem.data[pathItem.data.length - 2];
+                pathItem.coords[1] = pathItem.base[1] + pathItem.data[pathItem.data.length - 1];
+
+            }
+
+        }
+
+    });
+
+    // remove transform attr
+    elem.removeAttr('transform');
+
+    return path;
+};
+
+/**
+ * Apply transform 3x3 matrix to x-y point.
+ *
+ * @param {Array} matrix transform 3x3 matrix
+ * @param {Array} point x-y point
+ * @return {Array} point with new coordinates
+ */
+function transformPoint(matrix, x, y) {
+
+    return [
+        matrix[0] * x + matrix[2] * y + matrix[4],
+        matrix[1] * x + matrix[3] * y + matrix[5]
+    ];
+
+}
+
+/**
+ * Compute Cubic Bézie bounding box.
+ *
+ * @see http://processingjs.nihongoresources.com/bezierinfo/
+ *
+ * @param {Float} xa
+ * @param {Float} ya
+ * @param {Float} xb
+ * @param {Float} yb
+ * @param {Float} xc
+ * @param {Float} yc
+ * @param {Float} xd
+ * @param {Float} yd
+ *
+ * @return {Object}
+ */
+exports.computeCubicBoundingBox = function(xa, ya, xb, yb, xc, yc, xd, yd) {
+
+    var minx = Number.POSITIVE_INFINITY,
+        miny = Number.POSITIVE_INFINITY,
+        maxx = Number.NEGATIVE_INFINITY,
+        maxy = Number.NEGATIVE_INFINITY,
+        ts,
+        t,
+        x,
+        y,
+        i;
+
+    // X
+    if (xa < minx) { minx = xa; }
+    if (xa > maxx) { maxx = xa; }
+    if (xd < minx) { minx= xd; }
+    if (xd > maxx) { maxx = xd; }
+
+    ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd);
+
+    for (i = 0; i < ts.length; i++) {
+
+        t = ts[i];
+
+        if (t >= 0 && t <= 1) {
+            x = computeCubicBaseValue(t, xa, xb, xc, xd);
+            // y = computeCubicBaseValue(t, ya, yb, yc, yd);
+
+            if (x < minx) { minx = x; }
+            if (x > maxx) { maxx = x; }
+        }
+
+    }
+
+    // Y
+    if (ya < miny) { miny = ya; }
+    if (ya > maxy) { maxy = ya; }
+    if (yd < miny) { miny = yd; }
+    if (yd > maxy) { maxy = yd; }
+
+    ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd);
+
+    for (i = 0; i < ts.length; i++) {
+
+        t = ts[i];
+
+        if (t >= 0 && t <= 1) {
+            // x = computeCubicBaseValue(t, xa, xb, xc, xd);
+            y = computeCubicBaseValue(t, ya, yb, yc, yd);
+
+            if (y < miny) { miny = y; }
+            if (y > maxy) { maxy = y; }
+        }
+
+    }
+
+    return {
+        minx: minx,
+        miny: miny,
+        maxx: maxx,
+        maxy: maxy
+    };
+
+};
+
+// compute the value for the cubic bezier function at time=t
+function computeCubicBaseValue(t, a, b, c, d) {
+
+    var mt = 1 - t;
+
+    return mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d;
+
+}
+
+// compute the value for the first derivative of the cubic bezier function at time=t
+function computeCubicFirstDerivativeRoots(a, b, c, d) {
+
+    var result = [-1, -1],
+        tl = -a + 2 * b - c,
+        tr = -Math.sqrt(-a * (c - d) + b * b - b * (c + d) + c * c),
+        dn = -a + 3 * b - 3 * c + d;
+
+    if (dn !== 0) {
+        result[0] = (tl + tr) / dn;
+        result[1] = (tl - tr) / dn;
+    }
+
+    return result;
+
+}
+
+/**
+ * Compute Quadratic Bézier bounding box.
+ *
+ * @see http://processingjs.nihongoresources.com/bezierinfo/
+ *
+ * @param {Float} xa
+ * @param {Float} ya
+ * @param {Float} xb
+ * @param {Float} yb
+ * @param {Float} xc
+ * @param {Float} yc
+ *
+ * @return {Object}
+ */
+exports.computeQuadraticBoundingBox = function(xa, ya, xb, yb, xc, yc) {
+
+    var minx = Number.POSITIVE_INFINITY,
+        miny = Number.POSITIVE_INFINITY,
+        maxx = Number.NEGATIVE_INFINITY,
+        maxy = Number.NEGATIVE_INFINITY,
+        t,
+        x,
+        y;
+
+    // X
+    if (xa < minx) { minx = xa; }
+    if (xa > maxx) { maxx = xa; }
+    if (xc < minx) { minx = xc; }
+    if (xc > maxx) { maxx = xc; }
+
+    t = computeQuadraticFirstDerivativeRoot(xa, xb, xc);
+
+    if (t >= 0 && t <= 1) {
+        x = computeQuadraticBaseValue(t, xa, xb, xc);
+        // y = computeQuadraticBaseValue(t, ya, yb, yc);
+
+        if (x < minx) { minx = x; }
+        if (x > maxx) { maxx = x; }
+    }
+
+    // Y
+    if (ya < miny) { miny = ya; }
+    if (ya > maxy) { maxy = ya; }
+    if (yc < miny) { miny = yc; }
+    if (yc > maxy) { maxy = yc; }
+
+    t = computeQuadraticFirstDerivativeRoot(ya, yb, yc);
+
+    if (t >= 0 && t <=1 ) {
+        // x = computeQuadraticBaseValue(t, xa, xb, xc);
+        y = computeQuadraticBaseValue(t, ya, yb, yc);
+
+        if (y < miny) { miny = y; }
+        if (y > maxy) { maxy = y ; }
+
+    }
+
+    return {
+        minx: minx,
+        miny: miny,
+        maxx: maxx,
+        maxy: maxy
+    };
+
+};
+
+// compute the value for the quadratic bezier function at time=t
+function computeQuadraticBaseValue(t, a, b, c) {
+
+    var mt = 1 - t;
+
+    return mt * mt * a + 2 * mt * t * b + t * t * c;
+
+}
+
+// compute the value for the first derivative of the quadratic bezier function at time=t
+function computeQuadraticFirstDerivativeRoot(a, b, c) {
+
+    var t = -1,
+        denominator = a - 2 * b + c;
+
+    if (denominator !== 0) {
+        t = (a - b) / denominator;
+    }
+
+    return t;
+
+}
+
+/**
+ * Convert path array to string.
+ *
+ * @param {Array} path input path data
+ * @param {Object} params plugin params
+ * @return {String} output path string
+ */
+exports.js2path = function(path, data, params) {
+
+    path.pathJS = data;
+
+    if (params.collapseRepeated) {
+        data = collapseRepeated(data);
+    }
+
+    path.attr('d').value = data.reduce(function(pathString, item) {
+        var strData = '';
+        if (item.data) {
+            strData = cleanupOutData(item.data, params, item.instruction);
+        }
+        return pathString += item.instruction + strData;
+    }, '');
+
+};
+
+/**
+ * Collapse repeated instructions data
+ *
+ * @param {Array} path input path data
+ * @return {Array} output path data
+ */
+function collapseRepeated(data) {
+
+    var prev,
+        prevIndex;
+
+    // copy an array and modifieds item to keep original data untouched
+    data = data.reduce(function(newPath, item) {
+        if (
+            prev && item.data &&
+            item.instruction == prev.instruction
+        ) {
+            // concat previous data with current
+            if (item.instruction != 'M') {
+                prev = newPath[prevIndex] = {
+                    instruction: prev.instruction,
+                    data: prev.data.concat(item.data),
+                    coords: item.coords,
+                    base: prev.base
+                };
+            } else {
+                prev.data = item.data;
+                prev.coords = item.coords;
+            }
+        } else {
+            newPath.push(item);
+            prev = item;
+            prevIndex = newPath.length - 1;
+        }
+
+        return newPath;
+    }, []);
+
+    return data;
+
+}
+
+function set(dest, source) {
+    dest[0] = source[source.length - 2];
+    dest[1] = source[source.length - 1];
+    return dest;
+}
+
+/**
+ * Checks if two paths have an intersection by checking convex hulls
+ * collision using Gilbert-Johnson-Keerthi distance algorithm
+ * http://entropyinteractive.com/2011/04/gjk-algorithm/
+ *
+ * @param {Array} path1 JS path representation
+ * @param {Array} path2 JS path representation
+ * @return {Boolean}
+ */
+exports.intersects = function(path1, path2) {
+    if (path1.length < 3 || path2.length < 3) return false; // nothing to fill
+
+    // Collect points of every subpath.
+    var points1 = relative2absolute(path1).reduce(gatherPoints, []),
+        points2 = relative2absolute(path2).reduce(gatherPoints, []);
+
+    // Axis-aligned bounding box check.
+    if (points1.maxX <= points2.minX || points2.maxX <= points1.minX ||
+        points1.maxY <= points2.minY || points2.maxY <= points1.minY ||
+        points1.every(function (set1) {
+            return points2.every(function (set2) {
+                return set1[set1.maxX][0] <= set2[set2.minX][0] ||
+                    set2[set2.maxX][0] <= set1[set1.minX][0] ||
+                    set1[set1.maxY][1] <= set2[set2.minY][1] ||
+                    set2[set2.maxY][1] <= set1[set1.minY][1];
+            });
+        })
+    ) return false;
+
+    // Get a convex hull from points of each subpath. Has the most complexity O(n·log n).
+    var hullNest1 = points1.map(convexHull),
+        hullNest2 = points2.map(convexHull);
+
+    // Check intersection of every subpath of the first path with every subpath of the second.
+    return hullNest1.some(function(hull1) {
+        if (hull1.length < 3) return false;
+
+        return hullNest2.some(function(hull2) {
+            if (hull2.length < 3) return false;
+
+            var simplex = [getSupport(hull1, hull2, [1, 0])], // create the initial simplex
+                direction = minus(simplex[0]); // set the direction to point towards the origin
+
+            var iterations = 1e4; // infinite loop protection, 10 000 iterations is more than enough
+            while (true) {
+                if (iterations-- == 0) {
+                    console.error('Error: infinite loop while processing mergePaths plugin.');
+                    return true; // true is the safe value that means “do nothing with paths”
+                }
+                // add a new point
+                simplex.push(getSupport(hull1, hull2, direction));
+                // see if the new point was on the correct side of the origin
+                if (dot(direction, simplex[simplex.length - 1]) <= 0) return false;
+                // process the simplex
+                if (processSimplex(simplex, direction)) return true;
+            }
+        });
+    });
+
+    function getSupport(a, b, direction) {
+        return sub(supportPoint(a, direction), supportPoint(b, minus(direction)));
+    }
+
+    // Computes farthest polygon point in particular direction.
+    // Thanks to knowledge of min/max x and y coordinates we can choose a quadrant to search in.
+    // Since we're working on convex hull, the dot product is increasing until we find the farthest point.
+    function supportPoint(polygon, direction) {
+        var index = direction[1] >= 0 ?
+                direction[0] < 0 ? polygon.maxY : polygon.maxX :
+                direction[0] < 0 ? polygon.minX : polygon.minY,
+            max = -Infinity,
+            value;
+        while ((value = dot(polygon[index], direction)) > max) {
+            max = value;
+            index = ++index % polygon.length;
+        }
+        return polygon[(index || polygon.length) - 1];
+    }
+};
+
+function processSimplex(simplex, direction) {
+    /* jshint -W004 */
+
+    // we only need to handle to 1-simplex and 2-simplex
+    if (simplex.length == 2) { // 1-simplex
+        var a = simplex[1],
+            b = simplex[0],
+            AO = minus(simplex[1]),
+            AB = sub(b, a);
+        // AO is in the same direction as AB
+        if (dot(AO, AB) > 0) {
+            // get the vector perpendicular to AB facing O
+            set(direction, orth(AB, a));
+        } else {
+            set(direction, AO);
+            // only A remains in the simplex
+            simplex.shift();
+        }
+    } else { // 2-simplex
+        var a = simplex[2], // [a, b, c] = simplex
+            b = simplex[1],
+            c = simplex[0],
+            AB = sub(b, a),
+            AC = sub(c, a),
+            AO = minus(a),
+            ACB = orth(AB, AC), // the vector perpendicular to AB facing away from C
+            ABC = orth(AC, AB); // the vector perpendicular to AC facing away from B
+
+        if (dot(ACB, AO) > 0) {
+            if (dot(AB, AO) > 0) { // region 4
+                set(direction, ACB);
+                simplex.shift(); // simplex = [b, a]
+            } else { // region 5
+                set(direction, AO);
+                simplex.splice(0, 2); // simplex = [a]
+            }
+        } else if (dot(ABC, AO) > 0) {
+            if (dot(AC, AO) > 0) { // region 6
+                set(direction, ABC);
+                simplex.splice(1, 1); // simplex = [c, a]
+            } else { // region 5 (again)
+                set(direction, AO);
+                simplex.splice(0, 2); // simplex = [a]
+            }
+        } else // region 7
+            return true;
+    }
+    return false;
+}
+
+function minus(v) {
+    return [-v[0], -v[1]];
+}
+
+function sub(v1, v2) {
+    return [v1[0] - v2[0], v1[1] - v2[1]];
+}
+
+function dot(v1, v2) {
+    return v1[0] * v2[0] + v1[1] * v2[1];
+}
+
+function orth(v, from) {
+    var o = [-v[1], v[0]];
+    return dot(o, minus(from)) < 0 ? minus(o) : o;
+}
+
+function gatherPoints(points, item, index, path) {
+
+    var subPath = points.length && points[points.length - 1],
+        prev = index && path[index - 1],
+        basePoint = subPath.length && subPath[subPath.length - 1],
+        data = item.data,
+        ctrlPoint = basePoint;
+
+    switch (item.instruction) {
+        case 'M':
+            points.push(subPath = []);
+            break;
+        case 'H':
+            addPoint(subPath, [data[0], basePoint[1]]);
+            break;
+        case 'V':
+            addPoint(subPath, [basePoint[0], data[0]]);
+            break;
+        case 'Q':
+            addPoint(subPath, data.slice(0, 2));
+            prevCtrlPoint = [data[2] - data[0], data[3] - data[1]]; // Save control point for shorthand
+            break;
+        case 'T':
+            if (prev.instruction == 'Q' || prev.instruction == 'T') {
+                ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]];
+                addPoint(subPath, ctrlPoint);
+                prevCtrlPoint = [data[0] - ctrlPoint[0], data[1] - ctrlPoint[1]];
+            }
+            break;
+        case 'C':
+            // Approximate quibic Bezier curve with middle points between control points
+            addPoint(subPath, [.5 * (basePoint[0] + data[0]), .5 * (basePoint[1] + data[1])]);
+            addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]);
+            addPoint(subPath, [.5 * (data[2] + data[4]), .5 * (data[3] + data[5])]);
+            prevCtrlPoint = [data[4] - data[2], data[5] - data[3]]; // Save control point for shorthand
+            break;
+        case 'S':
+            if (prev.instruction == 'C' || prev.instruction == 'S') {
+                addPoint(subPath, [basePoint[0] + .5 * prevCtrlPoint[0], basePoint[1] + .5 * prevCtrlPoint[1]]);
+                ctrlPoint = [basePoint[0] + prevCtrlPoint[0], basePoint[1] + prevCtrlPoint[1]];
+            }
+            addPoint(subPath, [.5 * (ctrlPoint[0] + data[0]), .5 * (ctrlPoint[1]+ data[1])]);
+            addPoint(subPath, [.5 * (data[0] + data[2]), .5 * (data[1] + data[3])]);
+            prevCtrlPoint = [data[2] - data[0], data[3] - data[1]];
+            break;
+        case 'A':
+            // Convert the arc to bezier curves and use the same approximation
+            var curves = a2c.apply(0, basePoint.concat(data));
+            for (var cData; (cData = curves.splice(0,6).map(toAbsolute)).length;) {
+                addPoint(subPath, [.5 * (basePoint[0] + cData[0]), .5 * (basePoint[1] + cData[1])]);
+                addPoint(subPath, [.5 * (cData[0] + cData[2]), .5 * (cData[1] + cData[3])]);
+                addPoint(subPath, [.5 * (cData[2] + cData[4]), .5 * (cData[3] + cData[5])]);
+                if (curves.length) addPoint(subPath, basePoint = cData.slice(-2));
+            }
+            break;
+    }
+    // Save final command coordinates
+    if (data && data.length >= 2) addPoint(subPath, data.slice(-2));
+    return points;
+
+    function toAbsolute(n, i) { return n + basePoint[i % 2] }
+
+    // Writes data about the extreme points on each axle
+    function addPoint(path, point) {
+        if (!path.length || point[1] > path[path.maxY][1]) {
+            path.maxY = path.length;
+            points.maxY = points.length ? Math.max(point[1], points.maxY) : point[1];
+        }
+        if (!path.length || point[0] > path[path.maxX][0]) {
+            path.maxX = path.length;
+            points.maxX = points.length ? Math.max(point[0], points.maxX) : point[0];
+        }
+        if (!path.length || point[1] < path[path.minY][1]) {
+            path.minY = path.length;
+            points.minY = points.length ? Math.min(point[1], points.minY) : point[1];
+        }
+        if (!path.length || point[0] < path[path.minX][0]) {
+            path.minX = path.length;
+            points.minX = points.length ? Math.min(point[0], points.minX) : point[0];
+        }
+        path.push(point);
+    }
+}
+
+/**
+ * Forms a convex hull from set of points of every subpath using monotone chain convex hull algorithm.
+ * http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
+ *
+ * @param points An array of [X, Y] coordinates
+ */
+function convexHull(points) {
+    /* jshint -W004 */
+
+    points.sort(function(a, b) {
+        return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];
+    });
+
+    var lower = [],
+        minY = 0,
+        bottom = 0;
+    for (var i = 0; i < points.length; i++) {
+        while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) {
+            lower.pop();
+        }
+        if (points[i][1] < points[minY][1]) {
+            minY = i;
+            bottom = lower.length;
+        }
+        lower.push(points[i]);
+    }
+
+    var upper = [],
+        maxY = points.length - 1,
+        top = 0;
+    for (var i = points.length; i--;) {
+        while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) {
+            upper.pop();
+        }
+        if (points[i][1] > points[maxY][1]) {
+            maxY = i;
+            top = upper.length;
+        }
+        upper.push(points[i]);
+    }
+
+    // last points are equal to starting points of the other part
+    upper.pop();
+    lower.pop();
+
+    var hull = lower.concat(upper);
+
+    hull.minX = 0; // by sorting
+    hull.maxX = lower.length;
+    hull.minY = bottom;
+    hull.maxY = (lower.length + top) % hull.length;
+
+    return hull;
+}
+
+function cross(o, a, b) {
+    return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
+}
+
+/* Based on code from Snap.svg (Apache 2 license). http://snapsvg.io/
+ * Thanks to Dmitry Baranovskiy for his great work!
+ */
+
+// jshint ignore: start
+function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+    // for more information of where this Math came from visit:
+    // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+    var _120 = Math.PI * 120 / 180,
+        rad = Math.PI / 180 * (+angle || 0),
+        res = [],
+        rotateX = function(x, y, rad) { return x * Math.cos(rad) - y * Math.sin(rad) },
+        rotateY = function(x, y, rad) { return x * Math.sin(rad) + y * Math.cos(rad) };
+    if (!recursive) {
+        x1 = rotateX(x1, y1, -rad);
+        y1 = rotateY(x1, y1, -rad);
+        x2 = rotateX(x2, y2, -rad);
+        y2 = rotateY(x2, y2, -rad);
+        var x = (x1 - x2) / 2,
+            y = (y1 - y2) / 2;
+        var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+        if (h > 1) {
+            h = Math.sqrt(h);
+            rx = h * rx;
+            ry = h * ry;
+        }
+        var rx2 = rx * rx,
+            ry2 = ry * ry,
+            k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+            cx = k * rx * y / ry + (x1 + x2) / 2,
+            cy = k * -ry * x / rx + (y1 + y2) / 2,
+            f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
+            f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
+
+        f1 = x1 < cx ? Math.PI - f1 : f1;
+        f2 = x2 < cx ? Math.PI - f2 : f2;
+        f1 < 0 && (f1 = Math.PI * 2 + f1);
+        f2 < 0 && (f2 = Math.PI * 2 + f2);
+        if (sweep_flag && f1 > f2) {
+            f1 = f1 - Math.PI * 2;
+        }
+        if (!sweep_flag && f2 > f1) {
+            f2 = f2 - Math.PI * 2;
+        }
+    } else {
+        f1 = recursive[0];
+        f2 = recursive[1];
+        cx = recursive[2];
+        cy = recursive[3];
+    }
+    var df = f2 - f1;
+    if (Math.abs(df) > _120) {
+        var f2old = f2,
+            x2old = x2,
+            y2old = y2;
+        f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+        x2 = cx + rx * Math.cos(f2);
+        y2 = cy + ry * Math.sin(f2);
+        res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+    }
+    df = f2 - f1;
+    var c1 = Math.cos(f1),
+        s1 = Math.sin(f1),
+        c2 = Math.cos(f2),
+        s2 = Math.sin(f2),
+        t = Math.tan(df / 4),
+        hx = 4 / 3 * rx * t,
+        hy = 4 / 3 * ry * t,
+        m = [
+            - hx * s1, hy * c1,
+            x2 + hx * s2 - x1, y2 - hy * c2 - y1,
+            x2 - x1, y2 - y1
+        ];
+    if (recursive) {
+        return m.concat(res);
+    } else {
+        res = m.concat(res);
+        var newres = [];
+        for (var i = 0, n = res.length; i < n; i++) {
+            newres[i] = i % 2 ? rotateY(res[i - 1], res[i], rad) : rotateX(res[i], res[i + 1], rad);
+        }
+        return newres;
+    }
+}
+// jshint ignore: end
diff --git a/vendor/svgclean/src/svgclean/plugins/_transforms.js b/vendor/svgclean/src/svgclean/plugins/_transforms.js
new file mode 100644
index 000000000..a5160839a
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/_transforms.js
@@ -0,0 +1,310 @@
+'use strict';
+
+var regTransformTypes = /matrix|translate|scale|rotate|skewX|skewY/,
+    regTransformSplit = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/,
+    regNumericValues = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
+
+/**
+ * Convert transform string to JS representation.
+ *
+ * @param {String} transformString input string
+ * @param {Object} params plugin params
+ * @return {Array} output array
+ */
+exports.transform2js = function(transformString) {
+
+        // JS representation of the transform data
+    var transforms = [],
+        // current transform context
+        current;
+
+    // split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', '']
+    transformString.split(regTransformSplit).forEach(function(item) {
+        /*jshint -W084 */
+        var num;
+
+        if (item) {
+            // if item is a translate function
+            if (regTransformTypes.test(item)) {
+                // then collect it and change current context
+                transforms.push(current = { name: item });
+            // else if item is data
+            } else {
+                // then split it into [10, 50] and collect as context.data
+                while (num = regNumericValues.exec(item)) {
+                    num = Number(num);
+                    if (current.data)
+                        current.data.push(num);
+                    else
+                        current.data = [num];
+                }
+            }
+        }
+    });
+
+    // return empty array if broken transform (no data)
+    return current && current.data ? transforms : [];
+};
+
+/**
+ * Multiply transforms into one.
+ *
+ * @param {Array} input transforms array
+ * @return {Array} output matrix array
+ */
+exports.transformsMultiply = function(transforms) {
+
+    // convert transforms objects to the matrices
+    transforms = transforms.map(function(transform) {
+        if (transform.name === 'matrix') {
+            return transform.data;
+        }
+        return transformToMatrix(transform);
+    });
+
+    // multiply all matrices into one
+    transforms = {
+        name: 'matrix',
+        data: transforms.length > 0 ? transforms.reduce(multiplyTransformMatrices) : []
+    };
+
+    return transforms;
+
+};
+
+/**
+ * Do math like a schoolgirl.
+ *
+ * @type {Object}
+ */
+var mth = exports.mth = {
+
+    rad: function(deg) {
+        return deg * Math.PI / 180;
+    },
+
+    deg: function(rad) {
+        return rad * 180 / Math.PI;
+    },
+
+    cos: function(deg) {
+        return Math.cos(this.rad(deg));
+    },
+
+    acos: function(val, floatPrecision) {
+        return +(this.deg(Math.acos(val)).toFixed(floatPrecision));
+    },
+
+    sin: function(deg) {
+        return Math.sin(this.rad(deg));
+    },
+
+    asin: function(val, floatPrecision) {
+        return +(this.deg(Math.asin(val)).toFixed(floatPrecision));
+    },
+
+    tan: function(deg) {
+        return Math.tan(this.rad(deg));
+    },
+
+    atan: function(val, floatPrecision) {
+        return +(this.deg(Math.atan(val)).toFixed(floatPrecision));
+    }
+
+};
+
+/**
+ * Decompose matrix into simple transforms. See
+ * http://frederic-wang.fr/decomposition-of-2d-transform-matrices.html
+ *
+ * @param {Object} data matrix transform object
+ * @return {Object|Array} transforms array or original transform object
+ */
+exports.matrixToTransform = function(transform, params) {
+    var floatPrecision = params.floatPrecision,
+        data = transform.data,
+        transforms = [],
+        sx = +Math.hypot(data[0], data[1]).toFixed(params.transformPrecision),
+        sy = +((data[0] * data[3] - data[1] * data[2]) / sx).toFixed(params.transformPrecision),
+        colsSum = data[0] * data[2] + data[1] * data[3],
+        rowsSum = data[0] * data[1] + data[2] * data[3],
+        scaleBefore = rowsSum != 0 || sx == sy;
+
+    // [..., ..., ..., ..., tx, ty] → translate(tx, ty)
+    if (data[4] || data[5]) {
+        transforms.push({ name: 'translate', data: data.slice(4, data[5] ? 6 : 5) });
+    }
+
+    // [sx, 0, tan(a)·sy, sy, 0, 0] → skewX(a)·scale(sx, sy)
+    if (!data[1] && data[2]) {
+        transforms.push({ name: 'skewX', data: [mth.atan(data[2] / sy, floatPrecision)] });
+
+    // [sx, sx·tan(a), 0, sy, 0, 0] → skewY(a)·scale(sx, sy)
+    } else if (data[1] && !data[2]) {
+        transforms.push({ name: 'skewY', data: [mth.atan(data[1] / data[0], floatPrecision)] });
+        sx = data[0];
+        sy = data[3];
+
+    // [sx·cos(a), sx·sin(a), sy·-sin(a), sy·cos(a), x, y] → rotate(a[, cx, cy])·(scale or skewX) or
+    // [sx·cos(a), sy·sin(a), sx·-sin(a), sy·cos(a), x, y] → scale(sx, sy)·rotate(a[, cx, cy]) (if !scaleBefore)
+    } else if (!colsSum || (sx == 1 && sy == 1) || !scaleBefore) {
+        if (!scaleBefore) {
+            sx = (data[0] < 0 ? -1 : 1) * Math.hypot(data[0], data[2]);
+            sy = (data[3] < 0 ? -1 : 1) * Math.hypot(data[1], data[3]);
+            transforms.push({ name: 'scale', data: [sx, sy] });
+        }
+        var angle = Math.min(Math.max(-1, data[0] / sx), 1),
+            rotate = [mth.acos(angle, floatPrecision) * ((scaleBefore ? 1 : sy) * data[1] < 0 ? -1 : 1)];
+
+        if (rotate[0]) transforms.push({ name: 'rotate', data: rotate });
+
+        if (rowsSum && colsSum) transforms.push({
+            name: 'skewX',
+            data: [mth.atan(colsSum / (sx * sx), floatPrecision)]
+        });
+
+        // rotate(a, cx, cy) can consume translate() within optional arguments cx, cy (rotation point)
+        if (rotate[0] && (data[4] || data[5])) {
+            transforms.shift();
+            var cos = data[0] / sx,
+                sin = data[1] / (scaleBefore ? sx : sy),
+                x = data[4] * (scaleBefore || sy),
+                y = data[5] * (scaleBefore || sx),
+                denom = (Math.pow(1 - cos, 2) + Math.pow(sin, 2)) * (scaleBefore || sx * sy);
+            rotate.push(((1 - cos) * x - sin * y) / denom);
+            rotate.push(((1 - cos) * y + sin * x) / denom);
+        }
+
+    // Too many transformations, return original matrix if it isn't just a scale/translate
+    } else if (data[1] || data[2]) {
+        return transform;
+    }
+
+    if (scaleBefore && (sx != 1 || sy != 1) || !transforms.length) transforms.push({
+        name: 'scale',
+        data: sx == sy ? [sx] : [sx, sy]
+    });
+
+    return transforms;
+};
+
+/**
+ * Convert transform to the matrix data.
+ *
+ * @param {Object} transform transform object
+ * @return {Array} matrix data
+ */
+function transformToMatrix(transform) {
+
+    if (transform.name === 'matrix') return transform.data;
+
+    var matrix;
+
+    switch (transform.name) {
+        case 'translate':
+            // [1, 0, 0, 1, tx, ty]
+            matrix = [1, 0, 0, 1, transform.data[0], transform.data[1] || 0];
+            break;
+        case 'scale':
+            // [sx, 0, 0, sy, 0, 0]
+            matrix = [transform.data[0], 0, 0, transform.data[1] || transform.data[0], 0, 0];
+            break;
+        case 'rotate':
+            // [cos(a), sin(a), -sin(a), cos(a), x, y]
+            var cos = mth.cos(transform.data[0]),
+                sin = mth.sin(transform.data[0]),
+                cx = transform.data[1] || 0,
+                cy = transform.data[2] || 0;
+
+            matrix = [cos, sin, -sin, cos, (1 - cos) * cx + sin * cy, (1 - cos) * cy - sin * cx];
+            break;
+        case 'skewX':
+            // [1, 0, tan(a), 1, 0, 0]
+            matrix = [1, 0, mth.tan(transform.data[0]), 1, 0, 0];
+            break;
+        case 'skewY':
+            // [1, tan(a), 0, 1, 0, 0]
+            matrix = [1, mth.tan(transform.data[0]), 0, 1, 0, 0];
+            break;
+    }
+
+    return matrix;
+
+}
+
+/**
+ * Applies transformation to an arc. To do so, we represent ellipse as a matrix, multiply it
+ * by the transformation matrix and use a singular value decomposition to represent in a form
+ * rotate(θ)·scale(a b)·rotate(φ). This gives us new ellipse params a, b and θ.
+ * SVD is being done with the formulae provided by Wolffram|Alpha (svd {{m0, m2}, {m1, m3}})
+ *
+ * @param {Array} arc [a, b, rotation in deg]
+ * @param {Array} transform transformation matrix
+ * @return {Array} arc transformed input arc
+ */
+exports.transformArc = function(arc, transform) {
+
+    var a = arc[0],
+        b = arc[1],
+        rot = arc[2] * Math.PI / 180,
+        cos = Math.cos(rot),
+        sin = Math.sin(rot),
+        h = Math.pow(arc[5] * cos + arc[6] * sin, 2) / (4 * a * a) +
+            Math.pow(arc[6] * cos - arc[5] * sin, 2) / (4 * b * b);
+    if (h > 1) {
+        h = Math.sqrt(h);
+        a *= h;
+        b *= h;
+    }
+    var ellipse = [a * cos, a * sin, -b * sin, b * cos, 0, 0],
+        m = multiplyTransformMatrices(transform, ellipse),
+        // Decompose the new ellipse matrix
+        lastCol = m[2] * m[2] + m[3] * m[3],
+        squareSum = m[0] * m[0] + m[1] * m[1] + lastCol,
+        root = Math.hypot(m[0] - m[3], m[1] + m[2]) * Math.hypot(m[0] + m[3], m[1] - m[2]);
+
+    if (!root) { // circle
+        arc[0] = arc[1] = Math.sqrt(squareSum / 2);
+        arc[2] = 0;
+    } else {
+        var majorAxisSqr = (squareSum + root) / 2,
+            minorAxisSqr = (squareSum - root) / 2,
+            major = Math.abs(majorAxisSqr - lastCol) > 1e-6,
+            sub = (major ? majorAxisSqr : minorAxisSqr) - lastCol,
+            rowsSum = m[0] * m[2] + m[1] * m[3],
+            term1 = m[0] * sub + m[2] * rowsSum,
+            term2 = m[1] * sub + m[3] * rowsSum;
+        arc[0] = Math.sqrt(majorAxisSqr);
+        arc[1] = Math.sqrt(minorAxisSqr);
+        arc[2] = ((major ? term2 < 0 : term1 > 0) ? -1 : 1) *
+            Math.acos((major ? term1 : term2) / Math.hypot(term1, term2)) * 180 / Math.PI;
+    }
+
+    if ((transform[0] < 0) !== (transform[3] < 0)) {
+        // Flip the sweep flag if coordinates are being flipped horizontally XOR vertically
+        arc[4] = 1 - arc[4];
+    }
+
+    return arc;
+
+};
+
+/**
+ * Multiply transformation matrices.
+ *
+ * @param {Array} a matrix A data
+ * @param {Array} b matrix B data
+ * @return {Array} result
+ */
+function multiplyTransformMatrices(a, b) {
+
+    return [
+        a[0] * b[0] + a[2] * b[1],
+        a[1] * b[0] + a[3] * b[1],
+        a[0] * b[2] + a[2] * b[3],
+        a[1] * b[2] + a[3] * b[3],
+        a[0] * b[4] + a[2] * b[5] + a[4],
+        a[1] * b[4] + a[3] * b[5] + a[5]
+    ];
+
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/addAttributesToSVGElement.js b/vendor/svgclean/src/svgclean/plugins/addAttributesToSVGElement.js
new file mode 100644
index 000000000..da2448b1b
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/addAttributesToSVGElement.js
@@ -0,0 +1,82 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = false;
+
+exports.description = 'adds attributes to an outer <svg> element';
+
+var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
+It should have a list of "attributes" or one "attribute".
+Config example:
+
+plugins:
+- addAttributesToSVGElement:
+    attribute: "mySvg"
+
+plugins:
+- addAttributesToSVGElement:
+    attributes: ["mySvg", "size-big"]
+
+plugins:
+- addAttributesToSVGElement:
+    attributes:
+        - focusable: false
+        - data-image: icon`;
+
+/**
+ * Add attributes to an outer <svg> element. Example config:
+ *
+ * plugins:
+ * - addAttributesToSVGElement:
+ *     attribute: 'data-icon'
+ *
+ * plugins:
+ * - addAttributesToSVGElement:
+ *     attributes: ['data-icon', 'data-disabled']
+ *
+ * plugins:
+ * - addAttributesToSVGElement:
+ *     attributes:
+ *         - focusable: false
+ *         - data-image: icon
+ *
+ * @author April Arcus
+ */
+exports.fn = function(data, params) {
+    if (!params || !(Array.isArray(params.attributes) || params.attribute)) {
+        console.error(ENOCLS);
+        return data;
+    }
+
+    var attributes = params.attributes || [ params.attribute ],
+        svg = data.content[0];
+
+    if (svg.isElem('svg')) {
+        attributes.forEach(function (attribute) {
+            if (typeof attribute === 'string') {
+                if (!svg.hasAttr(attribute)) {
+                    svg.addAttr({
+                        name: attribute,
+                        prefix: '',
+                        local: attribute
+                    });
+                }
+            } else if (typeof attribute === 'object') {
+                Object.keys(attribute).forEach(function (key) {
+                    if (!svg.hasAttr(key)) {
+                        svg.addAttr({
+                            name: key,
+                            value: attribute[key],
+                            prefix: '',
+                            local: key
+                        });
+                    }
+                });
+            }
+        });
+    }
+
+    return data;
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/addClassesToSVGElement.js b/vendor/svgclean/src/svgclean/plugins/addClassesToSVGElement.js
new file mode 100644
index 000000000..ef215fc93
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/addClassesToSVGElement.js
@@ -0,0 +1,50 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = false;
+
+exports.description = 'adds classnames to an outer <svg> element';
+
+var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
+It should have a list of classes in "classNames" or one "className".
+Config example:
+
+plugins:
+- addClassesToSVGElement:
+    className: "mySvg"
+
+plugins:
+- addClassesToSVGElement:
+    classNames: ["mySvg", "size-big"]
+`;
+
+/**
+ * Add classnames to an outer <svg> element. Example config:
+ *
+ * plugins:
+ * - addClassesToSVGElement:
+ *     className: 'mySvg'
+ *
+ * plugins:
+ * - addClassesToSVGElement:
+ *     classNames: ['mySvg', 'size-big']
+ *
+ * @author April Arcus
+ */
+exports.fn = function(data, params) {
+    if (!params || !(Array.isArray(params.classNames) && params.classNames.some(String) || params.className)) {
+        console.error(ENOCLS);
+        return data;
+    }
+
+    var classNames = params.classNames || [ params.className ],
+        svg = data.content[0];
+
+    if (svg.isElem('svg')) {
+        svg.class.add.apply(svg.class, classNames);
+    }
+
+    return data;
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/cleanupAttrs.js b/vendor/svgclean/src/svgclean/plugins/cleanupAttrs.js
new file mode 100644
index 000000000..47e77ac79
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/cleanupAttrs.js
@@ -0,0 +1,56 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'cleanups attributes from newlines, trailing and repeating spaces';
+
+exports.params = {
+    newlines: true,
+    trim: true,
+    spaces: true
+};
+
+var regNewlinesNeedSpace = /(\S)\r?\n(\S)/g,
+    regNewlines = /\r?\n/g,
+    regSpaces = /\s{2,}/g;
+
+/**
+ * Cleanup attributes values from newlines, trailing and repeating spaces.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.isElem()) {
+
+        item.eachAttr(function(attr) {
+
+            if (params.newlines) {
+                // new line which requires a space instead of themselve
+                attr.value = attr.value.replace(regNewlinesNeedSpace, function(match, p1, p2) {
+                    return p1 + ' ' + p2;
+                });
+
+                // simple new line
+                attr.value = attr.value.replace(regNewlines, '');
+            }
+
+            if (params.trim) {
+                attr.value = attr.value.trim();
+            }
+
+            if (params.spaces) {
+                attr.value = attr.value.replace(regSpaces, ' ');
+            }
+
+        });
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/cleanupEnableBackground.js b/vendor/svgclean/src/svgclean/plugins/cleanupEnableBackground.js
new file mode 100644
index 000000000..e6384ab50
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/cleanupEnableBackground.js
@@ -0,0 +1,84 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.description = 'remove or cleanup enable-background attribute when possible';
+
+/**
+ * Remove or cleanup enable-background attr which coincides with a width/height box.
+ *
+ * @see http://www.w3.org/TR/SVG/filters.html#EnableBackgroundProperty
+ *
+ * @example
+ * <svg width="100" height="50" enable-background="new 0 0 100 50">
+ *             ⬇
+ * <svg width="100" height="50">
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(data) {
+
+    var regEnableBackground = /^new\s0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)$/,
+        hasFilter = false,
+        elems = ['svg', 'mask', 'pattern'];
+
+    function checkEnableBackground(item) {
+        if (
+            item.isElem(elems) &&
+            item.hasAttr('enable-background') &&
+            item.hasAttr('width') &&
+            item.hasAttr('height')
+        ) {
+
+            var match = item.attr('enable-background').value.match(regEnableBackground);
+
+            if (match) {
+                if (
+                    item.attr('width').value === match[1] &&
+                    item.attr('height').value === match[3]
+                ) {
+                    if (item.isElem('svg')) {
+                        item.removeAttr('enable-background');
+                    } else {
+                        item.attr('enable-background').value = 'new';
+                    }
+                }
+            }
+
+        }
+    }
+
+    function checkForFilter(item) {
+        if (item.isElem('filter')) {
+            hasFilter = true;
+        }
+    }
+
+    function monkeys(items, fn) {
+        items.content.forEach(function(item) {
+            fn(item);
+
+            if (item.content) {
+                monkeys(item, fn);
+            }
+        });
+        return items;
+    }
+
+    var firstStep = monkeys(data, function(item) {
+        checkEnableBackground(item);
+        if (!hasFilter) {
+            checkForFilter(item);
+        }
+    });
+
+    return hasFilter ? firstStep : monkeys(firstStep, function(item) {
+            //we don't need 'enable-background' if we have no filters
+            item.removeAttr('enable-background');
+        });
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/cleanupIDs.js b/vendor/svgclean/src/svgclean/plugins/cleanupIDs.js
new file mode 100644
index 000000000..71ffb5f93
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/cleanupIDs.js
@@ -0,0 +1,212 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.description = 'removes unused IDs and minifies used';
+
+exports.params = {
+    remove: true,
+    minify: true,
+    prefix: '',
+    preserve: [],
+    preservePrefixes: [],
+    force: false
+};
+
+var referencesProps = new Set(require('./_collections').referencesProps),
+    regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/,
+    regReferencesHref = /^#(.+?)$/,
+    regReferencesBegin = /(\w+)\./,
+    styleOrScript = ['style', 'script'],
+    generateIDchars = [
+        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
+    ],
+    maxIDindex = generateIDchars.length - 1;
+
+/**
+ * Remove unused and minify used IDs
+ * (only if there are no any <style> or <script>).
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(data, params) {
+    var currentID,
+        currentIDstring,
+        IDs = new Map(),
+        referencesIDs = new Map(),
+        hasStyleOrScript = false,
+        preserveIDs = new Set(Array.isArray(params.preserve) ? params.preserve : params.preserve ? [params.preserve] : []),
+        preserveIDPrefixes = new Set(Array.isArray(params.preservePrefixes) ? params.preservePrefixes : (params.preservePrefixes ? [params.preservePrefixes] : [])),
+        idValuePrefix = '#',
+        idValuePostfix = '.';
+
+    /**
+     * Bananas!
+     *
+     * @param {Array} items input items
+     * @return {Array} output items
+     */
+    function monkeys(items) {
+        for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) {
+            var item = items.content[i];
+
+            // quit if <style> or <script> present ('force' param prevents quitting)
+            if (!params.force) {
+                var isNotEmpty = Boolean(item.content);
+                if (item.isElem(styleOrScript) && isNotEmpty) {
+                    hasStyleOrScript = true;
+                    continue;
+                }
+
+                // Don't remove IDs if the whole SVG consists only of defs.
+                if (item.isElem('svg')) {
+                    var hasDefsOnly = true;
+
+                    for (var j = 0; j < item.content.length; j++) {
+                        if (!item.content[j].isElem('defs')) {
+                            hasDefsOnly = false;
+                            break;
+                        }
+                    }
+                    if (hasDefsOnly) {
+                        break;
+                    }
+                }
+            }
+            // …and don't remove any ID if yes
+            if (item.isElem()) {
+                item.eachAttr(function(attr) {
+                    var key, match;
+
+                    // save IDs
+                    if (attr.name === 'id') {
+                        key = attr.value;
+                        if (IDs.has(key)) {
+                            item.removeAttr('id'); // remove repeated id
+                        } else {
+                            IDs.set(key, item);
+                        }
+                        return;
+                    }
+                    // save references
+                    if (referencesProps.has(attr.name) && (match = attr.value.match(regReferencesUrl))) {
+                        key = match[2]; // url() reference
+                    } else if (
+                        attr.local === 'href' && (match = attr.value.match(regReferencesHref)) ||
+                        attr.name === 'begin' && (match = attr.value.match(regReferencesBegin))
+                    ) {
+                        key = match[1]; // href reference
+                    }
+                    if (key) {
+                        var ref = referencesIDs.get(key) || [];
+                        ref.push(attr);
+                        referencesIDs.set(key, ref);
+                    }
+                });
+            }
+            // go deeper
+            if (item.content) {
+                monkeys(item);
+            }
+        }
+        return items;
+    }
+
+    data = monkeys(data);
+
+    if (hasStyleOrScript) {
+        return data;
+    }
+
+    const idPreserved = id => preserveIDs.has(id) || idMatchesPrefix(preserveIDPrefixes, id);
+
+    for (var ref of referencesIDs) {
+        var key = ref[0];
+
+        if (IDs.has(key)) {
+            // replace referenced IDs with the minified ones
+            if (params.minify && !idPreserved(key)) {
+                do {
+                    currentIDstring = getIDstring(currentID = generateID(currentID), params);
+                } while (idPreserved(currentIDstring));
+
+                IDs.get(key).attr('id').value = currentIDstring;
+
+                for (var attr of ref[1]) {
+                    attr.value = attr.value.includes(idValuePrefix) ?
+                        attr.value.replace(idValuePrefix + key, idValuePrefix + currentIDstring) :
+                        attr.value.replace(key + idValuePostfix, currentIDstring + idValuePostfix);
+                }
+            }
+            // don't remove referenced IDs
+            IDs.delete(key);
+        }
+    }
+    // remove non-referenced IDs attributes from elements
+    if (params.remove) {
+        for(var keyElem of IDs) {
+            if (!idPreserved(keyElem[0])) {
+                keyElem[1].removeAttr('id');
+            }
+        }
+    }
+    return data;
+};
+
+/**
+ * Check if an ID starts with any one of a list of strings.
+ *
+ * @param {Array} of prefix strings
+ * @param {String} current ID
+ * @return {Boolean} if currentID starts with one of the strings in prefixArray
+ */
+function idMatchesPrefix(prefixArray, currentID) {
+    if (!currentID) return false;
+
+    for (var prefix of prefixArray) if (currentID.startsWith(prefix)) return true;
+    return false;
+}
+
+/**
+ * Generate unique minimal ID.
+ *
+ * @param {Array} [currentID] current ID
+ * @return {Array} generated ID array
+ */
+function generateID(currentID) {
+    if (!currentID) return [0];
+
+    currentID[currentID.length - 1]++;
+
+    for(var i = currentID.length - 1; i > 0; i--) {
+        if (currentID[i] > maxIDindex) {
+            currentID[i] = 0;
+
+            if (currentID[i - 1] !== undefined) {
+                currentID[i - 1]++;
+            }
+        }
+    }
+    if (currentID[0] > maxIDindex) {
+        currentID[0] = 0;
+        currentID.unshift(0);
+    }
+    return currentID;
+}
+
+/**
+ * Get string from generated ID array.
+ *
+ * @param {Array} arr input ID array
+ * @return {String} output ID string
+ */
+function getIDstring(arr, params) {
+    var str = params.prefix;
+    return str + arr.map(i => generateIDchars[i]).join('');
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/cleanupListOfValues.js b/vendor/svgclean/src/svgclean/plugins/cleanupListOfValues.js
new file mode 100644
index 000000000..84aaef1e9
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/cleanupListOfValues.js
@@ -0,0 +1,139 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'rounds list of values to the fixed precision';
+
+exports.params = {
+    floatPrecision: 3,
+    leadingZero: true,
+    defaultPx: true,
+    convertToPx: true
+};
+
+var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/,
+    regSeparator = /\s+,?\s*|,\s*/,
+    removeLeadingZero = require('../tools').removeLeadingZero,
+    absoluteLengths = { // relative to px
+        cm: 96/2.54,
+        mm: 96/25.4,
+        in: 96,
+        pt: 4/3,
+        pc: 16
+    };
+
+/**
+ * Round list of values to the fixed precision.
+ *
+ * @example
+ * <svg viewBox="0 0 200.28423 200.28423" enable-background="new 0 0 200.28423 200.28423">
+ *         ⬇
+ * <svg viewBox="0 0 200.284 200.284" enable-background="new 0 0 200.284 200.284">
+ *
+ *
+ * <polygon points="208.250977 77.1308594 223.069336 ... "/>
+ *         ⬇
+ * <polygon points="208.251 77.131 223.069 ... "/>
+ *
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author kiyopikko
+ */
+exports.fn = function(item, params) {
+
+
+    if ( item.hasAttr('points') ) {
+        roundValues(item.attrs.points);
+    }
+
+    if ( item.hasAttr('enable-background') ) {
+        roundValues(item.attrs['enable-background']);
+    }
+
+    if ( item.hasAttr('viewBox') ) {
+        roundValues(item.attrs.viewBox);
+    }
+
+    if ( item.hasAttr('stroke-dasharray') ) {
+        roundValues(item.attrs['stroke-dasharray']);
+    }
+
+    if ( item.hasAttr('dx') ) {
+        roundValues(item.attrs.dx);
+    }
+
+    if ( item.hasAttr('dy') ) {
+        roundValues(item.attrs.dy);
+    }
+
+    if ( item.hasAttr('x') ) {
+        roundValues(item.attrs.x);
+    }
+
+    if ( item.hasAttr('y') ) {
+        roundValues(item.attrs.y);
+    }
+
+
+    function roundValues($prop){
+
+        var num, units,
+            match,
+            matchNew,
+            lists = $prop.value,
+            listsArr = lists.split(regSeparator),
+            roundedListArr = [],
+            roundedList;
+
+        listsArr.forEach(function(elem){
+
+            match = elem.match(regNumericValues);
+            matchNew = elem.match(/new/);
+
+             // if attribute value matches regNumericValues
+            if (match) {
+                // round it to the fixed precision
+                num = +(+match[1]).toFixed(params.floatPrecision),
+                units = match[3] || '';
+
+                // convert absolute values to pixels
+                if (params.convertToPx && units && (units in absoluteLengths)) {
+                    var pxNum = +(absoluteLengths[units] * match[1]).toFixed(params.floatPrecision);
+
+                    if (String(pxNum).length < match[0].length)
+                        num = pxNum,
+                        units = 'px';
+                }
+
+                 // and remove leading zero
+                if (params.leadingZero) {
+                    num = removeLeadingZero(num);
+                }
+
+                // remove default 'px' units
+                if (params.defaultPx && units === 'px') {
+                    units = '';
+                }
+
+                roundedListArr.push(num+units);
+            }
+            // if attribute value is "new"(only enable-background).
+            else if (matchNew) {
+                roundedListArr.push('new');
+            } else if (elem) {
+                roundedListArr.push(elem);
+            }
+
+        });
+
+        roundedList = roundedListArr.join(' ');
+        $prop.value = roundedList;
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/cleanupNumericValues.js b/vendor/svgclean/src/svgclean/plugins/cleanupNumericValues.js
new file mode 100644
index 000000000..82c0f58e2
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/cleanupNumericValues.js
@@ -0,0 +1,88 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'rounds numeric values to the fixed precision, removes default ‘px’ units';
+
+exports.params = {
+    floatPrecision: 3,
+    leadingZero: true,
+    defaultPx: true,
+    convertToPx: true
+};
+
+var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/,
+    removeLeadingZero = require('../tools').removeLeadingZero,
+    absoluteLengths = { // relative to px
+        cm: 96/2.54,
+        mm: 96/25.4,
+        in: 96,
+        pt: 4/3,
+        pc: 16
+    };
+
+/**
+ * Round numeric values to the fixed precision,
+ * remove default 'px' units.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.isElem()) {
+
+        var floatPrecision = params.floatPrecision;
+
+        if (item.hasAttr('viewBox')) {
+            var nums = item.attr('viewBox').value.split(/\s,?\s*|,\s*/g);
+            item.attr('viewBox').value = nums.map(function(value) {
+                var num = +value;
+                return isNaN(num) ? value : +num.toFixed(floatPrecision);
+            }).join(' ');
+        }
+
+        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
+            if (match) {
+                // round it to the fixed precision
+                var num = +(+match[1]).toFixed(floatPrecision),
+                    units = match[3] || '';
+
+                // convert absolute values to pixels
+                if (params.convertToPx && units && (units in absoluteLengths)) {
+                    var pxNum = +(absoluteLengths[units] * match[1]).toFixed(floatPrecision);
+
+                    if (String(pxNum).length < match[0].length) {
+                        num = pxNum;
+                        units = 'px';
+                    }
+                }
+
+                // and remove leading zero
+                if (params.leadingZero) {
+                    num = removeLeadingZero(num);
+                }
+
+                // remove default 'px' units
+                if (params.defaultPx && units === 'px') {
+                    units = '';
+                }
+
+                attr.value = num + units;
+            }
+        });
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/collapseGroups.js b/vendor/svgclean/src/svgclean/plugins/collapseGroups.js
new file mode 100644
index 000000000..3daa5a889
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/collapseGroups.js
@@ -0,0 +1,87 @@
+'use strict';
+
+exports.type = 'perItemReverse';
+
+exports.active = true;
+
+exports.description = 'collapses useless groups';
+
+var collections = require('./_collections'),
+    attrsInheritable = collections.inheritableAttrs,
+    animationElems = collections.elemsGroups.animation;
+
+function hasAnimatedAttr(item) {
+    /* jshint validthis:true */
+    return item.isElem(animationElems) && item.hasAttr('attributeName', this) ||
+        !item.isEmpty() && item.content.some(hasAnimatedAttr, this);
+}
+
+/*
+ * Collapse useless groups.
+ *
+ * @example
+ * <g>
+ *     <g attr1="val1">
+ *         <path d="..."/>
+ *     </g>
+ * </g>
+ *         ⬇
+ * <g>
+ *     <g>
+ *         <path attr1="val1" d="..."/>
+ *     </g>
+ * </g>
+ *         ⬇
+ * <path attr1="val1" d="..."/>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    // non-empty elements
+    if (item.isElem() && !item.isElem('switch') && !item.isEmpty()) {
+        item.content.forEach(function(g, i) {
+            // non-empty groups
+            if (g.isElem('g') && !g.isEmpty()) {
+                // move group attibutes to the single content element
+                if (g.hasAttr() && g.content.length === 1) {
+                    var inner = g.content[0];
+
+                    if (inner.isElem() && !inner.hasAttr('id') && !g.hasAttr('filter') &&
+                        !(g.hasAttr('class') && inner.hasAttr('class')) && (
+                            !g.hasAttr('clip-path') && !g.hasAttr('mask') ||
+                            inner.isElem('g') && !g.hasAttr('transform') && !inner.hasAttr('transform')
+                        )
+                    ) {
+                        g.eachAttr(function(attr) {
+                            if (g.content.some(hasAnimatedAttr, attr.name)) return;
+
+                            if (!inner.hasAttr(attr.name)) {
+                                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)
+                            ) {
+                                return;
+                            }
+
+                            g.removeAttr(attr.name);
+                        });
+                    }
+                }
+
+                // collapse groups without attributes
+                if (!g.hasAttr() && !g.content.some(function(item) { return item.isElem(animationElems) })) {
+                    item.spliceContent(i, 1, g.content);
+                }
+            }
+        });
+    }
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/convertColors.js b/vendor/svgclean/src/svgclean/plugins/convertColors.js
new file mode 100644
index 000000000..77279e942
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/convertColors.js
@@ -0,0 +1,130 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'converts colors: rgb() to #rrggbb and #rrggbb to #rgb';
+
+exports.params = {
+    currentColor: false,
+    names2hex: true,
+    rgb2hex: true,
+    shorthex: true,
+    shortname: true
+};
+
+var collections = require('./_collections'),
+    rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)',
+    rComma = '\\s*,\\s*',
+    regRGB = new RegExp('^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$'),
+    regHEX = /^\#(([a-fA-F0-9])\2){3}$/,
+    none = /\bnone\b/i;
+
+/**
+ * Convert different colors formats in element attributes to hex.
+ *
+ * @see http://www.w3.org/TR/SVG/types.html#DataTypeColor
+ * @see http://www.w3.org/TR/SVG/single-page.html#types-ColorKeywords
+ *
+ * @example
+ * Convert color name keyword to long hex:
+ * fuchsia ➡ #ff00ff
+ *
+ * Convert rgb() to long hex:
+ * rgb(255, 0, 255) ➡ #ff00ff
+ * rgb(50%, 100, 100%) ➡ #7f64ff
+ *
+ * Convert long hex to short hex:
+ * #aabbcc ➡ #abc
+ *
+ * Convert hex to short name
+ * #000080 ➡ navy
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.elem) {
+
+        item.eachAttr(function(attr) {
+
+            if (collections.colorsProps.indexOf(attr.name) > -1) {
+
+                var val = attr.value,
+                    match;
+
+                // Convert colors to currentColor
+                if (params.currentColor) {
+                    if (typeof params.currentColor === 'string') {
+                        match = val === params.currentColor;
+                    } else if (params.currentColor.exec) {
+                        match = params.currentColor.exec(val);
+                    } else {
+                        match = !val.match(none);
+                    }
+                    if (match) {
+                        val = 'currentColor';
+                    }
+                }
+
+                // Convert color name keyword to long hex
+                if (params.names2hex && val.toLowerCase() in collections.colorsNames) {
+                    val = collections.colorsNames[val.toLowerCase()];
+                }
+
+                // Convert rgb() to long hex
+                if (params.rgb2hex && (match = val.match(regRGB))) {
+                    match = match.slice(1, 4).map(function(m) {
+                        if (m.indexOf('%') > -1)
+                            m = Math.round(parseFloat(m) * 2.55);
+
+                        return Math.max(0, Math.min(m, 255));
+                    });
+
+                    val = rgb2hex(match);
+                }
+
+                // Convert long hex to short hex
+                if (params.shorthex && (match = val.match(regHEX))) {
+                    val = '#' + match[0][1] + match[0][3] + match[0][5];
+                }
+
+                // Convert hex to short name
+                if (params.shortname) {
+                    var lowerVal = val.toLowerCase();
+                    if (lowerVal in collections.colorsShortNames) {
+                        val = collections.colorsShortNames[lowerVal];
+                    }
+                }
+
+                attr.value = val;
+
+            }
+
+        });
+
+    }
+
+};
+
+/**
+ * Convert [r, g, b] to #rrggbb.
+ *
+ * @see https://gist.github.com/983535
+ *
+ * @example
+ * rgb2hex([255, 255, 255]) // '#ffffff'
+ *
+ * @param {Array} rgb [r, g, b]
+ * @return {String} #rrggbb
+ *
+ * @author Jed Schmidt
+ */
+function rgb2hex(rgb) {
+    return '#' + ('00000' + (rgb[0] << 16 | rgb[1] << 8 | rgb[2]).toString(16)).slice(-6).toUpperCase();
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/convertEllipseToCircle.js b/vendor/svgclean/src/svgclean/plugins/convertEllipseToCircle.js
new file mode 100644
index 000000000..f1af11a94
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/convertEllipseToCircle.js
@@ -0,0 +1,39 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'converts non-eccentric <ellipse>s to <circle>s';
+
+/**
+ * Converts non-eccentric <ellipse>s to <circle>s.
+ *
+ * @see http://www.w3.org/TR/SVG/shapes.html
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Taylor Hunt
+ */
+exports.fn = function(item) {
+    if (item.isElem('ellipse')) {
+      var rx = item.attr('rx').value || 0;
+      var ry = item.attr('ry').value || 0;
+
+      if (rx === ry ||
+          rx === 'auto' || ry === 'auto' // SVG2
+         ) {
+        var radius = rx !== 'auto' ? rx : ry;
+        item.renameElem('circle');
+        item.removeAttr(['rx', 'ry']);
+        item.addAttr({
+            name: 'r',
+            value: radius,
+            prefix: '',
+            local: 'r',
+          });
+      }
+  }
+  return;
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/convertPathData.js b/vendor/svgclean/src/svgclean/plugins/convertPathData.js
new file mode 100644
index 000000000..954c2bfe1
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/convertPathData.js
@@ -0,0 +1,971 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'optimizes path data: writes in shorter form, applies transformations';
+
+exports.params = {
+    applyTransforms: true,
+    applyTransformsStroked: true,
+    makeArcs: {
+        threshold: 2.5, // coefficient of rounding error
+        tolerance: 0.5  // percentage of radius
+    },
+    straightCurves: true,
+    lineShorthands: true,
+    curveSmoothShorthands: true,
+    floatPrecision: 3,
+    transformPrecision: 5,
+    removeUseless: true,
+    collapseRepeated: true,
+    utilizeAbsolute: true,
+    leadingZero: true,
+    negativeExtraSpace: true,
+    noSpaceAfterFlags: true,
+    forceAbsolutePath: false
+};
+
+var pathElems = require('./_collections.js').pathElems,
+    path2js = require('./_path.js').path2js,
+    js2path = require('./_path.js').js2path,
+    applyTransforms = require('./_path.js').applyTransforms,
+    cleanupOutData = require('../tools').cleanupOutData,
+    roundData,
+    precision,
+    error,
+    arcThreshold,
+    arcTolerance,
+    hasMarkerMid,
+    hasStrokeLinecap;
+
+/**
+ * Convert absolute Path to relative,
+ * collapse repeated instructions,
+ * detect and convert Lineto shorthands,
+ * remove useless instructions like "l0,0",
+ * trim useless delimiters and leading zeros,
+ * decrease accuracy of floating-point numbers.
+ *
+ * @see http://www.w3.org/TR/SVG/paths.html#PathData
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.isElem(pathElems) && item.hasAttr('d')) {
+
+        precision = params.floatPrecision;
+        error = precision !== false ? +Math.pow(.1, precision).toFixed(precision) : 1e-2;
+        roundData = precision > 0 && precision < 20 ? strongRound : round;
+        if (params.makeArcs) {
+            arcThreshold = params.makeArcs.threshold;
+            arcTolerance = params.makeArcs.tolerance;
+        }
+        hasMarkerMid = item.hasAttr('marker-mid');
+
+        var stroke = item.computedAttr('stroke'),
+            strokeLinecap = item.computedAttr('stroke');
+        hasStrokeLinecap = stroke && stroke != 'none' && strokeLinecap && strokeLinecap != 'butt';
+
+        var data = path2js(item);
+
+        // TODO: get rid of functions returns
+        if (data.length) {
+            convertToRelative(data);
+
+            if (params.applyTransforms) {
+                data = applyTransforms(item, data, params);
+            }
+
+            data = filters(data, params);
+
+            if (params.utilizeAbsolute) {
+                data = convertToMixed(data, params);
+            }
+
+            js2path(item, data, params);
+        }
+
+    }
+
+};
+
+/**
+ * Convert absolute path data coordinates to relative.
+ *
+ * @param {Array} path input path data
+ * @param {Object} params plugin params
+ * @return {Array} output path data
+ */
+function convertToRelative(path) {
+
+    var point = [0, 0],
+        subpathPoint = [0, 0],
+        baseItem;
+
+    path.forEach(function(item, index) {
+
+        var instruction = item.instruction,
+            data = item.data;
+
+        // data !== !z
+        if (data) {
+
+            // already relative
+            // recalculate current point
+            if ('mcslqta'.indexOf(instruction) > -1) {
+
+                point[0] += data[data.length - 2];
+                point[1] += data[data.length - 1];
+
+                if (instruction === 'm') {
+                    subpathPoint[0] = point[0];
+                    subpathPoint[1] = point[1];
+                    baseItem = item;
+                }
+
+            } else if (instruction === 'h') {
+
+                point[0] += data[0];
+
+            } else if (instruction === 'v') {
+
+                point[1] += data[0];
+
+            }
+
+            // convert absolute path data coordinates to relative
+            // if "M" was not transformed from "m"
+            // M → m
+            if (instruction === 'M') {
+
+                if (index > 0) instruction = 'm';
+
+                data[0] -= point[0];
+                data[1] -= point[1];
+
+                subpathPoint[0] = point[0] += data[0];
+                subpathPoint[1] = point[1] += data[1];
+
+                baseItem = item;
+
+            }
+
+            // L → l
+            // T → t
+            else if ('LT'.indexOf(instruction) > -1) {
+
+                instruction = instruction.toLowerCase();
+
+                // x y
+                // 0 1
+                data[0] -= point[0];
+                data[1] -= point[1];
+
+                point[0] += data[0];
+                point[1] += data[1];
+
+            // C → c
+            } else if (instruction === 'C') {
+
+                instruction = 'c';
+
+                // x1 y1 x2 y2 x y
+                // 0  1  2  3  4 5
+                data[0] -= point[0];
+                data[1] -= point[1];
+                data[2] -= point[0];
+                data[3] -= point[1];
+                data[4] -= point[0];
+                data[5] -= point[1];
+
+                point[0] += data[4];
+                point[1] += data[5];
+
+            // S → s
+            // Q → q
+            } else if ('SQ'.indexOf(instruction) > -1) {
+
+                instruction = instruction.toLowerCase();
+
+                // x1 y1 x y
+                // 0  1  2 3
+                data[0] -= point[0];
+                data[1] -= point[1];
+                data[2] -= point[0];
+                data[3] -= point[1];
+
+                point[0] += data[2];
+                point[1] += data[3];
+
+            // A → a
+            } else if (instruction === 'A') {
+
+                instruction = 'a';
+
+                // rx ry x-axis-rotation large-arc-flag sweep-flag x y
+                // 0  1  2               3              4          5 6
+                data[5] -= point[0];
+                data[6] -= point[1];
+
+                point[0] += data[5];
+                point[1] += data[6];
+
+            // H → h
+            } else if (instruction === 'H') {
+
+                instruction = 'h';
+
+                data[0] -= point[0];
+
+                point[0] += data[0];
+
+            // V → v
+            } else if (instruction === 'V') {
+
+                instruction = 'v';
+
+                data[0] -= point[1];
+
+                point[1] += data[0];
+
+            }
+
+            item.instruction = instruction;
+            item.data = data;
+
+            // store absolute coordinates for later use
+            item.coords = point.slice(-2);
+
+        }
+
+        // !data === z, reset current point
+        else if (instruction == 'z') {
+            if (baseItem) {
+                item.coords = baseItem.coords;
+            }
+            point[0] = subpathPoint[0];
+            point[1] = subpathPoint[1];
+        }
+
+        item.base = index > 0 ? path[index - 1].coords : [0, 0];
+
+    });
+
+    return path;
+
+}
+
+/**
+ * Main filters loop.
+ *
+ * @param {Array} path input path data
+ * @param {Object} params plugin params
+ * @return {Array} output path data
+ */
+function filters(path, params) {
+
+    var stringify = data2Path.bind(null, params),
+        relSubpoint = [0, 0],
+        pathBase = [0, 0],
+        prev = {};
+
+    path = path.filter(function(item, index, path) {
+
+        var instruction = item.instruction,
+            data = item.data,
+            next = path[index + 1];
+
+        if (data) {
+
+            var sdata = data,
+                circle;
+
+            if (instruction === 's') {
+                sdata = [0, 0].concat(data);
+
+                if ('cs'.indexOf(prev.instruction) > -1) {
+                    var pdata = prev.data,
+                        n = pdata.length;
+
+                    // (-x, -y) of the prev tangent point relative to the current point
+                    sdata[0] = pdata[n - 2] - pdata[n - 4];
+                    sdata[1] = pdata[n - 1] - pdata[n - 3];
+                }
+
+            }
+
+            // convert curves to arcs if possible
+            if (
+                params.makeArcs &&
+                (instruction == 'c' || instruction == 's') &&
+                isConvex(sdata) &&
+                (circle = findCircle(sdata))
+            ) {
+                var r = roundData([circle.radius])[0],
+                    angle = findArcAngle(sdata, circle),
+                    sweep = sdata[5] * sdata[0] - sdata[4] * sdata[1] > 0 ? 1 : 0,
+                    arc = {
+                        instruction: 'a',
+                        data: [r, r, 0, 0, sweep, sdata[4], sdata[5]],
+                        coords: item.coords.slice(),
+                        base: item.base
+                    },
+                    output = [arc],
+                    // relative coordinates to adjust the found circle
+                    relCenter = [circle.center[0] - sdata[4], circle.center[1] - sdata[5]],
+                    relCircle = { center: relCenter, radius: circle.radius },
+                    arcCurves = [item],
+                    hasPrev = 0,
+                    suffix = '',
+                    nextLonghand;
+
+                if (
+                    prev.instruction == 'c' && isConvex(prev.data) && isArcPrev(prev.data, circle) ||
+                    prev.instruction == 'a' && prev.sdata && isArcPrev(prev.sdata, circle)
+                ) {
+                    arcCurves.unshift(prev);
+                    arc.base = prev.base;
+                    arc.data[5] = arc.coords[0] - arc.base[0];
+                    arc.data[6] = arc.coords[1] - arc.base[1];
+                    var prevData = prev.instruction == 'a' ? prev.sdata : prev.data;
+                    var prevAngle = findArcAngle(prevData,
+                        {
+                            center: [prevData[4] + circle.center[0], prevData[5] + circle.center[1]],
+                            radius: circle.radius
+                        }
+                    );
+                    angle += prevAngle;
+                    if (angle > Math.PI) arc.data[3] = 1;
+                    hasPrev = 1;
+                }
+
+                // check if next curves are fitting the arc
+                for (var j = index; (next = path[++j]) && ~'cs'.indexOf(next.instruction);) {
+                    var nextData = next.data;
+                    if (next.instruction == 's') {
+                        nextLonghand = makeLonghand({instruction: 's', data: next.data.slice() },
+                            path[j - 1].data);
+                        nextData = nextLonghand.data;
+                        nextLonghand.data = nextData.slice(0, 2);
+                        suffix = stringify([nextLonghand]);
+                    }
+                    if (isConvex(nextData) && isArc(nextData, relCircle)) {
+                        angle += findArcAngle(nextData, relCircle);
+                        if (angle - 2 * Math.PI > 1e-3) break; // more than 360°
+                        if (angle > Math.PI) arc.data[3] = 1;
+                        arcCurves.push(next);
+                        if (2 * Math.PI - angle > 1e-3) { // less than 360°
+                            arc.coords = next.coords;
+                            arc.data[5] = arc.coords[0] - arc.base[0];
+                            arc.data[6] = arc.coords[1] - arc.base[1];
+                        } else {
+                            // full circle, make a half-circle arc and add a second one
+                            arc.data[5] = 2 * (relCircle.center[0] - nextData[4]);
+                            arc.data[6] = 2 * (relCircle.center[1] - nextData[5]);
+                            arc.coords = [arc.base[0] + arc.data[5], arc.base[1] + arc.data[6]];
+                            arc = {
+                                instruction: 'a',
+                                data: [r, r, 0, 0, sweep,
+                                    next.coords[0] - arc.coords[0], next.coords[1] - arc.coords[1]],
+                                coords: next.coords,
+                                base: arc.coords
+                            };
+                            output.push(arc);
+                            j++;
+                            break;
+                        }
+                        relCenter[0] -= nextData[4];
+                        relCenter[1] -= nextData[5];
+                    } else break;
+                }
+
+                if ((stringify(output) + suffix).length < stringify(arcCurves).length) {
+                    if (path[j] && path[j].instruction == 's') {
+                        makeLonghand(path[j], path[j - 1].data);
+                    }
+                    if (hasPrev) {
+                        var prevArc = output.shift();
+                        roundData(prevArc.data);
+                        relSubpoint[0] += prevArc.data[5] - prev.data[prev.data.length - 2];
+                        relSubpoint[1] += prevArc.data[6] - prev.data[prev.data.length - 1];
+                        prev.instruction = 'a';
+                        prev.data = prevArc.data;
+                        item.base = prev.coords = prevArc.coords;
+                    }
+                    arc = output.shift();
+                    if (arcCurves.length == 1) {
+                        item.sdata = sdata.slice(); // preserve curve data for future checks
+                    } else if (arcCurves.length - 1 - hasPrev > 0) {
+                        // filter out consumed next items
+                        path.splice.apply(path, [index + 1, arcCurves.length - 1 - hasPrev].concat(output));
+                    }
+                    if (!arc) return false;
+                    instruction = 'a';
+                    data = arc.data;
+                    item.coords = arc.coords;
+                }
+            }
+
+            // Rounding relative coordinates, taking in account accummulating error
+            // to get closer to absolute coordinates. Sum of rounded value remains same:
+            // l .25 3 .25 2 .25 3 .25 2 -> l .3 3 .2 2 .3 3 .2 2
+            if (precision !== false) {
+                if ('mltqsc'.indexOf(instruction) > -1) {
+                    for (var i = data.length; i--;) {
+                        data[i] += item.base[i % 2] - relSubpoint[i % 2];
+                    }
+                } else if (instruction == 'h') {
+                    data[0] += item.base[0] - relSubpoint[0];
+                } else if (instruction == 'v') {
+                    data[0] += item.base[1] - relSubpoint[1];
+                } else if (instruction == 'a') {
+                    data[5] += item.base[0] - relSubpoint[0];
+                    data[6] += item.base[1] - relSubpoint[1];
+                }
+                roundData(data);
+
+                if      (instruction == 'h') relSubpoint[0] += data[0];
+                else if (instruction == 'v') relSubpoint[1] += data[0];
+                else {
+                    relSubpoint[0] += data[data.length - 2];
+                    relSubpoint[1] += data[data.length - 1];
+                }
+                roundData(relSubpoint);
+
+                if (instruction.toLowerCase() == 'm') {
+                    pathBase[0] = relSubpoint[0];
+                    pathBase[1] = relSubpoint[1];
+                }
+            }
+
+            // convert straight curves into lines segments
+            if (params.straightCurves) {
+
+                if (
+                    instruction === 'c' &&
+                    isCurveStraightLine(data) ||
+                    instruction === 's' &&
+                    isCurveStraightLine(sdata)
+                ) {
+                    if (next && next.instruction == 's')
+                        makeLonghand(next, data); // fix up next curve
+                    instruction = 'l';
+                    data = data.slice(-2);
+                }
+
+                else if (
+                    instruction === 'q' &&
+                    isCurveStraightLine(data)
+                ) {
+                    if (next && next.instruction == 't')
+                        makeLonghand(next, data); // fix up next curve
+                    instruction = 'l';
+                    data = data.slice(-2);
+                }
+
+                else if (
+                    instruction === 't' &&
+                    prev.instruction !== 'q' &&
+                    prev.instruction !== 't'
+                ) {
+                    instruction = 'l';
+                    data = data.slice(-2);
+                }
+
+                else if (
+                    instruction === 'a' &&
+                    (data[0] === 0 || data[1] === 0)
+                ) {
+                    instruction = 'l';
+                    data = data.slice(-2);
+                }
+            }
+
+            // horizontal and vertical line shorthands
+            // l 50 0 → h 50
+            // l 0 50 → v 50
+            if (
+                params.lineShorthands &&
+                instruction === 'l'
+            ) {
+                if (data[1] === 0) {
+                    instruction = 'h';
+                    data.pop();
+                } else if (data[0] === 0) {
+                    instruction = 'v';
+                    data.shift();
+                }
+            }
+
+            // collapse repeated commands
+            // h 20 h 30 -> h 50
+            if (
+                params.collapseRepeated &&
+                !hasMarkerMid &&
+                ('mhv'.indexOf(instruction) > -1) &&
+                prev.instruction &&
+                instruction == prev.instruction.toLowerCase() &&
+                (
+                    (instruction != 'h' && instruction != 'v') ||
+                    (prev.data[0] >= 0) == (data[0] >= 0)
+            )) {
+                prev.data[0] += data[0];
+                if (instruction != 'h' && instruction != 'v') {
+                    prev.data[1] += data[1];
+                }
+                prev.coords = item.coords;
+                path[index] = prev;
+                return false;
+            }
+
+            // convert curves into smooth shorthands
+            if (params.curveSmoothShorthands && prev.instruction) {
+
+                // curveto
+                if (instruction === 'c') {
+
+                    // c + c → c + s
+                    if (
+                        prev.instruction === 'c' &&
+                        data[0] === -(prev.data[2] - prev.data[4]) &&
+                        data[1] === -(prev.data[3] - prev.data[5])
+                    ) {
+                        instruction = 's';
+                        data = data.slice(2);
+                    }
+
+                    // s + c → s + s
+                    else if (
+                        prev.instruction === 's' &&
+                        data[0] === -(prev.data[0] - prev.data[2]) &&
+                        data[1] === -(prev.data[1] - prev.data[3])
+                    ) {
+                        instruction = 's';
+                        data = data.slice(2);
+                    }
+
+                    // [^cs] + c → [^cs] + s
+                    else if (
+                        'cs'.indexOf(prev.instruction) === -1 &&
+                        data[0] === 0 &&
+                        data[1] === 0
+                    ) {
+                        instruction = 's';
+                        data = data.slice(2);
+                    }
+
+                }
+
+                // quadratic Bézier curveto
+                else if (instruction === 'q') {
+
+                    // q + q → q + t
+                    if (
+                        prev.instruction === 'q' &&
+                        data[0] === (prev.data[2] - prev.data[0]) &&
+                        data[1] === (prev.data[3] - prev.data[1])
+                    ) {
+                        instruction = 't';
+                        data = data.slice(2);
+                    }
+
+                    // t + q → t + t
+                    else if (
+                        prev.instruction === 't' &&
+                        data[2] === prev.data[0] &&
+                        data[3] === prev.data[1]
+                    ) {
+                        instruction = 't';
+                        data = data.slice(2);
+                    }
+
+                }
+
+            }
+
+            // remove useless non-first path segments
+            if (params.removeUseless && !hasStrokeLinecap) {
+
+                // l 0,0 / h 0 / v 0 / q 0,0 0,0 / t 0,0 / c 0,0 0,0 0,0 / s 0,0 0,0
+                if (
+                    (
+                     'lhvqtcs'.indexOf(instruction) > -1
+                    ) &&
+                    data.every(function(i) { return i === 0; })
+                ) {
+                    path[index] = prev;
+                    return false;
+                }
+
+                // a 25,25 -30 0,1 0,0
+                if (
+                    instruction === 'a' &&
+                    data[5] === 0 &&
+                    data[6] === 0
+                ) {
+                    path[index] = prev;
+                    return false;
+                }
+
+            }
+
+            item.instruction = instruction;
+            item.data = data;
+
+            prev = item;
+
+        } else {
+
+            // z resets coordinates
+            relSubpoint[0] = pathBase[0];
+            relSubpoint[1] = pathBase[1];
+            if (prev.instruction == 'z') return false;
+            prev = item;
+
+        }
+
+        return true;
+
+    });
+
+    return path;
+
+}
+
+/**
+ * Writes data in shortest form using absolute or relative coordinates.
+ *
+ * @param {Array} data input path data
+ * @return {Boolean} output
+ */
+function convertToMixed(path, params) {
+
+    var prev = path[0];
+
+    path = path.filter(function(item, index) {
+
+        if (index == 0) return true;
+        if (!item.data) {
+            prev = item;
+            return true;
+        }
+
+        var instruction = item.instruction,
+            data = item.data,
+            adata = data && data.slice(0);
+
+        if ('mltqsc'.indexOf(instruction) > -1) {
+            for (var i = adata.length; i--;) {
+                adata[i] += item.base[i % 2];
+            }
+        } else if (instruction == 'h') {
+                adata[0] += item.base[0];
+        } else if (instruction == 'v') {
+                adata[0] += item.base[1];
+        } else if (instruction == 'a') {
+                adata[5] += item.base[0];
+                adata[6] += item.base[1];
+        }
+
+        roundData(adata);
+
+        var absoluteDataStr = cleanupOutData(adata, params),
+            relativeDataStr = cleanupOutData(data, params);
+
+        // 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 &&
+                instruction == prev.instruction &&
+                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;
+        }
+
+        prev = item;
+
+        return true;
+
+    });
+
+    return path;
+
+}
+
+/**
+ * Checks if curve is convex. Control points of such a curve must form
+ * a convex quadrilateral with diagonals crosspoint inside of it.
+ *
+ * @param {Array} data input path data
+ * @return {Boolean} output
+ */
+function isConvex(data) {
+
+    var center = getIntersection([0, 0, data[2], data[3], data[0], data[1], data[4], data[5]]);
+
+    return center &&
+        (data[2] < center[0] == center[0] < 0) &&
+        (data[3] < center[1] == center[1] < 0) &&
+        (data[4] < center[0] == center[0] < data[0]) &&
+        (data[5] < center[1] == center[1] < data[1]);
+
+}
+
+/**
+ * Computes lines equations by two points and returns their intersection point.
+ *
+ * @param {Array} coords 8 numbers for 4 pairs of coordinates (x,y)
+ * @return {Array|undefined} output coordinate of lines' crosspoint
+ */
+function getIntersection(coords) {
+
+        // Prev line equation parameters.
+    var a1 = coords[1] - coords[3], // y1 - y2
+        b1 = coords[2] - coords[0], // x2 - x1
+        c1 = coords[0] * coords[3] - coords[2] * coords[1], // x1 * y2 - x2 * y1
+
+        // Next line equation parameters
+        a2 = coords[5] - coords[7], // y1 - y2
+        b2 = coords[6] - coords[4], // x2 - x1
+        c2 = coords[4] * coords[7] - coords[5] * coords[6], // x1 * y2 - x2 * y1
+        denom = (a1 * b2 - a2 * b1);
+
+    if (!denom) return; // parallel lines havn't an intersection
+
+    var cross = [
+            (b1 * c2 - b2 * c1) / denom,
+            (a1 * c2 - a2 * c1) / -denom
+        ];
+    if (
+        !isNaN(cross[0]) && !isNaN(cross[1]) &&
+        isFinite(cross[0]) && isFinite(cross[1])
+    ) {
+        return cross;
+    }
+
+}
+
+/**
+ * Decrease accuracy of floating-point numbers
+ * in path data keeping a specified number of decimals.
+ * Smart rounds values like 2.3491 to 2.35 instead of 2.349.
+ * Doesn't apply "smartness" if the number precision fits already.
+ *
+ * @param {Array} data input data array
+ * @return {Array} output data array
+ */
+function strongRound(data) {
+    for (var i = data.length; i-- > 0;) {
+        if (data[i].toFixed(precision) != data[i]) {
+            var rounded = +data[i].toFixed(precision - 1);
+            data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= error ?
+                +data[i].toFixed(precision) :
+                rounded;
+        }
+    }
+    return data;
+}
+
+/**
+ * Simple rounding function if precision is 0.
+ *
+ * @param {Array} data input data array
+ * @return {Array} output data array
+ */
+function round(data) {
+    for (var i = data.length; i-- > 0;) {
+        data[i] = Math.round(data[i]);
+    }
+    return data;
+}
+
+/**
+ * Checks if a curve is a straight line by measuring distance
+ * from middle points to the line formed by end points.
+ *
+ * @param {Array} xs array of curve points x-coordinates
+ * @param {Array} ys array of curve points y-coordinates
+ * @return {Boolean}
+ */
+
+function isCurveStraightLine(data) {
+
+    // Get line equation a·x + b·y + c = 0 coefficients a, b (c = 0) by start and end points.
+    var i = data.length - 2,
+        a = -data[i + 1], // y1 − y2 (y1 = 0)
+        b = data[i],      // x2 − x1 (x1 = 0)
+        d = 1 / (a * a + b * b); // same part for all points
+
+    if (i <= 1 || !isFinite(d)) return false; // curve that ends at start point isn't the case
+
+    // Distance from point (x0, y0) to the line is sqrt((c − a·x0 − b·y0)² / (a² + b²))
+    while ((i -= 2) >= 0) {
+        if (Math.sqrt(Math.pow(a * data[i] + b * data[i + 1], 2) * d) > error)
+            return false;
+    }
+
+    return true;
+
+}
+
+/**
+ * Converts next curve from shorthand to full form using the current curve data.
+ *
+ * @param {Object} item curve to convert
+ * @param {Array} data current curve data
+ */
+
+function makeLonghand(item, data) {
+    switch (item.instruction) {
+        case 's': item.instruction = 'c'; break;
+        case 't': item.instruction = 'q'; break;
+    }
+    item.data.unshift(data[data.length - 2] - data[data.length - 4], data[data.length - 1] - data[data.length - 3]);
+    return item;
+}
+
+/**
+ * Returns distance between two points
+ *
+ * @param {Array} point1 first point coordinates
+ * @param {Array} point2 second point coordinates
+ * @return {Number} distance
+ */
+
+function getDistance(point1, point2) {
+    return Math.hypot(point1[0] - point2[0], point1[1] - point2[1]);
+}
+
+/**
+ * Returns coordinates of the curve point corresponding to the certain t
+ * a·(1 - t)³·p1 + b·(1 - t)²·t·p2 + c·(1 - t)·t²·p3 + d·t³·p4,
+ * where pN are control points and p1 is zero due to relative coordinates.
+ *
+ * @param {Array} curve array of curve points coordinates
+ * @param {Number} t parametric position from 0 to 1
+ * @return {Array} Point coordinates
+ */
+
+function getCubicBezierPoint(curve, t) {
+    var sqrT = t * t,
+        cubT = sqrT * t,
+        mt = 1 - t,
+        sqrMt = mt * mt;
+
+    return [
+        3 * sqrMt * t * curve[0] + 3 * mt * sqrT * curve[2] + cubT * curve[4],
+        3 * sqrMt * t * curve[1] + 3 * mt * sqrT * curve[3] + cubT * curve[5]
+    ];
+}
+
+/**
+ * Finds circle by 3 points of the curve and checks if the curve fits the found circle.
+ *
+ * @param {Array} curve
+ * @return {Object|undefined} circle
+ */
+
+function findCircle(curve) {
+    var midPoint = getCubicBezierPoint(curve, 1/2),
+        m1 = [midPoint[0] / 2, midPoint[1] / 2],
+        m2 = [(midPoint[0] + curve[4]) / 2, (midPoint[1] + curve[5]) / 2],
+        center = getIntersection([
+            m1[0], m1[1],
+            m1[0] + m1[1], m1[1] - m1[0],
+            m2[0], m2[1],
+            m2[0] + (m2[1] - midPoint[1]), m2[1] - (m2[0] - midPoint[0])
+        ]),
+        radius = center && getDistance([0, 0], center),
+        tolerance = Math.min(arcThreshold * error, arcTolerance * radius / 100);
+
+    if (center && radius < 1e15 &&
+        [1/4, 3/4].every(function(point) {
+        return Math.abs(getDistance(getCubicBezierPoint(curve, point), center) - radius) <= tolerance;
+    }))
+        return { center: center, radius: radius};
+}
+
+/**
+ * Checks if a curve fits the given circle.
+ *
+ * @param {Object} circle
+ * @param {Array} curve
+ * @return {Boolean}
+ */
+
+function isArc(curve, circle) {
+    var tolerance = Math.min(arcThreshold * error, arcTolerance * circle.radius / 100);
+
+    return [0, 1/4, 1/2, 3/4, 1].every(function(point) {
+        return Math.abs(getDistance(getCubicBezierPoint(curve, point), circle.center) - circle.radius) <= tolerance;
+    });
+}
+
+/**
+ * Checks if a previous curve fits the given circle.
+ *
+ * @param {Object} circle
+ * @param {Array} curve
+ * @return {Boolean}
+ */
+
+function isArcPrev(curve, circle) {
+    return isArc(curve, {
+        center: [circle.center[0] + curve[4], circle.center[1] + curve[5]],
+        radius: circle.radius
+    });
+}
+
+/**
+ * Finds angle of a curve fitting the given arc.
+
+ * @param {Array} curve
+ * @param {Object} relCircle
+ * @return {Number} angle
+ */
+
+function findArcAngle(curve, relCircle) {
+    var x1 = -relCircle.center[0],
+        y1 = -relCircle.center[1],
+        x2 = curve[4] - relCircle.center[0],
+        y2 = curve[5] - relCircle.center[1];
+
+    return Math.acos(
+            (x1 * x2 + y1 * y2) /
+            Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))
+        );
+}
+
+/**
+ * Converts given path data to string.
+ *
+ * @param {Object} params
+ * @param {Array} pathData
+ * @return {String}
+ */
+
+function data2Path(params, pathData) {
+    return pathData.reduce(function(pathString, item) {
+        var strData = '';
+        if (item.data) {
+            strData = cleanupOutData(roundData(item.data.slice()), params);
+        }
+        return pathString + item.instruction + strData;
+    }, '');
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/convertShapeToPath.js b/vendor/svgclean/src/svgclean/plugins/convertShapeToPath.js
new file mode 100644
index 000000000..907246145
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/convertShapeToPath.js
@@ -0,0 +1,149 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'converts basic shapes to more compact path form';
+
+exports.params = {
+    convertArcs: false
+};
+
+var none = { value: 0 },
+    regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
+
+/**
+ * Converts basic shape to more compact path.
+ * It also allows further optimizations like
+ * combining paths with similar attributes.
+ *
+ * @see http://www.w3.org/TR/SVG/shapes.html
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Lev Solntsev
+ */
+exports.fn = function(item, params) {
+    var convertArcs = params && params.convertArcs;
+
+    if (
+        item.isElem('rect') &&
+        item.hasAttr('width') &&
+        item.hasAttr('height') &&
+        !item.hasAttr('rx') &&
+        !item.hasAttr('ry')
+    ) {
+
+        var x = +(item.attr('x') || none).value,
+            y = +(item.attr('y') || none).value,
+            width  = +item.attr('width').value,
+            height = +item.attr('height').value;
+
+            // Values like '100%' compute to NaN, thus running after
+            // cleanupNumericValues when 'px' units has already been removed.
+            // TODO: Calculate sizes from % and non-px units if possible.
+        if (isNaN(x - y + width - height)) return;
+
+        var pathData =
+            'M' + x + ' ' + y +
+            'H' + (x + width) +
+            'V' + (y + height) +
+            'H' + x +
+            'z';
+
+        item.addAttr({
+                name: 'd',
+                value: pathData,
+                prefix: '',
+                local: 'd'
+            });
+
+        item.renameElem('path')
+            .removeAttr(['x', 'y', 'width', 'height']);
+
+    } else if (item.isElem('line')) {
+
+        var x1 = +(item.attr('x1') || none).value,
+            y1 = +(item.attr('y1') || none).value,
+            x2 = +(item.attr('x2') || none).value,
+            y2 = +(item.attr('y2') || none).value;
+        if (isNaN(x1 - y1 + x2 - y2)) return;
+
+        item.addAttr({
+                name: 'd',
+                value: 'M' + x1 + ' ' + y1 + 'L' + x2 + ' ' + y2,
+                prefix: '',
+                local: 'd'
+            });
+
+        item.renameElem('path')
+            .removeAttr(['x1', 'y1', 'x2', 'y2']);
+
+    } else if ((
+            item.isElem('polyline') ||
+            item.isElem('polygon')
+        ) &&
+        item.hasAttr('points')
+    ) {
+
+        var coords = (item.attr('points').value.match(regNumber) || []).map(Number);
+        if (coords.length < 4) return false;
+
+        item.addAttr({
+                name: 'd',
+                value: 'M' + coords.slice(0,2).join(' ') +
+                       'L' + coords.slice(2).join(' ') +
+                       (item.isElem('polygon') ? 'z' : ''),
+                prefix: '',
+                local: 'd'
+            });
+
+        item.renameElem('path')
+            .removeAttr('points');
+    } else if (item.isElem('circle') && convertArcs) {
+
+        var cx = +(item.attr('cx') || none).value;
+        var cy = +(item.attr('cy') || none).value;
+        var r = +(item.attr('r') || none).value;
+        if (isNaN(cx - cy + r)) {
+            return;
+        }
+        var cPathData =
+            'M' + cx  + ' ' + (cy - r) +
+                'A' + r + ' ' + r + ' 0 1 0 ' + cx + ' ' + (cy + r) +
+                'A' + r + ' ' + r + ' 0 1 0 ' + cx + ' ' + (cy - r) +
+                'Z';
+        item.addAttr({
+                name: 'd',
+                value: cPathData,
+                prefix: '',
+                local: 'd',
+        });
+        item.renameElem('path').removeAttr(['cx', 'cy', 'r']);
+
+    } else if (item.isElem('ellipse') && convertArcs) {
+
+        var ecx = +(item.attr('cx') || none).value;
+        var ecy = +(item.attr('cy') || none).value;
+        var rx = +(item.attr('rx') || none).value;
+        var ry = +(item.attr('ry') || none).value;
+        if (isNaN(ecx - ecy + rx - ry)) {
+            return;
+        }
+        var ePathData =
+            'M' + ecx + ' ' + (ecy - ry) +
+                'A' + rx + ' ' + ry + ' 0 1 0 ' + ecx + ' ' + (ecy + ry) +
+                'A' + rx + ' ' + ry + ' 0 1 0 ' + ecx + ' ' + (ecy - ry) +
+                'Z';
+        item.addAttr({
+                name: 'd',
+                value: ePathData,
+                prefix: '',
+                local: 'd',
+        });
+        item.renameElem('path').removeAttr(['cx', 'cy', 'rx', 'ry']);
+    }
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/convertStyleToAttrs.js b/vendor/svgclean/src/svgclean/plugins/convertStyleToAttrs.js
new file mode 100644
index 000000000..29d69a777
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/convertStyleToAttrs.js
@@ -0,0 +1,125 @@
+/* jshint quotmark: false */
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'converts style to attributes';
+
+exports.params = {
+    keepImportant: false
+};
+
+var stylingProps = require('./_collections').attrsGroups.presentation,
+    rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)',                 // Like \" or \2051. Code points consume one space.
+    rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*',          // attribute name like ‘fill’
+    rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth'
+    rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)',       // string in double quotes: "smth"
+    rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'),
+
+    // Parentheses, E.g.: url(data:image/png;base64,iVBO...).
+    // ':' and ';' inside of it should be threated as is. (Just like in strings.)
+    rParenthesis = '\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)',
+
+    // The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
+    rValue = '\\s*(' + g('[^!\'"();\\\\]+?', rEscape, rSingleQuotes, rQuotes, rParenthesis, '[^;]*?') + '*?' + ')',
+
+    // End of declaration. Spaces outside of capturing groups help to do natural trimming.
+    rDeclEnd = '\\s*(?:;\\s*|$)',
+
+    // Important rule
+    rImportant = '(\\s*!important(?![-(\w]))?',
+
+    // Final RegExp to parse CSS declarations.
+    regDeclarationBlock = new RegExp(rAttr + ':' + rValue + rImportant + rDeclEnd, 'ig'),
+
+    // Comments expression. Honors escape sequences and strings.
+    regStripComments = new RegExp(g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'), 'ig');
+
+/**
+ * Convert style in attributes. Cleanups comments and illegal declarations (without colon) as a side effect.
+ *
+ * @example
+ * <g style="fill:#000; color: #fff;">
+ *             ⬇
+ * <g fill="#000" color="#fff">
+ *
+ * @example
+ * <g style="fill:#000; color: #fff; -webkit-blah: blah">
+ *             ⬇
+ * <g fill="#000" color="#fff" style="-webkit-blah: blah">
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+    /* jshint boss: true */
+
+    if (item.elem && item.hasAttr('style')) {
+            // ['opacity: 1', 'color: #000']
+        var styleValue = item.attr('style').value,
+            styles = [],
+            attrs = {};
+
+        // Strip CSS comments preserving escape sequences and strings.
+        styleValue = styleValue.replace(regStripComments, function(match) {
+            return match[0] == '/' ? '' :
+                match[0] == '\\' && /[-g-z]/i.test(match[1]) ? match[1] : match;
+        });
+
+        regDeclarationBlock.lastIndex = 0;
+        for (var rule; rule = regDeclarationBlock.exec(styleValue);) {
+            if (!params.keepImportant || !rule[3]) {
+                styles.push([rule[1], rule[2]]);
+            }
+        }
+
+        if (styles.length) {
+
+            styles = styles.filter(function(style) {
+                if (style[0]) {
+                    var prop = style[0].toLowerCase(),
+                        val = style[1];
+
+                    if (rQuotedString.test(val)) {
+                        val = val.slice(1, -1);
+                    }
+
+                    if (stylingProps.indexOf(prop) > -1) {
+
+                        attrs[prop] = {
+                            name: prop,
+                            value: val,
+                            local: prop,
+                            prefix: ''
+                        };
+
+                        return false;
+                    }
+                }
+
+                return true;
+            });
+
+            Object.assign(item.attrs, attrs);
+
+            if (styles.length) {
+                item.attr('style').value = styles
+                    .map(function(declaration) { return declaration.join(':') })
+                    .join(';');
+            } else {
+                item.removeAttr('style');
+            }
+
+        }
+
+    }
+
+};
+
+function g() {
+    return '(?:' + Array.prototype.join.call(arguments, '|') + ')';
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/convertTransform.js b/vendor/svgclean/src/svgclean/plugins/convertTransform.js
new file mode 100644
index 000000000..5ed576ff0
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/convertTransform.js
@@ -0,0 +1,363 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'collapses multiple transformations and optimizes it';
+
+exports.params = {
+    convertToShorts: true,
+    // degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default
+    floatPrecision: 3,
+    transformPrecision: 5,
+    matrixToTransform: true,
+    shortTranslate: true,
+    shortScale: true,
+    shortRotate: true,
+    removeUseless: true,
+    collapseIntoOne: true,
+    leadingZero: true,
+    negativeExtraSpace: false
+};
+
+var cleanupOutData = require('../tools').cleanupOutData,
+    transform2js = require('./_transforms.js').transform2js,
+    transformsMultiply = require('./_transforms.js').transformsMultiply,
+    matrixToTransform = require('./_transforms.js').matrixToTransform,
+    degRound,
+    floatRound,
+    transformRound;
+
+/**
+ * Convert matrices to the short aliases,
+ * convert long translate, scale or rotate transform notations to the shorts ones,
+ * convert transforms to the matrices and multiply them all into one,
+ * remove useless transforms.
+ *
+ * @see http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (item.elem) {
+
+        // transform
+        if (item.hasAttr('transform')) {
+            convertTransform(item, 'transform', params);
+        }
+
+        // gradientTransform
+        if (item.hasAttr('gradientTransform')) {
+            convertTransform(item, 'gradientTransform', params);
+        }
+
+        // patternTransform
+        if (item.hasAttr('patternTransform')) {
+            convertTransform(item, 'patternTransform', params);
+        }
+
+    }
+
+};
+
+/**
+ * Main function.
+ *
+ * @param {Object} item input item
+ * @param {String} attrName attribute name
+ * @param {Object} params plugin params
+ */
+function convertTransform(item, attrName, params) {
+    var data = transform2js(item.attr(attrName).value);
+    params = definePrecision(data, params);
+
+    if (params.collapseIntoOne && data.length > 1) {
+        data = [transformsMultiply(data)];
+    }
+
+    if (params.convertToShorts) {
+        data = convertToShorts(data, params);
+    } else {
+        data.forEach(roundTransform);
+    }
+
+    if (params.removeUseless) {
+        data = removeUseless(data);
+    }
+
+    if (data.length) {
+        item.attr(attrName).value = js2transform(data, params);
+    } else {
+        item.removeAttr(attrName);
+    }
+}
+
+/**
+ * Defines precision to work with certain parts.
+ * transformPrecision - for scale and four first matrix parameters (needs a better precision due to multiplying),
+ * floatPrecision - for translate including two last matrix and rotate parameters,
+ * degPrecision - for rotate and skew. By default it's equal to (rougly)
+ * transformPrecision - 2 or floatPrecision whichever is lower. Can be set in params.
+ *
+ * @param {Array} transforms input array
+ * @param {Object} params plugin params
+ * @return {Array} output array
+ */
+function definePrecision(data, params) {
+    /* jshint validthis: true */
+    var matrixData = data.reduce(getMatrixData, []),
+        significantDigits = params.transformPrecision;
+
+    // Clone params so it don't affect other elements transformations.
+    params = Object.assign({}, params);
+
+    // Limit transform precision with matrix one. Calculating with larger precision doesn't add any value.
+    if (matrixData.length) {
+        params.transformPrecision = Math.min(params.transformPrecision,
+            Math.max.apply(Math, matrixData.map(floatDigits)) || params.transformPrecision);
+
+        significantDigits = Math.max.apply(Math, matrixData.map(function(n) {
+            return String(n).replace(/\D+/g, '').length; // Number of digits in a number. 123.45 → 5
+        }));
+    }
+    // No sense in angle precision more then number of significant digits in matrix.
+    if (!('degPrecision' in params)) {
+        params.degPrecision = Math.max(0, Math.min(params.floatPrecision, significantDigits - 2));
+    }
+
+    floatRound = params.floatPrecision >= 1 && params.floatPrecision < 20 ?
+        smartRound.bind(this, params.floatPrecision) :
+        round;
+    degRound = params.degPrecision >= 1 && params.floatPrecision < 20 ?
+        smartRound.bind(this, params.degPrecision) :
+        round;
+    transformRound = params.transformPrecision >= 1 && params.floatPrecision < 20 ?
+        smartRound.bind(this, params.transformPrecision) :
+        round;
+
+    return params;
+}
+
+/**
+ * Gathers four first matrix parameters.
+ *
+ * @param {Array} a array of data
+ * @param {Object} transform
+ * @return {Array} output array
+ */
+function getMatrixData(a, b) {
+    return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a;
+}
+
+/**
+ * Returns number of digits after the point. 0.125 → 3
+ */
+function floatDigits(n) {
+    return (n = String(n)).slice(n.indexOf('.')).length - 1;
+}
+
+/**
+ * Convert transforms to the shorthand alternatives.
+ *
+ * @param {Array} transforms input array
+ * @param {Object} params plugin params
+ * @return {Array} output array
+ */
+function convertToShorts(transforms, params) {
+
+    for(var i = 0; i < transforms.length; i++) {
+
+        var transform = transforms[i];
+
+        // convert matrix to the short aliases
+        if (
+            params.matrixToTransform &&
+            transform.name === 'matrix'
+        ) {
+            var decomposed = matrixToTransform(transform, params);
+            if (decomposed != transform &&
+                js2transform(decomposed, params).length <= js2transform([transform], params).length) {
+
+                transforms.splice.apply(transforms, [i, 1].concat(decomposed));
+            }
+            transform = transforms[i];
+        }
+
+        // fixed-point numbers
+        // 12.754997 → 12.755
+        roundTransform(transform);
+
+        // convert long translate transform notation to the shorts one
+        // translate(10 0) → translate(10)
+        if (
+            params.shortTranslate &&
+            transform.name === 'translate' &&
+            transform.data.length === 2 &&
+            !transform.data[1]
+        ) {
+            transform.data.pop();
+        }
+
+        // convert long scale transform notation to the shorts one
+        // scale(2 2) → scale(2)
+        if (
+            params.shortScale &&
+            transform.name === 'scale' &&
+            transform.data.length === 2 &&
+            transform.data[0] === transform.data[1]
+        ) {
+            transform.data.pop();
+        }
+
+        // convert long rotate transform notation to the short one
+        // translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy)
+        if (
+            params.shortRotate &&
+            transforms[i - 2] &&
+            transforms[i - 2].name === 'translate' &&
+            transforms[i - 1].name === 'rotate' &&
+            transforms[i].name === 'translate' &&
+            transforms[i - 2].data[0] === -transforms[i].data[0] &&
+            transforms[i - 2].data[1] === -transforms[i].data[1]
+        ) {
+            transforms.splice(i - 2, 3, {
+                name: 'rotate',
+                data: [
+                    transforms[i - 1].data[0],
+                    transforms[i - 2].data[0],
+                    transforms[i - 2].data[1]
+                ]
+            });
+
+            // splice compensation
+            i -= 2;
+
+            transform = transforms[i];
+        }
+
+    }
+
+    return transforms;
+
+}
+
+/**
+ * Remove useless transforms.
+ *
+ * @param {Array} transforms input array
+ * @return {Array} output array
+ */
+function removeUseless(transforms) {
+
+    return transforms.filter(function(transform) {
+
+        // translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0)
+        if (
+            ['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 &&
+            (transform.data.length == 1 || transform.name == 'rotate') &&
+            !transform.data[0] ||
+
+            // translate(0, 0)
+            transform.name == 'translate' &&
+            !transform.data[0] &&
+            !transform.data[1] ||
+
+            // scale(1)
+            transform.name == 'scale' &&
+            transform.data[0] == 1 &&
+            (transform.data.length < 2 || transform.data[1] == 1) ||
+
+            // matrix(1 0 0 1 0 0)
+            transform.name == 'matrix' &&
+            transform.data[0] == 1 &&
+            transform.data[3] == 1 &&
+            !(transform.data[1] || transform.data[2] || transform.data[4] || transform.data[5])
+        ) {
+            return false;
+        }
+
+        return true;
+
+    });
+
+}
+
+/**
+ * Convert transforms JS representation to string.
+ *
+ * @param {Array} transformJS JS representation array
+ * @param {Object} params plugin params
+ * @return {String} output string
+ */
+function js2transform(transformJS, params) {
+
+    var transformString = '';
+
+    // collect output value string
+    transformJS.forEach(function(transform) {
+        roundTransform(transform);
+        transformString += (transformString && ' ') + transform.name + '(' + cleanupOutData(transform.data, params) + ')';
+    });
+
+    return transformString;
+
+}
+
+function roundTransform(transform) {
+    switch (transform.name) {
+        case 'translate':
+            transform.data = floatRound(transform.data);
+            break;
+        case 'rotate':
+            transform.data = degRound(transform.data.slice(0, 1)).concat(floatRound(transform.data.slice(1)));
+            break;
+        case 'skewX':
+        case 'skewY':
+            transform.data = degRound(transform.data);
+            break;
+        case 'scale':
+            transform.data = transformRound(transform.data);
+            break;
+        case 'matrix':
+            transform.data = transformRound(transform.data.slice(0, 4)).concat(floatRound(transform.data.slice(4)));
+            break;
+    }
+    return transform;
+}
+
+/**
+ * Rounds numbers in array.
+ *
+ * @param {Array} data input data array
+ * @return {Array} output data array
+ */
+function round(data) {
+    return data.map(Math.round);
+}
+
+/**
+ * Decrease accuracy of floating-point numbers
+ * in transforms keeping a specified number of decimals.
+ * Smart rounds values like 2.349 to 2.35.
+ *
+ * @param {Number} fixed number of decimals
+ * @param {Array} data input data array
+ * @return {Array} output data array
+ */
+function smartRound(precision, data) {
+    for (var i = data.length, tolerance = +Math.pow(.1, precision).toFixed(precision); i--;) {
+        if (data[i].toFixed(precision) != data[i]) {
+            var rounded = +data[i].toFixed(precision - 1);
+            data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance ?
+                +data[i].toFixed(precision) :
+                rounded;
+        }
+    }
+    return data;
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/inlineStyles.js b/vendor/svgclean/src/svgclean/plugins/inlineStyles.js
new file mode 100644
index 000000000..009c532ef
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/inlineStyles.js
@@ -0,0 +1,245 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.params = {
+    onlyMatchedOnce: true,
+    removeMatchedSelectors: true,
+    useMqs: ['', 'screen'],
+    usePseudos: ['']
+};
+
+exports.description = 'inline styles (additional options)';
+
+
+var csstree = require('css-tree'),
+    cssTools = require('../css-tools');
+
+/**
+ * Moves + merges styles from style elements to element styles
+ *
+ * Options
+ *   onlyMatchedOnce (default: true)
+ *     inline only selectors that match once
+ *
+ *   removeMatchedSelectors (default: true)
+ *     clean up matched selectors,
+ *     leave selectors that hadn't matched
+ *
+ *   useMqs (default: ['', 'screen'])
+ *     what media queries to be used
+ *     empty string element for styles outside media queries
+ *
+ *   usePseudos (default: [''])
+ *     what pseudo-classes/-elements to be used
+ *     empty string element for all non-pseudo-classes and/or -elements
+ *
+ * @param {Object} document document element
+ * @param {Object} opts plugin params
+ *
+ * @author strarsis <strarsis@gmail.com>
+ */
+exports.fn = function(document, opts) {
+
+    // collect <style/>s
+    var styleEls = document.querySelectorAll('style');
+
+    //no <styles/>s, nothing to do
+    if (styleEls === null) {
+        return document;
+    }
+
+    var styles = [],
+        selectors = [];
+
+    for (var styleEl of styleEls) {
+        if (styleEl.isEmpty() || styleEl.closestElem('foreignObject')) {
+            // skip empty <style/>s or <foreignObject> content.
+            continue;
+        }
+
+        var cssStr = cssTools.getCssStr(styleEl);
+
+        // collect <style/>s and their css ast
+        var cssAst = {};
+        try {
+            cssAst = csstree.parse(cssStr, {
+                parseValue: false,
+                parseCustomProperty: false
+            });
+        } catch (parseError) {
+            // console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
+            continue;
+        }
+
+        styles.push({
+            styleEl: styleEl,
+            cssAst: cssAst
+        });
+
+        selectors = selectors.concat(cssTools.flattenToSelectors(cssAst));
+    }
+
+
+    // filter for mediaqueries to be used or without any mediaquery
+    var selectorsMq = cssTools.filterByMqs(selectors, opts.useMqs);
+
+
+    // filter for pseudo elements to be used
+    var selectorsPseudo = cssTools.filterByPseudos(selectorsMq, opts.usePseudos);
+
+    // remove PseudoClass from its SimpleSelector for proper matching
+    cssTools.cleanPseudos(selectorsPseudo);
+
+
+    // stable sort selectors
+    var sortedSelectors = cssTools.sortSelectors(selectorsPseudo).reverse();
+
+
+    var selector,
+        selectedEl;
+
+    // match selectors
+    for (selector of sortedSelectors) {
+        var selectorStr = csstree.generate(selector.item.data),
+            selectedEls = null;
+
+        try {
+            selectedEls = document.querySelectorAll(selectorStr);
+        } catch (selectError) {
+            if (selectError.constructor === SyntaxError) {
+                // console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError);
+                continue;
+            }
+            throw selectError;
+        }
+
+        if (selectedEls === null) {
+            // nothing selected
+            continue;
+        }
+
+        selector.selectedEls = selectedEls;
+    }
+
+
+    // apply <style/> styles to matched elements
+    for (selector of sortedSelectors) {
+        if(!selector.selectedEls) {
+            continue;
+        }
+
+        if (opts.onlyMatchedOnce && selector.selectedEls !== null && selector.selectedEls.length > 1) {
+            // skip selectors that match more than once if option onlyMatchedOnce is enabled
+            continue;
+        }
+
+        // apply <style/> to matched elements
+        for (selectedEl of selector.selectedEls) {
+            if (selector.rule === null) {
+                continue;
+            }
+
+            // merge declarations
+            csstree.walk(selector.rule, {visit: 'Declaration', enter: function(styleCsstreeDeclaration) {
+
+                // existing inline styles have higher priority
+                // no inline styles, external styles,                                    external styles used
+                // inline styles,    external styles same   priority as inline styles,   inline   styles used
+                // inline styles,    external styles higher priority than inline styles, external styles used
+                var styleDeclaration = cssTools.csstreeToStyleDeclaration(styleCsstreeDeclaration);
+                if (selectedEl.style.getPropertyValue(styleDeclaration.name) !== null &&
+                    selectedEl.style.getPropertyPriority(styleDeclaration.name) >= styleDeclaration.priority) {
+                    return;
+                }
+                selectedEl.style.setProperty(styleDeclaration.name, styleDeclaration.value, styleDeclaration.priority);
+            }});
+        }
+
+        if (opts.removeMatchedSelectors && selector.selectedEls !== null && selector.selectedEls.length > 0) {
+            // clean up matching simple selectors if option removeMatchedSelectors is enabled
+            selector.rule.prelude.children.remove(selector.item);
+        }
+    }
+
+
+    if (!opts.removeMatchedSelectors) {
+        return document; // no further processing required
+    }
+
+
+    // clean up matched class + ID attribute values
+    for (selector of sortedSelectors) {
+        if(!selector.selectedEls) {
+            continue;
+        }
+
+        if (opts.onlyMatchedOnce && selector.selectedEls !== null && selector.selectedEls.length > 1) {
+            // skip selectors that match more than once if option onlyMatchedOnce is enabled
+            continue;
+        }
+
+        for (selectedEl of selector.selectedEls) {
+            // class
+            var firstSubSelector = selector.item.data.children.first();
+            if(firstSubSelector.type === 'ClassSelector') {
+                selectedEl.class.remove(firstSubSelector.name);
+            }
+            // clean up now empty class attributes
+            if(typeof selectedEl.class.item(0) === 'undefined') {
+                selectedEl.removeAttr('class');
+            }
+
+            // ID
+            if(firstSubSelector.type === 'IdSelector') {
+                selectedEl.removeAttr('id', firstSubSelector.name);
+            }
+        }
+    }
+
+
+    // clean up now empty elements
+    for (var style of styles) {
+        csstree.walk(style.cssAst, {visit: 'Rule', enter: function(node, item, list) {
+            // clean up <style/> atrules without any rulesets left
+            if (node.type === 'Atrule' &&
+                // only Atrules containing rulesets
+                node.block !== null &&
+                node.block.children.isEmpty()) {
+                list.remove(item);
+                return;
+            }
+
+            // clean up <style/> rulesets without any css selectors left
+            if (node.type === 'Rule' &&
+                node.prelude.children.isEmpty()) {
+                list.remove(item);
+            }
+        }});
+
+
+        if (style.cssAst.children.isEmpty()) {
+            // clean up now emtpy <style/>s
+            var styleParentEl = style.styleEl.parentNode;
+            styleParentEl.spliceContent(styleParentEl.content.indexOf(style.styleEl), 1);
+
+            if (styleParentEl.elem === 'defs' &&
+                styleParentEl.content.length === 0) {
+                // also clean up now empty <def/>s
+                var defsParentEl = styleParentEl.parentNode;
+                defsParentEl.spliceContent(defsParentEl.content.indexOf(styleParentEl), 1);
+            }
+
+            continue;
+        }
+
+
+        // update existing, left over <style>s
+        cssTools.setCssStr(style.styleEl, csstree.generate(style.cssAst));
+    }
+
+
+    return document;
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/mergePaths.js b/vendor/svgclean/src/svgclean/plugins/mergePaths.js
new file mode 100644
index 000000000..6a18996bf
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/mergePaths.js
@@ -0,0 +1,73 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'merges multiple paths in one if possible';
+
+exports.params = {
+    collapseRepeated: true,
+    force: false,
+    leadingZero: true,
+    negativeExtraSpace: true,
+    noSpaceAfterFlags: true
+};
+
+var path2js = require('./_path.js').path2js,
+    js2path = require('./_path.js').js2path,
+    intersects = require('./_path.js').intersects;
+
+/**
+ * Merge multiple Paths into one.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich, Lev Solntsev
+ */
+exports.fn = function(item, params) {
+
+    if (!item.isElem() || item.isEmpty()) return;
+
+    var prevContentItem = null,
+        prevContentItemKeys = null;
+
+    item.content = item.content.filter(function(contentItem) {
+
+        if (prevContentItem &&
+            prevContentItem.isElem('path') &&
+            prevContentItem.isEmpty() &&
+            prevContentItem.hasAttr('d') &&
+            contentItem.isElem('path') &&
+            contentItem.isEmpty() &&
+            contentItem.hasAttr('d')
+        ) {
+
+            if (!prevContentItemKeys) {
+                prevContentItemKeys = Object.keys(prevContentItem.attrs);
+            }
+
+            var contentItemAttrs = Object.keys(contentItem.attrs),
+                equalData = prevContentItemKeys.length == contentItemAttrs.length &&
+                    contentItemAttrs.every(function(key) {
+                        return key == 'd' ||
+                            prevContentItem.hasAttr(key) &&
+                            prevContentItem.attr(key).value == contentItem.attr(key).value;
+                    }),
+                prevPathJS = path2js(prevContentItem),
+                curPathJS = path2js(contentItem);
+
+            if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) {
+                js2path(prevContentItem, prevPathJS.concat(curPathJS), params);
+                return false;
+            }
+        }
+
+        prevContentItem = contentItem;
+        prevContentItemKeys = null;
+        return true;
+
+    });
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/minifyStyles.js b/vendor/svgclean/src/svgclean/plugins/minifyStyles.js
new file mode 100755
index 000000000..d240900ed
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/minifyStyles.js
@@ -0,0 +1,160 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.description = 'minifies styles and removes unused styles based on usage data';
+
+exports.params = {
+    // ... CSSO options goes here
+
+    // additional 
+    usage: {
+        force: false,  // force to use usage data even if it unsafe (document contains <script> or on* attributes)
+        ids: true,
+        classes: true,
+        tags: true
+    }
+};
+
+var csso = require('csso');
+
+/**
+ * Minifies styles (<style> element + style attribute) using CSSO
+ *
+ * @author strarsis <strarsis@gmail.com>
+ */
+exports.fn = function(ast, options) {
+    options = options || {};
+
+    var minifyOptionsForStylesheet = cloneObject(options);
+    var minifyOptionsForAttribute = cloneObject(options);
+    var elems = findStyleElems(ast);
+
+    minifyOptionsForStylesheet.usage = collectUsageData(ast, options);
+    minifyOptionsForAttribute.usage = null;
+
+    elems.forEach(function(elem) {
+        if (elem.isElem('style')) {
+            // <style> element
+            var styleCss = elem.content[0].text || elem.content[0].cdata || [];
+            var DATA = styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0 ? 'cdata' : 'text';
+
+            elem.content[0][DATA] = csso.minify(styleCss, minifyOptionsForStylesheet).css;
+        } else {
+            // style attribute
+            var elemStyle = elem.attr('style').value;
+
+            elem.attr('style').value = csso.minifyBlock(elemStyle, minifyOptionsForAttribute).css;
+        }
+    });
+
+    return ast;
+};
+
+function cloneObject(obj) {
+    var result = {};
+
+    for (var key in obj) {
+        result[key] = obj[key];
+    }
+
+    return result;
+}
+
+function findStyleElems(ast) {
+
+    function walk(items, styles) {
+        for (var i = 0; i < items.content.length; i++) {
+            var item = items.content[i];
+
+            // go deeper
+            if (item.content) {
+                walk(item, styles);
+            }
+
+            if (item.isElem('style') && !item.isEmpty()) {
+                styles.push(item);
+            } else if (item.isElem() && item.hasAttr('style')) {
+                styles.push(item);
+            }
+        }
+
+        return styles;
+    }
+
+    return walk(ast, []);
+}
+
+function shouldFilter(options, name) {
+    if ('usage' in options === false) {
+        return true;
+    }
+
+    if (options.usage && name in options.usage === false) {
+        return true;
+    }
+
+    return Boolean(options.usage && options.usage[name]);
+}
+
+function collectUsageData(ast, options) {
+
+    function walk(items, usageData) {
+        for (var i = 0; i < items.content.length; i++) {
+            var item = items.content[i];
+
+            // go deeper
+            if (item.content) {
+                walk(item, usageData);
+            }
+
+            if (item.isElem('script')) {
+                safe = false;
+            }
+
+            if (item.isElem()) {
+                usageData.tags[item.elem] = true;
+
+                if (item.hasAttr('id')) {
+                    usageData.ids[item.attr('id').value] = true;
+                }
+
+                if (item.hasAttr('class')) {
+                    item.attr('class').value.replace(/^\s+|\s+$/g, '').split(/\s+/).forEach(function(className) {
+                        usageData.classes[className] = true;
+                    });
+                }
+
+                if (item.attrs && Object.keys(item.attrs).some(function(name) { return /^on/i.test(name); })) {
+                    safe = false;
+                }
+            }
+        }
+
+        return usageData;
+    }
+
+    var safe = true;
+    var usageData = {};
+    var hasData = false;
+    var rawData = walk(ast, {
+        ids: Object.create(null),
+        classes: Object.create(null),
+        tags: Object.create(null)
+    });
+
+    if (!safe && options.usage && options.usage.force) {
+        safe = true;
+    }
+
+    for (var key in rawData) {
+        if (shouldFilter(options, key)) {
+            usageData[key] = Object.keys(rawData[key]);
+            hasData = true;
+        }
+    }
+
+    return safe && hasData ? usageData : null;
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/moveElemsAttrsToGroup.js b/vendor/svgclean/src/svgclean/plugins/moveElemsAttrsToGroup.js
new file mode 100644
index 000000000..0f3b65c70
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/moveElemsAttrsToGroup.js
@@ -0,0 +1,126 @@
+'use strict';
+
+exports.type = 'perItemReverse';
+
+exports.active = true;
+
+exports.description = 'moves elements attributes to the existing group wrapper';
+
+var inheritableAttrs = require('./_collections').inheritableAttrs,
+    pathElems = require('./_collections.js').pathElems;
+
+/**
+ * Collapse content's intersected and inheritable
+ * attributes to the existing group wrapper.
+ *
+ * @example
+ * <g attr1="val1">
+ *     <g attr2="val2">
+ *         text
+ *     </g>
+ *     <circle attr2="val2" attr3="val3"/>
+ * </g>
+ *              ⬇
+ * <g attr1="val1" attr2="val2">
+ *     <g>
+ *         text
+ *     </g>
+ *    <circle attr3="val3"/>
+ * </g>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('g') && !item.isEmpty() && item.content.length > 1) {
+
+        var intersection = {},
+            hasTransform = false,
+            hasClip = item.hasAttr('clip-path') || item.hasAttr('mask'),
+            intersected = item.content.every(function(inner) {
+                if (inner.isElem() && inner.hasAttr()) {
+                    // don't mess with possible styles (hack until CSS parsing is implemented)
+                    if (inner.hasAttr('class')) return false;
+                    if (!Object.keys(intersection).length) {
+                        intersection = inner.attrs;
+                    } else {
+                        intersection = intersectInheritableAttrs(intersection, inner.attrs);
+
+                        if (!intersection) return false;
+                    }
+
+                    return true;
+                }
+            }),
+            allPath = item.content.every(function(inner) {
+                return inner.isElem(pathElems);
+            });
+
+        if (intersected) {
+
+            item.content.forEach(function(g) {
+
+                for (var name in intersection) {
+
+                    if (!allPath && !hasClip || name !== 'transform') {
+
+                        g.removeAttr(name);
+
+                        if (name === 'transform') {
+                            if (!hasTransform) {
+                                if (item.hasAttr('transform')) {
+                                    item.attr('transform').value += ' ' + intersection[name].value;
+                                } else {
+                                    item.addAttr(intersection[name]);
+                                }
+
+                                hasTransform = true;
+                            }
+                        } else {
+                            item.addAttr(intersection[name]);
+                        }
+
+                    }
+                }
+
+            });
+
+        }
+
+    }
+
+};
+
+/**
+ * Intersect inheritable attributes.
+ *
+ * @param {Object} a first attrs object
+ * @param {Object} b second attrs object
+ *
+ * @return {Object} intersected attrs object
+ */
+function intersectInheritableAttrs(a, b) {
+
+    var c = {};
+
+    for (var n in a) {
+        if (
+            b.hasOwnProperty(n) &&
+            inheritableAttrs.indexOf(n) > -1 &&
+            a[n].name === b[n].name &&
+            a[n].value === b[n].value &&
+            a[n].prefix === b[n].prefix &&
+            a[n].local === b[n].local
+        ) {
+            c[n] = a[n];
+        }
+    }
+
+    if (!Object.keys(c).length) return false;
+
+    return c;
+
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/moveGroupAttrsToElems.js b/vendor/svgclean/src/svgclean/plugins/moveGroupAttrsToElems.js
new file mode 100644
index 000000000..a966f882e
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/moveGroupAttrsToElems.js
@@ -0,0 +1,63 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'moves some group attributes to the content elements';
+
+var collections = require('./_collections.js'),
+    pathElems = collections.pathElems.concat(['g', 'text']),
+    referencesProps = collections.referencesProps;
+
+/**
+ * Move group attrs to the content elements.
+ *
+ * @example
+ * <g transform="scale(2)">
+ *     <path transform="rotate(45)" d="M0,0 L10,20"/>
+ *     <path transform="translate(10, 20)" d="M0,10 L20,30"/>
+ * </g>
+ *                          ⬇
+ * <g>
+ *     <path transform="scale(2) rotate(45)" d="M0,0 L10,20"/>
+ *     <path transform="scale(2) translate(10, 20)" d="M0,10 L20,30"/>
+ * </g>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    // move group transform attr to content's pathElems
+    if (
+        item.isElem('g') &&
+        item.hasAttr('transform') &&
+        !item.isEmpty() &&
+        !item.someAttr(function(attr) {
+            return ~referencesProps.indexOf(attr.name) && ~attr.value.indexOf('url(');
+        }) &&
+        item.content.every(function(inner) {
+            return inner.isElem(pathElems) && !inner.hasAttr('id');
+        })
+    ) {
+        item.content.forEach(function(inner) {
+            var attr = item.attr('transform');
+            if (inner.hasAttr('transform')) {
+                inner.attr('transform').value = attr.value + ' ' + inner.attr('transform').value;
+            } else {
+                inner.addAttr({
+                    'name': attr.name,
+                    'local': attr.local,
+                    'prefix': attr.prefix,
+                    'value': attr.value
+                });
+            }
+        });
+
+        item.removeAttr('transform');
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/prefixIds.js b/vendor/svgclean/src/svgclean/plugins/prefixIds.js
new file mode 100755
index 000000000..9aff8bf90
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/prefixIds.js
@@ -0,0 +1,274 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.params = {
+    delim: '__',
+    prefixIds: true,
+    prefixClassNames: true,
+};
+
+exports.description = 'prefix IDs';
+
+
+var path = require('path'),
+    csstree = require('css-tree'),
+    unquote = require('unquote'),
+    collections = require('./_collections.js'),
+    referencesProps = collections.referencesProps,
+    rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name
+    addPrefix = null;
+
+
+// Escapes a string for being used as ID
+var escapeIdentifierName = function(str) {
+    return str.replace(/[\. ]/g, '_');
+};
+
+// Matches an #ID value, captures the ID name
+var matchId = function(urlVal) {
+    var idUrlMatches = urlVal.match(rxId);
+    if (idUrlMatches === null) {
+        return false;
+    }
+    return idUrlMatches[1];
+};
+
+// Matches an url(...) value, captures the URL
+var matchUrl = function(val) {
+    var urlMatches = /url\((.*?)\)/gi.exec(val);
+    if (urlMatches === null) {
+        return false;
+    }
+    return urlMatches[1];
+};
+
+// Checks if attribute is empty
+var attrNotEmpty = function(attr) {
+    return (attr && attr.value && attr.value.length > 0);
+};
+
+// prefixes an #ID
+var prefixId = function(val) {
+    var idName = matchId(val);
+    if (!idName) {
+        return false;
+    }
+    return '#' + addPrefix(idName);
+};
+
+
+// attr.value helper methods
+
+// prefixes a class attribute value
+var addPrefixToClassAttr = function(attr) {
+    if (!attrNotEmpty(attr)) {
+        return;
+    }
+
+    attr.value = attr.value.split(/\s+/).map(addPrefix).join(' ');
+};
+
+// prefixes an ID attribute value
+var addPrefixToIdAttr = function(attr) {
+    if (!attrNotEmpty(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;
+    }
+    attr.value = idPrefixed;
+};
+
+// prefixes an URL attribute value
+var addPrefixToUrlAttr = function(attr) {
+    if (!attrNotEmpty(attr)) {
+        return;
+    }
+
+    // url(...) in value
+    var urlVal = matchUrl(attr.value);
+    if (!urlVal) {
+        return;
+    }
+
+    var idPrefixed = prefixId(urlVal);
+    if (!idPrefixed) {
+        return;
+    }
+
+    attr.value = 'url(' + idPrefixed + ')';
+};
+
+// prefixes begin/end attribute value
+var addPrefixToBeginEndAttr = function(attr) {
+    if (!attrNotEmpty(attr)) {
+        return;
+    }
+
+    var parts = attr.value.split('; ').map(function(val) {
+        val = val.trim();
+
+        if (val.endsWith('.end') || val.endsWith('.start')) {
+            var idPostfix = val.split('.'),
+                id = idPostfix[0],
+                postfix = idPostfix[1];
+
+            var idPrefixed = prefixId(`#${id}`);
+
+            if (!idPrefixed) {
+                return val;
+            }
+
+            idPrefixed = idPrefixed.slice(1);
+            return `${idPrefixed}.${postfix}`;
+        } else {
+            return val;
+        }
+    });
+
+    attr.value = parts.join('; ');
+};
+
+/**
+ * Prefixes identifiers
+ *
+ * @param {Object} node node
+ * @param {Object} opts plugin params
+ * @param {Object} extra plugin extra information
+ *
+ * @author strarsis <strarsis@gmail.com>
+ */
+exports.fn = function(node, opts, extra) {
+
+    // skip subsequent passes when multipass is used
+    if(extra.multipassCount && extra.multipassCount > 0) {
+        return node;
+    }
+
+    // prefix, from file name or option
+    var prefix = 'prefix';
+    if (opts.prefix) {
+        if (typeof opts.prefix === 'function') {
+            prefix = opts.prefix(node, 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;
+    }
+
+
+    // prefixes a normal value
+    addPrefix = function(name) {
+        if(prefix === false){
+            return escapeIdentifierName(name);
+        }
+        return escapeIdentifierName(prefix + opts.delim + name);
+    };
+
+
+    // <style/> property values
+
+    if (node.elem === 'style') {
+        if (node.isEmpty()) {
+            // skip empty <style/>s
+            return node;
+        }
+
+        var cssStr = node.content[0].text || node.content[0].cdata || [];
+
+        var cssAst = {};
+        try {
+            cssAst = csstree.parse(cssStr, {
+                parseValue: true,
+                parseCustomProperty: false
+            });
+        } catch (parseError) {
+            console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
+            return node;
+        }
+
+        var idPrefixed = '';
+        csstree.walk(cssAst, function(node) {
+
+            // #ID, .class
+            if (((opts.prefixIds        && node.type === 'IdSelector') ||
+                 (opts.prefixClassNames && node.type === 'ClassSelector')) &&
+                 node.name) {
+                node.name = addPrefix(node.name);
+                return;
+            }
+
+            // url(...) in value
+            if (node.type === 'Url' &&
+                node.value.value && node.value.value.length > 0) {
+                idPrefixed = prefixId(unquote(node.value.value));
+                if (!idPrefixed) {
+                    return;
+                }
+                node.value.value = idPrefixed;
+            }
+
+        });
+
+        // update <style>s
+        node.content[0].text = csstree.generate(cssAst);
+        return node;
+    }
+
+
+    // element attributes
+
+    if (!node.attrs) {
+        return node;
+    }
+
+
+    // Nodes
+
+    if(opts.prefixIds) {
+        // ID
+        addPrefixToIdAttr(node.attrs.id);
+    }
+
+    if(opts.prefixClassNames) {
+        // Class
+        addPrefixToClassAttr(node.attrs.class);
+    }
+
+
+    // References
+
+    // href
+    addPrefixToHrefAttr(node.attrs.href);
+
+    // (xlink:)href (deprecated, must be still supported)
+    addPrefixToHrefAttr(node.attrs['xlink:href']);
+
+    // (referenceable) properties
+    for (var referencesProp of referencesProps) {
+        addPrefixToUrlAttr(node.attrs[referencesProp]);
+    }
+
+    addPrefixToBeginEndAttr(node.attrs.begin);
+    addPrefixToBeginEndAttr(node.attrs.end);
+
+    return node;
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeAttributesBySelector.js b/vendor/svgclean/src/svgclean/plugins/removeAttributesBySelector.js
new file mode 100644
index 000000000..3ae7463da
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeAttributesBySelector.js
@@ -0,0 +1,70 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes attributes of elements that match a css selector';
+
+
+/**
+ * Removes attributes of elements that match a css selector.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @example
+ * <caption>A selector removing a single attribute</caption>
+ * plugins:
+ *   - removeAttributesBySelector:
+ *       selector: "[fill='#00ff00']"
+ *       attributes: "fill"
+ *
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
+ *   ↓
+ * <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>     
+ *
+ * <caption>A selector removing multiple attributes</caption>
+ * plugins:
+ *   - removeAttributesBySelector:
+ *       selector: "[fill='#00ff00']"
+ *       attributes:
+ *         - fill
+ *         - stroke
+ *
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
+ *   ↓
+ * <rect x="0" y="0" width="100" height="100"/>     
+ *
+ * <caption>Multiple selectors removing attributes</caption>
+ * plugins:
+ *   - removeAttributesBySelector:
+ *       selectors:
+ *         - selector: "[fill='#00ff00']"
+ *           attributes: "fill"
+ *
+ *         - selector: "#remove"
+ *           attributes:
+ *             - stroke
+ *             - id
+ *
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
+ *   ↓
+ * <rect x="0" y="0" width="100" height="100"/>
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors}
+ *
+ * @author Bradley Mease
+ */
+exports.fn = function(item, params) {
+
+    var selectors = Array.isArray(params.selectors) ? params.selectors : [params];
+
+    selectors.map(function(i) {
+        if (item.matches(i.selector)) {
+            item.removeAttr(i.attributes);
+        }
+    });
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeAttrs.js b/vendor/svgclean/src/svgclean/plugins/removeAttrs.js
new file mode 100644
index 000000000..491e0dc42
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeAttrs.js
@@ -0,0 +1,150 @@
+'use strict';
+
+var DEFAULT_SEPARATOR = ':';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes specified attributes';
+
+exports.params = {
+    elemSeparator: DEFAULT_SEPARATOR,
+    preserveCurrentColor: false,
+    attrs: []
+};
+
+/**
+ * Remove attributes
+ *
+ * @param elemSeparator
+ *   format: string
+ *
+ * @param preserveCurrentColor
+ *   format: boolean
+ *
+ * @param attrs:
+ *
+ *   format: [ element* : attribute* : value* ]
+ *
+ *   element   : regexp (wrapped into ^...$), single * or omitted > all elements (must be present when value is used)
+ *   attribute : regexp (wrapped into ^...$)
+ *   value     : regexp (wrapped into ^...$), single * or omitted > all values
+ *
+ *   examples:
+ *
+ *     > basic: remove fill attribute
+ *     ---
+ *     removeAttrs:
+ *       attrs: 'fill'
+ *
+ *     > remove fill attribute on path element
+ *     ---
+ *       attrs: 'path:fill'
+ *
+ *     > remove fill attribute on path element where value is none
+ *     ---
+ *       attrs: 'path:fill:none'
+ *
+ *
+ *     > remove all fill and stroke attribute
+ *     ---
+ *       attrs:
+ *         - 'fill'
+ *         - 'stroke'
+ *
+ *     [is same as]
+ *
+ *       attrs: '(fill|stroke)'
+ *
+ *     [is same as]
+ *
+ *       attrs: '*:(fill|stroke)'
+ *
+ *     [is same as]
+ *
+ *       attrs: '.*:(fill|stroke)'
+ *
+ *     [is same as]
+ *
+ *       attrs: '.*:(fill|stroke):.*'
+ *
+ *
+ *     > remove all stroke related attributes
+ *     ----
+ *     attrs: 'stroke.*'
+ *
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Benny Schudel
+ */
+exports.fn = function(item, params) {
+        // wrap into an array if params is not
+    if (!Array.isArray(params.attrs)) {
+        params.attrs = [params.attrs];
+    }
+
+    if (item.isElem()) {
+        var elemSeparator = typeof params.elemSeparator == 'string' ? params.elemSeparator : DEFAULT_SEPARATOR;
+        var preserveCurrentColor = typeof params.preserveCurrentColor == 'boolean' ? params.preserveCurrentColor : false;
+
+            // prepare patterns
+        var patterns = params.attrs.map(function(pattern) {
+
+                // if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
+            if (pattern.indexOf(elemSeparator) === -1) {
+                pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join('');
+
+                // if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
+            } else if (pattern.split(elemSeparator).length < 3) {
+                pattern = [pattern, elemSeparator, '.*'].join('');
+            }
+
+                // create regexps for element, attribute name, and attribute value
+            return pattern.split(elemSeparator)
+                .map(function(value) {
+
+                        // adjust single * to match anything
+                    if (value === '*') { value = '.*'; }
+
+                    return new RegExp(['^', value, '$'].join(''), 'i');
+                });
+
+        });
+
+            // loop patterns
+        patterns.forEach(function(pattern) {
+
+                // matches element
+            if (pattern[0].test(item.elem)) {
+
+                    // loop attributes
+                item.eachAttr(function(attr) {
+                    var name = attr.name;
+                    var value = attr.value;
+                    var isFillCurrentColor = preserveCurrentColor && name == 'fill' && value == 'currentColor';
+                    var isStrokeCurrentColor = preserveCurrentColor && name == 'stroke' && value == 'currentColor';
+
+                    if (!(isFillCurrentColor || isStrokeCurrentColor)) {
+                        // matches attribute name
+                        if (pattern[1].test(name)) {
+
+                            // matches attribute value
+                            if (pattern[2].test(attr.value)) {
+                                item.removeAttr(name);
+                            }
+                        }
+                    }
+
+                });
+
+            }
+
+        });
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeComments.js b/vendor/svgclean/src/svgclean/plugins/removeComments.js
new file mode 100644
index 000000000..8bfd21b09
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeComments.js
@@ -0,0 +1,27 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes comments';
+
+/**
+ * Remove comments.
+ *
+ * @example
+ * <!-- Generator: Adobe Illustrator 15.0.0, SVG Export
+ * Plug-In . SVG Version: 6.00 Build 0)  -->
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.comment && item.comment.charAt(0) !== '!') {
+        return false;
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeDesc.js b/vendor/svgclean/src/svgclean/plugins/removeDesc.js
new file mode 100644
index 000000000..76e695933
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeDesc.js
@@ -0,0 +1,32 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.params = {
+    removeAny: true
+};
+
+exports.description = 'removes <desc>';
+
+var standardDescs = /^(Created with|Created using)/;
+
+/**
+ * Removes <desc>.
+ * Removes only standard editors content or empty elements 'cause it can be used for accessibility.
+ * Enable parameter 'removeAny' to remove any description.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Daniel Wabyick
+ */
+exports.fn = function(item, params) {
+
+    return !item.isElem('desc') || !(params.removeAny || item.isEmpty() ||
+            standardDescs.test(item.content[0].text));
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeDimensions.js b/vendor/svgclean/src/svgclean/plugins/removeDimensions.js
new file mode 100644
index 000000000..7cbd65c72
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeDimensions.js
@@ -0,0 +1,49 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)';
+
+/**
+ * Remove width/height attributes and add the viewBox attribute if it's missing
+ *
+ * @example
+ * <svg width="100" height="50" />
+ *   ↓
+ * <svg viewBox="0 0 100 50" />
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if true, with and height will be filtered out
+ *
+ * @author Benny Schudel
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('svg')) {
+        if (item.hasAttr('viewBox')) {
+            item.removeAttr('width');
+            item.removeAttr('height');
+        } else if (
+            item.hasAttr('width') &&
+            item.hasAttr('height') &&
+            !isNaN(Number(item.attr('width').value)) &&
+            !isNaN(Number(item.attr('height').value))
+        ) {
+            item.addAttr({
+                name: 'viewBox',
+                value:
+                    '0 0 ' +
+                    Number(item.attr('width').value) +
+                    ' ' +
+                    Number(item.attr('height').value),
+                prefix: '',
+                local: 'viewBox'
+            });
+            item.removeAttr('width');
+            item.removeAttr('height');
+        }
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeDoctype.js b/vendor/svgclean/src/svgclean/plugins/removeDoctype.js
new file mode 100644
index 000000000..32ee4f461
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeDoctype.js
@@ -0,0 +1,40 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes doctype declaration';
+
+/**
+ * Remove DOCTYPE declaration.
+ *
+ * "Unfortunately the SVG DTDs are a source of so many
+ * issues that the SVG WG has decided not to write one
+ * for the upcoming SVG 1.2 standard. In fact SVG WG
+ * members are even telling people not to use a DOCTYPE
+ * declaration in SVG 1.0 and 1.1 documents"
+ * https://jwatt.org/svg/authoring/#doctype-declaration
+ *
+ * @example
+ * <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ * q"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+ *
+ * @example
+ * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [
+ *     <!-- an internal subset can be embedded here -->
+ * ]>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.doctype) {
+        return false;
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeEditorsNSData.js b/vendor/svgclean/src/svgclean/plugins/removeEditorsNSData.js
new file mode 100644
index 000000000..9e5c8cd8f
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeEditorsNSData.js
@@ -0,0 +1,65 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes editors namespaces, elements and attributes';
+
+var editorNamespaces = require('./_collections').editorNamespaces,
+    prefixes = [];
+
+exports.params = {
+    additionalNamespaces: []
+};
+
+/**
+ * Remove editors namespaces, elements and attributes.
+ *
+ * @example
+ * <svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd">
+ * <sodipodi:namedview/>
+ * <path sodipodi:nodetypes="cccc"/>
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    if (Array.isArray(params.additionalNamespaces)) {
+        editorNamespaces = editorNamespaces.concat(params.additionalNamespaces);
+    }
+
+    if (item.elem) {
+
+        if (item.isElem('svg')) {
+
+            item.eachAttr(function(attr) {
+                if (attr.prefix === 'xmlns' && editorNamespaces.indexOf(attr.value) > -1) {
+                    prefixes.push(attr.local);
+
+                    // <svg xmlns:sodipodi="">
+                    item.removeAttr(attr.name);
+                }
+            });
+
+        }
+
+        // <* sodipodi:*="">
+        item.eachAttr(function(attr) {
+            if (prefixes.indexOf(attr.prefix) > -1) {
+                item.removeAttr(attr.name);
+            }
+        });
+
+        // <sodipodi:*>
+        if (prefixes.indexOf(item.prefix) > -1) {
+            return false;
+        }
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeElementsByAttr.js b/vendor/svgclean/src/svgclean/plugins/removeElementsByAttr.js
new file mode 100644
index 000000000..c726f7084
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeElementsByAttr.js
@@ -0,0 +1,80 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes arbitrary elements by ID or className (disabled by default)';
+
+exports.params = {
+    id: [],
+    class: []
+};
+
+/**
+ * Remove arbitrary SVG elements by ID or className.
+ *
+ * @param id
+ *   examples:
+ *
+ *     > single: remove element with ID of `elementID`
+ *     ---
+ *     removeElementsByAttr:
+ *       id: 'elementID'
+ *
+ *     > list: remove multiple elements by ID
+ *     ---
+ *     removeElementsByAttr:
+ *       id:
+ *         - 'elementID'
+ *         - 'anotherID'
+ *
+ * @param class
+ *   examples:
+ *
+ *     > single: remove all elements with class of `elementClass`
+ *     ---
+ *     removeElementsByAttr:
+ *       class: 'elementClass'
+ *
+ *     > list: remove all elements with class of `elementClass` or `anotherClass`
+ *     ---
+ *     removeElementsByAttr:
+ *       class:
+ *         - 'elementClass'
+ *         - 'anotherClass'
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Eli Dupuis (@elidupuis)
+ */
+exports.fn = function(item, params) {
+    var elemId, elemClass;
+
+    // wrap params in an array if not already
+    ['id', 'class'].forEach(function(key) {
+        if (!Array.isArray(params[key])) {
+            params[key] = [ params[key] ];
+        }
+    });
+
+    // abort if current item is no an element
+    if (!item.isElem()) {
+        return;
+    }
+
+    // remove element if it's `id` matches configured `id` params
+    elemId = item.attr('id');
+    if (elemId) {
+        return params.id.indexOf(elemId.value) === -1;
+    }
+
+    // remove element if it's `class` contains any of the configured `class` params
+    elemClass = item.attr('class');
+    if (elemClass) {
+        var hasClassRegex = new RegExp(params.class.join('|'));
+        return !hasClassRegex.test(elemClass.value);
+    }
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeEmptyAttrs.js b/vendor/svgclean/src/svgclean/plugins/removeEmptyAttrs.js
new file mode 100644
index 000000000..ae73e6c96
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeEmptyAttrs.js
@@ -0,0 +1,29 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes empty attributes';
+
+/**
+ * Remove attributes with empty values.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.elem) {
+
+        item.eachAttr(function(attr) {
+            if (attr.value === '') {
+                item.removeAttr(attr.name);
+            }
+        });
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeEmptyContainers.js b/vendor/svgclean/src/svgclean/plugins/removeEmptyContainers.js
new file mode 100644
index 000000000..356a5835b
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeEmptyContainers.js
@@ -0,0 +1,32 @@
+'use strict';
+
+exports.type = 'perItemReverse';
+
+exports.active = true;
+
+exports.description = 'removes empty container elements';
+
+var container = require('./_collections').elemsGroups.container;
+
+/**
+ * Remove empty containers.
+ *
+ * @see http://www.w3.org/TR/SVG/intro.html#TermContainerElement
+ *
+ * @example
+ * <defs/>
+ *
+ * @example
+ * <g><marker><a/></marker></g>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    return !(item.isElem(container) && !item.isElem('svg') && item.isEmpty() &&
+        (!item.isElem('pattern') || !item.hasAttrLocal('href')));
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeEmptyText.js b/vendor/svgclean/src/svgclean/plugins/removeEmptyText.js
new file mode 100644
index 000000000..049b18196
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeEmptyText.js
@@ -0,0 +1,59 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes empty <text> elements';
+
+exports.params = {
+    text: true,
+    tspan: true,
+    tref: true
+};
+
+/**
+ * Remove empty Text elements.
+ *
+ * @see http://www.w3.org/TR/SVG/text.html
+ *
+ * @example
+ * Remove empty text element:
+ * <text/>
+ *
+ * Remove empty tspan element:
+ * <tspan/>
+ *
+ * Remove tref with empty xlink:href attribute:
+ * <tref xlink:href=""/>
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    // Remove empty text element
+    if (
+        params.text &&
+        item.isElem('text') &&
+        item.isEmpty()
+    ) return false;
+
+    // Remove empty tspan element
+    if (
+        params.tspan &&
+        item.isElem('tspan') &&
+        item.isEmpty()
+    ) return false;
+
+    // Remove tref with empty xlink:href attribute
+    if (
+        params.tref &&
+        item.isElem('tref') &&
+        !item.hasAttrLocal('href')
+    ) return false;
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeHiddenElems.js b/vendor/svgclean/src/svgclean/plugins/removeHiddenElems.js
new file mode 100644
index 000000000..7379125e7
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeHiddenElems.js
@@ -0,0 +1,225 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes hidden elements (zero sized, with absent attributes)';
+
+exports.params = {
+    isHidden: true,
+    displayNone: true,
+    opacity0: true,
+    circleR0: true,
+    ellipseRX0: true,
+    ellipseRY0: true,
+    rectWidth0: true,
+    rectHeight0: true,
+    patternWidth0: true,
+    patternHeight0: true,
+    imageWidth0: true,
+    imageHeight0: true,
+    pathEmptyD: true,
+    polylineEmptyPoints: true,
+    polygonEmptyPoints: true
+};
+
+var regValidPath = /M\s*(?:[-+]?(?:\d*\.\d+|\d+(?:\.|(?!\.)))([eE][-+]?\d+)?(?!\d)\s*,?\s*){2}\D*\d/i;
+
+/**
+ * Remove hidden elements with disabled rendering:
+ * - display="none"
+ * - opacity="0"
+ * - circle with zero radius
+ * - ellipse with zero x-axis or y-axis radius
+ * - rectangle with zero width or height
+ * - pattern with zero width or height
+ * - image with zero width or height
+ * - path with empty data
+ * - polyline with empty points
+ * - polygon with empty points
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+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"
+        //
+        // http://www.w3.org/TR/SVG/painting.html#DisplayProperty
+        // "A value of display: none indicates that the given element
+        // and its children shall not be rendered directly"
+        if (
+            params.displayNone &&
+            item.hasAttr('display', 'none')
+        ) return false;
+
+        // opacity="0"
+        //
+        // http://www.w3.org/TR/SVG/masking.html#ObjectAndGroupOpacityProperties
+        if (
+            params.opacity0 &&
+            item.hasAttr('opacity', '0')
+        ) return false;
+
+        // Circles with zero radius
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#CircleElementRAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <circle r="0">
+        if (
+            params.circleR0 &&
+            item.isElem('circle') &&
+            item.isEmpty() &&
+            item.hasAttr('r', '0')
+        ) return false;
+
+        // Ellipse with zero x-axis radius
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#EllipseElementRXAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <ellipse rx="0">
+        if (
+            params.ellipseRX0 &&
+            item.isElem('ellipse') &&
+            item.isEmpty() &&
+            item.hasAttr('rx', '0')
+        ) return false;
+
+        // Ellipse with zero y-axis radius
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#EllipseElementRYAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <ellipse ry="0">
+        if (
+            params.ellipseRY0 &&
+            item.isElem('ellipse') &&
+            item.isEmpty() &&
+            item.hasAttr('ry', '0')
+        ) return false;
+
+        // Rectangle with zero width
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#RectElementWidthAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <rect width="0">
+        if (
+            params.rectWidth0 &&
+            item.isElem('rect') &&
+            item.isEmpty() &&
+            item.hasAttr('width', '0')
+        ) return false;
+
+        // Rectangle with zero height
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#RectElementHeightAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <rect height="0">
+        if (
+            params.rectHeight0 &&
+            params.rectWidth0 &&
+            item.isElem('rect') &&
+            item.isEmpty() &&
+            item.hasAttr('height', '0')
+        ) return false;
+
+        // Pattern with zero width
+        //
+        // http://www.w3.org/TR/SVG/pservers.html#PatternElementWidthAttribute
+        // "A value of zero disables rendering of the element (i.e., no paint is applied)"
+        //
+        // <pattern width="0">
+        if (
+            params.patternWidth0 &&
+            item.isElem('pattern') &&
+            item.hasAttr('width', '0')
+        ) return false;
+
+        // Pattern with zero height
+        //
+        // http://www.w3.org/TR/SVG/pservers.html#PatternElementHeightAttribute
+        // "A value of zero disables rendering of the element (i.e., no paint is applied)"
+        //
+        // <pattern height="0">
+        if (
+            params.patternHeight0 &&
+            item.isElem('pattern') &&
+            item.hasAttr('height', '0')
+        ) return false;
+
+        // Image with zero width
+        //
+        // http://www.w3.org/TR/SVG/struct.html#ImageElementWidthAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <image width="0">
+        if (
+            params.imageWidth0 &&
+            item.isElem('image') &&
+            item.hasAttr('width', '0')
+        ) return false;
+
+        // Image with zero height
+        //
+        // http://www.w3.org/TR/SVG/struct.html#ImageElementHeightAttribute
+        // "A value of zero disables rendering of the element"
+        //
+        // <image height="0">
+        if (
+            params.imageHeight0 &&
+            item.isElem('image') &&
+            item.hasAttr('height', '0')
+        ) return false;
+
+        // Path with empty data
+        //
+        // http://www.w3.org/TR/SVG/paths.html#DAttribute
+        //
+        // <path d=""/>
+        if (
+            params.pathEmptyD &&
+            item.isElem('path') &&
+            (!item.hasAttr('d') || !regValidPath.test(item.attr('d').value))
+        ) return false;
+
+        // Polyline with empty points
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#PolylineElementPointsAttribute
+        //
+        // <polyline points="">
+        if (
+            params.polylineEmptyPoints &&
+            item.isElem('polyline') &&
+            !item.hasAttr('points')
+        ) return false;
+
+        // Polygon with empty points
+        //
+        // http://www.w3.org/TR/SVG/shapes.html#PolygonElementPointsAttribute
+        //
+        // <polygon points="">
+        if (
+            params.polygonEmptyPoints &&
+            item.isElem('polygon') &&
+            !item.hasAttr('points')
+        ) return false;
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeMetadata.js b/vendor/svgclean/src/svgclean/plugins/removeMetadata.js
new file mode 100644
index 000000000..fa7a57951
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeMetadata.js
@@ -0,0 +1,23 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes <metadata>';
+
+/**
+ * Remove <metadata>.
+ *
+ * http://www.w3.org/TR/SVG/metadata.html
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    return !item.isElem('metadata');
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeNonInheritableGroupAttrs.js b/vendor/svgclean/src/svgclean/plugins/removeNonInheritableGroupAttrs.js
new file mode 100644
index 000000000..85e3b9796
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeNonInheritableGroupAttrs.js
@@ -0,0 +1,37 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes non-inheritable group’s presentational attributes';
+
+var inheritableAttrs = require('./_collections').inheritableAttrs,
+    attrsGroups = require('./_collections').attrsGroups,
+    applyGroups = require('./_collections').presentationNonInheritableGroupAttrs;
+
+/**
+ * Remove non-inheritable group's "presentation" attributes.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('g')) {
+
+        item.eachAttr(function(attr) {
+            if (
+                ~attrsGroups.presentation.indexOf(attr.name) &&
+                !~inheritableAttrs.indexOf(attr.name) &&
+                !~applyGroups.indexOf(attr.name)
+            ) {
+                item.removeAttr(attr.name);
+            }
+        });
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeOffCanvasPaths.js b/vendor/svgclean/src/svgclean/plugins/removeOffCanvasPaths.js
new file mode 100644
index 000000000..717bb0e75
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeOffCanvasPaths.js
@@ -0,0 +1,134 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes elements that are drawn outside of the viewbox (disabled by default)';
+
+var tools      = require("../tools"),
+	_path      = require('./_path.js'),
+	intersects = _path.intersects,
+	path2js    = _path.path2js,
+	viewBox,
+	viewBoxJS;
+
+/**
+ * Remove elements that are drawn outside of the viewbox.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author JoshyPHP
+ */
+exports.fn = function(item) {
+
+	if (item.isElem('path') && item.hasAttr('d') && typeof viewBox !== 'undefined')
+	{
+		// Consider that any item with a transform attribute or a M instruction
+		// within the viewBox is visible
+		if (hasTransform(item) || pathMovesWithinViewBox(item.attr('d').value))
+		{
+			return true;
+		}
+
+		var pathJS = path2js(item);
+		if (pathJS.length === 2)
+		{
+			// Use a closed clone of the path if it's too short for intersects()
+			pathJS = JSON.parse(JSON.stringify(pathJS));
+			pathJS.push({ instruction: 'z' });
+		}
+
+		return intersects(viewBoxJS, pathJS);
+	}
+	if (item.isElem('svg'))
+	{
+		parseViewBox(item);
+	}
+
+	return true;
+};
+
+/**
+ * Test whether given item or any of its ancestors has a transform attribute.
+ *
+ * @param {String} path
+ * @return {Boolean}
+ */
+function hasTransform(item)
+{
+	return item.hasAttr('transform') || (item.parentNode && hasTransform(item.parentNode));
+}
+
+/**
+ * Parse the viewBox coordinates and compute the JS representation of its path.
+ *
+ * @param {Object} svg svg element item
+ */
+function parseViewBox(svg)
+{
+	var viewBoxData = '';
+	if (svg.hasAttr('viewBox'))
+	{
+		// Remove commas and plus signs, normalize and trim whitespace
+		viewBoxData = svg.attr('viewBox').value;
+	}
+	else if (svg.hasAttr('height') && svg.hasAttr('width'))
+	{
+		viewBoxData = '0 0 ' + svg.attr('width').value + ' ' + svg.attr('height').value;
+	}
+
+	// Remove commas and plus signs, normalize and trim whitespace
+	viewBoxData = viewBoxData.replace(/[,+]|px/g, ' ').replace(/\s+/g, ' ').replace(/^\s*|\s*$/g, '');
+
+	// Ensure that the dimensions are 4 values separated by space
+	var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(viewBoxData);
+	if (!m)
+	{
+		return;
+	}
+
+	// Store the viewBox boundaries
+	viewBox = {
+		left:   parseFloat(m[1]),
+		top:    parseFloat(m[2]),
+		right:  parseFloat(m[1]) + parseFloat(m[3]),
+		bottom: parseFloat(m[2]) + parseFloat(m[4])
+	};
+
+	var path = tools.createContentItem({
+		elem:   'path',
+		prefix: '',
+		local:  'path'
+	});
+
+	path.addAttr({
+		name:   'd',
+		prefix: '',
+		local:  'd',
+		value:  'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z'
+	});
+
+	viewBoxJS = path2js(path);
+}
+
+/**
+ * Test whether given path has a M instruction with coordinates within the viewBox.
+ *
+ * @param {String} path
+ * @return {Boolean}
+ */
+function pathMovesWithinViewBox(path)
+{
+	var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g, m;
+	while (null !== (m = regexp.exec(path)))
+	{
+		if (m[1] >= viewBox.left && m[1] <= viewBox.right && m[2] >= viewBox.top && m[2] <= viewBox.bottom)
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/removeRasterImages.js b/vendor/svgclean/src/svgclean/plugins/removeRasterImages.js
new file mode 100644
index 000000000..cbddebe3d
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeRasterImages.js
@@ -0,0 +1,28 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes raster images (disabled by default)';
+
+/**
+ * Remove raster images references in <image>.
+ *
+ * @see https://bugs.webkit.org/show_bug.cgi?id=63548
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (
+        item.isElem('image') &&
+        item.hasAttrLocal('href', /(\.|image\/)(jpg|png|gif)/)
+    ) {
+        return false;
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeScriptElement.js b/vendor/svgclean/src/svgclean/plugins/removeScriptElement.js
new file mode 100644
index 000000000..f80410bd4
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeScriptElement.js
@@ -0,0 +1,23 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes <script> elements (disabled by default)';
+
+/**
+ * Remove <script>.
+ *
+ * https://www.w3.org/TR/SVG/script.html
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Patrick Klingemann
+ */
+exports.fn = function(item) {
+
+    return !item.isElem('script');
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeStyleElement.js b/vendor/svgclean/src/svgclean/plugins/removeStyleElement.js
new file mode 100644
index 000000000..964ce45c8
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeStyleElement.js
@@ -0,0 +1,23 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes <style> element (disabled by default)';
+
+/**
+ * Remove <style>.
+ *
+ * http://www.w3.org/TR/SVG/styling.html#StyleElement
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Betsy Dupuis
+ */
+exports.fn = function(item) {
+
+    return !item.isElem('style');
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeTitle.js b/vendor/svgclean/src/svgclean/plugins/removeTitle.js
new file mode 100644
index 000000000..841d178b0
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeTitle.js
@@ -0,0 +1,23 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes <title>';
+
+/**
+ * Remove <title>.
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Igor Kalashnikov
+ */
+exports.fn = function(item) {
+
+    return !item.isElem('title');
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeUnknownsAndDefaults.js b/vendor/svgclean/src/svgclean/plugins/removeUnknownsAndDefaults.js
new file mode 100644
index 000000000..31970a4f0
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeUnknownsAndDefaults.js
@@ -0,0 +1,150 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes unknown elements content and attributes, removes attrs with default values';
+
+exports.params = {
+    unknownContent: true,
+    unknownAttrs: true,
+    defaultAttrs: true,
+    uselessOverrides: true,
+    keepDataAttrs: true,
+    keepAriaAttrs: true,
+    keepRoleAttr: false
+};
+
+var collections = require('./_collections'),
+    elems = collections.elems,
+    attrsGroups = collections.attrsGroups,
+    elemsGroups = collections.elemsGroups,
+    attrsGroupsDefaults = collections.attrsGroupsDefaults,
+    attrsInheritable = collections.inheritableAttrs,
+    applyGroups = collections.presentationNonInheritableGroupAttrs;
+
+// collect and extend all references
+for (var elem in elems) {
+    elem = elems[elem];
+
+    if (elem.attrsGroups) {
+        elem.attrs = elem.attrs || [];
+
+        elem.attrsGroups.forEach(function(attrsGroupName) {
+            elem.attrs = elem.attrs.concat(attrsGroups[attrsGroupName]);
+
+            var groupDefaults = attrsGroupsDefaults[attrsGroupName];
+
+            if (groupDefaults) {
+                elem.defaults = elem.defaults || {};
+
+                for (var attrName in groupDefaults) {
+                    elem.defaults[attrName] = groupDefaults[attrName];
+                }
+            }
+        });
+
+    }
+
+    if (elem.contentGroups) {
+        elem.content = elem.content || [];
+
+        elem.contentGroups.forEach(function(contentGroupName) {
+            elem.content = elem.content.concat(elemsGroups[contentGroupName]);
+        });
+    }
+}
+
+/**
+ * Remove unknown elements content and attributes,
+ * remove attributes with default values.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+
+    // elems w/o namespace prefix
+    if (item.isElem() && !item.prefix) {
+
+        var elem = item.elem;
+
+        // remove unknown element's content
+        if (
+            params.unknownContent &&
+            !item.isEmpty() &&
+            elems[elem] && // make sure we know of this element before checking its children
+            elem !== 'foreignObject' // Don't check foreignObject
+        ) {
+            item.content.forEach(function(content, i) {
+                if (
+                    content.isElem() &&
+                    !content.prefix &&
+                    (
+                        (
+                            elems[elem].content && // Do we have a record of its permitted content?
+                            elems[elem].content.indexOf(content.elem) === -1
+                        ) ||
+                        (
+                            !elems[elem].content && // we dont know about its permitted content
+                            !elems[content.elem] // check that we know about the element at all
+                        )
+                    )
+                ) {
+                    item.content.splice(i, 1);
+                }
+            });
+        }
+
+        // remove element's unknown attrs and attrs with default values
+        if (elems[elem] && elems[elem].attrs) {
+
+            item.eachAttr(function(attr) {
+
+                if (
+                    attr.name !== 'xmlns' &&
+                    (attr.prefix === 'xml' || !attr.prefix) &&
+                    (!params.keepDataAttrs || attr.name.indexOf('data-') != 0) &&
+                    (!params.keepAriaAttrs || attr.name.indexOf('aria-') != 0) &&
+                    (!params.keepRoleAttr || attr.name != 'role')
+                ) {
+                    if (
+                        // unknown attrs
+                        (
+                            params.unknownAttrs &&
+                            elems[elem].attrs.indexOf(attr.name) === -1
+                        ) ||
+                        // attrs with default values
+                        (
+                            params.defaultAttrs &&
+                            !item.hasAttr('id') &&
+                            elems[elem].defaults &&
+                            elems[elem].defaults[attr.name] === attr.value && (
+                                attrsInheritable.indexOf(attr.name) < 0 ||
+                                !item.parentNode.computedAttr(attr.name)
+                            )
+                        ) ||
+                        // useless overrides
+                        (
+                            params.uselessOverrides &&
+                            !item.hasAttr('id') &&
+                            applyGroups.indexOf(attr.name) < 0 &&
+                            attrsInheritable.indexOf(attr.name) > -1 &&
+                            item.parentNode.computedAttr(attr.name, attr.value)
+                        )
+                    ) {
+                        item.removeAttr(attr.name);
+                    }
+                }
+
+            });
+
+        }
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeUnusedNS.js b/vendor/svgclean/src/svgclean/plugins/removeUnusedNS.js
new file mode 100644
index 000000000..db0df7605
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeUnusedNS.js
@@ -0,0 +1,109 @@
+'use strict';
+
+exports.type = 'full';
+
+exports.active = true;
+
+exports.description = 'removes unused namespaces declaration';
+
+/**
+ * Remove unused namespaces declaration.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(data) {
+
+    var svgElem,
+        xmlnsCollection = [];
+
+    /**
+     * Remove namespace from collection.
+     *
+     * @param {String} ns namescape name
+     */
+    function removeNSfromCollection(ns) {
+
+        var pos = xmlnsCollection.indexOf(ns);
+
+        // if found - remove ns from the namespaces collection
+        if (pos > -1) {
+            xmlnsCollection.splice(pos, 1);
+        }
+
+    }
+
+    /**
+     * Bananas!
+     *
+     * @param {Array} items input items
+     *
+     * @return {Array} output items
+     */
+    function monkeys(items) {
+
+        var i = 0,
+            length = items.content.length;
+
+        while(i < length) {
+
+            var item = items.content[i];
+
+            if (item.isElem('svg')) {
+
+                item.eachAttr(function(attr) {
+                    // collect namespaces
+                    if (attr.prefix === 'xmlns' && attr.local) {
+                        xmlnsCollection.push(attr.local);
+                    }
+                });
+
+                // if svg element has ns-attr
+                if (xmlnsCollection.length) {
+                    // save svg element
+                    svgElem = item;
+                }
+
+            }
+
+            if (xmlnsCollection.length) {
+
+                // check item for the ns-attrs
+                if (item.prefix) {
+                    removeNSfromCollection(item.prefix);
+                }
+
+                // check each attr for the ns-attrs
+                item.eachAttr(function(attr) {
+                    removeNSfromCollection(attr.prefix);
+                });
+
+            }
+
+            // if nothing is found - go deeper
+            if (xmlnsCollection.length && item.content) {
+                monkeys(item);
+            }
+
+            i++;
+
+        }
+
+        return items;
+
+    }
+
+    data = monkeys(data);
+
+    // remove svg element ns-attributes if they are not used even once
+    if (xmlnsCollection.length) {
+        xmlnsCollection.forEach(function(name) {
+            svgElem.removeAttr('xmlns:' + name);
+        });
+    }
+
+    return data;
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeUselessDefs.js b/vendor/svgclean/src/svgclean/plugins/removeUselessDefs.js
new file mode 100644
index 000000000..87badd938
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeUselessDefs.js
@@ -0,0 +1,53 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes elements in <defs> without id';
+
+var nonRendering = require('./_collections').elemsGroups.nonRendering;
+
+/**
+ * Removes content of defs and properties that aren't rendered directly without ids.
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Lev Solntsev
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('defs')) {
+
+        if (item.content) {
+            item.content = getUsefulItems(item, []);
+        }
+        
+        if (item.isEmpty()) return false;
+
+    } else if (item.isElem(nonRendering) && !item.hasAttr('id')) {
+
+        return false;
+
+    }
+
+};
+
+function getUsefulItems(item, usefulItems) {
+
+    item.content.forEach(function(child) {
+        if (child.hasAttr('id') || child.isElem('style')) {
+
+            usefulItems.push(child);
+            child.parentNode = item;
+
+        } else if (!child.isEmpty()) {
+
+            child.content = getUsefulItems(child, usefulItems);
+
+        }
+    });
+
+    return usefulItems;
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/removeUselessStrokeAndFill.js b/vendor/svgclean/src/svgclean/plugins/removeUselessStrokeAndFill.js
new file mode 100644
index 000000000..e6b8eea71
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeUselessStrokeAndFill.js
@@ -0,0 +1,100 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes useless stroke and fill attributes';
+
+exports.params = {
+    stroke: true,
+    fill: true,
+    removeNone: false,
+    hasStyleOrScript: false
+};
+
+var shape = require('./_collections').elemsGroups.shape,
+    regStrokeProps = /^stroke/,
+    regFillProps = /^fill-/,
+    styleOrScript = ['style', 'script'];
+
+/**
+ * Remove useless stroke and fill attrs.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item, params) {
+    
+    if (item.isElem(styleOrScript)) {
+        params.hasStyleOrScript = true;
+    }
+
+    if (!params.hasStyleOrScript && item.isElem(shape) && !item.computedAttr('id')) {
+
+        var stroke = params.stroke && item.computedAttr('stroke'),
+            fill = params.fill && !item.computedAttr('fill', 'none');
+
+        // remove stroke*
+        if (
+            params.stroke &&
+            (!stroke ||
+                stroke == 'none' ||
+                item.computedAttr('stroke-opacity', '0') ||
+                item.computedAttr('stroke-width', '0')
+            )
+        ) {
+            var parentStroke = item.parentNode.computedAttr('stroke'),
+                declineStroke = parentStroke && parentStroke != 'none';
+
+            item.eachAttr(function(attr) {
+                if (regStrokeProps.test(attr.name)) {
+                    item.removeAttr(attr.name);
+                }
+            });
+
+            if (declineStroke) item.addAttr({
+                name: 'stroke',
+                value: 'none',
+                prefix: '',
+                local: 'stroke'
+            });
+        }
+
+        // remove fill*
+        if (
+            params.fill &&
+            (!fill || item.computedAttr('fill-opacity', '0'))
+        ) {
+            item.eachAttr(function(attr) {
+                if (regFillProps.test(attr.name)) {
+                    item.removeAttr(attr.name);
+                }
+            });
+
+            if (fill) {
+                if (item.hasAttr('fill'))
+                    item.attr('fill').value = 'none';
+                else
+                    item.addAttr({
+                        name: 'fill',
+                        value: 'none',
+                        prefix: '',
+                        local: 'fill'
+                    });
+            }
+        }
+
+        if (params.removeNone && 
+            (!stroke || item.hasAttr('stroke') && item.attr('stroke').value=='none') &&
+            (!fill || item.hasAttr('fill') && item.attr('fill').value=='none')) {
+
+            return false;
+        }
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeViewBox.js b/vendor/svgclean/src/svgclean/plugins/removeViewBox.js
new file mode 100644
index 000000000..2fbcba4f4
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeViewBox.js
@@ -0,0 +1,48 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes viewBox attribute when possible';
+
+var viewBoxElems = ['svg', 'pattern', 'symbol'];
+
+/**
+ * Remove viewBox attr which coincides with a width/height box.
+ *
+ * @see http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
+ *
+ * @example
+ * <svg width="100" height="50" viewBox="0 0 100 50">
+ *             ⬇
+ * <svg width="100" height="50">
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    if (
+        item.isElem(viewBoxElems) &&
+        item.hasAttr('viewBox') &&
+        item.hasAttr('width') &&
+        item.hasAttr('height')
+    ) {
+
+        var nums = item.attr('viewBox').value.split(/[ ,]+/g);
+
+        if (
+            nums[0] === '0' &&
+            nums[1] === '0' &&
+            item.attr('width').value.replace(/px$/, '') === nums[2] && // could use parseFloat too
+            item.attr('height').value.replace(/px$/, '') === nums[3]
+        ) {
+            item.removeAttr('viewBox');
+        }
+
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/removeXMLNS.js b/vendor/svgclean/src/svgclean/plugins/removeXMLNS.js
new file mode 100644
index 000000000..afda99d36
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeXMLNS.js
@@ -0,0 +1,28 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'removes xmlns attribute (for inline svg, disabled by default)';
+
+/**
+ * Remove the xmlns attribute when present.
+ *
+ * @example
+ * <svg viewBox="0 0 100 50" xmlns="http://www.w3.org/2000/svg">
+ *   ↓
+ * <svg viewBox="0 0 100 50">
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if true, xmlns will be filtered out
+ *
+ * @author Ricardo Tomasi
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('svg') && item.hasAttr('xmlns')) {
+        item.removeAttr('xmlns');
+    }
+
+};
\ No newline at end of file
diff --git a/vendor/svgclean/src/svgclean/plugins/removeXMLProcInst.js b/vendor/svgclean/src/svgclean/plugins/removeXMLProcInst.js
new file mode 100644
index 000000000..afd425d57
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/removeXMLProcInst.js
@@ -0,0 +1,24 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'removes XML processing instructions';
+
+/**
+ * Remove XML Processing Instruction.
+ *
+ * @example
+ * <?xml version="1.0" encoding="utf-8"?>
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author Kir Belevich
+ */
+exports.fn = function(item) {
+
+    return !(item.processinginstruction && item.processinginstruction.name === 'xml');
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/reusePaths.js b/vendor/svgclean/src/svgclean/plugins/reusePaths.js
new file mode 100644
index 000000000..dc1f92ed7
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/reusePaths.js
@@ -0,0 +1,169 @@
+/**
+ * @license
+ * The MIT License
+ *
+ * Copyright © 2012–2016 Kir Belevich
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ *
+ * Лицензия MIT
+ *
+ * Copyright © 2012–2016 Кир Белевич
+ *
+ * Данная лицензия разрешает лицам, получившим копию
+ * данного
+ * программного обеспечения и сопутствующей
+ * документации
+ * (в дальнейшем именуемыми «Программное Обеспечение»),
+ * безвозмездно
+ * использовать Программное Обеспечение без
+ * ограничений, включая
+ * неограниченное право на использование, копирование,
+ * изменение,
+ * добавление, публикацию, распространение,
+ * сублицензирование
+ * и/или продажу копий Программного Обеспечения, также
+ * как и лицам,
+ * которым предоставляется данное Программное
+ * Обеспечение,
+ * при соблюдении следующих условий:
+ *
+ * Указанное выше уведомление об авторском праве и
+ * данные условия
+ * должны быть включены во все копии или значимые части
+ * данного
+ * Программного Обеспечения.
+ *
+ * ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК
+ * ЕСТЬ»,
+ * БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ
+ * ПОДРАЗУМЕВАЕМЫХ,
+ * ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ
+ * ПРИГОДНОСТИ,
+ * СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И
+ * ОТСУТСТВИЯ НАРУШЕНИЙ
+ * ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ
+ * НЕСУТ
+ * ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ
+ * ИЛИ ДРУГИХ
+ * ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ
+ * ИНОМУ,
+ * ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С
+ * ПРОГРАММНЫМ
+ * ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО
+ * ОБЕСПЕЧЕНИЯ
+ * ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
+ */
+
+'use strict';
+
+const tools = require("../tools");
+
+exports.type = 'full';
+
+exports.active = false;
+
+exports.description = 'Finds <path> elements with the same d, fill, and ' +
+                      'stroke, and converts them to <use> elements ' +
+                      'referencing a single <path> def.';
+
+/**
+ * Finds <path> elements with the same d, fill, and stroke, and converts them to
+ * <use> elements referencing a single <path> def.
+ *
+ * @author Jacob Howcroft
+ */
+exports.fn = function(data) {
+  const seen = new Map();
+  let count = 0;
+  const defs = [];
+  traverse(data, item => {
+    if (!item.isElem('path') || !item.hasAttr('d')) {
+      return;
+    }
+    const d = item.attr('d').value;
+    const fill = (item.hasAttr('fill') && item.attr('fill').value) || '';
+    const stroke = (item.hasAttr('stroke') && item.attr('stroke').value) || '';
+    const key = d + ';s:' + stroke + ';f:' + fill;
+    const hasSeen = seen.get(key);
+    if (!hasSeen) {
+      seen.set(key, {elem: item, reused: false});
+      return;
+    }
+    if (!hasSeen.reused) {
+      hasSeen.reused = true;
+      if (!hasSeen.elem.hasAttr('id')) {
+        hasSeen.elem.addAttr({name: 'id', local: 'id',
+                              prefix: '', value: 'reuse-' + (count++)});
+      }
+      defs.push(hasSeen.elem);
+    }
+    item = convertToUse(item, hasSeen.elem.attr('id').value);
+  });
+  const defsTag = tools.createContentItem({
+    elem: 'defs', prefix: '', local: 'defs', content: [], attrs: []}, data);
+
+  data.content[0].spliceContent(0, 0, defsTag);
+  for (let def of defs) {
+    // Remove class and style before copying to avoid circular refs in
+    // JSON.stringify. This is fine because we don't actually want class or
+    // style information to be copied.
+    const style = def.style;
+    const defClass = def.class;
+    delete def.style;
+    delete def.class;
+    const defClone = def.clone();
+    def.style = style;
+    def.class = defClass;
+    defClone.removeAttr('transform');
+    defsTag.spliceContent(0, 0, defClone);
+    // Convert the original def to a use so the first usage isn't duplicated.
+    def = convertToUse(def, defClone.attr('id').value);
+    def.removeAttr('id');
+  }
+  return data;
+};
+
+/** */
+function convertToUse(item, href) {
+  item.renameElem('use');
+  item.removeAttr('d');
+  item.removeAttr('stroke');
+  item.removeAttr('fill');
+  item.addAttr({name: 'xlink:href', local: 'xlink:href',
+                prefix: 'none', value: '#' + href});
+  delete item.pathJS;
+  return item;
+}
+
+/** */
+function traverse(parent, callback) {
+  if (parent.isEmpty()) {
+    return;
+  }
+  for (let child of parent.content) {
+    callback(child);
+    traverse(child, callback);
+  }
+}
diff --git a/vendor/svgclean/src/svgclean/plugins/sortAttrs.js b/vendor/svgclean/src/svgclean/plugins/sortAttrs.js
new file mode 100644
index 000000000..cba5c1de6
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/sortAttrs.js
@@ -0,0 +1,84 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = false;
+
+exports.description = 'sorts element attributes (disabled by default)';
+
+exports.params = {
+	order: [
+		'id',
+		'width', 'height',
+		'x', 'x1', 'x2',
+		'y', 'y1', 'y2',
+		'cx', 'cy', 'r',
+		'fill', 'stroke', 'marker',
+		'd', 'points'
+	]
+};
+
+/**
+ * Sort element attributes for epic readability.
+ *
+ * @param {Object} item current iteration item
+ * @param {Object} params plugin params
+ *
+ * @author Nikolay Frantsev
+ */
+exports.fn = function(item, params) {
+
+	var attrs = [],
+		sorted = {},
+		orderlen = params.order.length + 1,
+		xmlnsOrder = params.xmlnsOrder || 'front';
+
+	if (item.elem) {
+
+		item.eachAttr(function(attr) {
+			attrs.push(attr);
+		});
+
+		attrs.sort(function(a, b) {
+			if (a.prefix != b.prefix) {
+				// xmlns attributes implicitly have the prefix xmlns
+				if (xmlnsOrder == 'front') {
+                    if (a.prefix == 'xmlns')
+                        return -1;
+                    if (b.prefix == 'xmlns')
+                        return 1;
+                }
+				return a.prefix < b.prefix ? -1 : 1;
+			}
+
+			var aindex = orderlen;
+			var bindex = orderlen;
+
+			for (var i = 0; i < params.order.length; i++) {
+				if (a.name == params.order[i]) {
+					aindex = i;
+				} else if (a.name.indexOf(params.order[i] + '-') === 0) {
+					aindex = i + .5;
+				}
+				if (b.name == params.order[i]) {
+					bindex = i;
+				} else if (b.name.indexOf(params.order[i] + '-') === 0) {
+					bindex = i + .5;
+				}
+			}
+
+			if (aindex != bindex) {
+				return aindex - bindex;
+			}
+			return a.name < b.name ? -1 : 1;
+		});
+
+		attrs.forEach(function (attr) {
+			sorted[attr.name] = attr;
+		});
+
+		item.attrs = sorted;
+
+	}
+
+};
diff --git a/vendor/svgclean/src/svgclean/plugins/sortDefsChildren.js b/vendor/svgclean/src/svgclean/plugins/sortDefsChildren.js
new file mode 100644
index 000000000..bf1d1c43f
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/plugins/sortDefsChildren.js
@@ -0,0 +1,47 @@
+'use strict';
+
+exports.type = 'perItem';
+
+exports.active = true;
+
+exports.description = 'Sorts children of <defs> to improve compression';
+
+/**
+ * Sorts children of defs in order to improve compression.
+ * Sorted first by frequency then by element name length then by element name (to ensure grouping).
+ *
+ * @param {Object} item current iteration item
+ * @return {Boolean} if false, item will be filtered out
+ *
+ * @author David Leston
+ */
+exports.fn = function(item) {
+
+    if (item.isElem('defs')) {
+
+        if (item.content) {
+            var frequency = item.content.reduce(function (frequency, child) {
+                if (child.elem in frequency) {
+                    frequency[child.elem]++;
+                } else {
+                    frequency[child.elem] = 1;
+                }
+                return frequency;
+            }, {});
+            item.content.sort(function (a, b) {
+                var frequencyComparison = frequency[b.elem] - frequency[a.elem];
+                if (frequencyComparison !== 0 ) {
+                    return frequencyComparison;
+                }
+                var lengthComparison = b.elem.length - a.elem.length;
+                if (lengthComparison !== 0) {
+                    return lengthComparison;
+                }
+                return a.elem != b.elem ? a.elem > b.elem ? -1 : 1 : 0;
+            });
+        }
+
+        return true;
+    }
+
+};
diff --git a/vendor/svgclean/src/svgclean/svg2js.js b/vendor/svgclean/src/svgclean/svg2js.js
new file mode 100644
index 000000000..892d3dbd5
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/svg2js.js
@@ -0,0 +1,190 @@
+"use strict";
+
+const SAX = require("sax");
+const JSAPI = require("./jsAPI.js");
+const CSSClassList = require("./css-class-list");
+const CSSStyleDeclaration = require("./css-style-declaration");
+const entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^\']+)'|"([^\"]+)")\s*>/g;
+
+const config = {
+  strict: true,
+  trim: false,
+  normalize: true,
+  lowercase: true,
+  xmlns: true,
+  position: true
+};
+
+/**
+ * Convert SVG (XML) string to SVG-as-JS object.
+ *
+ * @param {String} data input data
+ * @param {Function} callback
+ */
+module.exports = function(data, callback) {
+  return new Promise(function(resolve, reject) {
+    let sax = SAX.parser(config.strict, config);
+    let root = new JSAPI({ elem: "#document", content: [] });
+    let current = root;
+    let stack = [root];
+    let textContext = null;
+    let parsingError = false;
+
+    function pushToContent(content) {
+      content = new JSAPI(content, current);
+      (current.content = current.content || []).push(content);
+      return content;
+    }
+
+    sax.ondoctype = function(doctype) {
+      pushToContent({
+        doctype: doctype
+      });
+
+      let subsetStart = doctype.indexOf("[");
+      let entityMatch;
+
+      if (subsetStart >= 0) {
+        entityDeclaration.lastIndex = subsetStart;
+
+        while ((entityMatch = entityDeclaration.exec(data)) != null) {
+          sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3];
+        }
+      }
+    };
+
+    sax.onprocessinginstruction = function(data) {
+      pushToContent({
+        processinginstruction: data
+      });
+    };
+
+    sax.oncomment = function(comment) {
+      pushToContent({
+        comment: comment.trim()
+      });
+    };
+
+    sax.oncdata = function(cdata) {
+      pushToContent({
+        cdata: cdata
+      });
+    };
+
+    sax.onopentag = function(data) {
+      let elem = {
+        elem: data.name,
+        prefix: data.prefix,
+        local: data.local,
+        attrs: {}
+      };
+
+      elem.class = new CSSClassList(elem);
+      elem.style = new CSSStyleDeclaration(elem);
+
+      if (Object.keys(data.attributes).length) {
+        for (var name in data.attributes) {
+
+          if (name === "class") { // has class attribute
+            elem.class.hasClass();
+          }
+
+          if (name === "style") { // has style attribute
+            elem.style.hasStyle();
+          }
+
+          elem.attrs[name] = {
+            name: name,
+            value: data.attributes[name].value,
+            prefix: data.attributes[name].prefix,
+            local: data.attributes[name].local
+          };
+        }
+      }
+
+      elem = pushToContent(elem);
+      current = elem;
+
+      // Save info about <text> tag to prevent trimming of meaningful whitespace
+      if (data.name == "text" && !data.prefix) {
+        textContext = current;
+      }
+
+      stack.push(elem);
+
+    };
+
+    sax.ontext = function(text) {
+      if (/\S/.test(text) || textContext) {
+        if (!textContext) text = text.trim();
+
+        pushToContent({
+          text: text
+        });
+
+      }
+    };
+
+    sax.onclosetag = function() {
+      var last = stack.pop();
+
+      // Trim text inside <text> tag.
+      if (last == textContext) {
+        trim(textContext);
+        textContext = null;
+      }
+      current = stack[stack.length - 1];
+    };
+
+    sax.onerror = function(e) {
+        e.message = "Error in parsing SVG: " + e.message;
+        if (e.message.indexOf("Unexpected end") < 0) {
+          reject(e);
+        }
+    };
+
+    sax.onend = function() {
+      if (!this.error) {
+        resolve(root);
+      } else {
+        reject(this.error);
+      }
+
+    };
+
+    try {
+      sax.write(data);
+    } catch (e) {
+      reject(e)
+      parsingError = true;
+    }
+    if (!parsingError) {
+      sax.close();
+    }
+
+    function trim(elem) {
+      if (!elem.content) return elem;
+
+      let start = elem.content[0];
+      let end = elem.content[elem.content.length - 1];
+
+      while (start && start.content && !start.text) {
+        start = start.content[0];
+      }
+
+      if (start && start.text) {
+        start.text = start.text.replace(/^\s+/, "");
+      }
+
+      while (end && end.content && !end.text) {
+        end = end.content[end.content.length - 1];
+      }
+
+      if (end && end.text) {
+        end.text = end.text.replace(/\s+$/, "");
+      }
+
+      return elem;
+    }
+  });
+};
diff --git a/vendor/svgclean/src/svgclean/tools.js b/vendor/svgclean/src/svgclean/tools.js
new file mode 100644
index 000000000..75c78eb79
--- /dev/null
+++ b/vendor/svgclean/src/svgclean/tools.js
@@ -0,0 +1,152 @@
+'use strict';
+
+const JSAPI = require("./jsAPI");
+
+/**
+ * The factory that creates a content item with the helper methods.
+ *
+ * @param {Object} data which is passed to jsAPI constructor
+ * @returns {JSAPI} content item
+ */
+exports.createContentItem = function(data) {
+  return new JSAPI(data);
+};
+
+/**
+ * Encode plain SVG data string into Data URI string.
+ *
+ * @param {String} str input string
+ * @param {String} type Data URI type
+ * @return {String} output string
+ */
+exports.encodeSVGDatauri = function(str, type) {
+    var prefix = 'data:image/svg+xml';
+    if (!type || type === 'base64') {
+        // base64
+        prefix += ';base64,';
+        if (Buffer.from) {
+            str = prefix + Buffer.from(str).toString('base64');
+        } else {
+            str = prefix + new Buffer(str).toString('base64');
+        }
+    } else if (type === 'enc') {
+        // URI encoded
+        str = prefix + ',' + encodeURIComponent(str);
+    } else if (type === 'unenc') {
+        // unencoded
+        str = prefix + ',' + str;
+    }
+    return str;
+};
+
+/**
+ * Decode SVG Data URI string into plain SVG string.
+ *
+ * @param {string} str input string
+ * @return {String} output string
+ */
+exports.decodeSVGDatauri = function(str) {
+    var regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/;
+    var match = regexp.exec(str);
+
+    // plain string
+    if (!match) return str;
+
+    var data = match[3];
+
+    if (match[2]) {
+        // base64
+        str = new Buffer(data, 'base64').toString('utf8');
+    } else if (data.charAt(0) === '%') {
+        // URI encoded
+        str = decodeURIComponent(data);
+    } else if (data.charAt(0) === '<') {
+        // unencoded
+        str = data;
+    }
+    return str;
+};
+
+exports.intersectArrays = function(a, b) {
+    return a.filter(function(n) {
+        return b.indexOf(n) > -1;
+    });
+};
+
+/**
+ * Convert a row of numbers to an optimized string view.
+ *
+ * @example
+ * [0, -1, .5, .5] → "0-1 .5.5"
+ *
+ * @param {number[]} data
+ * @param {Object} params
+ * @param {string?} command path data instruction
+ * @return {string}
+ */
+exports.cleanupOutData = function(data, params, command) {
+    var str = '',
+        delimiter,
+        prev;
+
+    data.forEach(function(item, i) {
+        // space delimiter by default
+        delimiter = ' ';
+
+        // no extra space in front of first number
+        if (i == 0) delimiter = '';
+
+        // no extra space after 'arcto' command flags
+        if (params.noSpaceAfterFlags && (command == 'A' || command == 'a')) {
+            var pos = i % 7;
+            if (pos == 4 || pos == 5) delimiter = '';
+        }
+
+        // remove floating-point numbers leading zeros
+        // 0.5 → .5
+        // -0.5 → -.5
+        if (params.leadingZero) {
+            item = removeLeadingZero(item);
+        }
+
+        // no extra space in front of negative number or
+        // in front of a floating number if a previous number is floating too
+        if (
+            params.negativeExtraSpace &&
+            delimiter != '' &&
+            (item < 0 ||
+                (String(item).charCodeAt(0) == 46 && prev % 1 !== 0)
+            )
+        ) {
+            delimiter = '';
+        }
+        // save prev item value
+        prev = item;
+        str += delimiter + item;
+    });
+    return str;
+};
+
+/**
+ * Remove floating-point numbers leading zero.
+ *
+ * @example
+ * 0.5 → .5
+ *
+ * @example
+ * -0.5 → -.5
+ *
+ * @param {Float} num input number
+ *
+ * @return {String} output number as string
+ */
+var removeLeadingZero = exports.removeLeadingZero = function(num) {
+    var strNum = num.toString();
+
+    if (0 < num && num < 1 && strNum.charCodeAt(0) == 48) {
+        strNum = strNum.slice(1);
+    } else if (-1 < num && num < 0 && strNum.charCodeAt(1) == 48) {
+        strNum = strNum.charAt(0) + strNum.slice(2);
+    }
+    return strNum;
+};
diff --git a/vendor/svgclean/test.js b/vendor/svgclean/test.js
new file mode 100644
index 000000000..b37ab9d65
--- /dev/null
+++ b/vendor/svgclean/test.js
@@ -0,0 +1,8 @@
+const svgclean = require("./src/svgclean");
+const fs   = require("fs");
+
+fs.readFile("./test.svg", "utf-8", (err, data) => {
+  svgclean.optimize({}, data).then((result) => {
+    console.dir(result);
+  });
+});
diff --git a/vendor/svgclean/test.svg b/vendor/svgclean/test.svg
new file mode 100644
index 000000000..c539cd408
--- /dev/null
+++ b/vendor/svgclean/test.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" width="10" height="20">
+    test
+</svg>