home *** CD-ROM | disk | FTP | other *** search
- <?php
- //
- // +----------------------------------------------------------------------+
- // | DTD_XML_Validator class |
- // +----------------------------------------------------------------------+
- // | Copyright (c) 2003 Tomas Von Veschler Cox |
- // +----------------------------------------------------------------------+
- // | This source file is subject to version 2.02 of the PHP license, |
- // | that is bundled with this package in the file LICENSE, and is |
- // | available at through the world-wide-web at |
- // | http://www.php.net/license/2_02.txt. |
- // | If you did not receive a copy of the PHP license and are unable to |
- // | obtain it through the world-wide-web, please send a note to |
- // | license@php.net so we can mail you a copy immediately. |
- // +----------------------------------------------------------------------+
- // | Authors: Tomas V.V.Cox <cox@idecnet.com> |
- // | |
- // +----------------------------------------------------------------------+
- //
- // $Id: XmlValidator.php,v 1.5 2004/05/17 19:51:45 schst Exp $
- //
- //
- // TODO:
- // - Give better error messages :-)
- // - Implement error codes and better error reporting
- // - Add support for //XXX Missing .. (you may find them arround the code)
- //
-
- require_once 'XML/DTD.php';
- require_once 'XML/Tree.php';
-
- /**
- * XML_DTD_XmlValidator
- *
- * Usage:
- *
- * <code>
- * $validator = XML_DTD_XmlValidator;
- * // This will check if the xml is well formed
- * // and will validate it against its DTD
- * if (!$validator->isValid($dtd_file, $xml_file)) {
- * die($validator->getMessage());
- * }
- * </code>
- *
- * @package XML_DTD
- * @category XML
- * @author Tomas V.V.Cox <cox@idecnet.com>
- * @copyright Copyright (c) 2003
- * @version $Id: XmlValidator.php,v 1.5 2004/05/17 19:51:45 schst Exp $
- * @access public
- */
- class XML_DTD_XmlValidator
- {
-
- var $dtd = array();
- var $_errors = false;
-
- /**
- * XML_DTD_XmlValidator::isValid()
- *
- * Checks an XML file against its DTD
- *
- * @param string $dtd_file The DTD file name
- * @param string $xml_file The XML file
- * @return bool True if the XML conforms the definition
- **/
- function isValid($dtd_file, $xml_file)
- {
- $xml_tree =& new XML_Tree($xml_file);
- $nodes = $xml_tree->getTreeFromFile();
- if (PEAR::isError($nodes)) {
- $this->_errors($nodes->getMessage());
- return false;
- }
- $dtd_parser =& new XML_DTD_Parser;
- $this->dtd = @$dtd_parser->parse($dtd_file);
- $this->_runTree($nodes);
- return ($this->_errors) ? false : true;
- }
-
- /**
- * XML_DTD_XmlValidator::_runTree()
- *
- * Runs recursively over the XML_Tree tree of objects
- * validating each of its nodes
- *
- * @param object $node an XML_Tree_Node type object
- * @return null
- * @access private
- **/
- function _runTree(&$node)
- {
- //echo "Parsing node: $node->name\n";
- $children = array();
- $lines = array();
-
- // Get the list of children under the parent node
- foreach ($node->children as $child) {
- // a text node
- if (!strlen($child->name)) {
- $children[] = '#PCDATA';
- } else {
- $children[] = $child->name;
- }
- $lines[] = $child->lineno;
- }
-
- $this->_validateNode($node, $children, $lines);
- // Recursively run the tree
- foreach ($node->children as $child) {
- if (strlen($child->name)) {
- $this->_runTree($child);
- }
- }
- }
-
- /**
- * XML_DTD_XmlValidator::_validateNode()
- *
- * Validate a XML_Tree_Node: allowed childs, allowed content
- * and allowed attributes
- *
- * @param object $node an XML_Tree_Node type object
- * @param array $children the list of children
- * @param array $linenos linenumbers of the children
- * @return null
- * @access private
- **/
- function _validateNode($node, $children, $linenos)
- {
- $name = $node->name;
- $lineno = $node->lineno;
- if (!$this->dtd->elementIsDeclared($name)) {
- $this->_errors("No declaration for tag <$name> in DTD", $lineno);
- // We don't run over the childs of undeclared elements
- // contrary of what xmllint does
- return;
- }
-
- //
- // Children validation
- //
- $dtd_children = $this->dtd->getChildren($name);
- do {
- // There are children when no children allowed
- if (count($children) && !count($dtd_children)) {
- $this->_errors("No children allowed under <$name>", $lineno);
- break;
- }
- // Search for children names not allowed
- $was_error = false;
- $i = 0;
- foreach ($children as $child) {
- if (!in_array($child, $dtd_children)) {
- $this->_errors("<$child> not allowed under <$name>", $linenos[$i]);
- $was_error = true;
- }
- $i++;
- }
- // Validate the order of the children
- if (!$was_error && count($dtd_children)) {
- $children_list = implode(',', $children);
- $regex = $this->dtd->getPcreRegex($name);
- if (!preg_match('/^'.$regex.'$/', $children_list)) {
- $dtd_regex = $this->dtd->getDTDRegex($name);
- $this->_errors("In element <$name> the children list found:\n'$children_list', ".
- "does not conform the DTD definition: '$dtd_regex'", $lineno);
- }
- }
- } while (false);
-
- //
- // Content Validation
- //
- $node_content = $node->content;
- $dtd_content = $this->dtd->getContent($name);
- if (strlen($node_content)) {
- if ($dtd_content == null) {
- $this->_errors("No content allowed for tag <$name>", $lineno);
- } elseif ($dtd_content == 'EMPTY') {
- $this->_errors("No content allowed for tag <$name />, declared as 'EMPTY'", $lineno);
- }
- }
- // XXX Missing validate #PCDATA or ANY
-
- //
- // Attributes validation
- //
- $atts = $this->dtd->getAttributes($name);
- $node_atts = $node->attributes;
- foreach ($atts as $attname => $attvalue) {
- $opts = $attvalue['opts'];
- $default = $attvalue['defaults'];
- if ($default == '#REQUIRED' && !isset($node_atts[$attname])) {
- $this->_errors("Missing required '$attname' attribute in <$name>", $lineno);
- }
- if ($default == '#FIXED') {
- if (isset($node_atts[$attname]) && $node_atts[$attname] != $attvalue['fixed_value']) {
- $this->_errors("The value '{$node_atts[$attname]}' for attribute '$attname' ".
- "in <$name> can only be '{$attvalue['fixed_value']}'", $lineno);
- }
- }
- if (isset($node_atts[$attname])) {
- $node_val = $node_atts[$attname];
- // Enumerated type validation
- if (is_array($opts)) {
- if (!in_array($node_val, $opts)) {
- $this->_errors("'$node_val' value for attribute '$attname' under <$name> ".
- "can only be: '". implode(', ', $opts) . "'", $lineno);
- }
- }
- unset($node_atts[$attname]);
- }
- }
- // XXX Missing NMTOKEN, ID
-
- // If there are still attributes those are not declared in DTD
- if (count($node_atts) > 0) {
- $this->_errors("The attributes: '" . implode(', ', array_keys($node_atts)) .
- "' are not declared in DTD for tag <$name>", $lineno);
- }
- }
-
- /**
- * XML_DTD_XmlValidator::_errors()
- *
- * Stores errors
- *
- * @param string $str the error message to append
- * @param integer $lineno the line number where the tag is declared
- * @return null
- * @access private
- **/
- function _errors($str, $lineno = null)
- {
- if (is_null($lineno)) {
- $this->_errors .= "$str\n";
- } else {
- $this->_errors .= "line $lineno: $str\n";
- }
- }
-
- /**
- * XML_DTD_XmlValidator::getMessage()
- *
- * Gets all the errors the validator found in the
- * conformity of the xml document
- *
- * @return string the error message
- **/
- function getMessage()
- {
- return $this->_errors;
- }
-
- }
- ?>
-