Files
ai-agent-book/book/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()