/*! * VisualEditor parsing utilities, used when converting HTMLDocuments and strings. * * @copyright See AUTHORS.txt */ /** * Check whether a given DOM element has a block element type. * * @param {HTMLElement|string} element Element or element name * @return {boolean} Element is a block element */ ve.isBlockElement = function ( element ) { const elementName = typeof element === 'string' ? element : element.nodeName; return ve.elementTypes.block.includes( elementName.toLowerCase() ); }; /** * Check whether a given DOM element is a void element (can't have children). * * @param {HTMLElement|string} element Element or element name * @return {boolean} Element is a void element */ ve.isVoidElement = function ( element ) { const elementName = typeof element === 'string' ? element : element.nodeName; return ve.elementTypes.void.includes( elementName.toLowerCase() ); }; ve.elementTypes = { block: [ 'div', 'p', // Tables 'table', 'tbody', 'thead', 'tfoot', 'caption', 'th', 'tr', 'td', // Lists 'ul', 'ol', 'li', 'dl', 'dt', 'dd', // HTML5 heading content 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hgroup', // HTML5 sectioning content 'article', 'aside', 'body', 'nav', 'section', 'footer', 'header', 'figure', 'figcaption', 'fieldset', 'details', 'blockquote', // Other 'hr', 'button', 'canvas', 'center', 'col', 'colgroup', 'embed', 'map', 'object', 'pre', 'progress', 'video' ], void: [ // https://html.spec.whatwg.org/#void-elements 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr' ] }; /** * Match a specific HTML tag that appears once, e.g. 'html' or 'body' * * @param {string} html Document HTML * @param {string} tag Tag name * @return {string[]|null} Regex match, null if not found */ ve.matchTag = function ( html, tag ) { return html.match( new RegExp( '<' + tag + '(>|\\s[^>]*>)' ) ); }; /** * Add a tag to `` using HTML string splicing * * @param {string} docHtml Document HTML * @param {string} tagHtml Tag HTML to be added to `` * @return {string} Document HTML */ ve.addHeadTag = function ( docHtml, tagHtml ) { /** * Splice text after a regex match * * @param {string[]} match Regex match * @param {string} text Text to insert * @return {string} */ function insertAfter( match, text ) { const offset = match.index + match[ 0 ].length; return docHtml.slice( 0, offset ) + text + docHtml.slice( offset ); } const headMatch = ve.matchTag( docHtml, 'head' ); if ( headMatch ) { return insertAfter( headMatch, tagHtml ); } else { const htmlMatch = ve.matchTag( docHtml, 'html' ); if ( htmlMatch ) { // but no return insertAfter( htmlMatch, '' + tagHtml + '' ); } else { // No or return '' + tagHtml + '' + docHtml; } } }; /** * Create an HTMLDocument from an HTML string. * * The html parameter is supposed to be a full HTML document with a doctype and an `` tag. * If you pass a document fragment, it will be wrapped in `…`. * * To create an empty document, pass the empty string. * * If your input is both valid HTML and valid XML, and you need to work around style * normalization bugs in Internet Explorer, use #parseXhtml and #serializeXhtml. * * @param {string} html * @return {HTMLDocument} Document constructed from the HTML string */ ve.createDocumentFromHtml = function ( html ) { if ( html !== '' ) { if ( !ve.matchTag( html, 'body' ) ) { // When the given HTML fragment starts with a or