Spaces:
Running
Running
File size: 4,232 Bytes
3798d02 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | /**
* ๋ฒ์ ์ฒ 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(/</g, "<")
.replace(/>/g, ">")
.replace(/&/g, "&")
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/ /g, " ")
.trim();
}
|