# 公開中の Google スプレッドシート・ドキュメントの埋め込み15本を、サイトの中の表と本文に置き換える(2026-11-08) # 使い方: python3 build2.py import csv, html, re, sys, importlib.util from html.parser import HTMLParser OUT = sys.argv[1] G = '/tmp/claude-0/gs/' spec = importlib.util.spec_from_file_location('b1', '/tmp/claude-0/sheets/build.py') src = open('/tmp/claude-0/sheets/build.py', encoding='utf-8').read() CSS = re.search(r'CSS = """(.*?)"""', src, re.S).group(1) JS = open('/tmp/claude-0/sheets/height.js', encoding='utf-8').read() CSS = CSS.replace('', """th{white-space:normal} .blk+.blk{margin-top:28px} td.g{background:#f7f7f7;font-weight:700;font-size:12px;padding:8px 10px;border-bottom:0} .doc{font-size:14px;line-height:1.85} .doc h1,.doc h2,.doc h3,.doc h4{font-size:16px;line-height:1.5;margin:24px 0 8px} .doc h1:first-child,.doc h2:first-child,.doc h3:first-child{margin-top:0} .doc p{margin:0 0 6px} .doc ul,.doc ol{margin:0 0 10px;padding-left:1.4em} .doc li{margin:0 0 2px} @media (max-width:560px){ .auto td.g::before{display:none} .auto tr.g{background:none;padding:12px 0 4px;margin:0} .auto tr.g td{background:none;padding:0} } """) e = html.escape def head(title): return ('\n\n\n\n' '\n' '%s\n\n' '\n' '%s\n\n\n
\n') % (e(title), CSS) def tail(note=None): n = '

%s

\n' % e(note) if note else '' return n + '
\n' + JS + '\n\n\n' def clean(v): return v.replace('│', 'ー').strip() def blocks(rows): out, cur = [], [] for r in rows: r = [clean(c) for c in r] if not any(r): if cur: out.append(cur); cur = [] else: cur.append(r) if cur: out.append(cur) return out def table(rows, lab=None): n = max(len(r) for r in rows) rows = [r + [''] * (n - len(r)) for r in rows] title = None # 1つ目の行が「見出しだけ」(先頭の欄だけ)なら、表の名前にする if sum(1 for c in rows[0] if c) == 1 and rows[0][0] and len(rows) > 1: title = rows[0][0]; rows = rows[1:] # 先頭の列が見出しの行にしか無いなら、それは表の名前 if rows[0][0] and len(rows) > 1 and not any(r[0] for r in rows[1:]) and any(c for c in rows[0][1:]): title = title or rows[0][0] rows = [r[1:] for r in rows] keep = [i for i in range(len(rows[0])) if any(r[i] for r in rows)] rows = [[r[i] for i in keep] for r in rows] hd, body = rows[0], rows[1:] # 先頭が番号だけの列なら、名前の列の頭に付けて1つにする(カードの見出しを名前にするため) if body and all(re.fullmatch(r'\d+', r[0] or 'x') for r in body): k = next((i for i, h in enumerate(hd) if '名' in h), None) if k: for r in body: r[k] = r[0] + ' ' + r[k] order = [k] + [i for i in range(1, len(hd)) if i != k] hd = [hd[i] for i in order]; body = [[r[i] for i in order] for r in body] avg = sum(len(c) for r in body for c in r) / max(1, sum(len(r) for r in body)) mode = 'stack' if len(hd) >= 6 or (len(hd) == 5 and avg > 16) else 'auto' th = ''.join('%s' % e(h) for h in hd) trs = [] for r in body: if sum(1 for c in r if c) == 1 and r[0]: trs.append('%s' % (len(hd), e(r[0]))); continue tds = ''.join('%s' % (' class="k"' if i == 0 else '', e(h), e(v).replace('\n', '
')) for i, (h, v) in enumerate(zip(hd, r))) trs.append('' + tds + '') t = title or lab return ('
%s%s\n%s\n
\n' % (mode, '

%s

' % e(t) if t else '', th, '\n'.join(trs))) def sheet_page(fname, name, csvs, note=None): body = '' for i, c in enumerate(csvs): bl = blocks(list(csv.reader(open(G + c, encoding='utf-8')))) for j, b in enumerate(bl): body += table(b, lab=(name[i] if isinstance(name, list) else name) if j == 0 else None) t = name[0] if isinstance(name, list) else name open('%s/%s.html' % (OUT, fname), 'w', encoding='utf-8').write(head(t) + body + tail(note)) class Doc(HTMLParser): KEEP = {'h1', 'h2', 'h3', 'h4', 'p', 'ul', 'ol', 'li', 'table', 'tr', 'td', 'br', 'strong', 'b'} def __init__(self): super().__init__(); self.out = []; self.on = False; self.skip = 0 def handle_starttag(self, t, a): a = dict(a) if a.get('id') == 'contents': self.on = True; return if not self.on: return if t in ('style', 'script'): self.skip += 1 if t in self.KEEP: self.out.append('<%s>' % t) def handle_endtag(self, t): if not self.on: return if t in ('style', 'script'): self.skip -= 1 if t in self.KEEP and t != 'br': self.out.append('' % t) def handle_data(self, d): if self.on and not self.skip: self.out.append(e(d)) def doc_page(fname, title, path): p = Doc(); p.feed(open(G + path, encoding='utf-8').read()) h = ''.join(p.out) h = re.sub(r'

\s*

', '', h); h = re.sub(r'<(\w+)>\s*', '', h) open('%s/%s.html' % (OUT, fname), 'w', encoding='utf-8').write(head(title) + '
%s
\n' % h + tail()) sheet_page('day18-3QlV0Qdj', 'Figma-カラー/機能', ['day18-3QlV0Qdj.csv']) sheet_page('day18-s8a8NqF2', 'Figma-カラー/バリアブル運用', ['day18-s8a8NqF2.csv']) sheet_page('day20-opjVEsl9', 'ROS「ノード」', ['day20-opjVEsl9.csv']) sheet_page('day20-wWuDrA1m', 'UnityxROS解説', ['day20-wWuDrA1m.csv']) sheet_page('day21-rHvWywD8', 'ROS 2 基本コマンド(エラー対応メモ)', ['day21-rHvWywD8.csv']) sheet_page('day22-q3Z4LUDY', ['P2M テックログ:ROS 2 基本コマンド(エラー対応メモ)', 'P2M テックログ:デバック情報まとめ'], ['t1035248351.csv', 't220498241.csv']) sheet_page('day25-LBQh79h4', 'Unity-視点操作ガイド', ['day25-LBQh79h4.csv']) sheet_page('day31-jdGUh_aI', 'ロボティクスUXデザイナー戦略', ['day31-jdGUh_aI.csv']) sheet_page('day33-R9vno25x', 'フィジカルAIによるパラダイムシフト', ['day33-R9vno25x.csv']) sheet_page('day34-kFbqYm6g', 'フェーズ3', ['day34-kFbqYm6g.csv']) sheet_page('day37-5nNtmwHN', 'フェーズ3', ['day37-5nNtmwHN.csv']) sheet_page('day43-DNB5IfRA', '使用するツールセット', ['day43-DNB5IfRA.csv']) sheet_page('day43-rk6cAPq3', '実験用パラメーター表', ['day43-rk6cAPq3.csv']) sheet_page('day51-PCdp7_Yb', 'FigJam-ライブラリ部品', ['day51-PCdp7_Yb.csv']) doc_page('day78-XSSoeDI1', 'デジタルツイン学習-R2R2R LP構成素案', 'd78.html') print('ok')