2014-03-26 23:59:04 +00:00
|
|
|
/*
|
|
|
|
* This file is part of the MediaWiki extension MediaViewer.
|
|
|
|
*
|
|
|
|
* MediaViewer is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* MediaViewer is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with MediaViewer. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2018-11-12 16:33:24 +00:00
|
|
|
( function () {
|
2014-03-26 23:59:04 +00:00
|
|
|
var HUP, cache;
|
|
|
|
|
|
|
|
/**
|
2016-07-18 13:49:27 +00:00
|
|
|
* Shared cache between HtmlUtils instances to store the results of expensive text operations.
|
|
|
|
*
|
2014-03-26 23:59:04 +00:00
|
|
|
* @member mw.mmv.HtmlUtils
|
|
|
|
* @private
|
|
|
|
* @static
|
2014-12-30 22:00:37 +00:00
|
|
|
* @type {{text: Object.<string, string>, textWithLinks: Object.<string, string>, textWithTags: Object.<string, string>}}
|
2014-03-26 23:59:04 +00:00
|
|
|
*/
|
|
|
|
cache = {
|
|
|
|
text: {},
|
2014-12-30 22:00:37 +00:00
|
|
|
textWithLinks: {},
|
|
|
|
textWithTags: {}
|
2014-03-26 23:59:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper class that does various HTML-to-text transformations
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
|
|
|
* @class mw.mmv.HtmlUtils
|
2014-03-26 23:59:04 +00:00
|
|
|
* @constructor
|
|
|
|
*/
|
|
|
|
function HtmlUtils() {}
|
|
|
|
HUP = HtmlUtils.prototype;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a jQuery node which contains the given HTML (wrapped into a `<div>` - this is
|
|
|
|
* necessary since an arbitrary HTML string might not have a jQuery representation).
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-03-26 23:59:04 +00:00
|
|
|
* @param {string|HTMLElement|jQuery} html
|
|
|
|
* @return {jQuery}
|
|
|
|
*/
|
|
|
|
HUP.wrapAndJquerify = function ( html ) {
|
2014-04-01 07:56:18 +00:00
|
|
|
if ( this.isJQueryOrHTMLElement( html ) ) {
|
2014-03-26 23:59:04 +00:00
|
|
|
return $( '<div>' ).append( $( html ).clone() );
|
|
|
|
} else if ( typeof html === 'string' ) {
|
|
|
|
return $( '<div>' + html + '</div>' );
|
|
|
|
} else {
|
|
|
|
mw.log.warn( 'wrapAndJquerify: unknown type', html );
|
2017-05-03 09:15:05 +00:00
|
|
|
throw new Error( 'wrapAndJquerify: unknown type' );
|
2014-03-26 23:59:04 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-04-01 07:56:18 +00:00
|
|
|
/**
|
|
|
|
* Returns true of the object is a jQuery object or an HTMLElement, false otherwise
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-04-01 07:56:18 +00:00
|
|
|
* @param {string|HTMLElement|jQuery} html
|
2016-07-18 13:49:27 +00:00
|
|
|
* @return {boolean}
|
2014-04-01 07:56:18 +00:00
|
|
|
*/
|
|
|
|
HUP.isJQueryOrHTMLElement = function ( html ) {
|
|
|
|
if ( html instanceof jQuery ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( window.HTMLElement ) {
|
|
|
|
if ( html instanceof HTMLElement ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2014-03-26 23:59:04 +00:00
|
|
|
/**
|
2018-12-07 19:29:25 +00:00
|
|
|
* Filters display:none and <style></style> children of a node.
|
2014-03-26 23:59:04 +00:00
|
|
|
* The root element is never filtered, and generally ignored (i.e. whether the root element is
|
|
|
|
* visible won't affect the filtering).
|
|
|
|
* Works in place.
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-03-26 23:59:04 +00:00
|
|
|
* @param {jQuery} $jq
|
|
|
|
*/
|
|
|
|
HUP.filterInvisible = function ( $jq ) {
|
|
|
|
// We are not using :visible because
|
|
|
|
// 1) it would require appending $jq to the document which makes things complicated;
|
|
|
|
// 2) the main difference is that it looks for CSS rules hiding the element;
|
|
|
|
// since this function is intended to be used on html originating from a different
|
|
|
|
// document, possibly a different site, that would probably have unexpected results.
|
|
|
|
$jq
|
|
|
|
.find( '[style]' )
|
2015-01-23 12:48:27 +00:00
|
|
|
.filter( function () { return this.style.display === 'none'; } )
|
2014-03-26 23:59:04 +00:00
|
|
|
.remove();
|
2018-12-07 19:29:25 +00:00
|
|
|
|
|
|
|
// TemplateStyles can generate inline style tags
|
|
|
|
$jq
|
|
|
|
.find( 'style' )
|
|
|
|
.remove();
|
2014-03-26 23:59:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Discards all nodes which do not match the whitelist,
|
|
|
|
* but keeps the text and whitelisted nodes inside them.
|
|
|
|
* Works in-place.
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-03-26 23:59:04 +00:00
|
|
|
* @param {jQuery} $el
|
|
|
|
* @param {string} whitelist a jQuery selector string such as 'a, span, br'
|
|
|
|
*/
|
|
|
|
HUP.whitelistHtml = function ( $el, whitelist ) {
|
2015-12-24 15:01:56 +00:00
|
|
|
var child, $prev,
|
2016-07-18 13:49:27 +00:00
|
|
|
$child = $el.children().first();
|
2014-03-26 23:59:04 +00:00
|
|
|
|
|
|
|
while ( $child && $child.length ) {
|
|
|
|
child = $child.get( 0 );
|
|
|
|
|
|
|
|
if ( child.nodeType !== child.ELEMENT_NODE ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.whitelistHtml( $child, whitelist );
|
|
|
|
|
|
|
|
if ( !$child.is( whitelist ) ) {
|
|
|
|
$prev = $child.prev();
|
|
|
|
$child.replaceWith( $child.contents() );
|
|
|
|
} else {
|
|
|
|
$prev = $child;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $prev && $prev.length === 1 ) {
|
|
|
|
$child = $prev.next();
|
|
|
|
} else {
|
|
|
|
$child = $el.children().first();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a whitespace to block elements. This is useful if you want to convert the contents
|
|
|
|
* to text and don't want words that are visually separate (e.g. table cells) to be fused.
|
|
|
|
* Works in-place.
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-03-26 23:59:04 +00:00
|
|
|
* @param {jQuery} $el
|
|
|
|
*/
|
|
|
|
HUP.appendWhitespaceToBlockElements = function ( $el ) {
|
|
|
|
// the list of what elements to add whitespace to is somewhat ad-hoc (not all of these
|
|
|
|
// are technically block-level elements, and a lot of block-level elements are missing)
|
|
|
|
// but will hopefully cover the common cases where text is fused together.
|
|
|
|
$el
|
|
|
|
.find( 'blockquote, dd, dl, dt, li, td' )
|
|
|
|
.before( ' ' )
|
|
|
|
.after( ' ' );
|
|
|
|
$el
|
|
|
|
.find( 'br, tr, p' )
|
|
|
|
.before( '\n' )
|
|
|
|
.after( '\n' );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the HTML code for a jQuery element (only the first one if passed a set of elements).
|
|
|
|
* Unlike .html(), this includes HTML code for the outermost element; compare
|
|
|
|
* - `$('<div>').html() // ''`
|
|
|
|
* - `mw.mmv.HtmlUtils.jqueryToHtml( $('<div>') ) // '<div></div>'`
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-03-26 23:59:04 +00:00
|
|
|
* @param {jQuery} $el
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
HUP.jqueryToHtml = function ( $el ) {
|
|
|
|
// There are two possible implementations for this:
|
|
|
|
// 1) load innto a wrapper element and get its innerHTML;
|
|
|
|
// 2) use outerHTML.
|
2014-12-02 22:49:29 +00:00
|
|
|
// We go with 1) because it handles the case when a jQuery object contains something
|
2014-03-26 23:59:04 +00:00
|
|
|
// that is not an element (this can happen with e.g. $x.children() which returns text
|
|
|
|
// nodes as well).
|
|
|
|
return $( '<div>' ).append( $el ).html();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleans up superfluous whitespace.
|
|
|
|
* Given that the results will be displayed in a HTML environment, this doesn't have any real
|
|
|
|
* effect. It is mostly there to make testing easier.
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
|
|
|
* @protected
|
2014-03-26 23:59:04 +00:00
|
|
|
* @param {string} html a HTML (or plaintext) string
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
HUP.mergeWhitespace = function ( html ) {
|
|
|
|
html = html.replace( /^\s+|\s+$/g, '' );
|
|
|
|
html = html.replace( /\s*\n\s*/g, '\n' );
|
|
|
|
html = html.replace( / {2,}/g, ' ' );
|
|
|
|
return html;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the text content of a html string.
|
|
|
|
* Tries to give an approximation of what would be visible if the HTML would be displayed.
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-03-26 23:59:04 +00:00
|
|
|
* @param {string} html
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
HUP.htmlToText = function ( html ) {
|
|
|
|
var $html;
|
2016-07-18 13:49:27 +00:00
|
|
|
if ( !cache.text[ html ] ) {
|
2014-03-26 23:59:04 +00:00
|
|
|
$html = this.wrapAndJquerify( html );
|
|
|
|
this.filterInvisible( $html );
|
|
|
|
this.appendWhitespaceToBlockElements( $html );
|
2016-07-18 13:49:27 +00:00
|
|
|
cache.text[ html ] = this.mergeWhitespace( $html.text() );
|
2014-03-26 23:59:04 +00:00
|
|
|
}
|
2016-07-18 13:49:27 +00:00
|
|
|
return cache.text[ html ];
|
2014-03-26 23:59:04 +00:00
|
|
|
};
|
|
|
|
|
2014-12-30 22:00:37 +00:00
|
|
|
/**
|
|
|
|
* Returns the text content of a html string, with the `<a>`, `<i>`, `<b>` tags left intact.
|
|
|
|
* Tries to give an approximation of what would be visible if the HTML would be displayed.
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-12-30 22:00:37 +00:00
|
|
|
* @param {string} html
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
HUP.htmlToTextWithTags = function ( html ) {
|
|
|
|
var $html;
|
2016-07-18 13:49:27 +00:00
|
|
|
if ( !cache.textWithTags[ html ] ) {
|
2014-12-30 22:00:37 +00:00
|
|
|
$html = this.wrapAndJquerify( html );
|
|
|
|
this.filterInvisible( $html );
|
|
|
|
this.appendWhitespaceToBlockElements( $html );
|
2018-02-09 09:29:47 +00:00
|
|
|
this.whitelistHtml( $html, 'a, span, i, b, sup, sub' );
|
2016-07-18 13:49:27 +00:00
|
|
|
cache.textWithTags[ html ] = this.mergeWhitespace( $html.html() );
|
2014-12-30 22:00:37 +00:00
|
|
|
}
|
2016-07-18 13:49:27 +00:00
|
|
|
return cache.textWithTags[ html ];
|
2014-12-30 22:00:37 +00:00
|
|
|
};
|
|
|
|
|
2014-03-26 23:59:04 +00:00
|
|
|
/**
|
|
|
|
* Returns the text content of a html string, with the `<a>` tags left intact.
|
|
|
|
* Tries to give an approximation of what would be visible if the HTML would be displayed.
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2014-03-26 23:59:04 +00:00
|
|
|
* @param {string} html
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
HUP.htmlToTextWithLinks = function ( html ) {
|
|
|
|
var $html;
|
2016-07-18 13:49:27 +00:00
|
|
|
if ( !cache.textWithLinks[ html ] ) {
|
2014-03-26 23:59:04 +00:00
|
|
|
$html = this.wrapAndJquerify( html );
|
|
|
|
this.filterInvisible( $html );
|
|
|
|
this.appendWhitespaceToBlockElements( $html );
|
2014-04-10 22:58:19 +00:00
|
|
|
this.whitelistHtml( $html, 'a, span' );
|
2016-07-18 13:49:27 +00:00
|
|
|
cache.textWithLinks[ html ] = this.mergeWhitespace( $html.html() );
|
2014-03-26 23:59:04 +00:00
|
|
|
}
|
2016-07-18 13:49:27 +00:00
|
|
|
return cache.textWithLinks[ html ];
|
2014-03-26 23:59:04 +00:00
|
|
|
};
|
|
|
|
|
2016-06-19 12:11:30 +00:00
|
|
|
/**
|
|
|
|
* Generates HTML code for a link.
|
2016-07-18 13:49:27 +00:00
|
|
|
*
|
2016-06-19 12:11:30 +00:00
|
|
|
* @param {string} text Link text (plain text; will be sanitized)
|
|
|
|
* @param {Object} props Link attributes (should at a minumum include href; will be sanitized)
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
HUP.makeLinkText = function ( text, props ) {
|
|
|
|
var key;
|
|
|
|
for ( key in props ) {
|
|
|
|
if ( !props.hasOwnProperty( key ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-07-18 13:49:27 +00:00
|
|
|
props[ key ] = this.htmlToText( props[ key ] );
|
2016-06-19 12:11:30 +00:00
|
|
|
}
|
|
|
|
return this.jqueryToHtml( $( '<a>' ).prop( props ).text( text ) );
|
|
|
|
};
|
|
|
|
|
2014-03-26 23:59:04 +00:00
|
|
|
mw.mmv.HtmlUtils = HtmlUtils;
|
2018-11-12 16:33:24 +00:00
|
|
|
}() );
|