Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1

This commit is contained in:
2026-09-28 10:51:57 +09:00
2 changed files with 97 additions and 10 deletions
+20 -10
View File
@@ -252,29 +252,39 @@ function unescapeMsoNumFmt(v: string): string {
return v.replace(/^"|"$/g, "").replace(/\\(.)/g, "$1");
}
const BORDER_STYLE_MAP: Record<string, BorderLine> = {
solid: "thin",
dashed: "dashed",
dotted: "dotted",
double: "double",
};
/** pt 굵기 → 우리 선 종류(엑셀 내보내기 흔한 값 기준 — hairline ≈ .5pt 이하 · thin ≈ 1pt · medium ≈
* 1.5~2pt · thick ≈ 2pt 넘음). `solid` 아닌 것은 굵기와 상관없이 그 모양. */
function ptToBorderLine(pt: number, cssStyle: string): BorderLine {
if (cssStyle === "dashed") return "dashed";
if (cssStyle === "dotted") return "dotted";
if (cssStyle === "double") return "double";
if (pt <= 0.5) return "hair";
if (pt <= 1.25) return "thin";
if (pt <= 2) return "medium";
return "thick";
}
function parseBorderShorthand(v: string): BorderSide | undefined {
const m = /([\d.]+)\s*(?:pt|px)\s+(solid|dashed|dotted|double)\s*([^;]*)/i.exec(v);
if (!m) return undefined;
const 선 = BORDER_STYLE_MAP[m[2].toLowerCase()] ?? "thin";
const 선 = ptToBorderLine(Number(m[1]), m[2].toLowerCase());
const 색 = cssColorToHex(m[3]);
return 색 ? { 선, 색 } : { 선 };
}
/** `<style>...</style>` 안 `.xl65 {...}` 짜임(엑셀이 인라인 대신 클래스로 서식을 실을 때) → 클래스별 선언. */
/** `<style>...</style>` 안 `.xl65 {...}` · `.xl65, .xl70 {...}`(묶음 선택자) 짜임(엑셀이 인라인 대신
* 클래스로 서식을 실을 때) → 클래스별 선언(같은 클래스가 여러 규칙에 나오면 뒤엣것이 이김). */
function parseClassRules(doc: Document): Record<string, Record<string, string>> {
const css = Array.from(doc.querySelectorAll("style"))
.map((s) => s.textContent ?? "")
.join("\n");
const rules: Record<string, Record<string, string>> = {};
for (const m of css.matchAll(/\.([a-zA-Z0-9_-]+)\s*\{([^}]*)\}/g)) {
rules[m[1]] = parseStyleAttr(m[2]);
for (const m of css.matchAll(/([^{}]+)\{([^}]*)\}/g)) {
const decls = parseStyleAttr(m[2]);
for (const selectorPart of m[1].split(",")) {
const cls = /\.([a-zA-Z0-9_-]+)\s*$/.exec(selectorPart.trim());
if (cls) Object.assign((rules[cls[1]] ??= {}), decls);
}
}
return rules;
}
@@ -0,0 +1,77 @@
<!-- fixture_excel_style_paste.html — 엑셀이 표 서식(머리 채움 · 가는 선 · 합계 줄 위 굵은 선 · 숫자
쉼표)을 실제로 어떻게 싣는지 재현(브레인 9-28 사용자 지시 근거 = PLAN.md 12장). 서식 대부분이
인라인 style 이 아니라 <style> 안 클래스(.xl65 같은 이름 · 묶음 선택자 포함)에 있고 칸에는
class 만 두는 실제 엑셀 내보내기 짜임을 그대로 씀 — `parseHtmlTable`/`styleFromTd` 가 클래스
규칙을 인라인과 합쳐 읽는지 이 견본으로 시험. -->
<html
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns="http://www.w3.org/TR/REC-html40"
>
<head>
<meta charset="utf-8" />
<style>
<!--table
{mso-displayed-decimal-separator:"\.";
mso-displayed-thousand-separator:"\,";}
.xl_head, .xl_head2
{background:#D9D9D9;
font-weight:700;
text-align:center;
border:.5pt solid windowtext;}
.xl_num
{mso-number-format:"\#\,\#\#0";
text-align:right;
border:.5pt solid windowtext;}
.xl_label
{border:.5pt solid windowtext;}
.xl_total_label
{font-weight:700;
border-left:.5pt solid windowtext;
border-right:.5pt solid windowtext;
border-bottom:.5pt solid windowtext;
border-top:1.5pt solid windowtext;}
.xl_total_num
{mso-number-format:"\#\,\#\#0";
font-weight:700;
text-align:right;
border-left:.5pt solid windowtext;
border-right:.5pt solid windowtext;
border-bottom:.5pt solid windowtext;
border-top:1.5pt solid windowtext;}
-->
</style>
</head>
<body link="#0563C1" vlink="#954F72">
<!--StartFragment-->
<table
border="0"
cellpadding="0"
cellspacing="0"
width="288"
style="border-collapse: collapse; width: 216pt"
>
<tr height="20" style="height: 15pt">
<td class="xl_head" width="96" style="width: 72pt" x:str>품목</td>
<td class="xl_head2" width="96" style="width: 72pt" x:str>수량</td>
<td class="xl_head2" width="96" style="width: 72pt" x:str>금액</td>
</tr>
<tr height="20" style="height: 15pt">
<td class="xl_label" x:str>터파기</td>
<td class="xl_num" x:num>120</td>
<td class="xl_num" x:num>1200000</td>
</tr>
<tr height="20" style="height: 15pt">
<td class="xl_label" x:str>메우기</td>
<td class="xl_num" x:num>80</td>
<td class="xl_num" x:num>800000</td>
</tr>
<tr height="20" style="height: 15pt">
<td class="xl_total_label" x:str>합계</td>
<td class="xl_total_num" x:num>200</td>
<td class="xl_total_num" x:num>2000000</td>
</tr>
</table>
<!--EndFragment-->
</body>
</html>