hwpkit-dev 0.0.2 → 0.0.3
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/ .npmignore +4 -2
- package/README.md +39 -2
- package/dist/index.d.mts +41 -14
- package/dist/index.d.ts +41 -14
- package/dist/index.js +3553 -1159
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3553 -1159
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/playground/index.html +346 -0
- package/playground/main.ts +302 -0
- package/playground/vite.config.ts +16 -0
- package/src/contract/decoder.ts +1 -0
- package/src/contract/encoder.ts +6 -1
- package/src/core/BaseDecoder.ts +118 -0
- package/src/core/BaseEncoder.ts +146 -0
- package/src/decoders/docx/DocxDecoder.ts +743 -151
- package/src/decoders/html/HtmlDecoder.ts +366 -0
- package/src/decoders/hwp/HwpScanner.ts +325 -157
- package/src/decoders/hwpx/HwpxDecoder.ts +785 -297
- package/src/decoders/md/MdDecoder.ts +4 -4
- package/src/encoders/docx/DocxEncoder.ts +504 -240
- package/src/encoders/html/HtmlEncoder.ts +17 -19
- package/src/encoders/hwp/HwpEncoder.ts +1466 -859
- package/src/encoders/hwpx/HwpxEncoder.ts +1477 -469
- package/src/encoders/hwpx/constants.ts +148 -0
- package/src/encoders/hwpx/utils.ts +198 -0
- package/src/encoders/md/MdEncoder.ts +20 -15
- package/src/model/builders.ts +4 -4
- package/src/model/doc-props.ts +19 -5
- package/src/model/doc-tree.ts +12 -4
- package/src/pipeline/Pipeline.ts +7 -3
- package/src/pipeline/registry.ts +13 -2
- package/src/safety/StyleBridge.ts +51 -6
- package/src/toolkit/ArchiveKit.ts +56 -0
- package/src/toolkit/StyleMapper.ts +221 -0
- package/src/toolkit/UnitConverter.ts +138 -0
- package/src/toolkit/XmlKit.ts +0 -5
- package/test-styling.ts +210 -0
- package/hwp-analyze.ts +0 -90
- package/inspect-doc.ts +0 -57
- package/output_test.hwp +0 -0
- package/test-docx-to-hwp.ts +0 -45
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseDecoder - 모든 디코더의 기본 클래스
|
|
3
|
+
*
|
|
4
|
+
* 공통 로직을 제공하여 각 포맷별 디코더가 포맷 특유의 로직만 구현하도록 합니다.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Decoder } from '../contract/decoder';
|
|
8
|
+
import type { Outcome } from '../contract/result';
|
|
9
|
+
import type { DocRoot } from '../model/doc-tree';
|
|
10
|
+
import { TextKit } from '../toolkit/TextKit';
|
|
11
|
+
import { ArchiveKit } from '../toolkit/ArchiveKit';
|
|
12
|
+
|
|
13
|
+
// ─── BaseDecoder ───────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export abstract class BaseDecoder implements Decoder {
|
|
16
|
+
readonly format: string = this.getFormat();
|
|
17
|
+
readonly aliases: string[] = this.getAliases();
|
|
18
|
+
|
|
19
|
+
/** 포맷 이름 반환 (하위 클래스에서 오버라이드) */
|
|
20
|
+
protected abstract getFormat(): string;
|
|
21
|
+
|
|
22
|
+
/** 별칭 목록 반환 (하위 클래스에서 필요 시 오버라이드) */
|
|
23
|
+
protected getAliases(): string[] { return []; }
|
|
24
|
+
|
|
25
|
+
/** 데이터 디코딩 (하위 클래스에서 오버라이드) */
|
|
26
|
+
abstract decode(data: Uint8Array): Promise<Outcome<DocRoot>>;
|
|
27
|
+
|
|
28
|
+
// ─── 공통 유틸리티 메서드 ──────────────────────────
|
|
29
|
+
|
|
30
|
+
/** 바이트를 UTF-8 문자열로 변환 */
|
|
31
|
+
protected bytesToString(data: Uint8Array): string {
|
|
32
|
+
return TextKit.decode(data);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 문자열을 UTF-8 바이트로 변환 */
|
|
36
|
+
protected stringToBytes(s: string): Uint8Array {
|
|
37
|
+
return TextKit.encode(s);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** XML 이스케이프 */
|
|
41
|
+
protected escapeXml(s: string): string {
|
|
42
|
+
return TextKit.escapeXml(s);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** XML 언이스케이프 */
|
|
46
|
+
protected unescapeXml(s: string): string {
|
|
47
|
+
return TextKit.unescapeXml(s);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** base64 문자열을 Uint8Array 로 변환 */
|
|
51
|
+
protected base64ToBytes(b64: string): Uint8Array {
|
|
52
|
+
return TextKit.base64Decode(b64);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Uint8Array 를 base64 문자열로 변환 */
|
|
56
|
+
protected bytesToBase64(data: Uint8Array): string {
|
|
57
|
+
return TextKit.base64Encode(data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** ZIP 해제 */
|
|
61
|
+
protected async unzip(data: Uint8Array): Promise<Map<string, Uint8Array>> {
|
|
62
|
+
return ArchiveKit.unzip(data);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** ZIP 압축 */
|
|
66
|
+
protected async zip(entries: { name: string; data: Uint8Array }[]): Promise<Uint8Array> {
|
|
67
|
+
return ArchiveKit.zip(entries);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** inflate 해제 */
|
|
71
|
+
protected async inflate(data: Uint8Array): Promise<Uint8Array> {
|
|
72
|
+
return ArchiveKit.inflate(data);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** deflate 압축 */
|
|
76
|
+
protected async deflate(data: Uint8Array): Promise<Uint8Array> {
|
|
77
|
+
return ArchiveKit.deflate(data);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 제어 문자 제거 */
|
|
81
|
+
protected stripControl(s: string): string {
|
|
82
|
+
return TextKit.stripControl(s);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 공백 정규화 */
|
|
86
|
+
protected normalizeWhitespace(s: string): string {
|
|
87
|
+
return TextKit.normalizeWhitespace(s);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** XML 파싱 (DOMParser 사용) */
|
|
91
|
+
protected parseXml(xmlString: string): Document {
|
|
92
|
+
const parser = new DOMParser();
|
|
93
|
+
return parser.parseFromString(xmlString, 'text/xml');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** XML 요소에서 텍스트 내용 추출 */
|
|
97
|
+
protected getTextContent(element: Element | null | undefined): string {
|
|
98
|
+
if (!element) return '';
|
|
99
|
+
return element.textContent ?? '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** XML 요소의 속성 값 추출 */
|
|
103
|
+
protected getAttr(element: Element | null | undefined, name: string): string | null {
|
|
104
|
+
return element?.getAttribute(name) ?? null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** XML 요소의 자식 요소 찾기 */
|
|
108
|
+
protected getChild(element: Element | null | undefined, tagName: string): Element | null {
|
|
109
|
+
if (!element) return null;
|
|
110
|
+
return element.querySelector(`>${tagName}`) ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** XML 요소의 모든 자식 요소 찾기 */
|
|
114
|
+
protected getChildren(element: Element | null | undefined, tagName: string): Element[] {
|
|
115
|
+
if (!element) return [];
|
|
116
|
+
return Array.from(element.querySelectorAll(`>${tagName}`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseEncoder - 모든 인코더의 기본 클래스
|
|
3
|
+
*
|
|
4
|
+
* 공통 로직을 제공하여 각 포맷별 인코더가 포맷 특유의 로직만 구현하도록 합니다.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Encoder } from '../contract/encoder';
|
|
8
|
+
import type { Outcome } from '../contract/result';
|
|
9
|
+
import type { DocRoot, AnyNode, ImgNode } from '../model/doc-tree';
|
|
10
|
+
import { TextKit } from '../toolkit/TextKit';
|
|
11
|
+
import { ArchiveKit } from '../toolkit/ArchiveKit';
|
|
12
|
+
|
|
13
|
+
// ─── 타입 정의 ───────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface ImageData {
|
|
16
|
+
node: ImgNode;
|
|
17
|
+
path: string;
|
|
18
|
+
data: Uint8Array;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ZipEntry {
|
|
22
|
+
name: string;
|
|
23
|
+
data: Uint8Array;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── BaseEncoder ───────────────────────────────
|
|
27
|
+
|
|
28
|
+
export abstract class BaseEncoder implements Encoder {
|
|
29
|
+
readonly format: string = this.getFormat();
|
|
30
|
+
readonly aliases: string[] = this.getAliases();
|
|
31
|
+
|
|
32
|
+
/** 포맷 이름 반환 (하위 클래스에서 오버라이드) */
|
|
33
|
+
protected abstract getFormat(): string;
|
|
34
|
+
|
|
35
|
+
/** 별칭 목록 반환 (하위 클래스에서 필요 시 오버라이드) */
|
|
36
|
+
protected getAliases(): string[] { return []; }
|
|
37
|
+
|
|
38
|
+
/** 문서 인코딩 (하위 클래스에서 오버라이드) */
|
|
39
|
+
abstract encode(doc: DocRoot): Promise<Outcome<Uint8Array>>;
|
|
40
|
+
|
|
41
|
+
// ─── 공통 유틸리티 메서드 ───────────────────────────
|
|
42
|
+
|
|
43
|
+
/** 문서 내 모든 이미지 노드 수집 */
|
|
44
|
+
protected collectImages(doc: DocRoot): ImgNode[] {
|
|
45
|
+
const images: ImgNode[] = [];
|
|
46
|
+
this.collectImagesRecursive(doc, images);
|
|
47
|
+
return images;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 재귀적으로 이미지 수집 */
|
|
51
|
+
private collectImagesRecursive(node: AnyNode, images: ImgNode[]): void {
|
|
52
|
+
if (node.tag === 'img') {
|
|
53
|
+
images.push(node);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const children = this.getChildren(node);
|
|
58
|
+
for (const child of children) {
|
|
59
|
+
this.collectImagesRecursive(child, images);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 노드의 자식 노드 반환 */
|
|
64
|
+
private getChildren(node: AnyNode): AnyNode[] {
|
|
65
|
+
switch (node.tag) {
|
|
66
|
+
case 'root':
|
|
67
|
+
return node.kids;
|
|
68
|
+
case 'sheet':
|
|
69
|
+
return [...node.kids, ...(node.headers?.default ?? []), ...(node.footers?.default ?? [])];
|
|
70
|
+
case 'para':
|
|
71
|
+
return node.kids;
|
|
72
|
+
case 'span':
|
|
73
|
+
return node.kids;
|
|
74
|
+
case 'link':
|
|
75
|
+
return node.kids;
|
|
76
|
+
case 'row':
|
|
77
|
+
return node.kids;
|
|
78
|
+
case 'cell':
|
|
79
|
+
return node.kids;
|
|
80
|
+
case 'grid':
|
|
81
|
+
return node.kids;
|
|
82
|
+
default:
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** base64 문자열을 Uint8Array 로 변환 */
|
|
88
|
+
protected base64ToBytes(b64: string): Uint8Array {
|
|
89
|
+
return TextKit.base64Decode(b64);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Uint8Array 를 base64 문자열로 변환 */
|
|
93
|
+
protected bytesToBase64(data: Uint8Array): string {
|
|
94
|
+
return TextKit.base64Encode(data);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** XML 이스케이프 */
|
|
98
|
+
protected escapeXml(s: string): string {
|
|
99
|
+
return TextKit.escapeXml(s);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** XML 언이스케이프 */
|
|
103
|
+
protected unescapeXml(s: string): string {
|
|
104
|
+
return TextKit.unescapeXml(s);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 문자열을 UTF-8 바이트로 변환 */
|
|
108
|
+
protected stringToBytes(s: string): Uint8Array {
|
|
109
|
+
return TextKit.encode(s);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 바이트를 UTF-8 문자열로 변환 */
|
|
113
|
+
protected bytesToString(data: Uint8Array): string {
|
|
114
|
+
return TextKit.decode(data);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** ZIP 압축 */
|
|
118
|
+
protected async zip(entries: ZipEntry[]): Promise<Uint8Array> {
|
|
119
|
+
return ArchiveKit.zip(entries);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** ZIP 해제 */
|
|
123
|
+
protected async unzip(data: Uint8Array): Promise<Map<string, Uint8Array>> {
|
|
124
|
+
return ArchiveKit.unzip(data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** deflate 압축 */
|
|
128
|
+
protected async deflate(data: Uint8Array): Promise<Uint8Array> {
|
|
129
|
+
return ArchiveKit.deflate(data);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** inflate 해제 */
|
|
133
|
+
protected async inflate(data: Uint8Array): Promise<Uint8Array> {
|
|
134
|
+
return ArchiveKit.inflate(data);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 제어 문자 제거 */
|
|
138
|
+
protected stripControl(s: string): string {
|
|
139
|
+
return TextKit.stripControl(s);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 공백 정규화 */
|
|
143
|
+
protected normalizeWhitespace(s: string): string {
|
|
144
|
+
return TextKit.normalizeWhitespace(s);
|
|
145
|
+
}
|
|
146
|
+
}
|