mirror of
https://github.com/svg/svgo.git
synced 2025-07-29 20:21:14 +03:00
Rename js2svg to stringifier
This commit is contained in:
337
lib/stringifier.js
Normal file
337
lib/stringifier.js
Normal file
@ -0,0 +1,337 @@
|
||||
'use strict';
|
||||
|
||||
const { textElems } = require('../plugins/_collections.js');
|
||||
|
||||
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,
|
||||
eol: 'lf',
|
||||
finalNewline: false,
|
||||
};
|
||||
|
||||
var entities = {
|
||||
'&': '&',
|
||||
"'": ''',
|
||||
'"': '"',
|
||||
'>': '>',
|
||||
'<': '<',
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.eol === 'crlf') {
|
||||
this.eol = '\r\n';
|
||||
} else {
|
||||
this.eol = '\n';
|
||||
}
|
||||
|
||||
if (this.config.pretty) {
|
||||
this.config.doctypeEnd += this.eol;
|
||||
this.config.procInstEnd += this.eol;
|
||||
this.config.commentEnd += this.eol;
|
||||
this.config.cdataEnd += this.eol;
|
||||
this.config.tagShortEnd += this.eol;
|
||||
this.config.tagOpenEnd += this.eol;
|
||||
this.config.tagCloseEnd += this.eol;
|
||||
this.config.textEnd += this.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 = '';
|
||||
|
||||
this.indentLevel++;
|
||||
|
||||
for (const item of data.children) {
|
||||
if (item.type === 'element') {
|
||||
svg += this.createElem(item);
|
||||
}
|
||||
if (item.type === 'text') {
|
||||
svg += this.createText(item);
|
||||
}
|
||||
if (item.type === 'doctype') {
|
||||
svg += this.createDoctype(item);
|
||||
}
|
||||
if (item.type === 'instruction') {
|
||||
svg += this.createProcInst(item);
|
||||
}
|
||||
if (item.type === 'comment') {
|
||||
svg += this.createComment(item);
|
||||
}
|
||||
if (item.type === 'cdata') {
|
||||
svg += this.createCDATA(item);
|
||||
}
|
||||
}
|
||||
|
||||
this.indentLevel--;
|
||||
|
||||
if (
|
||||
this.config.finalNewline &&
|
||||
this.indentLevel === 0 &&
|
||||
svg.length > 0 &&
|
||||
svg[svg.length - 1] !== '\n'
|
||||
) {
|
||||
svg += this.eol;
|
||||
}
|
||||
|
||||
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 (node) {
|
||||
const { doctype } = node.data;
|
||||
return this.config.doctypeStart + doctype + this.config.doctypeEnd;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create XML Processing Instruction tag.
|
||||
*
|
||||
* @param {Object} instruction instruction object
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
JS2SVG.prototype.createProcInst = function (node) {
|
||||
const { name, value } = node;
|
||||
return (
|
||||
this.config.procInstStart + name + ' ' + value + this.config.procInstEnd
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create comment tag.
|
||||
*
|
||||
* @param {String} comment comment body
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
JS2SVG.prototype.createComment = function (node) {
|
||||
const { value } = node;
|
||||
return this.config.commentStart + value + this.config.commentEnd;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create CDATA section.
|
||||
*
|
||||
* @param {String} cdata CDATA body
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
JS2SVG.prototype.createCDATA = function (node) {
|
||||
const { value } = node;
|
||||
return (
|
||||
this.createIndent() + this.config.cdataStart + value + 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.name === 'svg' &&
|
||||
data.attributes.width != null &&
|
||||
data.attributes.height != null
|
||||
) {
|
||||
this.width = data.attributes.width;
|
||||
this.height = data.attributes.height;
|
||||
}
|
||||
|
||||
// empty element and short tag
|
||||
if (data.children.length === 0) {
|
||||
if (this.config.useShortTags) {
|
||||
return (
|
||||
this.createIndent() +
|
||||
this.config.tagShortStart +
|
||||
data.name +
|
||||
this.createAttrs(data) +
|
||||
this.config.tagShortEnd
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
this.createIndent() +
|
||||
this.config.tagShortStart +
|
||||
data.name +
|
||||
this.createAttrs(data) +
|
||||
this.config.tagOpenEnd +
|
||||
this.config.tagCloseStart +
|
||||
data.name +
|
||||
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(),
|
||||
closeIndent = this.createIndent(),
|
||||
processedData = '',
|
||||
dataEnd = '';
|
||||
|
||||
if (this.textContext) {
|
||||
tagOpenStart = defaults.tagOpenStart;
|
||||
tagOpenEnd = defaults.tagOpenEnd;
|
||||
tagCloseStart = defaults.tagCloseStart;
|
||||
tagCloseEnd = defaults.tagCloseEnd;
|
||||
openIndent = '';
|
||||
} else if (data.isElem(textElems)) {
|
||||
tagOpenEnd = defaults.tagOpenEnd;
|
||||
tagCloseStart = defaults.tagCloseStart;
|
||||
closeIndent = '';
|
||||
this.textContext = data;
|
||||
}
|
||||
|
||||
processedData += this.convert(data).data;
|
||||
|
||||
if (this.textContext == data) {
|
||||
this.textContext = null;
|
||||
}
|
||||
|
||||
return (
|
||||
openIndent +
|
||||
tagOpenStart +
|
||||
data.name +
|
||||
this.createAttrs(data) +
|
||||
tagOpenEnd +
|
||||
processedData +
|
||||
dataEnd +
|
||||
closeIndent +
|
||||
tagCloseStart +
|
||||
data.name +
|
||||
tagCloseEnd
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create element attributes.
|
||||
*
|
||||
* @param {Object} elem attributes object
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
JS2SVG.prototype.createAttrs = function (element) {
|
||||
let attrs = '';
|
||||
for (const [name, value] of Object.entries(element.attributes)) {
|
||||
if (value !== undefined) {
|
||||
const encodedValue = value
|
||||
.toString()
|
||||
.replace(this.config.regValEntities, this.config.encodeEntity);
|
||||
attrs +=
|
||||
' ' + name + this.config.attrStart + encodedValue + this.config.attrEnd;
|
||||
} else {
|
||||
attrs += ' ' + name;
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create text node.
|
||||
*
|
||||
* @param {String} text text
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
JS2SVG.prototype.createText = function (node) {
|
||||
const { value } = node;
|
||||
return (
|
||||
this.createIndent() +
|
||||
this.config.textStart +
|
||||
value.replace(this.config.regEntities, this.config.encodeEntity) +
|
||||
(this.textContext ? '' : this.config.textEnd)
|
||||
);
|
||||
};
|
Reference in New Issue
Block a user