2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3 // +----------------------------------------------------------------------+
4 // | Akelos Framework - http://www.akelos.org |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
7 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
8 // +----------------------------------------------------------------------+
11 * Validates Xhtml Documments
15 * require_once('XhtmlValidator.php');
16 * $XhtmlValidator = new XhtmlValidator();
17 * if($XhtmlValidator->validate($xhtml) === false){
18 * echo '<h1>Ooops! There are some errors on the XHTML page</h1>';
19 * $XhtmlValidator->showErrors();
20 * echo "<hr /><h2>Showing XHTML code</h2><hr /><div style='border:5px solid red;margin:5px;padding:15px;'>".$xhtml."</pre>";
25 * @package AkelosFramework
26 * @subpackage Development
27 * @author Bermi Ferrer <bermi a.t akelos c.om>
28 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
29 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
30 * @version $Revision 0.2 $
34 var $_attributes = array(
46 'attributes' => array(
62 'attributes' => array(
72 'attributes' => array(
73 'accesskey' => '/^(\w){1}$/',
74 'tabindex' => '/^(\d)+$/'
83 'attributes' => array(
98 'attributes' => array(
123 'attributes' => array(
142 'attributes' => array(
155 'attributes' => array(
161 'rel' => '/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/',
162 'rev' => '/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/',
163 'shape' => '/^(rect|rectangle|circ|circle|poly|polygon)$/',
171 'attributes' => array(
175 'nohref' => '/^(true|false)$/',
176 'shape' => '/^(rect|rectangle|circ|circle|poly|polygon)$/'
184 'attributes' => array(
192 'attributes' => array(
193 'dir' => '/^(ltr|rtl)$/'
200 'blockquote' => array(
201 'attributes' => array(
208 'attributes' => array(
209 'disabled' => '/^(disabled)$/',
210 'type' => '/^(button|reset|submit)$/',
219 'attributes' => array(
220 'align' => '/^(right|left|center|justify)$/',
223 'span' => '/^(\d)+$/',
224 'valign' => '/^(top|middle|bottom|baseline)$/',
227 'inside' => 'colgroup'
230 'attributes' => array(
231 'align' => '/^(right|left|center|justify)$/',
234 'span' => '/^(\d)+$/',
235 'valign' => '/^(top|middle|bottom|baseline)$/',
241 'attributes' => array(
243 'datetime' => '/^([0-9]){8}/'
255 'attributes' => array(
260 'method' => '/^(get|post)$/'
267 'attributes' => array(
279 'attributes' => array(
285 'attributes' => array(
300 'attributes' => array(
303 'checked' => '/^(checked)$/',
304 'disabled' => '/^(disabled)$/',
305 'maxlength' => '/^(\d)+$/',
307 'readonly' => '/^(readonly)$/',
308 'size' => '/^(\d)+$/',
310 'type' => '/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/',
316 'attributes' => array(
318 'datetime' => '/^([0-9]){8}/'
323 'attributes' => array(
331 'attributes' => array(
335 'media' => '/^(all|braille|print|projection|screen|speech|,|;| )+$/i',
336 'rel' => '/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i',
337 'rev' => '/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i',
343 'attributes' => array(
352 'attributes' => array(
354 'http-equiv' => '/^(content\-type|expires|refresh|set\-cookie)$/i',
364 'attributes' => array(
381 'attributes' => array(
383 'disabled' => '/^(disabled)$/'
390 'attributes' => array(
392 'disabled' => '/^(disabled)$/',
393 'selected' => '/^(selected)$/',
399 'inside' => 'select',
403 'attributes' => array(
405 'valuetype' => '/^(data|ref|object)$/',
415 'attributes' => array(
421 'attributes' => array(
422 'type' => '/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/',
424 'defer' => '/^(defer)$/',
432 'attributes' => array(
433 'disabled' => '/^(disabled)$/',
434 'multiple' => '/^(multiple)$/',
444 'attributes' => array(
446 'media' => '/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/'
455 'attributes' => array(
459 'frame' => '/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/',
460 'rules' => '/^(none|groups|rows|cols|all)$/',
466 'attributes' => array(
467 'align' => '/^(right|left|center|justify)$/',
470 'valign' => '/^(top|middle|bottom|baseline)$/'
474 'attributes' => array(
476 'align' => '/^(left|right|center|justify|char)$/',
480 'colspan' => '/^(\d)+$/',
482 'rowspan' => '/^(\d)+$/',
483 'scope' => '/^(col|colgroup|row|rowgroup)$/',
484 'valign' => '/^(top|middle|bottom|baseline)$/'
488 'attributes' => array(
502 'attributes' => array(
503 'align' => '/^(right|left|center|justify)$/',
506 'valign' => '/^(top|middle|bottom)$/',
511 'attributes' => array(
513 'align' => '/^(left|right|center|justify|char)$/',
517 'colspan' => '/^(\d)+$/',
519 'rowspan' => '/^(\d)+$/',
520 'scope' => '/^(col|colgroup|row|rowgroup)$/',
521 'valign' => '/^(top|middle|bottom|baseline)$/'
525 'attributes' => array(
526 'align' => '/^(right|left|center|justify)$/',
529 'valign' => '/^(top|middle|bottom|baseline)$/'
534 'attributes' => array(
535 'align' => '/^(right|left|center|justify|char)$/',
538 'valign' => '/^(top|middle|bottom|baseline)$/'
546 var $_entities = array(
547 ' ' => ' ',
548 '¡' => '¡',
549 '¢' => '¢',
550 '£' => '£',
551 '¤' => '¤',
553 '¦' => '¦',
554 '§' => '§',
556 '©' => '©',
557 'ª' => 'ª',
558 '«' => '«',
562 '¯' => '¯',
564 '±' => '±',
565 '²' => '²',
566 '³' => '³',
567 '´' => '´',
568 'µ' => 'µ',
569 '¶' => '¶',
570 '·' => '·',
571 '¸' => '¸',
572 '¹' => '¹',
573 'º' => 'º',
574 '»' => '»',
575 '¼' => '¼',
576 '½' => '½',
577 '¾' => '¾',
578 '¿' => '¿',
579 'À' => 'À',
580 'Á' => 'Á',
581 'Â' => 'Â',
582 'Ã' => 'Ã',
583 'Ä' => 'Ä',
584 'Å' => 'Å',
585 'Æ' => 'Æ',
586 'Ç' => 'Ç',
587 'È' => 'È',
588 'É' => 'É',
589 'Ê' => 'Ê',
590 'Ë' => 'Ë',
591 'Ì' => 'Ì',
592 'Í' => 'Í',
593 'Î' => 'Î',
594 'Ï' => 'Ï',
596 'Ñ' => 'Ñ',
597 'Ò' => 'Ò',
598 'Ó' => 'Ó',
599 'Ô' => 'Ô',
600 'Õ' => 'Õ',
601 'Ö' => 'Ö',
602 '×' => '×',
603 'Ø' => 'Ø',
604 'Ù' => 'Ù',
605 'Ú' => 'Ú',
606 'Û' => 'Û',
607 'Ü' => 'Ü',
608 'Ý' => 'Ý',
609 'Þ' => 'Þ',
610 'ß' => 'ß',
611 'à' => 'à',
612 'á' => 'á',
613 'â' => 'â',
614 'ã' => 'ã',
615 'ä' => 'ä',
616 'å' => 'å',
617 'æ' => 'æ',
618 'ç' => 'ç',
619 'è' => 'è',
620 'é' => 'é',
621 'ê' => 'ê',
622 'ë' => 'ë',
623 'ì' => 'ì',
624 'í' => 'í',
625 'î' => 'î',
626 'ï' => 'ï',
628 'ñ' => 'ñ',
629 'ò' => 'ò',
630 'ó' => 'ó',
631 'ô' => 'ô',
632 'õ' => 'õ',
633 'ö' => 'ö',
634 '÷' => '÷',
635 'ø' => 'ø',
636 'ù' => 'ù',
637 'ú' => 'ú',
638 'û' => 'û',
639 'ü' => 'ü',
640 'ý' => 'ý',
641 'þ' => 'þ',
642 'ÿ' => 'ÿ',
643 'ƒ' => 'ƒ',
644 'Α' => 'Α',
645 'Β' => 'Β',
646 'Γ' => 'Γ',
647 'Δ' => 'Δ',
648 'Ε' => 'Ε',
649 'Ζ' => 'Ζ',
651 'Θ' => 'Θ',
652 'Ι' => 'Ι',
653 'Κ' => 'Κ',
654 'Λ' => 'Λ',
658 'Ο' => 'Ο',
661 'Σ' => 'Σ',
663 'Υ' => 'Υ',
667 'Ω' => 'Ω',
668 'α' => 'α',
669 'β' => 'β',
670 'γ' => 'γ',
671 'δ' => 'δ',
672 'ε' => 'ε',
673 'ζ' => 'ζ',
675 'θ' => 'θ',
676 'ι' => 'ι',
677 'κ' => 'κ',
678 'λ' => 'λ',
682 'ο' => 'ο',
685 'ς' => 'ς',
686 'σ' => 'σ',
688 'υ' => 'υ',
692 'ω' => 'ω',
693 'ϑ' => 'ϑ',
694 'ϒ' => 'ϒ',
696 '•' => '•',
697 '…' => '…',
698 '′' => '′',
699 '″' => '″',
700 '‾' => '‾',
701 '⁄' => '⁄',
702 '℘' => '℘',
703 'ℑ' => 'ℑ',
704 'ℜ' => 'ℜ',
705 '™' => '™',
706 'ℵ' => 'ℵ',
707 '←' => '←',
708 '↑' => '↑',
709 '→' => '→',
710 '↓' => '↓',
711 '↔' => '↔',
712 '↵' => '↵',
713 '⇐' => '⇐',
714 '⇑' => '⇑',
715 '⇒' => '⇒',
716 '⇓' => '⇓',
717 '⇔' => '⇔',
718 '∀' => '∀',
719 '∂' => '∂',
720 '∃' => '∃',
721 '∅' => '∅',
722 '∇' => '∇',
723 '∈' => '∈',
724 '∉' => '∉',
726 '∏' => '∏',
727 '∑' => '∑',
728 '−' => '−',
729 '∗' => '∗',
730 '√' => '√',
731 '∝' => '∝',
732 '∞' => '∞',
733 '∠' => '∠',
734 '∧' => '∧',
736 '∩' => '∩',
737 '∪' => '∪',
738 '∫' => '∫',
739 '∴' => '∴',
740 '∼' => '∼',
741 '≅' => '≅',
742 '≈' => '≈',
744 '≡' => '≡',
747 '⊂' => '⊂',
748 '⊃' => '⊃',
749 '⊄' => '⊄',
750 '⊆' => '⊆',
751 '⊇' => '⊇',
752 '⊕' => '⊕',
753 '⊗' => '⊗',
754 '⊥' => '⊥',
755 '⋅' => '⋅',
756 '⌈' => '⌈',
757 '⌉' => '⌉',
758 '⌊' => '⌊',
759 '⌋' => '⌋',
760 '⟨' => '〈',
761 '⟩' => '〉',
762 '◊' => '◊',
763 '♠' => '♠',
764 '♣' => '♣',
765 '♥' => '♥',
766 '♦' => '♦',
771 'Œ' => 'Œ',
772 'œ' => 'œ',
773 'Š' => 'Š',
774 'š' => 'š',
775 'Ÿ' => 'Ÿ',
776 'ˆ' => 'ˆ',
777 '˜' => '˜',
778 ' ' => ' ',
779 ' ' => ' ',
780 ' ' => ' ',
781 '‌' => '‌',
782 '‍' => '‍',
783 '‎' => '‎',
784 '‏' => '‏',
785 '–' => '–',
786 '—' => '—',
787 '‘' => '‘',
788 '’' => '’',
789 '‚' => '‚',
790 '“' => '“',
791 '”' => '”',
792 '„' => '„',
793 '†' => '†',
794 '‡' => '‡',
795 '‰' => '‰',
796 '‹' => '‹',
797 '›' => '›',
798 '€' => '€'
802 var $_stack = array();
803 var $_errors = array();
805 function XhtmlValidator()
807 $this->_parser = xml_parser_create('');
808 xml_set_object($this->_parser, $this);
809 xml_set_element_handler($this->_parser, 'tagOpen', 'tagClose');
810 xml_set_character_data_handler($this->_parser, 'cdata');
811 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
812 xml_parser_set_option($this->_parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
815 function validateTagAttributes($tag, $attributes = array())
817 $possible_attributes = $this->getPossibleTagAttributes($tag);
818 foreach($attributes as $attribute => $value) {
819 if (!in_array($attribute, $possible_attributes)) {
820 $this->addError($this->translate("Attribute %attribute can't be used inside <%tag> tags", array(
821 '%attribute' => $attribute,
829 } elseif ($this->doesAttributeNeedsValidation($tag, $attribute)) {
830 $this->validateAttribute($tag, $attribute, $value);
835 function doesAttributeNeedsValidation($tag, $attribute)
837 return isset($this->_tags[$tag]['attributes'][$attribute]) || isset($this->_tags[$tag]['required']) && in_array($attribute, $this->_tags[$tag]['required']);
840 function validateAttribute($tag, $attribute, $value = null)
842 if (isset($this->_tags[$tag]['attributes'][$attribute]) && (strlen($value) > 0)) {
843 if (!preg_match($this->_tags[$tag]['attributes'][$attribute], $value)) {
844 $this->addError($this->translate("Invalid value on <%tag %attribute=\"%value\"... Valid values must match the pattern \"%pattern\"", array(
846 '%attribute' => $attribute,
848 '%pattern' => htmlentities($this->_tags[$tag]['attributes'][$attribute])
857 if (isset($this->_tags[$tag]['required']) && in_array($attribute, $this->_tags[$tag]['required']) && (strlen($value) == 0)) {
858 $this->addError($this->translate("Missing required attribute %attribute on <%tag>", array(
860 '%attribute' => $attribute
870 function addError($error, $highlight_text = array())
872 $this->_errors[] = $this->highlightError($error, $highlight_text) .' on line '.$this->getCurrentLine();
875 function highlightError($error, $highlight_text = array())
877 if (empty($highlight_text)) {
880 $line = $this->getCurrentLine();
881 $highlighted_error = '';
882 foreach($highlight_text as $phrases) {
883 $color = $this->getRandomHex();
884 if (is_array($phrases)) {
885 $highlighted_error_line = $error;
886 foreach($phrases as $phrase) {
887 $this->_linesToHighlight[$line][$error] = array(
889 'phrase' => htmlentities($phrase)
891 $highlighted_error_line = $this->highlight($highlighted_error_line, $phrase.' ', ' <strong style="border:2px solid #'.$color.'; background: #ffc;">\1</strong> ');
893 $highlighted_error.= $highlighted_error_line;
895 $highlighted_error = $this->highlight($error, $phrases.' ', ' <strong style="border:2px solid #'.$color.'; background: #ffc">\1</strong> ');
896 $this->_linesToHighlight[$line][$error] = array(
898 'phrase' => htmlentities($phrases)
902 return $highlighted_error;
905 function highlightErrors($xhtml)
907 $highlighted_xhtml = array();
908 if (!empty($this->_linesToHighlight)) {
909 $xhtml_arr = preg_split('/\n|\r/', $xhtml);
910 foreach($xhtml_arr as $k => $xhtml_line) {
911 $pos = $k+$this->_startLine;
912 $highlighted_xhtml[$k] = $pos." ";
913 $xhtml_line = htmlentities($xhtml_line);
914 if (isset($this->_linesToHighlight[$pos])) {
915 foreach($this->_linesToHighlight[$pos] as $highlight_details) {
916 $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>');
919 $highlighted_xhtml[$k].= $xhtml_line;
921 $highlighted_xhtml[$k].= "<br />\n";
924 return empty($highlighted_xhtml) ? $xhtml : join($highlighted_xhtml);
927 function getCurrentLine()
929 return xml_get_current_line_number($this->_parser) +$this->_startLine;
932 function hasErrors(&$xhtml)
934 $this->validateUniquenessOfIds();
935 if (count($this->getErrors()) > 0) {
936 $xhtml = $this->highlightErrors($xhtml);
945 return array_unique($this->_errors);
948 function showErrors()
950 echo '<ul><li>'.join("</li>\n<li>", $this->getErrors()) .'</li></ul>';
953 function getPossibleTagAttributes($tag)
956 if (!isset($cache[$tag])) {
957 $cache[$tag] = array_unique(array_merge($this->getUniqueAttributesAndEventsForTag($tag) , $this->getDefaultAttributesAndEventsForTag($tag)));
963 function validateRequiredAttributes($tag, $attributes)
965 $compulsory = $this->getCompulsoryTagAttributes($tag);
966 $errors = array_diff($compulsory, array_keys($attributes));
967 if (!empty($errors)) {
968 $this->addError($this->translate('Tag %tag requires %attributes to be defined', array(
970 '%attributes' => (count($errors) == 1 ? 'attribute "' : 'attributes "') .join('", "', $errors) .'"'
977 function protectFromDuplicatedIds($tag, $attributes)
979 if (isset($attributes['id'])) {
980 if (isset($this->_idTagXref[$attributes['id']])) {
981 $this->addError($this->translate('Repeating id %id', array(
982 '%id' => $attributes['id']
987 $this->_tagIdCounter[$attributes['id']] = isset($this->_tagIdCounter[$attributes['id']]) ? $this->_tagIdCounter[$attributes['id']]+1 : 1;
988 $this->_idTagXref[$attributes['id']][] = $tag;
992 function validateUniquenessOfIds()
994 if (isset($this->_tagIdCounter) && max(array_values($this->_tagIdCounter)) > 1) {
995 foreach($this->_tagIdCounter as $id => $count) {
997 $this->addError($this->translate('You have repeated the id %id %count times on your xhtml code. Duplicated Ids found on %tags', array(
1000 '%tags' => (count($this->_idTagXref[$id]) == 1 ? 'tag "' : 'tag "') .join('", "', $this->_idTagXref[$id]) .'"'
1007 function getCompulsoryTagAttributes($tag)
1009 return !empty($this->_tags[$tag]['required']) ? (array)$this->_tags[$tag]['required'] : array();
1012 function getUniqueAttributesAndEventsForTag($tag)
1015 if (isset($this->_tags[$tag]['attributes']) && is_array($this->_tags[$tag]['attributes'])) {
1016 foreach($this->_tags[$tag]['attributes'] as $k => $candidate) {
1017 $result[] = is_numeric($k) ? $candidate : $k;
1023 function getDefaultAttributesAndEventsForTag($tag)
1026 if (isset($this->_tags[$tag]) || in_array($tag, $this->_tags)) {
1027 foreach($this->getDefaultAttributesAndEventsForTags() as $defaults) {
1028 if ((isset($defaults['except']) && in_array($tag, $defaults['except'])) || (isset($defaults['only']) && !in_array($tag, $defaults['only']))) {
1031 foreach(isset($defaults['attributes']) ? $defaults['attributes'] : $defaults['events'] as $k => $candidate) {
1032 $default[] = is_array($candidate) ? $k : $candidate;;
1039 function getDefaultAttributesAndEventsForTags()
1041 if (!isset($this->default_values_for_tags)) {
1042 $this->default_values_for_tags = array_merge($this->_attributes, $this->_events);
1044 return $this->default_values_for_tags;
1047 function getAvailableTags()
1050 foreach(array_keys($this->_tags) as $k) {
1051 $tags[] = is_numeric($k) ? $this->_tags[$k] : $k;
1057 function validate(&$xhtml)
1059 $this->_startLine = 1;
1060 $xhtml_copy = $this->removeDoctypeHeader($xhtml);
1061 $xhtml_copy = $this->removeCdata($xhtml_copy);
1062 $xhtml_copy = $this->convertLiteralEntitiesToNumericalEntities($xhtml_copy);
1063 $xhtml_copy = '<all>'.$xhtml_copy.'</all>';
1064 if (!xml_parse($this->_parser, $xhtml_copy)) {
1065 $this->addError($this->translate('XHTML is not well-formed.') .' '.xml_error_string(xml_get_error_code($this->_parser)));
1067 return !$this->hasErrors($xhtml);
1070 function removeDoctypeHeader($xhtml)
1072 if (substr($xhtml, 0, 9) == '<!DOCTYPE') {
1073 $replacement = substr($xhtml, 0, strpos($xhtml, '>'));
1074 $this->_startLine = count(substr_count($replacement, "\n"));
1076 return (isset($replacement)) ? substr($xhtml, strlen($replacement)) : $xhtml;
1079 function removeCdata($xhtml)
1081 $xhtml = preg_replace('(<\!\[CDATA\[.*\]\]>)', '', $xhtml);
1082 return str_replace(array('<![CDATA[',']]>') , '', $xhtml);
1086 function convertLiteralEntitiesToNumericalEntities($xhtml)
1088 return str_replace(array_keys($this->_entities), array_values($this->_entities), $xhtml);
1091 function tagOpen($parser, $tag, $attributes)
1093 $this->_start_byte = xml_get_current_byte_index($parser);
1094 if ($tag == 'all') {
1095 $this->_stack[] = 'all';
1098 $previous = $this->_stack[count($this->_stack) -1];
1099 $this->validateRequiredAttributes($tag, $attributes);
1100 $this->protectFromDuplicatedIds($tag, $attributes);
1101 if (!in_array($previous, $this->getAvailableTags())) {
1102 $this->validateTagAttributes($tag, $attributes);
1103 $this->_stack[] = $tag;
1106 if (!in_array($tag, $this->getAvailableTags())) {
1107 $this->addError($this->translate("Illegal tag: <code>%tag</code>", array(
1112 $this->_stack[] = $tag;
1115 // Is tag allowed in the current context?
1116 if (!$this->isTagAlowedOnCurrentContext($tag, $previous)) {
1117 if ($previous != 'all') {
1118 //$this->addError($this->translate("Tag <code>%tag</code> must occur inside another tag",array('%tag'=>$tag)));
1120 $this->addError($this->translate("Tag %tag is not allowed within tag %previous", array(
1122 '%previous' => $previous
1128 $this->validateTagAttributes($tag, $attributes);
1129 $this->_stack[] = $tag;
1132 function isTagAlowedOnCurrentContext($tag, $previous)
1134 $rules = $this->getRules();
1135 $result = isset($rules[$previous]) ? in_array($tag, $rules[$previous]) : true;
1136 $inverse_rules = $this->getInverseRulesForTag($tag);
1137 $result = isset($inverse_rules[$tag]) ? in_array($previous, $inverse_rules[$tag]) : $result;
1144 if (!isset($rules)) {
1145 //$inline = array ('abbr','cite','code','dfn','em','kbd','object','quote','q','samp','span','strong','var','a','sup','sub','acronym','img','#PCDATA');
1186 //$block = array('dl','nl','ol','ul','address','blockcode','blockquote','div','p','pre','handler','section','separator','table');
1219 $flow = array_merge($block, $inline);
1233 'body' => array_merge(array(
1243 //'p' => array_merge($inline, array('blockcode', 'blockquote', 'pre', 'table', 'dl', 'nl', 'ol', 'ul')),
1244 'blockquote' => $block,
1249 'pre' => array_diff($inline, array(
1257 'form' => array_diff($flow, array(
1269 'colgroup' => array(
1282 'address' => array_merge($inline, array(
1285 'fieldset' => array_merge($flow, array(
1288 'a' => array_diff($inline, array(
1291 'object' => array_merge($flow, array(
1297 'map' => array_merge($block, array(
1304 'optgroup' => array(
1307 'label' => array_diff($inline, array(
1310 'button' => array_diff($flow, array(
1332 foreach($flow_tags as $flow_tag) {
1333 $rules[$flow_tag] = $flow;
1335 $inline_tags = array(
1371 foreach($inline_tags as $inline_tag) {
1372 $rules[$inline_tag] = $inline;
1378 function getInverseRulesForTag($tag)
1380 static $inverse_rules;
1381 if (!isset($inverse_rules[$tag])) {
1382 $inverse_rules[$tag] = array();
1383 $rules = $this->getRules();
1384 foreach($rules as $container_tag => $rule) {
1385 if (in_array($rule, $rule)) {
1386 $inverse_rules[$tag][] = $container_tag;
1390 return $inverse_rules[$tag];
1393 function cdata($parser, $cdata)
1395 // Simply check that the 'previous' tag allows CDATA
1396 $previous = $this->_stack[count($this->_stack) -1];
1397 if ($cdata != '' && in_array($previous, array(
1410 $this->addError($this->translate("%previous tag is not a content tag. close it like this '<%previous />'", array(
1411 '%previous' => $previous
1416 // If previous tag is illegal, no point in running test
1417 if (!in_array($previous, $this->getAvailableTags())) {
1420 if (trim($cdata) != '') {
1421 if (!$this->isTagAlowedOnCurrentContext('#pcdata', $previous)) {
1422 $this->addError($this->translate("Tag <code>%previous</code> may not contain raw character data", array(
1423 '%previous' => $previous
1431 function tagClose($parser, $tag)
1433 if (in_array($tag, array(
1446 $this->_end_byte = xml_get_current_byte_index($parser);
1447 if ($this->_end_byte-$this->_start_byte == 4) {
1448 $this->addError($this->translate("%tag tag is not a content tag. close it like this '<%tag />'", array(
1455 array_pop($this->_stack);
1461 * This functions belong to other classes in the Akelos Framework, but have been included here to avoid dependencies
1463 function highlight($text, $phrase, $highlighter = '<strong class="highlight">\1</strong>')
1465 $phrase = is_array($phrase) ? join('|',array_map('preg_quote',$phrase)) : preg_quote($phrase);
1466 return !empty($phrase) ? preg_replace('/('.$phrase.')/i', $highlighter,$text) : $text;
1471 $rgb = func_get_args();
1473 foreach (count($rgb) == 1 ? $rgb[0] : $rgb as $color){
1474 $color = dechex($color);
1475 $hex .= strlen($color) == 2 ? $color : $color.$color;
1480 function hexToRgb($hex_color)
1482 $hex_color = strtolower(trim($hex_color,'#;&Hh'));
1483 return array_map('hexdec',explode('.',wordwrap($hex_color, ceil(strlen($hex_color)/3),'.',1)));
1486 function getOpositeHex($hex_color)
1488 $rgb = $this->hexToRgb($hex_color);
1489 foreach ($rgb as $k=>$color){
1490 $rgb[$k] = (255-$color < 0 ? 0 : 255-$color);
1492 return $this->rgbToHex($rgb);
1495 function getRandomHex()
1497 return $this->rgbToHex(rand(0,255),rand(0,255),rand(0,255));
1500 function translate($string, $args = null)
1502 if(isset($args) && is_array($args)){
1503 $string = @str_replace(array_keys($args), array_values($args),$string);