针对同一 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 模式验证的主要内容,如果未能解决你的问题,请参考以下文章