knowledge(품셈md): 도구 check — 글자층 밖 글리프(U+E000–F8FF) 빼고 드러냄 · 호환 한자 통합 · 작성규칙 6장 두 줄
- PDF 사용자 영역 글리프는 ①② 에서 빼고 기계검사 칸에 「글자층 밖 글리프 N개(쪽) · 받아씀 N자」 - md 는 <!-- 글자층 밖 -->…<!-- /글자층 밖 --> 로 감싼 받아쓰기를 ①② 에서 뺌(감싸지 않으면 더함) - 호환 한자(U+F900–FAFF)만 통합 한자로 — NFKC 통째는 안 씀(㎥·①·㎡ 지킴) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgUhN55Z3BCYhUJTjwTNfj
This commit is contained in:
@@ -19,6 +19,7 @@ import difflib
|
||||
import hashlib
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
@@ -425,6 +426,17 @@ _TABLE_RULE = re.compile(r"^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$")
|
||||
#: 막음표 — 원문 글자가 md 문법으로 읽히지 않게 앞에 `\` 를 붙인 것(`\*` `\-` `\#` `\>` `\|` …)
|
||||
_ESCAPED = re.compile(r"\\([\\`*_{}\[\]()#+\-.!|>~^])")
|
||||
_HOLD = "\x00"
|
||||
#: 글자층 밖 — PDF 글자층의 사용자 영역 글리프(U+E000–F8FF)는 뜻 없는 코드(식의 「(m³)」·분수선).
|
||||
#: ①② 에서 빼고 개수·쪽을 기계검사 칸에 드러냄(③ 이 그림으로 봄). md 에서 그 자리를 받아쓴 글자는
|
||||
#: `<!-- 글자층 밖 -->…<!-- /글자층 밖 -->` 로 감싸면 ①② 에서 빼고 「받아씀 N자」로 드러냄.
|
||||
_PUA = re.compile("[\ue000-\uf8ff]")
|
||||
_OFF_OPEN, _OFF_CLOSE = "\x01", "\x02"
|
||||
_OFF_MARK = re.compile(r"<!--\s*(/?)글자층 밖\s*-->")
|
||||
|
||||
|
||||
def _same_han(ch: str) -> str:
|
||||
"""호환 한자(U+F900–FAFF) → 통합 한자(率 U+F9DB → 率 U+7387). ⚠ NFKC 통째로는 안 씀 — ㎥·①·㎡ 가 풀림."""
|
||||
return unicodedata.normalize("NFKC", ch) if "\uf900" <= ch <= "\ufaff" else ch
|
||||
|
||||
|
||||
def book_of(path: Path) -> dict:
|
||||
@@ -448,6 +460,7 @@ def md_lines(body: str) -> list[tuple[int, str]]:
|
||||
`<!-- 되풀이 머리: … -->` 는 셈 — 쪽을 넘은 표가 PDF 에서 되풀이한 머리 줄(md 표에서는 뺌 · 규칙 4-5).
|
||||
"""
|
||||
body = _REPEATED.sub(r"\1", body)
|
||||
body = _OFF_MARK.sub(lambda m: _OFF_CLOSE if m.group(1) else _OFF_OPEN, body)
|
||||
body = _COMMENT.sub(lambda m: "\n" * m.group(0).count("\n"), body)
|
||||
out = []
|
||||
for number, line in enumerate(body.split("\n"), start=1):
|
||||
@@ -490,12 +503,30 @@ def pdf_lines(doc, head: dict[str, str], footer: re.Pattern) -> list[tuple[int,
|
||||
return out
|
||||
|
||||
|
||||
def _split_off(lines: list[tuple[int, str]]) -> tuple[list[tuple[int, str]], list[tuple[int, str]]]:
|
||||
"""(셀 줄, 글자층 밖 줄) — PDF 는 사용자 영역 글리프 · md 는 `글자층 밖` 으로 감싼 글자를 떼어 냄."""
|
||||
kept, off, inside = [], [], False
|
||||
for at, line in lines:
|
||||
keep, gone = [], []
|
||||
for ch in line:
|
||||
if ch in (_OFF_OPEN, _OFF_CLOSE):
|
||||
inside = ch == _OFF_OPEN
|
||||
elif inside or _PUA.match(ch):
|
||||
gone.append(ch)
|
||||
else:
|
||||
keep.append(ch)
|
||||
kept.append((at, "".join(keep)))
|
||||
if "".join(gone).strip():
|
||||
off.append((at, "".join(gone)))
|
||||
return kept, off
|
||||
|
||||
|
||||
def _tight(lines: list[tuple[int, str]]) -> tuple[str, list[int]]:
|
||||
"""공백 뗀 글자 줄 + 글자마다 어디서 왔나(쪽 또는 md 줄)."""
|
||||
"""공백 뗀 글자 줄 + 글자마다 어디서 왔나(쪽 또는 md 줄) · 호환 한자는 통합 한자로."""
|
||||
text, where = [], []
|
||||
for at, line in lines:
|
||||
for ch in re.sub(r"\s", "", line):
|
||||
text.append(ch)
|
||||
text.append(_same_han(ch))
|
||||
where.append(at)
|
||||
return "".join(text), where
|
||||
|
||||
@@ -510,7 +541,9 @@ def _numbers(lines: list[tuple[int, str]]) -> tuple[collections.Counter, dict[st
|
||||
|
||||
|
||||
def compare(pdf: list[tuple[int, str]], md: list[tuple[int, str]]) -> dict:
|
||||
"""① 수(갯수까지) ② 글자(공백 뗌 · 갯수까지) — 양방향."""
|
||||
"""① 수(갯수까지) ② 글자(공백 뗌 · 갯수까지) — 양방향. 글자층 밖(사용자 영역 글리프 · 감싼 받아쓰기)은 빼고 따로 셈."""
|
||||
pdf, pdf_off = _split_off(pdf)
|
||||
md, md_off = _split_off(md)
|
||||
pdf_n, pdf_at = _numbers(pdf)
|
||||
md_n, md_at = _numbers(md)
|
||||
pdf_t, pdf_where = _tight(pdf)
|
||||
@@ -539,6 +572,11 @@ def compare(pdf: list[tuple[int, str]], md: list[tuple[int, str]]) -> dict:
|
||||
"char_missing": missing_c,
|
||||
"char_extra": extra_c,
|
||||
"spots": spots,
|
||||
"off_pdf": (
|
||||
sum(len(re.sub(r"\s", "", t)) for _, t in pdf_off),
|
||||
sorted({a for a, _ in pdf_off}),
|
||||
),
|
||||
"off_md": sum(len(re.sub(r"\s", "", t)) for _, t in md_off),
|
||||
}
|
||||
|
||||
|
||||
@@ -550,9 +588,14 @@ def verdict(result: dict) -> str:
|
||||
sum(result["char_extra"].values()),
|
||||
)
|
||||
word = "통과" if not any(counts) else "불통"
|
||||
return (
|
||||
line = (
|
||||
f"{word} · ①수 결손 {counts[0]} 허구 {counts[1]} · ②글자 빠짐 {counts[2]} 더함 {counts[3]}"
|
||||
)
|
||||
glyphs, pages = result.get("off_pdf", (0, []))
|
||||
if glyphs or result.get("off_md"): # ③ 이 그림으로 그 식을 꼭 보게
|
||||
where = f"({', '.join(map(str, pages))}쪽)" if pages else ""
|
||||
line += f" · 글자층 밖 글리프 {glyphs}개{where} · 받아씀 {result.get('off_md', 0)}자"
|
||||
return line
|
||||
|
||||
|
||||
def fingerprint(text: str) -> str:
|
||||
|
||||
@@ -81,6 +81,8 @@ PDF쪽: 135~136
|
||||
| ③ 자리 대조 | **작성하지 않은 다른 창** | 쪽 그림을 보며 표마다 수가 제 줄·제 칸에 있는지 · 병합 · 머리 · 밑수 · [주] |
|
||||
|
||||
- ①② 대조에서 빼는 것 — PDF 쪽: 쪽 맨 아래 꼬리(「- 51 -」 · 「30 공통부문」 · 「제1장 적용기준 31」) · 쪽 끝 이어짐 표지 「→」 / md 쪽: 머리 · `<!-- -->` · 그림 링크 · 표 문법(`|` · 구분 줄 · `<br>`) · 제목 `#` · 인용 `>` · 첨자 표시 `^` `_` (막음표 `\` 뒤 글자는 원문으로 셈).
|
||||
- 글자층 밖 — PDF 글자층의 사용자 영역 글리프(U+E000–F8FF · 식의 「(m³)」·분수선)는 ①② 에서 빼고 기계검사 칸에 「글자층 밖 글리프 N개(쪽)」로 적음. 그 자리를 그림 보고 받아쓴 md 글자는 `<!-- 글자층 밖 -->(m^3)<!-- /글자층 밖 -->` 로 감쌈(①② 에서 빠지고 「받아씀 N자」로 드러남) — **이 칸이 뜬 파일은 ③ 이 그림으로 그 식을 꼭 봄.** 사용자 영역 글리프 자리 말고는 감싸지 않음.
|
||||
- 호환 한자(U+F900–FAFF · 率 U+F9DB)는 ② 에서 통합 한자(率 U+7387)와 같게 봄. 다른 글자(㎥·①·㎡ 등)는 그대로 견줌.
|
||||
- ①②가 0 이어도 칸이 뒤바뀐 것은 못 잡음 → ③ 없이는 `확정` 아님.
|
||||
- ③에서 고칠 곳이 나오면 검증 창이 고치지 않고 **작성 창에 돌려보냄**(줄 번호 + PDF 쪽 + 무엇이 다른지).
|
||||
- `확정` 뒤 파일이 바뀌면 시험이 빨강. 고치려면 상태를 `작성중` 으로 되돌리고 ①②③ 다시.
|
||||
|
||||
@@ -170,3 +170,16 @@ def test_repeated_head_comment_counts_and_footer_dropped():
|
||||
]
|
||||
)
|
||||
assert tool.verdict(tool.compare(pdf, tool.md_lines(md))).startswith("통과")
|
||||
|
||||
|
||||
def test_off_layer_glyphs_and_compat_han():
|
||||
"""사용자 영역 글리프는 ①② 에서 빼고 드러냄 · md 는 감싼 받아쓰기를 뺌 · 호환 한자는 통합 한자로(NFKC 통째 아님)."""
|
||||
pdf = [(21, "체적 V \ue001\ue002 = 1.5"), (21, "공극\uf9db ㎥ ①")]
|
||||
md = tool.md_lines("체적 V <!-- 글자층 밖 -->(m^3)<!-- /글자층 밖 --> = 1.5\n공극率 ㎥ ①")
|
||||
result = tool.compare(pdf, md)
|
||||
line = tool.verdict(result)
|
||||
assert line.startswith("통과"), line
|
||||
assert "글자층 밖 글리프 2개(21쪽) · 받아씀 4자" in line # 「(m3)」 네 자
|
||||
# 감싸지 않으면 받아쓴 글자가 더함으로 뜸
|
||||
bare = tool.compare(pdf, tool.md_lines("체적 V (m^3) = 1.5\n공극率 ㎥ ①"))
|
||||
assert tool.verdict(bare).startswith("불통")
|
||||
|
||||
Reference in New Issue
Block a user