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

ownhwpx

v0.2.1

Published

HWPX file format reader/writer - TypeScript port of hwpxlib. Read, write, and manipulate HWPX (Hancom Word) documents.

Readme

ownhwpx

HWPX file format reader/writer — TypeScript port of hwpxlib (Java) by Park.sungkyon, with extended features for Node.js environments. Built with reference to Hancom's public HWPX specification.

Read, write, and manipulate HWPX (Hancom Word) documents in Node.js.

Features

Core (from hwpxlib)

  • Read HWPX files — from disk (fromFilepath), from URL (fromUrl), or from Buffer
  • Write HWPX files — to disk (toFilepath), to Buffer (toBytes), or to any Writable stream (toStream)
  • Roundtrip — Read → Modify → Write preserves document structure
  • Tables — Rows, cells, nested paragraphs inside table cells
  • Drawing objects — Picture, Rectangle, Ellipse, Line, Arc, Curve, Polygon, ConnectLine, Equation, TextArt, OLE, Video, Container
  • Form controls — Edit, ComboBox, ListBox, ScrollBar, Button, CheckButton, RadioButton
  • Ctrl items — Header/Footer, FootNote/EndNote, FieldBegin/FieldEnd, Bookmark, PageNum, Indexmark, HiddenComment, AutoNum/NewNum, PageHiding, ColPr

Extended (TypeScript-only additions)

  • Text extraction — Extract plain text from documents, with optional paragraph numbering
  • Object search — Find any element by type or field name throughout the document
  • Field finder — Locate field begin/end pairs by field name
  • Blank document creation — Create new empty HWPX documents from scratch
  • Template engine — Variable substitution ({{name}}), conditionals ({{#if}}), loops ({{#each}}), date formatting
  • Table of contents — Auto-generate TOC from heading paragraphs
  • Table from array — Programmatically create tables from 2D data with colspan/rowspan support
  • Horizontal rules — Insert line separator drawing objects
  • Section breaks — Create and insert new sections programmatically
  • Paragraph numbering — Auto-number headings (Outline/Bullet/Numbering)
  • TypeScript — Full type definitions, strict mode, 825+ source files
  • Zero compilation errors — Complete type safety
  • Unit tested — 121 tests across 3 test suites (Vitest)

Installation

npm install ownhwpx

Quick Start

import { HWPXReader, HWPXWriter, BlankFileMaker, TextExtractor, TextExtractMethod } from 'ownhwpx';

// Read an existing HWPX file
const doc = HWPXReader.fromFilepath('document.hwpx');
console.log(`Sections: ${doc.sectionXMLFileList.length}`);

// Access paragraphs
const section = doc.sectionXMLFileList.get(0);
for (let i = 0; i < section.paraListCore.count; i++) {
  const para = section.paraListCore.getPara(i);
  console.log(`Paragraph ${i}: ${para.runList.length} runs`);
}

// Write back to a new file
await HWPXWriter.toFilepath(doc, 'output.hwpx');

// Create a blank document
const blank = BlankFileMaker.make();
await HWPXWriter.toFilepath(blank, 'blank.hwpx');

// Extract text with paragraph headings
const text = TextExtractor.extract(doc, TextExtractMethod.AppendControlTextAfterParagraphText, true, null);
console.log(text);

API Overview

Reader

import { HWPXReader } from 'ownhwpx';

// From disk
const doc = HWPXReader.fromFilepath('document.hwpx');

// From URL (Node.js 18+)
// const doc = await HWPXReader.fromUrl('https://example.com/doc.hwpx');

// Document structure
doc.sectionXMLFileList       // sections
doc.headerXMLFile             // document header (styles, fonts, etc.)
doc.settingsXMLFile           // document settings
doc.containerXMLFile          // OCF container
doc.manifestXMLFile           // file manifest
doc.contentHPFFile            // content package

// Section content
const section = doc.sectionXMLFileList.get(0);
for (const para of section.paraListCore) {
  for (const run of para.runList) {
    for (const item of run.itemList) {
      // item can be T (text), Ctrl (control), Table, Picture, etc.
      console.log(item.objectType());
    }
  }
}

Writer

import { HWPXWriter } from 'ownhwpx';
import * as fs from 'fs';

// Write to file
await HWPXWriter.toFilepath(doc, 'output.hwpx');

// Write to buffer
const buffer = await HWPXWriter.toBytes(doc);

// Write to any Writable stream (HTTP response, S3 upload, etc.)
await HWPXWriter.toStream(doc, fs.createWriteStream('output.hwpx'));

ObjectFinder

import { ObjectFinder } from 'ownhwpx';
import { ObjectType } from 'ownhwpx';

// Find all fieldBegin elements
const results = ObjectFinder.find(doc, {
  isMatched: obj => obj.objectType() === ObjectType.hp_fieldBegin
}, false);

for (const r of results) {
  console.log(r.thisObject);
}

TextExtractor

import { TextExtractor, TextExtractMethod } from 'ownhwpx';

// Basic text extraction
const text = TextExtractor.extract(doc, TextExtractMethod.AppendControlTextAfterParagraphText);

// With paragraph headings
const textWithHeads = TextExtractor.extract(doc, TextExtractMethod.AppendControlTextAfterParagraphText, true, null);

BlankFileMaker

import { BlankFileMaker } from 'ownhwpx';

const blank = BlankFileMaker.make();
await HWPXWriter.toFilepath(blank, 'new_document.hwpx');

TemplateEngine

import { TemplateEngine } from 'ownhwpx';

// Create a document with template markers
const blank = BlankFileMaker.make();
const section = blank.sectionXMLFileList.get(0);
const para = section.paraListCore.getPara(0);
para.addNewRun().addNewT().addText('Hello, {{name}}!');

// Render with context
TemplateEngine.render(blank, { name: 'World' });
// → "Hello, World!"

TocMaker

import { TocMaker } from 'ownhwpx';

// Auto-generate TOC from heading paragraphs
const entries = TocMaker.generate(doc);
console.log(entries); // [{ level: 0, number: "1", text: "Introduction" }, ...]

// Insert TOC paragraphs at the start of section 0
TocMaker.insertToc(doc, { tocTitle: 'Table of Contents', maxLevel: 3 });

TableFromArray

import { TableFromArray } from 'ownhwpx';

const table = TableFromArray.make([
  ['Name', 'Age', 'City'],
  ['Alice', '30', 'Seoul'],
  ['Bob', '25', 'Busan'],
], { headerRows: 1 });

// Insert into a paragraph
const para = section.paraListCore.getPara(0);
para.addNewRun().addItem(table);

HrMaker

import { HrMaker } from 'ownhwpx';
import { LineWidth, LineType2 } from 'ownhwpx'; // re-exported from enums

const doc = BlankFileMaker.make();
const section = doc.sectionXMLFileList.get(0);
const para = section.paraListCore.getPara(0);

// Insert a horizontal rule
HrMaker.insertAfter(para, { thickness: LineWidth.MM_0_4, color: '#CCCCCC' });

SectionBreakMaker

import { SectionBreakMaker } from 'ownhwpx';

// Append a new section with landscape layout
SectionBreakMaker.append(doc, { landscape: true });

// Insert a section at a specific position
SectionBreakMaker.insertAt(doc, 1, { marginTop: 3000 });

FieldFinder

import { FieldFinder } from 'ownhwpx';

// Find all fields named "CustomerName"
const results = FieldFinder.find(doc, 'CustomerName', false);
for (const r of results) {
  console.log(r.beginField?.name, r.endField?.beginIDRef);
}

Test Reference

| 기능 | 테스트 파일 | 테스트 범위 | |------|------------|------------| | Reader/Writer 기초 | src/reader/HWPXReader.test.ts | 파일 읽기, 쓰기, 라운드트립, BlankFileMaker, TextExtractor, ObjectFinder | | TocMaker | src/tool/toc/TocMaker.test.ts | TOC 생성, 제목 찾기, maxLevel, TOC 삽입 | | 종합 테스트 | src/test/comprehensive.test.ts | 아래 전 범위 포함 | | ├ 기본 텍스트 | BasicText | 텍스트 삽입, 멀티라인, 유니코드, Tab, 라운드트립 | | ├ 문자 서식 | CharFormatting | charPrIDRef, charProperties 참조 | | ├ 표 | Tables | 표 생성/속성/셀병합, CellAddr/CellSpan, SimpleTable 읽기 | | ├ 그리기 개체 | DrawingObjects | Rectangle, Ellipse, Line, Arc, Curve, Polygon, ConnectLine, Picture, Equation, TextArt, Container, OLE, Video | | ├ Run 아이템 | RunItems | Compose, Dutmal | | ├ 폼 개체 | FormObjects | Buttons, ComboBox, Edit | | ├ 페이지 기능 | PageFeatures | HeaderFooter, MultiColumn, PageFunctions, PageSize/Margin, ChangeTrack | | ├ 라운드트립 | Roundtrip | 25개 reader_writer 테스트 파일 전체 | | ├ 에러 파일 | ErrorFiles | 20개 에러 케이스 | | ├ BlankFileMaker | BlankFileMaker | 생성, 문단/스타일/글꼴 확인, 라운드트립 | | ├ TextExtractor | TextExtractor | sample1 텍스트 추출 | | ├ ObjectFinder | ObjectFinder | 문단/Run/텍스트/섹션 검색, 다중 타입 | | ├ ValueConvertor | ValueConvertor | boolean/정수/실수 변환 | | └ TocMaker | TocMaker | 제목 찾기, maxLevel, TOC 삽입 |

# 특정 기능만 테스트하려면
npx vitest run -t "Tables"
npx vitest run -t "DrawingObjects"
npx vitest run -t "BlankFileMaker"
npx vitest run src/reader/HWPXReader.test.ts

# 전체 테스트
npm test

Performance

| Operation | Small (4KB) | Medium (138KB) | |-----------|-------------|-----------------| | Read | ~30ms | ~160ms | | Write | ~60ms | ~70ms | | Re-read | ~20ms | ~30ms |

Uses saxes SAX parser for efficient streaming XML parsing (4.2x faster than DOM-based parsing).

Document Model

HWPXFile
├── versionXMLFile
├── containerXMLFile
├── manifestXMLFile
├── contentHPFFile
├── headerXMLFile
│   ├── beginNum
│   ├── refList (fontfaces, styles, borderFills, etc.)
│   └── compatibleDocument
├── sectionXMLFileList[]
│   ├── paraListCore
│   │   └── Para[]
│   │       └── runList
│   │           └── Run[]
│   │               └── itemList
│   │                   ├── T (text)
│   │                   ├── Ctrl (fieldBegin, bookmark, etc.)
│   │                   ├── Table (with rows/cells/nested paragraphs)
│   │                   ├── Picture / Rectangle / Ellipse / Arc / ...
│   │                   └── ComboBox / Edit / Button / ...
│   └── secPr (section properties: grid, page layout, notes, etc.)
├── settingsXMLFile
└── masterPageXMLFileList[]

Limitations

HWPX 포맷 스펙상 근본적인 제약이 있어 완전한 구현이 불가능한 항목들입니다.

자동 목차 생성

TocMaker로 Heading 목록 수집 및 TOC paragraph 삽입은 가능하나, HWPX의 TOC 페이지 번호는 HWP Viewer가 렌더링 시 계산합니다. 라이브러리 레벨에서 정확한 페이지 번호를 알 수 없습니다.

수평선(구분선)

HWPX 스펙에 <hr> 개념이 없습니다. Line 벡터 도형으로 선을 삽입하거나 문단 테두리로 근사할 수 있으나, 완전한 <hr> 동작과는 차이가 있습니다.

구역 나누기

HWPX 구역은 별도 XML 파일 단위로 관리됩니다. MS Word처럼 문단 중간에 inline section break를 삽입하는 개념이 없습니다.

Project Status

| Layer | Files | Status | |-------|-------|--------| | Object Model | 329 | ✅ Complete | | Reader | 220 | ✅ Complete | | Writer | 108 | ✅ Complete | | Tool | 143 | ✅ Complete (Finder, TextExtractor, BlankMaker, Template, TOC, TableFromArray, Hr, SectionBreak) | | Total | 825+ | ✅ 0 compilation errors |

Requirements

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

Dependencies

  • saxes — SAX XML parser (ISC)
  • archiver — ZIP creation (MIT)

Development

# Install dependencies
npm install

# Build
npm run build

# Test
npm test

# Watch mode
npm run test:watch

# Type check
npx tsc --noEmit

License

Apache-2.0

This project is a TypeScript port of hwpxlib by Park.sungkyon (박성견), which is also Apache-2.0 licensed. See LICENSE for details.