rtf-parser
A streaming Node.js parser that turns RTF documents into a structured tree of paragraphs and styled text spans.
Repository Health
Technical Analysis
rtf-parser is a small Node.js library for reading RTF (Rich Text Format) input and converting it into a plain JavaScript object model — an RTFDocument made up of RTFParagraph and RTFSpan nodes carrying font, color, alignment, and character-set information. It exists purely as a building block: the parsed document isn’t useful on its own, but gives downstream tools a stable structure to walk when converting RTF into HTML, plain text, or another format.
The library is built as two piped Node streams — a tokenizing Transform stream that turns raw RTF bytes into typed tokens, and an interpreting Writable stream that tracks nested {...} group scopes and dispatches each RTF control word to build up the document tree. It supports a wide range of RTF 1.x features (multiple code pages, Unicode escapes, bold/italic/underline/strikethrough, alignment, indentation, font and color tables) while gracefully degrading on unsupported constructs like tables and stylesheets.
What You Get
- A streaming, two-stage tokenizer + interpreter architecture that can parse RTF incrementally from a Readable stream, a string, or piped input
- An RTFDocument/RTFParagraph/RTFSpan object model with cascading style inheritance (bold, italic, underline, strikethrough, alignment, indentation, sub/superscript)
- Character-set handling for 25+ code pages plus Unicode escape sequences, via iconv-lite decoding
- Font table and color table parsing so spans carry resolved font names and RGB colors rather than raw indices
- Graceful degradation on unsupported RTF constructs (tables, stylesheets) instead of hard failures
Common Use Cases
- Converting legacy RTF documents (from Word, email clients, or older desktop apps) into HTML for web display
- Extracting plain text from RTF attachments in an email or document-processing pipeline
- Building a custom RTF-to-Markdown or RTF-to-JSON converter on top of the parsed document tree
- Migrating archives of RTF-formatted content into a structured CMS or database
Under The Hood
Architecture
The library is a two-stage streaming pipeline. rtf-parser.js implements RTFParser, a Transform stream that walks raw RTF text character-by-character through a small finite-state machine (parseText, parseEscapes, parseControlSymbol, parseControlWord, parseControlWordParam, parseHexChar), emitting typed tokens (text, control-word, group-start, group-end, hexchar, ignorable, end-paragraph, error). Those tokens feed into RTFInterpreter (rtf-interpreter.js), a Writable stream that maintains a groupStack of RTFGroup instances mirroring RTF’s nested {...} scoping, dynamically dispatching each control word to a ctrl$<name> method to mutate document state. The data model (rtf-document.js, rtf-paragraph.js, rtf-span.js, rtf-group.js) uses a parent-pointer chain so get/getStyle cascade unset properties up through enclosing groups to the document root, and index.js composes the two streams behind simple parse/parse.string/parse.stream entry points with shared error handling. Style resolution logic is spread across RTFGroup.addContent, RTFDocument.addContent, and RTFInterpreter.finisher, so changing the cascading-style model would touch all three.
Tech Stack
Plain CommonJS Node.js with no build step or TypeScript. Runtime dependencies are minimal: iconv-lite (^0.4.15) decodes hex-escaped and code-page text into proper Unicode, and readable-stream (^2.2.2) provides a consistent Stream implementation across older Node versions. Dev tooling — standard (zero-config linting run via pretest), tap (referenced by the test script), weallbehave/weallcontribute (community doc scaffolding), and standard-version (changelog/release automation) — reflects a mid-2010s Node library setup; there’s no CI config or bundler present.
Code Quality
No test/ directory exists in the repository despite package.json’s test script invoking tap test/, so there is no verifiable automated test coverage in this snapshot. Control-word dispatch uses string-built method names ('ctrl$' + name) rather than an explicit switch or map, which is compact but harder to statically audit. Error handling is inconsistent: most errors flow through proper error events on the parser/interpreter streams, but one code path (_write in rtf-interpreter.js) calls process.emit('error', ...) for unknown token types — process isn’t the stream’s own EventEmitter, so that particular failure class is effectively swallowed rather than surfaced to callers. No type annotations (plain JS, no JSDoc/TypeScript), though standard’s lint rules are enforced via pretest.
API Design
The public surface is small and pragmatic: parse(cb) returns a writable stream you can pipe into, parse.string(str, cb) and parse.stream(readableStream, cb) cover the common cases directly, and the resulting RTFDocument is a plain object with content/style arrays rather than a heavy class hierarchy — easy to walk without learning a bespoke API. The trade-offs are dated ergonomics: callback-only (no Promise or async-iterator surface), no shipped TypeScript types, and documentation limited to the README’s feature list with no dedicated examples directory or docs site.