1 changed files with 292 additions and 0 deletions
@ -0,0 +1,292 @@ |
|||
#!/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() |
|||
Loading…
Reference in new issue