Files
ai-agent-book/book-zhtw/strip_titles.py
T
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

112 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Strip in-figure titles from checked-in SVGs and crop top whitespace.
按學術規範:圖本身不包含標題(標題寫在正文)。本指令碼保守地處理
`images/` 中已簽入的 SVG 檔案。
判定規則(保守):
- 第一處出現的 <text> 元素,若同時滿足:font-size>=20 且 y<60 且 text-anchor=middle
則視為圖示題——刪除該元素,並將 viewBox 上沿和 height 各下移/縮小 CROP_PX。
- 否則該檔案不做任何修改。
用法:python3 strip_titles.py [--dry-run]
"""
import os
import re
import sys
IMAGES_DIR = os.path.join(os.path.dirname(__file__), 'images')
CROP_PX = 40
TEXT_RE = re.compile(
r'<text\b([^>]*?)>(.*?)</text>',
flags=re.DOTALL,
)
ATTR_RE = re.compile(r'(\w[\w-]*)\s*=\s*"([^"]*)"')
SVG_TAG_RE = re.compile(r'<svg\b([^>]*)>', flags=re.DOTALL)
VIEWBOX_RE = re.compile(r'viewBox="([^"]+)"')
HEIGHT_ATTR_RE = re.compile(r'\bheight="([^"]+)"')
def parse_attrs(attr_str):
return {m.group(1): m.group(2) for m in ATTR_RE.finditer(attr_str)}
def process_file(path, dry_run=False):
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
# 找第一個 <text>
m = TEXT_RE.search(content)
if not m:
return 'no-text'
attrs = parse_attrs(m.group(1))
fs = float(attrs.get('font-size', '0') or 0)
y = float(attrs.get('y', '0') or 0)
anchor = attrs.get('text-anchor', '')
if not (fs >= 20 and y < 60 and anchor == 'middle'):
return 'no-title'
# 刪除該 <text> 元素
new_content = content[: m.start()] + content[m.end():]
# 調整 viewBox 與 height
svg_m = SVG_TAG_RE.search(new_content)
if not svg_m:
return 'no-svg-tag'
svg_attrs_str = svg_m.group(1)
def replace_viewbox(vbm):
parts = vbm.group(1).split()
if len(parts) != 4:
return vbm.group(0)
x0, y0, w, h = parts
try:
new_y0 = float(y0) + CROP_PX
new_h = float(h) - CROP_PX
except ValueError:
return vbm.group(0)
def fmt(v):
return str(int(v)) if v == int(v) else str(v)
return f'viewBox="{fmt(float(x0))} {fmt(new_y0)} {fmt(float(w))} {fmt(new_h)}"'
new_svg_attrs = VIEWBOX_RE.sub(replace_viewbox, svg_attrs_str)
def replace_height(hm):
try:
new_h = float(hm.group(1)) - CROP_PX
new_h_s = str(int(new_h)) if new_h == int(new_h) else str(new_h)
return f'height="{new_h_s}"'
except ValueError:
return hm.group(0)
new_svg_attrs = HEIGHT_ATTR_RE.sub(replace_height, new_svg_attrs, count=1)
new_content = new_content[:svg_m.start()] + f'<svg{new_svg_attrs}>' + new_content[svg_m.end():]
if not dry_run:
with open(path, 'w', encoding='utf-8') as f:
f.write(new_content)
return 'stripped'
def main():
dry_run = '--dry-run' in sys.argv
counts = {'stripped': 0, 'no-title': 0, 'no-text': 0, 'no-svg-tag': 0}
examples = {k: [] for k in counts}
for name in sorted(os.listdir(IMAGES_DIR)):
if not name.endswith('.svg'):
continue
result = process_file(os.path.join(IMAGES_DIR, name), dry_run=dry_run)
counts[result] = counts.get(result, 0) + 1
if len(examples[result]) < 5:
examples[result].append(name)
mode = '[DRY-RUN]' if dry_run else '[APPLIED]'
print(f'{mode} summary:')
for k, v in counts.items():
print(f' {k}: {v} e.g. {examples[k]}')
if __name__ == '__main__':
main()