mirror of
https://github.com/svg/svgo.git
synced 2025-07-31 07:44:22 +03:00
Cover svg parser with tsdoc (#1584)
Moved to lib/parser.js. The code will be slightly simpler when JSAPI will be removed in v3.
This commit is contained in:
@ -1,10 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {import('./types').XastNode} XastNode
|
||||
* @typedef {import('./types').XastInstruction} XastInstruction
|
||||
* @typedef {import('./types').XastDoctype} XastDoctype
|
||||
* @typedef {import('./types').XastComment} XastComment
|
||||
* @typedef {import('./types').XastRoot} XastRoot
|
||||
* @typedef {import('./types').XastElement} XastElement
|
||||
* @typedef {import('./types').XastCdata} XastCdata
|
||||
* @typedef {import('./types').XastText} XastText
|
||||
* @typedef {import('./types').XastParent} XastParent
|
||||
*/
|
||||
|
||||
// @ts-ignore sax will be replaced with something else later
|
||||
const SAX = require('@trysound/sax');
|
||||
const JSAPI = require('./jsAPI.js');
|
||||
const { textElems } = require('../../plugins/_collections.js');
|
||||
const JSAPI = require('./svgo/jsAPI.js');
|
||||
const { textElems } = require('../plugins/_collections.js');
|
||||
|
||||
class SvgoParserError extends Error {
|
||||
/**
|
||||
* @param message {string}
|
||||
* @param line {number}
|
||||
* @param column {number}
|
||||
* @param source {string}
|
||||
* @param file {void | string}
|
||||
*/
|
||||
constructor(message, line, column, source, file) {
|
||||
super(message);
|
||||
this.name = 'SvgoParserError';
|
||||
@ -67,103 +87,160 @@ const config = {
|
||||
/**
|
||||
* Convert SVG (XML) string to SVG-as-JS object.
|
||||
*
|
||||
* @param {String} data input data
|
||||
* @type {(data: string, from?: string) => XastRoot}
|
||||
*/
|
||||
module.exports = function (data, from) {
|
||||
const parseSvg = (data, from) => {
|
||||
const sax = SAX.parser(config.strict, config);
|
||||
/**
|
||||
* @type {XastRoot}
|
||||
*/
|
||||
const root = new JSAPI({ type: 'root', children: [] });
|
||||
/**
|
||||
* @type {XastParent}
|
||||
*/
|
||||
let current = root;
|
||||
let stack = [root];
|
||||
/**
|
||||
* @type {Array<XastParent>}
|
||||
*/
|
||||
const stack = [root];
|
||||
|
||||
function pushToContent(node) {
|
||||
/**
|
||||
* @type {<T extends XastNode>(node: T) => T}
|
||||
*/
|
||||
const pushToContent = (node) => {
|
||||
const wrapped = new JSAPI(node, current);
|
||||
current.children.push(wrapped);
|
||||
return wrapped;
|
||||
}
|
||||
};
|
||||
|
||||
sax.ondoctype = function (doctype) {
|
||||
pushToContent({
|
||||
/**
|
||||
* @type {(doctype: string) => void}
|
||||
*/
|
||||
sax.ondoctype = (doctype) => {
|
||||
/**
|
||||
* @type {XastDoctype}
|
||||
*/
|
||||
const node = {
|
||||
type: 'doctype',
|
||||
// TODO parse doctype for name, public and system to match xast
|
||||
name: 'svg',
|
||||
data: {
|
||||
doctype,
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
pushToContent(node);
|
||||
const subsetStart = doctype.indexOf('[');
|
||||
let entityMatch;
|
||||
|
||||
if (subsetStart >= 0) {
|
||||
entityDeclaration.lastIndex = subsetStart;
|
||||
|
||||
while ((entityMatch = entityDeclaration.exec(data)) != null) {
|
||||
let entityMatch = entityDeclaration.exec(data);
|
||||
while (entityMatch != null) {
|
||||
sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3];
|
||||
entityMatch = entityDeclaration.exec(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sax.onprocessinginstruction = function (data) {
|
||||
pushToContent({
|
||||
/**
|
||||
* @type {(data: { name: string, body: string }) => void}
|
||||
*/
|
||||
sax.onprocessinginstruction = (data) => {
|
||||
/**
|
||||
* @type {XastInstruction}
|
||||
*/
|
||||
const node = {
|
||||
type: 'instruction',
|
||||
name: data.name,
|
||||
value: data.body,
|
||||
});
|
||||
};
|
||||
pushToContent(node);
|
||||
};
|
||||
|
||||
sax.oncomment = function (comment) {
|
||||
pushToContent({
|
||||
/**
|
||||
* @type {(comment: string) => void}
|
||||
*/
|
||||
sax.oncomment = (comment) => {
|
||||
/**
|
||||
* @type {XastComment}
|
||||
*/
|
||||
const node = {
|
||||
type: 'comment',
|
||||
value: comment.trim(),
|
||||
});
|
||||
};
|
||||
pushToContent(node);
|
||||
};
|
||||
|
||||
sax.oncdata = function (cdata) {
|
||||
pushToContent({
|
||||
/**
|
||||
* @type {(cdata: string) => void}
|
||||
*/
|
||||
sax.oncdata = (cdata) => {
|
||||
/**
|
||||
* @type {XastCdata}
|
||||
*/
|
||||
const node = {
|
||||
type: 'cdata',
|
||||
value: cdata,
|
||||
});
|
||||
};
|
||||
pushToContent(node);
|
||||
};
|
||||
|
||||
sax.onopentag = function (data) {
|
||||
var element = {
|
||||
/**
|
||||
* @type {(data: { name: string, attributes: Record<string, { value: string }>}) => void}
|
||||
*/
|
||||
sax.onopentag = (data) => {
|
||||
/**
|
||||
* @type {XastElement}
|
||||
*/
|
||||
let element = {
|
||||
type: 'element',
|
||||
name: data.name,
|
||||
attributes: {},
|
||||
children: [],
|
||||
};
|
||||
|
||||
for (const [name, attr] of Object.entries(data.attributes)) {
|
||||
element.attributes[name] = attr.value;
|
||||
}
|
||||
|
||||
element = pushToContent(element);
|
||||
current = element;
|
||||
|
||||
stack.push(element);
|
||||
};
|
||||
|
||||
sax.ontext = function (text) {
|
||||
/**
|
||||
* @type {(text: string) => void}
|
||||
*/
|
||||
sax.ontext = (text) => {
|
||||
if (current.type === 'element') {
|
||||
// prevent trimming of meaningful whitespace inside textual tags
|
||||
if (textElems.includes(current.name) && !data.prefix) {
|
||||
pushToContent({
|
||||
if (textElems.includes(current.name)) {
|
||||
/**
|
||||
* @type {XastText}
|
||||
*/
|
||||
const node = {
|
||||
type: 'text',
|
||||
value: text,
|
||||
});
|
||||
};
|
||||
pushToContent(node);
|
||||
} else if (/\S/.test(text)) {
|
||||
pushToContent({
|
||||
/**
|
||||
* @type {XastText}
|
||||
*/
|
||||
const node = {
|
||||
type: 'text',
|
||||
value: text.trim(),
|
||||
});
|
||||
};
|
||||
pushToContent(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sax.onclosetag = function () {
|
||||
sax.onclosetag = () => {
|
||||
stack.pop();
|
||||
current = stack[stack.length - 1];
|
||||
};
|
||||
|
||||
sax.onerror = function (e) {
|
||||
/**
|
||||
* @type {(e: any) => void}
|
||||
*/
|
||||
sax.onerror = (e) => {
|
||||
const error = new SvgoParserError(
|
||||
e.reason,
|
||||
e.line + 1,
|
||||
@ -179,3 +256,4 @@ module.exports = function (data, from) {
|
||||
sax.write(data).close();
|
||||
return root;
|
||||
};
|
||||
exports.parseSvg = parseSvg;
|
@ -7,7 +7,7 @@
|
||||
|
||||
const { collectStylesheet, computeStyle } = require('./style.js');
|
||||
const { visit } = require('./xast.js');
|
||||
const svg2js = require('./svgo/svg2js.js');
|
||||
const { parseSvg } = require('./parser.js');
|
||||
|
||||
/**
|
||||
* @type {(node: XastParent, id: string) => XastElement}
|
||||
@ -33,7 +33,7 @@ const getElementById = (node, id) => {
|
||||
};
|
||||
|
||||
it('collects styles', () => {
|
||||
const root = svg2js(`
|
||||
const root = parseSvg(`
|
||||
<svg>
|
||||
<rect id="class" class="a" />
|
||||
<rect id="two-classes" class="b a" />
|
||||
@ -88,7 +88,7 @@ it('collects styles', () => {
|
||||
});
|
||||
|
||||
it('prioritizes different kinds of styles', () => {
|
||||
const root = svg2js(`
|
||||
const root = parseSvg(`
|
||||
<svg>
|
||||
<style>
|
||||
g > .a { fill: red; }
|
||||
@ -130,7 +130,7 @@ it('prioritizes different kinds of styles', () => {
|
||||
});
|
||||
|
||||
it('prioritizes important styles', () => {
|
||||
const root = svg2js(`
|
||||
const root = parseSvg(`
|
||||
<svg>
|
||||
<style>
|
||||
g > .a { fill: red; }
|
||||
@ -166,7 +166,7 @@ it('prioritizes important styles', () => {
|
||||
});
|
||||
|
||||
it('treats at-rules and pseudo-classes as dynamic styles', () => {
|
||||
const root = svg2js(`
|
||||
const root = parseSvg(`
|
||||
<svg>
|
||||
<style>
|
||||
@media screen {
|
||||
@ -208,7 +208,7 @@ it('treats at-rules and pseudo-classes as dynamic styles', () => {
|
||||
});
|
||||
|
||||
it('considers <style> media attribute', () => {
|
||||
const root = svg2js(`
|
||||
const root = parseSvg(`
|
||||
<svg>
|
||||
<style media="print">
|
||||
@media screen {
|
||||
@ -241,7 +241,7 @@ it('considers <style> media attribute', () => {
|
||||
});
|
||||
|
||||
it('ignores <style> with invalid type', () => {
|
||||
const root = svg2js(`
|
||||
const root = parseSvg(`
|
||||
<svg>
|
||||
<style type="text/css">
|
||||
.a { fill: red; }
|
||||
@ -270,7 +270,7 @@ it('ignores <style> with invalid type', () => {
|
||||
});
|
||||
|
||||
it('ignores keyframes atrule', () => {
|
||||
const root = svg2js(`
|
||||
const root = parseSvg(`
|
||||
<svg>
|
||||
<style>
|
||||
.a {
|
||||
|
@ -5,7 +5,7 @@ const {
|
||||
resolvePluginConfig,
|
||||
extendDefaultPlugins,
|
||||
} = require('./svgo/config.js');
|
||||
const svg2js = require('./svgo/svg2js.js');
|
||||
const { parseSvg } = require('./parser.js');
|
||||
const js2svg = require('./svgo/js2svg.js');
|
||||
const { invokePlugins } = require('./svgo/plugins.js');
|
||||
const JSAPI = require('./svgo/jsAPI.js');
|
||||
@ -31,7 +31,7 @@ const optimize = (input, config) => {
|
||||
info.multipassCount = i;
|
||||
// TODO throw this error in v3
|
||||
try {
|
||||
svgjs = svg2js(input, config.path);
|
||||
svgjs = parseSvg(input, config.path);
|
||||
} catch (error) {
|
||||
return { error: error.toString(), modernError: error };
|
||||
}
|
||||
|
2
lib/svgo/svg2js.d.ts
vendored
2
lib/svgo/svg2js.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
declare let obj: any;
|
||||
export = obj;
|
10
lib/types.ts
10
lib/types.ts
@ -1,4 +1,4 @@
|
||||
type XastDoctype = {
|
||||
export type XastDoctype = {
|
||||
type: 'doctype';
|
||||
name: string;
|
||||
data: {
|
||||
@ -6,23 +6,23 @@ type XastDoctype = {
|
||||
};
|
||||
};
|
||||
|
||||
type XastInstruction = {
|
||||
export type XastInstruction = {
|
||||
type: 'instruction';
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type XastComment = {
|
||||
export type XastComment = {
|
||||
type: 'comment';
|
||||
value: string;
|
||||
};
|
||||
|
||||
type XastCdata = {
|
||||
export type XastCdata = {
|
||||
type: 'cdata';
|
||||
value: string;
|
||||
};
|
||||
|
||||
type XastText = {
|
||||
export type XastText = {
|
||||
type: 'text';
|
||||
value: string;
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ const PATH = require('path');
|
||||
const JSAPI = require('../../lib/svgo/jsAPI');
|
||||
const CSSClassList = require('../../lib/svgo/css-class-list');
|
||||
const CSSStyleDeclaration = require('../../lib/svgo/css-style-declaration');
|
||||
const SVG2JS = require('../../lib/svgo/svg2js');
|
||||
const { parseSvg } = require('../../lib/parser.js');
|
||||
|
||||
describe('svg2js', function () {
|
||||
describe('working svg', function () {
|
||||
@ -18,7 +18,7 @@ describe('svg2js', function () {
|
||||
throw err;
|
||||
}
|
||||
|
||||
root = SVG2JS(data);
|
||||
root = parseSvg(data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -11,6 +11,7 @@
|
||||
},
|
||||
"include": [
|
||||
"plugins/**/*",
|
||||
"lib/svg-parser.js",
|
||||
"lib/xast.test.js",
|
||||
"lib/path.test.js",
|
||||
"lib/style.test.js",
|
||||
|
Reference in New Issue
Block a user