/** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or http://ckeditor.com/license */ ( function() { var fragmentPrototype = CKEDITOR.htmlParser.fragment.prototype, elementPrototype = CKEDITOR.htmlParser.element.prototype; fragmentPrototype.onlyChild = elementPrototype.onlyChild = function() { var children = this.children, count = children.length, firstChild = ( count == 1 ) && children[ 0 ]; return firstChild || null; }; elementPrototype.removeAnyChildWithName = function( tagName ) { var children = this.children, childs = [], child; for ( var i = 0; i < children.length; i++ ) { child = children[ i ]; if ( !child.name ) continue; if ( child.name == tagName ) { childs.push( child ); children.splice( i--, 1 ); } childs = childs.concat( child.removeAnyChildWithName( tagName ) ); } return childs; }; elementPrototype.getAncestor = function( tagNameRegex ) { var parent = this.parent; while ( parent && !( parent.name && parent.name.match( tagNameRegex ) ) ) parent = parent.parent; return parent; }; fragmentPrototype.firstChild = elementPrototype.firstChild = function( evaluator ) { var child; for ( var i = 0; i < this.children.length; i++ ) { child = this.children[ i ]; if ( evaluator( child ) ) return child; else if ( child.name ) { child = child.firstChild( evaluator ); if ( child ) return child; } } return null; }; // Adding a (set) of styles to the element's 'style' attributes. elementPrototype.addStyle = function( name, value, isPrepend ) { var styleText, addingStyleText = ''; // name/value pair. if ( typeof value == 'string' ) addingStyleText += name + ':' + value + ';'; else { // style literal. if ( typeof name == 'object' ) { for ( var style in name ) { if ( name.hasOwnProperty( style ) ) addingStyleText += style + ':' + name[ style ] + ';'; } } // raw style text form. else { addingStyleText += name; } isPrepend = value; } if ( !this.attributes ) this.attributes = {}; styleText = this.attributes.style || ''; styleText = ( isPrepend ? [ addingStyleText, styleText ] : [ styleText, addingStyleText ] ).join( ';' ); this.attributes.style = styleText.replace( /^;+|;(?=;)/g, '' ); }; // Retrieve a style property value of the element. elementPrototype.getStyle = function( name ) { var styles = this.attributes.style; if ( styles ) { styles = CKEDITOR.tools.parseCssText( styles, 1 ); return styles[ name ]; } }; /** * Return the DTD-valid parent tag names of the specified one. * * @member CKEDITOR.dtd * @param {String} tagName * @returns {Object} */ CKEDITOR.dtd.parentOf = function( tagName ) { var result = {}; for ( var tag in this ) { if ( tag.indexOf( '$' ) == -1 && this[ tag ][ tagName ] ) result[ tag ] = 1; } return result; }; // 1. move consistent list item styles up to list root. // 2. clear out unnecessary list item numbering. function postProcessList( list ) { var children = list.children, child, attrs, count = list.children.length, match, mergeStyle, styleTypeRegexp = /list-style-type:(.*?)(?:;|$)/, stylesFilter = CKEDITOR.plugins.pastefromword.filters.stylesFilter; attrs = list.attributes; if ( styleTypeRegexp.exec( attrs.style ) ) return; for ( var i = 0; i < count; i++ ) { child = children[ i ]; if ( child.attributes.value && Number( child.attributes.value ) == i + 1 ) delete child.attributes.value; match = styleTypeRegexp.exec( child.attributes.style ); if ( match ) { if ( match[ 1 ] == mergeStyle || !mergeStyle ) mergeStyle = match[ 1 ]; else { mergeStyle = null; break; } } } if ( mergeStyle ) { for ( i = 0; i < count; i++ ) { attrs = children[ i ].attributes; attrs.style && ( attrs.style = stylesFilter( [ [ 'list-style-type' ] ] )( attrs.style ) || '' ); } list.addStyle( 'list-style-type', mergeStyle ); } } var emptyMarginRegex = /^(?:\b0[^\s]*\s*){1,4}$/; // e.g. 0px 0pt 0px var romanLiternalPattern = '^m{0,4}(cm|cd|d?c{0,3})(xc|xl|l?x{0,3})(ix|iv|v?i{0,3})$', lowerRomanLiteralRegex = new RegExp( romanLiternalPattern ), upperRomanLiteralRegex = new RegExp( romanLiternalPattern.toUpperCase() ); var orderedPatterns = { 'decimal': /\d+/, 'lower-roman': lowerRomanLiteralRegex, 'upper-roman': upperRomanLiteralRegex, 'lower-alpha': /^[a-z]+$/, 'upper-alpha': /^[A-Z]+$/ }, unorderedPatterns = { 'disc': /[l\u00B7\u2002]/, 'circle': /[\u006F\u00D8]/, 'square': /[\u006E\u25C6]/ }, listMarkerPatterns = { 'ol': orderedPatterns, 'ul': unorderedPatterns }, romans = [ [ 1000, 'M' ], [ 900, 'CM' ], [ 500, 'D' ], [ 400, 'CD' ], [ 100, 'C' ], [ 90, 'XC' ], [ 50, 'L' ], [ 40, 'XL' ], [ 10, 'X' ], [ 9, 'IX' ], [ 5, 'V' ], [ 4, 'IV' ], [ 1, 'I' ] ], alpahbets = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // Convert roman numbering back to decimal. function fromRoman( str ) { str = str.toUpperCase(); var l = romans.length, retVal = 0; for ( var i = 0; i < l; ++i ) { for ( var j = romans[ i ], k = j[ 1 ].length; str.substr( 0, k ) == j[ 1 ]; str = str.substr( k ) ) retVal += j[ 0 ]; } return retVal; } // Convert alphabet numbering back to decimal. function fromAlphabet( str ) { str = str.toUpperCase(); var l = alpahbets.length, retVal = 1; for ( var x = 1; str.length > 0; x *= l ) { retVal += alpahbets.indexOf( str.charAt( str.length - 1 ) ) * x; str = str.substr( 0, str.length - 1 ); } return retVal; } var listBaseIndent = 0, previousListItemMargin = null, previousListId; var plugin = ( CKEDITOR.plugins.pastefromword = { utils: { // Create a which indicate an list item type. createListBulletMarker: function( bullet, bulletText ) { var marker = new CKEDITOR.htmlParser.element( 'cke:listbullet' ); marker.attributes = { 'cke:listsymbol': bullet[ 0 ] }; marker.add( new CKEDITOR.htmlParser.text( bulletText ) ); return marker; }, isListBulletIndicator: function( element ) { var styleText = element.attributes && element.attributes.style; if ( /mso-list\s*:\s*Ignore/i.test( styleText ) ) return true; }, isContainingOnlySpaces: function( element ) { var text; return ( ( text = element.onlyChild() ) && ( /^(:?\s| )+$/ ).test( text.value ) ); }, resolveList: function( element ) { // indicate a list item. var attrs = element.attributes, listMarker; if ( ( listMarker = element.removeAnyChildWithName( 'cke:listbullet' ) ) && listMarker.length && ( listMarker = listMarker[ 0 ] ) ) { element.name = 'cke:li'; if ( attrs.style ) { attrs.style = plugin.filters.stylesFilter( [ // Text-indent is not representing list item level any more. [ 'text-indent' ], [ 'line-height' ], // First attempt is to resolve indent level from on a constant margin increment. [ ( /^margin(:?-left)?$/ ), null, function( margin ) { // Deal with component/short-hand form. var values = margin.split( ' ' ); margin = CKEDITOR.tools.convertToPx( values[ 3 ] || values[ 1 ] || values[ 0 ] ); // Figure out the indent unit by checking the first time of incrementation. if ( !listBaseIndent && previousListItemMargin !== null && margin > previousListItemMargin ) listBaseIndent = margin - previousListItemMargin; previousListItemMargin = margin; attrs[ 'cke:indent' ] = listBaseIndent && ( Math.ceil( margin / listBaseIndent ) + 1 ) || 1; } ], // The best situation: "mso-list:l0 level1 lfo2" tells the belonged list root, list item indentation, etc. [ ( /^mso-list$/ ), null, function( val ) { val = val.split( ' ' ); // Ignore values like "mso-list:Ignore". (FF #11976) if ( val.length < 2 ) { return; } var listId = Number( val[ 0 ].match( /\d+/ ) ), indent = Number( val[ 1 ].match( /\d+/ ) ); if ( indent == 1 ) { listId !== previousListId && ( attrs[ 'cke:reset' ] = 1 ); previousListId = listId; } attrs[ 'cke:indent' ] = indent; } ] ] )( attrs.style, element ) || ''; } // First level list item might be presented without a margin. // In case all above doesn't apply. if ( !attrs[ 'cke:indent' ] ) { previousListItemMargin = 0; attrs[ 'cke:indent' ] = 1; } // Inherit attributes from bullet. CKEDITOR.tools.extend( attrs, listMarker.attributes ); return true; } // Current list disconnected. else { previousListId = previousListItemMargin = listBaseIndent = null; } return false; }, // Providing a shorthand style then retrieve one or more style component values. getStyleComponents: ( function() { var calculator = CKEDITOR.dom.element.createFromHtml( '
', CKEDITOR.document ); CKEDITOR.document.getBody().append( calculator ); return function( name, styleValue, fetchList ) { calculator.setStyle( name, styleValue ); var styles = {}, count = fetchList.length; for ( var i = 0; i < count; i++ ) styles[ fetchList[ i ] ] = calculator.getStyle( fetchList[ i ] ); return styles; }; } )(), listDtdParents: CKEDITOR.dtd.parentOf( 'ol' ) }, filters: { // Transform a normal list into flat list items only presentation. // E.g.