4.0 KiB
svg-parser — SVG Parser and Serializer
(no submodules)
Purpose
parseSvg and serializeSvg are the low-level I/O layer for @suaveplan/compress-svg. The parser is a pure-TypeScript push parser that processes SVG strings character-by-character without requiring DOMParser, making it safe to use in Node.js, Bun, edge runtimes, and workers. serializeSvg produces compact, whitespace-free output optimised for minimum byte count.
Features
parseSvg(svg)— converts an SVG string to anSvgNodetree; no DOM dependency; handles element, text, comment, CDATA, and doctype nodesserializeSvg(node)— converts anSvgNodetree back to a compact SVG string; self-closing tags for empty elements- Handles XML declarations (
<?xml ...?>) by silently skipping them (irrelevant to the SVG tree) - Decodes HTML/XML character entities (
&,<,>,",', numeric&#...;and&#x...;) - Throws
SvgCompressError(SVG_COMPRESS_PARSE_FAILED)with a descriptive message on malformed input - Supports both double-quoted and single-quoted attribute values
- Supports boolean (value-less) attributes
Usage: Basic Example
import { parseSvg, serializeSvg } from '@suaveplan/compress-svg';
const node = parseSvg('<svg xmlns="http://www.w3.org/2000/svg"><circle r="40"/></svg>');
console.log(node.name); // 'svg'
console.log(node.children![0]!.name); // 'circle'
const output = serializeSvg(node);
// '<svg xmlns="http://www.w3.org/2000/svg"><circle r="40"/></svg>'
Usage: Advanced Example
import { parseSvg, serializeSvg } from '@suaveplan/compress-svg';
import { SvgCompressError, SVG_COMPRESS_CODES } from '@suaveplan/compress-svg';
function safeRoundTrip(svg: string): string | null {
try {
return serializeSvg(parseSvg(svg));
} catch (e) {
if (e instanceof SvgCompressError && e.code === SVG_COMPRESS_CODES.PARSE_FAILED) {
return null;
}
throw e;
}
}
// Handles comments, CDATA, and nested structures
const svg = `
<?xml version="1.0"?>
<!-- icon -->
<svg><![CDATA[raw]]><g><rect/></g></svg>
`;
const output = safeRoundTrip(svg.trim());
// '<svg><![CDATA[raw]]><g><rect/></g></svg>'
API Reference
parseSvg(svg: string): SvgNode
Parses an SVG string into a tree of SvgNode objects. The returned node is always the root element. XML declarations are silently skipped. Doctype nodes are captured as children of the virtual root but not returned as the root element — the root is always the first element node.
Throws: SvgCompressError(SVG_COMPRESS_PARSE_FAILED) when:
- Input is empty or all whitespace
- A tag is left unclosed
- A closing tag does not match the open tag
- There is no root element in the input
serializeSvg(node: SvgNode): string
Serializes a single SvgNode (and all its descendants) to a compact SVG string. Empty element nodes (no children) are serialized as self-closing (<rect/>). No whitespace or newlines are added between elements.
| Node type | Output format |
|---|---|
element |
<name attrs>children</name> or <name attrs/> |
text |
Raw text value |
comment |
<!--value--> |
cdata |
<![CDATA[value]]> |
doctype |
<!DOCTYPE value> |
Implementation Notes
The parser is a single-pass, index-advancing loop over the input string. It maintains an explicit stack of open elements rather than recursing, so deeply nested SVGs do not risk stack overflow. Attribute parsing uses a single regex that correctly handles double-quoted, single-quoted, and unquoted values. The findTagEnd helper handles > characters inside attribute values by tracking quote state. Entity decoding covers the five XML predefined entities plus numeric decimal and hexadecimal code points. The serializer uses a switch statement over node type, making it branch-exhaustive with TypeScript's never check at compile time.