Opened 6 years ago

Closed 6 years ago

Last modified 4 years ago

#9922 closed Bug (duplicate)

Stylesheet parser should accept CSS classes without elements

Reported by: Olek Nowodziński Owned by:
Priority: Normal Milestone:
Component: General Version: 4.0.2
Keywords: Cc:

Description

Suppose we want our CSS class to be parsed:

.myclass {
    color: red;
}

So the setup for this is as follows:

config.stylesheetParser_skipSelectors = /^body\./i; // need to override this
config.stylesheetParser_validSelectors = /\.\w+/;

This setup works, however the list of styles contains a broken style because .myclass isn't associated with any element.

More info.

Attachments (1)

ssparserPureClass.png (20.3 KB) - added by Olek Nowodziński 6 years ago.

Download all attachments as: .zip

Change History (9)

Changed 6 years ago by Olek Nowodziński

Attachment: ssparserPureClass.png added

comment:1 Changed 6 years ago by Olek Nowodziński

comment:2 Changed 6 years ago by Alfonso Martínez de Lizarrondo

The problem isn't the stylesheet parser but the Style system in CKEditor that doesn't allow to create generic rules for any element.

The parser could try to parse those rules and asign them to span, div, etc... but it will be just a wild guess, the fix must come first in the Style system.

comment:3 Changed 6 years ago by Jakub Ś

Resolution: duplicate
Status: newclosed

We have this ticket already - #5980.

In short it is not possible to use wildcard (not assigned to any element) style because you have to present it somehow in dropdown.

comment:4 Changed 4 years ago by Richie

Hello a.nowodzinski,

after 22 hour I found your post on stackoverflow and this one.
After a mix of patches it work now also for me.


Patched File:

/**
 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

/**
 * @fileOverview stylesheetParser plugin.
 */

( function() {
	// We want to extract only the elements with classes defined in the stylesheets:
	function parseClasses( aRules, skipSelectors, validSelectors ) {
		// aRules are just the different rules in the style sheets
		// We want to merge them and them split them by commas, so we end up with only
		// the selectors
		var s = aRules.join( ' ' );
		// Remove selectors splitting the elements, leave only the class selector (.)
		s = s.replace( /(,|>|\+|~)/g, ' ' );
		// Remove attribute selectors: table[border="0"]
		s = s.replace( /\[[^\]]*/g, '' );
		// Remove Ids: div#main
		s = s.replace( /#[^\s]*/g, '' );
		// Remove pseudo-selectors and pseudo-elements: :hover :nth-child(2n+1) ::before
		s = s.replace( /\:{1,2}[^\s]*/g, '' );

		s = s.replace( /\s+/g, ' ' );

		var aSelectors = s.split( ' ' ),
			aClasses = [];

		for ( var i = 0; i < aSelectors.length; i++ ) {
			var selector = aSelectors[ i ];

			if ( validSelectors.test( selector ) && !skipSelectors.test( selector ) ) {
                                    
				// If we still don't know about this one, add it
				if ( CKEDITOR.tools.indexOf( aClasses, selector ) == -1 )
					aClasses.push( selector );
			}
		}

		return aClasses;
	}

	function LoadStylesCSS( theDoc, skipSelectors, validSelectors ) {
		var styles = [],
			// It will hold all the rules of the applied stylesheets (except those internal to CKEditor)
			aRules = [],
			i;

		for ( i = 0; i < theDoc.styleSheets.length; i++ ) {
			var sheet = theDoc.styleSheets[ i ],
				node = sheet.ownerNode || sheet.owningElement;

			// Skip the internal stylesheets
			if ( node.getAttribute( 'data-cke-temp' ) )
				continue;

			// Exclude stylesheets injected by extensions
			if ( sheet.href && sheet.href.substr( 0, 9 ) == 'chrome://' )
				continue;

			// Bulletproof with x-domain content stylesheet.
			try {
				var sheetRules = sheet.cssRules || sheet.rules;
				for ( var j = 0; j < sheetRules.length; j++ )
					aRules.push( sheetRules[ j ].selectorText );
			} catch ( e ) {}
		}

		var aClasses = parseClasses( aRules, skipSelectors, validSelectors );

		// Add each style to our "Styles" collection.      
      for ( i = 0; i < aClasses.length; i++ ) {
         // Stylesheet parser should accept CSS classes without elements - IcemanX 2015.07.22 - en.cescon.de (http://dev.ckeditor.com/ticket/9922)
          var oElement = aClasses[ i ].split( '.' ),
              element, sClassName;

          if ( !oElement.length ) {
              element = '',
              sClassName = oElement;
          } else {
              element = oElement[ 0 ].toLowerCase(),
              sClassName = oElement[ 1 ];
          }

          styles.push({
              name: element + '.' + sClassName,
              element: !element.length ? 'span' : element,
              attributes: { 'class': sClassName }
          });
      }
console.log(styles);
		return styles;
	}

	// Register a plugin named "stylesheetparser".
	CKEDITOR.plugins.add( 'stylesheetparser', {
		init: function( editor ) {
			// Stylesheet parser is incompatible with filter (#10136).
			editor.filter.disable();

			var cachedDefinitions;

			editor.once( 'stylesSet', function( evt ) {
				// Cancel event and fire it again when styles are ready.
				evt.cancel();

				// Overwrite editor#getStylesSet asap (contentDom is the first moment
				// when editor.document is ready), but before stylescombo reads styles set (priority 5).
				editor.once( 'contentDom', function() {
               window.setTimeout( function() { // Patch stylesheet parser error if file not on cache - IcemanX 2015.07.22 - en.cescon.de (http://dev.ckeditor.com/ticket/8832)
                  editor.getStylesSet( function( definitions ) {
                     // Rules that must be skipped
                     var skipSelectors = editor.config.stylesheetParser_skipSelectors || ( /(^body\.|^\.)/i ),
                        // Rules that are valid
                        validSelectors = editor.config.stylesheetParser_validSelectors || ( /\w+\.\w+/ );

                     cachedDefinitions = definitions.concat( LoadStylesCSS( editor.document.$, skipSelectors, validSelectors ) );

                     editor.getStylesSet = function( callback ) {
                        if ( cachedDefinitions )
                           return callback( cachedDefinitions );
                     };

                     editor.fire( 'stylesSet', { styles: cachedDefinitions } );
                  } );
               }, 1500);
				} );
			}, null, null, 1 );
		}
	} );
} )();


/**
 * A regular expression that defines whether a CSS rule will be
 * skipped by the Stylesheet Parser plugin. A CSS rule matching
 * the regular expression will be ignored and will not be available
 * in the Styles drop-down list.
 *
 *		// Ignore rules for body and caption elements, classes starting with "high", and any class defined for no specific element.
 *		config.stylesheetParser_skipSelectors = /(^body\.|^caption\.|\.high|^\.)/i;
 *
 * @since 3.6
 * @cfg {RegExp} [stylesheetParser_skipSelectors=/(^body\.|^\.)/i]
 * @member CKEDITOR.config
 * @see CKEDITOR.config#stylesheetParser_validSelectors
 */

/**
 * A regular expression that defines which CSS rules will be used
 * by the Stylesheet Parser plugin. A CSS rule matching the regular
 * expression will be available in the Styles drop-down list.
 *
 *		// Only add rules for p and span elements.
 *		config.stylesheetParser_validSelectors = /\^(p|span)\.\w+/;
 *
 * @since 3.6
 * @cfg {RegExp} [stylesheetParser_validSelectors=/\w+\.\w+/]
 * @member CKEDITOR.config
 * @see CKEDITOR.config#stylesheetParser_skipSelectors
 */

But it work only on Chrome on FF the Combo is still empty, do you have an idea?
Also is it possiblke to increase the size of the Combo-List a little bit,
because the user see only
<class="....

comment:5 Changed 4 years ago by Richie

Addition:
if I not overwrite the default styles with:

stylesSet: [],


it work also for FF.

My Config: CKEDITOR.replace('ckeContent', { language: 'en', bodyClass: 'pnlCMS', stylesheetParser_validSelectors: /\.\w+/, stylesheetParser_skipSelectors: /body\./i, allowedContent: true, extraPlugins: 'stylesheetparser', contentsCss: './my.css', on: {

save: function(evt) {

saveContent(); return false;

}

}, });

My plugin.js

/**
 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

/**
 * @fileOverview stylesheetParser plugin.
 */

( function() {
	// We want to extract only the elements with classes defined in the stylesheets:
	function parseClasses( aRules, skipSelectors, validSelectors ) {
		// aRules are just the different rules in the style sheets
		// We want to merge them and them split them by commas, so we end up with only
		// the selectors
		var s = aRules.join( ' ' );
		// Remove selectors splitting the elements, leave only the class selector (.)
		s = s.replace( /(,|>|\+|~)/g, ' ' );
		// Remove attribute selectors: table[border="0"]
		s = s.replace( /\[[^\]]*/g, '' );
		// Remove Ids: div#main
		s = s.replace( /#[^\s]*/g, '' );
		// Remove pseudo-selectors and pseudo-elements: :hover :nth-child(2n+1) ::before
		s = s.replace( /\:{1,2}[^\s]*/g, '' );

		s = s.replace( /\s+/g, ' ' );

		var aSelectors = s.split( ' ' ),
			aClasses = [];

		for ( var i = 0; i < aSelectors.length; i++ ) {
			var selector = aSelectors[ i ];

			if ( validSelectors.test( selector ) && !skipSelectors.test( selector ) ) {
                                    
				// If we still don't know about this one, add it
				if ( CKEDITOR.tools.indexOf( aClasses, selector ) == -1 )
					aClasses.push( selector );
			}
		}

		return aClasses;
	}

	function LoadStylesCSS( theDoc, skipSelectors, validSelectors ) {
		var styles = [],
			// It will hold all the rules of the applied stylesheets (except those internal to CKEditor)
			aRules = [],
			i;

		for ( i = 0; i < theDoc.styleSheets.length; i++ ) {
			var sheet = theDoc.styleSheets[ i ],
				node = sheet.ownerNode || sheet.owningElement;

			// Skip the internal stylesheets
			if ( node.getAttribute( 'data-cke-temp' ) )
				continue;

			// Exclude stylesheets injected by extensions
			if ( sheet.href && sheet.href.substr( 0, 9 ) == 'chrome://' )
				continue;

			// Bulletproof with x-domain content stylesheet.
			try {
				var sheetRules = sheet.cssRules || sheet.rules;
				for ( var j = 0; j < sheetRules.length; j++ )
					aRules.push( sheetRules[ j ].selectorText );
			} catch ( e ) {}
		}

		var aClasses = parseClasses( aRules, skipSelectors, validSelectors );

		// Add each style to our "Styles" collection.      
      for ( i = 0; i < aClasses.length; i++ ) {
         // Stylesheet parser should accept CSS classes without elements - IcemanX 2015.07.22 - en.cescon.de (http://dev.ckeditor.com/ticket/9922)
          var oElement = aClasses[ i ].split( '.' ),
              element, sClassName, name;

          if ( !oElement.length ) {
              element = '',
              sClassName = oElement;     
              name = element + '.' + sClassName;
          } else {
              element = oElement[ 0 ].toLowerCase(),                      
              sClassName = oElement[ 1 ];      
              name = (element?element:'') + '.' + sClassName;
          }

          styles.push({
              name: name,
              element: !element.length ? 'span' : element,
              attributes: { 'class': sClassName }
          });
      }

		return styles;
	}

	// Register a plugin named "stylesheetparser".
	CKEDITOR.plugins.add( 'stylesheetparser', {
		init: function( editor ) {
			// Stylesheet parser is incompatible with filter (#10136).
			editor.filter.disable();

			var cachedDefinitions;
        
         
 	      if(typeof timer_delay_parse_styles != 'undefined')
            window.clearTimeout( timer_delay_parse_styles );
         
         timer_delay_parse_styles = null;
         
			editor.once( 'stylesSet', function( evt ) {
				// Cancel event and fire it again when styles are ready.
				evt.cancel();

				// Overwrite editor#getStylesSet asap (contentDom is the first moment
				// when editor.document is ready), but before stylescombo reads styles set (priority 5).
				editor.once( 'contentDom', function() {
               // Use a delay before parsing the stylesheet to avoid errors with Firefox 4. #7784 	
               timer_delay_parse_styles = window.setTimeout( function() { // Patch stylesheet parser error if file not on cache - IcemanX 2015.07.22 - en.cescon.de (http://dev.ckeditor.com/ticket/8832)
                  editor.getStylesSet( function( definitions ) {
                     // Rules that must be skipped
                     var skipSelectors = editor.config.stylesheetParser_skipSelectors || ( /(^body\.|^\.)/i ),
                        // Rules that are valid
                        validSelectors = editor.config.stylesheetParser_validSelectors || ( /\w+\.\w+/ );

                     cachedDefinitions = definitions.concat( LoadStylesCSS( editor.document.$, skipSelectors, validSelectors ) );
                                          

                     editor.getStylesSet = function( callback ) {
                        if ( cachedDefinitions )
                           return callback( cachedDefinitions );
                     };

                     editor.fire( 'stylesSet', { styles: cachedDefinitions } );                     
                  } );
               }, 1500);
				} );
			}, null, null, 1 );         
		}
	} );
} )();


/**
 * A regular expression that defines whether a CSS rule will be
 * skipped by the Stylesheet Parser plugin. A CSS rule matching
 * the regular expression will be ignored and will not be available
 * in the Styles drop-down list.
 *
 *		// Ignore rules for body and caption elements, classes starting with "high", and any class defined for no specific element.
 *		config.stylesheetParser_skipSelectors = /(^body\.|^caption\.|\.high|^\.)/i;
 *
 * @since 3.6
 * @cfg {RegExp} [stylesheetParser_skipSelectors=/(^body\.|^\.)/i]
 * @member CKEDITOR.config
 * @see CKEDITOR.config#stylesheetParser_validSelectors
 */

/**
 * A regular expression that defines which CSS rules will be used
 * by the Stylesheet Parser plugin. A CSS rule matching the regular
 * expression will be available in the Styles drop-down list.
 *
 *		// Only add rules for p and span elements.
 *		config.stylesheetParser_validSelectors = /\^(p|span)\.\w+/;
 *
 * @since 3.6
 * @cfg {RegExp} [stylesheetParser_validSelectors=/\w+\.\w+/]
 * @member CKEDITOR.config
 * @see CKEDITOR.config#stylesheetParser_skipSelectors
 */

comment:6 Changed 4 years ago by Jakub Ś

@IcemanX could I ask you to submit your pull request to ckeditor-dev ?

Please note that it is required to provide tests to your PR and describe what or which bug does it fix exactly (In this case this is #5980). Please see e.g. ​https://github.com/ckeditor/ckeditor-dev/pull/185 or other accepted pull requests.

Please also read ​http://docs.ckeditor.com/#!/guide/dev_contributing_code


To make dropdown wider, please see http://dev.ckeditor.com/ticket/6162#comment:18.

comment:7 Changed 4 years ago by Richie

My last fixes with "pull request" on Github https://github.com/ckeditor/ckeditor-dev/pull/203

/**
 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

/**
 * @fileOverview stylesheetParser plugin.
 */

( function() {
	// We want to extract only the elements with classes defined in the stylesheets:
	function parseClasses( aRules, skipSelectors, validSelectors ) {
		// aRules are just the different rules in the style sheets
		// We want to merge them and them split them by commas, so we end up with only
		// the selectors
		var s = aRules.join( ' ' );
		// Remove selectors splitting the elements, leave only the class selector (.)
		s = s.replace( /(,|>|\+|~)/g, ' ' );
		// Remove attribute selectors: table[border="0"]
		s = s.replace( /\[[^\]]*/g, '' );
		// Remove Ids: div#main
		s = s.replace( /#[^\s]*/g, '' );
		// Remove pseudo-selectors and pseudo-elements: :hover :nth-child(2n+1) ::before
		s = s.replace( /\:{1,2}[^\s]*/g, '' );

		s = s.replace( /\s+/g, ' ' );

		var aSelectors = s.split( ' ' ),
			aClasses = [];

		for ( var i = 0; i < aSelectors.length; i++ ) {
			var selector = aSelectors[ i ];

			if ( validSelectors.test( selector ) && !skipSelectors.test( selector ) ) {
                                    
				// If we still don't know about this one, add it
				if ( CKEDITOR.tools.indexOf( aClasses, selector ) == -1 )
					aClasses.push( selector );
			}
		}

		return aClasses;
	}

	function LoadStylesCSS( theDoc, skipSelectors, validSelectors ) {
		var styles = [],
			// It will hold all the rules of the applied stylesheets (except those internal to CKEditor)
			aRules = [],
			i;

		for ( i = 0; i < theDoc.styleSheets.length; i++ ) {
			var sheet = theDoc.styleSheets[ i ],
				node = sheet.ownerNode || sheet.owningElement;

			// Skip the internal stylesheets
			if ( node.getAttribute( 'data-cke-temp' ) )
				continue;

			// Exclude stylesheets injected by extensions
			if ( sheet.href && sheet.href.substr( 0, 9 ) == 'chrome://' )
				continue;

			// Bulletproof with x-domain content stylesheet.
			try {
				var sheetRules = sheet.cssRules || sheet.rules;
				for ( var j = 0; j < sheetRules.length; j++ )
					aRules.push( sheetRules[ j ].selectorText );
			} catch ( e ) {}
		}

		var aClasses = parseClasses( aRules, skipSelectors, validSelectors );

		// Add each style to our "Styles" collection.      
      for ( i = 0; i < aClasses.length; i++ ) {
         // Stylesheet parser should accept CSS classes without elements (sample: .table_darkgrey) - IcemanX 2015.07.22 - en.cescon.de (http://dev.ckeditor.com/ticket/9922)
          var oElement = aClasses[ i ].split( '.' ),
              element, sClassName, name;

          if ( !oElement.length ) {
              element = '',
              sClassName = oElement;     
              name = element + '.' + sClassName;
          } else {
              element = oElement[ 0 ].toLowerCase(),                      
              sClassName = oElement[ 1 ];      
              name = (element?element:'') + '.' + sClassName;
          }

          styles.push({
              name: name,
              element: !element.length ? 'span' : element,
              attributes: { 'class': sClassName }
          });
      }

		return styles;
	}

	// Register a plugin named "stylesheetparser".
	CKEDITOR.plugins.add( 'stylesheetparser', {
		init: function( editor ) {
			// Stylesheet parser is incompatible with filter (#10136).
			editor.filter.disable();

			var cachedDefinitions;
        
         
 	      if(typeof timer_delay_parse_styles != 'undefined')
            window.clearTimeout( timer_delay_parse_styles );
         
         timer_delay_parse_styles = null;
         
			editor.once( 'stylesSet', function( evt ) {
				// Cancel event and fire it again when styles are ready.
				evt.cancel();

				// Overwrite editor#getStylesSet asap (contentDom is the first moment
				// when editor.document is ready), but before stylescombo reads styles set (priority 5).
				editor.once( 'contentDom', function() {
               // Use a delay before parsing the stylesheet to avoid errors with Firefox 4 and Safari. #7784 	
               timer_delay_parse_styles = window.setTimeout( function() { // Patch stylesheet parser error if file not on cache - IcemanX 2015.07.22 - en.cescon.de (http://dev.ckeditor.com/ticket/8832)
                  editor.getStylesSet( function( definitions ) {
                     // Rules that must be skipped
                     var skipSelectors = editor.config.stylesheetParser_skipSelectors || ( /^body\./i ), // Stylesheet parser should accept CSS classes without elements (sample: .table_darkgrey) - IcemanX 2015.07.22 - en.cescon.de (http://dev.ckeditor.com/ticket/9922)
                        // Rules that are valid
                        validSelectors = editor.config.stylesheetParser_validSelectors || ( /\.\w+/ ); // Stylesheet parser should accept CSS classes without elements (sample: .table_darkgrey) - IcemanX 2015.07.22 - en.cescon.de (http://dev.ckeditor.com/ticket/9922)

                     cachedDefinitions = definitions.concat( LoadStylesCSS( editor.document.$, skipSelectors, validSelectors ) );
                                         

                     editor.getStylesSet = function( callback ) {
                        if ( cachedDefinitions )
                           return callback( cachedDefinitions );
                     };

                     editor.fire( 'stylesSet', { styles: cachedDefinitions } );                     
                  } );
               }, 1500);
				} );
			}, null, null, 1 );         
		}
	} );
} )();


/**
 * A regular expression that defines whether a CSS rule will be
 * skipped by the Stylesheet Parser plugin. A CSS rule matching
 * the regular expression will be ignored and will not be available
 * in the Styles drop-down list.
 *
 *		// Ignore rules for body and caption elements, classes starting with "high", and any class defined for no specific element.
 *		config.stylesheetParser_skipSelectors = /(^body\.|^caption\.|\.high|^\.)/i;
 *
 * @since 3.6
 * @cfg {RegExp} [stylesheetParser_skipSelectors=/(^body\.|^\.)/i]
 * @member CKEDITOR.config
 * @see CKEDITOR.config#stylesheetParser_validSelectors
 */

/**
 * A regular expression that defines which CSS rules will be used
 * by the Stylesheet Parser plugin. A CSS rule matching the regular
 * expression will be available in the Styles drop-down list.
 *
 *		// Only add rules for p and span elements.
 *		config.stylesheetParser_validSelectors = /\^(p|span)\.\w+/;
 *
 * @since 3.6
 * @cfg {RegExp} [stylesheetParser_validSelectors=/\w+\.\w+/]
 * @member CKEDITOR.config
 * @see CKEDITOR.config#stylesheetParser_skipSelectors
 */

Last edited 4 years ago by Richie (previous) (diff)

comment:8 Changed 4 years ago by Piotrek Koszuliński

Unfortunately, I needed to close the PRs. Please keep comment:2 in mind. As Alfonso correctly commented, the real fix would need to be a feature of the styles system, not a workaround in the stylesheetparser plugin.

Note: See TracTickets for help on using tickets.
© 2003 – 2019 CKSource – Frederico Knabben. All rights reserved. | Terms of use | Privacy policy