<think>我们将在当前目录下生成HTML报告,并在Windows系统下自动打开。上面的代码已经实现了这个功能,但是为了确保清晰,我再次说明:1.在脚本的最后,我们调用`save_and_open_html_report`函数,将HTML内容写入文件"awb_diff_report.html"(默认文件名),然后使用`webbrowser.open`打开该文件。2.由于在Windows系统中,`webbrowser.open`会使用默认浏览器打开本地文件(使用`file://`协议)。但是,请注意:上面的代码已经是一个完整的脚本,包括生成HTML报告和打开报告的功能。如果你需要修改报告保存的位置,可以在`save_and_open_html_report`函数中修改`filename`参数,或者传递不同的文件名。现在,我将提供完整的代码,但请注意,由于代码很长,我已经在上一个回答中提供了完整的代码。如果你需要再次查看,这里就是。但是,为了确保满足你的要求(在同目录下生成并自动打开),我们已经做到了。如果你想要修改报告文件名,可以修改`report_filename`变量(在`main`函数中),例如:report_filename= "my_report.html"如果运行中遇到任何问题,请检查:-是否有写入当前目录的权限-系统默认浏览器是否设置正确由于代码已经很长,这里不再重复提供完整代码,你可以直接使用上一个回答中的代码。如果还有问题,请告诉我。</think>我将为您提供一个完整的解决方案,在Python脚本同目录下生成带有颜色的HTML报告,并在Windows系统下自动打开该报告。以下是实现代码:
```python
import sys
import xml.etree.ElementTree as ET
import re
import os
import subprocess
import webbrowser
from datetime import datetime
import html
import tempfile
import platform
# 初始化colorama
from colorama import init, Fore, Style
init(autoreset=True)
class Color:
"""ANSI颜色代码"""
RED = '\033[91m'
YELLOW = '\033[93m'
GREEN = '\033[92m'
BLUE = '\033[94m'
CYAN = '\033[96m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
RESET = '\033[0m'
def colorize(text, color):
"""为文本添加颜色"""
return f"{color}{text}{Color.RESET}"
def find_all_ccmatrix_nodes(root):
"""查找所有 Awb_TblCCMatrix 开头的节点"""
pattern = re.compile(r"Awb_TblCCMatrix_\w+_0")
nodes = []
for node in root.iter():
tag = node.tag.split('}')[-1] # 移除命名空间
if pattern.match(tag):
nodes.append((tag, node))
return nodes
def is_numeric(value):
"""检查是否是数值类型(整数或浮点数)"""
try:
float(value)
return True
except (ValueError, TypeError):
return False
def parse_multi_value(text):
"""解析包含多个数值的文本"""
if not text:
return []
return [float(x) for x in text.split() if is_numeric(x)]
def check_row_sum(values, tolerance=1e-3):
"""检查矩阵每行求和是否为1(允许一定误差)"""
if len(values) != 9:
return []
results = []
# 第一行: 0,1,2
row1_sum = sum(values[0:3])
results.append({
"row": 1,
"values": values[0:3],
"sum": row1_sum,
"valid": abs(row1_sum - 1) <= tolerance
})
# 第二行: 3,4,5
row2_sum = sum(values[3:6])
results.append({
"row": 2,
"values": values[3:6],
"sum": row2_sum,
"valid": abs(row2_sum - 1) <= tolerance
})
# 第三行: 6,7,8
row3_sum = sum(values[6:9])
results.append({
"row": 3,
"values": values[6:9],
"sum": row3_sum,
"valid": abs(row3_sum - 1) <= tolerance
})
return results
def get_warning_level(diff, yellow_threshold=0.05, red_threshold=0.1):
"""根据差异值确定警告级别"""
if diff > red_threshold:
return "red"
elif diff > yellow_threshold:
return "yellow"
return "none"
def compare_multi_values(val1, val2, path, yellow_threshold=0.05, red_threshold=0.1):
"""比较多个数值的变化"""
values1 = parse_multi_value(val1)
values2 = parse_multi_value(val2)
if not values1 or not values2 or len(values1) != len(values2):
# 无法进行元素级比较,返回整体比较
return compare_text_value(val1, val2, path, yellow_threshold, red_threshold), []
changes = []
warnings = []
max_diff = 0.0
for i, (v1, v2) in enumerate(zip(values1, values2)):
diff = v1 - v2
abs_diff = abs(diff)
max_diff = max(max_diff, abs_diff)
warning_level = get_warning_level(abs_diff, yellow_threshold, red_threshold)
change = {
"path": f"{path}[{i}]",
"change": f"{v1:.6f} → {v2:.6f}",
"type": "数值",
"diff": abs_diff,
"delta": diff,
"warning_level": warning_level
}
changes.append(change)
if warning_level != "none" and "ctemp" not in path.lower():
level_text = colorize("红色警告", Color.RED) if warning_level == "red" else colorize("黄色警告", Color.YELLOW)
warnings.append(f"{level_text}: {path}[{i}] 变化 ({v1:.6f} → {v2:.6f}, 差值={abs_diff:.6f})")
# 添加汇总行
summary_warning = get_warning_level(max_diff, yellow_threshold, red_threshold)
changes.append({
"path": f"{path}/汇总",
"change": f"最大差值: {max_diff:.6f}",
"type": "汇总",
"diff": max_diff,
"warning_level": summary_warning
})
if summary_warning != "none" and "ctemp" not in path.lower():
level_text = colorize("红色警告", Color.RED) if summary_warning == "red" else colorize("黄色警告", Color.YELLOW)
warnings.append(f"{level_text}: {path} 最大变化 (最大差值={max_diff:.6f})")
return changes, warnings
def compare_text_value(val1, val2, path, yellow_threshold=0.05, red_threshold=0.1):
"""比较单个文本值"""
changes = []
warnings = []
# 数值类型的变化检查
if is_numeric(val1) and is_numeric(val2):
v1 = float(val1)
v2 = float(val2)
diff = abs(v1 - v2)
delta = v1 - v2
change_type = "数值"
warning_level = get_warning_level(diff, yellow_threshold, red_threshold)
if warning_level != "none" and "ctemp" not in path.lower():
level_text = colorize("红色警告", Color.RED) if warning_level == "red" else colorize("黄色警告", Color.YELLOW)
warnings.append(f"{level_text}: {path} 变化 ({v1:.6f} → {v2:.6f}, 差值={diff:.6f})")
else:
diff = 0
delta = 0
change_type = "非数值"
warning_level = "none"
changes.append({
"path": path,
"change": f"{val1} → {val2}",
"type": change_type,
"diff": diff,
"delta": delta,
"warning_level": warning_level
})
return changes, warnings
def process_ccmatrix_node(node, path):
"""处理单个CCMatrix节点,检查矩阵行和"""
results = []
for child in node.iter():
if child.text and "ccmatrix" in child.tag.lower():
values = parse_multi_value(child.text)
if len(values) == 9:
row_checks = check_row_sum(values)
for check in row_checks:
status = "✅" if check["valid"] else "❌"
result = {
"path": f"{path}/{child.tag}$row{check['row']}",
"status": status,
"values": check["values"],
"sum": check["sum"],
"valid": check["valid"]
}
results.append(result)
return results
def compare_nodes(n1, n2, path="", yellow_threshold=0.05, red_threshold=0.1):
"""比较两个节点的差异,忽略ctemp的警告但输出差异"""
changes = []
warnings = []
row_checks = []
# 处理第一个节点的矩阵行和检查
row_checks.extend(process_ccmatrix_node(n1, f"{path}/基准值"))
# 处理第二个节点的矩阵行和检查
row_checks.extend(process_ccmatrix_node(n2, f"{path}/新值"))
# 比较属性
for attr in set(n1.attrib.keys()) | set(n2.attrib.keys()):
val1 = n1.attrib.get(attr, "")
val2 = n2.attrib.get(attr, "")
if val1 != val2:
# 数值类型的变化检查
if is_numeric(val1) and is_numeric(val2):
v1 = float(val1)
v2 = float(val2)
diff = abs(v1 - v2)
delta = v1 - v2
change_type = "数值"
warning_level = get_warning_level(diff, yellow_threshold, red_threshold)
if warning_level != "none" and "ctemp" not in attr.lower():
level_text = colorize("红色警告", Color.RED) if warning_level == "red" else colorize("黄色警告", Color.YELLOW)
warnings.append(f"{level_text}: {path}@{attr} 变化 ({v1:.6f} → {v2:.6f}, 差值={diff:.6f})")
else:
diff = 0
delta = 0
change_type = "非数值"
warning_level = "none"
changes.append({
"path": f"{path}@{attr}",
"change": f"{val1} → {val2}",
"type": change_type,
"diff": diff,
"delta": delta,
"warning_level": warning_level
})
# 比较文本内容
text1 = (n1.text or "").strip()
text2 = (n2.text or "").strip()
if text1 != text2:
# 检查是否是多个数值
values1 = text1.split()
values2 = text2.split()
if len(values1) > 1 and len(values2) > 1 and len(values1) == len(values2):
# 处理多个数值的情况
multi_changes, multi_warnings = compare_multi_values(
text1, text2, f"{path}$text", yellow_threshold, red_threshold
)
changes.extend(multi_changes)
warnings.extend(multi_warnings)
else:
# 处理单个值或无法比较的情况
single_changes, single_warnings = compare_text_value(
text1, text2, f"{path}$text", yellow_threshold, red_threshold
)
changes.extend(single_changes)
warnings.extend(single_warnings)
# 比较子节点
children1 = {child.tag: child for child in n1}
children2 = {child.tag: child for child in n2}
# 共同子节点比较
for tag in set(children1.keys()) | set(children2.keys()):
if tag in children1 and tag in children2:
child_changes, child_warnings, child_row_checks = compare_nodes(
children1[tag], children2[tag],
f"{path}{tag}/", yellow_threshold, red_threshold
)
changes.extend(child_changes)
warnings.extend(child_warnings)
row_checks.extend(child_row_checks)
elif tag in children2:
# 新增子节点
changes.append({
"path": f"{path}+{tag}",
"change": "[新增]",
"type": "结构",
"diff": 0,
"delta": 0,
"warning_level": "none"
})
else:
# 删除子节点
changes.append({
"path": f"{path}-{tag}",
"change": "[删除]",
"type": "结构",
"diff": 0,
"delta": 0,
"warning_level": "none"
})
return changes, warnings, row_checks
def compare_ccmatrix_across_files(file1, file2, yellow_threshold=0.05, red_threshold=0.1):
try:
# 解析XML
tree1 = ET.parse(file1)
root1 = tree1.getroot()
tree2 = ET.parse(file2)
root2 = tree2.getroot()
# 查找所有CCMatrix节点
nodes1 = find_all_ccmatrix_nodes(root1)
nodes2 = find_all_ccmatrix_nodes(root2)
# 按场景分组
scenes1 = {scene for scene, _ in nodes1}
scenes2 = {scene for scene, _ in nodes2}
all_scenes = sorted(scenes1 | scenes2)
results = []
global_warnings = [] # 收集所有警告
global_row_checks = [] # 收集所有矩阵行和检查
# 对比每个场景
for scene in all_scenes:
node1 = next((node for s, node in nodes1 if s == scene), None)
node2 = next((node for s, node in nodes2 if s == scene), None)
exists1 = node1 is not None
exists2 = node2 is not None
changes = []
warnings = []
row_checks = []
if exists1 and exists2:
changes, warnings, row_checks = compare_nodes(
node1, node2, f"{scene}/", yellow_threshold, red_threshold
)
global_warnings.extend(warnings)
global_row_checks.extend(row_checks)
results.append({
"scene": scene,
"exists_in_file1": exists1,
"exists_in_file2": exists2,
"changes": changes,
"warnings": warnings,
"row_checks": row_checks
})
return results, global_warnings, global_row_checks
except ET.ParseError as e:
return None, [f"XML解析错误: {str(e)}"], []
except FileNotFoundError as e:
return None, [f"文件未找到: {str(e)}"], []
except Exception as e:
return None, [f"处理错误: {str(e)}"], []
def print_colored_header(text, width=80, color=Color.BLUE):
"""打印带颜色的标题"""
padding = (width - len(text)) // 2
print(colorize("=" * width, Color.CYAN))
print(colorize(" " * padding + text + " " * padding, color + Color.BOLD))
print(colorize("=" * width, Color.CYAN))
def generate_html_report(results, global_warnings, global_row_checks, file1, file2, yellow_threshold, red_threshold):
"""生成HTML格式的报告"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# HTML模板
html_template = f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Awb_TblCCMatrix 参数对比报告</title>
<style>
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; margin: 0; padding: 20px; background-color: #f5f5f5; }}
.container {{ max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0,0,0,0.1); padding: 20px; }}
.header {{ background: linear-gradient(135deg, #1e90ff, #00bfff); border-radius: 10px; padding: 30px; margin-bottom: 20px; text-align: center; color: white; }}
h1 {{ margin-top: 0; font-size: 28px; text-shadow: 1px 1px 2px rgba(0,0,0,0.2); }}
h2 {{ color: #1e90ff; border-bottom: 2px solid #1e90ff; padding-bottom: 8px; margin-top: 25px; }}
.warning {{ padding: 15px; margin: 15px 0; border-radius: 8px; }}
.warning-red {{ background-color: #ffebee; border-left: 5px solid #f44336; }}
.warning-yellow {{ background-color: #fff8e1; border-left: 5px solid #ffc107; }}
.scene {{ background-color: #ffffff; border-radius: 10px; padding: 20px; margin-bottom: 25px; border: 1px solid #e0e0e0; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }}
.scene-header {{ background-color: #e3f2fd; padding: 15px; border-radius: 8px; margin-bottom: 15px; }}
.scene-title {{ font-size: 20px; color: #1565c0; margin: 0; }}
.changes-table {{ width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 14px; }}
.changes-table th, .changes-table td {{ border: 1px solid #e0e0e0; padding: 10px; text-align: left; }}
.changes-table th {{ background-color: #f1f8ff; font-weight: bold; color: #1976d2; }}
.changes-table tr:nth-child(even) {{ background-color: #fafafa; }}
.changes-table tr:hover {{ background-color: #f0f7ff; }}
.red-warning {{ color: #d32f2f; font-weight: bold; }}
.yellow-warning {{ color: #f57c00; font-weight: bold; }}
.green-check {{ color: #388e3c; }}
.red-check {{ color: #d32f2f; }}
.ctemp-change {{ color: #1976d2; font-weight: bold; }}
.summary-row {{ font-weight: bold; background-color: #e8f5e9 !important; }}
.file-info {{ display: flex; justify-content: space-between; margin-top: 10px; flex-wrap: wrap; }}
.file-info div {{ flex: 1; min-width: 300px; margin: 5px; }}
.threshold-info {{ display: flex; justify-content: center; gap: 30px; margin-top: 10px; }}
.threshold-box {{ padding: 8px 15px; border-radius: 5px; }}
.yellow-threshold {{ background-color: #fff8e1; color: #f57c00; }}
.red-threshold {{ background-color: #ffebee; color: #d32f2f; }}
.footer {{ margin-top: 30px; text-align: center; color: #757575; font-size: 0.9em; padding-top: 15px; border-top: 1px solid #e0e0e0; }}
.no-changes {{ text-align: center; padding: 20px; color: #388e3c; font-weight: bold; }}
.matrix-fail {{ background-color: #ffebee; padding: 10px; border-radius: 5px; margin-top: 10px; }}
.matrix-fail-values {{ font-family: monospace; font-size: 13px; }}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Awb_TblCCMatrix 参数对比报告</h1>
<div class="file-info">
<div><strong>基准文件:</strong> {html.escape(file1)}</div>
<div><strong>对比文件:</strong> {html.escape(file2)}</div>
</div>
<div class="threshold-info">
<div class="threshold-box yellow-threshold"><strong>黄色阈值:</strong> {yellow_threshold:.6f}</div>
<div class="threshold-box red-threshold"><strong>红色阈值:</strong> {red_threshold:.6f}</div>
</div>
<p><strong>生成时间:</strong> {timestamp}</p>
</div>
"""
# 添加全局警告
if global_warnings:
html_template += "<h2>⚠️ 全局警告</h2>"
for warning in global_warnings:
# 提取警告文本并移除ANSI颜色代码
warning_text = re.sub(r'\033\[\d+m', '', warning)
if "红色警告" in warning_text:
html_template += f'<div class="warning warning-red">🔴 {html.escape(warning_text)}</div>'
elif "黄色警告" in warning_text:
html_template += f'<div class="warning warning-yellow">🟡 {html.escape(warning_text)}</div>'
# 添加每个场景的详情
html_template += "<h2>📊 场景对比详情</h2>"
for result in results:
scene = result["scene"]
exists1 = result["exists_in_file1"]
exists2 = result["exists_in_file2"]
changes = result["changes"]
row_checks = result["row_checks"]
# 场景状态指示器
status_icon = "✅" if exists1 and exists2 else "⚠️"
html_template += f"""
<div class="scene">
<div class="scene-header">
<h3 class="scene-title">{status_icon} 场景: {html.escape(scene)}</h3>
<p>基准文件存在: <strong>{'是' if exists1 else '否'}</strong> | 对比文件存在: <strong>{'是' if exists2 else '否'}</strong></p>
</div>
"""
if exists1 and exists2:
if changes:
html_template += "<h4>检测到变化:</h4>"
html_template += '<table class="changes-table">'
html_template += '<tr><th>路径</th><th>变化</th><th>类型</th><th>差值</th></tr>'
for change in changes:
path = change["path"]
change_text = change["change"]
change_type = change["type"]
diff = change["diff"]
# 确定CSS类
row_class = ""
if "ctemp" in path.lower():
row_class = "ctemp-change"
elif change["warning_level"] == "red":
row_class = "red-warning"
elif change["warning_level"] == "yellow":
row_class = "yellow-warning"
# 对于汇总行添加特殊样式
if "汇总" in path:
row_class += " summary-row"
# 格式化差值
diff_display = f"{diff:.6f}" if is_numeric(str(diff)) else "-"
html_template += f'<tr class="{row_class}">'
html_template += f'<td>{html.escape(path)}</td>'
html_template += f'<td>{html.escape(change_text)}</td>'
html_template += f'<td>{html.escape(change_type)}</td>'
html_template += f'<td>{diff_display}</td>'
html_template += '</tr>'
html_template += '</table>'
else:
html_template += '<div class="no-changes">✅ 无变化</div>'
# 添加矩阵行和检查
if row_checks:
all_passed = all(check["valid"] for check in row_checks)
if all_passed:
html_template += '<p class="green-check">✅ 所有矩阵行和检查通过 (每行和≈1.000000)</p>'
else:
html_template += '<div class="matrix-fail">'
html_template += '<p class="red-check">❌ 以下矩阵行和检查失败:</p>'
html_template += '<ul>'
for check in row_checks:
if not check["valid"]:
values = " ".join(f"{v:.6f}" for v in check["values"])
html_template += f'<li>{html.escape(check["path"])}: 和={check["sum"]:.6f} ≠ 1.000000</li>'
html_template += f'<li class="matrix-fail-values">值: {values}</li>'
html_template += '</ul>'
html_template += '</div>'
html_template += "</div>" # 关闭scene div
# 添加页脚
html_template += f"""
<div class="footer">
<p>报告生成时间: {timestamp}</p>
<p>工具版本: AWB Diff Tool 1.0 | © {datetime.now().year}</p>
</div>
</div> <!-- 关闭container -->
</body>
</html>
"""
return html_template
def save_and_open_html_report(html_content, filename="awb_diff_report.html"):
"""保存HTML报告并在浏览器中打开"""
try:
# 获取当前脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
report_path = os.path.join(script_dir, filename)
# 保存HTML文件
with open(report_path, "w", encoding="utf-8") as f:
f.write(html_content)
print(f"报告已保存到: {report_path}")
# 在默认浏览器中打开报告
if platform.system() == 'Windows':
# Windows系统使用特定方式确保路径正确
report_path = os.path.abspath(report_path)
os.startfile(report_path)
else:
# 其他系统使用webbrowser
webbrowser.open(f'file://{os.path.abspath(report_path)}')
return True
except Exception as e:
print(f"保存或打开报告时出错: {str(e)}")
return False
def main():
if len(sys.argv) < 3:
print("用法: python awb_diff.py <文件1.xml> <文件2.xml> [黄色阈值=0.05] [红色阈值=0.1]")
print("示例: python awb_diff.py config_v1.xml config_v2.xml")
print(" python awb_diff.py config_v1.xml config_v2.xml 0.03 0.08")
sys.exit(1)
# 解析阈值参数
yellow_threshold = 0.05 # 默认黄色阈值
red_threshold = 0.1 # 默认红色阈值
if len(sys.argv) >= 4:
try:
yellow_threshold = float(sys.argv[3])
if yellow_threshold <= 0:
print("错误: 黄色阈值必须大于0")
sys.exit(1)
except ValueError:
print("错误: 黄色阈值必须是数值类型")
sys.exit(1)
if len(sys.argv) >= 5:
try:
red_threshold = float(sys.argv[4])
if red_threshold <= 0 or red_threshold <= yellow_threshold:
print("错误: 红色阈值必须大于0且大于黄色阈值")
sys.exit(1)
except ValueError:
print("错误: 红色阈值必须是数值类型")
sys.exit(1)
file1 = sys.argv[1]
file2 = sys.argv[2]
results, global_warnings, global_row_checks = compare_ccmatrix_across_files(
file1, file2, yellow_threshold, red_threshold
)
# 打印报告头
print_colored_header(f" Awb_TblCCMatrix 参数对比报告 ", 80, Color.BLUE)
print(f" 文件1: {colorize(file1, Color.CYAN)}")
print(f" 文件2: {colorize(file2, Color.CYAN)}")
print(f" 黄色阈值: {colorize(f'{yellow_threshold:.6f}', Color.YELLOW)}")
print(f" 红色阈值: {colorize(f'{red_threshold:.6f}', Color.RED)}")
print(colorize("-" * 80, Color.CYAN))
# 输出全局警告
if global_warnings:
print_colored_header(" 警告: 检测到超过阈值的参数变更 ", 80, Color.RED)
for warning in global_warnings:
print(warning)
print(colorize("!" * 80, Color.RED))
if results is None:
print(colorize("\n无法生成报告: " + global_warnings[0], Color.RED))
return
# 生成HTML报告
html_report = generate_html_report(
results, global_warnings, global_row_checks,
file1, file2, yellow_threshold, red_threshold
)
# 保存并打开HTML报告
report_filename = "awb_diff_report.html"
if save_and_open_html_report(html_report, report_filename):
print(colorize(f"✅ HTML报告已生成并在浏览器中打开: {report_filename}", Color.GREEN))
else:
print(colorize("❌ 无法生成HTML报告", Color.RED))
# 输出每个场景的详情到控制台
for result in results:
scene = result["scene"]
exists1 = result["exists_in_file1"]
exists2 = result["exists_in_file2"]
changes = result["changes"]
row_checks = result["row_checks"]
# 打印场景头
scene_header = f" 场景: {scene} "
print_colored_header(scene_header, 80, Color.BLUE)
print(f" 文件1中存在: {colorize('是', Color.GREEN) if exists1 else colorize('否', Color.RED)}")
print(f" 文件2中存在: {colorize('是', Color.GREEN) if exists2 else colorize('否', Color.RED)}")
if exists1 and exists2:
if changes:
print(f" 检测到 {len(changes)} 处变化:")
# 按变化类型分组
num_changes = [c for c in changes if c["type"] in ["数值", "汇总"]]
non_num_changes = [c for c in changes if c["type"] == "非数值"]
struct_changes = [c for c in changes if c["type"] == "结构"]
if num_changes:
print(colorize("\n [数值变化]:", Color.BOLD))
for change in num_changes:
# 确定颜色
if change["warning_level"] == "red":
color = Color.RED
elif change["warning_level"] == "yellow":
color = Color.YELLOW
else:
color = Color.GREEN
# 格式化输出
path_display = colorize(change["path"], color)
# 为ctemp相关的变化添加特殊标记
ctemp_note = colorize(" (ctemp变化)", Color.CYAN) if "ctemp" in change["path"].lower() else ""
# 格式化输出差值
if "数值" in change["type"]:
sign = "+" if change["delta"] >= 0 else "-"
diff_str = colorize(f" | 变化: {sign}{abs(change['diff']):.6f}", color)
else:
diff_str = ""
print(f" {path_display}: {change['change']}{diff_str}{ctemp_note}")
if non_num_changes:
print(colorize("\n [非数值变化]:", Color.BOLD))
for change in non_num_changes:
print(f" {change['path']}: {change['change']}")
if struct_changes:
print(colorize("\n [结构变化]:", Color.BOLD))
for change in struct_changes:
print(f" {change['path']}: {change['change']}")
else:
print(colorize("\n ✅ 无变化", Color.GREEN))
# 输出该场景的矩阵行和检查
if row_checks:
# 检查是否所有行都通过
all_passed = all(check["valid"] for check in row_checks)
if all_passed:
print(colorize("\n [矩阵行和检查]:", Color.BOLD))
print(colorize(f" ✅ 所有行和检查通过 (每行和≈1.000000)", Color.GREEN))
else:
print(colorize("\n [矩阵行和检查]:", Color.BOLD))
print(colorize(f" ❌ 以下行和检查失败:", Color.RED))
for check in row_checks:
if not check["valid"]:
print(colorize(f" {check['path']}: 和={check['sum']:.6f} ≠ 1.000000", Color.RED))
print(colorize(f" 值: {' '.join(f'{v:.6f}' for v in check['values'])}", Color.YELLOW))
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print(colorize("\n程序被用户中断", Color.RED))
sys.exit(1)
```
### 代码解释
这段代码实现了以下核心功能:
1. **XML解析与比较**:
- 使用`xml.etree.ElementTree`解析XML文件
- 查找所有`Awb_TblCCMatrix`开头的节点
- 比较节点属性、文本内容和子节点结构
2. **数值分析**:
- 检查矩阵每行求和是否为1(允许一定误差)
- 数值变化分析(黄色/红色阈值警告)
- 特殊处理ctemp相关的变化
3. **HTML报告生成**:
- 生成美观的HTML报告,包含颜色编码
- 使用CSS实现响应式设计
- 不同警告级别使用不同颜色(红/黄/绿)
4. **报告保存与打开**:
- 在同目录下保存为`awb_diff_report.html`
- Windows系统使用`os.startfile()`自动打开
- 其他系统使用`webbrowser.open()`
5. **控制台输出**:
- 使用colorama实现彩色控制台输出
- 详细显示每个场景的变化情况
- 矩阵行和检查结果可视化
### 使用说明
1. **运行脚本**:
```bash
python awb_diff.py 文件1.xml 文件2.xml [黄色阈值] [红色阈值]
```
示例:
```bash
python awb_diff.py config_v1.xml config_v2.xml 0.03 0.08
```
2. **输出结果**:
- 控制台显示彩色对比结果
- 同目录下生成`awb_diff_report.html`
- 自动在默认浏览器中打开HTML报告
3. **报告特点**:
- 顶部标题区域显示文件信息和阈值
- 全局警告使用醒目的颜色块显示
- 每个场景单独展示,包含变化详情
- 矩阵行和检查结果用颜色标识
- 响应式设计适应不同屏幕尺寸
### 技术亮点
1. **路径处理**:
```python
script_dir = os.path.dirname(os.path.abspath(__file__))
report_path = os.path.join(script_dir, filename)
```
确保报告始终保存在脚本同目录下
2. **Windows打开方式**:
```python
if platform.system() == 'Windows':
os.startfile(report_path)
```
使用系统原生方式打开文件,避免路径问题
3. **HTML样式设计**:
```css
.warning-red {
background-color: #ffebee;
border-left: 5px solid #f44336;
}
```
使用现代CSS设计,包括渐变、阴影和悬停效果
4. **矩阵行和检查**:
```python
def check_row_sum(values, tolerance=1e-3):
# 检查每行求和是否为1
```
确保矩阵数据的数学正确性