Index: trunk/phase3/includes/parser/Parser.php |
— | — | @@ -795,6 +795,18 @@ |
796 | 796 | $has_opened_tr = array(); # Did this table open a <tr> element? |
797 | 797 | $indent_level = 0; # indent level of the table |
798 | 798 | |
| 799 | + $table_tag = 'table'; |
| 800 | + $tr_tag = 'tr'; |
| 801 | + $th_tag = 'th'; |
| 802 | + $td_tag = 'td'; |
| 803 | + $caption_tag = 'caption'; |
| 804 | + |
| 805 | + $extra_table_attribs = null; |
| 806 | + $extra_tr_attribs = null; |
| 807 | + $extra_td_attribs = null; |
| 808 | + |
| 809 | + $convert_style = false; |
| 810 | + |
799 | 811 | foreach ( $lines as $outLine ) { |
800 | 812 | $line = trim( $outLine ); |
801 | 813 | |
— | — | @@ -811,9 +823,31 @@ |
812 | 824 | $indent_level = strlen( $matches[1] ); |
813 | 825 | |
814 | 826 | $attributes = $this->mStripState->unstripBoth( $matches[2] ); |
815 | | - $attributes = Sanitizer::fixTagAttributes( $attributes , 'table' ); |
816 | 827 | |
817 | | - $outLine = str_repeat( '<dl><dd>' , $indent_level ) . "<table{$attributes}>"; |
| 828 | + $attr = Sanitizer::decodeTagAttributes( $attributes ); |
| 829 | + |
| 830 | + $mode = @$attr['mode']; |
| 831 | + if ( !$mode ) $mode = 'data'; |
| 832 | + |
| 833 | + if ( $mode == 'grid' || $mode == 'layout' ) { |
| 834 | + $table_tag = 'div'; |
| 835 | + $tr_tag = 'div'; |
| 836 | + $th_tag = 'div'; |
| 837 | + $td_tag = 'div'; |
| 838 | + $caption_tag = 'div'; |
| 839 | + |
| 840 | + $extra_table_attribs = array( 'class' => 'grid-table', 'style' => 'display:table;' ); |
| 841 | + $extra_tr_attribs = array( 'class' => 'grid-row', 'style' => 'display:table-row;' ); |
| 842 | + $extra_td_attribs = array( 'class' => 'grid-cell', 'style' => 'display:table-cell;' ); |
| 843 | + |
| 844 | + $convert_style = true; |
| 845 | + } |
| 846 | + |
| 847 | + if ($convert_style) $attr['style'] = Sanitizer::styleFromAttributes( $attr ); |
| 848 | + $attr = Sanitizer::validateTagAttributes( $attr, $table_tag ); |
| 849 | + $attributes = Sanitizer::collapseTagAttributes( $attr, $extra_table_attribs ); |
| 850 | + |
| 851 | + $outLine = str_repeat( '<dl><dd>' , $indent_level ) . "<$table_tag{$attributes}>"; |
818 | 852 | array_push( $td_history , false ); |
819 | 853 | array_push( $last_tag_history , '' ); |
820 | 854 | array_push( $tr_history , false ); |
— | — | @@ -825,15 +859,15 @@ |
826 | 860 | continue; |
827 | 861 | } elseif ( substr( $line , 0 , 2 ) === '|}' ) { |
828 | 862 | # We are ending a table |
829 | | - $line = '</table>' . substr( $line , 2 ); |
| 863 | + $line = "</$table_tag>" . substr( $line , 2 ); |
830 | 864 | $last_tag = array_pop( $last_tag_history ); |
831 | 865 | |
832 | 866 | if ( !array_pop( $has_opened_tr ) ) { |
833 | | - $line = "<tr><td></td></tr>{$line}"; |
| 867 | + $line = "<$tr_tag><$td_tag></$td_tag></$tr_tag>{$line}"; |
834 | 868 | } |
835 | 869 | |
836 | 870 | if ( array_pop( $tr_history ) ) { |
837 | | - $line = "</tr>{$line}"; |
| 871 | + $line = "</$tr_tag>{$line}"; |
838 | 872 | } |
839 | 873 | |
840 | 874 | if ( array_pop( $td_history ) ) { |
— | — | @@ -847,7 +881,12 @@ |
848 | 882 | |
849 | 883 | # Whats after the tag is now only attributes |
850 | 884 | $attributes = $this->mStripState->unstripBoth( $line ); |
851 | | - $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' ); |
| 885 | + |
| 886 | + $attr = Sanitizer::decodeTagAttributes( $attributes ); |
| 887 | + if ($convert_style) $attr['style'] = Sanitizer::styleFromAttributes( $attr ); |
| 888 | + $attr = Sanitizer::validateTagAttributes( $attr, $tr_tag ); |
| 889 | + $attributes = Sanitizer::collapseTagAttributes( $attr, $extra_tr_attribs ); |
| 890 | + |
852 | 891 | array_pop( $tr_attributes ); |
853 | 892 | array_push( $tr_attributes, $attributes ); |
854 | 893 | |
— | — | @@ -857,7 +896,7 @@ |
858 | 897 | array_push( $has_opened_tr , true ); |
859 | 898 | |
860 | 899 | if ( array_pop( $tr_history ) ) { |
861 | | - $line = '</tr>'; |
| 900 | + $line = "</$tr_tag>"; |
862 | 901 | } |
863 | 902 | |
864 | 903 | if ( array_pop( $td_history ) ) { |
— | — | @@ -895,7 +934,7 @@ |
896 | 935 | if ( $first_character !== '+' ) { |
897 | 936 | $tr_after = array_pop( $tr_attributes ); |
898 | 937 | if ( !array_pop( $tr_history ) ) { |
899 | | - $previous = "<tr{$tr_after}>\n"; |
| 938 | + $previous = "<$tr_tag{$tr_after}>\n"; |
900 | 939 | } |
901 | 940 | array_push( $tr_history , true ); |
902 | 941 | array_push( $tr_attributes , '' ); |
— | — | @@ -910,11 +949,11 @@ |
911 | 950 | } |
912 | 951 | |
913 | 952 | if ( $first_character === '|' ) { |
914 | | - $last_tag = 'td'; |
| 953 | + $last_tag = $td_tag; |
915 | 954 | } elseif ( $first_character === '!' ) { |
916 | | - $last_tag = 'th'; |
| 955 | + $last_tag = $th_tag; |
917 | 956 | } elseif ( $first_character === '+' ) { |
918 | | - $last_tag = 'caption'; |
| 957 | + $last_tag = $caption_tag; |
919 | 958 | } else { |
920 | 959 | $last_tag = ''; |
921 | 960 | } |
— | — | @@ -924,15 +963,24 @@ |
925 | 964 | # A cell could contain both parameters and data |
926 | 965 | $cell_data = explode( '|' , $cell , 2 ); |
927 | 966 | |
| 967 | + $attributes = ''; |
| 968 | + |
928 | 969 | # Bug 553: Note that a '|' inside an invalid link should not |
929 | 970 | # be mistaken as delimiting cell parameters |
930 | 971 | if ( strpos( $cell_data[0], '[[' ) !== false ) { |
931 | | - $cell = "{$previous}<{$last_tag}>{$cell}"; |
| 972 | + if ($extra_td_attribs) $attributes = Sanitizer::collapseTagAttributes( $extra_td_attribs ); |
| 973 | + $cell = "{$previous}<{$last_tag}{$attributes}>{$cell}"; |
932 | 974 | } elseif ( count( $cell_data ) == 1 ) { |
933 | | - $cell = "{$previous}<{$last_tag}>{$cell_data[0]}"; |
| 975 | + if ($extra_td_attribs) $attributes = Sanitizer::collapseTagAttributes( $extra_td_attribs ); |
| 976 | + $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[0]}"; |
934 | 977 | } else { |
935 | 978 | $attributes = $this->mStripState->unstripBoth( $cell_data[0] ); |
936 | | - $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag ); |
| 979 | + |
| 980 | + $attr = Sanitizer::decodeTagAttributes( $attributes ); |
| 981 | + if ($convert_style) $attr['style'] = Sanitizer::styleFromAttributes( $attr ); |
| 982 | + $attr = Sanitizer::validateTagAttributes( $attr, $last_tag ); |
| 983 | + $attributes = Sanitizer::collapseTagAttributes( $attr, $extra_td_attribs ); |
| 984 | + |
937 | 985 | $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}"; |
938 | 986 | } |
939 | 987 | |
— | — | @@ -946,16 +994,16 @@ |
947 | 995 | # Closing open td, tr && table |
948 | 996 | while ( count( $td_history ) > 0 ) { |
949 | 997 | if ( array_pop( $td_history ) ) { |
950 | | - $out .= "</td>\n"; |
| 998 | + $out .= "</$td_tag>\n"; |
951 | 999 | } |
952 | 1000 | if ( array_pop( $tr_history ) ) { |
953 | | - $out .= "</tr>\n"; |
| 1001 | + $out .= "</$tr_tag>\n"; |
954 | 1002 | } |
955 | 1003 | if ( !array_pop( $has_opened_tr ) ) { |
956 | | - $out .= "<tr><td></td></tr>\n" ; |
| 1004 | + $out .= "<$tr_tag><$td_tag></$td_tag></$tr_tag>\n" ; |
957 | 1005 | } |
958 | 1006 | |
959 | | - $out .= "</table>\n"; |
| 1007 | + $out .= "</$table_tag>\n"; |
960 | 1008 | } |
961 | 1009 | |
962 | 1010 | # Remove trailing line-ending (b/c) |
— | — | @@ -964,7 +1012,7 @@ |
965 | 1013 | } |
966 | 1014 | |
967 | 1015 | # special case: don't return empty table |
968 | | - if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) { |
| 1016 | + if ( $out === "<$table_tag>\n<$tr_tag><$td_tag></$td_tag></$tr_tag>\n</$table_tag>" ) { |
969 | 1017 | $out = ''; |
970 | 1018 | } |
971 | 1019 | |
Index: trunk/phase3/includes/Sanitizer.php |
— | — | @@ -793,6 +793,51 @@ |
794 | 794 | } |
795 | 795 | } |
796 | 796 | |
| 797 | + /** |
| 798 | + * Take an associative array of attribute name/value pairs |
| 799 | + * and generate a css style representing all the style-related |
| 800 | + * attributes. If there already a style attribute in the array, |
| 801 | + * it is also included in the value returned. |
| 802 | + */ |
| 803 | + static function styleFromAttributes( $attributes ) { |
| 804 | + $styles = array(); |
| 805 | + |
| 806 | + foreach ( $attributes as $attribute => $value ) { |
| 807 | + if ( $attribute == 'bgcolor' ) { |
| 808 | + $styles[] = "background-color: $value"; |
| 809 | + } else if ( $attribute == 'border' ) { |
| 810 | + $styles[] = "border-width: $value"; |
| 811 | + } else if ( $attribute == 'align' ) { |
| 812 | + $styles[] = "text-align: $value"; |
| 813 | + } else if ( $attribute == 'valign' ) { |
| 814 | + $styles[] = "vertical-align: $value"; |
| 815 | + } else if ( $attribute == 'width' ) { |
| 816 | + if ( preg_match( '/\d+/', $value ) === false ) { |
| 817 | + $value .= 'px'; |
| 818 | + } |
| 819 | + |
| 820 | + $styles[] = "width: $value"; |
| 821 | + } else if ( $attribute == 'height' ) { |
| 822 | + if ( preg_match( '/\d+/', $value ) === false ) { |
| 823 | + $value .= 'px'; |
| 824 | + } |
| 825 | + |
| 826 | + $styles[] = "height: $value"; |
| 827 | + } else if ( $attribute == 'nowrap' ) { |
| 828 | + if ( $value ) { |
| 829 | + $styles[] = "white-space: nowrap"; |
| 830 | + } |
| 831 | + } |
| 832 | + } |
| 833 | + |
| 834 | + if ( isset( $attributes[ 'style' ] ) ) { |
| 835 | + $styles[] = $attributes[ 'style' ]; |
| 836 | + } |
| 837 | + |
| 838 | + if ( !$styles ) return ''; |
| 839 | + else return implode( '; ', $styles ); |
| 840 | + } |
| 841 | + |
797 | 842 | /** |
798 | 843 | * Take a tag soup fragment listing an HTML element's attributes |
799 | 844 | * and normalize it to well-formed XML, discarding unwanted attributes. |
— | — | @@ -810,24 +855,66 @@ |
811 | 856 | * |
812 | 857 | * @param $text String |
813 | 858 | * @param $element String |
| 859 | + * @param $defaults Array (optional) associative array of default attributes to splice in. |
| 860 | + * class and style attributes are combined. Otherwise, values from |
| 861 | + * $attributes take precedence over values from $defaults. |
814 | 862 | * @return String |
815 | 863 | */ |
816 | | - static function fixTagAttributes( $text, $element ) { |
| 864 | + static function fixTagAttributes( $text, $element, $defaults = null ) { |
817 | 865 | if( trim( $text ) == '' ) { |
818 | 866 | return ''; |
819 | 867 | } |
820 | 868 | |
821 | | - $stripped = Sanitizer::validateTagAttributes( |
822 | | - Sanitizer::decodeTagAttributes( $text ), $element ); |
| 869 | + $decoded = Sanitizer::decodeTagAttributes( $text ); |
| 870 | + $stripped = Sanitizer::validateTagAttributes( $decoded, $element ); |
| 871 | + $attribs = Sanitizer::collapseTagAttributes( $stripped, $defaults ); |
823 | 872 | |
824 | | - $attribs = array(); |
825 | | - foreach( $stripped as $attribute => $value ) { |
| 873 | + return $attribs; |
| 874 | + } |
| 875 | + |
| 876 | + /** |
| 877 | + * Take an associative array or attribute name/value pairs |
| 878 | + * and collapses it to well-formed XML. |
| 879 | + * Does not filter attributes. |
| 880 | + * Output is safe for further wikitext processing, with escaping of |
| 881 | + * values that could trigger problems. |
| 882 | + * |
| 883 | + * - Double-quotes all attribute values |
| 884 | + * - Prepends space if there are attributes. |
| 885 | + * |
| 886 | + * @param $attributes Array is an associative array of attribute name/value pairs. |
| 887 | + * Assumed to be sanitized already. |
| 888 | + * @param $defaults Array (optional) associative array of default attributes to splice in. |
| 889 | + * class and style attributes are combined. Otherwise, values from |
| 890 | + * $attributes take precedence over values from $defaults. |
| 891 | + * @return String |
| 892 | + */ |
| 893 | + static function collapseTagAttributes( $attributes, $defaults = null ) { |
| 894 | + if ( $defaults ) { |
| 895 | + foreach( $defaults as $attribute => $value ) { |
| 896 | + if ( isset( $attributes[ $attribute ] ) ) { |
| 897 | + if ( $attribute == 'class' ) { |
| 898 | + $value .= ' '. $attributes[ $attribute ]; |
| 899 | + } else if ( $attribute == 'style' ) { |
| 900 | + $value .= '; ' . $attributes[ $attribute ]; |
| 901 | + } else { |
| 902 | + continue; |
| 903 | + } |
| 904 | + } |
| 905 | + |
| 906 | + $attributes[ $attribute ] = $value; |
| 907 | + } |
| 908 | + } |
| 909 | + |
| 910 | + $chunks = array(); |
| 911 | + |
| 912 | + foreach( $attributes as $attribute => $value ) { |
826 | 913 | $encAttribute = htmlspecialchars( $attribute ); |
827 | 914 | $encValue = Sanitizer::safeEncodeAttribute( $value ); |
828 | 915 | |
829 | | - $attribs[] = "$encAttribute=\"$encValue\""; |
| 916 | + $chunks[] = "$encAttribute=\"$encValue\""; |
830 | 917 | } |
831 | | - return count( $attribs ) ? ' ' . implode( ' ', $attribs ) : ''; |
| 918 | + return count( $chunks ) ? ' ' . implode( ' ', $chunks ) : ''; |
832 | 919 | } |
833 | 920 | |
834 | 921 | /** |