grunt-svgstore
Advanced tools
Comparing version 0.2.5 to 0.2.6
@@ -104,2 +104,11 @@ /* | ||
} | ||
}, | ||
cleanup: { | ||
options: { | ||
cleanup: true | ||
}, | ||
files: { | ||
'tmp/cleanup.svg': ['test/fixtures/cleanup.svg'] | ||
} | ||
} | ||
@@ -106,0 +115,0 @@ }, |
{ | ||
"name": "grunt-svgstore", | ||
"description": "Merge SVGs from a folder.", | ||
"version": "0.2.5", | ||
"version": "0.2.6", | ||
"homepage": "https://github.com/FWeinb/grunt-svgstore", | ||
@@ -6,0 +6,0 @@ "author": { |
@@ -100,2 +100,8 @@ # grunt-svgstore [![NPM version](https://badge.fury.io/js/grunt-svgstore.svg)](http://badge.fury.io/js/grunt-svgstore) [![Build Status](https://travis-ci.org/FWeinb/grunt-svgstore.svg?branch=master)](https://travis-ci.org/FWeinb/grunt-svgstore) | ||
#### options.cleanup (since 0.2.6) | ||
Type: `boolean` | ||
Default value: `false` | ||
Clean up all inline style definitions that may jeopardise later stylesheet-based colouring (`fill`). | ||
### Usage Examples | ||
@@ -131,2 +137,6 @@ | ||
#### 0.2.6 | ||
* Add `options.clean` to remove inline styles from source svgs. (Thanks to [ain](https://github.com/FWeinb/grunt-svgstore/pull/37)) | ||
* Reformat source to use 2 spaces for indentation (Fix [#36](https://github.com/FWeinb/grunt-svgstore/issues/36)) | ||
#### 0.2.5 | ||
@@ -133,0 +143,0 @@ * To generate the id from the filename is now used as a title. (Fix [#33](https://github.com/FWeinb/grunt-svgstore/issues/33)) |
@@ -11,215 +11,211 @@ /* | ||
module.exports = function (grunt) { | ||
var crypto = require('crypto'); | ||
var multiline = require('multiline'); | ||
var path = require('path'); | ||
var crypto = require('crypto'); | ||
var multiline = require('multiline'); | ||
var path = require('path'); | ||
var beautify = require('js-beautify').html; | ||
var cheerio = require('cheerio'); | ||
var chalk = require('chalk'); | ||
var beautify = require('js-beautify').html; | ||
var cheerio = require('cheerio'); | ||
var chalk = require('chalk'); | ||
var md5 = function (str) { | ||
return crypto.createHash('md5').update(str).digest('hex'); | ||
}; | ||
var md5 = function (str) { | ||
return crypto.createHash('md5').update(str).digest('hex'); | ||
}; | ||
var symbolAttributes = function(attrs){ | ||
if (typeof attrs === 'undefined') {return '';} | ||
var symbolAttributes = function(attrs){ | ||
if(typeof attrs === 'undefined') {return '';} | ||
var result = ''; | ||
Object.keys(attrs).forEach(function (key) { | ||
result += ' ' + key + '="' + attrs[key] + '"'; | ||
}); | ||
return result; | ||
}; | ||
var result = ''; | ||
Object.keys(attrs).forEach(function (key) { | ||
result += ' ' + key + '="' + attrs[key] + '"'; | ||
}); | ||
return result; | ||
}; | ||
var convertNameToId = function( name ){ | ||
var dotPos = name.indexOf('.'); | ||
if ( dotPos > -1){ | ||
name = name.substring(0, dotPos); | ||
} | ||
return name; | ||
}; | ||
var convertNameToId = function( name ){ | ||
var dotPos = name.indexOf('.'); | ||
if ( dotPos > -1){ | ||
name = name.substring(0, dotPos); | ||
} | ||
return name; | ||
}; | ||
// Matching an url() reference. To correct references broken by making ids unquie to the source svg | ||
var urlPattern = /url\(\s*#([^ ]+?)\s*\)/g; | ||
// Matching an url() reference. To correct references broken by making ids unquie to the source svg | ||
var urlPattern = /url\(\s*#([^ ]+?)\s*\)/g; | ||
// Please see the Grunt documentation for more information regarding task | ||
// creation: http://gruntjs.com/creating-tasks | ||
// Please see the Grunt documentation for more information regarding task | ||
// creation: http://gruntjs.com/creating-tasks | ||
grunt.registerMultiTask('svgstore', 'Merge SVGs from a folder.', function () { | ||
// Merge task-specific and/or target-specific options with these defaults. | ||
var options = this.options({ | ||
prefix: '', | ||
svg: { | ||
'xmlns': "http://www.w3.org/2000/svg" | ||
}, | ||
formatting: false, | ||
includedemo: false, | ||
symbol: {} | ||
}); | ||
grunt.registerMultiTask('svgstore', 'Merge SVGs from a folder.', function () { | ||
// Merge task-specific and/or target-specific options with these defaults. | ||
var options = this.options({ | ||
prefix: '', | ||
svg: { | ||
'xmlns': "http://www.w3.org/2000/svg" | ||
}, | ||
formatting: false, | ||
includedemo: false, | ||
symbol: {} | ||
}); | ||
var symbolAttrs = symbolAttributes(options.symbol); | ||
var symbolAttrs = symbolAttributes(options.symbol); | ||
this.files.forEach(function (file) { | ||
var $resultDocument = cheerio.load('<svg><defs></defs></svg>', { lowerCaseAttributeNames : false }), | ||
$resultSvg = $resultDocument('svg'), | ||
$resultDefs = $resultDocument('defs').first(), | ||
iconNameViewBoxArray = []; // Used to store information of all icons that are added | ||
// { name : '' } | ||
this.files.forEach(function (file) { | ||
// Merge in SVG attributes from option | ||
for (var attr in options.svg) { | ||
$resultSvg.attr(attr, options.svg[attr]); | ||
} | ||
var $resultDocument = cheerio.load('<svg><defs></defs></svg>', { lowerCaseAttributeNames : false }), | ||
$resultSvg = $resultDocument('svg'), | ||
$resultDefs = $resultDocument('defs').first(), | ||
iconNameViewBoxArray = []; // Used to store information of all icons that are added | ||
// { name : '' } | ||
file.src.filter(function (filepath) { | ||
if (!grunt.file.exists(filepath)) { | ||
grunt.log.warn('File "' + filepath + '" not found.'); | ||
return false; | ||
} else { | ||
return true; | ||
} | ||
}).map(function (filepath) { | ||
var filename = path.basename(filepath, '.svg'); | ||
var contentStr = grunt.file.read(filepath); | ||
var uniqueId = md5(contentStr); | ||
var $ = cheerio.load(contentStr, { | ||
normalizeWhitespace: true, | ||
xmlMode: true | ||
}); | ||
// Merge in SVG attributes from option | ||
for (var attr in options.svg) { | ||
$resultSvg.attr(attr, options.svg[attr]); | ||
} | ||
// Map to store references from id to uniqueId + id; | ||
var mappedIds = {}; | ||
file.src.filter(function (filepath) { | ||
if (!grunt.file.exists(filepath)) { | ||
grunt.log.warn('File "' + filepath + '" not found.'); | ||
return false; | ||
} else { | ||
return true; | ||
} | ||
}).map(function (filepath) { | ||
var filename = path.basename(filepath, '.svg'); | ||
var contentStr = grunt.file.read(filepath); | ||
var uniqueId = md5(contentStr); | ||
var $ = cheerio.load(contentStr, { | ||
normalizeWhitespace: true, | ||
xmlMode: true | ||
}); | ||
// Make IDs unique | ||
$('[id]').each(function () { | ||
var $elem = $(this); | ||
var id = $elem.attr('id'); | ||
var newId = uniqueId + id; | ||
mappedIds[id] = newId; | ||
$elem.attr('id', newId); | ||
}); | ||
// Map to store references from id to uniqueId + id; | ||
var mappedIds = {}; | ||
// Search for an url() reference in every attribute of every tag | ||
// replace the id with the unique one. | ||
$('*').each(function () { | ||
var $elem = $(this); | ||
var attrs = $elem.attr(); | ||
Object.keys(attrs).forEach(function (key) { | ||
var value = attrs[key]; | ||
var match; | ||
while ((match = urlPattern.exec(value)) !== null) { | ||
if (mappedIds[match[1]] !== undefined) { | ||
value = value.replace(match[0], 'url(#' + mappedIds[match[1]] + ')'); | ||
} else { | ||
grunt.log.warn('Can\'t reference to id "' + match[1] + '" from attribute "' + attr + '" in "' + this[0].name + '" because it is not defined.'); | ||
} | ||
} | ||
if (options.cleanup && key === 'style') { | ||
value = null; | ||
} | ||
$elem.attr(key, value); | ||
}); | ||
}); | ||
// Make IDs unique | ||
$('[id]').each(function () { | ||
var $elem = $(this); | ||
var id = $elem.attr('id'); | ||
var newId = uniqueId + id; | ||
mappedIds[id] = newId; | ||
$elem.attr('id', newId); | ||
}); | ||
var $svg = $('svg'); | ||
var $title = $('title'); | ||
var $desc = $('desc'); | ||
var $def = $('defs').first(); | ||
// Search for an url() reference in every attribute of every tag | ||
// replace the id with the unique one. | ||
$('*').each(function () { | ||
var $elem = $(this); | ||
var attrs = $elem.attr(); | ||
Object.keys(attrs).forEach(function (key) { | ||
var value = attrs[key]; | ||
var match; | ||
while ((match = urlPattern.exec(value)) !== null) { | ||
if (mappedIds[match[1]] !== undefined) { | ||
value = value.replace(match[0], 'url(#' + mappedIds[match[1]] + ')'); | ||
} else { | ||
grunt.log.warn('Can\'t reference to id "' + match[1] + '" from attribute "' + attr + '" in "' + this[0].name + '" because it is not defined.'); | ||
} | ||
} | ||
$elem.attr(key, value); | ||
}); | ||
}); | ||
// merge in the defs from this svg in the result defs block. | ||
$resultDefs.append($def.html()); | ||
var $svg = $('svg'); | ||
var $title = $('title'); | ||
var $desc = $('desc'); | ||
var $def = $('defs').first(); | ||
var title = $title.first().html(); | ||
var desc = $desc.first().html(); | ||
// merge in the defs from this svg in the result defs block. | ||
$resultDefs.append($def.html()); | ||
// remove def, title, desc from this svg | ||
$def.remove(); | ||
$title.remove(); | ||
$desc.remove(); | ||
var title = $title.first().html(); | ||
var desc = $desc.first().html(); | ||
var id = convertNameToId(filename); | ||
// remove def, title, desc from this svg | ||
$def.remove(); | ||
$title.remove(); | ||
$desc.remove(); | ||
// If there is no title use the filename | ||
title = title || id; | ||
var id = convertNameToId(filename); | ||
var resultStr = '<symbol' + symbolAttrs + '>' + '<title>' + title + '</title>'; | ||
// If there is no title use the filename | ||
title = title || id; | ||
// Only add desc if it was set | ||
if ( desc ) { resultStr +='<desc>'+ desc +'</desc>'; } | ||
var resultStr = '<symbol' + symbolAttrs + '>' + '<title>' + title + '</title>'; | ||
resultStr += $svg.html() + '</symbol>'; | ||
// Only add desc if it was set | ||
if ( desc ) { resultStr +='<desc>'+ desc +'</desc>'; } | ||
// Create a object | ||
var $res = cheerio.load(resultStr, { lowerCaseAttributeNames : false }); | ||
resultStr += $svg.html() + '</symbol>'; | ||
$res('symbol').attr('viewBox', $svg.attr('viewBox')); | ||
// Create a object | ||
var $res = cheerio.load(resultStr, { lowerCaseAttributeNames : false }); | ||
var graphicId = options.prefix + id; | ||
// Add ID to the first Element | ||
$res('*').first().attr('id', graphicId); | ||
$res('symbol').attr('viewBox', $svg.attr('viewBox')); | ||
// Append <symbol> to resulting SVG | ||
$resultSvg.append($res.html()); | ||
var graphicId = options.prefix + id; | ||
// Add ID to the first Element | ||
$res('*').first().attr('id', graphicId); | ||
// Append <symbol> to resulting SVG | ||
$resultSvg.append($res.html()); | ||
// Add icon to the demo.html array | ||
if (options.includedemo) { | ||
iconNameViewBoxArray.push({ | ||
name: graphicId | ||
}); | ||
} | ||
}); | ||
// remove defs block if empty | ||
if ( $resultDefs.html().trim() === '' ) { | ||
$resultDefs.remove(); | ||
} | ||
// Add icon to the demo.html array | ||
if (options.includedemo) { | ||
iconNameViewBoxArray.push({ | ||
name: graphicId | ||
}); | ||
} | ||
var result = options.formatting ? beautify($resultDocument.xml(), options.formatting) : $resultDocument.xml(); | ||
var destName = path.basename(file.dest, '.svg'); | ||
}); | ||
grunt.file.write(file.dest, result); | ||
// remove defs block if empty | ||
if ( $resultDefs.html().trim() === '' ) { | ||
$resultDefs.remove(); | ||
} | ||
grunt.log.writeln('File ' + chalk.cyan(file.dest) + ' created.'); | ||
var result = options.formatting ? beautify($resultDocument.xml(), options.formatting) : $resultDocument.xml(); | ||
var destName = path.basename(file.dest, '.svg'); | ||
if (options.includedemo) { | ||
$resultSvg.attr('style', 'width:0;height:0;visibility:hidden;'); | ||
grunt.file.write(file.dest, result); | ||
var demoHTML = multiline.stripIndent(function () { /* | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<style> | ||
svg{ | ||
width:50px; | ||
height:50px; | ||
fill:black !important; | ||
} | ||
</style> | ||
<head> | ||
<body> | ||
{{svg}} | ||
{{useBlock}} | ||
</body> | ||
</html> | ||
*/}); | ||
grunt.log.writeln('File ' + chalk.cyan(file.dest) + ' created.'); | ||
var useBlock = ''; | ||
iconNameViewBoxArray.forEach(function (item) { | ||
useBlock += '\t\t<svg>\n\t\t\t<use xlink:href="#' + item.name + '"></use>\n\t\t</svg>\n'; | ||
}); | ||
if (options.includedemo) { | ||
demoHTML = demoHTML.replace('{{svg}}', $resultDocument.xml()); | ||
demoHTML = demoHTML.replace('{{useBlock}}', useBlock); | ||
$resultSvg.attr('style', 'width:0;height:0;visibility:hidden;'); | ||
var demoHTML = multiline.stripIndent(function () { /* | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<style> | ||
svg{ | ||
width:50px; | ||
height:50px; | ||
fill:black !important; | ||
} | ||
</style> | ||
<head> | ||
<body> | ||
{{svg}} | ||
{{useBlock}} | ||
</body> | ||
</html> | ||
*/}); | ||
var useBlock = ''; | ||
iconNameViewBoxArray.forEach(function (item) { | ||
useBlock += '\t\t<svg>\n\t\t\t<use xlink:href="#' + item.name + '"></use>\n\t\t</svg>\n'; | ||
}); | ||
demoHTML = demoHTML.replace('{{svg}}', $resultDocument.xml()); | ||
demoHTML = demoHTML.replace('{{useBlock}}', useBlock); | ||
var demoPath = path.resolve(path.dirname(file.dest), destName + '-demo.html'); | ||
grunt.file.write(demoPath, demoHTML); | ||
grunt.log.writeln('Demo file ' + chalk.cyan(demoPath) + ' created.'); | ||
} | ||
}); | ||
var demoPath = path.resolve(path.dirname(file.dest), destName + '-demo.html'); | ||
grunt.file.write(demoPath, demoHTML); | ||
grunt.log.writeln('Demo file ' + chalk.cyan(demoPath) + ' created.'); | ||
} | ||
}); | ||
}); | ||
}; |
@@ -93,2 +93,12 @@ 'use strict'; | ||
withCleanup: function(test) { | ||
test.expect(1); | ||
var actual = grunt.file.read('tmp/cleanup.svg'); | ||
var expected = grunt.file.read('test/expected/cleanup.svg'); | ||
test.equal(actual, expected, 'All path inline style attributes should be removed'); | ||
test.done(); | ||
}, | ||
with_include_demo : function(test){ | ||
@@ -113,2 +123,3 @@ test.expect(1); | ||
} | ||
}; |
Sorry, the diff of this file is not supported yet
102612
35
386
180