正则表达式在正常情况下工作,但在XML架构中失败

2 投票
2 回答
797 浏览
提问于 2025-04-15 22:34

我有一个简单的 doc.xml 文件,这个文件里面有一个根元素,并且有一个叫做 Timestamp 的属性:

<?xml version="1.0" encoding="utf-8"?>
<root Timestamp="04-21-2010 16:00:19.000" />

我想用一个简单的 schema.xsd 文件来验证这个文档,确保 Timestamp 的格式是正确的:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:attribute name="Timestamp" use="required" type="timeStampType"/>
    </xs:complexType>
  </xs:element>
  <xs:simpleType name="timeStampType">
    <xs:restriction base="xs:string">
      <xs:pattern value="(0[0-9]{1})|(1[0-2]{1})-(3[0-1]{1}|[0-2]{1}[0-9]{1})-[2-9]{1}[0-9]{3} ([0-1]{1}[0-9]{1}|2[0-3]{1}):[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}.[0-9]{3}" />
    </xs:restriction>
  </xs:simpleType>
</xs:schema>

于是我使用了 lxml 这个 Python 模块,尝试进行简单的模式验证,并报告任何错误:

from lxml import etree

schema = etree.XMLSchema( etree.parse("schema.xsd") )
doc = etree.parse("doc.xml")

if not schema.validate(doc):
    for e in schema.error_log:
        print e.message

我的 XML 文档在验证时失败了,出现了以下错误信息:

Element 'root', attribute 'Timestamp': [facet 'pattern'] The value '04-21-2010 16:00:19.000' is not accepted by the pattern '(0[0-9]{1})|(1[0-2]{1})-(3[0-1]{1}|[0-2]{1}[0-9]{1})-[2-9]{1}[0-9]{3} ([0-1]{1}[0-9]{1}|2[0-3]{1}):[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}.[0-9]{3}'.
Element 'root', attribute 'Timestamp': '04-21-2010 16:00:19.000' is not a valid value of the atomic type 'timeStampType'.

看起来我的正则表达式可能有问题。但是当我在命令行验证这个正则表达式时,它却通过了:

>>> import re
>>> pat = '(0[0-9]{1})|(1[0-2]{1})-(3[0-1]{1}|[0-2]{1}[0-9]{1})-[2-9]{1}[0-9]{3} ([0-1]{1}[0-9]{1}|2[0-3]{1}):[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}.[0-9]{3}'
>>> assert re.match(pat, '04-21-2010 16:00:19.000')
>>> 

我知道 XSD 的正则表达式并不支持所有功能,但我找到的 文档 表示我使用的每个功能应该都是可以的。

那么我到底哪里理解错了呢?为什么我的文档会失败呢?

2 个回答

3

这个表达式有几个错误。

  1. 你允许 00 作为有效的月份。
  2. A|BC 这个写法会匹配 ABC,而不是 ACBC。所以你用的表达式开头是 (0[0-9]{1})|,这会匹配任何包含 0009 的字符串。你想要的应该是 (0[1-9]|1[0-2])-,这样才能只匹配 0112,后面跟一个短横线。
  3. 你允许 00 作为有效的日期。
  4. 这个模式没有固定在文本的开头和结尾 - 你需要加上 ^$。这就是为什么你在用 Python 测试时会成功的原因。

顺便问一下,为什么不使用 xs:dateTime 呢?它的格式很相似 - yyyy-mm-ddThh:mm:ss.fff,我觉得。

3

你的 | 符号匹配的范围比你想象的要大。

(0[0-9]{1})|(1[0-2]{1})-(3[0-1]{1}|[0-2]{1}[0-9]{1})-[2-9]{1}[0-9]{3}

被解析为:

(0[0-9]{1})
    -or-
(1[0-2]{1})-(3[0-1]{1}|[0-2]{1}[0-9]{1})-[2-9]{1}[0-9]{3}

如果你想避免这种情况,你需要使用更多的分组,比如:

((0[0-9]{1})|(1[0-2]{1}))-((3[0-1]{1}|[0-2]{1}[0-9]{1}))-[2-9]{1}[0-9]{3} (([0-1]{1}[0-9]{1}|2[0-3]{1})):[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}.[0-9]{3}

撰写回答