- BizinfoApiSource: bizinfo.go.kr 자체 crtfcKey 사용, /uss/rss/bizinfoApi.do - 페이지네이션 없음 → totCnt 파악 후 전체 일괄 요청(1,463건 검증) - bsnsSumryCn(HTML) 본문 → stripHtml 로 태그 제거, 단일패스 적재(전건 DETAILED) - reqstBeginEndDe "YYYY-MM-DD ~ ..." → 신청기간 파싱(706건), 텍스트형은 null - util: stripHtml, parsePeriodRange 추가 - 데몬 4소스 가동: kstartup/bizinfo/mss/smes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
87 lines
2.4 KiB
JavaScript
87 lines
2.4 KiB
JavaScript
// 공용 유틸: HTML 엔티티 디코드, YYYYMMDD 날짜 파싱.
|
|
|
|
const ENTITIES = {
|
|
'&': '&', '<': '<', '>': '>', '"': '"',
|
|
''': "'", ''': "'", ' ': ' ',
|
|
};
|
|
|
|
export function decodeEntities(s) {
|
|
if (s == null) return null;
|
|
return String(s)
|
|
.replace(/&|<|>|"|'|'| /g, (m) => ENTITIES[m])
|
|
.replace(/&#(\d+);/g, (_, n) => String.fromCharCode(Number(n)))
|
|
.trim();
|
|
}
|
|
|
|
/**
|
|
* 'YYYYMMDD' 또는 'YYYY-MM-DD' 를 Date 로. 형식 불일치면 null.
|
|
*/
|
|
export function parseYmd(s) {
|
|
if (s == null) return null;
|
|
const digits = String(s).replace(/[^0-9]/g, '');
|
|
if (digits.length !== 8) return null;
|
|
const y = Number(digits.slice(0, 4));
|
|
const m = Number(digits.slice(4, 6));
|
|
const d = Number(digits.slice(6, 8));
|
|
if (m < 1 || m > 12 || d < 1 || d > 31) return null;
|
|
return new Date(Date.UTC(y, m - 1, d));
|
|
}
|
|
|
|
/**
|
|
* 'YY-MM-DD' / 'YYYY-MM-DD' / 'YYYYMMDD' / 'YYMMDD' 를 Date 로. 불일치면 null.
|
|
* 6자리는 20YY 로 간주한다.
|
|
*/
|
|
export function parseFlexibleDate(s) {
|
|
if (s == null) return null;
|
|
const d = String(s).replace(/[^0-9]/g, '');
|
|
let y;
|
|
let mo;
|
|
let day;
|
|
if (d.length === 8) {
|
|
y = Number(d.slice(0, 4));
|
|
mo = Number(d.slice(4, 6));
|
|
day = Number(d.slice(6, 8));
|
|
} else if (d.length === 6) {
|
|
y = 2000 + Number(d.slice(0, 2));
|
|
mo = Number(d.slice(2, 4));
|
|
day = Number(d.slice(4, 6));
|
|
} else {
|
|
return null;
|
|
}
|
|
if (mo < 1 || mo > 12 || day < 1 || day > 31) return null;
|
|
return new Date(Date.UTC(y, mo - 1, day));
|
|
}
|
|
|
|
export function nonEmpty(s) {
|
|
if (s == null) return null;
|
|
const t = String(s).trim();
|
|
return t === '' ? null : t;
|
|
}
|
|
|
|
/**
|
|
* HTML 태그 제거 후 엔티티 디코드. <br>/<p>/</div> 는 줄바꿈으로.
|
|
*/
|
|
export function stripHtml(s) {
|
|
if (s == null) return null;
|
|
const text = String(s)
|
|
.replace(/<\s*br\s*\/?>/gi, '\n')
|
|
.replace(/<\/(p|div|li|tr|h[1-6])\s*>/gi, '\n')
|
|
.replace(/<[^>]+>/g, '')
|
|
.replace(/[ \t]+\n/g, '\n')
|
|
.replace(/\n{3,}/g, '\n\n')
|
|
.trim();
|
|
return decodeEntities(text);
|
|
}
|
|
|
|
/**
|
|
* "A ~ B" 형식 기간 문자열을 {start, end} Date 로. 날짜형이 아니면 null.
|
|
*/
|
|
export function parsePeriodRange(s, sep = '~') {
|
|
if (s == null) return { start: null, end: null };
|
|
const segs = String(s).split(sep).map((x) => x.trim());
|
|
return {
|
|
start: parseFlexibleDate(segs[0]),
|
|
end: parseFlexibleDate(segs[1] || segs[0]),
|
|
};
|
|
}
|