<?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(
    
'&nbsp;' => '&#160;',
    
'&iexcl;' => '&#161;',
    
'&cent;' => '&#162;',
    
'&pound;' => '&#163;',
    
'&curren;' => '&#164;',
    
'&yen;' => '&#165;',
    
'&brvbar;' => '&#166;',
    
'&sect;' => '&#167;',
    
'&uml;' => '&#168;',
    
'&copy;' => '&#169;',
    
'&ordf;' => '&#170;',
    
'&laquo;' => '&#171;',
    
'&not;' => '&#172;',
    
'&shy;' => '&#173;',
    
'&reg;' => '&#174;',
    
'&macr;' => '&#175;',
    
'&deg;' => '&#176;',
    
'&plusmn;' => '&#177;',
    
'&sup2;' => '&#178;',
    
'&sup3;' => '&#179;',
    
'&acute;' => '&#180;',
    
'&micro;' => '&#181;',
    
'&para;' => '&#182;',
    
'&middot;' => '&#183;',
    
'&cedil;' => '&#184;',
    
'&sup1;' => '&#185;',
    
'&ordm;' => '&#186;',
    
'&raquo;' => '&#187;',
    
'&frac14;' => '&#188;',
    
'&frac12;' => '&#189;',
    
'&frac34;' => '&#190;',
    
'&iquest;' => '&#191;',
    
'&Agrave;' => '&#192;',
    
'&Aacute;' => '&#193;',
    
'&Acirc;' => '&#194;',
    
'&Atilde;' => '&#195;',
    
'&Auml;' => '&#196;',
    
'&Aring;' => '&#197;',
    
'&AElig;' => '&#198;',
    
'&Ccedil;' => '&#199;',
    
'&Egrave;' => '&#200;',
    
'&Eacute;' => '&#201;',
    
'&Ecirc;' => '&#202;',
    
'&Euml;' => '&#203;',
    
'&Igrave;' => '&#204;',
    
'&Iacute;' => '&#205;',
    
'&Icirc;' => '&#206;',
    
'&Iuml;' => '&#207;',
    
'&ETH;' => '&#208;',
    
'&Ntilde;' => '&#209;',
    
'&Ograve;' => '&#210;',
    
'&Oacute;' => '&#211;',
    
'&Ocirc;' => '&#212;',
    
'&Otilde;' => '&#213;',
    
'&Ouml;' => '&#214;',
    
'&times;' => '&#215;',
    
'&Oslash;' => '&#216;',
    
'&Ugrave;' => '&#217;',
    
'&Uacute;' => '&#218;',
    
'&Ucirc;' => '&#219;',
    
'&Uuml;' => '&#220;',
    
'&Yacute;' => '&#221;',
    
'&THORN;' => '&#222;',
    
'&szlig;' => '&#223;',
    
'&agrave;' => '&#224;',
    
'&aacute;' => '&#225;',
    
'&acirc;' => '&#226;',
    
'&atilde;' => '&#227;',
    
'&auml;' => '&#228;',
    
'&aring;' => '&#229;',
    
'&aelig;' => '&#230;',
    
'&ccedil;' => '&#231;',
    
'&egrave;' => '&#232;',
    
'&eacute;' => '&#233;',
    
'&ecirc;' => '&#234;',
    
'&euml;' => '&#235;',
    
'&igrave;' => '&#236;',
    
'&iacute;' => '&#237;',
    
'&icirc;' => '&#238;',
    
'&iuml;' => '&#239;',
    
'&eth;' => '&#240;',
    
'&ntilde;' => '&#241;',
    
'&ograve;' => '&#242;',
    
'&oacute;' => '&#243;',
    
'&ocirc;' => '&#244;',
    
'&otilde;' => '&#245;',
    
'&ouml;' => '&#246;',
    
'&divide;' => '&#247;',
    
'&oslash;' => '&#248;',
    
'&ugrave;' => '&#249;',
    
'&uacute;' => '&#250;',
    
'&ucirc;' => '&#251;',
    
'&uuml;' => '&#252;',
    
'&yacute;' => '&#253;',
    
'&thorn;' => '&#254;',
    
'&yuml;' => '&#255;',
    
'&fnof;' => '&#402;',
    
'&Alpha;' => '&#913;',
    
'&Beta;' => '&#914;',
    
'&Gamma;' => '&#915;',
    
'&Delta;' => '&#916;',
    
'&Epsilon;' => '&#917;',
    
'&Zeta;' => '&#918;',
    
'&Eta;' => '&#919;',
    
'&Theta;' => '&#920;',
    
'&Iota;' => '&#921;',
    
'&Kappa;' => '&#922;',
    
'&Lambda;' => '&#923;',
    
'&Mu;' => '&#924;',
    
'&Nu;' => '&#925;',
    
'&Xi;' => '&#926;',
    
'&Omicron;' => '&#927;',
    
'&Pi;' => '&#928;',
    
'&Rho;' => '&#929;',
    
'&Sigma;' => '&#931;',
    
'&Tau;' => '&#932;',
    
'&Upsilon;' => '&#933;',
    
'&Phi;' => '&#934;',
    
'&Chi;' => '&#935;',
    
'&Psi;' => '&#936;',
    
'&Omega;' => '&#937;',
    
'&alpha;' => '&#945;',
    
'&beta;' => '&#946;',
    
'&gamma;' => '&#947;',
    
'&delta;' => '&#948;',
    
'&epsilon;' => '&#949;',
    
'&zeta;' => '&#950;',
    
'&eta;' => '&#951;',
    
'&theta;' => '&#952;',
    
'&iota;' => '&#953;',
    
'&kappa;' => '&#954;',
    
'&lambda;' => '&#955;',
    
'&mu;' => '&#956;',
    
'&nu;' => '&#957;',
    
'&xi;' => '&#958;',
    
'&omicron;' => '&#959;',
    
'&pi;' => '&#960;',
    
'&rho;' => '&#961;',
    
'&sigmaf;' => '&#962;',
    
'&sigma;' => '&#963;',
    
'&tau;' => '&#964;',
    
'&upsilon;' => '&#965;',
    
'&phi;' => '&#966;',
    
'&chi;' => '&#967;',
    
'&psi;' => '&#968;',
    
'&omega;' => '&#969;',
    
'&thetasym;' => '&#977;',
    
'&upsih;' => '&#978;',
    
'&piv;' => '&#982;',
    
'&bull;' => '&#8226;',
    
'&hellip;' => '&#8230;',
    
'&prime;' => '&#8242;',
    
'&Prime;' => '&#8243;',
    
'&oline;' => '&#8254;',
    
'&frasl;' => '&#8260;',
    
'&weierp;' => '&#8472;',
    
'&image;' => '&#8465;',
    
'&real;' => '&#8476;',
    
'&trade;' => '&#8482;',
    
'&alefsym;' => '&#8501;',
    
'&larr;' => '&#8592;',
    
'&uarr;' => '&#8593;',
    
'&rarr;' => '&#8594;',
    
'&darr;' => '&#8595;',
    
'&harr;' => '&#8596;',
    
'&crarr;' => '&#8629;',
    
'&lArr;' => '&#8656;',
    
'&uArr;' => '&#8657;',
    
'&rArr;' => '&#8658;',
    
'&dArr;' => '&#8659;',
    
'&hArr;' => '&#8660;',
    
'&forall;' => '&#8704;',
    
'&part;' => '&#8706;',
    
'&exist;' => '&#8707;',
    
'&empty;' => '&#8709;',
    
'&nabla;' => '&#8711;',
    
'&isin;' => '&#8712;',
    
'&notin;' => '&#8713;',
    
'&ni;' => '&#8715;',
    
'&prod;' => '&#8719;',
    
'&sum;' => '&#8721;',
    
'&minus;' => '&#8722;',
    
'&lowast;' => '&#8727;',
    
'&radic;' => '&#8730;',
    
'&prop;' => '&#8733;',
    
'&infin;' => '&#8734;',
    
'&ang;' => '&#8736;',
    
'&and;' => '&#8743;',
    
'&or;' => '&#8744;',
    
'&cap;' => '&#8745;',
    
'&cup;' => '&#8746;',
    
'&int;' => '&#8747;',
    
'&there4;' => '&#8756;',
    
'&sim;' => '&#8764;',
    
'&cong;' => '&#8773;',
    
'&asymp;' => '&#8776;',
    
'&ne;' => '&#8800;',
    
'&equiv;' => '&#8801;',
    
'&le;' => '&#8804;',
    
'&ge;' => '&#8805;',
    
'&sub;' => '&#8834;',
    
'&sup;' => '&#8835;',
    
'&nsub;' => '&#8836;',
    
'&sube;' => '&#8838;',
    
'&supe;' => '&#8839;',
    
'&oplus;' => '&#8853;',
    
'&otimes;' => '&#8855;',
    
'&perp;' => '&#8869;',
    
'&sdot;' => '&#8901;',
    
'&lceil;' => '&#8968;',
    
'&rceil;' => '&#8969;',
    
'&lfloor;' => '&#8970;',
    
'&rfloor;' => '&#8971;',
    
'&lang;' => '&#9001;',
    
'&rang;' => '&#9002;',
    
'&loz;' => '&#9674;',
    
'&spades;' => '&#9824;',
    
'&clubs;' => '&#9827;',
    
'&hearts;' => '&#9829;',
    
'&diams;' => '&#9830;',
    
'&quot;' => '&#34;',
    
'&amp;' => '&#38;',
    
'&lt;' => '&#60;',
    
'&gt;' => '&#62;',
    
'&OElig;' => '&#338;',
    
'&oelig;' => '&#339;',
    
'&Scaron;' => '&#352;',
    
'&scaron;' => '&#353;',
    
'&Yuml;' => '&#376;',
    
'&circ;' => '&#710;',
    
'&tilde;' => '&#732;',
    
'&ensp;' => '&#8194;',
    
'&emsp;' => '&#8195;',
    
'&thinsp;' => '&#8201;',
    
'&zwnj;' => '&#8204;',
    
'&zwj;' => '&#8205;',
    
'&lrm;' => '&#8206;',
    
'&rlm;' => '&#8207;',
    
'&ndash;' => '&#8211;',
    
'&mdash;' => '&#8212;',
    
'&lsquo;' => '&#8216;',
    
'&rsquo;' => '&#8217;',
    
'&sbquo;' => '&#8218;',
    
'&ldquo;' => '&#8220;',
    
'&rdquo;' => '&#8221;',
    
'&bdquo;' => '&#8222;',
    
'&dagger;' => '&#8224;',
    
'&Dagger;' => '&#8225;',
    
'&permil;' => '&#8240;',
    
'&lsaquo;' => '&#8249;',
    
'&rsaquo;' => '&#8250;',
    
'&euro;' => '&#8364;'
    
);

    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->_parserXML_OPTION_CASE_FOLDINGfalse);
        
xml_parser_set_option($this->_parserXML_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 &lt;%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 &lt;%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,