// 最终验证:从 Axure 页面 HTML 提取全部文本(解码 &#xHHHH; 实体),按控件类型分组 import fs from 'node:fs'; const html = fs.readFileSync('e:/code/crm-backend-matt/.scratch/lanhu-latest/team-page.html', 'utf8'); // 解码 HTML 实体 const decode = (s) => s .replace(/&#x([0-9A-Fa-f]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))) .replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(parseInt(d, 10))) .replace(/ /g, ' ').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); // 按 Axure 控件注释分组提取: 后跟
...

文本

const re = /\s*
]*>/g; const items = []; let m; while ((m = re.exec(html)) !== null) { const [full, comment, id] = m; const typeMatch = comment.match(/^(.*?)\s*[((](.+?)[))]$/); const name = typeMatch ? typeMatch[1] : comment; const type = typeMatch ? typeMatch[2] : '其他'; // 在该控件开始的后续 600 字符里找 uNNN_text 的内容 const after = html.slice(m.index, m.index + 700); const textRe = new RegExp(`id="${id}_text"[^>]*>[\\s\\S]*?
`); const tm = after.match(textRe); let text = ''; if (tm) { text = (tm[0].match(/

([\s\S]*?)<\/span><\/p>/g) || []) .map(p => decode(p.replace(/<\/?p>/g, '').replace(/<\/?span>/g, ''))) .join(' | '); } items.push({ id, name: decode(name), type: decode(type), text: text.trim() }); } console.log('总控件数:', items.length); console.log('有文本的控件:', items.filter(i => i.text).length); console.log('\n=== 全部有文本的控件(id | 类型 | 名称 | 文本)==='); for (const i of items) { if (i.text) console.log(`${i.id.padEnd(7)} ${i.type.padEnd(6)} ${i.name.slice(0, 12).padEnd(12)} ${i.text.slice(0, 70)}`); } console.log('\n=== 类型分布 ==='); const dist = {}; items.forEach(i => dist[i.type] = (dist[i.type] || 0) + 1); console.log(JSON.stringify(dist, null, 0));