npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@xwordly/xword-parser

v1.0.5

Published

Fast, type-safe TypeScript library for parsing crossword puzzles (PUZ, iPUZ, JPZ, XD)

Readme

xword-parser

A TypeScript library for parsing popular crossword puzzle file formats into a unified, easy-to-use data structure.

NPM Version Test codecov

Features

  • Format Support: Parse PUZ, iPUZ, JPZ, and XD crossword formats
  • Unified Data Model: All formats are converted to a common representation
  • Type-Safe: Full TypeScript support with comprehensive type definitions
  • Lightweight: Minimal runtime dependencies (only fast-xml-parser for JPZ support)
  • Error Handling: Robust error handling with format-specific error classes
  • Lazy Loading: Optional lazy-loading support to reduce bundle size
  • Format Detection: Automatic format detection with optional filename hints
  • Encoding Support: Configurable character encoding for text-based formats

Installation

npm install @xwordly/xword-parser

or

yarn add @xwordly/xword-parser

or

pnpm add @xwordly/xword-parser

Usage

Basic Example

import { parse } from '@xwordly/xword-parser';
import { readFileSync } from 'fs';

// Parse from file contents (auto-detects format)
const fileContent = readFileSync('puzzle.puz');
const puzzle = parse(fileContent);

console.log(puzzle.title);
console.log(puzzle.author);
console.log(puzzle.grid.width, 'x', puzzle.grid.height);

With Format Hints

Providing a filename helps with faster and more accurate format detection:

import { parse } from '@xwordly/xword-parser';

// Provide filename hint for better format detection
const puzzle = parse(fileContent, { 
  filename: 'crossword.puz' 
});

// Specify encoding for text-based formats
const puzzle = parse(fileContent, { 
  filename: 'puzzle.ipuz',
  encoding: 'latin1' // default is 'utf-8'
});

// Limit maximum grid size
const puzzle = parse(fileContent, {
  maxGridSize: { width: 50, height: 50 }
});

Lazy Loading

For smaller bundle sizes in web applications, use the lazy-loading version:

import { parseLazy } from '@xwordly/xword-parser/lazy';

// Parsers are loaded dynamically only when needed
const puzzle = await parseLazy(fileContent);

Format-Specific Parsers

If you know the format in advance, you can use format-specific parsers:

import { 
  parseIpuz, 
  parsePuz, 
  parseJpz, 
  parseXd 
} from '@xwordly/xword-parser';

// Use specific parser for known format
const ipuzPuzzle = parseIpuz(jsonString);
const puzPuzzle = parsePuz(binaryBuffer);
const jpzPuzzle = parseJpz(xmlString);
const xdPuzzle = parseXd(textString);

Parsing Different Formats

The library automatically detects the format based on the file contents:

import { parse } from '@xwordly/xword-parser';

// Parse PUZ format (binary)
const puzData = await fetch('https://example.com/puzzle.puz')
  .then(res => res.arrayBuffer());
const puzPuzzle = parse(puzData);

// Parse iPUZ format (JSON)
const ipuzData = await fetch('https://example.com/puzzle.ipuz')
  .then(res => res.text());
const ipuzPuzzle = parse(ipuzData);

// Parse JPZ format (XML)
const jpzData = await fetch('https://example.com/puzzle.jpz')
  .then(res => res.text());
const jpzPuzzle = parse(jpzData);

// Parse XD format (text)
const xdData = await fetch('https://example.com/puzzle.xd')
  .then(res => res.text());
const xdPuzzle = parse(xdData);

API Reference

Main Functions

parse(data: string | Buffer | ArrayBuffer, options?: ParseOptions): Puzzle

Parses crossword puzzle data from various formats. This is a pure, synchronous function.

Parameters:

  • data: The puzzle data as a string (for text formats) or binary data (for PUZ format)
  • options (optional):
    • filename: Hint for format detection (e.g., "puzzle.puz")
    • encoding: Character encoding for text formats (default: "utf-8")
    • maxGridSize: Maximum allowed grid dimensions (e.g., {width: 50, height: 50})

Returns: A Puzzle object

Throws:

  • FormatDetectionError if the format cannot be detected
  • ParseError for general parsing errors
  • IpuzParseError, PuzParseError, JpzParseError, or XdParseError for format-specific errors
  • UnsupportedPuzzleTypeError if the puzzle type is not a crossword

parseLazy(data: string | Buffer | ArrayBuffer, options?: ParseOptions): Promise<Puzzle>

Lazy-loading version of parse() that loads parsers dynamically.

Parameters: Same as parse()

Returns: A Promise that resolves to a Puzzle object

Throws: Same errors as parse()

Format-Specific Functions

Each format has its own parse and convert functions:

  • parseIpuz(content: string | Buffer, options?: ParseOptions): IpuzPuzzle
  • parsePuz(data: Buffer | ArrayBuffer | Uint8Array | string, options?: ParseOptions): PuzPuzzle
  • parseJpz(content: string, options?: ParseOptions): JpzPuzzle
  • parseXd(content: string, options?: ParseOptions): XdPuzzle

And corresponding converters:

  • convertIpuzToUnified(puzzle: IpuzPuzzle): Puzzle
  • convertPuzToUnified(puzzle: PuzPuzzle): Puzzle
  • convertJpzToUnified(puzzle: JpzPuzzle): Puzzle
  • convertXdToUnified(puzzle: XdPuzzle): Puzzle

Supported Formats

PUZ Format

The .puz format is a binary format created by Across Lite. It's one of the most common crossword formats and includes:

  • Grid layout and solutions
  • Across and Down clues
  • Metadata (title, author, copyright)
  • Optional features like rebuses and circles

iPUZ Format

The .ipuz format is a JSON-based open standard that supports:

  • Standard crosswords
  • Variety puzzles (cryptics, acrostics, etc.)
  • Rich metadata
  • Styled cells and advanced features
  • Unicode support

JPZ Format

The .jpz format is an XML-based format used by Crossword Compiler. Features include:

  • Complete puzzle data
  • Timer and solving information
  • Publishing metadata
  • Support for various puzzle types

XD Format

The .xd format is a simple text-based format that's human-readable and includes:

  • Grid representation using text
  • Simple clue format
  • Basic metadata
  • Easy to create and edit manually

Data Types

Puzzle Interface

interface Puzzle {
  title?: string;
  author?: string;
  copyright?: string;
  notes?: string;
  date?: string;
  grid: Grid;
  clues: Clues;
  rebusTable?: Map<number, string>;
  additionalProperties?: Record<string, unknown>;
}

Grid and Cell Types

interface Grid {
  width: number;
  height: number;
  cells: Cell[][];
}

interface Cell {
  solution?: string;
  number?: number;
  isBlack: boolean;
  isCircled?: boolean;
  hasRebus?: boolean;
  rebusKey?: number;
}

Clue Types

interface Clues {
  across: Clue[];
  down: Clue[];
}

interface Clue {
  number: number;
  text: string;
}

Error Handling

The library provides specific error classes for different scenarios:

  • ParseError: Base class for all parsing errors
  • FormatDetectionError: Unable to detect the puzzle format
  • IpuzParseError: iPUZ-specific parsing errors
  • PuzParseError: PUZ-specific parsing errors
  • JpzParseError: JPZ-specific parsing errors
  • XdParseError: XD-specific parsing errors
  • UnsupportedPuzzleTypeError: When a file contains a non-crossword puzzle
  • InvalidFileError: General file format issues

All error classes extend ParseError and include error codes for programmatic handling:

try {
  const puzzle = parse(data);
} catch (error) {
  if (error instanceof IpuzParseError) {
    console.error('iPUZ parsing failed:', error.message);
    console.error('Error code:', error.code);
  }
}

Library Architecture

The library is designed with the following principles:

  1. Sans I/O: All parsers are pure functions with no side effects or file I/O
  2. Format-First Parsing: Each parser first captures all format-specific data, then converts to the unified format
  3. Type Safety: Comprehensive TypeScript types for both format-specific and unified structures
  4. Error Recovery: Smart error handling that distinguishes between format mismatches and real parsing errors
  5. Extensibility: Easy to add new formats by implementing the parser/converter pattern

Development

Building

npm run build            # Build for production
npm run dev              # Build with watch mode

Testing

npm test                 # Run tests once
npm run test:watch       # Run tests in watch mode
npm run test:coverage    # Generate coverage report

The test suite includes:

  • Unit tests for each format parser
  • Property-based testing with fast-check
  • Performance benchmarks
  • Integration tests with real puzzle files

Linting & Formatting

npm run lint             # Check for linting errors
npm run lint:fix         # Fix linting errors
npm run format           # Format code with Prettier
npm run typecheck        # Type-check without building

Requirements

  • Node.js >= 18
  • TypeScript >= 5.3 (for development)