replace_hwp_text
Find and replace specified text in HWPX files. Provide file path, old text, new text, and optional output path.
Instructions
Find and replace text in an HWPX file. v0.2: only .hwpx is supported as input/output. Args: file_path, old_text, new_text, output_path (optional).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | ||
| old_text | Yes | ||
| new_text | Yes | ||
| output_path | No |
Implementation Reference
- src/tools/write.ts:60-91 (handler)Main handler function for replace_hwp_text tool. Validates input file existence, format compatibility, rejects .hwp files (v0.2 limitation), then delegates to mutateHwpxText() for the actual replacement logic.
export async function replaceHwpText(args: ReplaceTextArgs): Promise<string> { if (!existsSync(args.file_path)) { return `파일을 찾을 수 없습니다 (file not found): ${args.file_path}`; } let inFmt; try { inFmt = getFormatFromPath(args.file_path); } catch (e) { return (e as Error).message; } const outputPath = args.output_path && args.output_path.length > 0 ? args.output_path : defaultOutputPath(args.file_path, "modified"); try { ensureSameFormat(args.file_path, outputPath); } catch (e) { return (e as Error).message; } const reject = rejectIfHwp(args.file_path); if (reject) return reject; // .hwpx path — direct mutation, no rhwp export needed. try { const r = await mutateHwpxText(args.file_path, outputPath, { [args.old_text]: args.new_text, }); return `'${args.old_text}' → '${args.new_text}': ${r.total}건 교체 (replaced ${r.total})\n저장 (saved): ${outputPath}`; } catch (e) { return `텍스트 교체 오류 (replace error): ${(e as Error).message}`; } } - src/tools/write.ts:14-19 (schema)TypeScript interface defining input arguments: file_path, old_text, new_text (required), and output_path (optional).
export interface ReplaceTextArgs { file_path: string; old_text: string; new_text: string; output_path?: string; } - src/core/hwpx-mutate.ts:19-85 (helper)Core mutation engine that reads a .hwpx ZIP, performs text replacement inside <hp:t> XML text nodes across section*.xml and header.xml, then writes the modified .hwpx back to disk. Only replaces within text nodes to avoid corrupting XML structure.
export async function mutateHwpxText( inputPath: string, outputPath: string, replacements: Record<string, string> ): Promise<MutationResult> { const bytes = await readFile(inputPath); const zip = await JSZip.loadAsync(bytes); const counts: Record<string, number> = {}; let total = 0; // Mutate every XML carrying body content: section*.xml, header.xml (HF blocks), // and master pages. mimetype/content.hpf are excluded. const sectionFiles = Object.keys(zip.files).filter((n) => /^Contents\/(section\d+|header)\.xml$/i.test(n) ); for (const fname of sectionFiles) { const file = zip.files[fname]; let xml = await file.async("string"); for (const [key, value] of Object.entries(replacements)) { const escapedXml = xmlEscape(value); // Only replace inside <hp:t>...</hp:t> text nodes, to avoid touching // tag names or attribute values. const pattern = new RegExp( "(<hp:t(?:\\s[^>]*)?>)([^<]*)(" + escapeRegex(key) + ")([^<]*)(</hp:t>)", "g" ); let didReplace = true; while (didReplace) { didReplace = false; xml = xml.replace(pattern, (_match, open, pre, _hit, post, close) => { counts[key] = (counts[key] ?? 0) + 1; total += 1; didReplace = true; return open + pre + escapedXml + post + close; }); // Loop because a single node may contain multiple occurrences; // String.replace with /g consumes from current position, so one pass // catches all non-overlapping. Set didReplace=false after one pass. break; } } zip.file(fname, xml); } // mimetype must remain stored (uncompressed); JSZip preserves per-file // compression options if we re-set them. if (zip.files["mimetype"]) { const mt = await zip.files["mimetype"].async("string"); zip.file("mimetype", mt, { compression: "STORE" }); } const out = await zip.generateAsync({ type: "uint8array", compression: "DEFLATE", compressionOptions: { level: 6 }, }); await writeFile(outputPath, out); for (const k of Object.keys(replacements)) { if (counts[k] === undefined) counts[k] = 0; } return { total, perKey: counts }; } - src/server.ts:92-106 (registration)Tool registration in the TOOLS array with name 'replace_hwp_text', description, and JSON Schema input definition listing file_path, old_text, new_text as required and output_path as optional.
{ name: "replace_hwp_text", description: "Find and replace text in an HWPX file. v0.2: only .hwpx is supported as input/output. Args: file_path, old_text, new_text, output_path (optional).", inputSchema: { type: "object", properties: { file_path: { type: "string" }, old_text: { type: "string" }, new_text: { type: "string" }, output_path: { type: "string" }, }, required: ["file_path", "old_text", "new_text"], }, }, - src/server.ts:515-515 (registration)Handler mapping in the HANDLERS record that associates the 'replace_hwp_text' tool name string with the replaceHwpText function imported from write.ts.
replace_hwp_text: replaceHwpText,