redscv95
Initial deploy: korean-privacy-law-mcp v0.0.1 (37 tools, RAG 2,202 chunks)
3798d02
Raw
History Blame Contribute Delete
4.23 kB
/**
* ๋ฒ•์ œ์ฒ˜ API XML ์‘๋‹ต ํŒŒ์„œ.
* ๋‹จ์ˆœ ํƒœ๊ทธ ์ถ”์ถœ์€ regex(๋น ๋ฅด๊ณ  ๊ฐ€๋ฒผ์›€), ๊ตฌ์กฐํ™” ํŒŒ์‹ฑ์€ fast-xml-parser ์‚ฌ์šฉ.
*/
import { XMLParser } from "fast-xml-parser";
/**
* `<![CDATA[...]]>` wrapper ์ž๋™ ์ œ๊ฑฐ.
* ๋ฒ•์ œ์ฒ˜ XML์ด ํ•œ๊ธ€ยทํŠน์ˆ˜๋ฌธ์ž ํ•„๋“œ๋ฅผ CDATA๋กœ ๊ฐ์‹ธ์„œ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— ํ•„์ˆ˜.
*/
function unwrapCdata(s: string): string {
const m = s.match(/^<!\[CDATA\[([\s\S]*?)\]\]>$/);
return m && m[1] !== undefined ? m[1] : s;
}
/**
* ํƒœ๊ทธ 1๊ฐœ ์ถ”์ถœ โ€” ์ฒซ ๋งค์นญ๋งŒ ๋ฐ˜ํ™˜.
* **case-sensitive** โ€” ๋ฒ•์ œ์ฒ˜ XML ์‘๋‹ต์— `<Ppc>`(rootTag)์™€ `<ppc>`(itemTag)๊ฐ€
* ์ผ€์ด์Šค๋งŒ ๋‹ค๋ฅธ ์ฑ„ ๊ณต์กดํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด case-insensitive ๋งค์นญ ์‹œ outer ํƒœ๊ทธ๊ฐ€
* inner์˜ ๋‹ซ๋Š” ํƒœ๊ทธ(`</ppc>`)์— ์ž˜๋ฆผ.
*
* CDATA wrapper ์ž๋™ ์ œ๊ฑฐ โ€” ํ•œ๊ธ€ ํ•„๋“œ(๋ฒ•๋ น๋ช…ยท์•ˆ๊ฑด๋ช… ๋“ฑ)๋Š” ๊ฑฐ์˜ ๋ชจ๋‘ CDATA๋กœ ๊ฐ์‹ธ์ง.
*/
export function extractTag(xml: string, tagName: string): string {
// ์•ˆ์ „ํ•œ ํƒœ๊ทธ๋ช… (์˜๋ฌธยทํ•œ๊ธ€ยท์ˆซ์ž๋งŒ ํ—ˆ์šฉ)
if (!/^[๊ฐ€-ํžฃA-Za-z0-9_]+$/.test(tagName)) {
return "";
}
const re = new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)</${tagName}>`);
const m = xml.match(re);
if (!m || !m[1]) return "";
return unwrapCdata(m[1].trim());
}
/** ํƒœ๊ทธ N๊ฐœ ์ถ”์ถœ โ€” ๋ชจ๋“  ๋งค์นญ ๋ฐ˜ํ™˜. case-sensitive + CDATA unwrap (`extractTag`์™€ ๋™์ผ). */
export function extractTagAll(xml: string, tagName: string): string[] {
if (!/^[๊ฐ€-ํžฃA-Za-z0-9_]+$/.test(tagName)) {
return [];
}
const re = new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)</${tagName}>`, "g");
const out: string[] = [];
let m: RegExpExecArray | null;
while ((m = re.exec(xml)) !== null) {
if (m[1] !== undefined) out.push(unwrapCdata(m[1].trim()));
}
return out;
}
export interface SearchXmlResult<T> {
totalCnt: number;
page: number;
items: T[];
}
export interface ParseSearchOptions {
/** ์ด๊ฑด์ˆ˜ ํƒœ๊ทธ๋ช… (๊ธฐ๋ณธ "totalCnt"). aiSearch๋Š” "๊ฒ€์ƒ‰๊ฒฐ๊ณผ๊ฐœ์ˆ˜" ์‚ฌ์šฉ. */
totalCntTag?: string;
/** ํŽ˜์ด์ง€ ํƒœ๊ทธ๋ช… (๊ธฐ๋ณธ "page") */
pageTag?: string;
}
/**
* ๋ฒ•์ œ์ฒ˜ ๊ฒ€์ƒ‰ ์‘๋‹ต ํŒŒ์‹ฑ.
* <{rootTag}><totalCnt>N</totalCnt><page>N</page><{itemTag}>...</{itemTag}>...</{rootTag}>
*
* mapper๋Š” ๊ฐ itemTag ๋‚ด๋ถ€ XML์„ ๋ฐ›์•„ ๋„๋ฉ”์ธ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜.
* ์ผ๋ถ€ endpoint๋Š” totalCnt ํƒœ๊ทธ๋ช…์ด ๋‹ค๋ฆ„ (์˜ˆ: aiSearch=๊ฒ€์ƒ‰๊ฒฐ๊ณผ๊ฐœ์ˆ˜) โ€” options๋กœ override.
*/
export function parseSearchXML<T>(
xml: string,
rootTag: string,
itemTag: string,
mapper: (itemXml: string) => T,
options: ParseSearchOptions = {}
): SearchXmlResult<T> {
const totalCntTag = options.totalCntTag ?? "totalCnt";
const pageTag = options.pageTag ?? "page";
const totalCnt = parseInt(extractTag(xml, totalCntTag), 10) || 0;
const page = parseInt(extractTag(xml, pageTag), 10) || 1;
// rootTag ๋‚ด๋ถ€์—์„œ๋งŒ itemTag ๊ฒ€์ƒ‰ (์„ ํƒ์  โ€” ๋ถ€์ •ํ™•ํ•œ ํ•„ํ„ฐ๋ง ํšŒํ”ผ)
const rootContent = extractTag(xml, rootTag) || xml;
const itemContents = extractTagAll(rootContent, itemTag);
const items = itemContents.map(mapper);
return { totalCnt, page, items };
}
const DEFAULT_PARSER = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "@_",
parseTagValue: false,
parseAttributeValue: false,
trimValues: true,
});
/**
* ์ „์ฒด XML โ†’ ์ค‘์ฒฉ ๊ฐ์ฒด. ๋ณต์žกํ•œ ๊ตฌ์กฐ(๋ฒ•๋ น ๋ณธ๋ฌธ ๋“ฑ) ํŒŒ์‹ฑ์šฉ.
* ๋‹จ์ผ vs ๋ฐฐ์—ด ์ •๊ทœํ™”๋Š” ํ˜ธ์ถœ์ž ์ฑ…์ž„.
*/
export function parseXML(xml: string): unknown {
return DEFAULT_PARSER.parse(xml);
}
/** ๋‹จ์ผ ๊ฐ์ฒด ๋˜๋Š” ๋ฐฐ์—ด์„ ํ•ญ์ƒ ๋ฐฐ์—ด๋กœ ์ •๊ทœํ™” (fast-xml-parser quirk ์ฒ˜๋ฆฌ) */
export function asArray<T>(value: T | T[] | undefined | null): T[] {
if (value === undefined || value === null) return [];
return Array.isArray(value) ? value : [value];
}
/**
* ๋ณธ๋ฌธ ์•ˆ์˜ HTML ํƒœ๊ทธ ์ œ๊ฑฐ + ์—”ํ‹ฐํ‹ฐ ๋””์ฝ”๋”ฉ.
* ์˜๋ฌธ๋ฒ•๋ น(elaw) ์‘๋‹ต์— ๊ฒ€์ƒ‰ ๊ฐ•์กฐ `<strong>` ํƒœ๊ทธ๊ฐ€ ๋“ค์–ด์˜ค๋Š” quirk ๋“ฑ ์ฒ˜๋ฆฌ.
*/
export function stripHtmlTags(s: string): string {
if (!s) return s;
return s
.replace(/<[^>]+>/g, "")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&amp;/g, "&")
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&nbsp;/g, " ")
.trim();
}