[evolvis-commits] r6807: http://www.bermi.org/projects/ xhtml-validator↵
mirabilos at evolvis.org
mirabilos at evolvis.org
Fri Jul 16 16:06:00 CEST 2010
Author: mirabilos
Date: 2010-07-16 16:06:00 +0200 (Fri, 16 Jul 2010)
New Revision: 6807
Added:
trunk/gforge_base/evolvisforge/gforge/common/include/XhtmlValidator.php
Log:
http://www.bermi.org/projects/xhtml-validator
Added: trunk/gforge_base/evolvisforge/gforge/common/include/XhtmlValidator.php
===================================================================
--- trunk/gforge_base/evolvisforge/gforge/common/include/XhtmlValidator.php (rev 0)
+++ trunk/gforge_base/evolvisforge/gforge/common/include/XhtmlValidator.php 2010-07-16 14:06:00 UTC (rev 6807)
@@ -0,0 +1,1509 @@
+ <?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+// +----------------------------------------------------------------------+
+// | Akelos Framework - http://www.akelos.org |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
+// | Released under the GNU Lesser General Public License, see LICENSE.txt|
+// +----------------------------------------------------------------------+
+
+/**
+* Validates Xhtml Documments
+*
+* Ussage:
+*
+* require_once('XhtmlValidator.php');
+* $XhtmlValidator = new XhtmlValidator();
+* if($XhtmlValidator->validate($xhtml) === false){
+* echo '<h1>Ooops! There are some errors on the XHTML page</h1>';
+* $XhtmlValidator->showErrors();
+* echo "<hr /><h2>Showing XHTML code</h2><hr /><div style='border:5px solid red;margin:5px;padding:15px;'>".$xhtml."</pre>";
+* }else{
+* echo 'Valid XHTML';
+* }
+*
+* @package AkelosFramework
+* @subpackage Development
+* @author Bermi Ferrer <bermi a.t akelos c.om>
+* @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
+* @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
+* @version $Revision 0.2 $
+*/
+class XhtmlValidator
+{
+ var $_attributes = array(
+ 'core' => array(
+ 'except' => array(
+ 'base',
+ 'head',
+ 'html',
+ 'meta',
+ 'param',
+ 'script',
+ 'style',
+ 'title'
+ ) ,
+ 'attributes' => array(
+ 'class',
+ 'id',
+ 'style',
+ 'title'
+ ) ,
+ ) ,
+ 'language' => array(
+ 'except' => array(
+ 'base',
+ 'br',
+ 'hr',
+ 'iframe',
+ 'param',
+ 'script'
+ ) ,
+ 'attributes' => array(
+ 'dir' => array(
+ 'ltr',
+ 'rtl'
+ ) ,
+ 'lang',
+ 'xml:lang'
+ ) ,
+ ) ,
+ 'keyboard' => array(
+ 'attributes' => array(
+ 'accesskey' => '/^(\w){1}$/',
+ 'tabindex' => '/^(\d)+$/'
+ ) ,
+ ) ,
+ );
+ var $_events = array(
+ 'window' => array(
+ 'only' => array(
+ 'body'
+ ) ,
+ 'attributes' => array(
+ 'onload',
+ 'onunload'
+ ) ,
+ ) ,
+ 'form' => array(
+ 'only' => array(
+ 'form',
+ 'input',
+ 'textarea',
+ 'select',
+ 'a',
+ 'label',
+ 'button'
+ ) ,
+ 'attributes' => array(
+ 'onchange',
+ 'onsubmit',
+ 'onreset',
+ 'onselect',
+ 'onblur',
+ 'onfocus'
+ ) ,
+ ) ,
+ 'keyboard' => array(
+ 'except' => array(
+ 'base',
+ 'bdo',
+ 'br',
+ 'frame',
+ 'frameset',
+ 'head',
+ 'html',
+ 'iframe',
+ 'meta',
+ 'param',
+ 'script',
+ 'style',
+ 'title'
+ ) ,
+ 'attributes' => array(
+ 'onkeydown',
+ 'onkeypress',
+ 'onkeyup'
+ ) ,
+ ) ,
+ 'mouse' => array(
+ 'except' => array(
+ 'base',
+ 'bdo',
+ 'br',
+ 'head',
+ 'html',
+ 'meta',
+ 'param',
+ 'script',
+ 'style',
+ 'title'
+ ) ,
+ 'attributes' => array(
+ 'onclick',
+ 'ondblclick',
+ 'onmousedown',
+ 'onmousemove',
+ 'onmouseover',
+ 'onmouseout',
+ 'onmouseup'
+ ) ,
+ ) ,
+ );
+ var $_tags = array(
+ 'a' => array(
+ 'attributes' => array(
+ 'charset',
+ 'coords',
+ 'href',
+ 'hreflang',
+ 'name',
+ 'rel' => '/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/',
+ 'rev' => '/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/',
+ 'shape' => '/^(rect|rectangle|circ|circle|poly|polygon)$/',
+ 'type',
+ ) ,
+ ) ,
+ 'abbr',
+ 'acronym',
+ 'address',
+ 'area' => array(
+ 'attributes' => array(
+ 'alt',
+ 'coords',
+ 'href',
+ 'nohref' => '/^(true|false)$/',
+ 'shape' => '/^(rect|rectangle|circ|circle|poly|polygon)$/'
+ ) ,
+ 'required' => array(
+ 'alt'
+ ) ,
+ ) ,
+ 'b',
+ 'base' => array(
+ 'attributes' => array(
+ 'href'
+ ) ,
+ 'required' => array(
+ 'href'
+ )
+ ) ,
+ 'bdo' => array(
+ 'attributes' => array(
+ 'dir' => '/^(ltr|rtl)$/'
+ ) ,
+ 'required' => array(
+ 'dir'
+ )
+ ) ,
+ 'big',
+ 'blockquote' => array(
+ 'attributes' => array(
+ 'cite'
+ )
+ ) ,
+ 'body',
+ 'br',
+ 'button' => array(
+ 'attributes' => array(
+ 'disabled' => '/^(disabled)$/',
+ 'type' => '/^(button|reset|submit)$/',
+ 'value'
+ ) ,
+ 'inside' => 'form'
+ ) ,
+ 'caption',
+ 'cite',
+ 'code',
+ 'col' => array(
+ 'attributes' => array(
+ 'align' => '/^(right|left|center|justify)$/',
+ 'char',
+ 'charoff',
+ 'span' => '/^(\d)+$/',
+ 'valign' => '/^(top|middle|bottom|baseline)$/',
+ 'width',
+ ) ,
+ 'inside' => 'colgroup'
+ ) ,
+ 'colgroup' => array(
+ 'attributes' => array(
+ 'align' => '/^(right|left|center|justify)$/',
+ 'char',
+ 'charoff',
+ 'span' => '/^(\d)+$/',
+ 'valign' => '/^(top|middle|bottom|baseline)$/',
+ 'width',
+ )
+ ) ,
+ 'dd',
+ 'del' => array(
+ 'attributes' => array(
+ 'cite',
+ 'datetime' => '/^([0-9]){8}/'
+ )
+ ) ,
+ 'div',
+ 'dfn',
+ 'dl',
+ 'dt',
+ 'em',
+ 'fieldset' => array(
+ 'inside' => 'form'
+ ) ,
+ 'form' => array(
+ 'attributes' => array(
+ 'action',
+ 'accept',
+ 'accept-charset',
+ 'enctype',
+ 'method' => '/^(get|post)$/'
+ ) ,
+ 'required' => array(
+ 'action'
+ )
+ ) ,
+ 'head' => array(
+ 'attributes' => array(
+ 'profile'
+ )
+ ) ,
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'hr',
+ 'html' => array(
+ 'attributes' => array(
+ 'xmlns'
+ )
+ ) ,
+ 'i',
+ 'img' => array(
+ 'attributes' => array(
+ 'alt',
+ 'src',
+ 'height',
+ 'ismap',
+ 'longdesc',
+ 'usemap',
+ 'width'
+ ) ,
+ 'required' => array(
+ 'alt',
+ 'src'
+ ) ,
+ ) ,
+ 'input' => array(
+ 'attributes' => array(
+ 'accept',
+ 'alt',
+ 'checked' => '/^(checked)$/',
+ 'disabled' => '/^(disabled)$/',
+ 'maxlength' => '/^(\d)+$/',
+ 'name',
+ 'readonly' => '/^(readonly)$/',
+ 'size' => '/^(\d)+$/',
+ 'src',
+ 'type' => '/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/',
+ 'value'
+ ) ,
+ 'inside' => 'form'
+ ) ,
+ 'ins' => array(
+ 'attributes' => array(
+ 'cite',
+ 'datetime' => '/^([0-9]){8}/'
+ )
+ ) ,
+ 'kbd',
+ 'label' => array(
+ 'attributes' => array(
+ 'for'
+ ) ,
+ 'inside' => 'form'
+ ) ,
+ 'legend',
+ 'li',
+ 'link' => array(
+ 'attributes' => array(
+ 'charset',
+ 'href',
+ 'hreflang',
+ 'media' => '/^(all|braille|print|projection|screen|speech|,|;| )+$/i',
+ 'rel' => '/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i',
+ 'rev' => '/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i',
+ 'type'
+ ) ,
+ 'inside' => 'head'
+ ) ,
+ 'map' => array(
+ 'attributes' => array(
+ 'id',
+ 'name'
+ ) ,
+ 'required' => array(
+ 'id'
+ )
+ ) ,
+ 'meta' => array(
+ 'attributes' => array(
+ 'content',
+ 'http-equiv' => '/^(content\-type|expires|refresh|set\-cookie)$/i',
+ 'name',
+ 'scheme'
+ ) ,
+ 'required' => array(
+ 'content'
+ )
+ ) ,
+ 'noscript',
+ 'object' => array(
+ 'attributes' => array(
+ 'archive',
+ 'classid',
+ 'codebase',
+ 'codetype',
+ 'data',
+ 'declare',
+ 'height',
+ 'name',
+ 'standby',
+ 'type',
+ 'usemap',
+ 'width'
+ )
+ ) ,
+ 'ol',
+ 'optgroup' => array(
+ 'attributes' => array(
+ 'label',
+ 'disabled' => '/^(disabled)$/'
+ ) ,
+ 'required' => array(
+ 'label'
+ )
+ ) ,
+ 'option' => array(
+ 'attributes' => array(
+ 'label',
+ 'disabled' => '/^(disabled)$/',
+ 'selected' => '/^(selected)$/',
+ 'value'
+ ) ,
+ 'required' => array(
+ 'label'
+ ) ,
+ 'inside' => 'select',
+ ) ,
+ 'p',
+ 'param' => array(
+ 'attributes' => array(
+ 'type',
+ 'valuetype' => '/^(data|ref|object)$/',
+ 'valuetype',
+ 'value'
+ ) ,
+ 'required' => array(
+ 'name'
+ ) ,
+ ) ,
+ 'pre',
+ 'q' => array(
+ 'attributes' => array(
+ 'cite'
+ )
+ ) ,
+ 'samp',
+ 'script' => array(
+ 'attributes' => array(
+ 'type' => '/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/',
+ 'charset',
+ 'defer' => '/^(defer)$/',
+ 'src'
+ ) ,
+ 'required' => array(
+ 'type'
+ )
+ ) ,
+ 'select' => array(
+ 'attributes' => array(
+ 'disabled' => '/^(disabled)$/',
+ 'multiple' => '/^(multiple)$/',
+ 'name',
+ 'size'
+ ) ,
+ 'inside' => 'form'
+ ) ,
+ 'small',
+ 'span',
+ 'strong',
+ 'style' => array(
+ 'attributes' => array(
+ 'type',
+ 'media' => '/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/'
+ ) ,
+ 'required' => array(
+ 'type'
+ )
+ ) ,
+ 'sub',
+ 'sup',
+ 'table' => array(
+ 'attributes' => array(
+ 'border',
+ 'cellpadding',
+ 'cellspacing',
+ 'frame' => '/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/',
+ 'rules' => '/^(none|groups|rows|cols|all)$/',
+ 'summary',
+ 'width'
+ )
+ ) ,
+ 'tbody' => array(
+ 'attributes' => array(
+ 'align' => '/^(right|left|center|justify)$/',
+ 'char',
+ 'charoff',
+ 'valign' => '/^(top|middle|bottom|baseline)$/'
+ )
+ ) ,
+ 'td' => array(
+ 'attributes' => array(
+ 'abbr',
+ 'align' => '/^(left|right|center|justify|char)$/',
+ 'axis',
+ 'char',
+ 'charoff',
+ 'colspan' => '/^(\d)+$/',
+ 'headers',
+ 'rowspan' => '/^(\d)+$/',
+ 'scope' => '/^(col|colgroup|row|rowgroup)$/',
+ 'valign' => '/^(top|middle|bottom|baseline)$/'
+ )
+ ) ,
+ 'textarea' => array(
+ 'attributes' => array(
+ 'cols',
+ 'rows',
+ 'disabled',
+ 'name',
+ 'readonly'
+ ) ,
+ 'required' => array(
+ 'cols',
+ 'rows'
+ ) ,
+ 'inside' => 'form'
+ ) ,
+ 'tfoot' => array(
+ 'attributes' => array(
+ 'align' => '/^(right|left|center|justify)$/',
+ 'char',
+ 'charoff',
+ 'valign' => '/^(top|middle|bottom)$/',
+ 'baseline'
+ )
+ ) ,
+ 'th' => array(
+ 'attributes' => array(
+ 'abbr',
+ 'align' => '/^(left|right|center|justify|char)$/',
+ 'axis',
+ 'char',
+ 'charoff',
+ 'colspan' => '/^(\d)+$/',
+ 'headers',
+ 'rowspan' => '/^(\d)+$/',
+ 'scope' => '/^(col|colgroup|row|rowgroup)$/',
+ 'valign' => '/^(top|middle|bottom|baseline)$/'
+ )
+ ) ,
+ 'thead' => array(
+ 'attributes' => array(
+ 'align' => '/^(right|left|center|justify)$/',
+ 'char',
+ 'charoff',
+ 'valign' => '/^(top|middle|bottom|baseline)$/'
+ )
+ ) ,
+ 'title',
+ 'tr' => array(
+ 'attributes' => array(
+ 'align' => '/^(right|left|center|justify|char)$/',
+ 'char',
+ 'charoff',
+ 'valign' => '/^(top|middle|bottom|baseline)$/'
+ )
+ ) ,
+ 'tt',
+ 'ul',
+ 'var',
+ );
+
+ var $_entities = array(
+ ' ' => ' ',
+ '¡' => '¡',
+ '¢' => '¢',
+ '£' => '£',
+ '¤' => '¤',
+ '¥' => '¥',
+ '¦' => '¦',
+ '§' => '§',
+ '¨' => '¨',
+ '©' => '©',
+ 'ª' => 'ª',
+ '«' => '«',
+ '¬' => '¬',
+ '­' => '­',
+ '®' => '®',
+ '¯' => '¯',
+ '°' => '°',
+ '±' => '±',
+ '²' => '²',
+ '³' => '³',
+ '´' => '´',
+ 'µ' => 'µ',
+ '¶' => '¶',
+ '·' => '·',
+ '¸' => '¸',
+ '¹' => '¹',
+ 'º' => 'º',
+ '»' => '»',
+ '¼' => '¼',
+ '½' => '½',
+ '¾' => '¾',
+ '¿' => '¿',
+ 'À' => 'À',
+ 'Á' => 'Á',
+ 'Â' => 'Â',
+ 'Ã' => 'Ã',
+ 'Ä' => 'Ä',
+ 'Å' => 'Å',
+ 'Æ' => 'Æ',
+ 'Ç' => 'Ç',
+ 'È' => 'È',
+ 'É' => 'É',
+ 'Ê' => 'Ê',
+ 'Ë' => 'Ë',
+ 'Ì' => 'Ì',
+ 'Í' => 'Í',
+ 'Î' => 'Î',
+ 'Ï' => 'Ï',
+ 'Ð' => 'Ð',
+ 'Ñ' => 'Ñ',
+ 'Ò' => 'Ò',
+ 'Ó' => 'Ó',
+ 'Ô' => 'Ô',
+ 'Õ' => 'Õ',
+ 'Ö' => 'Ö',
+ '×' => '×',
+ 'Ø' => 'Ø',
+ 'Ù' => 'Ù',
+ 'Ú' => 'Ú',
+ 'Û' => 'Û',
+ 'Ü' => 'Ü',
+ 'Ý' => 'Ý',
+ 'Þ' => 'Þ',
+ 'ß' => 'ß',
+ 'à' => 'à',
+ 'á' => 'á',
+ 'â' => 'â',
+ 'ã' => 'ã',
+ 'ä' => 'ä',
+ 'å' => 'å',
+ 'æ' => 'æ',
+ 'ç' => 'ç',
+ 'è' => 'è',
+ 'é' => 'é',
+ 'ê' => 'ê',
+ 'ë' => 'ë',
+ 'ì' => 'ì',
+ 'í' => 'í',
+ 'î' => 'î',
+ 'ï' => 'ï',
+ 'ð' => 'ð',
+ 'ñ' => 'ñ',
+ 'ò' => 'ò',
+ 'ó' => 'ó',
+ 'ô' => 'ô',
+ 'õ' => 'õ',
+ 'ö' => 'ö',
+ '÷' => '÷',
+ 'ø' => 'ø',
+ 'ù' => 'ù',
+ 'ú' => 'ú',
+ 'û' => 'û',
+ 'ü' => 'ü',
+ 'ý' => 'ý',
+ 'þ' => 'þ',
+ 'ÿ' => 'ÿ',
+ 'ƒ' => 'ƒ',
+ 'Α' => 'Α',
+ 'Β' => 'Β',
+ 'Γ' => 'Γ',
+ 'Δ' => 'Δ',
+ 'Ε' => 'Ε',
+ 'Ζ' => 'Ζ',
+ 'Η' => 'Η',
+ 'Θ' => 'Θ',
+ 'Ι' => 'Ι',
+ 'Κ' => 'Κ',
+ 'Λ' => 'Λ',
+ 'Μ' => 'Μ',
+ 'Ν' => 'Ν',
+ 'Ξ' => 'Ξ',
+ 'Ο' => 'Ο',
+ 'Π' => 'Π',
+ 'Ρ' => 'Ρ',
+ 'Σ' => 'Σ',
+ 'Τ' => 'Τ',
+ 'Υ' => 'Υ',
+ 'Φ' => 'Φ',
+ 'Χ' => 'Χ',
+ 'Ψ' => 'Ψ',
+ 'Ω' => 'Ω',
+ 'α' => 'α',
+ 'β' => 'β',
+ 'γ' => 'γ',
+ 'δ' => 'δ',
+ 'ε' => 'ε',
+ 'ζ' => 'ζ',
+ 'η' => 'η',
+ 'θ' => 'θ',
+ 'ι' => 'ι',
+ 'κ' => 'κ',
+ 'λ' => 'λ',
+ 'μ' => 'μ',
+ 'ν' => 'ν',
+ 'ξ' => 'ξ',
+ 'ο' => 'ο',
+ 'π' => 'π',
+ 'ρ' => 'ρ',
+ 'ς' => 'ς',
+ 'σ' => 'σ',
+ 'τ' => 'τ',
+ 'υ' => 'υ',
+ 'φ' => 'φ',
+ 'χ' => 'χ',
+ 'ψ' => 'ψ',
+ 'ω' => 'ω',
+ 'ϑ' => 'ϑ',
+ 'ϒ' => 'ϒ',
+ 'ϖ' => 'ϖ',
+ '•' => '•',
+ '…' => '…',
+ '′' => '′',
+ '″' => '″',
+ '‾' => '‾',
+ '⁄' => '⁄',
+ '℘' => '℘',
+ 'ℑ' => 'ℑ',
+ 'ℜ' => 'ℜ',
+ '™' => '™',
+ 'ℵ' => 'ℵ',
+ '←' => '←',
+ '↑' => '↑',
+ '→' => '→',
+ '↓' => '↓',
+ '↔' => '↔',
+ '↵' => '↵',
+ '⇐' => '⇐',
+ '⇑' => '⇑',
+ '⇒' => '⇒',
+ '⇓' => '⇓',
+ '⇔' => '⇔',
+ '∀' => '∀',
+ '∂' => '∂',
+ '∃' => '∃',
+ '∅' => '∅',
+ '∇' => '∇',
+ '∈' => '∈',
+ '∉' => '∉',
+ '∋' => '∋',
+ '∏' => '∏',
+ '∑' => '∑',
+ '−' => '−',
+ '∗' => '∗',
+ '√' => '√',
+ '∝' => '∝',
+ '∞' => '∞',
+ '∠' => '∠',
+ '∧' => '∧',
+ '∨' => '∨',
+ '∩' => '∩',
+ '∪' => '∪',
+ '∫' => '∫',
+ '∴' => '∴',
+ '∼' => '∼',
+ '≅' => '≅',
+ '≈' => '≈',
+ '≠' => '≠',
+ '≡' => '≡',
+ '≤' => '≤',
+ '≥' => '≥',
+ '⊂' => '⊂',
+ '⊃' => '⊃',
+ '⊄' => '⊄',
+ '⊆' => '⊆',
+ '⊇' => '⊇',
+ '⊕' => '⊕',
+ '⊗' => '⊗',
+ '⊥' => '⊥',
+ '⋅' => '⋅',
+ '⌈' => '⌈',
+ '⌉' => '⌉',
+ '⌊' => '⌊',
+ '⌋' => '⌋',
+ '⟨' => '〈',
+ '⟩' => '〉',
+ '◊' => '◊',
+ '♠' => '♠',
+ '♣' => '♣',
+ '♥' => '♥',
+ '♦' => '♦',
+ '"' => '"',
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+ 'Œ' => 'Œ',
+ 'œ' => 'œ',
+ 'Š' => 'Š',
+ 'š' => 'š',
+ 'Ÿ' => 'Ÿ',
+ 'ˆ' => 'ˆ',
+ '˜' => '˜',
+ ' ' => ' ',
+ ' ' => ' ',
+ ' ' => ' ',
+ '‌' => '‌',
+ '‍' => '‍',
+ '‎' => '‎',
+ '‏' => '‏',
+ '–' => '–',
+ '—' => '—',
+ '‘' => '‘',
+ '’' => '’',
+ '‚' => '‚',
+ '“' => '“',
+ '”' => '”',
+ '„' => '„',
+ '†' => '†',
+ '‡' => '‡',
+ '‰' => '‰',
+ '‹' => '‹',
+ '›' => '›',
+ '€' => '€'
+ );
+
+ var $_parser;
+ var $_stack = array();
+ var $_errors = array();
+
+ function XhtmlValidator()
+ {
+ $this->_parser = xml_parser_create('');
+ xml_set_object($this->_parser, &$this);
+ xml_set_element_handler($this->_parser, 'tagOpen', 'tagClose');
+ xml_set_character_data_handler($this->_parser, 'cdata');
+ xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
+ xml_parser_set_option($this->_parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
+ }
+
+ function validateTagAttributes($tag, $attributes = array())
+ {
+ $possible_attributes = $this->getPossibleTagAttributes($tag);
+ foreach($attributes as $attribute => $value) {
+ if (!in_array($attribute, $possible_attributes)) {
+ $this->addError($this->translate("Attribute %attribute can't be used inside <%tag> tags", array(
+ '%attribute' => $attribute,
+ '%tag' => $tag
+ )) , array(
+ array(
+ $attribute,
+ $tag
+ )
+ ));
+ } elseif ($this->doesAttributeNeedsValidation($tag, $attribute)) {
+ $this->validateAttribute($tag, $attribute, $value);
+ }
+ }
+ }
+
+ function doesAttributeNeedsValidation($tag, $attribute)
+ {
+ return isset($this->_tags[$tag]['attributes'][$attribute]) || isset($this->_tags[$tag]['required']) && in_array($attribute, $this->_tags[$tag]['required']);
+ }
+
+ function validateAttribute($tag, $attribute, $value = null)
+ {
+ if (isset($this->_tags[$tag]['attributes'][$attribute]) && (strlen($value) > 0)) {
+ if (!preg_match($this->_tags[$tag]['attributes'][$attribute], $value)) {
+ $this->addError($this->translate("Invalid value on <%tag %attribute=\"%value\"... Valid values must match the pattern \"%pattern\"", array(
+ '%tag' => $tag,
+ '%attribute' => $attribute,
+ '%value' => $value,
+ '%pattern' => htmlentities($this->_tags[$tag]['attributes'][$attribute])
+ )) , array(
+ array(
+ $attribute,
+ $value
+ )
+ ));
+ }
+ }
+ if (isset($this->_tags[$tag]['required']) && in_array($attribute, $this->_tags[$tag]['required']) && (strlen($value) == 0)) {
+ $this->addError($this->translate("Missing required attribute %attribute on <%tag>", array(
+ '%tag' => $tag,
+ '%attribute' => $attribute
+ )) , array(
+ array(
+ $tag,
+ $attribute
+ )
+ ));
+ }
+ }
+
+ function addError($error, $highlight_text = array())
+ {
+ $this->_errors[] = $this->highlightError($error, $highlight_text) .' on line '.$this->getCurrentLine();
+ }
+
+ function highlightError($error, $highlight_text = array())
+ {
+ if (empty($highlight_text)) {
+ return $error;
+ }
+ $line = $this->getCurrentLine();
+ $highlighted_error = '';
+ foreach($highlight_text as $phrases) {
+ $color = $this->getRandomHex();
+ if (is_array($phrases)) {
+ $highlighted_error_line = $error;
+ foreach($phrases as $phrase) {
+ $this->_linesToHighlight[$line][$error] = array(
+ 'color' => $color,
+ 'phrase' => htmlentities($phrase)
+ );
+ $highlighted_error_line = $this->highlight($highlighted_error_line, $phrase.' ', ' <strong style="border:2px solid #'.$color.'; background: #ffc;">\1</strong> ');
+ }
+ $highlighted_error.= $highlighted_error_line;
+ } else {
+ $highlighted_error = $this->highlight($error, $phrases.' ', ' <strong style="border:2px solid #'.$color.'; background: #ffc">\1</strong> ');
+ $this->_linesToHighlight[$line][$error] = array(
+ 'color' => $color,
+ 'phrase' => htmlentities($phrases)
+ );
+ }
+ }
+ return $highlighted_error;
+ }
+
+ function highlightErrors($xhtml)
+ {
+ $highlighted_xhtml = array();
+ if (!empty($this->_linesToHighlight)) {
+ $xhtml_arr = preg_split('/\n|\r/', $xhtml);
+ foreach($xhtml_arr as $k => $xhtml_line) {
+ $pos = $k+$this->_startLine;
+ $highlighted_xhtml[$k] = $pos." ";
+ $xhtml_line = htmlentities($xhtml_line);
+ if (isset($this->_linesToHighlight[$pos])) {
+ foreach($this->_linesToHighlight[$pos] as $highlight_details) {
+ $highlighted_xhtml[$k].= $this->highlight($xhtml_line, $highlight_details['phrase'], '<strong style="border:2px solid #'.$highlight_details['color'].';padding:1px; margin:1px; background: #ffc;">\1</strong>');
+ }
+ } else {
+ $highlighted_xhtml[$k].= $xhtml_line;
+ }
+ $highlighted_xhtml[$k].= "<br />\n";
+ }
+ }
+ return empty($highlighted_xhtml) ? $xhtml : join($highlighted_xhtml);
+ }
+
+ function getCurrentLine()
+ {
+ return xml_get_current_line_number($this->_parser) +$this->_startLine;
+ }
+
+ function hasErrors(&$xhtml)
+ {
+ $this->validateUniquenessOfIds();
+ if (count($this->getErrors()) > 0) {
+ $xhtml = $this->highlightErrors($xhtml);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function getErrors()
+ {
+ return array_unique($this->_errors);
+ }
+
+ function showErrors()
+ {
+ echo '<ul><li>'.join("</li>\n<li>", $this->getErrors()) .'</li></ul>';
+ }
+
+ function getPossibleTagAttributes($tag)
+ {
+ static $cache;
+ if (!isset($cache[$tag])) {
+ $cache[$tag] = array_unique(array_merge($this->getUniqueAttributesAndEventsForTag($tag) , $this->getDefaultAttributesAndEventsForTag($tag)));
+ sort($cache[$tag]);
+ }
+ return $cache[$tag];
+ }
+
+ function validateRequiredAttributes($tag, $attributes)
+ {
+ $compulsory = $this->getCompulsoryTagAttributes($tag);
+ $errors = array_diff($compulsory, array_keys($attributes));
+ if (!empty($errors)) {
+ $this->addError($this->translate('Tag %tag requires %attributes to be defined', array(
+ '%tag' => $tag,
+ '%attributes' => (count($errors) == 1 ? 'attribute "' : 'attributes "') .join('", "', $errors) .'"'
+ )) , array(
+ $tag
+ ));
+ }
+ }
+
+ function protectFromDuplicatedIds($tag, $attributes)
+ {
+ if (isset($attributes['id'])) {
+ if (isset($this->_idTagXref[$attributes['id']])) {
+ $this->addError($this->translate('Repeating id %id', array(
+ '%id' => $attributes['id']
+ )) , array(
+ $attributes['id']
+ ));
+ }
+ $this->_tagIdCounter[$attributes['id']] = isset($this->_tagIdCounter[$attributes['id']]) ? $this->_tagIdCounter[$attributes['id']]+1 : 1;
+ $this->_idTagXref[$attributes['id']][] = $tag;
+ }
+ }
+
+ function validateUniquenessOfIds()
+ {
+ if (isset($this->_tagIdCounter) && max(array_values($this->_tagIdCounter)) > 1) {
+ foreach($this->_tagIdCounter as $id => $count) {
+ if ($count > 1) {
+ $this->addError($this->translate('You have repeated the id %id %count times on your xhtml code. Duplicated Ids found on %tags', array(
+ '%id' => "\"$id\"",
+ '%count' => $count,
+ '%tags' => (count($this->_idTagXref[$id]) == 1 ? 'tag "' : 'tag "') .join('", "', $this->_idTagXref[$id]) .'"'
+ )));
+ }
+ }
+ }
+ }
+
+ function getCompulsoryTagAttributes($tag)
+ {
+ return !empty($this->_tags[$tag]['required']) ? (array)$this->_tags[$tag]['required'] : array();
+ }
+
+ function getUniqueAttributesAndEventsForTag($tag)
+ {
+ $result = array();
+ if (isset($this->_tags[$tag]['attributes']) && is_array($this->_tags[$tag]['attributes'])) {
+ foreach($this->_tags[$tag]['attributes'] as $k => $candidate) {
+ $result[] = is_numeric($k) ? $candidate : $k;
+ }
+ }
+ return $result;
+ }
+
+ function getDefaultAttributesAndEventsForTag($tag)
+ {
+ $default = array();
+ if (isset($this->_tags[$tag]) || in_array($tag, $this->_tags)) {
+ foreach($this->getDefaultAttributesAndEventsForTags() as $defaults) {
+ if ((isset($defaults['except']) && in_array($tag, $defaults['except'])) || (isset($defaults['only']) && !in_array($tag, $defaults['only']))) {
+ continue;
+ }
+ foreach(isset($defaults['attributes']) ? $defaults['attributes'] : $defaults['events'] as $k => $candidate) {
+ $default[] = is_array($candidate) ? $k : $candidate;;
+ }
+ }
+ }
+ return $default;
+ }
+
+ function getDefaultAttributesAndEventsForTags()
+ {
+ if (!isset($this->default_values_for_tags)) {
+ $this->default_values_for_tags = array_merge($this->_attributes, $this->_events);
+ }
+ return $this->default_values_for_tags;
+ }
+
+ function getAvailableTags()
+ {
+ $tags = array();
+ foreach(array_keys($this->_tags) as $k) {
+ $tags[] = is_numeric($k) ? $this->_tags[$k] : $k;
+ }
+ sort($tags);
+ return $tags;
+ }
+
+ function validate(&$xhtml)
+ {
+ $this->_startLine = 1;
+ $xhtml_copy = $this->removeDoctypeHeader($xhtml);
+ $xhtml_copy = $this->removeCdata($xhtml_copy);
+ $xhtml_copy = $this->convertLiteralEntitiesToNumericalEntities($xhtml_copy);
+ $xhtml_copy = '<all>'.$xhtml_copy.'</all>';
+ if (!xml_parse($this->_parser, $xhtml_copy)) {
+ $this->addError($this->translate('XHTML is not well-formed.') .' '.xml_error_string(xml_get_error_code($this->_parser)));
+ }
+ return !$this->hasErrors($xhtml);
+ }
+
+ function removeDoctypeHeader($xhtml)
+ {
+ if (substr($xhtml, 0, 9) == '<!DOCTYPE') {
+ $replacement = substr($xhtml, 0, strpos($xhtml, '>'));
+ $this->_startLine = count(substr_count($replacement, "\n"));
+ }
+ return (isset($replacement)) ? substr($xhtml, strlen($replacement)) : $xhtml;
+ }
+
+ function removeCdata($xhtml)
+ {
+ $xhtml = preg_replace('(<\!\[CDATA\[.*\]\]>)', '', $xhtml);
+ return str_replace(array('<![CDATA[',']]>') , '', $xhtml);
+ }
+
+
+ function convertLiteralEntitiesToNumericalEntities($xhtml)
+ {
+ return str_replace(array_keys($this->_entities), array_values($this->_entities), $xhtml);
+ }
+
+ function tagOpen($parser, $tag, $attributes)
+ {
+ $this->_start_byte = xml_get_current_byte_index($parser);
+ if ($tag == 'all') {
+ $this->_stack[] = 'all';
+ return;
+ }
+ $previous = $this->_stack[count($this->_stack) -1];
+ $this->validateRequiredAttributes($tag, $attributes);
+ $this->protectFromDuplicatedIds($tag, $attributes);
+ if (!in_array($previous, $this->getAvailableTags())) {
+ $this->validateTagAttributes($tag, $attributes);
+ $this->_stack[] = $tag;
+ return;
+ }
+ if (!in_array($tag, $this->getAvailableTags())) {
+ $this->addError($this->translate("Illegal tag: <code>%tag</code>", array(
+ '%tag' => $tag
+ )) , array(
+ $tag
+ ));
+ $this->_stack[] = $tag;
+ return;
+ }
+ // Is tag allowed in the current context?
+ if (!$this->isTagAlowedOnCurrentContext($tag, $previous)) {
+ if ($previous != 'all') {
+ //$this->addError($this->translate("Tag <code>%tag</code> must occur inside another tag",array('%tag'=>$tag)));
+ //} else {
+ $this->addError($this->translate("Tag %tag is not allowed within tag %previous", array(
+ '%tag' => $tag,
+ '%previous' => $previous
+ )) , array(
+ $tag
+ ));
+ }
+ }
+ $this->validateTagAttributes($tag, $attributes);
+ $this->_stack[] = $tag;
+ }
+
+ function isTagAlowedOnCurrentContext($tag, $previous)
+ {
+ $rules = $this->getRules();
+ $result = isset($rules[$previous]) ? in_array($tag, $rules[$previous]) : true;
+ $inverse_rules = $this->getInverseRulesForTag($tag);
+ $result = isset($inverse_rules[$tag]) ? in_array($previous, $inverse_rules[$tag]) : $result;
+ return $result;
+ }
+
+ function getRules()
+ {
+ static $rules;
+ if (!isset($rules)) {
+ //$inline = array ('abbr','cite','code','dfn','em','kbd','object','quote','q','samp','span','strong','var','a','sup','sub','acronym','img','#PCDATA');
+ $inline = array(
+ '#pcdata',
+ 'a',
+ 'abbr',
+ 'acronym',
+ 'applet',
+ 'b',
+ 'basefont',
+ 'bdo',
+ 'big',
+ 'br',
+ 'button',
+ 'cite',
+ 'code',
+ 'dfn',
+ 'em',
+ 'font',
+ 'i',
+ 'img',
+ 'input',
+ 'kbd',
+ 'label',
+ 'map',
+ 'object',
+ 'q',
+ 's',
+ 'samp',
+ 'script',
+ 'select',
+ 'small',
+ 'span',
+ 'strike',
+ 'strong',
+ 'sub',
+ 'sup',
+ 'textarea',
+ 'tt',
+ 'u',
+ 'var'
+ );
+ //$block = array('dl','nl','ol','ul','address','blockcode','blockquote','div','p','pre','handler','section','separator','table');
+ $block = array(
+ 'address',
+ 'blockcode',
+ 'blockquote',
+ 'center',
+ 'dir',
+ 'div',
+ 'dl',
+ 'fieldset',
+ 'form',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'handler',
+ 'hr',
+ 'iframe',
+ 'isindex',
+ 'menu',
+ 'nl',
+ 'noframes',
+ 'noscript',
+ 'ol',
+ 'p',
+ 'pre',
+ 'section',
+ 'separator',
+ 'table',
+ 'ul'
+ );
+ $flow = array_merge($block, $inline);
+ $rules = array(
+ 'html' => array(
+ 'head',
+ 'body'
+ ) ,
+ 'head' => array(
+ 'script',
+ 'style',
+ 'meta',
+ 'base',
+ 'link',
+ 'title'
+ ) ,
+ 'body' => array_merge(array(
+ 'ins',
+ 'del'
+ ) , $flow) ,
+ 'ul' => array(
+ 'li'
+ ) ,
+ 'ol' => array(
+ 'li'
+ ) ,
+ //'p' => array_merge($inline, array('blockcode', 'blockquote', 'pre', 'table', 'dl', 'nl', 'ol', 'ul')),
+ 'blockquote' => $block,
+ 'dl' => array(
+ 'dt',
+ 'dd'
+ ) ,
+ 'pre' => array_diff($inline, array(
+ 'img',
+ 'object',
+ 'big',
+ 'small',
+ 'sub',
+ 'sup'
+ )) ,
+ 'form' => array_diff($flow, array(
+ 'form'
+ )) ,
+ // Tables
+ 'table' => array(
+ 'caption',
+ 'colgroup',
+ 'col',
+ 'thead',
+ 'tbody',
+ 'tr'
+ ) ,
+ 'colgroup' => array(
+ 'col'
+ ) ,
+ 'thead' => array(
+ 'tr'
+ ) ,
+ 'tbody' => array(
+ 'tr'
+ ) ,
+ 'tr' => array(
+ 'th',
+ 'td'
+ ) ,
+ 'address' => array_merge($inline, array(
+ 'p'
+ )) ,
+ 'fieldset' => array_merge($flow, array(
+ 'legend'
+ )) ,
+ 'a' => array_diff($inline, array(
+ 'a'
+ )) ,
+ 'object' => array_merge($flow, array(
+ 'param'
+ )) ,
+ 'script' => array(
+ 'cdata'
+ ) ,
+ 'map' => array_merge($block, array(
+ 'area'
+ )) ,
+ 'select' => array(
+ 'optgroup',
+ 'option'
+ ) ,
+ 'optgroup' => array(
+ 'option'
+ ) ,
+ 'label' => array_diff($inline, array(
+ 'label'
+ )) ,
+ 'button' => array_diff($flow, array(
+ 'a',
+ 'input',
+ 'select',
+ 'textarea',
+ 'label',
+ 'button',
+ 'form',
+ 'fieldset',
+ 'iframe'
+ )) ,
+ );
+ $flow_tags = array(
+ 'div',
+ 'center',
+ 'blockquote',
+ 'noscript',
+ 'dd',
+ 'li',
+ 'th',
+ 'td'
+ );
+ foreach($flow_tags as $flow_tag) {
+ $rules[$flow_tag] = $flow;
+ }
+ $inline_tags = array(
+ 'p',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'dt',
+ 'caption',
+ 'legend',
+ 'tt',
+ 'abbr',
+ 'acronym',
+ 'b',
+ 'bdo',
+ 'big',
+ 'cite',
+ 'code',
+ 'dfn',
+ 'em',
+ 'font',
+ 'i',
+ 'kbd',
+ 'q',
+ 's',
+ 'samp',
+ 'small',
+ 'span',
+ 'strike',
+ 'strong',
+ 'sub',
+ 'sup',
+ 'u',
+ 'var'
+ );
+ foreach($inline_tags as $inline_tag) {
+ $rules[$inline_tag] = $inline;
+ }
+ }
+ return $rules;
+ }
+
+ function getInverseRulesForTag($tag)
+ {
+ static $inverse_rules;
+ if (!isset($inverse_rules[$tag])) {
+ $inverse_rules[$tag] = array();
+ $rules = $this->getRules();
+ foreach($rules as $container_tag => $rule) {
+ if (in_array($rule, $rule)) {
+ $inverse_rules[$tag][] = $container_tag;
+ }
+ }
+ }
+ return $inverse_rules[$tag];
+ }
+
+ function cdata($parser, $cdata)
+ {
+ // Simply check that the 'previous' tag allows CDATA
+ $previous = $this->_stack[count($this->_stack) -1];
+ if ($cdata != '' && in_array($previous, array(
+ 'base',
+ 'area',
+ 'basefont',
+ 'br',
+ 'col',
+ 'hr',
+ 'img',
+ 'input',
+ 'link',
+ 'meta',
+ 'param'
+ ))) {
+ $this->addError($this->translate("%previous tag is not a content tag. close it like this '<%previous />'", array(
+ '%previous' => $previous
+ )) , array(
+ $previous
+ ));
+ }
+ // If previous tag is illegal, no point in running test
+ if (!in_array($previous, $this->getAvailableTags())) {
+ return;
+ }
+ if (trim($cdata) != '') {
+ if (!$this->isTagAlowedOnCurrentContext('#pcdata', $previous)) {
+ $this->addError($this->translate("Tag <code>%previous</code> may not contain raw character data", array(
+ '%previous' => $previous
+ )) , array(
+ $previous
+ ));
+ }
+ }
+ }
+
+ function tagClose($parser, $tag)
+ {
+ if (in_array($tag, array(
+ 'base',
+ 'area',
+ 'basefont',
+ 'br',
+ 'col',
+ 'hr',
+ 'img',
+ 'input',
+ 'link',
+ 'meta',
+ 'param'
+ ))) {
+ $this->_end_byte = xml_get_current_byte_index($parser);
+ if ($this->_end_byte-$this->_start_byte == 4) {
+ $this->addError($this->translate("%tag tag is not a content tag. close it like this '<%tag />'", array(
+ '%tag' => $tag
+ )) , array(
+ $tag
+ ));
+ }
+ }
+ array_pop($this->_stack);
+ }
+
+
+
+ /**
+ * This functions belong to other classes in the Akelos Framework, but have been included here to avoid dependencies
+ */
+ function highlight($text, $phrase, $highlighter = '<strong class="highlight">\1</strong>')
+ {
+ $phrase = is_array($phrase) ? join('|',array_map('preg_quote',$phrase)) : preg_quote($phrase);
+ return !empty($phrase) ? preg_replace('/('.$phrase.')/i', $highlighter,$text) : $text;
+ }
+
+ function rgbToHex()
+ {
+ $rgb = func_get_args();
+ $hex = '';
+ foreach (count($rgb) == 1 ? $rgb[0] : $rgb as $color){
+ $color = dechex($color);
+ $hex .= strlen($color) == 2 ? $color : $color.$color;
+ }
+ return $hex;
+ }
+
+ function hexToRgb($hex_color)
+ {
+ $hex_color = strtolower(trim($hex_color,'#;&Hh'));
+ return array_map('hexdec',explode('.',wordwrap($hex_color, ceil(strlen($hex_color)/3),'.',1)));
+ }
+
+ function getOpositeHex($hex_color)
+ {
+ $rgb = $this->hexToRgb($hex_color);
+ foreach ($rgb as $k=>$color){
+ $rgb[$k] = (255-$color < 0 ? 0 : 255-$color);
+ }
+ return $this->rgbToHex($rgb);
+ }
+
+ function getRandomHex()
+ {
+ return $this->rgbToHex(rand(0,255),rand(0,255),rand(0,255));
+ }
+
+ function translate($string, $args = null)
+ {
+ if(isset($args) && is_array($args)){
+ $string = @str_replace(array_keys($args), array_values($args),$string);
+ }
+ return $string;
+ }
+}
+
+?>
More information about the evolvis-commits
mailing list