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 // +----------------------------------------------------------------------+
13 * @author Bermi Ferrer <bermi a.t akelos c.om>
14 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
15 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
18 class AkXhtmlValidator
20 var $_attributes = array(
32 'attributes' => array(
50 'attributes' => array(
60 'attributes' => array(
61 'accesskey' => '/^(\w){1}$/',
62 'tabindex' => '/^(\d)+$/'
71 'attributes' => array(
86 'attributes' => array(
111 'attributes' => array(
130 'attributes' => array(
143 'attributes' => array(
149 'rel' => '/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/',
150 'rev' => '/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/',
151 'shape' => '/^(rect|rectangle|circ|circle|poly|polygon)$/',
159 'attributes' => array(
163 'nohref' => '/^(true|false)$/',
164 'shape' => '/^(rect|rectangle|circ|circle|poly|polygon)$/'
172 'attributes' => array(
180 'attributes' => array(
181 'dir' => '/^(ltr|rtl)$/'
188 'blockquote' => array(
189 'attributes' => array(
196 'attributes' => array(
197 'disabled' => '/^(disabled)$/',
198 'type' => '/^(button|reset|submit)$/',
207 'attributes' => array(
208 'align' => '/^(right|left|center|justify)$/',
211 'span' => '/^(\d)+$/',
212 'valign' => '/^(top|middle|bottom|baseline)$/',
215 'inside' => 'colgroup'
218 'attributes' => array(
219 'align' => '/^(right|left|center|justify)$/',
222 'span' => '/^(\d)+$/',
223 'valign' => '/^(top|middle|bottom|baseline)$/',
229 'attributes' => array(
231 'datetime' => '/^([0-9]){8}/'
243 'attributes' => array(
248 'method' => '/^(get|post)$/'
255 'attributes' => array(
267 'attributes' => array(
273 'attributes' => array(
288 'attributes' => array(
291 'checked' => '/^(checked)$/',
292 'disabled' => '/^(disabled)$/',
293 'maxlength' => '/^(\d)+$/',
295 'readonly' => '/^(readonly)$/',
296 'size' => '/^(\d)+$/',
298 'type' => '/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/',
304 'attributes' => array(
306 'datetime' => '/^([0-9]){8}/'
311 'attributes' => array(
319 'attributes' => array(
323 'media' => '/^(all|braille|print|projection|screen|speech|,|;| )+$/i',
324 'rel' => '/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i',
325 'rev' => '/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i',
331 'attributes' => array(
340 'attributes' => array(
342 'http-equiv' => '/^(content\-type|expires|refresh|set\-cookie)$/i',
352 'attributes' => array(
369 'attributes' => array(
371 'disabled' => '/^(disabled)$/'
378 'attributes' => array(
380 'disabled' => '/^(disabled)$/',
381 'selected' => '/^(selected)$/',
384 'inside' => 'select',
388 'attributes' => array(
390 'valuetype' => '/^(data|ref|object)$/',
400 'attributes' => array(
406 'attributes' => array(
407 'type' => '/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/',
409 'defer' => '/^(defer)$/',
417 'attributes' => array(
418 'disabled' => '/^(disabled)$/',
419 'multiple' => '/^(multiple)$/',
429 'attributes' => array(
431 'media' => '/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/'
440 'attributes' => array(
444 'frame' => '/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/',
445 'rules' => '/^(none|groups|rows|cols|all)$/',
451 'attributes' => array(
452 'align' => '/^(right|left|center|justify)$/',
455 'valign' => '/^(top|middle|bottom|baseline)$/'
459 'attributes' => array(
461 'align' => '/^(left|right|center|justify|char)$/',
465 'colspan' => '/^(\d)+$/',
467 'rowspan' => '/^(\d)+$/',
468 'scope' => '/^(col|colgroup|row|rowgroup)$/',
469 'valign' => '/^(top|middle|bottom|baseline)$/'
473 'attributes' => array(
487 'attributes' => array(
488 'align' => '/^(right|left|center|justify)$/',
491 'valign' => '/^(top|middle|bottom)$/',
496 'attributes' => array(
498 'align' => '/^(left|right|center|justify|char)$/',
502 'colspan' => '/^(\d)+$/',
504 'rowspan' => '/^(\d)+$/',
505 'scope' => '/^(col|colgroup|row|rowgroup)$/',
506 'valign' => '/^(top|middle|bottom|baseline)$/'
510 'attributes' => array(
511 'align' => '/^(right|left|center|justify)$/',
514 'valign' => '/^(top|middle|bottom|baseline)$/'
519 'attributes' => array(
520 'align' => '/^(right|left|center|justify|char)$/',
523 'valign' => '/^(top|middle|bottom|baseline)$/'
531 var $_entities = array(
532 ' ' => ' ',
533 '¡' => '¡',
534 '¢' => '¢',
535 '£' => '£',
536 '¤' => '¤',
538 '¦' => '¦',
539 '§' => '§',
541 '©' => '©',
542 'ª' => 'ª',
543 '«' => '«',
547 '¯' => '¯',
549 '±' => '±',
550 '²' => '²',
551 '³' => '³',
552 '´' => '´',
553 'µ' => 'µ',
554 '¶' => '¶',
555 '·' => '·',
556 '¸' => '¸',
557 '¹' => '¹',
558 'º' => 'º',
559 '»' => '»',
560 '¼' => '¼',
561 '½' => '½',
562 '¾' => '¾',
563 '¿' => '¿',
564 'À' => 'À',
565 'Á' => 'Á',
566 'Â' => 'Â',
567 'Ã' => 'Ã',
568 'Ä' => 'Ä',
569 'Å' => 'Å',
570 'Æ' => 'Æ',
571 'Ç' => 'Ç',
572 'È' => 'È',
573 'É' => 'É',
574 'Ê' => 'Ê',
575 'Ë' => 'Ë',
576 'Ì' => 'Ì',
577 'Í' => 'Í',
578 'Î' => 'Î',
579 'Ï' => 'Ï',
581 'Ñ' => 'Ñ',
582 'Ò' => 'Ò',
583 'Ó' => 'Ó',
584 'Ô' => 'Ô',
585 'Õ' => 'Õ',
586 'Ö' => 'Ö',
587 '×' => '×',
588 'Ø' => 'Ø',
589 'Ù' => 'Ù',
590 'Ú' => 'Ú',
591 'Û' => 'Û',
592 'Ü' => 'Ü',
593 'Ý' => 'Ý',
594 'Þ' => 'Þ',
595 'ß' => 'ß',
596 'à' => 'à',
597 'á' => 'á',
598 'â' => 'â',
599 'ã' => 'ã',
600 'ä' => 'ä',
601 'å' => 'å',
602 'æ' => 'æ',
603 'ç' => 'ç',
604 'è' => 'è',
605 'é' => 'é',
606 'ê' => 'ê',
607 'ë' => 'ë',
608 'ì' => 'ì',
609 'í' => 'í',
610 'î' => 'î',
611 'ï' => 'ï',
613 'ñ' => 'ñ',
614 'ò' => 'ò',
615 'ó' => 'ó',
616 'ô' => 'ô',
617 'õ' => 'õ',
618 'ö' => 'ö',
619 '÷' => '÷',
620 'ø' => 'ø',
621 'ù' => 'ù',
622 'ú' => 'ú',
623 'û' => 'û',
624 'ü' => 'ü',
625 'ý' => 'ý',
626 'þ' => 'þ',
627 'ÿ' => 'ÿ',
628 'ƒ' => 'ƒ',
629 'Α' => 'Α',
630 'Β' => 'Β',
631 'Γ' => 'Γ',
632 'Δ' => 'Δ',
633 'Ε' => 'Ε',
634 'Ζ' => 'Ζ',
636 'Θ' => 'Θ',
637 'Ι' => 'Ι',
638 'Κ' => 'Κ',
639 'Λ' => 'Λ',
643 'Ο' => 'Ο',
646 'Σ' => 'Σ',
648 'Υ' => 'Υ',
652 'Ω' => 'Ω',
653 'α' => 'α',
654 'β' => 'β',
655 'γ' => 'γ',
656 'δ' => 'δ',
657 'ε' => 'ε',
658 'ζ' => 'ζ',
660 'θ' => 'θ',
661 'ι' => 'ι',
662 'κ' => 'κ',
663 'λ' => 'λ',
667 'ο' => 'ο',
670 'ς' => 'ς',
671 'σ' => 'σ',
673 'υ' => 'υ',
677 'ω' => 'ω',
678 'ϑ' => 'ϑ',
679 'ϒ' => 'ϒ',
681 '•' => '•',
682 '…' => '…',
683 '′' => '′',
684 '″' => '″',
685 '‾' => '‾',
686 '⁄' => '⁄',
687 '℘' => '℘',
688 'ℑ' => 'ℑ',
689 'ℜ' => 'ℜ',
690 '™' => '™',
691 'ℵ' => 'ℵ',
692 '←' => '←',
693 '↑' => '↑',
694 '→' => '→',
695 '↓' => '↓',
696 '↔' => '↔',
697 '↵' => '↵',
698 '⇐' => '⇐',
699 '⇑' => '⇑',
700 '⇒' => '⇒',
701 '⇓' => '⇓',
702 '⇔' => '⇔',
703 '∀' => '∀',
704 '∂' => '∂',
705 '∃' => '∃',
706 '∅' => '∅',
707 '∇' => '∇',
708 '∈' => '∈',
709 '∉' => '∉',
711 '∏' => '∏',
712 '∑' => '∑',
713 '−' => '−',
714 '∗' => '∗',
715 '√' => '√',
716 '∝' => '∝',
717 '∞' => '∞',
718 '∠' => '∠',
719 '∧' => '∧',
721 '∩' => '∩',
722 '∪' => '∪',
723 '∫' => '∫',
724 '∴' => '∴',
725 '∼' => '∼',
726 '≅' => '≅',
727 '≈' => '≈',
729 '≡' => '≡',
732 '⊂' => '⊂',
733 '⊃' => '⊃',
734 '⊄' => '⊄',
735 '⊆' => '⊆',
736 '⊇' => '⊇',
737 '⊕' => '⊕',
738 '⊗' => '⊗',
739 '⊥' => '⊥',
740 '⋅' => '⋅',
741 '⌈' => '⌈',
742 '⌉' => '⌉',
743 '⌊' => '⌊',
744 '⌋' => '⌋',
745 '⟨' => '〈',
746 '⟩' => '〉',
747 '◊' => '◊',
748 '♠' => '♠',
749 '♣' => '♣',
750 '♥' => '♥',
751 '♦' => '♦',
756 'Œ' => 'Œ',
757 'œ' => 'œ',
758 'Š' => 'Š',
759 'š' => 'š',
760 'Ÿ' => 'Ÿ',
761 'ˆ' => 'ˆ',
762 '˜' => '˜',
763 ' ' => ' ',
764 ' ' => ' ',
765 ' ' => ' ',
766 '‌' => '‌',
767 '‍' => '‍',
768 '‎' => '‎',
769 '‏' => '‏',
770 '–' => '–',
771 '—' => '—',
772 '‘' => '‘',
773 '’' => '’',
774 '‚' => '‚',
775 '“' => '“',
776 '”' => '”',
777 '„' => '„',
778 '†' => '†',
779 '‡' => '‡',
780 '‰' => '‰',
781 '‹' => '‹',
782 '›' => '›',
783 '€' => '€'
787 var $_stack = array();
788 var $_errors = array();
790 function AkXhtmlValidator()
792 $this->_parser
= xml_parser_create('');
793 xml_set_object($this->_parser
, &$this);
794 xml_set_element_handler($this->_parser
, 'tagOpen', 'tagClose');
795 xml_set_character_data_handler($this->_parser
, 'cdata');
796 xml_parser_set_option($this->_parser
, XML_OPTION_CASE_FOLDING
, false);
797 xml_parser_set_option($this->_parser
, XML_OPTION_TARGET_ENCODING
, 'UTF-8');
800 function validateTagAttributes($tag, $attributes = array())
802 $possible_attributes = $this->getPossibleTagAttributes($tag);
803 foreach($attributes as $attribute => $value) {
804 if (!in_array($attribute, $possible_attributes)) {
805 $this->addError(Ak
::t("Attribute %attribute can't be used inside <%tag> tags", array(
806 '%attribute' => $attribute,
814 } elseif ($this->doesAttributeNeedsValidation($tag, $attribute)) {
815 $this->validateAttribute($tag, $attribute, $value);
820 function doesAttributeNeedsValidation($tag, $attribute)
822 return isset($this->_tags
[$tag]['attributes'][$attribute]) ||
isset($this->_tags
[$tag]['required']) && in_array($attribute, $this->_tags
[$tag]['required']);
825 function validateAttribute($tag, $attribute, $value = null)
827 if (isset($this->_tags
[$tag]['attributes'][$attribute]) && (strlen($value) > 0)) {
828 if (!preg_match($this->_tags
[$tag]['attributes'][$attribute], $value)) {
829 $this->addError(Ak
::t("Invalid value on <%tag %attribute=\"%value\"... Valid values must match the pattern \"%pattern\"", array(
831 '%attribute' => $attribute,
833 '%pattern' => htmlentities($this->_tags
[$tag]['attributes'][$attribute])
842 if (isset($this->_tags
[$tag]['required']) && in_array($attribute, $this->_tags
[$tag]['required']) && (strlen($value) == 0)) {
843 $this->addError(Ak
::t("Missing required attribute %attribute on <%tag>", array(
845 '%attribute' => $attribute
855 function addError($error, $highlight_text = array())
857 $this->_errors
[] = $this->highlightError($error, $highlight_text) .' on line '.$this->getCurrentLine();
860 function highlightError($error, $highlight_text = array())
862 if (empty($highlight_text)) {
865 require_once (AK_LIB_DIR
.DS
.'AkColor.php');
866 require_once (AK_LIB_DIR
.DS
.'AkActionView'.DS
.'helpers'.DS
.'text_helper.php');
867 $line = $this->getCurrentLine();
868 $highlighted_error = '';
869 foreach($highlight_text as $phrases) {
870 $color = AkColor
::getRandomHex();
871 if (is_array($phrases)) {
872 $highlighted_error_line = $error;
873 foreach($phrases as $phrase) {
874 $this->_linesToHighlight
[$line][$error] = array(
876 'phrase' => htmlentities($phrase)
878 $highlighted_error_line = TextHelper
::highlight($highlighted_error_line, $phrase.' ', ' <strong style="border:2px solid #'.$color.'; background: #ffc;">\1</strong> ');
880 $highlighted_error.= $highlighted_error_line;
882 $highlighted_error = TextHelper
::highlight($error, $phrases.' ', ' <strong style="border:2px solid #'.$color.'; background: #ffc">\1</strong> ');
883 $this->_linesToHighlight
[$line][$error] = array(
885 'phrase' => htmlentities($phrases)
889 return $highlighted_error;
892 function highlightErrors($xhtml)
894 $highlighted_xhtml = array();
895 if (!empty($this->_linesToHighlight
)) {
896 $xhtml_arr = preg_split('/\n|\r/', $xhtml);
897 foreach($xhtml_arr as $k => $xhtml_line) {
898 $pos = $k+
$this->_startLine
;
899 $highlighted_xhtml[$k] = $pos." ";
900 $xhtml_line = htmlentities($xhtml_line);
901 if (isset($this->_linesToHighlight
[$pos])) {
902 foreach($this->_linesToHighlight
[$pos] as $highlight_details) {
903 $highlighted_xhtml[$k].= TextHelper
::highlight($xhtml_line, $highlight_details['phrase'], '<strong style="border:2px solid #'.$highlight_details['color'].';padding:1px; margin:1px; background: #ffc;">\1</strong>');
906 $highlighted_xhtml[$k].= $xhtml_line;
908 $highlighted_xhtml[$k].= "<br />\n";
911 return empty($highlighted_xhtml) ?
$xhtml : join($highlighted_xhtml);
914 function getCurrentLine()
916 return xml_get_current_line_number($this->_parser
) +
$this->_startLine
;
919 function hasErrors(&$xhtml)
921 $this->validateUniquenessOfIds();
922 if (count($this->getErrors()) > 0) {
923 $xhtml = $this->highlightErrors($xhtml);
932 return array_unique($this->_errors
);
935 function showErrors()
937 echo '<ul><li>'.join("</li>\n<li>", $this->getErrors()) .'</li></ul>';
940 function getPossibleTagAttributes($tag)
943 if (!isset($cache[$tag])) {
944 $cache[$tag] = array_unique(array_merge($this->getUniqueAttributesAndEventsForTag($tag) , $this->getDefaultAttributesAndEventsForTag($tag)));
950 function validateRequiredAttributes($tag, $attributes)
952 $compulsory = $this->getCompulsoryTagAttributes($tag);
953 $errors = array_diff($compulsory, array_keys($attributes));
954 if (!empty($errors)) {
955 $this->addError(Ak
::t('Tag %tag requires %attributes to be defined', array(
957 '%attributes' => (count($errors) == 1 ?
'attribute "' : 'attributes "') .join('", "', $errors) .'"'
964 function protectFromDuplicatedIds($tag, $attributes)
966 if (isset($attributes['id'])) {
967 if (isset($this->_idTagXref
[$attributes['id']])) {
968 $this->addError(Ak
::t('Repeating id %id', array(
969 '%id' => $attributes['id']
974 $this->_tagIdCounter
[$attributes['id']] = isset($this->_tagIdCounter
[$attributes['id']]) ?
$this->_tagIdCounter
[$attributes['id']]+
1 : 1;
975 $this->_idTagXref
[$attributes['id']][] = $tag;
979 function validateUniquenessOfIds()
981 if (isset($this->_tagIdCounter
) && max(array_values($this->_tagIdCounter
)) > 1) {
982 foreach($this->_tagIdCounter
as $id => $count) {
984 $this->addError(Ak
::t('You have repeated the id %id %count times on your xhtml code. Duplicated Ids found on %tags', array(
987 '%tags' => (count($this->_idTagXref
[$id]) == 1 ?
'tag "' : 'tag "') .join('", "', $this->_idTagXref
[$id]) .'"'
994 function getCompulsoryTagAttributes($tag)
996 return !empty($this->_tags
[$tag]['required']) ?
(array)$this->_tags
[$tag]['required'] : array();
999 function getUniqueAttributesAndEventsForTag($tag)
1002 if (isset($this->_tags
[$tag]['attributes']) && is_array($this->_tags
[$tag]['attributes'])) {
1003 foreach($this->_tags
[$tag]['attributes'] as $k => $candidate) {
1004 $result[] = is_numeric($k) ?
$candidate : $k;
1010 function getDefaultAttributesAndEventsForTag($tag)
1013 if (isset($this->_tags
[$tag]) ||
in_array($tag, $this->_tags
)) {
1014 foreach($this->getDefaultAttributesAndEventsForTags() as $defaults) {
1015 if ((isset($defaults['except']) && in_array($tag, $defaults['except'])) ||
(isset($defaults['only']) && !in_array($tag, $defaults['only']))) {
1018 foreach(isset($defaults['attributes']) ?
$defaults['attributes'] : $defaults['events'] as $k => $candidate) {
1019 $default[] = is_array($candidate) ?
$k : $candidate;;
1026 function getDefaultAttributesAndEventsForTags()
1028 if (!isset($this->default_values_for_tags
)) {
1029 $this->default_values_for_tags
= array_merge($this->_attributes
, $this->_events
);
1031 return $this->default_values_for_tags
;
1034 function getAvailableTags()
1037 foreach(array_keys($this->_tags
) as $k) {
1038 $tags[] = is_numeric($k) ?
$this->_tags
[$k] : $k;
1044 function validate(&$xhtml)
1046 $this->_startLine
= 1;
1047 $xhtml_copy = $this->removeDoctypeHeader($xhtml);
1048 $xhtml_copy = $this->removeCdata($xhtml_copy);
1049 $xhtml_copy = $this->convertLiteralEntitiesToNumericalEntities($xhtml_copy);
1050 $xhtml_copy = '<all>'.$xhtml_copy.'</all>';
1051 if (!xml_parse($this->_parser
, $xhtml_copy)) {
1052 $this->addError(Ak
::t('XHTML is not well-formed.') .' '.xml_error_string(xml_get_error_code($this->_parser
)));
1054 return !$this->hasErrors($xhtml);
1057 function removeDoctypeHeader($xhtml)
1059 if (substr($xhtml, 0, 9) == '<!DOCTYPE') {
1060 $replacement = substr($xhtml, 0, strpos($xhtml, '>'));
1061 $this->_startLine
= count(substr_count($replacement, "\n"));
1063 return (isset($replacement)) ?
substr($xhtml, strlen($replacement)) : $xhtml;
1066 function removeCdata($xhtml)
1068 $xhtml = preg_replace('(<\!\[CDATA\[(.|\n)*\]\]>)', '', $xhtml);
1069 return str_replace(array('<![CDATA[',']]>') , '', $xhtml);
1073 function convertLiteralEntitiesToNumericalEntities($xhtml)
1075 return str_replace(array_keys($this->_entities
), array_values($this->_entities
), $xhtml);
1078 function tagOpen($parser, $tag, $attributes)
1080 $this->_start_byte
= xml_get_current_byte_index($parser);
1081 if ($tag == 'all') {
1082 $this->_stack
[] = 'all';
1085 $previous = $this->_stack
[count($this->_stack
) -1];
1086 $this->validateRequiredAttributes($tag, $attributes);
1087 $this->protectFromDuplicatedIds($tag, $attributes);
1088 if (!in_array($previous, $this->getAvailableTags())) {
1089 $this->validateTagAttributes($tag, $attributes);
1090 $this->_stack
[] = $tag;
1093 if (!in_array($tag, $this->getAvailableTags())) {
1094 $this->addError(Ak
::t("Illegal tag: <code>%tag</code>", array(
1099 $this->_stack
[] = $tag;
1102 // Is tag allowed in the current context?
1103 if (!$this->isTagAlowedOnCurrentContext($tag, $previous)) {
1104 if ($previous != 'all') {
1105 //$this->addError(Ak::t("Tag <code>%tag</code> must occur inside another tag",array('%tag'=>$tag)));
1107 $this->addError(Ak
::t("Tag %tag is not allowed within tag %previous", array(
1109 '%previous' => $previous
1115 $this->validateTagAttributes($tag, $attributes);
1116 $this->_stack
[] = $tag;
1119 function isTagAlowedOnCurrentContext($tag, $previous)
1121 $rules = $this->getRules();
1122 $result = isset($rules[$previous]) ?
in_array($tag, $rules[$previous]) : true;
1123 $inverse_rules = $this->getInverseRulesForTag($tag);
1124 $result = isset($inverse_rules[$tag]) ?
in_array($previous, $inverse_rules[$tag]) : $result;
1131 if (!isset($rules)) {
1132 //$inline = array ('abbr','cite','code','dfn','em','kbd','object','quote','q','samp','span','strong','var','a','sup','sub','acronym','img','#PCDATA');
1172 //$block = array('dl','nl','ol','ul','address','blockcode','blockquote','div','p','pre','handler','section','separator','table');
1206 $flow = array_merge($block, $inline);
1220 'body' => array_merge(array(
1230 //'p' => array_merge($inline, array('blockcode', 'blockquote', 'pre', 'table', 'dl', 'nl', 'ol', 'ul')),
1231 'blockquote' => $block,
1236 'pre' => array_diff($inline, array(
1244 'form' => array_diff($flow, array(
1256 'colgroup' => array(
1269 'address' => array_merge($inline, array(
1272 'fieldset' => array_merge($flow, array(
1275 'a' => array_diff($inline, array(
1278 'object' => array_merge($flow, array(
1281 'map' => array_merge($block, array(
1288 'optgroup' => array(
1291 'label' => array_diff($inline, array(
1294 'button' => array_diff($flow, array(
1317 foreach($flow_tags as $flow_tag) {
1318 $rules[$flow_tag] = $flow;
1320 $inline_tags = array(
1356 foreach($inline_tags as $inline_tag) {
1357 $rules[$inline_tag] = $inline;
1363 function getInverseRulesForTag($tag)
1365 static $inverse_rules;
1366 if (!isset($inverse_rules[$tag])) {
1367 $inverse_rules[$tag] = array();
1368 $rules = $this->getRules();
1369 foreach($rules as $container_tag => $rule) {
1370 if (in_array($tag, $rule)) {
1371 $inverse_rules[$tag][] = $container_tag;
1375 return $inverse_rules[$tag];
1378 function cdata($parser, $cdata)
1380 // Simply check that the 'previous' tag allows CDATA
1381 $previous = $this->_stack
[count($this->_stack
) -1];
1382 if ($cdata != '' && in_array($previous, array(
1395 $this->addError(Ak
::t("%previous tag is not a content tag. close it like this '<%previous />'", array(
1396 '%previous' => $previous
1401 // If previous tag is illegal, no point in running test
1402 if (!in_array($previous, $this->getAvailableTags())) {
1405 if (trim($cdata) != '') {
1406 if (!$this->isTagAlowedOnCurrentContext('#pcdata', $previous)) {
1407 $this->addError(Ak
::t("Tag <code>%previous</code> may not contain raw character data", array(
1408 '%previous' => $previous
1416 function tagClose($parser, $tag)
1418 if (in_array($tag, array(
1431 $this->_end_byte
= xml_get_current_byte_index($parser);
1432 if ($this->_end_byte
-$this->_start_byte
== 4) {
1433 $this->addError(Ak
::t("%tag tag is not a content tag. close it like this '<%tag />'", array(
1440 array_pop($this->_stack
);