Spaces:
Running
Running
| /** | |
| * ๋ฒ์ ์ฒ 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(); | |
| } | |