Files
Aislo/tools/stmate-test/scr_edit_lang.js
T
eomsangdonandClaude Opus 5.5 3d8fddfc34 feat(stmate-test): 보고서·제잡비·식언어·수량산출·화면 묶음 합침 · 재계산 후 저장 무변경
- 보고서: 원본 STMATE 엑셀과 셀 대조(목록표·자재단가대비·중기·일위대가·산근 목록·설계내역서·원가계산서) · 조달청 XML 공식 XSD 검증 · PM_Prt 화면
- 제잡비: 원본 계산 순서 4형식(T/S/TA/SA) · 실무 7건 저장 결과키 전수 1원 · 요율표 조회 · 선택지 계산 · 제잡비·제비율 화면
- 식언어·수량: 함수·조건식·금액변수·문법 검사·수량변수·QCODE · 산근·구성·중기공식 편집 화면 · 순환 알림
- 화면: 원본 메뉴 체계·단축키 · 설계내역서 편집(행 넣기·지우기·이동·레벨·보관함) · 초기값 · 단가목록표
- 저장: 초기값(Start8x.Ini) 바뀐 키 저장 · 노임 환산 직종 = 초기값 CONLD · 산근·구성 결과에 적용단가 칸 채우지 않음(원본 0)
- DBF 글 칸 앞 빈칸 보존 · 시험 도우미 표준입력 utf8(한글 조각 깨짐 원인)
- 실무 5건 재계산 후 저장 = 바뀐 칸 0 · 시험 332건 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 19:12:04 +09:00

163 lines
7.3 KiB
JavaScript

/* -*- coding: utf-8 -*- */
/**
* 단가산출 편집기 글 도우미 (scr_edit_lang.js) — 코드 줄 만들기(F6/F7 · 중기비 Q·A·T·N) · 계표시 ·
* 금액결과 삭제 · 문법 강조 · 문법설명 · 순수 계산
* 근거: REA 30 TWM_EDIT_SAN(cBox_Trans 5택 · ComOp / * · ComQty Q A T N · Chk0Not 「단가가 0원인 비목은
* 입력안함」 · Act_InsSum1~5 · Act_InsCon1~5 · Memo_Use 문법 (1)~(10) · CBoxSystax · CBoxEnable)
* · result_text_restoration_rules(소계~총합계 · 제외금액 10종 · 참조줄 '=' 까지 남김) · 지식DB 26 §3
*/
const StmateSmtLang = (() => {
const esc = (s) =>
String(s == null ? "" : s).replace(
/[&<>"]/g,
(c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c],
);
const num = (v) => Number(v) || 0;
// 계표시 — 레벨 1~5 · 제외금액
const MARK_NAMES = ["소계", "계", "합계", "총계", "총합계"];
const marker = (level, excluded) =>
`(${excluded ? "-" : ""}${"=".repeat(Math.max(1, Math.min(5, level)))})`;
// 일반코드 입력 꼴(cBox_Trans) 0 코드 · 1 명칭 코드 · 2 명칭 규격 코드 · 3 명칭 규격 코드 단위 · 4 코드번호만
const TRANS = [
"코드 * 수량(0) =",
"명칭 코드 * 수량(0) =",
"명칭 규격 코드 * 수량(0) =",
"명칭 규격 코드 * 수량(0) 단위 =",
"코드번호(만 입력)",
];
const COMP_LABEL = { L: "노 무 비", M: "재 료 비", S: "경 비" };
/**
* 코드 줄 만들기 — row = COSTN 행 · opts { trans, op('/'|'*'), qtyVar('Q'|'A'|'T'|'N'), skipZero, qty }
* 중기(X)·일위대가(B)·산근(D)는 성분마다 한 줄(「'노 무 비' &X00006L& / Q =」) · 0원 성분은 skipZero 면 뺌
*/
function codeLines(code, row = {}, opts = {}) {
const c = String(code || "").trim();
const q = opts.qty === undefined || opts.qty === "" ? "0" : String(opts.qty);
if (/^[XBD]/.test(c)) {
const op = opts.op === "*" ? "*" : "/";
const v = /^[QATN]$/.test(opts.qtyVar || "") ? opts.qtyVar : "Q";
return ["L", "M", "S"]
.filter((k) => !opts.skipZero || num(row[`${k}AMT1`]) !== 0)
.map((k) => `'${COMP_LABEL[k]}' &${c}${k}& ${op} ${v} =`);
}
const nm = String(row.NAME || "").trim(),
sz = String(row.SIZE || "").trim(),
un = String(row.UNIT || "").trim();
const t = Number(opts.trans) || 0;
if (t === 4) return [`&${c}&`];
const label = t === 1 ? nm : t >= 2 ? `${nm} ${sz}`.trim() : "";
const unit = t === 3 && un ? `'${un}'` : "";
return [`${label ? `'${label}' ` : ""}&${c}& * ${q}${unit} =`];
}
/**
* 금액결과 삭제(Act_Del_ResultAmt) — 「소계」…「총합계(제외금액)」 글을 계표시로 · 참조 줄은 마지막 '=' 까지
* 남기고 뒤는 따옴표 주석 · '->' · 금액변수(A..)만 · 그 밖의 '=' 없는 글 줄은 따옴표로 감쌈
*/
function stripResults(text) {
const marks = {};
MARK_NAMES.forEach((n, i) => {
marks[n] = marker(i + 1, false);
marks[`${n}(제외금액)`] = marker(i + 1, true);
});
return String(text || "")
.split(/\r?\n/)
.map((ln) => {
const t = ln.trim();
if (marks[t]) return ln.replace(t, marks[t]);
const amps = (ln.match(/&/g) || []).length;
const eq = ln.lastIndexOf("=");
if (amps === 2 && eq > ln.lastIndexOf("&")) {
// ':= A01' 금액변수 · '->' 제외 · 첫 따옴표부터의 단위 주석만 남김
const tail = ln.slice(eq + 1);
const q = tail.indexOf("'");
const lbl = /^\s*A/.test(tail) ? tail.trim().slice(0, 3) : "";
const arrow = tail.includes("->") ? `${lbl ? " " : ""}->` : "";
return ln.slice(0, eq + 1) + lbl + arrow + (q >= 0 && !arrow ? tail.slice(q) : "");
}
if (
t &&
!t.includes("=") &&
!t.includes("'") &&
!/^\(-?=+\)/.test(t) &&
!t.startsWith(";;") &&
!t.startsWith("//")
)
return ln.replace(t, `'${t}'`);
return ln;
})
.join("\r\n");
}
// 문법 강조 — 주석문 · Text(따옴표) · 숫자 · 변수·단가코드 · 계표시
function highlight(line) {
const s = String(line);
const out = [];
let i = 0;
while (i < s.length) {
const rest = s.slice(i);
let m;
if (rest.startsWith("//")) {
out.push(`<span class="syn-cmt">${esc(rest)}</span>`);
break;
}
if (rest[0] === "'") {
const j = s.indexOf("'", i + 1);
const seg = j < 0 ? rest : s.slice(i, j + 1);
out.push(`<span class="syn-txt">${esc(seg)}</span>`);
i += seg.length;
continue;
}
if ((m = rest.match(/^&[^&\s]*&/)) || (m = rest.match(/^~[^~]*~/))) {
out.push(`<span class="syn-code">${esc(m[0])}</span>`);
i += m[0].length;
continue;
}
if ((m = rest.match(/^\(-?=+\)(?::\s*T\d{2})?/))) {
out.push(`<span class="syn-mark">${esc(m[0])}</span>`);
i += m[0].length;
continue;
}
if ((m = rest.match(/^\d+\.?\d*/))) {
out.push(`<span class="syn-num">${esc(m[0])}</span>`);
i += m[0].length;
continue;
}
if ((m = rest.match(/^[A-Za-z_][A-Za-z_0-9]*/))) {
out.push(`<span class="syn-var">${esc(m[0])}</span>`);
i += m[0].length;
continue;
}
out.push(esc(rest[0]));
i += 1;
}
return out.join("");
}
// 문법설명(CBoxSystax) — TWM_EDIT_SAN Memo_Use 요지
const GRAMMAR_HELP = [
"(1) Text — 계산과 무관한 글은 양쪽을 ' 로 막음",
"(2) 변수 — 「K = 1.0」 · 첫 글자 영문 · 대소문자 구분 없음 · 영문 뒤 숫자 가능(Q1, Q2)",
"(3) 계산식 — 「Q = qo*K*E/Cm」 소수 그대로 · 끝에 '=' 를 붙이면 단가산출서 소수 자리(ZCD)로 반올림",
" 자리 함수 round(A/B,4) · int(B/A) · A/B=[?:4R] 반올림 · [?:2U] 올림 · [?:0C] 절사",
"(4) 단가코드 — 「&L00084&」 앞뒤에 & · 짧게 &L84&",
"(5) 그림코드 — 「IMG(I00001)」 · 한 줄에 하나",
"(6) 단가코드+단가코드 불가 — 「0.208 * &L84& =」 「0.208 * &L87& =」 처럼 줄을 나눔",
"(7) 계표시 — (=)소계 (==)계 (===)합계 (====)총계 (=====)총합계 · (-=) 제외금액(셈은 하되 계에서 뺌)",
"(8) 주석문 — 「//」 뒤는 계산·인쇄에서 빠짐(한 줄 안)",
"(9) 손료계산 — [Q=0.208] * &L84& :=A01 금액변수 누적 · :=A01 -> 누적하되 제외 · (=):T01 누계번지",
" &PA01L:S& * 10'%' = · &RA01L:S& * 0.1 = · &PC_S:M& 마지막 계표시 이후 · &PS_:M& 처음부터",
"(10) 결과금액 조정 — 합계 × M·L·S 배율 · 결과적용 비목(각 해당 · 재료 · 노무 · 경비)",
"수량변수 — ~Q00001.QTY1~ 기본 수량변수 · ~Q00001.별명~ 확장 수량변수(수량산출서)",
"함수 — ∏ INT SIN COS TAN SQRT √ ROUND ROUNDDOWN ROUNDUP CUT LOG LN ABS EXP MOD POWER FACT IIF(조건, 참, 거짓)",
];
return { MARK_NAMES, TRANS, marker, codeLines, stripResults, highlight, GRAMMAR_HELP, esc };
})();
if (typeof window !== "undefined") window.STMATE_SMT_LANG = StmateSmtLang;
if (typeof module !== "undefined" && module.exports) module.exports = StmateSmtLang;