You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python解析UML XML生成CSV遇格式错误求解决

问题:解析UML XML文件时触发XML格式错误

我编写了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列,替换非法字符(比如将&改为&amp;)。
  • 自动清理脚本:
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)
    # 转义未转义的&(避免破坏已转义的&amp;等)
    content = re.sub(r'&(?!amp;|lt;|gt;|quot;|apos;)', '&amp;', 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 01:29:54