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 🙏

© 2026 – Pkg Stats / Ryan Hefner

doc-table-extract

v0.3.0

Published

Extract tables from HTML, Markdown, CSV, and plain text as structured JSON

Readme

doc-table-extract

Extract tables from HTML, Markdown, CSV, TSV, and plain text as structured JSON. Zero runtime dependencies.

npm version npm downloads license node TypeScript


Description

doc-table-extract is a lightweight, dependency-free TypeScript library that detects and extracts tabular data from multiple document formats. It auto-detects the input format, parses tables into a uniform ExtractedTable structure with headers, data rows, cell-level metadata, and column type inference, then optionally exports the result to JSON, CSV, Markdown, or HTML.

Key design decisions:

  • Zero runtime dependencies. The package has no production dependencies. All parsing -- HTML tag matching, CSV field splitting, Markdown pipe parsing, ASCII border detection -- is implemented from scratch using regex and string processing.
  • Format-agnostic output. Regardless of whether the input is an HTML <table>, a GFM pipe table, a comma-separated file, or a space-aligned text table, the output is always the same ExtractedTable interface with headers, rows, cells, metadata, and columnTypes.
  • Auto-detection. The extract() function inspects the input string and determines the format automatically. You can override this with an explicit format option.
  • Cell-level detail. Every cell carries its row index, column index, row span, column span, and header flag, enabling faithful round-trip export back to HTML with merged cells intact.

Installation

npm install doc-table-extract

Requires Node.js >= 18.


Quick Start

import { extract } from 'doc-table-extract';

const tables = extract(`
| Name  | Age | City |
|-------|-----|------|
| Alice | 30  | NYC  |
| Bob   | 25  | LA   |
`);

console.log(tables[0].headers);     // ['Name', 'Age', 'City']
console.log(tables[0].rows);        // [['Alice', '30', 'NYC'], ['Bob', '25', 'LA']]
console.log(tables[0].columnTypes); // ['string', 'number', 'string']

Features

  • HTML extraction -- Parses <table> elements including <thead>, <tbody>, <th>, <td>, with full colspan and rowspan support. Handles nested tables (extracts outermost), multiple tables per document, HTML entity decoding (&amp;, &lt;, &gt;, &quot;, &#39;, &nbsp;, numeric entities), and inline tag stripping (<b>, <i>, <a>, <span>).
  • Markdown extraction -- Parses GFM pipe tables with alignment indicators (:---, :---:, ---:). Handles escaped pipes (\|), multiple tables per document, empty cells, and rows with fewer columns than the header.
  • CSV/TSV extraction -- RFC 4180 compliant parsing with quoted fields, escaped double quotes, and embedded newlines. Auto-detects delimiter from comma, tab, semicolon, and pipe. Handles CRLF and CR line endings, uneven row lengths (short rows are padded), and multi-level headers.
  • Plain text extraction -- Detects ASCII bordered tables (+---+---+), box-drawing character tables (Unicode ---, |, corner characters), and space/tab-aligned columnar data. Handles multiple tables separated by blank lines.
  • Format auto-detection -- The detectFormat() function inspects the input and returns the detected format: html, markdown, csv, tsv, text, or unknown.
  • Column type inference -- After extraction, the normalizer analyzes each column's values and classifies them as string, number, date, or mixed. Recognizes currency ($1,000), percentages (50%), and common date formats (YYYY-MM-DD, MM/DD/YYYY, Jan 15, 2024, Q1 2024).
  • Normalization -- Trims and collapses whitespace in all cells. Optionally preserves newlines within cells via the preserveCellNewlines option.
  • Confidence scoring -- Each extracted table includes a confidence score (0.0--1.0). HTML tables receive 0.95, Markdown tables 0.9, CSV/TSV 0.85, and plain text 0.7. Filter low-confidence results with the confidenceThreshold option.
  • Multi-format export -- Export any ExtractedTable to CSV (RFC 4180), GFM Markdown (with optional column alignment), HTML (<table> with rowspan/colspan attributes), or JSON ({ headers, rows }).
  • Extraction timing -- Every result includes metadata.extractionMs reporting the extraction duration in milliseconds.
  • TypeScript strict mode -- Full type exports, declaration maps, and source maps. Compiled with strict: true.

API Reference

extract(input, options?)

The primary entry point. Auto-detects the input format and extracts all tables.

function extract(input: string, options?: ExtractOptions): ExtractedTable[]

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | input | string | The document content to parse | | options | ExtractOptions | Optional extraction configuration |

Returns: An array of ExtractedTable objects. Returns an empty array if no tables are found or all tables fall below the confidence threshold.

Behavior:

  1. Detects the input format (or uses options.format if provided).
  2. Delegates to the format-specific extractor.
  3. Normalizes all tables (whitespace cleaning, column type detection).
  4. Filters tables below options.confidenceThreshold.
  5. Records extraction timing in metadata.extractionMs.

For unknown format, all extractors are tried in sequence (HTML, Markdown, CSV, text) until one produces results.

import { extract } from 'doc-table-extract';

// Auto-detect
const tables = extract(inputString);

// Explicit format with options
const tables = extract(csvString, {
  format: 'csv',
  headerRows: 1,
  confidenceThreshold: 0.5,
});

detectFormat(input)

Detect the input format without extracting tables.

function detectFormat(input: string): InputFormat

Returns: One of 'html', 'markdown', 'csv', 'tsv', 'text', or 'unknown'.

Detection rules (evaluated in order):

  1. HTML -- Contains <table followed by > or whitespace.
  2. Markdown -- Contains pipe-delimited rows with a separator line matching |---|.
  3. TSV -- At least 80% of non-empty lines contain tab characters.
  4. CSV -- At least 2 lines with a consistent comma count (>= 1 comma per line).
  5. Text -- At least 2 horizontal separator lines (+---+ or box-drawing), or at least 60% of lines have multi-space gaps between content.
  6. Unknown -- None of the above matched.
import { detectFormat } from 'doc-table-extract';

detectFormat('<table><tr><td>x</td></tr></table>'); // 'html'
detectFormat('| A | B |\n|---|---|\n| 1 | 2 |');     // 'markdown'
detectFormat('a,b,c\n1,2,3');                         // 'csv'
detectFormat('a\tb\tc\n1\t2\t3');                     // 'tsv'

extractFromHtml(html, options?)

Extract tables from HTML content.

function extractFromHtml(html: string, options?: ExtractOptions): ExtractedTable[]

Finds all top-level <table> elements (nested tables are treated as part of the outer table's cell content). Supports:

  • <thead> / <tbody> sections
  • <th> and <td> cells
  • colspan and rowspan attributes
  • HTML entity decoding (&amp;, &lt;, &gt;, &quot;, &#39;, &nbsp;, &#NNN;, &#xHHH;)
  • Inline tag stripping (preserves text content)
  • Multi-level header flattening (with headerRows > 1)

Confidence: 0.95

import { extractFromHtml } from 'doc-table-extract';

const tables = extractFromHtml(`
  <table>
    <thead><tr><th>Name</th><th>Age</th></tr></thead>
    <tbody>
      <tr><td>Alice</td><td>30</td></tr>
      <tr><td>Bob</td><td>25</td></tr>
    </tbody>
  </table>
`);

extractFromMarkdown(markdown, options?)

Extract tables from Markdown content (GFM pipe tables).

function extractFromMarkdown(markdown: string, options?: ExtractOptions): ExtractedTable[]

Parses GFM pipe table syntax. A valid table requires a header row, a separator row (e.g., |---|), and zero or more data rows. Supports:

  • Alignment indicators (:---, :---:, ---:)
  • Escaped pipes (\|) in cell content
  • Multiple tables in one document
  • Rows with fewer columns than the header (padded with empty strings)

Confidence: 0.9

import { extractFromMarkdown } from 'doc-table-extract';

const tables = extractFromMarkdown(`
| Product | Price  |
|---------|-------:|
| Widget  | $9.99  |
| Gadget  | $19.99 |
`);

extractFromCsv(text, options?)

Extract a table from CSV or TSV content.

function extractFromCsv(text: string, options?: ExtractOptions): ExtractedTable[]

RFC 4180 compliant parser. Supports:

  • Auto-detection of delimiter (comma, tab, semicolon, pipe)
  • Quoted fields with embedded commas, newlines, and double quotes
  • Escaped double quotes ("")
  • CRLF and CR line endings
  • Uneven row lengths (short rows padded with empty strings)
  • Header row auto-detection (compares first row's numeric content against subsequent rows)

Confidence: 0.85

import { extractFromCsv } from 'doc-table-extract';

const tables = extractFromCsv('Name,Age\nAlice,30\nBob,25');

// Explicit delimiter
const tables = extractFromCsv(data, { delimiter: ';' });

// Tab-separated
const tables = extractFromCsv(tsvData, { delimiter: '\t' });

extractFromText(text, options?)

Extract tables from plain text content.

function extractFromText(text: string, options?: ExtractOptions): ExtractedTable[]

Detects two types of plain text tables:

  1. ASCII/box-drawing bordered tables -- Tables delimited by +, -, =, |, or Unicode box-drawing characters. Requires at least 2 separator lines. Column boundaries are inferred from separator character positions.

  2. Space-aligned columnar data -- Text where columns are separated by 2+ consecutive spaces. Requires at least 3 content lines and at least 2 detected columns. At least 50% of rows must have content in 2+ columns.

ASCII tables are tried first. Space-aligned detection is used as a fallback.

Confidence: 0.7

import { extractFromText } from 'doc-table-extract';

// ASCII bordered table
const tables = extractFromText(`
+-------+-----+------+
| Name  | Age | City |
+-------+-----+------+
| Alice | 30  | NYC  |
| Bob   | 25  | LA   |
+-------+-----+------+
`);

// Space-aligned columnar data
const tables = extractFromText(`
Name       Age    City
Alice       30    NYC
Bob         25    LA
Charlie     35    Chicago
`);

normalizeTable(table, preserveNewlines?)

Normalize an extracted table: clean whitespace and detect column types.

function normalizeTable(table: ExtractedTable, preserveNewlines?: boolean): ExtractedTable

Parameters:

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | table | ExtractedTable | -- | The table to normalize | | preserveNewlines | boolean | false | When true, newlines within cells are preserved; when false, all whitespace is collapsed to single spaces |

Behavior:

  • Trims leading/trailing whitespace from all headers, row values, and cell text.
  • Collapses internal whitespace (multiple spaces/tabs become a single space).
  • Detects column types from data row values (see Column Types below).
  • Returns a new ExtractedTable (does not mutate the input).

Note: extract() calls normalizeTable automatically. Use this function directly only when working with format-specific extractors.


normalizeTables(tables, preserveNewlines?)

Normalize an array of extracted tables.

function normalizeTables(tables: ExtractedTable[], preserveNewlines?: boolean): ExtractedTable[]

Convenience wrapper that calls normalizeTable on each table in the array.


toCSV(table)

Export a table to RFC 4180 CSV format.

function toCSV(table: ExtractedTable): string

Fields containing commas, double quotes, or newlines are quoted. Internal double quotes are escaped by doubling ("").

import { toCSV } from 'doc-table-extract';

const csv = toCSV(table);
// "Name,Age,City\nAlice,30,NYC\nBob,25,LA"

toMarkdown(table, alignment?)

Export a table to GFM Markdown pipe table format.

function toMarkdown(table: ExtractedTable, alignment?: boolean): string

Parameters:

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | table | ExtractedTable | -- | The table to export | | alignment | boolean | false | When true, numeric columns use right-alignment (---:) in the separator row |

Pipe characters in cell content are escaped with backslash. Newlines in cells are replaced with spaces.

import { toMarkdown } from 'doc-table-extract';

toMarkdown(table);
// | Name | Age | City |
// | --- | --- | --- |
// | Alice | 30 | NYC |

toMarkdown(table, true);
// | Name | Age | City |
// | --- | ---: | --- |
// | Alice | 30 | NYC |

toHTML(table)

Export a table to an HTML <table> element string.

function toHTML(table: ExtractedTable): string

Produces a complete <table> with <thead> and <tbody> sections. Includes rowspan and colspan attributes on cells where the source cells array specifies spans greater than 1. HTML-escapes all content (&, <, >, ", ').

import { toHTML } from 'doc-table-extract';

const html = toHTML(table);
// <table>
//   <thead>
//     <tr>
//       <th>Name</th>
//       <th>Age</th>
//     </tr>
//   </thead>
//   <tbody>
//     <tr>
//       <td>Alice</td>
//       <td>30</td>
//     </tr>
//   </tbody>
// </table>

toJSON(table)

Export a table to a plain JSON-serializable object.

function toJSON(table: ExtractedTable): { headers: string[]; rows: string[][] }

Returns a simple { headers, rows } object without cell-level metadata. Useful for serialization or passing to APIs that expect a flat table format.

import { toJSON } from 'doc-table-extract';

const json = toJSON(table);
// { headers: ['Name', 'Age'], rows: [['Alice', '30'], ['Bob', '25']] }

exportTable(table, format)

Export a table to a specified format by name.

function exportTable(table: ExtractedTable, format: ExportFormat): string

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | table | ExtractedTable | The table to export | | format | ExportFormat | One of 'json', 'csv', 'markdown', 'html' |

Returns: A string in the requested format. For 'json', returns a pretty-printed JSON string (2-space indent).

Throws: Error with message Unsupported export format: <format> if the format is not recognized.

import { exportTable } from 'doc-table-extract';

const csvString = exportTable(table, 'csv');
const mdString = exportTable(table, 'markdown');
const htmlString = exportTable(table, 'html');
const jsonString = exportTable(table, 'json');

Configuration

ExtractOptions

Options passed to extract() and all format-specific extractors.

| Option | Type | Default | Description | |--------|------|---------|-------------| | format | InputFormat | auto-detect | Input format hint. One of 'html', 'markdown', 'csv', 'tsv', 'text', 'unknown'. When omitted, detectFormat() is called automatically. | | confidenceThreshold | number | 0 | Minimum confidence score (0.0--1.0) to include a table in results. Tables below this threshold are filtered out. | | headerRows | number \| 'auto' | 'auto' | Number of header rows. 0 = no headers (generates Column 1, Column 2, etc.). A positive integer = treat that many rows as headers. 'auto' = detect automatically. | | delimiter | string | auto-detect | Delimiter for CSV/TSV parsing. When omitted, the delimiter is auto-detected from comma, tab, semicolon, and pipe. | | preserveCellNewlines | boolean | false | When true, newlines within cells are preserved during normalization. When false, all whitespace including newlines is collapsed to single spaces. |


Types

ExtractedTable

The primary output type. Every extraction function returns an array of these.

interface ExtractedTable {
  headers: string[];         // Column header labels
  rows: string[][];          // Data rows (each row is an array of cell values)
  cells: TableCell[];        // Cell-level detail with position and span info
  metadata: TableMetadata;   // Source format, dimensions, confidence, timing
  columnTypes: ColumnType[]; // Inferred type for each column
}

TableCell

Cell-level detail for an individual table cell.

interface TableCell {
  text: string;       // Text content of the cell
  rowIndex: number;   // Zero-based row index
  colIndex: number;   // Zero-based column index
  rowSpan: number;    // Number of rows this cell spans (default: 1)
  colSpan: number;    // Number of columns this cell spans (default: 1)
  isHeader: boolean;  // Whether this cell is a header cell
}

TableMetadata

Metadata about an extracted table.

interface TableMetadata {
  format: InputFormat;      // Source format that was detected
  rowCount: number;         // Number of data rows (excluding headers)
  columnCount: number;      // Number of columns
  confidence: number;       // Confidence score 0.0-1.0
  pageNumber?: number;      // Page number (1-based) if applicable
  extractionMs?: number;    // Extraction time in milliseconds
}

TableRegion

A rectangular region on a page where a table was detected.

interface TableRegion {
  x: number;           // X coordinate of the top-left corner
  y: number;           // Y coordinate of the top-left corner
  width: number;       // Width of the region
  height: number;      // Height of the region
  pageNumber: number;  // Page number (1-based)
  confidence: number;  // Confidence score 0.0-1.0
}

InputFormat

type InputFormat = 'html' | 'markdown' | 'csv' | 'tsv' | 'text' | 'unknown';

ExportFormat

type ExportFormat = 'json' | 'csv' | 'markdown' | 'html';

ColumnType

type ColumnType = 'string' | 'number' | 'date' | 'mixed';

Detection rules:

  • number -- At least 80% of non-empty values parse as numeric after stripping $, ,, %, and whitespace.
  • date -- At least 80% of non-empty values match a recognized date pattern (YYYY-MM-DD, MM/DD/YYYY, DD/MM/YYYY, 15 Jan 2024, Jan 15, 2024, 2024, Q1 2024).
  • mixed -- Column contains a mixture of numeric and non-numeric values that does not meet the 80% threshold for either.
  • string -- Default when no numeric or date patterns are detected.

Error Handling

doc-table-extract is designed to be fault-tolerant. Extraction functions do not throw on malformed input; they return an empty array instead.

extract('');                    // [] -- empty input
extract('hello world');         // [] -- no detectable table
extract('<table></table>');     // [] -- empty table element

The only function that throws is exportTable when given an unsupported format:

exportTable(table, 'xml' as any);
// throws Error: "Unsupported export format: xml"

Advanced Usage

Extracting multiple tables

HTML and Markdown documents can contain multiple tables. All are returned in document order.

const html = `
  <table><tr><th>A</th></tr><tr><td>1</td></tr></table>
  <table><tr><th>B</th></tr><tr><td>2</td></tr></table>
`;
const tables = extract(html);
// tables.length === 2

Filtering by confidence

Use confidenceThreshold to exclude low-confidence extractions.

const tables = extract(ambiguousInput, { confidenceThreshold: 0.8 });
// Only tables with confidence >= 0.8 are returned

Handling tables without headers

Set headerRows: 0 to treat all rows as data. Generated headers (Column 1, Column 2, ...) are assigned automatically.

const tables = extract(csvData, { headerRows: 0 });
// tables[0].headers === ['Column 1', 'Column 2', 'Column 3']

Multi-level headers in HTML

For HTML tables with two header rows (e.g., a category row spanning columns and a sub-category row beneath it), use headerRows: 2. Headers are flattened by concatenating unique values per column.

const tables = extractFromHtml(html, { headerRows: 2 });
// A column under "Sales" > "Q1" gets header "Sales Q1"

Round-trip extraction and export

Extract from one format and export to another.

import { extract, toMarkdown, toCSV, toHTML } from 'doc-table-extract';

const tables = extract(htmlString);
const table = tables[0];

const markdown = toMarkdown(table, true); // with column alignment
const csv = toCSV(table);
const html = toHTML(table);               // preserves rowspan/colspan

Preserving newlines in cells

By default, newlines within cells are collapsed to spaces. To preserve them:

const tables = extract(csvWithMultilineCells, { preserveCellNewlines: true });
// Cell text retains \n characters

Working with column types

After extraction, use columnTypes to drive formatting or validation logic.

const table = extract(csvData)[0];

table.columnTypes.forEach((type, i) => {
  if (type === 'number') {
    console.log(`Column "${table.headers[i]}" is numeric`);
  }
});

TypeScript

The package ships with full TypeScript declarations (dist/index.d.ts) and declaration maps. All types are exported from the package root.

import type {
  ExtractedTable,
  TableCell,
  TableMetadata,
  TableRegion,
  InputFormat,
  ExportFormat,
  ExtractOptions,
  ColumnType,
} from 'doc-table-extract';

Compiled with:

  • target: ES2022
  • module: commonjs
  • strict: true
  • declaration: true
  • declarationMap: true
  • sourceMap: true

License

MIT