Ускорение проверки XML-схемы пакета XML-файлов по той же схеме XML (XSD)

Я хотел бы ускорить процесс проверки партии XML-файлов по одной и той же единой схеме XML (XSD). Только ограничения заключаются в том, что я в среде PHP.

Моя текущая проблема заключается в том, что схема, к которой я хочу выполнить проверку, включает довольно сложную схему xhtml из 2755 строк (http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd). Даже для очень простых данных это занимает много времени (около 30 секунд проверки). Поскольку у меня есть тысячи XML-файлов в моей партии, это не очень хорошо масштабируется.

Для проверки файла XML я использую оба этих метода, из стандартных библиотек phpxml.

  • DOMDocument :: schemaValidate
  • DOMDocument :: schemaValidateSource

Я думаю, что реализация PHP получает схему XHTML через HTTP и создает некоторое внутреннее представление (возможно, DOMDocument) и что это отбрасывается, когда проверка завершена. Я думал, что некоторый вариант для XML-libs может изменить это поведение, чтобы кэшировать что-то в этом процессе для повторного использования.

Я создал простую тестовую установку, которая иллюстрирует мою проблему:

Тест-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> 

Тест-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. 

Любая помощь и предложения о том, как решить эту проблему, очень приветствуются!

Вы можете безопасно вычесть 30 секунд из значений времени в качестве накладных расходов.

Удаленные запросы на серверы W3C задерживаются, поскольку большинство библиотек не отражают кэширование документов (даже заголовки HTTP предлагают это). Но читайте сами :

Серверы W3C медленно возвращают DTD. Является ли задержка преднамеренной?

Да. Из-за различных программных систем, загружающих DTD с нашего сайта миллионы раз в день (несмотря на директивы кэширования наших серверов), мы начали обслуживать DTD и схему (DTD, XSD, ENT, MOD и т. Д.) С нашего сайта с помощью искусственная задержка. Наши цели в том, чтобы привлечь больше внимания к текущим проблемам с чрезмерным DTD-трафиком и защитить стабильность и время отклика остальной части нашего сайта. Мы рекомендуем HTTP-кеширование или файлы каталога для повышения производительности.

W3.org пытается поддерживать низкие запросы. Это понятно. PHP DomDocument основан на libxml. И libxml позволяет установить внешний загрузчик объектов. В этом случае интересен весь раздел поддержки каталога .

Чтобы решить проблему, catalog.xml файл 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:///... абсолютных путей 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. 

Если это по-прежнему занимает много времени, это просто признак того, что переменная окружения не установлена ​​в нужное место. Я обработал переменную, а также некоторые краевые случаи, а также в блоге:

  • Использование каталогов для проверки с помощью DOMDocument PHP и 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 который не имеет сопоставления, RuntimeException будет 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. Он также работает для разных инструментов.

В качестве альтернативы @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; } );