# -*- coding: utf-8 -*-
u"""把「从原理图导入变更」对话框的变更表**按 DOM 行结构**读出来(不是 innerText 拼接)。
/c/Python314/python .claude/p1_import_rows2.py [--board2]
## 为什么要再写一个
`pcb1_import.py --probe` 读的是滚动容器的 `innerText`。那样拿到的是一大坨文本,
**表头行(位号)和明细行(动作/旧值/新值)之间的归属关系是靠换行猜的**,
一旦表格是虚拟滚动 / 分列渲染,就会把 A 的表头和 B 的明细拼到一起 ——
2026-09-08 就是这么读出一份「整个风扇块按位号错位一格」的吓人清单,
而 `pcb1_import.py --verify`(PCB 元件表 vs 原理图 BOM)说 301 个位号全对、
封装只差 J1 一处。两者必有一个是假的。
这里改成逐 `
` 读、每行自己带着单元格,谁属于谁不用猜。
每行还带上 `rowTop`,行序一目了然。
"""
import io
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from eda import run
if '--base' in sys.argv: # R2 底板
PCB = '7b9524b6aaf6f235'
OUT = os.path.join(HERE, 'r2', '_base_import_rows2.json')
else:
PCB = '942ed1560d81c1e7' if '--board2' in sys.argv else '7e9415a16a909c51'
OUT = os.path.join(HERE, '_p1_import_rows2.json')
JS = u"""
const sleep = ms => new Promise(r => setTimeout(r, ms));
const propsOf = el => { for (const k of Object.keys(el)) if (k.startsWith('__reactProps$')) return el[k]; return null; };
const fire = (el) => { let t = el; for (let i=0;i<8&&t;i++){const p=propsOf(t); if(p&&typeof p.onClick==='function'){p.onClick({target:t,currentTarget:t,stopPropagation(){},preventDefault(){},nativeEvent:{},button:0,detail:1});return true;} t=t.parentElement;} return false; };
const leaf = (txt) => { for (const el of document.querySelectorAll('*')) if (el.children.length===0 && (el.textContent||'').trim()===txt && el.offsetParent!==null) return el; return null; };
await eda.dmt_EditorControl.openDocument('%s');
await sleep(1800);
const out = {rows: [], note: ''};
fire(leaf('设计')); await sleep(900);
out.opened = fire(leaf('从原理图导入变更'));
await sleep(9000);
// 找滚动容器(和 pcb1_import 一样的判据,取最紧的那个)
let sc = null;
for (const el of document.querySelectorAll('div'))
if (el.scrollHeight > el.clientHeight + 20 && /移除元件|增加元件|修改/.test(el.innerText || ''))
if (!sc || el.innerText.length < sc.innerText.length) sc = el;
if (!sc) { out.note = 'no scroll container'; return out; }
// 逐行收集:key = 该行在文档流里的 offsetTop,避免虚拟滚动重复
const seen = {};
const grab = () => {
for (const tr of sc.querySelectorAll('tr')) {
if (tr.offsetParent === null) continue;
const cells = [...tr.querySelectorAll('td,th')].map(td => (td.innerText || '').trim());
if (!cells.length) continue;
const key = tr.offsetTop + '|' + cells.join('\\u0001');
if (seen[key]) continue;
seen[key] = 1;
out.rows.push({top: tr.offsetTop, cells});
}
};
sc.scrollTop = 0; await sleep(500);
let last = -1;
for (let i = 0; i < 300; i++) {
grab();
if (sc.scrollTop === last) break;
last = sc.scrollTop;
if (sc.scrollTop + sc.clientHeight >= sc.scrollHeight - 2) break;
sc.scrollTop += Math.max(30, sc.clientHeight / 3);
await sleep(240);
}
grab();
out.rows.sort((a, b) => a.top - b.top);
out.nTr = sc.querySelectorAll('tr').length;
return out;
""" % PCB
def main():
r = run(JS, timeout=300)
if not r.get('success'):
print(json.dumps(r, ensure_ascii=False)[:900]); sys.exit(1)
res = r['result']
if res.get('note'):
print(res['note']); sys.exit(1)
io.open(OUT, 'w', encoding='utf-8').write(json.dumps(res, ensure_ascii=False, indent=1))
print(u'opened=%s 行数 %d -> %s' % (res.get('opened'), len(res['rows']), OUT))
for row in res['rows']:
print(u' top=%-6s %s' % (row['top'], ' | '.join(row['cells'])))
if __name__ == '__main__':
main()