针对同一 XML 模式 (XSD) 加快一批 XML 文件的 XML 模式验证

Posted

技术标签:

【中文标题】针对同一 XML 模式 (XSD) 加快一批 XML 文件的 XML 模式验证【英文标题】:Speeding up XML schema validations of a batch of XML files against the same XML schema (XSD) 【发布时间】:2012-12-01 15:44:28 【问题描述】:

我想加快针对同一个 XML 模式 (XSD) 验证一批 XML 文件的过程。唯一的限制是我在 php 环境中。

我当前的问题是我要验证的架构包括相当复杂的 2755 行 xhtml 架构 (http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd)。 即使对于非常简单的数据,这也需要很长时间(大约 30 秒验证)。 由于我的批处理中有数千个 XML 文件,因此扩展性并不好。

为了验证 XML 文件,我使用了标准 php-xml 库中的这两种方法。

DOMDocument::schemaValidate DOMDocument::schemaValidateSource

我认为 PHP 实现通过 HTTP 获取 XHTML 模式并构建一些内部表示(可能是 DOMDocument),并且在验证完成时将其丢弃。我在想 XML-libs 的某些选项可能会更改此行为以缓存此过程中的某些内容以供重用。

我已经建立了一个简单的测试设置来说明我的问题:

test-schema.xsd

<xs:schema attributeFormDefault="unqualified"
    elementFormDefault="qualified"
    targetNamespace="http://myschema.example.com/"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:myschema="http://myschema.example.com/"
    xmlns:xhtml="http://www.w3.org/1999/xhtml">
    <xs:import
        schemaLocation="http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd"
        namespace="http://www.w3.org/1999/xhtml">
    </xs:import>
    <xs:element name="Root">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="MyHTMLElement">
                    <xs:complexType>
                        <xs:complexContent>
                            <xs:extension base="xhtml:Flow"></xs:extension>
                        </xs:complexContent>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

test-data.xml

<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns="http://myschema.example.com/" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://myschema.example.com/ test-schema.xsd ">
  <MyHTMLElement>
    <xhtml:p>This is an XHTML paragraph!</xhtml:p>
  </MyHTMLElement>
</Root>

schematest.php

<?php
$data_dom = new DOMDocument();
$data_dom->load('test-data.xml');

// Multiple validations using the schemaValidate method.
for ($attempt = 1; $attempt <= 3; $attempt++) 
    $start = time();
    echo "schemaValidate: Attempt #$attempt returns ";
    if (!$data_dom->schemaValidate('test-schema.xsd')) 
        echo "Invalid!";
     else 
        echo "Valid!";
    
    $end = time();
    echo " in " . ($end-$start) . " seconds.\n";


// Loading schema into a string.
$schema_source = file_get_contents('test-schema.xsd');

// Multiple validations using the schemaValidate method.
for ($attempt = 1; $attempt <= 3; $attempt++) 
    $start = time();
    echo "schemaValidateSource: Attempt #$attempt returns ";
    if (!$data_dom->schemaValidateSource($schema_source)) 
        echo "Invalid!";
     else 
        echo "Valid!";
    
    $end = time();
    echo " in " . ($end-$start) . " seconds.\n";

运行这个 schematest.php 文件会产生以下输出:

schemaValidate: Attempt #1 returns Valid! in 30 seconds.
schemaValidate: Attempt #2 returns Valid! in 30 seconds.
schemaValidate: Attempt #3 returns Valid! in 30 seconds.
schemaValidateSource: Attempt #1 returns Valid! in 32 seconds.
schemaValidateSource: Attempt #2 returns Valid! in 30 seconds.
schemaValidateSource: Attempt #3 returns Valid! in 30 seconds.

非常欢迎任何有关如何解决此问题的帮助和建议!

【问题讨论】:

请制作该 W3C 架构的本地副本。 【参考方案1】:

您可以安全地从计时值中减去 30 秒作为开销。

对 W3C 服务器的远程请求被延迟,因为大多数库不反映缓存文档(甚至 HTTP 标头都表明这一点)。但是read your own:

W3C 服务器返回 DTD 的速度很慢。延迟是故意的吗?

是的。由于各种软件系统每天从我们的站点下载 DTD 数百万次(尽管我们的服务器有缓存指令),我们已经开始从我们的站点提供 DTD 和模式(DTD、XSD、ENT、MOD 等)。人为延迟。我们这样做的目标是让更多人关注我们持续存在的过多 DTD 流量问题,并保护我们网站其他部分的稳定性和响应时间。我们建议使用 HTTP 缓存或目录文件来提高性能。

W3.org 试图保持低请求。这是可以理解的。 PHP 的DomDocument 基于libxml。 libxml 允许设置外部实体加载器。在这种情况下,整个Catalog support section 都很有趣。

要解决相关问题,请设置catalog.xml 文件:

<?xml version="1.0"?>
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
    <system systemId="http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd"
            uri="xhtml1-transitional.xsd"/>
    <system systemId="http://www.w3.org/2001/xml.xsd"
            uri="xml.xsd"/>
</catalog>

保存两个.xsd 文件的副本,并在目录旁边使用该目录文件中给出的名称(如果您喜欢不同的目录,则相对路径和绝对路径file:///... 都可以使用)。

然后确保您的系统环境变量XML_CATALOG_FILES 设置为catalog.xml 文件的文件名。当一切都设置好后,验证就会运行:

schemaValidate: Attempt #1 returns Valid! in 0 seconds.
schemaValidate: Attempt #2 returns Valid! in 0 seconds.
schemaValidate: Attempt #3 returns Valid! in 0 seconds.
schemaValidateSource: Attempt #1 returns Valid! in 0 seconds.
schemaValidateSource: Attempt #2 returns Valid! in 0 seconds.
schemaValidateSource: Attempt #3 returns Valid! in 0 seconds.

如果仍然需要很长时间,这只是环境变量未设置到正确位置的标志。我已经在博客文章中处理了变量以及一些边缘情况:

Using Catalogs for Validation with PHP’s DOMDocument and Libxml2

它应该处理各种边缘情况,例如包含空格的文件名。

或者,可以创建一个简单的外部实体加载器回调函数,该函数使用 URL => 以数组形式映射本地文件系统:

$mapping = [
     'http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd'
         => 'schema/xhtml1-transitional.xsd',

     'http://www.w3.org/2001/xml.xsd'                          
         => 'schema/xml.xsd',
];

如图所示,我已将这两个 XSD 文件的逐字副本放入名为 schema 的子目录中。下一步是使用libxml_set_external_entity_loader 来激活带有映射的回调函数。磁盘上已经存在的文件是首选并直接加载。如果例程遇到没有映射的非文件,则会抛出 RuntimeException 并带有详细消息:

libxml_set_external_entity_loader(
    function ($public, $system, $context) use ($mapping) 

        if (is_file($system)) 
            return $system;
        

        if (isset($mapping[$system])) 
            return __DIR__ . '/' . $mapping[$system];
        

        $message = sprintf(
            "Failed to load external entity: Public: %s; System: %s; Context: %s",
            var_export($public, 1), var_export($system, 1),
            strtr(var_export($context, 1), [" (\n  " => '(', "\n " => '', "\n" => ''])
        );

        throw new RuntimeException($message);
    
);

设置此外部实体加载器后,远程请求不再有延迟。

就是这样。见Gist。注意:这个外部实体加载器是为加载 XML 文件而编写的,以从磁盘进行验证并将 XSD URI“解析”为本地文件名。其他类型的操作(例如基于 DTD 的验证)可能需要一些代码更改/扩展。更可取的是 XML 目录。它也适用于不同的工具。

【讨论】:

非常感谢!我认为这是一个解析问题 :) 但是当我回想 30 秒听起来很圆,作为一个随机的人工制品发生。那是一堆! @creen:我再次编辑了答案,它现在展示了如何设置外部实体加载器,它可以即时翻译本地文件。我会说这是首选方式,而不是编辑本地副本。 外部实体加载器很好,但请注意,使用 libxml 的目录支持可以在没有新 PHP 代码的情况下完成基本相同的事情。 @C. M. Sperberg-McQueen:是的,我今天在 Windows 上进行测试,但在安装了 PHP 的 Windows 上找不到目录的位置信息。我想直接使用目录,因为它对我来说更直接。你知道更多吗?可能还与 PHP 结合使用? @hakre 非常好......这将在未来为我解决很多问题:)【参考方案2】:

作为@hakre 的替代方案:第一次尝试下载外部资源 (DTD),然后使用下载的版本:

libxml_set_external_entity_loader(    
    function ($public, $system, $context) 
        if(is_file($system))
            return $system;
        
        $cached_file= tempnam(sys_get_temp_dir(), md5($system));
        if (is_file($cached_file)) 
            return $cached_file;
        
        copy($system,$cached_file);
        return $cached_file;
    
);

【讨论】:

以上是关于针对同一 XML 模式 (XSD) 加快一批 XML 文件的 XML 模式验证的主要内容,如果未能解决你的问题,请参考以下文章

要针对多个 xsd 模式验证 XML

针对多个模式定义验证 XML 文件

针对XML模式的通用XML文件验证器(XSD文件)

使用C#导入使用XSD验证XML

针对 xsd 执行 xml 验证

在 Java 中针对 xsd 的 XML 验证