Changeset 1920

Show
Ignore:
Timestamp:
05/10/07 14:52:23 (20 months ago)
Author:
david
Message:

initial support for native xml config handlers. config validation much more flexible now, added internal support for xml schema, relaxng and (dummy) schematron. all the old stuff still works and always will (or, well, at least until 1.1 or so). more to come. refs #519

Location:
branches/david-xml_config_handlers/src
Files:
6 modified

Legend:

Unmodified
Added
Removed
  • branches/david-xml_config_handlers/src/config/AgaviConfigCache.class.php

    r1917 r1920  
    5656   * @since      0.9.0 
    5757   */ 
    58   private static function callHandler($handler, $config, $cache, $context) 
    59   { 
    60  
     58  private static function callHandler($name, $config, $cache, $context) 
     59  { 
    6160    if(self::$handlers === null) { 
    6261      // we need to load the handlers first 
     
    6463      self::loadConfigHandlers(); 
    6564    } 
    66  
     65     
    6766    // grab the base name of the handler 
    68     $basename = basename($handler); 
    69  
    70     if(isset(self::$handlers[$handler])) { 
     67    $basename = basename($name); 
     68     
     69    $handlerInfo = null; 
     70     
     71    if(isset(self::$handlers[$name])) { 
    7172      // we have a handler associated with the full configuration path 
    72       // call the handler and retrieve the cache data 
    73       $data = self::$handlers[$handler]->execute($config, $context); 
    74       self::writeCacheFile($config, $cache, $data, false); 
    75       return; 
     73      $handlerInfo = self::$handlers[$name]; 
    7674    } elseif(isset(self::$handlers[$basename])) { 
    7775      // we have a handler associated with the configuration base name 
    78       // call the handler and retrieve the cache data 
    79       $data = self::$handlers[$basename]->execute($config, $context); 
    80       self::writeCacheFile($config, $cache, $data, false); 
    81       return; 
     76      $handlerInfo = self::$handlers[$basename]; 
    8277    } else { 
    8378      // let's see if we have any wildcard handlers registered that match 
    8479      // this basename 
    85       foreach(self::$handlers as $key => $handlerInstance)  { 
     80      foreach(self::$handlers as $key => $value)  { 
    8681        // replace wildcard chars in the configuration and create the pattern 
    8782        $pattern = sprintf('#%s#', str_replace('\*', '.*?', preg_quote($key))); 
    88  
    89         if(preg_match($pattern, $handler)) { 
    90           // call the handler and retrieve the cache data 
    91           $data = $handlerInstance->execute($config, $context); 
    92           self::writeCacheFile($config, $cache, $data, false); 
    93           return; 
     83         
     84        if(preg_match($pattern, $name)) { 
     85          $handlerInfo = $value; 
     86          break; 
    9487        } 
    9588      } 
    9689    } 
    97  
    98     // we do not have a registered handler for this file 
    99     $error = 'Configuration file "%s" does not have a registered handler'; 
    100     $error = sprintf($error, $config); 
    101     throw new AgaviConfigurationException($error); 
     90     
     91    if($handlerInfo === null) { 
     92      // we do not have a registered handler for this file 
     93      $error = 'Configuration file "%s" does not have a registered handler'; 
     94      $error = sprintf($error, $config); 
     95      throw new AgaviConfigurationException($error); 
     96    } 
     97     
     98    // call the handler and retrieve the cache data 
     99    $handler = new $handlerInfo['class']; 
     100    if($handler instanceof AgaviIXmlConfigHandler) { 
     101       
     102    } else { 
     103      if(isset($handlerInfo['validation'])) 
     104      $handler->initialize($handlerInfo['validation'], null, $handlerInfo['parameters']); 
     105    } 
     106     
     107    $data = $handler->execute($config, $context); 
     108    self::writeCacheFile($config, $cache, $data, false); 
    102109  } 
    103110 
     
    141148 
    142149    return $cache; 
    143  
    144150  } 
    145151 
     
    234240    // since we only need the parser and handlers when the config is not cached 
    235241    // it is sufficient to include them at this stage 
     242    require(AgaviConfig::get('core.agavi_dir') . '/config/AgaviBaseConfigHandler.class.php'); 
     243    require(AgaviConfig::get('core.agavi_dir') . '/config/AgaviConfigHandler.class.php'); 
     244    require(AgaviConfig::get('core.agavi_dir') . '/config/AgaviIXmlConfigHandler.class.php'); 
     245    require(AgaviConfig::get('core.agavi_dir') . '/config/AgaviXmlConfigHandler.class.php'); 
     246    require(AgaviConfig::get('core.agavi_dir') . '/config/AgaviAutoloadConfigHandler.class.php'); 
    236247    require(AgaviConfig::get('core.agavi_dir') . '/config/AgaviConfigHandlersConfigHandler.class.php'); 
    237248    require(AgaviConfig::get('core.agavi_dir') . '/config/AgaviConfigValueHolder.class.php'); 
     
    240251 
    241252    // manually create our config_handlers.xml handler 
    242     self::$handlers['config_handlers.xml'] = new AgaviConfigHandlersConfigHandler(); 
    243     self::$handlers['config_handlers.xml']->initialize(AgaviConfig::get('core.agavi_dir') . '/config/xsd/config_handlers.xsd'); 
     253    self::$handlers['config_handlers.xml'] = array( 
     254      'class' => 'AgaviConfigHandlersConfigHandler',  
     255      'parameters' => array( 
     256      ), 
     257      'validation' => array( 
     258        AgaviXmlConfigParser::VALIDATION_TYPE_XMLSCHEMA => array( 
     259          AgaviConfig::get('core.agavi_dir') . '/config/xsd/config_handlers.xsd', 
     260        ), 
     261      ), 
     262    ); 
    244263 
    245264    $cfg = AgaviConfig::get('core.config_dir') . '/config_handlers.xml'; 
     
    303322   *             extension couldn't be found. 
    304323   * 
     324   * @deprecated New-style config handlers don't call this method anymore. 
     325   * 
    305326   * @author     Dominik del Bondio <ddb@bitxtender.com> 
    306327   * @author     David Zülke <dz@bitxtender.com> 
     
    309330  public static function parseConfig($config, $autoloadParser = true, $validateFile = null, $parserClass = null) 
    310331  { 
    311     $parser = new AgaviXmlConfigParser(); 
    312      
    313     return $parsers->parse($config, $validateFile); 
    314   } 
    315  
     332    $parser = new AgaviConfigParser(); 
     333     
     334    return $parser->parse($config, $validateFile); 
     335  } 
    316336} 
    317337 
  • branches/david-xml_config_handlers/src/config/AgaviConfigHandlersConfigHandler.class.php

    r1737 r1920  
    5353    // parse the config file 
    5454    $configurations = $this->orderConfigurations(AgaviConfigCache::parseConfig($config, false, $this->getValidationFile(), $this->parser)->configurations, AgaviConfig::get('core.environment')); 
    55  
     55     
    5656    // init our data arrays 
    57     $data     = array(); 
    58  
     57    $data = array(); 
     58     
    5959    foreach($configurations as $cfg) { 
    6060      // let's do our fancy work 
    6161      foreach($cfg->handlers as $handler) { 
    6262        $pattern = $handler->getAttribute('pattern'); 
    63  
     63         
    6464        $category = var_export(AgaviToolkit::normalizePath($this->replaceConstants($pattern)), true); 
    65  
    66         $class = $handler->getAttribute('class'); 
    67  
    68         $parameters = $this->getItemParameters($handler); 
    69  
     65         
     66        $class = var_export($handler->getAttribute('class'), true); 
     67         
     68        $validation = array( 
     69          AgaviXmlConfigParser::VALIDATION_TYPE_RELAXNG    => array( 
     70          ), 
     71          AgaviXmlConfigParser::VALIDATION_TYPE_SCHEMATRON => array( 
     72          ), 
     73          AgaviXmlConfigParser::VALIDATION_TYPE_XMLSCHEMA  => array( 
     74          ), 
     75        ); 
     76        if($handler->hasAttribute('validate')) { 
     77          $validation[AgaviXmlConfigParser::VALIDATION_TYPE_XMLSCHEMA][] = $this->literalize($handler->getAttribute('validate')); 
     78        } elseif(false) { 
     79          // TODO: check for <validations><validation type="schematron"> children here 
     80        } 
     81        $validation = var_export($validation, true); 
     82         
     83        $parameters = var_export($this->getItemParameters($handler), true); 
     84         
    7085        // append new data 
    71         $tmp    = "self::\$handlers[%s] = new %s();"; 
    72         $data[] = sprintf($tmp, $category, $class); 
    73  
    74         $tmp    = "self::\$handlers[%s]->initialize(%s, %s, %s);"; 
    75         $data[] = sprintf($tmp, $category, var_export($this->literalize($handler->getAttribute('validate')), true), var_export($handler->getAttribute('parser'), true), var_export($parameters, true)); 
     86        $tmp    = "self::\$handlers[%s] = array('class' => %s, 'parameters' => %s, 'validation' => %s);"; 
     87        $data[] = sprintf($tmp, $category, $class, $parameters, $validation); 
    7688      } 
    7789    } 
  • branches/david-xml_config_handlers/src/config/AgaviConfigParser.class.php

    r1656 r1920  
    1616 
    1717/** 
    18  * AgaviConfigHandler allows a developer to create a custom formatted  
    19  * configuration file format. 
     18 * AgaviConfigParser parses XML files using AgaviXmlConfigParser, but returns 
     19 * old-style ConfigValueHolders. 
    2020 * 
    2121 * @package    agavi 
    2222 * @subpackage config 
    2323 * 
    24  * @author     Dominik del Bondio <ddb@bitxtender.com> 
     24 * @author     David Zülke <dz@bitxtender.com> 
    2525 * @copyright  Authors 
    2626 * @copyright  The Agavi Project 
     27 * 
     28 * @deprecated Superseded by AgaviXmlConfigParser 
    2729 * 
    2830 * @since      0.11.0 
     
    3032 * @version    $Id$ 
    3133 */ 
    32 abstract class AgaviConfigParser 
     34class AgaviConfigParser 
    3335{ 
    3436  /** 
    35    * Execute this configuration parser. 
     37   * @var        string The encoding of the DOMDocument 
     38   */ 
     39  protected $encoding = 'utf-8'; 
     40   
     41  /** 
     42   * @param      string An absolute filesystem path to a configuration file. 
     43   * @param      array  An associative array of validation information. 
    3644   * 
    37    * @param      string An absolute filesystem path to a configuration file. 
     45   * @return     AgaviConfigValueHolder The data handlers use to perform tasks. 
    3846   * 
    39    * @return     AgaviConfigValueHolder Data to be written to a cache file. 
     47   * @author     David Zülke <dz@bitxtender.com> 
     48   * @since      0.11.0 
     49   */ 
     50  public function parse($config, array $validationInfo = array()) 
     51  { 
     52    $parser = new AgaviXmlConfigParser(); 
     53     
     54    $doc = $parser->parse($config, $validationInfo); 
     55     
     56    $this->encoding = $doc->encoding; 
     57     
     58    $rootRes = new AgaviConfigValueHolder(); 
     59     
     60    if($doc->documentElement) { 
     61      $this->parseNodes(array($doc->documentElement), $rootRes); 
     62    } 
     63     
     64    return $rootRes; 
     65  } 
     66 
     67  /** 
     68   * Iterates thru a list of nodes and stores to each node in the 
     69   * ConfigValueHolder 
    4070   * 
    41    * @throws     <b>AgaviUnreadableException</b> If a requested configuration 
    42    *                                             file does not exist or is not 
    43    *                                             readable. 
    44    * @throws     <b>AgaviParseException</b>      If a requested configuration 
    45    *                                             file is improperly formatted. 
     71   * @param      mixed An array or an object that can be iterated over 
     72   * @param      AgaviXmlValueHolder The storage for the info from the nodes 
     73   * @param      bool Whether this list is the singular form of the parent node 
    4674   * 
     75   * @author     David Zülke <dz@bitxtender.com> 
    4776   * @author     Dominik del Bondio <ddb@bitxtender.com> 
    4877   * @since      0.11.0 
    4978   */ 
    50   public abstract function parse($config); 
     79  protected function parseNodes($nodes, AgaviConfigValueHolder $parentVh, $isSingular = false) 
     80  { 
     81    foreach($nodes as $node) { 
     82      if($node->nodeType == XML_ELEMENT_NODE && (!$node->namespaceURI || $node->namespaceURI == AgaviXmlConfigParser::XML_NAMESPACE)) { 
     83        $vh = new AgaviConfigValueHolder(); 
     84        $nodeName = $this->convertEncoding($node->localName); 
     85        $vh->setName($nodeName); 
     86        $parentVh->addChildren($nodeName, $vh); 
    5187 
     88        foreach($node->attributes as $attribute) { 
     89          if((!$attribute->namespaceURI || $attribute->namespaceURI == AgaviXmlConfigParser::XML_NAMESPACE)) { 
     90            $vh->setAttribute($this->convertEncoding($attribute->localName), $this->convertEncoding($attribute->nodeValue)); 
     91          } 
     92        } 
     93 
     94        // there are no child nodes so we set the node text contents as the value for the valueholder 
     95        if($node->getElementsByTagName('*')->length == 0) { 
     96          $vh->setValue($this->convertEncoding($node->nodeValue)); 
     97        } 
     98 
     99        if($node->hasChildNodes()) { 
     100          $this->parseNodes($node->childNodes, $vh); 
     101        } 
     102      } 
     103    } 
     104  } 
     105   
     106  /** 
     107   * Handle encoding for a value, i.e. translate from UTF-8 if necessary. 
     108   * 
     109   * @param      string A UTF-8 string value from the DomDocument. 
     110   * 
     111   * @return     string A value in the correct encoding of the parsed document. 
     112   * 
     113   * @author     David Zülke <dz@bitxtender.com> 
     114   * @since      0.11.0 
     115   */ 
     116  protected function convertEncoding($value) 
     117  { 
     118    if($this->encoding == 'utf-8') { 
     119      return $value; 
     120    } elseif($this->encoding == 'iso-8859-1') { 
     121      return utf8_decode($value); 
     122    } elseif(function_exists('iconv')) { 
     123      return iconv('UTF-8', $this->encoding, $value); 
     124    } else { 
     125      throw new AgaviParseException('No iconv module available, configuration file "' . $this->config . '" with input encoding "' . $this->encoding . '" cannot be parsed.'); 
     126    } 
     127  } 
    52128} 
    53129 
  • branches/david-xml_config_handlers/src/config/AgaviXmlConfigHandler.class.php

    r1917 r1920  
    3030class AgaviXmlConfigHandler extends AgaviBaseConfigHandler implements AgaviIXmlConfigHandler 
    3131{ 
     32  public function execute($config, $context = null) 
     33  { 
     34  } 
    3235} 
  • branches/david-xml_config_handlers/src/config/AgaviXmlConfigParser.class.php

    r1913 r1920  
    2929 * @version    $Id$ 
    3030 */ 
    31  
    3231class AgaviXmlConfigParser extends AgaviConfigParser 
    3332{ 
    3433  const XML_NAMESPACE = 'http://agavi.org/agavi/1.0/config'; 
    3534   
    36   /** 
    37    * @var        DomXPath A DomXPath instance used to parse this document. 
    38    */ 
    39   protected $xpath = null; 
    40    
    41   /** 
    42    * @var        string The encoding of the file that's being parsed here. 
    43    */ 
    44   protected $encoding = 'utf-8'; 
    45    
    46   /** 
    47    * @var        string The name of the config file we're parsing. 
    48    */ 
    49   protected $config = ''; 
    50  
    51   /** 
    52    * @see        AgaviConfigParser::parse() 
     35  const VALIDATION_TYPE_XMLSCHEMA = 'xml_schema'; 
     36   
     37  const VALIDATION_TYPE_RELAXNG = 'relax_ng'; 
     38   
     39  const VALIDATION_TYPE_SCHEMATRON = 'schematron'; 
     40   
     41  /** 
     42   * @param      string An absolute filesystem path to a configuration file. 
     43   * @param      array  An associative array of validation information. 
     44   * 
     45   * @return     array An array of DOMDocuments (from child to parent). 
    5346   * 
    5447   * @author     David Zülke <dz@bitxtender.com> 
     
    5649   * @since      0.11.0 
    5750   */ 
    58   public function parse($config, $validationFile = null) 
     51  public function parse($config, array $validation = array()) 
    5952  { 
    6053    if(!is_readable($config)) { 
     
    6356    } 
    6457     
    65     $doc = $this->loadAndTransform($config, $validationFile = null); 
    66      
    67     $rootRes = new AgaviConfigValueHolder(); 
    68      
    69     if($doc->documentElement) { 
    70       $this->parseNodes(array($doc->documentElement), $rootRes); 
    71     } 
    72      
    73     return $rootRes; 
    74   } 
    75    
    76   /** 
    77    * Load the file into DOM, resolve XIncludes, apply XSL, validate against XSD. 
    78    * 
    79    * @param      string The path to the XML file 
    80    * @param      string The path to the validation file. 
    81    * 
    82    * @return     DOMDocument The fully loaded and transformed DOM document. 
    83    * 
    84    * @author     David Zülke <dz@bitxtender.com> 
    85    * @since      0.11.0 
    86    */ 
    87   public function loadAndTransform($config, $validationFile = null) 
    88   { 
    89     $this->config = $config; 
    90      
     58    $doc = $this->load($config); 
     59     
     60    $this->transform($doc); 
     61     
     62    $this->validate($doc, $validation); 
     63     
     64    $this->cleanup($doc); 
     65     
     66    return $doc; 
     67  } 
     68   
     69  /** 
     70   * Create and return a DOMXPath object for the document. 
     71   * 
     72   * @param      DOMDocument The document to create the DOMXPath object for. 
     73   * @param      bool        If the XML namespace from the document element 
     74   *                         should be registered as 'agavi'. 
     75   * 
     76   * @return     DOMXPath A DOMXPath instance for the document. 
     77   * 
     78   * @author     David Zülke <dz@bitxtender.com> 
     79   * @since      0.11.0 
     80   */ 
     81  public function createXpath(DOMDocument $doc, $registerNamespace = true) 
     82  { 
     83    $xpath = new DOMXPath($doc); 
     84     
     85    if($registerNamespace && $doc->documentElement) { 
     86      $xpath->registerNamespace('agavi', $doc->documentElement->namespaceURI); 
     87    } 
     88     
     89    return $xpath; 
     90  } 
     91   
     92  /** 
     93   * Load the configuration file into DOM and resolve XIncludes. 
     94   * 
     95   * @param      string The path to the configuration file. 
     96   * 
     97   * @return     DOMDocument The loaded document. 
     98   * 
     99   * @author     David Zülke <dz@bitxtender.com> 
     100   * @since      0.11.0 
     101   */ 
     102  public function load($config) 
     103  { 
    91104    $luie = libxml_use_internal_errors(true); 
    92105    libxml_clear_errors(); 
     
    110123    } 
    111124     
    112     $this->encoding = strtolower($doc->encoding); 
    113      
    114125    // replace %lala% directives in XInclude href attributes 
    115126    foreach($doc->getElementsByTagNameNS('http://www.w3.org/2001/XInclude', '*') as $element) { 
     
    156167    } 
    157168     
    158     $this->xpath = new DOMXPath($doc); 
    159      
    160     $stylesheetProcessingInstructions = $this->xpath->query("//processing-instruction('xml-stylesheet')", $doc); 
     169    libxml_use_internal_errors($luie); 
     170     
     171    return $doc; 
     172  } 
     173   
     174  /** 
     175   * Transform the document using info from embedded processing instructions. 
     176   * 
     177   * @param      DOMDocument The document to transform. 
     178   * 
     179   * @author     David Zülke <dz@bitxtender.com> 
     180   * @since      0.11.0 
     181   */ 
     182  public function transform(DOMDocument $doc) 
     183  { 
     184    $luie = libxml_use_internal_errors(true); 
     185     
     186    $xpath = $this->createXpath($doc, false); 
     187     
     188    $stylesheetProcessingInstructions = $xpath->query("//processing-instruction('xml-stylesheet')", $doc); 
    161189    foreach($stylesheetProcessingInstructions as $pi) { 
    162190      $fragment = $doc->createDocumentFragment(); 
     
    168196        if(strpos($href, '#') === 0) { 
    169197          // embedded XSL 
    170           $stylesheets = $this->xpath->query("//*[@id='" . substr($href, 1) . "']", $doc); 
     198          $stylesheets = $xpath->query("//*[@id='" . substr($href, 1) . "']", $doc); 
    171199          if($stylesheets->length) { 
    172200            $xsl = new DomDocument(); 
     
    220248          } 
    221249        } 
    222  
     250         
    223251        $proc = new XSLTProcessor(); 
    224252        $proc->importStylesheet($xsl); 
     
    242270          ); 
    243271        } 
    244  
    245         $this->xpath = null; 
     272         
     273        unset($xpath); 
    246274         
    247275        $newdoc = $proc->transformToDoc($doc); 
     
    264292          ); 
    265293        } 
    266  
     294         
    267295        if($newdoc) { 
    268296          $doc = $newdoc; 
    269297        } 
    270298         
    271         $this->xpath = new DOMXPath($doc); 
    272          
    273299        $pi->parentNode->removeChild($pi); 
    274300         
     
    277303    } 
    278304     
    279     if($doc->documentElement) { 
    280       $this->xpath->registerNamespace('agavi', $doc->documentElement->namespaceURI); 
    281      
    282       // remove top-level <sandbox> elements 
    283       $sandboxes = $this->xpath->query('/agavi:configurations/agavi:sandbox', $doc); 
    284       foreach($sandboxes as $sandbox) { 
    285         $sandbox->parentNode->removeChild($sandbox); 
    286       } 
    287     } 
    288      
    289     if($validationFile) { 
     305    libxml_use_internal_errors($luie); 
     306  } 
     307   
     308  /** 
     309   * Load the file into DOM, resolve XIncludes, apply XSL, validate against XSD. 
     310   * 
     311   * @param      string The path to the XML file 
     312   * @param      string The path to the validation file. 
     313   * 
     314   * @return     DOMDocument The fully loaded and transformed DOM document. 
     315   * 
     316   * @author     David Zülke <dz@bitxtender.com> 
     317   * @since      0.11.0 
     318   */ 
     319  public function validate(DOMDocument $doc, array $validationInfo = array()) 
     320  { 
     321    foreach($validationInfo as $type => $files) { 
     322      switch($type) { 
     323        case self::VALIDATION_TYPE_XMLSCHEMA: 
     324          $this->validateXmlschema($doc, (array) $files); 
     325          break; 
     326        case self::VALIDATION_TYPE_RELAXNG: 
     327          $this->validateRelaxng($doc, (array) $files); 
     328          break; 
     329        case self::VALIDATION_TYPE_SCHEMATRON: 
     330          $this->validateSchematron($doc, (array) $files); 
     331          break; 
     332      } 
     333    } 
     334  } 
     335   
     336  /** 
     337   * Clean up the document. 
     338   * 
     339   * @param      DOMDocument The document to clean up. 
     340   * 
     341   * @author     David Zülke <dz@bitxtender.com> 
     342   * @since      0.11.0 
     343   */ 
     344  public function cleanup(DOMDocument $doc) 
     345  { 
     346    $xpath = $this->createXpath($doc); 
     347     
     348    // remove top-level <sandbox> elements 
     349    $sandboxes = $xpath->query('/agavi:configurations/agavi:sandbox', $doc); 
     350    foreach($sandboxes as $sandbox) { 
     351      $sandbox->parentNode->removeChild($sandbox); 
     352    } 
     353     
     354    unset($xpath); 
     355  } 
     356   
     357  /** 
     358   * Validate the document against the given list of XML Schema files. 
     359   * 
     360   * @param      DOMDocument The document to validate. 
     361   * @param      array       An array of file names to validate. 
     362   * 
     363   * @author     David Zülke <dz@bitxtender.com> 
     364   * @since      0.11.0 
     365   */ 
     366  public function validateXmlschema(DOMDocument $doc, array $validationFiles = array()) 
     367  { 
     368    $luie = libxml_use_internal_errors(true); 
     369     
     370    foreach($validationFiles as $validationFile) { 
    290371      if(!is_readable($validationFile)) { 
    291372        libxml_use_internal_errors($luie); 
     
    293374        throw new AgaviUnreadableException($error); 
    294375      } 
     376       
    295377      if(!$doc->schemaValidate($validationFile)) { 
    296378        $errors = array(); 
     
    312394     
    313395    libxml_use_internal_errors($luie); 
    314      
    315     return $doc; 
    316   } 
     396  } 
     397   
     398  /** 
     399   * Validate the document against the given list of RELAX NG files. 
     400   * 
     401   * @param      DOMDocument The document to validate. 
     402   * @param      array       An array of file names to validate. 
     403   * 
     404   * @author     David Zülke <dz@bitxtender.com> 
     405   * @since      0.11.0 
     406   */ 
     407  public function validateRelaxng(DOMDocument $doc, array $validationFiles = array()) 
     408  { 
     409    $luie = libxml_use_internal_errors(true); 
     410     
     411    foreach($validationFiles as $validationFile) { 
     412      if(!is_readable($validationFile)) { 
     413        libxml_use_internal_errors($luie); 
     414        $error = 'Validation file "' . $validationFile . '" for configuration file "' . $config . '" does not exist or is unreadable'; 
     415        throw new AgaviUnreadableException($error); 
     416      } 
     417       
     418      if(!$doc->relaxNGValidate($validationFile)) {