peasy-document 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peasy Tools
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # peasy-document
2
+
3
+ [![npm](https://img.shields.io/npm/v/peasy-document)](https://www.npmjs.com/package/peasy-document)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://www.npmjs.com/package/peasy-document)
7
+
8
+ Pure TypeScript document conversion library for Markdown, HTML, CSV, JSON, and YAML transformations. Convert between 6 document formats with 10 conversion functions and frozen result objects -- all with zero runtime dependencies. Every conversion uses pure string processing, making it lightweight and fast for any JavaScript or TypeScript project.
9
+
10
+ Part of the [Peasy Tools](https://peasytools.com) developer tools ecosystem.
11
+
12
+ ## Table of Contents
13
+
14
+ - [Install](#install)
15
+ - [Quick Start](#quick-start)
16
+ - [What You Can Do](#what-you-can-do)
17
+ - [Markdown Conversion](#markdown-conversion)
18
+ - [HTML Processing](#html-processing)
19
+ - [CSV and JSON Conversion](#csv-and-json-conversion)
20
+ - [YAML Generation](#yaml-generation)
21
+ - [Table Formatting](#table-formatting)
22
+ - [TypeScript Types](#typescript-types)
23
+ - [API Reference](#api-reference)
24
+ - [Also Available for Python](#also-available-for-python)
25
+ - [Peasy Developer Tools](#peasy-developer-tools)
26
+ - [License](#license)
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ npm install peasy-document
32
+ ```
33
+
34
+ Zero runtime dependencies. Works in Node.js, Deno, Bun, and modern bundlers.
35
+
36
+ ## Quick Start
37
+
38
+ ```typescript
39
+ import { markdownToHtml, csvToJson, htmlToText } from "peasy-document";
40
+
41
+ // Convert Markdown to HTML with headings, bold, italic, code blocks, and lists
42
+ const result = markdownToHtml("# Hello World\n\nThis is **bold** text.");
43
+ console.log(result.content);
44
+ // <h1>Hello World</h1>
45
+ // <p>This is <strong>bold</strong> text.</p>
46
+
47
+ // Convert CSV data to JSON array of objects
48
+ const json = csvToJson("name,age\nAlice,30\nBob,25");
49
+ console.log(json.content);
50
+ // [{"name": "Alice", "age": "30"}, {"name": "Bob", "age": "25"}]
51
+
52
+ // Strip HTML to plain text with entity decoding
53
+ const text = htmlToText("<h1>Title</h1><p>Hello &amp; welcome.</p>");
54
+ console.log(text.content);
55
+ // Title
56
+ // Hello & welcome.
57
+ ```
58
+
59
+ All functions return a `ConversionResult` with metadata:
60
+
61
+ ```typescript
62
+ const result = markdownToHtml("# Hello");
63
+ console.log(result.sourceFormat); // "markdown"
64
+ console.log(result.targetFormat); // "html"
65
+ console.log(result.sourceSize); // 7 (bytes)
66
+ console.log(result.targetSize); // 14 (bytes)
67
+ ```
68
+
69
+ ## What You Can Do
70
+
71
+ ### Markdown Conversion
72
+
73
+ Convert Markdown to HTML using a built-in pure TypeScript parser. Supports headings (h1-h6), bold, italic, inline code, fenced code blocks with language hints, links, images, unordered and ordered lists, blockquotes, horizontal rules, and paragraphs.
74
+
75
+ ```typescript
76
+ import { markdownToHtml } from "peasy-document";
77
+
78
+ // Full Markdown document with multiple elements
79
+ const result = markdownToHtml(`
80
+ # API Documentation
81
+
82
+ | Feature | Status |
83
+ |---------|--------|
84
+ | Auth | Done |
85
+
86
+ \`\`\`typescript
87
+ const api = new Client({ key: "abc" });
88
+ \`\`\`
89
+
90
+ - First item
91
+ - Second item
92
+
93
+ > Important note about the API
94
+ `);
95
+
96
+ console.log(result.content);
97
+ // <h1>API Documentation</h1>
98
+ // <pre><code class="language-typescript">...</code></pre>
99
+ // <ul><li>First item</li>...</ul>
100
+ // <blockquote><p>Important note about the API</p></blockquote>
101
+ ```
102
+
103
+ ### HTML Processing
104
+
105
+ Extract plain text from HTML documents, convert HTML to Markdown, or turn plain text into HTML paragraphs.
106
+
107
+ ```typescript
108
+ import { htmlToText, htmlToMarkdown, textToHtml } from "peasy-document";
109
+
110
+ // Strip all HTML tags and decode entities
111
+ const text = htmlToText(`
112
+ <html><body>
113
+ <h1>Welcome</h1>
114
+ <p>This is a <strong>formatted</strong> document with &amp; entities.</p>
115
+ <script>alert('ignored')</script>
116
+ </body></html>
117
+ `);
118
+ console.log(text.content);
119
+ // Welcome
120
+ // This is a formatted document with & entities.
121
+
122
+ // Convert HTML back to Markdown (handles h1-h6, a, strong, em, lists, code, pre, img)
123
+ const md = htmlToMarkdown(`
124
+ <h1>Document</h1>
125
+ <p>Visit <a href="https://example.com">our site</a> for <strong>more info</strong>.</p>
126
+ <ul><li>First</li><li>Second</li></ul>
127
+ `);
128
+ console.log(md.content);
129
+ // # Document
130
+ // Visit [our site](https://example.com) for **more info**.
131
+ // - First
132
+ // - Second
133
+
134
+ // Convert plain text to HTML paragraphs
135
+ const html = textToHtml("First paragraph.\n\nSecond paragraph.\nWith a line break.");
136
+ console.log(html.content);
137
+ // <p>First paragraph.</p>
138
+ // <p>Second paragraph.<br>With a line break.</p>
139
+ ```
140
+
141
+ ### CSV and JSON Conversion
142
+
143
+ Transform between CSV and JSON formats with proper handling of quoted fields, commas inside values, escaped quotes, and custom delimiters. Roundtrip-safe.
144
+
145
+ ```typescript
146
+ import { csvToJson, jsonToCsv } from "peasy-document";
147
+
148
+ // CSV to JSON array of objects
149
+ const json = csvToJson("name,role,team\nAlice,Engineer,Backend\nBob,Designer,Frontend");
150
+ console.log(json.content);
151
+ // [
152
+ // {"name": "Alice", "role": "Engineer", "team": "Backend"},
153
+ // {"name": "Bob", "role": "Designer", "team": "Frontend"}
154
+ // ]
155
+
156
+ // JSON back to CSV
157
+ const csv = jsonToCsv(json.content);
158
+ console.log(csv.content);
159
+ // name,role,team
160
+ // Alice,Engineer,Backend
161
+ // Bob,Designer,Frontend
162
+
163
+ // Tab-separated values
164
+ const tsv = csvToJson("name\tage\nAlice\t30", "\t");
165
+ ```
166
+
167
+ ### YAML Generation
168
+
169
+ Convert JSON to YAML-like format without any external YAML library. Handles nested objects, arrays, strings, numbers, booleans, and null.
170
+
171
+ ```typescript
172
+ import { jsonToYaml } from "peasy-document";
173
+
174
+ const result = jsonToYaml(JSON.stringify({
175
+ server: { host: "localhost", port: 8080 },
176
+ features: ["auth", "logging"],
177
+ debug: true,
178
+ cache: null,
179
+ }));
180
+ console.log(result.content);
181
+ // server:
182
+ // host: localhost
183
+ // port: 8080
184
+ // features:
185
+ // - auth
186
+ // - logging
187
+ // debug: true
188
+ // cache: null
189
+ ```
190
+
191
+ ### Table Formatting
192
+
193
+ Parse CSV into structured table data, or render it directly as Markdown or HTML tables.
194
+
195
+ ```typescript
196
+ import { csvToTable, csvToMarkdown, csvToHtml } from "peasy-document";
197
+
198
+ // Parse into structured TableData
199
+ const table = csvToTable("Name,Age,City\nAlice,30,NYC\nBob,25,LA");
200
+ console.log(table.headers); // ["Name", "Age", "City"]
201
+ console.log(table.rowCount); // 2
202
+ console.log(table.columnCount); // 3
203
+ console.log(table.rows[0]); // ["Alice", "30", "NYC"]
204
+
205
+ // Render as Markdown table with aligned columns
206
+ const md = csvToMarkdown("Name,Age,City\nAlice,30,NYC\nBob,25,LA");
207
+ console.log(md.content);
208
+ // | Name | Age | City |
209
+ // | ----- | --- | ---- |
210
+ // | Alice | 30 | NYC |
211
+ // | Bob | 25 | LA |
212
+
213
+ // Render as HTML table with thead/tbody structure
214
+ const html = csvToHtml("Name,Age\nAlice,30");
215
+ console.log(html.content);
216
+ // <table>
217
+ // <thead>
218
+ // <tr><th>Name</th><th>Age</th></tr>
219
+ // </thead>
220
+ // <tbody>
221
+ // <tr><td>Alice</td><td>30</td></tr>
222
+ // </tbody>
223
+ // </table>
224
+ ```
225
+
226
+ ## TypeScript Types
227
+
228
+ ```typescript
229
+ interface ConversionResult {
230
+ content: string; // The converted content
231
+ sourceFormat: string; // e.g. "markdown", "html", "csv", "json", "text"
232
+ targetFormat: string; // e.g. "html", "text", "json", "csv", "yaml", "markdown"
233
+ sourceSize: number; // Byte size of source (UTF-8)
234
+ targetSize: number; // Byte size of output (UTF-8)
235
+ }
236
+
237
+ interface TableData {
238
+ headers: string[]; // Column headers from first row
239
+ rows: string[][]; // Data rows
240
+ rowCount: number; // Number of data rows
241
+ columnCount: number; // Number of columns
242
+ }
243
+ ```
244
+
245
+ ## API Reference
246
+
247
+ | Function | Input | Output | Description |
248
+ |----------|-------|--------|-------------|
249
+ | `markdownToHtml(source)` | Markdown | HTML | Headings, bold, italic, code, lists, links, images, blockquotes |
250
+ | `htmlToText(source)` | HTML | Plain text | Strip tags, decode entities, normalize whitespace |
251
+ | `htmlToMarkdown(source)` | HTML | Markdown | h1-h6, a, strong, em, lists, code, pre, img, blockquote |
252
+ | `textToHtml(source)` | Plain text | HTML | Paragraphs to `<p>` tags, newlines to `<br>` |
253
+ | `csvToJson(source, delimiter?)` | CSV | JSON | First row as headers, quoted field support |
254
+ | `jsonToCsv(source)` | JSON | CSV | All unique keys as headers, nested values stringified |
255
+ | `csvToTable(source, delimiter?)` | CSV | `TableData` | Structured headers, rows, and counts |
256
+ | `csvToMarkdown(source, delimiter?)` | CSV | Markdown | Pipe-separated table with alignment row |
257
+ | `csvToHtml(source, delimiter?)` | CSV | HTML | `<table>` with `<thead>` and `<tbody>` |
258
+ | `jsonToYaml(source)` | JSON | YAML | Objects, arrays, primitives, null handling |
259
+
260
+ ## Also Available for Python
261
+
262
+ ```bash
263
+ pip install peasy-document
264
+ ```
265
+
266
+ The Python version provides the same 10 conversion functions with frozen dataclass results. See [peasy-document on PyPI](https://pypi.org/project/peasy-document/).
267
+
268
+ ## Peasy Developer Tools
269
+
270
+ | Package | PyPI | npm | Description |
271
+ |---------|------|-----|-------------|
272
+ | **peasy-document** | [PyPI](https://pypi.org/project/peasy-document/) | [npm](https://www.npmjs.com/package/peasy-document) | Document conversion -- Markdown, HTML, CSV, JSON, YAML |
273
+ | peasy-pdf | [PyPI](https://pypi.org/project/peasy-pdf/) | -- | PDF manipulation and conversion |
274
+ | peasy-image | [PyPI](https://pypi.org/project/peasy-image/) | -- | Image format conversion and optimization |
275
+ | peasytext | [PyPI](https://pypi.org/project/peasytext/) | [npm](https://www.npmjs.com/package/peasytext) | Text analysis and transformation |
276
+ | peasy-css | [PyPI](https://pypi.org/project/peasy-css/) | [npm](https://www.npmjs.com/package/peasy-css) | CSS minification and processing |
277
+ | peasy-compress | [PyPI](https://pypi.org/project/peasy-compress/) | [npm](https://www.npmjs.com/package/peasy-compress) | File compression utilities |
278
+ | peasy-convert | [PyPI](https://pypi.org/project/peasy-convert/) | -- | Unified CLI for all Peasy tools |
279
+
280
+ ## License
281
+
282
+ MIT
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Result of a document format conversion.
3
+ *
4
+ * All conversion functions return this interface with the converted content
5
+ * and metadata about the source/target formats and sizes (in bytes).
6
+ */
7
+ interface ConversionResult {
8
+ /** The converted content string. */
9
+ content: string;
10
+ /** Source format identifier (e.g. "markdown", "html", "csv", "json", "text"). */
11
+ sourceFormat: string;
12
+ /** Target format identifier (e.g. "html", "text", "json", "csv", "yaml", "markdown"). */
13
+ targetFormat: string;
14
+ /** Byte size of the source input (UTF-8 encoded). */
15
+ sourceSize: number;
16
+ /** Byte size of the converted output (UTF-8 encoded). */
17
+ targetSize: number;
18
+ }
19
+ /**
20
+ * Structured table data parsed from CSV or similar tabular sources.
21
+ */
22
+ interface TableData {
23
+ /** Column header names from the first row. */
24
+ headers: string[];
25
+ /** Data rows (each row is an array of cell values). */
26
+ rows: string[][];
27
+ /** Number of data rows (excluding the header). */
28
+ rowCount: number;
29
+ /** Number of columns. */
30
+ columnCount: number;
31
+ }
32
+
33
+ /**
34
+ * Document conversion engine — pure TypeScript functions for Markdown, HTML,
35
+ * CSV, JSON, and YAML transformations.
36
+ *
37
+ * Zero runtime dependencies. All conversions use string processing only.
38
+ */
39
+
40
+ /**
41
+ * Convert Markdown to HTML.
42
+ *
43
+ * Supports headings (#-######), bold, italic, inline code, fenced code blocks,
44
+ * links, images, unordered lists (- or *), ordered lists (1.), blockquotes (>),
45
+ * horizontal rules (---/***), and paragraphs.
46
+ */
47
+ declare function markdownToHtml(source: string): ConversionResult;
48
+ /**
49
+ * Strip all HTML tags and decode entities to produce plain text.
50
+ *
51
+ * Removes script/style contents. Normalizes whitespace.
52
+ */
53
+ declare function htmlToText(source: string): ConversionResult;
54
+ /**
55
+ * Convert CSV to a JSON array of objects.
56
+ *
57
+ * The first row is used as headers (object keys). Handles quoted fields,
58
+ * escaped quotes, and custom delimiters.
59
+ */
60
+ declare function csvToJson(source: string, delimiter?: string): ConversionResult;
61
+ /**
62
+ * Convert a JSON array of objects to CSV.
63
+ *
64
+ * Extracts all unique keys as headers. Nested values are JSON-stringified.
65
+ */
66
+ declare function jsonToCsv(source: string): ConversionResult;
67
+ /**
68
+ * Parse CSV into structured TableData with headers, rows, and counts.
69
+ */
70
+ declare function csvToTable(source: string, delimiter?: string): TableData;
71
+ /**
72
+ * Convert CSV to a Markdown table with pipe separators and alignment row.
73
+ */
74
+ declare function csvToMarkdown(source: string, delimiter?: string): ConversionResult;
75
+ /**
76
+ * Convert CSV to an HTML table with proper thead/tbody structure.
77
+ */
78
+ declare function csvToHtml(source: string, delimiter?: string): ConversionResult;
79
+ /**
80
+ * Convert a JSON string to YAML-like format.
81
+ *
82
+ * Handles objects, arrays, strings, numbers, booleans, and null.
83
+ * No external YAML library required.
84
+ */
85
+ declare function jsonToYaml(source: string): ConversionResult;
86
+ /**
87
+ * Convert plain text to HTML paragraphs.
88
+ *
89
+ * Splits on double newlines, wraps each paragraph in <p> tags.
90
+ * Single newlines within a paragraph become <br> tags.
91
+ * HTML entities in the source text are escaped.
92
+ */
93
+ declare function textToHtml(source: string): ConversionResult;
94
+ /**
95
+ * Convert basic HTML to Markdown.
96
+ *
97
+ * Handles: h1-h6, p, a, strong/b, em/i, ul/ol/li, code, pre, br, img, blockquote.
98
+ * Uses regex-based tag replacement.
99
+ */
100
+ declare function htmlToMarkdown(source: string): ConversionResult;
101
+
102
+ export { type ConversionResult, type TableData, csvToHtml, csvToJson, csvToMarkdown, csvToTable, htmlToMarkdown, htmlToText, jsonToCsv, jsonToYaml, markdownToHtml, textToHtml };
package/dist/index.js ADDED
@@ -0,0 +1,461 @@
1
+ // src/engine.ts
2
+ function byteLength(s) {
3
+ return new TextEncoder().encode(s).length;
4
+ }
5
+ function makeResult(content, sourceFormat, targetFormat, source) {
6
+ return {
7
+ content,
8
+ sourceFormat,
9
+ targetFormat,
10
+ sourceSize: byteLength(source),
11
+ targetSize: byteLength(content)
12
+ };
13
+ }
14
+ function escapeHtml(s) {
15
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
16
+ }
17
+ function decodeEntities(s) {
18
+ return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&nbsp;/g, " ").replace(/&#(\d+);/g, (_match, code) => String.fromCharCode(Number(code))).replace(
19
+ /&#x([0-9a-fA-F]+);/g,
20
+ (_match, hex) => String.fromCharCode(parseInt(hex, 16))
21
+ );
22
+ }
23
+ function parseCsv(source, delimiter = ",") {
24
+ const rows = [];
25
+ let row = [];
26
+ let field = "";
27
+ let inQuotes = false;
28
+ for (let i = 0; i < source.length; i++) {
29
+ const ch = source[i];
30
+ if (inQuotes) {
31
+ if (ch === '"') {
32
+ if (i + 1 < source.length && source[i + 1] === '"') {
33
+ field += '"';
34
+ i++;
35
+ } else {
36
+ inQuotes = false;
37
+ }
38
+ } else {
39
+ field += ch;
40
+ }
41
+ } else {
42
+ if (ch === '"') {
43
+ inQuotes = true;
44
+ } else if (ch === delimiter) {
45
+ row.push(field);
46
+ field = "";
47
+ } else if (ch === "\n" || ch === "\r" && source[i + 1] === "\n") {
48
+ row.push(field);
49
+ field = "";
50
+ rows.push(row);
51
+ row = [];
52
+ if (ch === "\r") i++;
53
+ } else if (ch === "\r") {
54
+ row.push(field);
55
+ field = "";
56
+ rows.push(row);
57
+ row = [];
58
+ } else {
59
+ field += ch;
60
+ }
61
+ }
62
+ }
63
+ if (field || row.length > 0) {
64
+ row.push(field);
65
+ rows.push(row);
66
+ }
67
+ return rows;
68
+ }
69
+ function processInline(text) {
70
+ text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
71
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
72
+ text = text.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
73
+ text = text.replace(/__(.+?)__/g, "<strong>$1</strong>");
74
+ text = text.replace(/\*(.+?)\*/g, "<em>$1</em>");
75
+ text = text.replace(/(?<!\w)_(.+?)_(?!\w)/g, "<em>$1</em>");
76
+ text = text.replace(/`([^`]+)`/g, "<code>$1</code>");
77
+ return text;
78
+ }
79
+ function markdownToHtml(source) {
80
+ const lines = source.split("\n");
81
+ const htmlParts = [];
82
+ let inCodeBlock = false;
83
+ let codeBlockContent = "";
84
+ let codeBlockLang = "";
85
+ let inList = null;
86
+ let paragraphBuffer = [];
87
+ function flushParagraph() {
88
+ if (paragraphBuffer.length > 0) {
89
+ const content2 = paragraphBuffer.map(processInline).join("\n");
90
+ htmlParts.push(`<p>${content2}</p>`);
91
+ paragraphBuffer = [];
92
+ }
93
+ }
94
+ function closeList() {
95
+ if (inList) {
96
+ htmlParts.push(inList === "ul" ? "</ul>" : "</ol>");
97
+ inList = null;
98
+ }
99
+ }
100
+ for (let i = 0; i < lines.length; i++) {
101
+ const line = lines[i];
102
+ if (line.trimStart().startsWith("```")) {
103
+ if (!inCodeBlock) {
104
+ flushParagraph();
105
+ closeList();
106
+ inCodeBlock = true;
107
+ codeBlockLang = line.trimStart().slice(3).trim();
108
+ codeBlockContent = "";
109
+ } else {
110
+ const langAttr = codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : "";
111
+ htmlParts.push(
112
+ `<pre><code${langAttr}>${escapeHtml(codeBlockContent.replace(/\n$/, ""))}</code></pre>`
113
+ );
114
+ inCodeBlock = false;
115
+ codeBlockContent = "";
116
+ codeBlockLang = "";
117
+ }
118
+ continue;
119
+ }
120
+ if (inCodeBlock) {
121
+ codeBlockContent += (codeBlockContent ? "\n" : "") + line;
122
+ continue;
123
+ }
124
+ if (line.trim() === "") {
125
+ flushParagraph();
126
+ closeList();
127
+ continue;
128
+ }
129
+ if (/^(\s*[-*_]\s*){3,}$/.test(line)) {
130
+ flushParagraph();
131
+ closeList();
132
+ htmlParts.push("<hr>");
133
+ continue;
134
+ }
135
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
136
+ if (headingMatch) {
137
+ flushParagraph();
138
+ closeList();
139
+ const level = headingMatch[1].length;
140
+ const text = processInline(headingMatch[2]);
141
+ htmlParts.push(`<h${level}>${text}</h${level}>`);
142
+ continue;
143
+ }
144
+ const bqMatch = line.match(/^>\s?(.*)$/);
145
+ if (bqMatch) {
146
+ flushParagraph();
147
+ closeList();
148
+ const content2 = processInline(bqMatch[1]);
149
+ htmlParts.push(`<blockquote><p>${content2}</p></blockquote>`);
150
+ continue;
151
+ }
152
+ const ulMatch = line.match(/^[\s]*[-*]\s+(.+)$/);
153
+ if (ulMatch) {
154
+ flushParagraph();
155
+ if (inList !== "ul") {
156
+ closeList();
157
+ htmlParts.push("<ul>");
158
+ inList = "ul";
159
+ }
160
+ htmlParts.push(`<li>${processInline(ulMatch[1])}</li>`);
161
+ continue;
162
+ }
163
+ const olMatch = line.match(/^[\s]*\d+\.\s+(.+)$/);
164
+ if (olMatch) {
165
+ flushParagraph();
166
+ if (inList !== "ol") {
167
+ closeList();
168
+ htmlParts.push("<ol>");
169
+ inList = "ol";
170
+ }
171
+ htmlParts.push(`<li>${processInline(olMatch[1])}</li>`);
172
+ continue;
173
+ }
174
+ paragraphBuffer.push(line);
175
+ }
176
+ if (inCodeBlock) {
177
+ const langAttr = codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : "";
178
+ htmlParts.push(
179
+ `<pre><code${langAttr}>${escapeHtml(codeBlockContent)}</code></pre>`
180
+ );
181
+ }
182
+ flushParagraph();
183
+ closeList();
184
+ const content = htmlParts.join("\n");
185
+ return makeResult(content, "markdown", "html", source);
186
+ }
187
+ function htmlToText(source) {
188
+ let text = source;
189
+ text = text.replace(/<script[\s\S]*?<\/script>/gi, "");
190
+ text = text.replace(/<style[\s\S]*?<\/style>/gi, "");
191
+ text = text.replace(
192
+ /<\/?(?:p|div|br|h[1-6]|li|tr|blockquote|pre|hr)(?:\s[^>]*)?\/?>/gi,
193
+ "\n"
194
+ );
195
+ text = text.replace(/<[^>]+>/g, "");
196
+ text = decodeEntities(text);
197
+ const lines = text.split("\n").map((line) => line.replace(/\s+/g, " ").trim());
198
+ text = lines.join("\n");
199
+ while (text.includes("\n\n\n")) {
200
+ text = text.replace(/\n\n\n/g, "\n\n");
201
+ }
202
+ const content = text.trim();
203
+ return makeResult(content, "html", "text", source);
204
+ }
205
+ function csvToJson(source, delimiter = ",") {
206
+ const rows = parseCsv(source, delimiter);
207
+ if (rows.length === 0) {
208
+ return makeResult("[]", "csv", "json", source);
209
+ }
210
+ const headers = rows[0];
211
+ const dataRows = rows.slice(1);
212
+ const objects = dataRows.map((row) => {
213
+ const obj = {};
214
+ headers.forEach((header, i) => {
215
+ obj[header] = i < row.length ? row[i] : "";
216
+ });
217
+ return obj;
218
+ });
219
+ const content = JSON.stringify(objects, null, 2);
220
+ return makeResult(content, "csv", "json", source);
221
+ }
222
+ function quoteCsvField(value) {
223
+ if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
224
+ return '"' + value.replace(/"/g, '""') + '"';
225
+ }
226
+ return value;
227
+ }
228
+ function jsonToCsv(source) {
229
+ const data = JSON.parse(source);
230
+ if (!Array.isArray(data) || data.length === 0) {
231
+ return makeResult("", "json", "csv", source);
232
+ }
233
+ const allKeys = [];
234
+ const seen = /* @__PURE__ */ new Set();
235
+ for (const item of data) {
236
+ if (item && typeof item === "object" && !Array.isArray(item)) {
237
+ for (const key of Object.keys(item)) {
238
+ if (!seen.has(key)) {
239
+ allKeys.push(key);
240
+ seen.add(key);
241
+ }
242
+ }
243
+ }
244
+ }
245
+ const lines = [];
246
+ lines.push(allKeys.map(quoteCsvField).join(","));
247
+ for (const item of data) {
248
+ if (item && typeof item === "object" && !Array.isArray(item)) {
249
+ const record = item;
250
+ const row = allKeys.map((key) => {
251
+ const val = record[key];
252
+ if (val === null || val === void 0) return "";
253
+ if (typeof val === "object") return quoteCsvField(JSON.stringify(val));
254
+ return quoteCsvField(String(val));
255
+ });
256
+ lines.push(row.join(","));
257
+ }
258
+ }
259
+ const content = lines.join("\n");
260
+ return makeResult(content, "json", "csv", source);
261
+ }
262
+ function csvToTable(source, delimiter = ",") {
263
+ const rows = parseCsv(source, delimiter);
264
+ if (rows.length === 0) {
265
+ return { headers: [], rows: [], rowCount: 0, columnCount: 0 };
266
+ }
267
+ const headers = rows[0];
268
+ const dataRows = rows.slice(1);
269
+ return {
270
+ headers,
271
+ rows: dataRows,
272
+ rowCount: dataRows.length,
273
+ columnCount: headers.length
274
+ };
275
+ }
276
+ function csvToMarkdown(source, delimiter = ",") {
277
+ const table = csvToTable(source, delimiter);
278
+ if (table.headers.length === 0) {
279
+ return makeResult("", "csv", "markdown", source);
280
+ }
281
+ const widths = table.headers.map((h) => h.length);
282
+ for (const row of table.rows) {
283
+ for (let i = 0; i < row.length && i < widths.length; i++) {
284
+ widths[i] = Math.max(widths[i], row[i].length);
285
+ }
286
+ }
287
+ for (let i = 0; i < widths.length; i++) {
288
+ widths[i] = Math.max(widths[i], 3);
289
+ }
290
+ const headerCells = table.headers.map((h, i) => h.padEnd(widths[i]));
291
+ const headerLine = "| " + headerCells.join(" | ") + " |";
292
+ const sepCells = widths.map((w) => "-".repeat(w));
293
+ const sepLine = "| " + sepCells.join(" | ") + " |";
294
+ const dataLines = table.rows.map((row) => {
295
+ const cells = table.headers.map((_, i) => {
296
+ const value = i < row.length ? row[i] : "";
297
+ return value.padEnd(widths[i]);
298
+ });
299
+ return "| " + cells.join(" | ") + " |";
300
+ });
301
+ const content = [headerLine, sepLine, ...dataLines].join("\n");
302
+ return makeResult(content, "csv", "markdown", source);
303
+ }
304
+ function csvToHtml(source, delimiter = ",") {
305
+ const table = csvToTable(source, delimiter);
306
+ if (table.headers.length === 0) {
307
+ const content2 = "<table></table>";
308
+ return makeResult(content2, "csv", "html", source);
309
+ }
310
+ const lines = ["<table>", " <thead>", " <tr>"];
311
+ for (const h of table.headers) {
312
+ lines.push(` <th>${escapeHtml(h)}</th>`);
313
+ }
314
+ lines.push(" </tr>", " </thead>", " <tbody>");
315
+ for (const row of table.rows) {
316
+ lines.push(" <tr>");
317
+ for (let i = 0; i < table.headers.length; i++) {
318
+ const value = i < row.length ? row[i] : "";
319
+ lines.push(` <td>${escapeHtml(value)}</td>`);
320
+ }
321
+ lines.push(" </tr>");
322
+ }
323
+ lines.push(" </tbody>", "</table>");
324
+ const content = lines.join("\n");
325
+ return makeResult(content, "csv", "html", source);
326
+ }
327
+ function yamlScalar(value) {
328
+ if (value === null || value === void 0) return "null";
329
+ if (typeof value === "boolean") return value ? "true" : "false";
330
+ if (typeof value === "number") return String(value);
331
+ const s = String(value);
332
+ if (/[:#\[\]{},"&*!|>']/.test(s) || /^(true|false|null|yes|no|on|off)$/i.test(s) || s === "") {
333
+ const escaped = s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
334
+ return `"${escaped}"`;
335
+ }
336
+ return s;
337
+ }
338
+ function toYamlLines(obj, indent = 0) {
339
+ const prefix = " ".repeat(indent);
340
+ const lines = [];
341
+ if (obj && typeof obj === "object" && !Array.isArray(obj)) {
342
+ const record = obj;
343
+ for (const [key, value] of Object.entries(record)) {
344
+ if (value && typeof value === "object" && !Array.isArray(value)) {
345
+ lines.push(`${prefix}${key}:`);
346
+ lines.push(...toYamlLines(value, indent + 1));
347
+ } else if (Array.isArray(value)) {
348
+ lines.push(`${prefix}${key}:`);
349
+ for (const item of value) {
350
+ if (item && typeof item === "object") {
351
+ lines.push(`${prefix} -`);
352
+ lines.push(...toYamlLines(item, indent + 2));
353
+ } else {
354
+ lines.push(`${prefix} - ${yamlScalar(item)}`);
355
+ }
356
+ }
357
+ } else {
358
+ lines.push(`${prefix}${key}: ${yamlScalar(value)}`);
359
+ }
360
+ }
361
+ } else if (Array.isArray(obj)) {
362
+ for (const item of obj) {
363
+ if (item && typeof item === "object") {
364
+ lines.push(`${prefix}-`);
365
+ lines.push(...toYamlLines(item, indent + 1));
366
+ } else {
367
+ lines.push(`${prefix}- ${yamlScalar(item)}`);
368
+ }
369
+ }
370
+ } else {
371
+ lines.push(`${prefix}${yamlScalar(obj)}`);
372
+ }
373
+ return lines;
374
+ }
375
+ function jsonToYaml(source) {
376
+ const data = JSON.parse(source);
377
+ const lines = toYamlLines(data);
378
+ const content = lines.join("\n");
379
+ return makeResult(content, "json", "yaml", source);
380
+ }
381
+ function textToHtml(source) {
382
+ if (!source.trim()) {
383
+ return makeResult("", "text", "html", source);
384
+ }
385
+ const paragraphs = source.split(/\n\n+/).map((p) => p.trim()).filter((p) => p);
386
+ const htmlParts = paragraphs.map((para) => {
387
+ const escaped = escapeHtml(para);
388
+ const withBreaks = escaped.replace(/\n/g, "<br>");
389
+ return `<p>${withBreaks}</p>`;
390
+ });
391
+ const content = htmlParts.join("\n");
392
+ return makeResult(content, "text", "html", source);
393
+ }
394
+ function htmlToMarkdown(source) {
395
+ let md = source;
396
+ md = md.replace(/<script[\s\S]*?<\/script>/gi, "");
397
+ md = md.replace(/<style[\s\S]*?<\/style>/gi, "");
398
+ md = md.replace(
399
+ /<pre[^>]*>\s*<code[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/gi,
400
+ (_match, content2) => "\n\n```\n" + decodeEntities(content2).trim() + "\n```\n\n"
401
+ );
402
+ md = md.replace(
403
+ /<pre[^>]*>([\s\S]*?)<\/pre>/gi,
404
+ (_match, content2) => "\n\n```\n" + decodeEntities(content2).trim() + "\n```\n\n"
405
+ );
406
+ md = md.replace(
407
+ /<img[^>]*\ssrc=["']([^"']+)["'][^>]*\salt=["']([^"']*?)["'][^>]*\/?>/gi,
408
+ (_match, src, alt) => `![${alt}](${src})`
409
+ );
410
+ md = md.replace(
411
+ /<img[^>]*\salt=["']([^"']*?)["'][^>]*\ssrc=["']([^"']+)["'][^>]*\/?>/gi,
412
+ (_match, alt, src) => `![${alt}](${src})`
413
+ );
414
+ md = md.replace(
415
+ /<img[^>]*\ssrc=["']([^"']+)["'][^>]*\/?>/gi,
416
+ (_match, src) => `![](${src})`
417
+ );
418
+ md = md.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "\n# $1\n");
419
+ md = md.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, "\n## $1\n");
420
+ md = md.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, "\n### $1\n");
421
+ md = md.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, "\n#### $1\n");
422
+ md = md.replace(/<h5[^>]*>([\s\S]*?)<\/h5>/gi, "\n##### $1\n");
423
+ md = md.replace(/<h6[^>]*>([\s\S]*?)<\/h6>/gi, "\n###### $1\n");
424
+ md = md.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_match, content2) => {
425
+ const clean = content2.replace(/<\/?p[^>]*>/gi, "").trim();
426
+ return "\n> " + clean + "\n";
427
+ });
428
+ md = md.replace(/<(?:strong|b)[^>]*>([\s\S]*?)<\/(?:strong|b)>/gi, "**$1**");
429
+ md = md.replace(/<(?:em|i)[^>]*>([\s\S]*?)<\/(?:em|i)>/gi, "*$1*");
430
+ md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, "`$1`");
431
+ md = md.replace(
432
+ /<a[^>]*\shref=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi,
433
+ (_match, href, text) => `[${text}](${href})`
434
+ );
435
+ md = md.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_match, content2) => {
436
+ return "- " + content2.replace(/<\/?[^>]+>/g, "").trim() + "\n";
437
+ });
438
+ md = md.replace(/<\/?(?:ul|ol)[^>]*>/gi, "\n");
439
+ md = md.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, "\n$1\n");
440
+ md = md.replace(/<br\s*\/?>/gi, " \n");
441
+ md = md.replace(/<hr\s*\/?>/gi, "\n---\n");
442
+ md = md.replace(/<[^>]+>/g, "");
443
+ md = decodeEntities(md);
444
+ while (md.includes("\n\n\n")) {
445
+ md = md.replace(/\n\n\n/g, "\n\n");
446
+ }
447
+ const content = md.trim();
448
+ return makeResult(content, "html", "markdown", source);
449
+ }
450
+ export {
451
+ csvToHtml,
452
+ csvToJson,
453
+ csvToMarkdown,
454
+ csvToTable,
455
+ htmlToMarkdown,
456
+ htmlToText,
457
+ jsonToCsv,
458
+ jsonToYaml,
459
+ markdownToHtml,
460
+ textToHtml
461
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "peasy-document",
3
+ "version": "0.1.0",
4
+ "description": "Document conversion library — Markdown, HTML, CSV, JSON, YAML. Zero dependencies, TypeScript-first.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format esm --dts",
19
+ "test": "vitest run",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "keywords": [
23
+ "document",
24
+ "markdown",
25
+ "html",
26
+ "csv",
27
+ "json",
28
+ "yaml",
29
+ "converter",
30
+ "peasy"
31
+ ],
32
+ "author": "Peasy Tools",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "url": "https://github.com/peasytools/peasy-document-js.git"
36
+ },
37
+ "homepage": "https://peasytools.com",
38
+ "devDependencies": {
39
+ "tsup": "^8.0",
40
+ "typescript": "^5.7",
41
+ "vitest": "^3.0"
42
+ }
43
+ }