Ask Ben: Getting CSS Class Names From My CSS Data

Posted March 16, 2007 at 8:25 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

Last week, I was asked about getting CSS class names from a CSS file. This data was going to be used to create an XML file that populated the style menu of a rich text editor. I am not sure how to go from CSS class names to XML file (how do you automate the translation of CSS classes to "human-friendly" style options??). I can't figure that one out, but getting the CSS class names is relatively easy.

If you stop and think about how a CSS file is put together, you have your class names followed by rules. You can have multiple class names for the same rule such as "ul, ol { font-size: 11px ; }" where both UL and OL share the same rule. If we then think about the rule "{...}" as a single item, you can look as CSS data as dual-delimited list. The first delimiter is the rule construct - this separates the class names. Then, within each of those sub lists, each class name is delimited by a comma.

Using that, we can clean the CSS data and treat it like a simple list:

  • <!---
  • Store CSS for testing. This data could be built in
  • either by pulling it into a CFSaveContent or perhaps
  • a CFFile file read.
  • --->
  • <cfsavecontent variable="strCSSData">
  •  
  • /* This is my site header */
  • #header {
  • background-color: gold ;
  • }
  •  
  • /*
  • These are the general style for my site. These
  • will be applied everywhere and should have a
  • standard look and feel.
  • */
  • ol,
  • ul,
  • p,
  • form,
  • h1, h2, h3, h4, h5 {
  • font-family: verdana ;
  • font-size: 11px ;
  • margin-bottom: 14px ;
  • margin-top: 0px ;
  • }
  •  
  • p {
  • line-height: 16px ;
  • }
  •  
  • ol,
  • ul {
  • line-height: 15px ;
  • }
  •  
  • ul li,
  • ol li {
  • margin-bottom: 3px ;
  • }
  •  
  • form {
  • margin: 0px 0px 0px 0px ;
  • }
  •  
  • form {
  • padding: 10px 10px 10px 10px ;
  • }
  •  
  • #footer {
  • font-size: 10px ;
  • }
  •  
  • #footer p {
  • margin-bottom: 0px ;
  • }
  •  
  • #footer p span.note,
  • #footer p span.date {
  • font-style: italic ;
  • }
  •  
  • </cfsavecontent>
  •  
  •  
  • <!---
  • Remove all line breaks. We are going to be doing some
  • regular expressions and stripping out line breaks will
  • make things slightly less complicated.
  • --->
  • <cfset strCSSData = strCSSData.ReplaceAll( "[\r\n]+", " " ) />
  •  
  • <!---
  • Strip out the CSS comments. These hold no value for us
  • when we are getting the classes.
  • --->
  • <cfset strCSSData = strCSSData.ReplaceAll( "/\*.*?\*/", " " ) />
  •  
  • <!---
  • Strip out anything between brackets and replace it with the
  • pipe. Remember, we don't care about what the rules are, we
  • just want to get the names of all the classes.
  • --->
  • <cfset strCSSData = strCSSData.ReplaceAll( "\{[^\}]*\}", "|" ) />
  •  
  •  
  • <!---
  • ASSERT: At this point, we have stripped out all the rule
  • data, comments, line breaks, and brackets. Right now, the
  • CSS data has become what is essentially a dual-delimited
  • list. The "|" separates rules. The "," sepparated
  • class names.
  • --->
  •  
  •  
  • <!--- Create an array to hold all of the class names. --->
  • <cfset arrClasses = ArrayNew( 1 ) />
  •  
  •  
  • <!---
  • Loop over the rules. Remember, each rule is now sepparated
  • by a pipe (when we stripped out the {..} stuff), so we
  • can loop over the rules ase a pipe-delimited list.
  • --->
  • <cfloop
  • index="strRule"
  • list="#strCSSData#"
  • delimiters="|">
  •  
  • <!---
  • Each rule may have one or more class sepparated by
  • commas such as (p, ul, ol). Each of these should be
  • its own class. Add each of these itesm to the master
  • class array.
  • --->
  •  
  • <!--- Trim the rule. --->
  • <cfset strRule = strRule.Trim() />
  •  
  • <!---
  • Check to see if we still have a length (name(s)) of
  • CSS classes.
  • --->
  • <cfif Len( strRule )>
  •  
  • <!---
  • Using the Array's underlying Java method, AddAll(),
  • we can add the entire array of class names
  • (created by splitting the comma delimited list) to
  • the master array.
  • --->
  • <cfset arrClasses.AddAll(
  • ListToArray( strRule, "," )
  • ) />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!---
  • ASSERT: At this point, we now have an array of the CSS class
  • names. Now, due to the cascading nature of CSS, we might
  • have duplicate class names in the master class array. In
  • order to get the unique names for all classes, we can use
  • the indexing power of the ColdFusion struct to eliminate
  • the duplicates.
  •  
  • NOTE: Struct do NOT have any sense of ordering. If order of
  • classes names is important to you, you would have to do
  • a more involved loop.
  • --->
  •  
  •  
  • <!--- Create a struct to hold unique class names. --->
  • <cfset objUniqueClasses = StructNew() />
  •  
  • <!---
  • Loop over the master class array and add each class name to
  • the struct allowing like-names to overwrite each other.
  • --->
  • <cfloop
  • index="intClass"
  • from="1"
  • to="#ArrayLen( arrClasses )#"
  • step="1">
  •  
  • <!---
  • Add class name. Just set value to true - we will never
  • use this value again.
  • --->
  • <cfset objUniqueClasses[ arrClasses[ intClass ] ] = true />
  •  
  • </cfloop>
  •  
  •  
  • <!--- Output the results. --->
  • <cfoutput>
  •  
  • All classes:
  •  
  • <cfdump
  • var="#arrClasses#"
  • label="All CSS Classes"
  • />
  •  
  • Unique classes:
  •  
  • <cfdump
  • var="#StructKeyArray( objUniqueClasses )#"
  • label="Unique CSS Classes"
  • />
  •  
  • </cfoutput>

Running the above code gets us the two CFDumps for the master class array:


 
 
 

 
All CSS Class Names  
 
 
 

... and the unique class array:


 
 
 

 
Unique CSS Class Names  
 
 
 

That's all there is to it. Frankly, that was the easy step. How do you take that list and create a meaningful XML file? That I cannot answer.



Reader Comments

Mar 16, 2007 at 8:47 AM // reply »
21 Comments

In your example, you technically do not have any class names. In CSS class names begin with a '.'

.header will style elements with an attribute of class equal to 'header'

#header will style elements with an attribute of id equal to 'header'

In your resulting array, there really were no classes shown. To use this list in a Rich Text Editor, you would probably want only the classes and not ids, or tags since the text editor would most likely use class="className" for an element.


Mar 16, 2007 at 9:24 AM // reply »
4 Comments

What Scott Said ;)

What you are getting are the selectors basically what rules are in the CSS file.


Mar 16, 2007 at 9:51 AM // reply »
2 Comments

The following code might do the trick as well:

<!--- Create an array to hold the CSS classes --->
<cfset aCSS = ArrayNew(1)>
<cfset start=1>
<!--- Find CSS class occurances (.something) --->
<cfloop condition="start lt len(strCSSData)">
<cfset temp = reFind("\.[-]?[_a-zA-Z][_a-zA-Z0-9-]*|[^\0-\177]*\\[0-9a-f]{1,6}(\r\n[ \n\r\t\f])?|\\[^\n\r\f0-9a-f]*",strCSSData,start,true)>
<cfif temp.pos[1]>
<cfset arrayappend(aCSS,mid(strCSSData,temp.pos[1],temp.len[1]))>
<cfset start = temp.pos[1]+temp.len[1]>
<cfelse>
<cfbreak>
</cfif>
</cfloop>
<cfdump var="#aCSS#">


Mar 16, 2007 at 10:24 AM // reply »
1 Comments

Big post. Please make it available full after clicking more and leave several lines for preview


Mar 16, 2007 at 10:31 AM // reply »
2 Comments

Oh, by the way .... I am deeply offended by this entry... it does not contain any semi-nude photos whatsoever !!!

reference:http://www.bennadel.com/blog/574-How-Do-I-Offend-You-Please-Let-Me-Know.htm


Mar 16, 2007 at 11:34 AM // reply »
11,241 Comments

Yeah, I call them class names, but to be honest, I wasn't really thinking about what actually was a class name. I was thinking in my head "selector" but calling it class name.

If you think about it, class names are generally not enough for the ultimate goal here. Many "selectors" don't even have class names but should be totally valid markup (think about all the H tags).... this is part of why I would not be able to figure out the next step: translating this list to some sort of usable XML file.


Mar 16, 2007 at 1:22 PM // reply »
6 Comments

Ben -
If you have a CSS file that defined styles for tag based selectors, you wouldn't want to use those selectors as style options in a drop down for a rich text editor. Rather, you'd have options in the RTE for h1,h2,h3 (or large header, medium header, small header), which the user could then use. The user is going to define the formatting for their content (whether they want lists, or paragraphs, or whatever). The fact that you have style definitions for these doesn't matter (IMHO).

However, if you have custom class definitions -- say "caption" (small, bold letters), or "important" (large red letters) -- these would make sense to have available in a drop down pulled from the CSS file. In this case, you could actually use a comment string to give a "user-friendly" name for the class which would be used to populate the drop down.


Mar 16, 2007 at 1:34 PM // reply »
11,241 Comments

@Toby,

While I agree with in the most part, I think that letting the user choose H1/2/3/etc. might not be the best idea. As much as they are "standard", they are still left up to interpretation. So, your rich editor might have a style called "Page Title" or "Section Title" or "Item Title" or "Sub Title" or something along those lines. These might just put in the appropriate H tags. By letting the user arbitrarily choose what H tag they think is most appropriate, you might get screwy results. However, if you only allow them to choose "descriptive" titles (ie. Section Titlte), it might help to standardize (but of course, nothing is for sure).


Mar 16, 2007 at 2:29 PM // reply »
111 Comments

Wow, lots of food for thought!!! I was the guy who asked Ben if he had any clue how to go about doing this and who was more than a little surprised to get a complete working app about 20 minutes later (thanks Ben!) :->

Goal is indeed to get a drop down list of unique styles for a WYSIWYG editor, and ideal would be something like "H1,H2,H3,p,MyStyle1,MyStyle2,Mystyle3" which I can then drop into the trivial FCK Editor XML file which doesn't know anything about the properties of each class, but just allows you to tell it what the class names are so people can select them from a list.


Mar 16, 2007 at 2:30 PM // reply »
11,241 Comments

I have never used FCK editor, so I leave advice up to the rest of you :)


Mar 16, 2007 at 2:35 PM // reply »
4 Comments

The problem with trying to allow your users to style their content in this way is that items get misapplied.

Its much better and easier for an end user not to have to worry about styles and simply apply structural HTML rather than CSS.

Not sure how you can do it with a WYSIWYG editor, but its a lot easier to have simple HTML for your users to create and leave the complexity of the styling to the style sheet itself.

One of my sessions at CFUNITED this year (CSS: Back to the Basics) is going to be covering Structural HTML and how to select it using advanced CSS Selectors - strangely enough ;)


Mar 16, 2007 at 3:48 PM // reply »
21 Comments

Another potential issue of allowing a user to use any/all selectors in a CSS file is the fact that, depending on the editor, it may assign class="p" to an element when chosen from the list of styles, yet if the style is only p{CSS stuff;} then the style would not be applied.

Peter, if FCK applies the styles as class="className", and the class is chosen from the drop down, then you would need to make sure only cvalid class names appear in the drop down, and not every selector.


Mar 16, 2007 at 5:54 PM // reply »
45 Comments

It might also be wise to have your CSS styles that will typically be applied by an end user in a separate CSS file, rather than in your layout CSS file(s) or others. You would then be running your regex on a much smaller file and you would have the added benefit of always knowing where to go to specify your user selectable styles too :)

Another thing to consider is that typically your css for a content area on a site might look like this:

#content p.extract {}
#content blockquote.feature {}
#content div.col1 {}

etc... or instead of using the id #content it might just be a class name .content. However, when you go to apply this to FCK editor the id/class will need to be stripped from the start of the selector - not terribly difficult, just make sure you can configure which id's/classes need to be stripped, so that you don't strip too much out of a rule, as in:

#content table.alt td.odd {}

You don't want to lose the "table.alt" part out of that selector, only the id (#content) part!

Depending on the project and the size of your CSS (this is gonna hurt), it's almost just easier to keep your CSS well organised and hand edit it. I realise that's not the point of this post, and Peter will kill me for even suggesting that (haha!), but sometimes (for simple projects) it's the truth :P

For a larger project though, or one where your users are competent enough and/or have the ability to edit the CSS themselves (for e.g. maybe in a FarCry site where CSS can be a content object, editable by users), a generated solution would be excellent.


Mar 16, 2007 at 6:04 PM // reply »
45 Comments

Err, actually I should have said there is kinda 2 steps to applying styles to FCK editor. One is actually providing the CSS for the editor to apply when the user is editing the content, which is kinda what I was on about above (in regards to stripping the id/class from some selectors so that FCK editor can apply the styles - obviously you need to leave the stuff in the curly braces alone*), but the second part of the process is the XML, which I didn't realise (until now) was such a pain to edit... I am totally going to need a generated solution for this for use with a FarCry site of mine :P


Jan 2, 2009 at 8:15 PM // reply »
42 Comments

Hey Ben,

I know that it has been a while since this thread was discussed, but I just stumbled upon it after looking for some sort of regex to parse css (I am terrible at regex, no practice). I created a UDF which might make life easier for some people. It takes the rules and returns structures. That way people can loop over the structs and create xml, or whatever.

Here is is-

<!---
Store CSS for testing. This data could be built in
either by pulling it into a CFSaveContent or perhaps
a CFFile file read.
--->
<cfsavecontent variable="strCSSData">

/* This is my site header */
#header {
background-color: gold ;
}

/*
These are the general style for my site. These
will be applied everywhere and should have a
standard look and feel.
*/
ol,
ul,
p,
form,
h1, h2, h3, h4, h5 {
font-family: verdana ;
font-size: 11px ;
margin-bottom: 14px ;
margin-top: 0px ;
}

p {
line-height: 16px ;
}

ol,
ul {
line-height: 15px ;
}

ul li,
ol li {
margin-bottom: 3px ;
}

form {
margin: 0px 0px 0px 0px ;
}

form {
padding: 10px 10px 10px 10px ;
}

#footer {
font-size: 10px ;
}

#footer p {
margin-bottom: 0px ;
}

#footer p span.note,
#footer p span.date {
font-style: italic ;
}

</cfsavecontent>

<cffunction name="cssToStruct" access="public" returntype="any">
<cfargument name="css_data" type="string" required="yes">

<!---Create the local scope--->
<cfset var local = {}>

<!---This struct will hold all of the rules--->
<cfset LOCAL.cssRules = {}>

<!---
Remove all line breaks. We are going to be doing some
regular expressions and stripping out line breaks will
make things slightly less complicated.
--->
<cfset LOCAL.strCSSData = reReplace(arguments.css_data,"[\r\n]+", " ","all") />

<!---
Strip out the CSS comments. These hold no value for us
when we are getting the classes.
--->
<cfset LOCAL.strCSSData = reReplace(LOCAL.strCSSData,"/\*.*?\*/", " ", "all" ) />

<!--- Create an array to hold all of the class names. --->
<cfset LOCAL.arrClasses = ArrayNew( 1 ) />


<!---
Loop over the rules. Remember, each rule is now sepparated
by a pipe (when we stripped out the {..} stuff), so we
can loop over the rules ase a pipe-delimited list.
--->
<cfloop
index="LOCAL.strRule"
list="#LOCAL.strCSSData#"
delimiters="}">

<!---
Check to see if we still have a length (name(s)) of
CSS classes.
--->
<cfif Len( trim(LOCAL.strRule) )>
<!---Add the item to the array--->
<cfset arrayAppend(LOCAL.arrClasses,LOCAL.strRule) />

</cfif>

</cfloop>

<cfloop array="#LOCAL.arrClasses#" index="LOCAL.each_class">
<cfset LOCAL.cssRules[listFirst(LOCAL.each_class,"{")] = {}>
<cfloop list="#listLast(LOCAL.each_class,"{")#" delimiters=";" index="LOCAL.each_rule">
<cfset LOCAL.cssRules[listFirst(LOCAL.each_class,"{")][trim(listFirst(LOCAL.each_rule,":"))] = trim(listLast(LOCAL.each_rule,":"))>
</cfloop>
</cfloop>

<cfreturn LOCAL.cssRules>
</cffunction>

<cfdump var="#cssToStruct(strCSSData)#">



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 22, 2013 at 7:52 AM
Nested Views, Routing, And Deep Linking With AngularJS
Hi, Just a quick thank you. As it happens, for my own purposes, the pending ui-router work being done in native angular is likely the one I'll adopt, but your exploration, code and documentation of ... read »
May 22, 2013 at 4:43 AM
How Do You Use The ColdFusion CFParam Tag?
'<cfparam>' or 'isDefined()and <cfset>' performs the same task.Is there any difference? ... read »
May 21, 2013 at 7:46 PM
Using Plupload For Drag & Drop File Uploads In ColdFusion
No luck. At least I have uncovered the cause, URLScan 3.1. Here is what I see in the IIS log when a file is over 30mb. 2013-05-21 23:29:05 10.105.45.128 GET /plupload/assets/jquery/jquery-1.8. ... read »
May 21, 2013 at 6:12 PM
Using Plupload For Drag & Drop File Uploads In ColdFusion
Ben, I did not see you after Pete Freitag's Lockdown session at cfObjective but he said that IIS sets file size limits at 30MB by default which just happened to be the threshold for file size when ... read »
May 21, 2013 at 11:51 AM
Ask Ben: Parsing Very Large XML Documents In ColdFusion
Looking at my first ever XML document that I have to parse and put into MS SQL 2000 with CF8. I get it to list the desired Field name, many times over, and have a long list of this field name displa ... read »
May 21, 2013 at 9:25 AM
Turning Off and On Identity Column in SQL Server
you are awesome..i am lucky to get this blog between such a garbage one....Thanks, Prashant ... read »
May 20, 2013 at 4:38 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, Your confusion is well founded, since this is a very confusing features. In fact, it ONLY works if you use array notation. Meaning, that this: arrayToList( query[ "columnName" ] ) ... read »
May 20, 2013 at 4:34 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I was thinking chicken and the egg, I wouldn't have expected it to work in the valuelist going in I guess. Maybe I just need a beer, long day :) ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools