Python解析UML XML生成CSV遇格式错误求解决
我编写了Python脚本,目标是解析UML格式的XML文件,为每个Class生成包含其属性的CSV文件,同时生成表间关系的CSV文件。代码如下:
import xml.etree.ElementTree as ET import csv def extract_tables_and_attributes(xml_file): parser = ET.XMLParser(encoding="windows-1252") tree = ET.parse(xml_file, parser=parser) root = tree.getroot() tables = root.findall(".//{http://www.omg.org/spec/UML/20090901}Class") table_data = [] for table in tables: table_name = table.find("{http://www.omg.org/spec/UML/20090901}name").text attributes = table.findall(".//{http://www.omg.org/spec/UML/20090901}Property") table_attributes = [] for attr in attributes: attr_name = attr.find("{http://www.omg.org/spec/UML/20090901}name").text attr_type = attr.find("{http://www.omg.org/spec/UML/20090901}type").text table_attributes.append([attr_name, attr_type]) table_data.append((table_name, table_attributes)) return table_data def export_to_csv(table_data): for table_name, attributes in table_data: csv_file_name = f"{table_name}.csv" with open(csv_file_name, 'w', newline='') as csvfile: csv_writer = csv.writer(csvfile) csv_writer.writerow(['Attribute Name', 'Attribute Type']) for attr_name, attr_type in attributes: csv_writer.writerow([attr_name, attr_type]) if __name__ == "__main__": xml_file = r"Gemeentelijk Gegevensmodel XMI2.1.2.xml" table_data = extract_tables_and_attributes(xml_file) export_to_csv(table_data)
执行时触发以下错误:
Traceback (most recent call last): File "ggm to csv.py", line 39, in <module> table_data = extract_tables_and_attributes(xml_file) File "ggm to csv.py", line 6, in extract_tables_and_attributes tree = ET.parse(xml_file, parser=parser) File "xml\etree\ElementTree.py", line 1203, in parse tree.parse(source, parser) File "xml\etree\ElementTree.py", line 571, in parse parser.feed(data) xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 71765, column 176
该XML文件是市政数据模型导出的,尝试用不同版本的Enterprise Architect导出,均在同一位置报错。期望输出示例(以PAND表为例):
Attribute Name,Attribute Type Bruto inhoud pand,EAJava_N6 Datum begin
geldigheid pand,EAJava_DATUM Datum einde geldigheid pand,EAJava_DATUM
GeometriePunt,EAJava_GM_Point Hoogste bouwlaag pand,EAJava_N3
Identificatie BGTPND,EAJava_NEN3610ID Ind planobject,EAJava_INDIC
Indicatie geconstateerd pand,EAJava_INDIC Inwinning geometrie
bovenaanzicht,EAJava_GM_Object Inwinning geometrie
maaiveld,EAJava_GM_Object Laagste bouwlaag pand,EAJava_N3 Label
nummeraanduidingreeks,EAJava_C74E1553_32AE_4fd8_9796_00C6E1C51A11 Lod1
geometrie pand,EAJava_GM_Object Lod2 geometrie pand,EAJava_GM_Object
Lod3 geometrie pand,EAJava_GM_Object Oorspronkelijk bouwjaar
pand,EAJava_JAAR Oppervlakte pand,EAJava_N6 Pandgeometrie
bovenaanzicht,EAJava_GM_Surface Pandgeometrie
maaiveld,EAJava_GM_MultiSurface
Pandidentificatie,EAJava_AB8B30D0_FD1F_4c44_9396_BB05389EA20B
Pandstatus,EAJava_E2CC5DFC_C264_4c21_8E47_F551958E1C17 Relatieve
hoogteligging pand,EAJava_N2 Status voortgang
bouw,EAJava_8C49F097_6D95_4406_B3B7_58AC102B6FD2
解决思路与方案
1. 修复XML中的非法字符
xml.etree.ElementTree对XML格式要求严格,报错位置通常存在未转义的特殊字符(如&、<)或控制字符。可以手动或自动清理:
- 手动修复:用文本编辑器定位到第71765行第176列,替换非法字符(比如将
&改为&)。 - 自动清理脚本:
import re def clean_xml(input_path, output_path): with open(input_path, 'r', encoding='windows-1252') as f: content = f.read() # 移除非打印控制字符(保留换行、制表符) content = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F]', '', content) # 转义未转义的&(避免破坏已转义的&等) content = re.sub(r'&(?!amp;|lt;|gt;|quot;|apos;)', '&', content) with open(output_path, 'w', encoding='windows-1252') as f: f.write(content) # 调用示例 clean_xml("Gemeentelijk Gegevensmodel XMI2.1.2.xml", "cleaned_GGM.xml")
2. 使用宽松解析器兼容不规范XML
改用lxml库的容错解析模式,无需修改原文件:
首先安装依赖:
pip install lxml
修改解析部分代码:
from lxml import etree import csv def extract_tables_and_attributes(xml_file): # 启用恢复模式,兼容不规范XML parser = etree.XMLParser(encoding="windows-1252", recover=True) tree = etree.parse(xml_file, parser=parser) root = tree.getroot() tables = root.findall(".//{http://www.omg.org/spec/UML/20090901}Class") table_data = [] for table in tables: table_name_elem = table.find("{http://www.omg.org/spec/UML/20090901}name") table_name = table_name_elem.text if table_name_elem is not None else "Unnamed_Table" attributes = table.findall(".//{http://www.omg.org/spec/UML/20090901}Property") table_attributes = [] for attr in attributes: attr_name_elem = attr.find("{http://www.omg.org/spec/UML/20090901}name") attr_type_elem = attr.find("{http://www.omg.org/spec/UML/20090901}type") # 避免空值报错 if attr_name_elem is not None and attr_type_elem is not None: attr_name = attr_name_elem.text attr_type = attr_type_elem.text table_attributes.append([attr_name, attr_type]) table_data.append((table_name, table_attributes)) return table_data # 原export_to_csv函数保持不变
3. 补充表间关系生成逻辑
原代码未处理表间关系,新增生成关系CSV的函数:
def export_relationships_csv(xml_file, output_csv="table_relationships.csv"): parser = etree.XMLParser(encoding="windows-1252", recover=True) tree = etree.parse(xml_file, parser=parser) root = tree.getroot() # 查找UML关联关系 relationships = root.findall(".//{http://www.omg.org/spec/UML/20090901}Association") with open(output_csv, 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(["Source Table", "Target Table", "Relationship Type"]) for rel in relationships: ends = rel.findall(".//{http://www.omg.org/spec/UML/20090901}Property") if len(ends) >= 2: source_elem = ends[0].find("{http://www.omg.org/spec/UML/20090901}type") target_elem = ends[1].find("{http://www.omg.org/spec/UML/20090901}type") if source_elem is not None and target_elem is not None: source_table = source_elem.text target_table = target_elem.text rel_name_elem = rel.find("{http://www.omg.org/spec/UML/20090901}name") rel_type = rel_name_elem.text if rel_name_elem is not None else "Association" writer.writerow([source_table, target_table, rel_type])
更新主函数调用:
if __name__ == "__main__": xml_file = r"cleaned_GGM.xml" # 用lxml的话也可以直接用原文件 table_data = extract_tables_and_attributes(xml_file) export_to_csv(table_data) export_relationships_csv(xml_file)
内容的提问来源于stack exchange,提问作者Taliesin

