Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
# svg-parser — SVG Parser and Serializer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
```typescript
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
```typescript
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.