Skip to content

Commit 24b2e96

Browse files
committed
feat(v6.1.0): core infrastructure stability and documentation overhaul
Core Infrastructure & Performance: - Implement `OcrSchedulerManager` with intelligent Tesseract.js worker pooling. - Support parallel OCR requests with LRU language-affinity worker reuse. - Migrate from `yauzl` to `fflate` for robust browser/Node zip extraction. - Add `terminateOcr()` and `autoTerminateTimeout` for manual lifecycle control. - Fix #59: Implement lazy-loading for OCR to prevent top-level requirement leaks. - Enable full native ESM support and modern `Node16` resolution. Documentation & UI/UX: - Standardize download metrics to "260k+" with Shields.io badges. - Add `npm-stat.com` verification hyperlinks to the Landing Page and Hero sections. - Rewrite `index.html` as a premium SPA with persistent documentation fragments. - Expand "Troubleshooting & Debugging" guides in README and documentation. - Implement high-fidelity SVG icon system for all 8 supported document formats. Metadata & Parsing Refinements: - Add extraction of custom document properties for OOXML, ODF, and PDF. - Upgrade `pdfjs-dist` to v5.6.205 with verified worker registry logic. - Refine hierarchical coordinate alignment for PDF text and link layers. - Improve ODT/RTF list parsing stability and indentation accuracy. Project Ecosystem: - Integrate GitHub `FUNDING.yml` and local `funding.json` manifests. - Standardize IIFE/ESM browser bundles with automated sync-to-docs logic. - Hardened CLI and Test suite (270+ test cases validated).
1 parent 880f682 commit 24b2e96

32 files changed

Lines changed: 1287 additions & 371 deletions

.github/FUNDING.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# These are supported funding model platforms
2+
3+
github: harshankur
4+
patreon:
5+
open_collective:
6+
ko_fi:
7+
tidelift:
8+
community_bridge:
9+
liberapay:
10+
issuehunt:
11+
lfx_crowdfunding:
12+
polar:
13+
buy_me_a_coffee: harshankur
14+
custom: ["https://www.paypal.me/hakuna0404"]

.github/workflows/build-and-publish.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
# - Checks out repository code and sets up the Node.js environment
44
# - Runs the full test suite (artifact validation + parser tests)
55
# - Builds all release artifacts:
6-
# officeparser@<version>.browser.js — IIFE bundle for <script> tags
7-
# officeparser@<version>.browser.mjs — ESM bundle for bundlers (Vite, webpack)
8-
# officeparser@<version>.browser.d.ts — TypeScript declarations
9-
# sbom.cdx.json — CycloneDX Software Bill of Materials
6+
# officeparser@<version>.browser.iife.js — IIFE bundle for <script> tags
7+
# officeparser@<version>.browser.mjs — ESM bundle for bundlers (Vite, webpack)
8+
# officeparser@<version>.browser.d.ts — TypeScript declarations
9+
# sbom.cdx.json — CycloneDX Software Bill of Materials
1010
# - Attaches all artifacts to the GitHub Release
1111
# - Publishes to npm registry with OIDC provenance (Sigstore attestation)
1212
# - Publishes to GitHub Packages with @harshankur scope
@@ -61,7 +61,7 @@ jobs:
6161
VERSION=${{ github.event.release.tag_name }}
6262
VERSION_CLEAN=${VERSION#v}
6363
mkdir -p release_assets
64-
cp dist/officeparser.browser.iife.js "release_assets/officeparser@$VERSION_CLEAN.browser.js"
64+
cp dist/officeparser.browser.iife.js "release_assets/officeparser@$VERSION_CLEAN.browser.iife.js"
6565
cp dist/officeparser.browser.mjs "release_assets/officeparser@$VERSION_CLEAN.browser.mjs"
6666
cp dist/officeparser.browser.d.ts "release_assets/officeparser@$VERSION_CLEAN.browser.d.ts"
6767
@@ -79,8 +79,8 @@ jobs:
7979
8080
# ── Step 7: Attach all assets to the GitHub Release ───────────────────
8181
# This uploads:
82-
# officeparser@<version>.browser.js — IIFE bundle
83-
# officeparser@<version>.browser.mjs — ESM bundle
82+
# officeparser@<version>.browser.iife.js — IIFE bundle
83+
# officeparser@<version>.browser.mjs — ESM bundle
8484
# officeparser@<version>.browser.d.ts — TypeScript declarations
8585
# sbom.cdx.json — CycloneDX SBOM
8686
- name: Upload Release Assets

.well-known/funding-manifest-urls

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
https://officeparser.harshankur.com/funding.json
2+
https://raw.githubusercontent.com/harshankur/officeParser/master/funding.json

README.md

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
A robust, strictly-typed Node.js and Browser library for parsing office files ([`docx`](https://en.wikipedia.org/wiki/Office_Open_XML), [`pptx`](https://en.wikipedia.org/wiki/Office_Open_XML), [`xlsx`](https://en.wikipedia.org/wiki/Office_Open_XML), [`odt`](https://en.wikipedia.org/wiki/OpenDocument), [`odp`](https://en.wikipedia.org/wiki/OpenDocument), [`ods`](https://en.wikipedia.org/wiki/OpenDocument), [`pdf`](https://en.wikipedia.org/wiki/PDF), [`rtf`](https://en.wikipedia.org/wiki/Rich_Text_Format)). It produces a clean, hierarchical Abstract Syntax Tree (AST) with rich metadata, text formatting, and full attachment support.
44

55
[![npm version](https://badge.fury.io/js/officeparser.svg)](https://badge.fury.io/js/officeparser)
6+
[![Total Downloads](https://img.shields.io/npm/dt/officeparser.svg)](https://www.npmjs.com/package/officeparser)
7+
[![Weekly Downloads](https://img.shields.io/npm/dw/officeparser.svg)](https://www.npmjs.com/package/officeparser)
68
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
79

810
---
@@ -22,6 +24,13 @@ A robust, strictly-typed Node.js and Browser library for parsing office files ([
2224

2325

2426
#### Update
27+
* 2026-04-14 - **v6.1.0 Release**: Major Infrastructure & Resource Stability. (Incremental since v6.0.0)
28+
- **OCR Scheduler**: Intelligent worker pool that optimizes Tesseract lifecycle across parallel requests. **Note**: By default, Node.js processes stay active for 10s after OCR to keep workers warm (configurable via `ocrConfig.autoTerminateTimeout`); use `terminateOcr()` for immediate CLI/script exit.
29+
- **Core Engine**: Replaced legacy zip extraction with `fflate` for significant performance gains and robust browser/edge compatibility.
30+
- **Module System**: Full native ESM support with `Node16` resolution and verified browser bundles (Vite/Angular compatible).
31+
- **Format Refinements**: Hierarchical PDF coordinate alignment and ODT/RTF list parsing stability.
32+
- **Custom Properties**: Added support for extracting custom document metadata across OOXML, ODF, and PDF formats.
33+
- **Sponsorship**: Integrated `funding.json` manifest and GitHub Sponsors support.
2534
* 2025/12/29 - **v6.0.0 Release**: Major overhaul of the library. Transitioned from simple text extraction to a rich **Abstract Syntax Tree (AST)** output.
2635
- Simplified API: Use `parseOffice` for all parsing needs (returns a Promise).
2736
- Structured Output: Access hierarchical document structure (paragraphs, headings, tables, lists, etc.).
@@ -82,6 +91,7 @@ npx officeparser /path/to/officeFile.docx --ignoreNotes=true --newlineDelimiter=
8291
- `--extractAttachments=[true|false]` Flag to extract images/charts as Base64. Default is false.
8392
- `--ocr=[true|false]` Flag to enable OCR for extracted images. Default is false.
8493
- `--includeRawContent=[true|false]` Flag to include raw XML/RTF content in nodes. Default is false.
94+
- `--verbose=[true|false]` Show full error stack traces.
8595

8696

8797
## Library Usage
@@ -156,7 +166,7 @@ The `OfficeParserAST` provides a format-agnostic representation of your document
156166
```text
157167
OfficeParserAST
158168
├── type: "docx" | "pptx" | "xlsx" | ...
159-
├── metadata: { author, title, created, modified, ... }
169+
├── metadata: { author, title, created, modified, ..., customProperties }
160170
├── content: [ OfficeContentNode ]
161171
│ ├── type: "paragraph" | "heading" | "table" | "list" | ...
162172
│ ├── text: "Concatenated text of this node and all children"
@@ -177,7 +187,7 @@ OfficeParserAST
177187
```json
178188
{
179189
"type": "docx",
180-
"metadata": { "author": "John Doe", "title": "Annual Report" },
190+
"metadata": { "author": "John Doe", "title": "Annual Report", "customProperties": { "Department": "Finance" } },
181191
"content": [
182192
{
183193
"type": "heading",
@@ -303,6 +313,16 @@ Formatting can be found at two levels:
303313
The `ast.metadata` object provides document-wide context:
304314
- **`styleMap`**: A dictionary of style names to their `TextFormatting` definitions found in the document.
305315
- **`formatting`**: Document-wide default settings (e.g., default font or font size).
316+
- **`customProperties`**: A dictionary of user-defined metadata embedded in the document (OOXML `custom.xml`, ODF `meta:user-defined`, or PDF Info dictionary).
317+
318+
### 7. Custom Properties
319+
You can access custom user-defined metadata that might be embedded in the document:
320+
321+
```javascript
322+
const ast = await officeParser.parseOffice("contract.docx");
323+
console.log("Custom Metadata:", ast.metadata.customProperties);
324+
// Output: { "ProjectID": "ABC-123", "InternalReview": true }
325+
```
306326

307327
### Advanced AST Usage
308328
Beyond using `ast.toText()`, you can interact with the structural data directly:
@@ -403,10 +423,46 @@ Pass an optional config object as the second argument to `parseOffice`.
403423
| `ignoreNotes` | boolean | `false` | Ignore notes in files like PowerPoint/ODP. |
404424
| `putNotesAtLast` | boolean | `false` | Put notes text at the end of the document. (Note: Does not work for RTF. It is treated as true always.) |
405425
| `extractAttachments` | boolean | `false` | Extract images and charts as Base64. |
406-
| `ocr` | boolean | `false` | Enable OCR for images (requires `extractAttachments: true`). |
407-
| `ocrLanguage` | string | `eng` | Language for OCR (e.g., 'eng', 'fra'). Supports multiple languages with '+'. See [Language Codes](https://tesseract-ocr.github.io/tessdoc/Data-Files#data-files-for-version-400-november-29-2016). |
408426
| `includeRawContent` | boolean | `false` | Include raw XML/RTF markup in the nodes. |
427+
| `serializeRawContent` | boolean | `true` | When `includeRawContent` is true, re-serializes raw XML to clean strings. If false, extracts original raw substring. |
428+
| `preserveXmlWhitespace` | boolean | `false` | When `serializeRawContent` is true, preserves original XML whitespace and line endings. |
429+
| `ocr` | boolean | `false` | Enable OCR for images (requires `extractAttachments: true`). |
430+
| `ocrLanguage` | string | `eng` | **Deprecated**: Use `ocrConfig.language` instead. Language for OCR. |
409431
| `pdfWorkerSrc` | string | `(see below)` | Path to PDF.js worker. Defaults to a CDN link if not provided. |
432+
| `ocrConfig` | object | `{}` | **OCR Scheduler** configuration for fine-grained worker control. |
433+
| `ocrConfig.language` | string | `eng` | Language(s) for OCR (e.g., 'eng', 'fra', 'eng+fra'). |
434+
| `ocrConfig.autoTerminateTimeout` | number | `10000` | Inactivity timeout in milliseconds before workers are killed. |
435+
| `ocrConfig.workerPath` | string | `undefined` | Path to Tesseract worker script (for offline use). |
436+
| `ocrConfig.corePath` | string | `undefined` | Path to Tesseract core script (for offline use). |
437+
| `ocrConfig.langPath` | string | `undefined` | Path for Tesseract language files (for offline use). |
438+
439+
### OCR Scheduler & Resource Management
440+
If your application uses OCR, `officeParser` utilizes an intelligent **Smart Worker Pool** to maintain a background worker pool and optimize repeated parse requests.
441+
442+
- **Dynamic Affinity**: Workers in the pool persist with their last used language affinity.
443+
- **Smart Re-initialization**: If a new language is requested and the pool is full, the manager identifies the **Least Recently Used (LRU)** idle worker and re-initializes it for the new language using the Tesseract.js v5 API. This avoids the overhead of destroying and recreating workers.
444+
- **Auto-Termination**: Workers are automatically cleaned up after 10 seconds of inactivity (configurable via `ocrConfig.autoTerminateTimeout`).
445+
446+
#### `OfficeParser.terminateOcr()`
447+
If you have used OCR (`{ ocr: true }`) in a short-lived script (like CLI tools or one-off automation), we recommend explicitly calling `terminateOcr()` after your processing is finished. This bypasses the 10-second idle timer and allows the process to return to the terminal prompt immediately.
448+
449+
> [!NOTE]
450+
> If OCR was not used, this function is a no-op and does not need to be called.
451+
452+
```js
453+
const officeParser = require('officeparser');
454+
455+
async function runCleaner() {
456+
await officeParser.parseOffice("file.pdf", { ocr: true });
457+
// ... process results ...
458+
459+
// Manually kill OCR workers for an immediate exit
460+
await officeParser.terminateOcr();
461+
}
462+
```
463+
464+
> [!TIP]
465+
> This is handled automatically in our own CLI (`npx officeparser ...`). You only need to call this manually if you are using the library in your own custom script and want a snappy exit.
410466
411467
```js
412468
const config = {
@@ -449,27 +505,40 @@ officeParser.parseOffice("presentation.pptx", config).then(ast => {
449505
```
450506
451507
## Browser Usage
452-
The browser bundle exposes the `officeParser` namespace. Include the bundle file available in the release assets.
508+
The library provides two types of browser bundles in the `dist/` directory:
509+
1. **`officeparser.browser.iife.js`**: Standard IIFE bundle for direct `<script>` tag usage. Exposes the global `officeParser` namespace.
510+
2. **`officeparser.browser.mjs`**: Modern ESM bundle for use with `import` statements or modern bundlers.
511+
512+
### Usage (Script Tag)
513+
Include the IIFE bundle file available in the release assets.
453514
454515
```html
455-
<script src="dist/officeparser.browser.js"></script>
516+
<script src="dist/officeparser.browser.iife.js"></script>
456517
<script>
457518
async function handleFile(file) {
458519
// file can be a File object from an input element or an ArrayBuffer
459-
// The browser bundle exposes the global variable `officeParser`
460-
// which contains the `OfficeParser` class.
461-
462520
try {
463521
const ast = await officeParser.parseOffice(file, { ocr: true });
464522
console.log(ast.toText());
465-
console.log("Metadata:", ast.metadata);
466523
} catch (error) {
467524
console.error(error);
468525
}
469526
}
470527
</script>
471528
```
472529
530+
### Usage (ESM)
531+
If you are using a modern browser that supports modules or a dev server like Vite:
532+
533+
```html
534+
<script type="module">
535+
import { OfficeParser } from './dist/officeparser.browser.mjs';
536+
537+
const ast = await OfficeParser.parseOffice(fileBuffer);
538+
console.log(ast.metadata);
539+
</script>
540+
```
541+
473542
### PDF Worker Configuration in Browser
474543
When using `officeparser` in a browser environment to parse PDF files, you may provide the `pdfWorkerSrc` configuration option. If not provided, it defaults to a CDN link for `pdfjs-dist@5.6.205`.
475544
@@ -485,7 +554,17 @@ const ast2 = await officeParser.parseOffice(file, {
485554
});
486555
```
487556
488-
> **Note:** The version of `pdfjs-dist` in the worker source should match the version used by `officeparser` (currently `5.4.530`).
557+
> **Note:** The version of `pdfjs-dist` in the worker source should match the version used by `officeparser` (currently `5.6.205`).
558+
559+
## Troubleshooting & Common Issues
560+
561+
- **Node.js process stays alive after finishing**: If using OCR, the worker pool stays warm for 10s by default. Use `await terminateOcr()` at the end of your script for a snappy exit.
562+
- **"Worker not found" in Browser**: Ensure `pdfWorkerSrc` is correctly pointed to the `pdf.worker.min.mjs` file matching version `5.6.205`.
563+
- **OCR accuracy is low**: Verify your `ocrConfig.language` matches the document content. Note that OCR quality depends on image resolution.
564+
- **Out of memory on large files**: For massive spreadsheets, consider using `ast.toText()` early and allowing the full AST object to be garbage-collected.
565+
566+
For a comprehensive guide, visit our [Debugging & Troubleshooting Documentation](https://harshankur.github.io/officeParser/#spec/debugging).
567+
489568
490569
## Known Limitations
491570
1. **ODT/ODS Charts**: Extraction may occasionally show inaccurate data when referencing external cell ranges or complex layout-based data.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
https://officeparser.harshankur.com/funding.json
2+
https://raw.githubusercontent.com/harshankur/officeParser/master/funding.json

docs/browser-usage.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const handleFile = async (event) => {
4141
You can also use the IIFE bundle directly in a script tag:
4242

4343
```html
44-
<script src="node_modules/officeparser/dist/officeparser.bundle.js"></script>
44+
<script src="dist/officeparser.browser.iife.js"></script>
4545
<script>
4646
const handleFile = async (file) => {
4747
const buffer = await file.arrayBuffer();
@@ -74,3 +74,21 @@ const ast = await OfficeParser.parseOffice(buffer, {
7474

7575
- **serializeRawContent: true** (Default): Re-serializes the XML from the DOM. This ensures valid XML but may change whitespace or attribute order.
7676
- **serializeRawContent: false**: Extracts the exact substring from the original file based on DOM locators. This is faster and preserves the byte-for-byte original content.
77+
78+
## OCR Scheduler & Resource Management
79+
80+
If you enable OCR in the browser (`{ ocr: true }`), `officeParser` will initialize a pool of Tesseract.js workers managed by an intelligent **Smart Worker Pool**.
81+
82+
- **Efficient Switching**: These workers persist with their language affinity. If you switch between parsing of English and French documents, the pool will automatically re-allocate workers using an **LRU (Least Recently Used)** strategy—re-initializing the oldest idle worker to the new language only when necessary.
83+
- **Resource Cleanup**: Workers are automatically cleaned up after an inactivity timeout of 10 seconds.
84+
85+
### Manual OCR Cleanup
86+
If you have performed a parse with OCR enabled and want to free up memory and terminate workers immediately:
87+
88+
```javascript
89+
await officeParser.parseOffice(file, { ocr: true });
90+
// ... process results ...
91+
92+
// Kill all background workers immediately
93+
await officeParser.terminateOcr();
94+
```

docs/dist/officeparser.browser.iife.js

Lines changed: 31 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/dist/officeparser.browser.js

Lines changed: 0 additions & 141 deletions
This file was deleted.

docs/dist/officeparser.browser.mjs

Lines changed: 31 additions & 31 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)