Initial toolkit: scripts, references, skills, and term database
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
"""Extract source.dj and bilingual.dj from .docx.md file.
|
||||
|
||||
Handles:
|
||||
- CN/EN paragraph pairs (CN line → EN line)
|
||||
- Headings with merged CN+EN on same line (pandoc artifact: CN**EN)
|
||||
- TOC with markdown links [CN text](#anchor)
|
||||
- {#anchor} pandoc heading anchors
|
||||
- Markdown heading cleanup
|
||||
"""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SRC = ROOT / "translate-files/佛法与企业管理/副本59 佛法与企业管理-maple 初翻.docx.md"
|
||||
OUT = ROOT / "translate-files/佛法与企业管理"
|
||||
|
||||
def has_cjk(s):
|
||||
return any('\u4e00' <= c <= '\u9fff' for c in s)
|
||||
|
||||
def strip_anchors(s):
|
||||
"""Remove {#anchor} and markdown links [text](#anchor) — keep text."""
|
||||
s = re.sub(r'\{#[^}]*\}', '', s) # {#anchor}
|
||||
s = re.sub(r'\[([^\]]*)\]\([^)]*\)', r'\1', s) # [text](#link) → text
|
||||
return s
|
||||
|
||||
def split_cnen(line):
|
||||
"""Split merged CN+EN line. First strips anchors, then finds CJK→EN boundary."""
|
||||
s = strip_anchors(line).strip()
|
||||
if not has_cjk(s):
|
||||
return ('', s)
|
||||
# Find where CJK ends and ASCII English begins
|
||||
# Pattern: CJK, optional ws, optional *, optional ws, then English letter
|
||||
m = re.search(r'[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]\s*\**\s*([A-Za-z])', s)
|
||||
if m:
|
||||
split_at = m.start(1)
|
||||
cn = s[:split_at].rstrip('* ').strip()
|
||||
en = s[split_at:].strip()
|
||||
if en and not has_cjk(en):
|
||||
return (cn, en)
|
||||
return (s, '')
|
||||
|
||||
def clean_cn(s):
|
||||
"""Clean CN heading/paragraph."""
|
||||
s = re.sub(r'^\d+\.\s*', '', s) # leading number (e.g. "1. ")
|
||||
s = re.sub(r'^#+\s*\**', '', s) # heading markers: # **
|
||||
s = re.sub(r'\**\s*$', '', s) # trailing **
|
||||
s = re.sub(r'\t\d+$', '', s) # trailing page number
|
||||
s = s.strip()
|
||||
return s
|
||||
|
||||
def clean_en(s):
|
||||
"""Clean EN line."""
|
||||
s = re.sub(r'^\d+[、,.]\s*', '', s) # leading number/separator
|
||||
s = re.sub(r'\*+$', '', s) # trailing orphaned italic * (from split)
|
||||
s = s.strip()
|
||||
return s
|
||||
|
||||
def is_toc_line(line, cn_raw):
|
||||
"""True if this looks like a TOC entry (markdown link or tab+page-number)."""
|
||||
if re.search(r'\[.*\]\(.*\)', line):
|
||||
return True
|
||||
if re.search(r'\t\d+', cn_raw):
|
||||
return True
|
||||
return False
|
||||
|
||||
def parse_doc(text):
|
||||
"""Parse into list of (cn, en) pairs."""
|
||||
lines = text.split('\n')
|
||||
pairs = []
|
||||
i = 0
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if not line:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
stripped = strip_anchors(line)
|
||||
has_cn = has_cjk(stripped)
|
||||
|
||||
if has_cn:
|
||||
cn_raw, en_raw = split_cnen(line)
|
||||
|
||||
if en_raw:
|
||||
# Merged CN+EN on same line
|
||||
pairs.append((clean_cn(cn_raw), clean_en(en_raw)))
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Look ahead for EN
|
||||
nxt = lines[i+1].strip() if i+1 < len(lines) else ''
|
||||
nnxt = lines[i+2].strip() if i+2 < len(lines) else ''
|
||||
|
||||
if nxt and not has_cjk(nxt) and not is_toc_line(line, cn_raw):
|
||||
# Standard: CN → EN
|
||||
pairs.append((clean_cn(cn_raw), clean_en(nxt)))
|
||||
i += 2
|
||||
elif not nxt and nnxt and not has_cjk(nnxt) and not is_toc_line(line, cn_raw):
|
||||
# CN → blank → EN (heading pattern)
|
||||
pairs.append((clean_cn(cn_raw), clean_en(nnxt)))
|
||||
i += 3
|
||||
else:
|
||||
# Solo CN (TOC entry, orphan, or heading)
|
||||
pairs.append((clean_cn(cn_raw), ''))
|
||||
i += 1
|
||||
else:
|
||||
# Pure EN — TOC entry, pair with first unpaired CN TOC entry
|
||||
en = clean_en(line)
|
||||
for j in range(len(pairs)):
|
||||
if not pairs[j][1] and has_cjk(pairs[j][0]):
|
||||
pairs[j] = (pairs[j][0], en)
|
||||
break
|
||||
else:
|
||||
pairs.append(('', en))
|
||||
i += 1
|
||||
|
||||
return pairs
|
||||
|
||||
def classify(pairs):
|
||||
"""Classify each pair as title, subtitle, toc, heading, or para."""
|
||||
result = []
|
||||
# Pairs 0-1: title and subtitle
|
||||
result.append(('title', 0))
|
||||
result.append(('subtitle', 1))
|
||||
# Pairs 2-9: TOC (一、 through 八、)
|
||||
for i in range(2, min(10, len(pairs))):
|
||||
result.append(('toc', i))
|
||||
# Remaining: heuristics
|
||||
for i in range(10, len(pairs)):
|
||||
cn = pairs[i][0]
|
||||
if re.match(r'^[一二三四五六七八九十]、', cn):
|
||||
result.append(('heading', i))
|
||||
elif re.match(r'^\d+\\?\.\s', cn):
|
||||
result.append(('subheading', i))
|
||||
else:
|
||||
result.append(('para', i))
|
||||
return result
|
||||
|
||||
def write_bilingual(pairs, out_path):
|
||||
types = classify(pairs)
|
||||
lines = []
|
||||
# Title
|
||||
lines.append('# ' + pairs[0][0])
|
||||
lines.append('# ' + pairs[0][1])
|
||||
lines.append('')
|
||||
# Subtitle
|
||||
lines.append('---' + pairs[1][0])
|
||||
lines.append('---' + pairs[1][1])
|
||||
lines.append('')
|
||||
# Author
|
||||
lines.append('济群法师')
|
||||
lines.append('Master Jiqun')
|
||||
lines.append('')
|
||||
# TOC — CN block then EN block (not interleaved)
|
||||
for typ, idx in types:
|
||||
if typ == 'toc':
|
||||
lines.append('- ' + pairs[idx][0])
|
||||
lines.append('')
|
||||
for typ, idx in types:
|
||||
if typ == 'toc':
|
||||
lines.append('- ' + pairs[idx][1])
|
||||
lines.append('')
|
||||
# Body
|
||||
for typ, idx in types:
|
||||
if typ in ('title', 'subtitle', 'toc'):
|
||||
continue
|
||||
cn, en = pairs[idx]
|
||||
if cn:
|
||||
lines.append(cn)
|
||||
if en:
|
||||
lines.append(en)
|
||||
if cn or en:
|
||||
lines.append('')
|
||||
with open(out_path, 'w') as f:
|
||||
f.write('\n'.join(lines))
|
||||
print(f"bilingual.dj: {len(pairs)} pairs -> {out_path}")
|
||||
|
||||
def write_source(pairs, out_path):
|
||||
lines = [cn for cn, en in pairs if cn]
|
||||
with open(out_path, 'w') as f:
|
||||
f.write('\n'.join(lines) + '\n')
|
||||
print(f"source.dj: {len(lines)} CN lines -> {out_path}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
text = SRC.read_text()
|
||||
pairs = parse_doc(text)
|
||||
print(f"Parsed {len(pairs)} pairs from .docx.md")
|
||||
solo_cn = sum(1 for cn, en in pairs if cn and not en)
|
||||
solo_en = sum(1 for cn, en in pairs if en and not cn)
|
||||
both = sum(1 for cn, en in pairs if cn and en)
|
||||
print(f" Both: {both}, CN-only: {solo_cn}, EN-only: {solo_en}")
|
||||
write_source(pairs, OUT / "source.dj")
|
||||
write_bilingual(pairs, OUT / "bilingual.dj")
|
||||
Reference in New Issue
Block a user