mirror of
https://github.com/iconify/iconify.git
synced 2024-11-08 14:20:57 +00:00
feat: prettifySVG for utils package
This commit is contained in:
parent
b3c0a9b34c
commit
3717f1f062
@ -372,6 +372,11 @@
|
|||||||
"require": "./lib/svg/parse.cjs",
|
"require": "./lib/svg/parse.cjs",
|
||||||
"import": "./lib/svg/parse.mjs"
|
"import": "./lib/svg/parse.mjs"
|
||||||
},
|
},
|
||||||
|
"./lib/svg/pretty": {
|
||||||
|
"types": "./lib/svg/pretty.d.ts",
|
||||||
|
"require": "./lib/svg/pretty.cjs",
|
||||||
|
"import": "./lib/svg/pretty.mjs"
|
||||||
|
},
|
||||||
"./lib/svg/size": {
|
"./lib/svg/size": {
|
||||||
"types": "./lib/svg/size.d.ts",
|
"types": "./lib/svg/size.d.ts",
|
||||||
"require": "./lib/svg/size.cjs",
|
"require": "./lib/svg/size.cjs",
|
||||||
|
@ -58,6 +58,7 @@ export { replaceIDs } from './svg/id';
|
|||||||
export { calculateSize } from './svg/size';
|
export { calculateSize } from './svg/size';
|
||||||
export { encodeSvgForCss } from './svg/encode-svg-for-css';
|
export { encodeSvgForCss } from './svg/encode-svg-for-css';
|
||||||
export { trimSVG } from './svg/trim';
|
export { trimSVG } from './svg/trim';
|
||||||
|
export { prettifySVG } from './svg/pretty';
|
||||||
export { iconToHTML } from './svg/html';
|
export { iconToHTML } from './svg/html';
|
||||||
export { svgToURL, svgToData } from './svg/url';
|
export { svgToURL, svgToData } from './svg/url';
|
||||||
export { cleanUpInnerHTML } from './svg/inner-html';
|
export { cleanUpInnerHTML } from './svg/inner-html';
|
||||||
|
81
packages/utils/src/svg/pretty.ts
Normal file
81
packages/utils/src/svg/pretty.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* Prettify SVG
|
||||||
|
*/
|
||||||
|
export function prettifySVG(
|
||||||
|
content: string,
|
||||||
|
tab = '\t',
|
||||||
|
depth = 0
|
||||||
|
): string | null {
|
||||||
|
let result = '';
|
||||||
|
let level = 0;
|
||||||
|
|
||||||
|
while (content.length > 0) {
|
||||||
|
const openIndex = content.indexOf('<');
|
||||||
|
let closeIndex = content.indexOf('>');
|
||||||
|
|
||||||
|
// Should have both '<' and '>'
|
||||||
|
if (openIndex === -1 && closeIndex === -1) {
|
||||||
|
// Done
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openIndex === -1 || closeIndex === -1 || closeIndex < openIndex) {
|
||||||
|
// Fail: either incomplete code or unescaped '<' or '>'
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add content before tag as is
|
||||||
|
const text = content.slice(0, openIndex);
|
||||||
|
const trimmedText = text.trim();
|
||||||
|
if (trimmedText.length) {
|
||||||
|
if (text.trimStart() !== text && text.trimEnd() !== text) {
|
||||||
|
// Spacing is present on both sides: can safely add spacing
|
||||||
|
result += trimmedText + '\n' + tab.repeat(level + depth);
|
||||||
|
} else {
|
||||||
|
// Add as is
|
||||||
|
result = result.trim() + text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content = content.slice(openIndex);
|
||||||
|
closeIndex -= openIndex;
|
||||||
|
|
||||||
|
// Check for bad tag
|
||||||
|
const nextOpenIndex = content.indexOf('<', 1);
|
||||||
|
if (nextOpenIndex !== -1 && nextOpenIndex < closeIndex) {
|
||||||
|
// Fail: unexpected '<'
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tag
|
||||||
|
const lastChar = content.slice(closeIndex - 1, closeIndex);
|
||||||
|
const isClosing = content.slice(0, 2) === '</';
|
||||||
|
const isSelfClosing = lastChar === '/' || lastChar === '?';
|
||||||
|
if (isClosing && isSelfClosing) {
|
||||||
|
// Bad code
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tag
|
||||||
|
if (isClosing && tab.length) {
|
||||||
|
// Remove one level
|
||||||
|
if (result.slice(0 - tab.length) === tab) {
|
||||||
|
result = result.slice(0, result.length - tab.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += content.slice(0, closeIndex + 1);
|
||||||
|
content = content.slice(closeIndex + 1);
|
||||||
|
|
||||||
|
// Prepare for next tag
|
||||||
|
if (isClosing) {
|
||||||
|
level--;
|
||||||
|
if (level < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (!isSelfClosing) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
result += '\n' + tab.repeat(level + depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return level === 0 ? result : null;
|
||||||
|
}
|
71
packages/utils/tests/prettify-svg-test.ts
Normal file
71
packages/utils/tests/prettify-svg-test.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { prettifySVG } from '../lib/svg/pretty';
|
||||||
|
|
||||||
|
describe('Prettify SVG', () => {
|
||||||
|
test('Basic XML', () => {
|
||||||
|
expect(
|
||||||
|
prettifySVG(
|
||||||
|
'<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg"><circle cx="60" cy="60" r="50"></circle></svg>'
|
||||||
|
)
|
||||||
|
).toBe(
|
||||||
|
'<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">\n\t<circle cx="60" cy="60" r="50">\n\t</circle>\n</svg>\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Self closing tag
|
||||||
|
expect(
|
||||||
|
prettifySVG(
|
||||||
|
'<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg"><circle cx="60" cy="60" r="50"/></svg>'
|
||||||
|
)
|
||||||
|
).toBe(
|
||||||
|
'<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">\n\t<circle cx="60" cy="60" r="50"/>\n</svg>\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
// With xml tag
|
||||||
|
expect(
|
||||||
|
prettifySVG(
|
||||||
|
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">\n<circle cx="60" cy="60" r="50"/>\n</svg>\n\t'
|
||||||
|
)
|
||||||
|
).toBe(
|
||||||
|
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">\n\t<circle cx="60" cy="60" r="50"/>\n</svg>\n'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Text', () => {
|
||||||
|
expect(prettifySVG('<svg><title>Trimmed Text</title></svg>')).toBe(
|
||||||
|
'<svg>\n\t<title>Trimmed Text</title>\n</svg>\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
prettifySVG('<svg><title> Right Trimmed Text</title></svg>')
|
||||||
|
).toBe('<svg>\n\t<title> Right Trimmed Text</title>\n</svg>\n');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
prettifySVG('<svg><title>Left Trimmed Text </title></svg>')
|
||||||
|
).toBe('<svg>\n\t<title>Left Trimmed Text </title>\n</svg>\n');
|
||||||
|
|
||||||
|
expect(prettifySVG('<svg><title> Text </title></svg>')).toBe(
|
||||||
|
'<svg>\n\t<title>\n\t\tText\n\t</title>\n</svg>\n'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Bad code', () => {
|
||||||
|
expect(prettifySVG('<svg><title>Incomplete SVG</title>')).toBeNull();
|
||||||
|
expect(
|
||||||
|
prettifySVG('<svg><title>Incomplete SVG</title>/svg>')
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
prettifySVG('<svg><title>Incomplete SVG</title></g></svg>')
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
prettifySVG('<svg><title>Unescaped < text</title></svg>')
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
prettifySVG('<svg><title>Unescaped > text</title></svg>')
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
prettifySVG('<svg><title foo=">">Unescaped attr</title></svg>')
|
||||||
|
).toBeNull();
|
||||||
|
expect(
|
||||||
|
prettifySVG('<svg><title foo="<">Unescaped attr</title></svg>')
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user