+++ /dev/null
- <?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;
- }
-}
-
-?>