Python3 XML 验证

🎉摘要:本教程详细讲解Python中XML验证的两种方式:DTD和XSD。通过lxml库实战演示XML文档验证、运行时校验,并提供完整的配置验证器案例,帮助开发者掌握XML数据校验技术。

命名空间(Namespace)是 XML 中避免元素名冲突的机制,而 XML 验证则确保文档符合预定义的结构规则。XML 常用验证方式有 DTD 和 XSD(XML Schema)。本章下面将介绍如何使用 Python 实现 XML 验证:

DTD(Document Type Definition)

DTD 是 XML 的原始验证机制,可以直接定义在文档内部,也可以使用 dtd 外部文件存储。下面为了演示,直接在 XML 中定义:

from lxml import etree

def validate_xml_with_dtd(xml_content: str) -> tuple[bool, str]:
    """
    校验XML文档语法 + 内部DTD约束是否合法
    :param xml_content: 完整XML字符串(包含DOCTYPE DTD定义)
    :return: (是否合法, 错误信息/成功提示)
    """
    try:
        # 1. 解析XML,开启DTD验证
        parser = etree.XMLParser(dtd_validation=True)
        tree = etree.fromstring(xml_content, parser=parser)

        # 2. 检查解析器是否捕获校验错误
        if parser.error_log:
            error_msg = "\n".join([f"行{err.lineno}: {err.message}" for err in parser.error_log])
            return False, f"XML/DTD校验失败:\n{error_msg}"

        return True, "XML文档语法正确,且完全符合DTD约束,校验通过!"

    except etree.XMLSyntaxError as e:
        # XML标签语法错误(未闭合、标签非法等)
        return False, f"XML语法解析错误:{e.msg} 行号:{e.lineno}"
    except Exception as e:
        # 其他未知异常
        return False, f"校验发生未知异常:{str(e)}"


if __name__ == "__main__":
    # 题目提供的完整XML内容
    xml_str = """<?xml version="1.0"?>
<!DOCTYPE note [
    <!ELEMENT note (to, from, heading, body)>
    <!ELEMENT to (#PCDATA)>
    <!ELEMENT from (#PCDATA)>
    <!ELEMENT heading (#PCDATA)>
    <!ELEMENT body (#PCDATA)>
]>
<note>
    <to>张三</to>
    <from>李四</from>
    <heading>提醒</heading>
    <body>记得开会</body>
</note>
"""

    # 执行校验
    is_valid, msg = validate_xml_with_dtd(xml_str)
    print("===== XML校验结果 =====")
    print(msg)

运行示例,输出如下:

===== XML校验结果 =====
XML文档语法正确,且完全符合DTD约束,校验通过!

我们也可以将 XML 文档提取到外部 XML 文件,如将 XML 保存到 note.xml 文件:

<?xml version="1.0"?>
<!DOCTYPE note [
    <!ELEMENT note (to, from, heading, body)>
    <!ELEMENT to (#PCDATA)>
    <!ELEMENT from (#PCDATA)>
    <!ELEMENT heading (#PCDATA)>
    <!ELEMENT body (#PCDATA)>
]>
<note>
    <to>张三</to>
    <from>李四</from>
    <heading>提醒</heading>
    <body>记得开会</body>
</note>

在 Python 代码中个,直接读取 note.xml 文件进行验证,如下:

from lxml import etree

def validate_xml_file(file_path: str) -> tuple[bool, str]:
    """
    读取本地XML文件,校验XML语法与内部DTD约束
    :param file_path: xml文件路径
    :return: (是否合法, 提示信息)
    """
    try:
        # 开启DTD校验解析器
        parser = etree.XMLParser(dtd_validation=True)
        # 读取并解析文件
        tree = etree.parse(file_path, parser=parser)

        # 收集校验错误日志
        if parser.error_log:
            err_list = [f"第{err.lineno}行:{err.message}" for err in parser.error_log]
            return False, "DTD校验不通过:\n" + "\n".join(err_list)

        return True, "XML格式合法,完全符合DTD约束"

    except etree.XMLSyntaxError as e:
        return False, f"XML语法错误:行{e.lineno} {e.msg}"
    except FileNotFoundError:
        return False, f"文件不存在:{file_path}"
    except Exception as e:
        return False, f"未知错误:{str(e)}"


if __name__ == "__main__":
    xml_file = "note.xml"
    valid, message = validate_xml_file(xml_file)
    print("==== XML校验结果 ====")
    print(message)

运行示例,输出如下:

==== XML校验结果 ====
XML格式合法,完全符合DTD约束

XSD(XML Schema Definition)

XSD 使用 XML 语法定义结构,功能比 DTD 更强大在前面的 Python3 XML Schema 比 DTD 更强大的约束工具 已经详细介绍。假如,我们存在 schema.xsd:

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    
    <xs:element name="product">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string"/>
                <xs:element name="price" type="xs:decimal"/>
                <xs:element name="quantity" type="xs:integer"/>
            </xs:sequence>
            <xs:attribute name="id" type="xs:string" use="required"/>
        </xs:complexType>
    </xs:element>
    
</xs:schema>

以及 XML 文档 product.xml:

<?xml version="1.0"?>
<product id="P001" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="schema.xsd">
    <name>机械键盘</name>
    <price>299.00</price>
    <quantity>100</quantity>
</product>

下面使用 lxml 来验证该文档。

使用 lxml 验证 XML

验证代码非常简介,需要在解析 XML 前,先使用 etree.XMLSchema() 加载 Schema 定义,代码如下:

from lxml import etree

# 加载 Schema
with open('schema.xsd', 'rb') as f:
    schema_root = etree.XML(f.read())
schema = etree.XMLSchema(schema_root)

# 创建验证器,见 schema 配置选项
parser = etree.XMLParser(schema=schema)

# 验证并解析 XML
try:
    with open('product.xml', 'rb') as f:
        doc = etree.parse(f, parser)
    print("验证通过!")
except etree.XMLSyntaxError as e:
    print(f"验证失败: {e}")

运行示例,输出:

验证通过!

运行时验证

指应用启动运行后,在执行阶段动态校验数据、格式、规则,而非编译 / 静态阶段提前检查。

即代码跑起来后,才加载 XML 文本、用 XSD 规则实时校验字段类型(age 是否数字)、结构合规性。

主要特点:

  • 动态:输入数据可变,每次执行都重新校验;

  • 延迟校验:静态扫描看不出问题,只有运行传入数据才暴露错误;

  • 用于外部不可控输入:XML、接口参数、用户输入等。

运行时校验可以拦截非法数据,避免脏数据导致逻辑崩溃,输出明确错误日志定位问题。例如:

from lxml import etree

def validate_xml(xml_string, xsd_string):
    """验证 XML 字符串是否符合 Schema"""
    # 解析 Schema
    schema_root = etree.fromstring(xsd_string.encode('utf-8'))
    schema = etree.XMLSchema(schema_root)
    
    # 解析 XML
    xml_doc = etree.fromstring(xml_string.encode('utf-8'))
    
    # 验证
    is_valid = schema.validate(xml_doc)
    
    if not is_valid:
        print("验证错误:")
        for error in schema.error_log:
            print(f"  行 {error.line}: {error.message}")
    
    return is_valid

# 定义 schema,直接定义到代码中
xsd_schema = '''
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="user">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string"/>
                <xs:element name="age" type="xs:integer"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>
'''

# 有效的 XML
valid_xml = '''
<user>
    <name>张三</name>
    <age>25</age>
</user>
'''

# 无效的 XML(age 不是整数)
invalid_xml = '''
<user>
    <name>李四</name>
    <age>abc</age>
</user>
'''

print("验证有效 XML:", validate_xml(valid_xml, xsd_schema))
print("\n验证无效 XML:", validate_xml(invalid_xml, xsd_schema))

运行示例,输出如下:

验证有效 XML: True
验证错误:
  行 4: Element 'age': 'abc' is not a valid value of the atomic type 'xs:integer'.

验证无效 XML: False

实战案例

创建一个 ConfigValidator 配置验证类,该类内部维护了一个 Schema,并封装了验证方法,方便操作。完整代码如下:

from lxml import etree
from typing import List, Dict

class ConfigValidator:
    """配置文件验证器"""
    
    # 内嵌 Schema
    CONFIG_SCHEMA = '''
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
        
        <xs:simpleType name="LogLevel">
            <xs:restriction base="xs:string">
                <xs:enumeration value="DEBUG"/>
                <xs:enumeration value="INFO"/>
                <xs:enumeration value="WARNING"/>
                <xs:enumeration value="ERROR"/>
            </xs:restriction>
        </xs:simpleType>
        
        <xs:element name="configuration">
            <xs:complexType>
                <xs:all>
                    <xs:element name="database" minOccurs="1">
                        <xs:complexType>
                            <xs:sequence>
                                <xs:element name="host" type="xs:string"/>
                                <xs:element name="port" type="xs:integer"/>
                                <xs:element name="name" type="xs:string"/>
                            </xs:sequence>
                        </xs:complexType>
                    </xs:element>
                    
                    <xs:element name="logging" minOccurs="0">
                        <xs:complexType>
                            <xs:sequence>
                                <xs:element name="level" type="LogLevel"/>
                                <xs:element name="file" type="xs:string"/>
                            </xs:sequence>
                        </xs:complexType>
                    </xs:element>
                </xs:all>
            </xs:complexType>
        </xs:element>
        
    </xs:schema>
    '''
    
    def __init__(self):
        schema_root = etree.fromstring(self.CONFIG_SCHEMA.encode('utf-8'))
        self.schema = etree.XMLSchema(schema_root)
    
    def validate(self, xml_source: str) -> tuple[bool, List[str]]:
        """验证 XML,返回 (是否有效, 错误列表)"""
        try:
            if xml_source.strip().startswith('<'):
                doc = etree.fromstring(xml_source.encode('utf-8'))
            else:
                doc = etree.parse(xml_source).getroot()
            
            self.schema.assertValid(doc)
            return True, []
        
        except etree.DocumentInvalid as e:
            errors = [str(err) for err in self.schema.error_log]
            return False, errors
        except etree.XMLSyntaxError as e:
            return False, [f"XML 语法错误: {e}"]
    
    def validate_and_parse(self, xml_source: str) -> Dict:
        """验证并解析配置"""
        is_valid, errors = self.validate(xml_source)
        
        if not is_valid:
            raise ValueError(f"配置验证失败: {errors}")
        
        # 解析为字典
        if xml_source.strip().startswith('<'):
            root = etree.fromstring(xml_source.encode('utf-8'))
        else:
            root = etree.parse(xml_source).getroot()
        
        result = {}
        for section in root:
            section_data = {}
            for field in section:
                section_data[field.tag] = field.text
            result[section.tag] = section_data
        
        return result

# 使用示例
if __name__ == '__main__':
    validator = ConfigValidator()
    
    # 有效配置
    valid_config = '''
    <configuration>
        <database>
            <host>localhost</host>
            <port>5432</port>
            <name>myapp</name>
        </database>
        <logging>
            <level>INFO</level>
            <file>app.log</file>
        </logging>
    </configuration>
    '''
    
    # 无效配置(错误的日志级别)
    invalid_config = '''
    <configuration>
        <database>
            <host>localhost</host>
            <port>5432</port>
            <name>myapp</name>
        </database>
        <logging>
            <level>VERBOSE</level>
            <file>app.log</file>
        </logging>
    </configuration>
    '''
    
    print("验证有效配置:")
    is_valid, errors = validator.validate(valid_config)
    print(f"  结果: {is_valid}")
    if is_valid:
        config = validator.validate_and_parse(valid_config)
        print(f"  配置内容: {config}")
    
    print("\n验证无效配置:")
    is_valid, errors = validator.validate(invalid_config)
    print(f"  结果: {is_valid}")
    print(f"  错误: {errors}")

运行示例,输出如下:

验证有效配置:
  结果: True
  配置内容: {'database': {'host': 'localhost', 'port': '5432', 'name': 'myapp'}, 'logging': {'level': 'INFO', 'file': 'app.log'}}

验证无效配置:
  结果: False
  错误: ["<string>:9:0:ERROR:SCHEMASV:SCHEMAV_CVC_ENUMERATION_VALID: Element 'level': [facet 'enumeration'] The value 'VERBOSE' is not an element of the set {'DEBUG', 'INFO', 'WARNING', 'ERROR'}."]

到这里,关于 XML 部分的知识就介绍完了,更多知识,请查看后续章节。

 

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号