2012-12-13 81 views
4

我想加快驗證一批XML文件對照同一個XML模式(XSD)的過程。只有限制是我在PHP環境中。針對同一個XML模式(XSD)加快對一批XML文件的XML模式驗證

我目前的問題是我想驗證的模式包括2755行(http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd)的相當複雜的xhtml模式。 即使對於非常簡單的數據,這也需要很長時間(約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. 

任何幫助,並就如何解決這一問題的建議,非常歡迎!

+0

請即W3C架構的本地副本。 – DanMan

回答

11

您可以安全地將時間值減去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. 

如果它仍然需要很長,它只是一個跡象,表明環境變量沒有設置到正確的位置。我已經在博客文章中處理了變量以及一些邊緣情況:

它應該照顧不同的邊緣情況,例如包含空格的文件名。

替代地,可以創建一個使用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', 
]; 

作爲該顯示,I」已經將這兩個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目錄。它也適用於不同的工具。

+0

非常感謝!我認爲這是一個解析問題:)但是當我回想起30秒聽起來很像是隨機的人造物出現時。這是一堆! – kraenhansen

+0

@creen:我再次編輯了答案,它現在顯示瞭如何設置外部實體加載程序,它可以即時地轉換爲本地文件。我會說這是首選方式,而不是編輯本地副本。 – hakre

+0

外部實體加載器很好,但請注意,使用libxml的目錄支持在沒有新的PHP代碼的情況下實現基本相同的事情。 –

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