Index: trunk/phase3/resources/mediawiki.util/mediawiki.util.test.js |
— | — | @@ -28,7 +28,7 @@ |
29 | 29 | contain = result; |
30 | 30 | } |
31 | 31 | this.addedTests.push([code, result, contain]); |
32 | | - this.$table.append('<tr><td>' + mw.util.htmlEscape(code) + '</td><td>' + mw.util.htmlEscape(result) + '<td></td></td><td>?</td></tr>'); |
| 32 | + this.$table.append('<tr><td>' + mw.html.escape(code) + '</td><td>' + mw.html.escape(result) + '<td></td></td><td>?</td></tr>'); |
33 | 33 | }, |
34 | 34 | |
35 | 35 | /* Initialisation */ |
— | — | @@ -88,10 +88,6 @@ |
89 | 89 | 'function (string)'); |
90 | 90 | mw.test.addTest('mw.util.getParamValue( \'action\' )', |
91 | 91 | 'mwutiltest (string)'); |
92 | | - mw.test.addTest('typeof mw.util.htmlEscape', |
93 | | - 'function (string)'); |
94 | | - mw.test.addTest('mw.util.htmlEscape( \'<a href="http://mw.org/?a=b&c=d">link</a>\' )', |
95 | | - '<a href="http://mw.org/?a=b&c=d">link</a> (string)'); |
96 | 92 | mw.test.addTest('mw.util.tooltipAccessKeyRegexp.constructor.name', |
97 | 93 | 'RegExp (string)'); |
98 | 94 | mw.test.addTest('typeof mw.util.updateTooltipAccessKeys', |
Index: trunk/phase3/resources/mediawiki.util/mediawiki.util.js |
— | — | @@ -148,30 +148,6 @@ |
149 | 149 | return null; |
150 | 150 | }, |
151 | 151 | |
152 | | - /** |
153 | | - * Convert special characters to their HTML entities |
154 | | - * |
155 | | - * @param str Text to escape |
156 | | - */ |
157 | | - 'htmlEscape': function( str ) { |
158 | | - return str.replace( /['"<>&]/g, this.htmlEscape_callback ); |
159 | | - }, |
160 | | - |
161 | | - 'htmlEscape_callback': function( str ) { |
162 | | - switch ( str ) { |
163 | | - case "'": |
164 | | - return '''; |
165 | | - case '"': |
166 | | - return '"'; |
167 | | - case '<': |
168 | | - return '<'; |
169 | | - case '>': |
170 | | - return '>'; |
171 | | - case '&': |
172 | | - return '&'; |
173 | | - } |
174 | | - }, |
175 | | - |
176 | 152 | // Access key prefix. |
177 | 153 | // Will be re-defined based on browser/operating system detection in |
178 | 154 | // mw.util.init(). |
Index: trunk/phase3/resources/mediawiki/mediawiki.js |
— | — | @@ -468,16 +468,18 @@ |
469 | 469 | // Add style sheet to document |
470 | 470 | if ( typeof registry[module].style === 'string' && registry[module].style.length ) { |
471 | 471 | $( 'head' ) |
472 | | - .append( '<style type="text/css">' + registry[module].style + '</style>' ); |
| 472 | + .append( mediaWiki.html.element( 'style', |
| 473 | + { type: "text/css" }, |
| 474 | + new mediaWiki.html.Cdata( registry[module].style ) |
| 475 | + ) ); |
473 | 476 | } else if ( typeof registry[module].style === 'object' |
474 | 477 | && !( registry[module].style instanceof Array ) ) |
475 | 478 | { |
476 | 479 | for ( var media in registry[module].style ) { |
477 | | - $( 'head' ).append( |
478 | | - '<style type="text/css" media="' + media + '">' + |
479 | | - registry[module].style[media] + |
480 | | - '</style>' |
481 | | - ); |
| 480 | + $( 'head' ).append( mediaWiki.html.element( 'style', |
| 481 | + { type: 'text/css', media: media }, |
| 482 | + new mediaWiki.html.Cdata( registry[module].style[media] ) |
| 483 | + ) ); |
482 | 484 | } |
483 | 485 | } |
484 | 486 | // Add localizations to message system |
— | — | @@ -652,7 +654,8 @@ |
653 | 655 | requests[r] = sortQuery( requests[r] ); |
654 | 656 | // Build out the HTML |
655 | 657 | var src = mediaWiki.config.get( 'wgLoadScript' ) + '?' + $.param( requests[r] ); |
656 | | - html += '<script type="text/javascript" src="' + src + '"></script>'; |
| 658 | + html += mediaWiki.html.element( 'script', |
| 659 | + { type: 'text/javascript', src: src }, '' ); |
657 | 660 | } |
658 | 661 | return html; |
659 | 662 | } |
— | — | @@ -711,7 +714,7 @@ |
712 | 715 | * calls to this function. |
713 | 716 | */ |
714 | 717 | this.implement = function( module, script, style, localization ) { |
715 | | - // Automaically register module |
| 718 | + // Automatically register module |
716 | 719 | if ( typeof registry[module] === 'undefined' ) { |
717 | 720 | mediaWiki.loader.register( module ); |
718 | 721 | } |
— | — | @@ -825,7 +828,8 @@ |
826 | 829 | .attr( 'href', modules ) ); |
827 | 830 | return true; |
828 | 831 | } else if ( type === 'text/javascript' || typeof type === 'undefined' ) { |
829 | | - var script = '<script type="text/javascript" src="' + modules + '"></script>'; |
| 832 | + var script = mediaWiki.html.element( 'script', |
| 833 | + { type: 'text/javascript', src: modules }, '' ); |
830 | 834 | if ( ready ) { |
831 | 835 | $( 'body' ).append( script ); |
832 | 836 | } else { |
— | — | @@ -900,6 +904,97 @@ |
901 | 905 | $(document).ready( function() { ready = true; } ); |
902 | 906 | } )(); |
903 | 907 | |
| 908 | + /** HTML construction helper functions */ |
| 909 | + this.html = new ( function () { |
| 910 | + function escapeCallback( s ) { |
| 911 | + switch ( s ) { |
| 912 | + case "'": |
| 913 | + return '''; |
| 914 | + case '"': |
| 915 | + return '"'; |
| 916 | + case '<': |
| 917 | + return '<'; |
| 918 | + case '>': |
| 919 | + return '>'; |
| 920 | + case '&': |
| 921 | + return '&'; |
| 922 | + } |
| 923 | + } |
| 924 | + |
| 925 | + /** |
| 926 | + * Escape a string for HTML. Converts special characters to HTML entities. |
| 927 | + * @param s The string to escape |
| 928 | + */ |
| 929 | + this.escape = function( s ) { |
| 930 | + return s.replace( /['"<>&]/g, escapeCallback ); |
| 931 | + }; |
| 932 | + |
| 933 | + /** |
| 934 | + * Wrapper object for raw HTML passed to mediaWiki.html.element(). |
| 935 | + */ |
| 936 | + this.Raw = function( value ) { |
| 937 | + this.value = value; |
| 938 | + }; |
| 939 | + |
| 940 | + /** |
| 941 | + * Wrapper object for CDATA element contents passed to mediaWiki.html.element() |
| 942 | + */ |
| 943 | + this.Cdata = function( value ) { |
| 944 | + this.value = value; |
| 945 | + } |
| 946 | + |
| 947 | + /** |
| 948 | + * Create an HTML element string, with safe escaping. |
| 949 | + * |
| 950 | + * @param name The tag name. |
| 951 | + * @param attrs An object with members mapping element names to values |
| 952 | + * @param contents The contents of the element. May be either: |
| 953 | + * - string: The string is escaped. |
| 954 | + * - null or undefined: The short closing form is used, e.g. <br/>. |
| 955 | + * - this.Raw: The value attribute is included without escaping. |
| 956 | + * - this.Cdata: The value attribute is included, and an exception is |
| 957 | + * thrown if it contains an illegal ETAGO delimiter. |
| 958 | + * See http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.3.2 |
| 959 | + * |
| 960 | + * Example: |
| 961 | + * var h = mediaWiki.html; |
| 962 | + * return h.element( 'div', {}, |
| 963 | + * new h.Raw( h.element( 'img', {src: '<'} ) ) ); |
| 964 | + * Returns <div><img src="<"/></div> |
| 965 | + */ |
| 966 | + this.element = function( name, attrs, contents ) { |
| 967 | + var s = '<' + name; |
| 968 | + for ( attrName in attrs ) { |
| 969 | + s += ' ' + attrName + '="' + this.escape( attrs[attrName] ) + '"'; |
| 970 | + } |
| 971 | + if ( typeof contents == 'undefined' || contents === null ) { |
| 972 | + // Short close tag |
| 973 | + s += '/>'; |
| 974 | + return s; |
| 975 | + } |
| 976 | + // Regular close tag |
| 977 | + s += '>'; |
| 978 | + if (typeof contents === 'string') { |
| 979 | + // Escaped |
| 980 | + s += this.escape( contents ); |
| 981 | + } else if ( contents instanceof this.Raw ) { |
| 982 | + // Raw HTML inclusion |
| 983 | + s += contents.value; |
| 984 | + } else if ( contents instanceof this.Cdata ) { |
| 985 | + // CDATA |
| 986 | + if ( /<\/[a-zA-z]/.test( contents.value ) ) { |
| 987 | + throw new Error( 'mw.html.element: Illegal end tag found in CDATA' ); |
| 988 | + } |
| 989 | + s += contents.value; |
| 990 | + } else { |
| 991 | + throw new Error( 'mw.html.element: Invalid type of contents' ); |
| 992 | + } |
| 993 | + s += '</' + name + '>'; |
| 994 | + return s; |
| 995 | + }; |
| 996 | + } )(); |
| 997 | + |
| 998 | + |
904 | 999 | /* Extension points */ |
905 | 1000 | |
906 | 1001 | this.legacy = {}; |