Files
2026-05-27 23:29:53 +01:00

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 an SvgNode tree; no DOM dependency; handles element, text, comment, CDATA, and doctype nodes
  • serializeSvg(node) — converts an SvgNode tree 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 (&amp;, &lt;, &gt;, &quot;, &apos;, 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.