7

我想加快针对同一个 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 的某些选项可能会更改此行为以缓存此过程中的某些内容以供重用。

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

测试模式.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>

测试数据.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.

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

4

2 回答 2

17

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

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

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

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

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

要解决有问题的问题,请设置一个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.

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

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

或者,可以创建一个简单的外部实体加载器回调函数,该函数以数组的形式使用本地文件系统的 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将抛出 a 并显示详细消息:

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);
    }
);

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

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

于 2012-12-13T18:00:18.120 回答
0

作为@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;
    }
);
于 2015-03-16T16:09:15.533 回答