skills/ppt-station-skill/ppt_station/chart_builder/cleaner.py
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:

- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
  ../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
  zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
  - docx family: kept docx (official, full toolchain) + docx-cn
    (GB/T 9704 Chinese official-document constants),
    dropped docx_writer (no scripts, name collided with docx)
  - humanizer family: kept humanizer-zh (6 zh reference docs),
    dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
  ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
  claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
  live credentials

Result: 25 skills, 572 files, ~7.5 MB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:47:12 +08:00

145 lines
5.2 KiB
Python

"""ChartJunkCleaner — 自动清洗图表,去除默认 PPT 味
在 ChartBuilder.build() 完成后自动执行:
- 移除图表外边框
- Y 轴网格线 -> 极浅虚线或隐藏
- 坐标轴刻度线 -> inside 或 none
- 图例框边框 -> 移除
- 默认线宽 -> 1.5pt
"""
from lxml import etree
from .oxml_ns import NAMESPACES
class ChartJunkCleaner:
"""清洗图表默认样式,向 JP 标准靠拢"""
# 极浅灰色(用于残留网格线)
GRID_COLOR = "E8E8E8"
GRID_WIDTH = 6350 # 0.5pt in EMUs
LINE_WIDTH = 19050 # 1.5pt in EMUs
def __init__(self, chart):
self.chart = chart
self.chartSpace = chart._chartSpace
self.plotArea = self.chartSpace.plotArea
def clean(self):
"""执行全部清洗步骤"""
self._remove_chart_border()
self._clean_gridlines()
self._clean_tick_marks()
self._clean_legend_border()
self._clean_plot_area_border()
return self.chart
def _remove_chart_border(self):
"""移除图表外边框"""
spPr = self.chartSpace.find('c:spPr', namespaces=NAMESPACES)
if spPr is None:
spPr = etree.SubElement(self.chartSpace, f"{{{NAMESPACES['c']}}}spPr")
# 移除线条
ln = spPr.find('a:ln', namespaces=NAMESPACES)
if ln is not None:
spPr.remove(ln)
ln = etree.SubElement(spPr, f"{{{NAMESPACES['a']}}}ln")
etree.SubElement(ln, f"{{{NAMESPACES['a']}}}noFill")
def _clean_gridlines(self):
"""将 Y 轴网格线设为极浅虚线或隐藏"""
# 处理所有值轴的主要网格线
for axis_tag in ['valAx', 'catAx']:
for axis in self.plotArea.findall(f'c:{axis_tag}', namespaces=NAMESPACES):
major_gl = axis.find('c:majorGridlines', namespaces=NAMESPACES)
if major_gl is not None:
self._set_light_gridline(major_gl)
# 移除次要网格线
minor_gl = axis.find('c:minorGridlines', namespaces=NAMESPACES)
if minor_gl is not None:
axis.remove(minor_gl)
def _set_light_gridline(self, gridline_elem):
"""将网格线设为极浅灰色虚线"""
# 清除现有 spPr
spPr = gridline_elem.find('c:spPr', namespaces=NAMESPACES)
if spPr is not None:
gridline_elem.remove(spPr)
spPr = etree.SubElement(gridline_elem, f"{{{NAMESPACES['c']}}}spPr")
ln = etree.SubElement(spPr, f"{{{NAMESPACES['a']}}}ln")
ln.set('w', str(self.GRID_WIDTH))
# 颜色
solidFill = etree.SubElement(ln, f"{{{NAMESPACES['a']}}}solidFill")
srgbClr = etree.SubElement(solidFill, f"{{{NAMESPACES['a']}}}srgbClr")
srgbClr.set('val', self.GRID_COLOR)
# 虚线样式
prstDash = etree.SubElement(ln, f"{{{NAMESPACES['a']}}}prstDash")
prstDash.set('val', 'dot')
def _clean_tick_marks(self):
"""坐标轴刻度线 -> none"""
for axis_tag in ['valAx', 'catAx']:
for axis in self.plotArea.findall(f'c:{axis_tag}', namespaces=NAMESPACES):
# 主刻度线
major_tick = axis.find('c:majorTickMark', namespaces=NAMESPACES)
if major_tick is not None:
major_tick.set('val', 'none')
else:
mt = etree.SubElement(axis, f"{{{NAMESPACES['c']}}}majorTickMark")
mt.set('val', 'none')
# 次刻度线
minor_tick = axis.find('c:minorTickMark', namespaces=NAMESPACES)
if minor_tick is not None:
minor_tick.set('val', 'none')
else:
mt = etree.SubElement(axis, f"{{{NAMESPACES['c']}}}minorTickMark")
mt.set('val', 'none')
def _clean_legend_border(self):
"""移除图例框边框"""
legend = self.chartSpace.find('.//c:legend', namespaces=NAMESPACES)
if legend is None:
return
spPr = legend.find('c:spPr', namespaces=NAMESPACES)
if spPr is None:
spPr = etree.SubElement(legend, f"{{{NAMESPACES['c']}}}spPr")
# 移除线条
ln = spPr.find('a:ln', namespaces=NAMESPACES)
if ln is not None:
spPr.remove(ln)
ln = etree.SubElement(spPr, f"{{{NAMESPACES['a']}}}ln")
etree.SubElement(ln, f"{{{NAMESPACES['a']}}}noFill")
# 移除填充
for fill_tag in ['solidFill', 'gradFill', 'pattFill']:
fill = spPr.find(f'a:{fill_tag}', namespaces=NAMESPACES)
if fill is not None:
spPr.remove(fill)
etree.SubElement(spPr, f"{{{NAMESPACES['a']}}}noFill")
def _clean_plot_area_border(self):
"""移除 plotArea 边框"""
spPr = self.plotArea.find('c:spPr', namespaces=NAMESPACES)
if spPr is None:
spPr = etree.SubElement(self.plotArea, f"{{{NAMESPACES['c']}}}spPr")
ln = spPr.find('a:ln', namespaces=NAMESPACES)
if ln is not None:
spPr.remove(ln)
ln = etree.SubElement(spPr, f"{{{NAMESPACES['a']}}}ln")
etree.SubElement(ln, f"{{{NAMESPACES['a']}}}noFill")
def clean_chart(chart):
"""便捷函数: 对 chart 执行全部清洗"""
return ChartJunkCleaner(chart).clean()