<think>我们需要修改`process_device_configs`函数,使其在找不到文件夹或配置时保留Excel原有内容(不覆盖),并将错误信息输出到控制台。
同时,我们还需要确保在提取配置信息失败时,不在Excel中覆盖原有数据,而是将错误信息打印到控制台。
修改思路:
1. 在遍历Excel行时,我们只读取第一列(显示名),其他列的数据我们不读取,而是直接写入新数据(如果找到的话),否则保留原数据。
2. 但是原代码中,我们每次都是重新写入提取的数据(即使没有找到,也写入了错误信息)。现在要求:如果没有匹配到,则保留原有内容(即不覆盖)。
3. 因此,我们需要在写入之前检查提取结果。如果提取结果有效(比如提取到了目标字符串),则写入;否则,跳过写入(保留原有内容),并将错误信息打印到控制台。
具体步骤:
- 在`extract_device_buildroot_config`和`extract_device_wlan_config`函数中,我们返回两个值:配置字符串和提取的目标字符串。
- 在`process_device_configs`函数中,对于每一行,我们只读取第一列(显示名),其他列(比如第2、3、4列)我们不去读取,而是准备写入新数据。
但是,现在要求:如果提取失败,则保留原有数据(即不覆盖),那么我们就不能直接覆盖写入,而是先判断提取结果是否有效。
定义有效提取的标准:
- 文件夹存在且配置文件存在,并且成功提取到非空的目标字符串,我们认为有效。
- 否则,视为无效,不覆盖原有单元格内容,并打印错误信息。
因此,我们需要:
- 在调用提取函数后,判断提取到的目标字符串(extracted_target和wlan_info_target)是否为空(或者包含错误信息字符串)。
- 如果有效,则写入;如果无效,则跳过写入,并打印错误信息。
另外,对于文件夹不存在的处理,我们之前已经跳过,现在要求保留原有内容,所以也不写入。
修改`process_device_configs`函数中的相关部分:
注意:原代码中,我们使用openpyxl库,在遍历行时,我们是从第2行开始(row_index=2),然后通过ws.cell()写入单元格。
原代码中,我们覆盖了第2、3、4列(B、C、D列)的内容。现在,我们只在提取成功时覆盖这些列。
同时,我们还需要保留原有内容,所以不能直接覆盖,而是先判断提取结果是否有效。
具体修改如下:
1. 对于buildroot配置的提取:
- 如果文件夹存在,我们调用`extract_device_buildroot_config`,然后检查返回的extracted_target是否有效(非空且不是错误信息)。
- 如果有效,则写入第2列(B列)和第3列(C列);否则,不写入,并打印错误信息。
2. 对于wlan配置的提取:
- 同样,如果提取到的wlan_info_target有效,则写入第4列(D列);否则,不写入,并打印错误信息。
3. 如果文件夹不存在,我们也不写入,并打印错误信息。
此外,原代码中已经对错误进行了颜色标记,现在要求不覆盖,所以颜色标记也应该在写入的时候才设置(或者如果没写入就不设置?)。我们可以调整:只有当我们写入错误信息时才设置颜色(但现在要求不覆盖原有内容,所以错误信息也不写入,而是打印到控制台)。因此,我们去掉颜色标记,改为打印错误信息。
但是,我们可能希望保留原Excel中的格式(包括颜色),所以如果不需要覆盖,那么颜色标记也不做。
因此,我们修改如下:
- 移除原有的颜色标记代码。
- 在提取失败时,打印错误信息到控制台,并且不覆盖Excel中的原有内容。
另外,我们还需要注意:原代码中,如果转换文件夹名失败,我们记录错误并跳过,现在要求保留原有内容,所以也不覆盖。
修改后的`process_device_configs`函数部分代码如下:
注意:我们不再使用颜色填充来标记错误,而是将错误信息打印到控制台。
同时,为了保留原有内容,我们只在我们有有效数据时才写入单元格。
具体代码修改如下:
</think>
我将修改代码以保留Excel原有内容,只在成功匹配时更新单元格,并将错误信息输出到控制台。以下是完整的修改方案:
```python
import os
import re
import openpyxl
from openpyxl.styles import PatternFill
def extract_target_from_config(config_str):
"""从CONFIG_TARGET字符串中提取目标部分(如ssc37x_ssc377_mc230v1)"""
if not config_str or "CONFIG_TARGET" not in config_str:
return ""
# 移除末尾的=y等赋值部分
if '=' in config_str:
config_name = config_str.split('=')[0]
else:
config_name = config_str
# 移除开头的"CONFIG_TARGET_"前缀
if config_name.startswith("CONFIG_TARGET_"):
target_part = config_name[len("CONFIG_TARGET_"):]
else:
# 尝试更灵活的匹配:找到第一个"TARGET_"后的内容
target_index = config_name.find("TARGET_")
if target_index != -1:
target_part = config_name[target_index + len("TARGET_"):]
else:
return ""
return target_part
def extract_wlan_from_config(config_str):
"""从CONFIG_WLAN_CHIP字符串中提取无线芯片值"""
if not config_str:
return ""
# 检查是否包含CONFIG_WLAN_CHIP
if "CONFIG_WLAN_CHIP" not in config_str:
return ""
# 分割字符串获取等号后面的值
if '=' in config_str:
parts = config_str.split('=', 1) # 只分割一次
if len(parts) > 1:
value = parts[1].strip()
# 移除可能的引号
value = value.strip('"').strip("'")
# 移除可能的注释和尾部空格
value = value.split('#')[0].split(';')[0].strip()
return value
return ""
def convert_display_name_to_folder(display_name):
"""
将Excel中的显示名转换为文件夹名
支持新机型:
"Tapo C668B 1.0" -> "c668bv1"
"Tapo C520 1.0" -> "c520v1"
"Tapo C615F KIT(EU)1.0" -> "c615kv1"
"""
try:
# 移除品牌名和括号内容
model_part = re.sub(r'^Tapo\s*', '', display_name, flags=re.IGNORECASE)
model_part = re.sub(r'\([^)]*\)', '', model_part)
# 处理特殊后缀 (如KIT)
model_part = re.sub(r'\bKIT\b', 'k', model_part, flags=re.IGNORECASE)
# 提取型号和版本号
match = re.search(
r'([a-zA-Z]+\d+[a-zA-Z]*)\s*(\w*)\s*(\d+)\.(\d+)',
model_part
)
if not match:
# 尝试更宽松的匹配模式
match = re.search(
r'([a-zA-Z]+\d+[a-zA-Z]*\w*)\s*(\d+)\.(\d+)',
model_part
)
if not match:
return None
model = match.group(1).lower()
major_version = match.group(2)
else:
# 组合型号和后缀
model = match.group(1).lower() + (match.group(2).lower() if match.group(2) else "")
major_version = match.group(3)
# 处理特殊型号转换规则
model = re.sub(r'\s+', '', model) # 移除所有空格
# 特殊型号处理
if 'fkit' in model:
model = model.replace('fkit', 'k')
elif 'kit' in model:
model = model.replace('kit', 'k')
# 构建文件夹名
return f"{model}v{major_version}"
except Exception as e:
print(f"转换错误: {display_name} - {str(e)}")
return None
def extract_device_buildroot_config(device_dir, device_folder_name):
"""在机型目录中查找build_root.config并提取包含特定机型名称的CONFIG_TARGET行"""
config_path = os.path.join(device_dir, "buildroot.config")
if not os.path.exists(config_path):
return None, None # 返回None表示配置不存在
try:
with open(config_path, 'r', encoding='utf-8') as f:
content = f.read()
# 创建匹配包含机型名的CONFIG_TARGET字段的正则表达式
pattern = re.compile(
r'(CONFIG_TARGET[^\s=]*' + re.escape(device_folder_name) + r'[^\s=]*)(?:\s*=\s*["\']?([^"\'\s]+)["\']?)?',
re.IGNORECASE
)
matches = pattern.findall(content)
# 提取唯一匹配项
unique_matches = set()
for match in matches:
config_key = match[0].strip()
config_value = match[1].strip() if match[1] else ""
unique_matches.add(f"{config_key}={config_value}")
# 返回原始配置和提取的目标部分
config_str = "; ".join(unique_matches) if unique_matches else None
extracted_target = "; ".join(set(extract_target_from_config(c) for c in unique_matches)) if unique_matches else None
return config_str, extracted_target
except Exception as e:
print(f"读取错误: {device_dir} - {str(e)}")
return None, None
def extract_device_wlan_config(device_dir):
"""在机型目录中查找wlan.config并提取CONFIG_WLAN_CHIP配置"""
config_path = os.path.join(device_dir, "wlan.config")
if not os.path.exists(config_path):
return None, None # 返回None表示配置不存在
try:
with open(config_path, 'r', encoding='utf-8') as f:
content = f.read()
# 新正则:只匹配以CONFIG_WLAN_CHIP=开头的行
pattern = re.compile(r'^\s*CONFIG_WLAN_CHIP\s*=\s*["\']?([^"\'\s]+)["\']?',
re.IGNORECASE | re.MULTILINE)
matches = pattern.findall(content)
# 提取唯一值
unique_values = set(matches)
# 返回原始配置和提取的值
config_str = f"CONFIG_WLAN_CHIP={';'.join(unique_values)}" if unique_values else None
extracted_target = "; ".join(unique_values) if unique_values else None
return config_str, extracted_target
except Exception as e:
print(f"读取错误: {device_dir} - {str(e)}")
return None, None
def process_device_configs(excel_path, config_base_dir):
"""处理Excel中的机型配置"""
# 加载工作簿
wb = openpyxl.load_workbook(excel_path)
ws = wb.active
# 设置标题样式
header_fill = PatternFill(start_color="DDEBF7", end_color="DDEBF7", fill_type="solid")
for cell in ws[1]:
cell.fill = header_fill
# 遍历Excel中的机型
row_index = 2
errors = [] # 收集所有错误信息
for row in ws.iter_rows(min_row=2, max_col=4, values_only=False):
display_cell = row[0] # A列: 显示名
target_cell = row[1] # B列: 提取的目标字段
config_cell = row[2] # C列: 原始配置信息
wlan_cell = row[3] # D列: WLAN配置信息
display_name = display_cell.value.strip() if display_cell.value else ""
if not display_name:
row_index += 1
continue
# 转换显示名为文件夹名
folder_name = convert_display_name_to_folder(display_name)
if not folder_name:
error_msg = f"转换失败: {display_name} -> 无法生成文件夹名"
errors.append(error_msg)
print(error_msg)
row_index += 1
continue
device_dir = os.path.join(config_base_dir, folder_name)
# 检查文件夹是否存在
if not os.path.exists(device_dir):
error_msg = f"文件夹不存在: {device_dir}"
errors.append(error_msg)
print(error_msg)
row_index += 1
continue
# 提取配置信息
config_info, extracted_target = extract_device_buildroot_config(device_dir, folder_name)
wlan_info, wlan_info_target = extract_device_wlan_config(device_dir)
# 只有在成功提取时才更新单元格
if extracted_target is not None:
target_cell.value = extracted_target
else:
error_msg = f"未找到匹配的CONFIG_TARGET: {device_dir}"
errors.append(error_msg)
print(error_msg)
if config_info is not None:
config_cell.value = config_info
else:
error_msg = f"未找到buildroot配置: {device_dir}"
errors.append(error_msg)
print(error_msg)
if wlan_info_target is not None:
wlan_cell.value = wlan_info_target
else:
error_msg = f"未找到WLAN配置: {device_dir}"
errors.append(error_msg)
print(error_msg)
row_index += 1
# 调整列宽
ws.column_dimensions['A'].width = 30
ws.column_dimensions['B'].width = 20
ws.column_dimensions['C'].width = 70
ws.column_dimensions['D'].width = 20
# 保存结果
output_path = excel_path.replace(".xlsx", "_result.xlsx")
wb.save(output_path)
return output_path, errors
if __name__ == "__main__":
# 用户输入
excel_path = r"E:\脚本\tapo_camera_config1.xlsx"
config_base_dir = r"Y:\NVMP\nvmp\product_config"
# 路径验证
if not os.path.isfile(excel_path):
print(f"错误: Excel文件 '{excel_path}' 不存在")
exit(1)
if not os.path.isdir(config_base_dir):
print(f"错误: 配置目录 '{config_base_dir}' 不存在")
exit(1)
# 处理并输出结果
result_path, errors = process_device_configs(excel_path, config_base_dir)
print(f"\n处理完成! 结果已保存到: {result_path}")
if not errors:
print("所有配置提取成功")
else:
print(f"警告: 遇到 {len(errors)} 个错误,详细信息已在处理过程中输出")
print("注: 未匹配的配置项已保留Excel原有内容")
```
### 主要修改说明:
1. **保留原有内容**:
- 修改了提取函数,在未找到匹配时返回 `None` 而不是错误字符串
- 只在成功提取数据时才更新Excel单元格的值
2. **错误信息输出**:
- 将所有错误信息输出到控制台
- 收集所有错误信息并返回给调用者
3. **Excel处理优化**:
```python
# 遍历时获取单元格对象而不是值
for row in ws.iter_rows(min_row=2, max_col=4, values_only=False):
display_cell = row[0] # A列
target_cell = row[1] # B列
# ...
# 只有在有有效数据时才更新值
if extracted_target is not None:
target_cell.value = extracted_target
else:
print(f"未找到匹配的CONFIG_TARGET: {device_dir}")
```
4. **错误处理改进**:
- 添加了详细的错误分类:
- 文件夹名转换失败
- 文件夹不存在
- 未找到CONFIG_TARGET匹配
- 未找到buildroot配置
- 未找到WLAN配置
5. **结果报告**:
```python
if not errors:
print("所有配置提取成功")
else:
print(f"警告: 遇到 {len(errors)} 个错误,详细信息已在处理过程中输出")
```
### 使用说明:
1. **输入要求**:
- Excel文件必须包含至少4列:显示名、目标字段、原始配置、WLAN配置
- 配置目录必须包含以转换后文件夹名命名的子目录
2. **输出结果**:
- 成功提取的配置会更新到相应单元格
- 未找到的配置会保留原有内容
- 所有错误信息会输出到控制台
- 结果保存为新的Excel文件(在原文件名后添加 `_result`)
3. **错误处理**:
- 转换失败:显示名无法转换为有效文件夹名
- 文件夹不存在:转换后的文件夹在配置目录中不存在
- 配置未找到:在文件夹中未找到匹配的配置项