fix(est): preserve links and formula precision

This commit is contained in:
2026-11-05 12:51:49 +09:00
parent d922ad13f3
commit 516d2ae329
10 changed files with 215 additions and 19 deletions
+19 -15
View File
@@ -17,15 +17,16 @@ const EstText = (() => {
function expression(text) {
return clean(
String(text).replace(
/\x0e[^\x0e]*\x0e|\x13[^\x13]*\x13|\x15[^\x15]*\x15|\{[^}]*\}|\([가-힣][^()]*\)|\(\s*\d+(?:\.\d+)?\s*[˚°]\s*\)|<[^>]*>/g,
/\x0e[^\x0e]*\x0e|\x13[^\x13]*\x13|\x15[^\x15]*\x15|\{[^}]*\}|〔[^〕]*〕|[[^]]*]|\([가-힣][^()]*\)|\(\s*\d+(?:\.\d+)?\s*[˚°]\s*\)|<[^>]*>/g,
"",
),
)
.replace(
/(\d)\s*(?:공\/?㎥|공|단|면|겹|경간|개월|월|년|인|㎥|㎢|㎡|hr|sec|min|kwh|kw|km|ton|kg|㎏|㎞|ℓ|회|개소|개|대|분|초|본|장|매|병|조|식|일|시간|원|포|동|주|톤|μ|₩|EA|mm|cm|m3|m2|m|g|L|a)(?:\/(?:개소|경간|개|대|본|인|분|포|매|주|톤|\$|hr|일|㎥|㎡|m|a|EA))?/gi,
/(?<![A-Za-z0-9_가-힣.])(\d[\d,.]*|\.\d+)\s*(?:공\/?㎥|공|단|면|겹|경간|개월|월|년|인|㎥|㎢|㎠|㎡|hr|sec|min|kwh|kw|km|ton|kg|㎏|㎞|ℓ|회|개소|개|대|분|초|본|장|매|병|조|식|일|시간|원|포|동|주|톤|μ|₩|EA|mm|cm|m3|m2|m|g|L|a)(?:\/(?:개소|경간|개|대|본|인|분|포|매|주|톤|공|회|조|span|ton|\$|hr|일|㎥|㎡|m3|m2|m|a|EA))?/gi,
"$1",
)
.replace(/(?<=\d)\s*\/(?:일|㎥|㎡)(?=\s|$)/g, "")
.replace(/(?<=\d)\s*\/\s*(?:일|㎥|㎡|hr)(?=\s|$|[×*+)])/gi, "")
.replace(/[】]〕]\s*$/, "")
.replace(/(?<=\))\s*(?:개|인|본|면|단|회)(?:\/(?:㎡|㎥|hr))?/g, "")
.replace(/^\s*[-*•]?\s*[가-힣][가-힣\s]*\s+(?=[\d.(])/, "")
.replace(/[①-⑳]/g, "")
@@ -99,7 +100,7 @@ const EstText = (() => {
errors.push(`${head.Code}:${index + 1} ${error.message}`);
}
}
let text = original;
let text = original.replace(/\{([+-])\}/g, "$1");
if (currentSource && sc < 0 && component[sc])
text = text.replace(
/\x03([-\d.,]+)\x03/g,
@@ -134,14 +135,14 @@ const EstText = (() => {
const assignedNames = new Map();
let componentDeclaration;
try {
if (matched && /─/.test(text)) {
if ((matched || /^\s*[A-Za-z][A-Za-z0-9]*\s*=/.test(text)) && /─/.test(text)) {
const above = clean(rows[index - 1]?.LineData),
below = clean(rows[index + 1]?.LineData);
const columns = (value) =>
value
.replace(/\s*([×÷+*/−-])\s*/g, "$1")
.replace(/\s*([×÷·+*/−-])\s*/g, "$1")
.trim()
.split(/\s{2,}/);
.split(/\s+/);
const tops = columns(above),
bottoms = columns(below);
let part = 0;
@@ -187,7 +188,7 @@ const EstText = (() => {
let body = stripLabels(text.slice(0, text.indexOf("\x12"))).replace(/=\s*$/, "");
if (body.includes(":")) body = body.slice(body.indexOf(":") + 1);
const parts = body.split("=");
if (parts.length > 1 && /^[A-Za-z][A-Za-z0-9]*$/.test(parts[0].trim())) {
if (parts.length > 1 && /^[A-Za-z가-힣][A-Za-z0-9가-힣]*$/.test(parts[0].trim())) {
componentDeclaration = parts[0].trim();
body = parts[1];
} else body = parts[0];
@@ -210,7 +211,9 @@ const EstText = (() => {
end = assignments[j + 1]?.index ?? text.length;
let body = text.slice(assignment.index + assignment[0].length, end);
if (!matched && /^\s*\*.*\*\s*$/.test(text)) body = body.replace(/\s+\*\s*$/, "");
body = body.split(/\{(?!%)|\s+[::]\s*/)[0].replace(/[},,]\s*$/, "");
body = body.replace(/\{[^}]*\}/g, "");
if (!matched) body = body.split("{")[0];
body = body.split(/\s+[::]\s*/)[0].replace(/[,,]\s*$/, "");
if (body.includes("─") && calculated !== null) {
variables[assignment[1]] = floating(calculated, 2, 1);
continue;
@@ -265,7 +268,7 @@ const EstText = (() => {
.pop();
if (body.includes(":")) body = body.slice(body.indexOf(":") + 1);
body = expression(body)
.replace(/\s*([×÷+*/−-])\s*/g, "$1")
.replace(/\s*([×÷·+*/−-])\s*/g, "$1")
.trim()
.split(/\s{2,}/)
.pop();
@@ -278,7 +281,8 @@ const EstText = (() => {
explicit?.digits ??
(sc < 0 || num(row.LinkIndex) !== 0
? (num(row.Level) === finalLevel && text.includes("\x1a")) ||
index === singleComponent
index === singleComponent ||
options.CostValueTrunc
? 0
: 1
: Math.max(2, matched[1].split(".")[1]?.length || 0));
@@ -289,8 +293,8 @@ const EstText = (() => {
results.length > 1 && sc < 0 && !explicit && row.Precision === undefined;
calculated = floating(
calculated,
intermediate ? Math.max(2, matched[1].split(".")[1]?.length || 0) : digits,
intermediate ? 1 : method,
intermediate ? (options.MidCostDiget ?? 2) : digits,
intermediate ? 2 : method,
);
const firstValue = calculated;
let rendered = text.replace(marker, `\x12${calculated}\x12`);
@@ -311,9 +315,9 @@ const EstText = (() => {
),
format?.digits ??
(intermediate && part < results.length - 1
? Math.max(2, next[1].split(".")[1]?.length || 0)
? (options.MidCostDiget ?? 2)
: digits),
format?.method ?? (intermediate && part < results.length - 1 ? 1 : method),
format?.method ?? (intermediate && part < results.length - 1 ? 2 : method),
);
assignmentName = assignedNames.get(next.index) || assignmentName;
if (assignmentName) variables[assignmentName] = calculated;