Ускорение проверки XML-схемы пакета XML-файлов по одной и той же XML-схеме (XSD)
Я хотел бы ускорить процесс проверки пакета XML-файлов по одной и той же схеме XML (XSD). Единственные ограничения заключаются в том, что я нахожусь в среде PHP.
Моя текущая проблема заключается в том, что схема, которую я хотел бы проверить, включает довольно сложную схему xhtml из 2755 строк(http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd). Даже для очень простых данных это занимает много времени (около 30 секунд для проверки). Поскольку у меня есть тысячи XML-файлов в моя партия, это не очень хорошо масштабируется.
Для проверки XML-файла я использую оба этих метода из стандартных библиотек php-xml.
- DOMDocument::Проверка схемы
- DOMDocument::schemavalidatesource домдокумент::schemavalidatesource
Я думаю, что реализация PHP извлекает схему XHTML через HTTP и создает некоторое внутреннее представление (возможно, DOMDocument), и это отбрасывается после завершения проверки. Я думал, что какой-нибудь вариант для библиотеки XML могут изменить это поведение, чтобы кэшировать что-то в этом процессе для повторного использования.
Я построил простую тестовую установку, которая иллюстрирует мою проблему:
Тест-схема.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.
Любая помощь и предложения о том, как решить эту проблему, очень приветствуются!
2 answers
Вы можете безопасно вычесть 30 секунд из значений времени в качестве накладных расходов.
Удаленные запросы к серверам W3C задерживаются, потому что большинство библиотек не отражают кэширование документов (даже заголовки HTTP предполагают это). Но читайте свои собственные:
Серверы W3C медленно возвращают DTD. Является ли задержка преднамеренной?
Да. Из-за различных программных систем, загружающих DTD с нашего сайта миллионы раз в день (несмотря на директивы кэширования наших серверов), мы начали обслуживать DTD и схемы (DTD, XSD, ENT, MOD и т.д.) с нашего сайта с искусственной задержкой. Наши цели при этом заключаются в том, чтобы привлечь больше внимания к нашим текущим проблемам с чрезмерным трафиком DTD, а также защитить стабильность и время отклика остальной части нашего сайта. Мы рекомендуем кэширование HTTP или файлы каталога для повышения производительности.
W3.org старается держать запросы на низком уровне. Это понятно. PHP DomDocument
основан на 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
с подробным сообщением:
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-файла для проверки с диска и "разрешения" URI XSD в локальные имена файлов. Для других операций (например, проверка на основе DTD) могут потребоваться некоторые изменения кода / расширение. Более предпочтительным является XML-каталог. Это также работает для различных инструментов.
В качестве альтернативы @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;
}
);