You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
9.6 KiB

#!/usr/bin/env python3
"""
解析 C 文件中的 TKeyForest 初始化表,生成全局共享模式的 HTML 交互树状图。
用法:
python gen_shared_html_tree.py <c_file> [table_name]
"""
import re
import sys
import os
from collections import defaultdict
def parse_table(c_file, table_name):
"""从 .c 文件中提取并解析 TKeyForest 初始化表"""
with open(c_file, "r", encoding="utf-8") as f:
content = f.read()
# 1. 定位初始化块
pat = re.compile(
r'(?:const\s+)?TKeyForest\s+' + re.escape(table_name) +
r'\s*=\s*\{(.*)\}\s*;',
re.DOTALL
)
m = pat.search(content)
if not m:
print(f"错误:未找到 {table_name} 的初始化块", file=sys.stderr)
sys.exit(1)
body = m.group(1)
key_blocks = []
pos = 0
# 2. 拆分各个 Key
while pos < len(body):
km = re.search(r'\.(\w+)\s*=\s*\{', body[pos:])
if not km:
break
field_name = km.group(1)
start = pos + km.end()
depth = 1
i = start
while i < len(body) and depth > 0:
if body[i] == '{': depth += 1
elif body[i] == '}': depth -= 1
i += 1
block_content = body[start:i - 1]
key_blocks.append((field_name, block_content))
pos = i
# 3. 解析三元组 {Mode, SubMode, Func}
# 使用集合去重,确保相同的 Mode->Func 关系只被记录一次
edges = set()
all_keys = set()
for field_name, block in key_blocks:
all_keys.add(field_name)
block_clean = re.sub(r'//.*', '', block) # 去除注释
for em in re.finditer(r'\{([^}]+)\}', block_clean):
parts = [p.strip().strip(',') for p in em.group(1).split(',')]
if len(parts) >= 3:
mode, sub_mode, func = parts[0], parts[1], parts[2]
if func != "NULL":
# 记录边关系:Key -> Mode -> Func
edges.add((field_name, mode, func))
return list(all_keys), edges
def build_html(keys, edges):
"""构建包含 CSS 和 JS 的完整 HTML 字符串"""
# 预处理数据,计算每个节点的入度和出度
node_stats = defaultdict(lambda: {'in': 0, 'out': 0})
# 用于生成唯一 ID,避免特殊字符
def get_id(name):
return name.replace('-', '_').replace('(', '').replace(')', '')
for key, mode, func in edges:
k_id = get_id(key)
m_id = get_id(mode)
f_id = get_id(func)
node_stats[k_id]['out'] += 1
node_stats[m_id]['in'] += 1
node_stats[m_id]['out'] += 1
node_stats[f_id]['in'] += 1
# 构建 HTML 结构
html_parts = []
html_parts.append("""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>KeyForest 全局共享树状图</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f4f6f9; padding: 20px; }
h2 { text-align: center; color: #333; }
.container { display: flex; flex-direction: column; gap: 10px; align-items: center; }
/* 节点样式 */
.node {
padding: 8px 15px; margin: 5px; border-radius: 6px; cursor: pointer;
transition: all 0.3s; border: 1px solid #ccc; display: inline-block;
font-size: 14px; background: white; position: relative;
}
.node:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
/* 层级样式 */
.layer-keys { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-bottom: 20px; }
.layer-modes { display: flex; flex-wrap: wrap; justify-content: center; gap: 15px; margin-bottom: 20px; }
.layer-funcs { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
/* 颜色区分 */
.type-key { background: #e3f2fd; border-color: #90caf9; color: #0d47a1; font-weight: bold; }
.type-mode { background: #fff3e0; border-color: #ffcc80; color: #e65100; font-weight: bold; }
.type-func { background: #e8f5e9; border-color: #a5d6a7; color: #1b5e20; font-family: monospace; }
/* 高亮与变暗 */
.dimmed { opacity: 0.15; filter: grayscale(100%); }
.highlight {
background: #d4edda !important; border-color: #28a745 !important;
color: #155724; transform: scale(1.1); z-index: 10; box-shadow: 0 0 15px rgba(40, 167, 69, 0.5);
}
/* 连线模拟 (使用伪元素) */
.connector { height: 20px; width: 2px; background: #ccc; margin: 0 auto; }
.connector.highlight { background: #28a745; }
/* 提示框 */
#tooltip {
position: fixed; display: none; background: rgba(0,0,0,0.85); color: white;
padding: 10px; border-radius: 5px; font-size: 13px; pointer-events: none; z-index: 1000;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
}
</style>
</head>
<body>
<h2>TKeyForest 全局共享交互图 (单击节点高亮路径)</h2>
<div class="container">
<!-- 第一层:Keys -->
<div class="layer-keys" id="layer-keys">
""")
# 渲染 Keys
for key in sorted(keys):
k_id = get_id(key)
stats = node_stats[k_id]
html_parts.append(f'<div class="node type-key" id="{k_id}" data-in="{stats["in"]}" data-out="{stats["out"]}" onclick="handleClick(this)">{key}</div>')
html_parts.append("""
</div>
<div class="connector" id="conn-1"></div>
<!-- 第二层:Modes -->
<div class="layer-modes" id="layer-modes">
""")
# 收集所有唯一的 Modes
all_modes = sorted(set(mode for _, mode, _ in edges))
for mode in all_modes:
m_id = get_id(mode)
stats = node_stats[m_id]
html_parts.append(f'<div class="node type-mode" id="{m_id}" data-in="{stats["in"]}" data-out="{stats["out"]}" onclick="handleClick(this)">{mode}</div>')
html_parts.append("""
</div>
<div class="connector" id="conn-2"></div>
<!-- 第三层:Funcs -->
<div class="layer-funcs" id="layer-funcs">
""")
# 收集所有唯一的 Funcs
all_funcs = sorted(set(func for _, _, func in edges))
for func in all_funcs:
f_id = get_id(func)
stats = node_stats[f_id]
html_parts.append(f'<div class="node type-func" id="{f_id}" data-in="{stats["in"]}" data-out="{stats["out"]}" onclick="handleClick(this)">{func}()</div>')
html_parts.append("""
</div>
</div>
<div id="tooltip"></div>
<script>
// 预计算所有边关系,用于高亮逻辑
const edges = [];
""")
# 将 Python 解析的边数据注入到 JS 中
for key, mode, func in edges:
html_parts.append(f'edges.push(["{get_id(key)}", "{get_id(mode)}", "{get_id(func)}"]);')
html_parts.append("""
let activeNode = null;
function handleClick(node) {
if (activeNode === node) {
resetHighlight();
return;
}
resetHighlight();
activeNode = node;
const nodeId = node.id;
// 找出所有经过该节点的路径
const relatedNodes = new Set();
const relatedConns = new Set();
// 1. 如果点击的是 Key,高亮 Key -> Mode -> Func
// 2. 如果点击的是 Mode,高亮 Key -> Mode -> Func
// 3. 如果点击的是 Func,高亮 Key -> Mode -> Func
edges.forEach(([k, m, f]) => {
if (k === nodeId || m === nodeId || f === nodeId) {
relatedNodes.add(k);
relatedNodes.add(m);
relatedNodes.add(f);
}
});
// 应用高亮
relatedNodes.forEach(id => {
document.getElementById(id).classList.add('highlight');
});
// 变暗无关节点
document.querySelectorAll('.node').forEach(n => {
if (!relatedNodes.has(n.id)) {
n.classList.add('dimmed');
}
});
// 变暗连线
document.querySelectorAll('.connector').forEach(c => c.classList.add('dimmed'));
}
function resetHighlight() {
document.querySelectorAll('.highlight').forEach(el => el.classList.remove('highlight'));
document.querySelectorAll('.dimmed').forEach(el => el.classList.remove('dimmed'));
activeNode = null;
}
// 鼠标悬停显示统计信息
const tooltip = document.getElementById('tooltip');
document.querySelectorAll('.node').forEach(node => {
node.addEventListener('mousemove', (e) => {
const inDeg = node.getAttribute('data-in');
const outDeg = node.getAttribute('data-out');
tooltip.style.display = 'block';
tooltip.style.left = (e.pageX + 15) + 'px';
tooltip.style.top = (e.pageY + 15) + 'px';
tooltip.innerHTML = `<b>${node.innerText}</b><br>入度(指向此节点): ${inDeg}<br>出度(从此指出): ${outDeg}`;
});
node.addEventListener('mouseleave', () => {
tooltip.style.display = 'none';
});
});
</script>
</body>
</html>
""")
return "".join(html_parts)
def main():
if len(sys.argv) < 2:
print("用法: python gen_shared_html_tree.py <c_file> [table_name]", file=sys.stderr)
sys.exit(1)
c_file = sys.argv[1]
table_name = sys.argv[2] if len(sys.argv) > 2 else "g_ActionTable"
if not os.path.isfile(c_file):
print(f"错误:文件不存在: {c_file}", file=sys.stderr)
sys.exit(1)
keys, edges = parse_table(c_file, table_name)
html_content = build_html(keys, edges)
out_file = os.path.splitext(c_file)[0] + "_shared_tree.html"
with open(out_file, "w", encoding="utf-8") as f:
f.write(html_content)
print(f"成功生成全局共享 HTML 树状图: {out_file}")
if __name__ == "__main__":
main()