CSSRule.cfc - Parsing CSS Rules In ColdFusion

Posted February 4, 2008 at 9:39 AM by Ben Nadel

Tags: ColdFusion

When I was building my POIUtility.cfc, I started to dabble with parsing CSS in ColdFusion for use with Excel formatting. I love CSS; I think it's such an awesome, efficient, easy-to-read, and usable way to define the format of things. I have a few projects floating around in my head that could use some easy formatting, including updates to the POIUtility.cfc and so, I thought it was time to sit down and formalize some ColdFusion CSS parsing and modeling that could be use in a repeatable way. I came up with the CSSRule.cfc ColdFusion component. The CSSRule.cfc is meant do model a single rule in a style sheet. There are of course, no concepts of inherited properties, as that is greater than the rule itself - that is part of the rule application. I am also modelling just a sub-set of the CSS, albeit, a large sub-set that includes short hands for things like "font" and "background".

Each rule is modelled internally using the simplest rules possible. For example, internally, I don't have a "margin" property; instead, I have a margin-top, margin-right, margin-bottom, and margin-left properties. And, when you set the margin property, it, in turn, sets all four sub-properties. This felt like the easiest way to maintain the CSS data.

Before, I show you how it works internally, let's just take a look at a small example:

  • <!--- Create the CSS rule. --->
  • <cfset objCSS = CreateObject( "component", "CSSRule" ).Init() />
  •  
  • <!---
  • Add CSS to test values to make sure that the appropriate
  • properties are populating. The AddCSS() returns a reference
  • to the CSS Rule so that you can chain these method calls
  • for better readability. Also notice that I am using several
  • short hands just as "Background" and "Font".
  • --->
  • <cfset objCSS
  • .AddCSS( "background: ##000000 url( 'back.jpg' ) repeat-y ;" )
  • .AddCSS( "border: 2px solid green ; " )
  • .AddCSS( "border-bottom: 5px dotted ##FF0000 ;" )
  • .AddCSS( "font: bold italic 11px verdana ;" )
  • .AddCSS( "list-style: none ;" )
  • .AddCSS( "margin: 0px ; padding: 20px 10px ;" )
  • .AddCSS( "text-align: justify ; text-decoration: underline ;" )
  • .AddCSS( "white-space: nowrap ; z-index: 500" )
  • .AddCSS( "bottom: 5px ; top: 2px ; left: 1px ; right: 3px ;" )
  • .AddCSS( "display: block ; position: relative ;" )
  • .AddCSS( "white-space: nowrap ; width: 500px ;" )
  • />
  •  
  • <!--- Dump out CSS property map. --->
  • <cfdump
  • var="#objCSS.GetPropertyMap()#"
  • label="CSS Rule Property Map"
  • />

As you can see, there is a simple AddCSS() method which takes CSS property strings. You can add as many semi-colon-delimited properties per method call as you want, but I have broken them up here for easier viewing (and to demonstrate the chainability of the method). Once all the properties are all set, you can either get each property value individually using the GetProperty() method (not shown), or you can get the entire property map using GetPropertyMap(). Running the above code, we get the following CFDump output:


 
 
 

 
CSS Rule Properties Parsed Using ColdFusion  
 
 
 

As you can see, not only were simple values like "text-align" set, CSS short hands like "background", "font", and "list-style" all parsed and were set properly into their own sub-properties.

I don't want to get into how it works so much, since I have "real" work that I need to get done, but it has some nifty stuff and uses regular expressions to validate the values for each property. I am sure there are cleaner ways that I can do some of this stuff, but this was my first pass. Here is the code for the CSSRule.css ColdFusion component:

  • <cfcomponent
  • output="false"
  • hint="Handles a single CSS rule.">
  •  
  • <!--- Set up internal structure. --->
  • <cfset VARIABLES.Instance = {} />
  •  
  • <!--- Set up the default CSS properties for this rule. --->
  • <cfset VARIABLES.Instance.CSS = {} />
  • <cfset VARIABLES.Instance.CSS[ "background-attachment" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "background-color" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "background-image" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "background-position" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "background-repeat" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-top-width" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-top-color" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-top-style" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-right-width" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-right-color" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-right-style" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-bottom-width" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-bottom-color" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-bottom-style" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-left-width" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-left-color" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "border-left-style" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "bottom" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "display" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "font-family" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "font-size" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "font-style" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "font-weight" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "left" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "list-style-image" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "list-style-position" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "list-style-type" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "margin-top" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "margin-right" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "margin-bottom" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "margin-left" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "padding-top" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "padding-right" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "padding-bottom" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "padding-left" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "position" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "right" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "text-align" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "text-decoration" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "top" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "white-space" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "width" ] = "" />
  • <cfset VARIABLES.Instance.CSS[ "z-index" ] = "" />
  •  
  • <!---
  • Set up the validation rules for the CSS properties. Each
  • property must fit in a certain format. These formats
  • will be defined using regular expressions and will be
  • used to match the entire value (no partial matching).
  • --->
  • <cfset VARIABLES.Instance.CSSValidation = {} />
  • <cfset VARIABLES.Instance.CSSValidation[ "background-attachment" ] = "scroll|fixed" />
  • <cfset VARIABLES.Instance.CSSValidation[ "background-color" ] = "\w+|##[0-9ABCDEF]{6}" />
  • <cfset VARIABLES.Instance.CSSValidation[ "background-image" ] = "url\([^\)]+\)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "background-position" ] = "(top|right|bottom|left|\d+(\.\d+)?(px|%|em)) (top|right|bottom|left|\d+(\.\d+)?(px|%|em))" />
  • <cfset VARIABLES.Instance.CSSValidation[ "background-repeat" ] = "(no-)?repeat(-x|-y)?" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-top-width" ] = "\d+(\.\d+)?px" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-top-color" ] = "\w+|##[0-9ABCDEF]{6}" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-top-style" ] = "none|dotted|dashed|solid|double|groove" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-right-width" ] = "\d+(\.\d+)?px" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-right-color" ] = "\w+|##[0-9ABCDEF]{6}" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-right-style" ] = "none|dotted|dashed|solid|double|groove" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-bottom-width" ] = "\d+(\.\d+)?px" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-bottom-color" ] = "\w+|##[0-9ABCDEF]{6}" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-bottom-style" ] = "none|dotted|dashed|solid|double|groove" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-left-width" ] = "\d+(\.\d+)?px" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-left-color" ] = "\w+|##[0-9ABCDEF]{6}" />
  • <cfset VARIABLES.Instance.CSSValidation[ "border-left-style" ] = "none|dotted|dashed|solid|double|groove" />
  • <cfset VARIABLES.Instance.CSSValidation[ "bottom" ] = "-?\d+(\.\d+)?px" />
  • <cfset VARIABLES.Instance.CSSValidation[ "display" ] = "inline|block|block" />
  • <cfset VARIABLES.Instance.CSSValidation[ "font-family" ] = "((\w+|""[^""]""+)(\s*,\s*)?)+" />
  • <cfset VARIABLES.Instance.CSSValidation[ "font-size" ] = "\d+(\.\d+)?(px|pt|em|%)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "font-style" ] = "normal|italic" />
  • <cfset VARIABLES.Instance.CSSValidation[ "font-weight" ] = "normal|lighter|bold|bolder|[1-9]00" />
  • <cfset VARIABLES.Instance.CSSValidation[ "left" ] = "-?\d+(\.\d+)?px" />
  • <cfset VARIABLES.Instance.CSSValidation[ "list-style-image" ] = "none|url\([^\)]+\)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "list-style-position" ] = "inside|outside" />
  • <cfset VARIABLES.Instance.CSSValidation[ "list-style-type" ] = "disc|circle|square|none" />
  • <cfset VARIABLES.Instance.CSSValidation[ "margin-top" ] = "\d+(\.\d+)?(px|em)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "margin-right" ] = "\d+(\.\d+)?(px|em)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "margin-bottom" ] = "\d+(\.\d+)?(px|em)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "margin-left" ] = "\d+(\.\d+)?(px|em)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "padding-top" ] = "\d+(\.\d+)?(px|em)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "padding-right" ] = "\d+(\.\d+)?(px|em)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "padding-bottom" ] = "\d+(\.\d+)?(px|em)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "padding-left" ] = "\d+(\.\d+)?(px|em)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "position" ] = "static|relative|absolute|fixed" />
  • <cfset VARIABLES.Instance.CSSValidation[ "right" ] = "-?\d+(\.\d+)?px" />
  • <cfset VARIABLES.Instance.CSSValidation[ "text-align" ] = "left|right|center|justify" />
  • <cfset VARIABLES.Instance.CSSValidation[ "text-decoration" ] = "none|underline|overline|line-through" />
  • <cfset VARIABLES.Instance.CSSValidation[ "top" ] = "-?\d+(\.\d+)?px" />
  • <cfset VARIABLES.Instance.CSSValidation[ "white-space" ] = "normal|pre|nowrap" />
  • <cfset VARIABLES.Instance.CSSValidation[ "width" ] = "\d+(\.\d+)?(px|pt|em|%)" />
  • <cfset VARIABLES.Instance.CSSValidation[ "z-index" ] = "\d+" />
  •  
  •  
  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Returns an initialized component.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="CSS"
  • type="string"
  • required="false"
  • default=""
  • hint="Default CSS properties for this rule (may have multiple properties separated by semi-colons)."
  • />
  •  
  • <!--- Add this properties. --->
  • <cfset THIS.AddCSS( ARGUMENTS.CSS ) />
  •  
  • <!--- Return THIS reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="AddCSS"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Adds CSS properties to this rule and return THIS for chaining.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="CSS"
  • type="string"
  • required="true"
  • hint="CSS properties for this rule (may have multiple properties separated by semi-colons)."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Loop over the list of properties passed in. --->
  • <cfloop
  • index="LOCAL.Property"
  • list="#ARGUMENTS.CSS#"
  • delimiters=";">
  •  
  • <!--- Add this property to the rule. --->
  • <cfset THIS.AddProperty( Trim( LOCAL.Property ) ) />
  •  
  • </cfloop>
  •  
  • <!--- Return THIS reference for chaining. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="AddProperty"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="Parses the given property and adds it to the rule.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Property"
  • type="string"
  • required="true"
  • hint="The name-value pair property that will be added to the CSS rule."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!---
  • The property should be in name=value pair format. Break up the
  • property into the two parts. Also, make sure that we only have
  • one property being set (as delimited by ";").
  • --->
  • <cfset LOCAL.Pair = ListToArray(
  • Trim( ListFirst( ARGUMENTS.Property , ";" ) ),
  • ":"
  • ) />
  •  
  • <!---
  • Check to see if we have two parts. If we have
  • anything but two parts, then this is not a valid
  • name-value pair.
  • --->
  • <cfif (ArrayLen( LOCAL.Pair ) EQ 2)>
  •  
  • <!--- Trim both parts of the pair. --->
  • <cfset LOCAL.Name = Trim( LOCAL.Pair[ 1 ] ) />
  • <cfset LOCAL.Value = Trim( LOCAL.Pair[ 2 ] ) />
  •  
  • <!---
  • When it comes to parsing the property, they might be
  • using a simple one that we have. If not, we have to
  • get a little more creative with the parsing.
  • --->
  • <cfif THIS.IsValidValue( LOCAL.Name, LOCAL.Value )>
  •  
  • <!--- This value has validated. Add it to the CSS properties. --->
  • <cfset VARIABLES.Instance.CSS[ LOCAL.Name ] = LOCAL.Value />
  •  
  • <!--- Return true for success. --->
  • <cfreturn true />
  •  
  • <cfelse>
  •  
  • <!---
  • We were not given a simple value; we were given a value that
  • we will have to parse out into the individual properties.
  • --->
  • <cfswitch expression="#LOCAL.Name#">
  •  
  • <cfcase value="background">
  • <cfset THIS.SetBackground( LOCAL.Value ) />
  • </cfcase>
  •  
  • <cfcase value="border,border-top,border-right,border-bottom,border-left" delimiters=",">
  • <cfset THIS.SetBorder( LOCAL.Name, LOCAL.Value ) />
  • </cfcase>
  •  
  • <cfcase value="font">
  • <cfset THIS.SetFont( LOCAL.Value ) />
  • </cfcase>
  •  
  • <cfcase value="list-style">
  • <cfset THIS.SetListStyle( LOCAL.Value ) />
  • </cfcase>
  •  
  • <cfcase value="margin" delimiters=",">
  • <cfset THIS.SetMargin( LOCAL.Value ) />
  • </cfcase>
  •  
  • <cfcase value="padding" delimiters=",">
  • <cfset THIS.SetPadding( LOCAL.Value ) />
  • </cfcase>
  •  
  • </cfswitch>
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  • <!---
  • Return out. If we made it this far, then we
  • didn't add a valid property.
  • --->
  • <cfreturn false />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="GetProperty"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="Returns the given property.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Property"
  • type="string"
  • required="true"
  • hint="The CSS property."
  • />
  •  
  • <!--- Check to make sure that the property exists. --->
  • <cfif StructKeyExists( VARIABLES.Instance.CSS, ARGUMENTS.Property )>
  •  
  • <!--- Return given property. --->
  • <cfreturn VARIABLES.Instance.CSS[ ARGUMENTS.Property ] />
  •  
  • <cfelse>
  •  
  • <!--- Invalid property name, so just return empty string. --->
  • <cfreturn "" />
  •  
  • </cfif>
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="GetPropertyMap"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="Returns the CSS properties for this rule.">
  •  
  • <!--- Return a duplicate of the CSS properties. --->
  • <cfreturn StructCopy( VARIABLES.Instance.CSS ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="GetPropertyTokens"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="Parsese the property value into individual tokens.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The value we want to parse into an array of tokens."
  • />
  •  
  • <!---
  • Get the tokens. These are the smallest meaningful
  • pieces of any CSS property.
  • --->
  • <cfreturn REMatch(
  • (
  • "(?i)" &
  • "url\([^\)]+\)|" &
  • """[^""]+""|" &
  • "##[0-9ABCDEF]{6}|" &
  • "([\w\.\-%]+(\s*,\s*)?)+"
  • ),
  • ARGUMENTS.Value
  • ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="IsValidValue"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="Checks to see if the given value validated for a given property.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Property"
  • type="string"
  • required="true"
  • hint="The property we are checking for."
  • />
  •  
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The value we are checking for validity."
  • />
  •  
  • <!---
  • Return whether it validates. If the property is not
  • valid, we are returning false (same as an invalid value).
  • --->
  • <cfreturn (
  • StructKeyExists( VARIABLES.Instance.CSS, ARGUMENTS.Property ) AND
  • REFind( "(?i)^#VARIABLES.Instance.CSSValidation[ ARGUMENTS.Property ]#$", ARGUMENTS.Value )
  • ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="ParseQuadMetric"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="Takes a quad metric and returns a four-point array.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The metric which may have between one and four values."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Grab metric values. --->
  • <cfset LOCAL.Values = REMatch( "\d+(\.\d+)?(px|em)", ARGUMENTS.Value ) />
  •  
  • <!--- Set up the return array. --->
  • <cfset LOCAL.Return = [ "", "", "", "" ] />
  •  
  • <!--- Check to see how many values we have. --->
  • <cfif (ArrayLen( LOCAL.Values ) EQ 1)>
  •  
  • <!--- Copy to all positions. --->
  • <cfset ArraySet( LOCAL.Return, 1, 4, LOCAL.Values[ 1 ] ) />
  •  
  • <cfelseif (ArrayLen( LOCAL.Values ) EQ 2)>
  •  
  • <!--- Copy 2 and 2. --->
  • <cfset LOCAL.Return[ 1 ] = LOCAL.Values[ 1 ] />
  • <cfset LOCAL.Return[ 2 ] = LOCAL.Values[ 2 ] />
  • <cfset LOCAL.Return[ 3 ] = LOCAL.Values[ 1 ] />
  • <cfset LOCAL.Return[ 4 ] = LOCAL.Values[ 2 ] />
  •  
  • <cfelseif (ArrayLen( LOCAL.Values ) EQ 3)>
  •  
  • <!--- Copy 3 and 1. --->
  • <cfset LOCAL.Return[ 1 ] = LOCAL.Values[ 1 ] />
  • <cfset LOCAL.Return[ 2 ] = LOCAL.Values[ 2 ] />
  • <cfset LOCAL.Return[ 3 ] = LOCAL.Values[ 3 ] />
  • <cfset LOCAL.Return[ 4 ] = LOCAL.Values[ 1 ] />
  •  
  • <cfelseif (ArrayLen( LOCAL.Values ) GTE 4)>
  •  
  • <!--- Copy first four values. --->
  • <cfset LOCAL.Return[ 1 ] = LOCAL.Values[ 1 ] />
  • <cfset LOCAL.Return[ 2 ] = LOCAL.Values[ 2 ] />
  • <cfset LOCAL.Return[ 3 ] = LOCAL.Values[ 3 ] />
  • <cfset LOCAL.Return[ 4 ] = LOCAL.Values[ 4 ] />
  •  
  • </cfif>
  •  
  • <!--- Return results. --->
  • <cfreturn LOCAL.Return />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="SetBackground"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Parses the background short-hand and sets the equivalent CSS properties.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The background short hand value."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Set up base properties that make up the background short hand. --->
  • <cfset LOCAL.CSS[ "background-attachment" ] = "" />
  • <cfset LOCAL.CSS[ "background-color" ] = "" />
  • <cfset LOCAL.CSS[ "background-image" ] = "" />
  • <cfset LOCAL.CSS[ "background-position" ] = "" />
  • <cfset LOCAL.CSS[ "background-repeat" ] = "" />
  •  
  • <!--- Get property tokens. --->
  • <cfset LOCAL.Tokens = THIS.GetPropertyTokens( ARGUMENTS.Value ) />
  •  
  • <!---
  • Now that we have all of our tokens, we are going to loop over the
  • tokens and the properties and try to apply each. We want to apply
  • tokens with the hardest to accomodate first.
  • --->
  • <cfloop
  • index="LOCAL.Token"
  • array="#LOCAL.Tokens#">
  •  
  • <!--- Loop over properties, most restrictive first. --->
  • <cfloop
  • index="LOCAL.Property"
  • list="background-attachment,background-position,background-repeat,background-image,background-color"
  • delimiters=",">
  •  
  • <!---
  • Check to see if this value is valid. If this property
  • already has a value, then skip.
  • --->
  • <cfif (
  • (NOT Len( LOCAL.CSS[ LOCAL.Property ] )) AND
  • THIS.IsValidValue( LOCAL.Property, LOCAL.Token )
  • )>
  •  
  • <!--- Assign to property. --->
  • <cfset LOCAL.CSS[ LOCAL.Property ] = LOCAL.Token />
  •  
  • <!--- Move to next token. --->
  • <cfbreak />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Loop over local CSS to apply property. --->
  • <cfloop
  • item="LOCAL.Property"
  • collection="#LOCAL.CSS#">
  •  
  • <!--- Set properties. --->
  • <cfif Len( LOCAL.CSS[ LOCAL.Property ] )>
  • <cfset VARIABLES.Instance.CSS[ LOCAL.Property ] = LOCAL.CSS[ LOCAL.Property ] />
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="SetBorder"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Parses the border short-hand and sets the equivalent CSS properties.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Name"
  • type="string"
  • required="true"
  • hint="The name of the pseudo property that we want to set."
  • />
  •  
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The border short hand value."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!---
  • Set up base properties. We will use the top-border as our base
  • since all borders act the same and we have validation set up for it.
  • --->
  • <cfset LOCAL.CSS = {} />
  • <cfset LOCAL.CSS[ "border-top-width" ] = "" />
  • <cfset LOCAL.CSS[ "border-top-color" ] = "" />
  • <cfset LOCAL.CSS[ "border-top-style" ] = "" />
  •  
  • <!--- Get property tokens. --->
  • <cfset LOCAL.Tokens = THIS.GetPropertyTokens( ARGUMENTS.Value ) />
  •  
  • <!---
  • Now that we have all of our tokens, we are going to loop over the
  • tokens and the properties and try to apply each. We want to apply
  • tokens with the hardest to accomodate first.
  • --->
  • <cfloop
  • index="LOCAL.Token"
  • array="#LOCAL.Tokens#">
  •  
  • <!--- Loop over properties, most restrictive first. --->
  • <cfloop
  • index="LOCAL.Property"
  • list="border-top-style,border-top-width,border-top-color"
  • delimiters=",">
  •  
  • <!---
  • Check to see if this value is valid. If this property
  • already has a value, then skip.
  • --->
  • <cfif (
  • (NOT Len( LOCAL.CSS[ LOCAL.Property ] )) AND
  • THIS.IsValidValue( LOCAL.Property, LOCAL.Token )
  • )>
  •  
  • <!--- Assign to property. --->
  • <cfset LOCAL.CSS[ LOCAL.Property ] = LOCAL.Token />
  •  
  • <!--- Move to next token. --->
  • <cfbreak />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  •  
  • <!---
  • If we are dealing with the main border, then we have to apply
  • these results to all four borders. Otherwise, we are only dealing
  • with the given property.
  • --->
  • <cfif (ARGUMENTS.Name EQ "border")>
  •  
  • <!--- All four borders. --->
  • <cfset LOCAL.PropertyList = "border-top,border-right,border-bottom,border-left" />
  •  
  • <cfelse>
  •  
  • <!--- Just the given property. --->
  • <cfset LOCAL.PropertyList = ARGUMENTS.Name />
  •  
  • </cfif>
  •  
  • <!--- Loop over list to apply CSS. --->
  • <cfloop
  • index="LOCAL.Property"
  • list="#LOCAL.PropertyList#"
  • delimiters=",">
  •  
  • <!--- Set properties. --->
  • <cfif Len( LOCAL.CSS[ "border-top-color" ] )>
  • <cfset VARIABLES.Instance.CSS[ "#LOCAL.Property#-color" ] = LOCAL.CSS[ "border-top-color" ] />
  • </cfif>
  •  
  • <cfif Len( LOCAL.CSS[ "border-top-style" ] )>
  • <cfset VARIABLES.Instance.CSS[ "#LOCAL.Property#-style" ] = LOCAL.CSS[ "border-top-style" ] />
  • </cfif>
  •  
  • <cfif Len( LOCAL.CSS[ "border-top-width" ] )>
  • <cfset VARIABLES.Instance.CSS[ "#LOCAL.Property#-width" ] = LOCAL.CSS[ "border-top-width" ] />
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="SetFont"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Parses the font short-hand and sets the equivalent CSS properties.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The font short hand value."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Set up base properties that make up the font short hand. --->
  • <cfset LOCAL.CSS[ "font-family" ] = "" />
  • <cfset LOCAL.CSS[ "font-size" ] = "" />
  • <cfset LOCAL.CSS[ "font-style" ] = "" />
  • <cfset LOCAL.CSS[ "font-weight" ] = "" />
  •  
  • <!--- Get property tokens. --->
  • <cfset LOCAL.Tokens = THIS.GetPropertyTokens( ARGUMENTS.Value ) />
  •  
  • <!---
  • Now that we have all of our tokens, we are going to loop over the
  • tokens and the properties and try to apply each. We want to apply
  • tokens with the hardest to accomodate first.
  • --->
  • <cfloop
  • index="LOCAL.Token"
  • array="#LOCAL.Tokens#">
  •  
  • <!--- Loop over properties, most restrictive first. --->
  • <cfloop
  • index="LOCAL.Property"
  • list="font-style,font-size,font-weight,font-family"
  • delimiters=",">
  •  
  • <!---
  • Check to see if this value is valid. If this property
  • already has a value, then skip.
  • --->
  • <cfif (
  • (NOT Len( LOCAL.CSS[ LOCAL.Property ] )) AND
  • THIS.IsValidValue( LOCAL.Property, LOCAL.Token )
  • )>
  •  
  • <!--- Assign to property. --->
  • <cfset LOCAL.CSS[ LOCAL.Property ] = LOCAL.Token />
  •  
  • <!--- Move to next token. --->
  • <cfbreak />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Loop over local CSS to apply property. --->
  • <cfloop
  • item="LOCAL.Property"
  • collection="#LOCAL.CSS#">
  •  
  • <!--- Set properties. --->
  • <cfif Len( LOCAL.CSS[ LOCAL.Property ] )>
  • <cfset VARIABLES.Instance.CSS[ LOCAL.Property ] = LOCAL.CSS[ LOCAL.Property ] />
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="SetListStyle"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Parses the list style short-hand and sets the equivalent CSS properties.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The list style short hand value."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Set up base properties that make up the list style short hand. --->
  • <cfset LOCAL.CSS[ "list-style-image" ] = "" />
  • <cfset LOCAL.CSS[ "list-style-position" ] = "" />
  • <cfset LOCAL.CSS[ "list-style-type" ] = "" />
  •  
  • <!--- Get property tokens. --->
  • <cfset LOCAL.Tokens = THIS.GetPropertyTokens( ARGUMENTS.Value ) />
  •  
  • <!---
  • Now that we have all of our tokens, we are going to loop over the
  • tokens and the properties and try to apply each. We want to apply
  • tokens with the hardest to accomodate first.
  • --->
  • <cfloop
  • index="LOCAL.Token"
  • array="#LOCAL.Tokens#">
  •  
  • <!--- Loop over properties, most restrictive first. --->
  • <cfloop
  • index="LOCAL.Property"
  • list="list-style-type,list-style-image,list-style-position"
  • delimiters=",">
  •  
  • <!---
  • Check to see if this value is valid. If this property
  • already has a value, then skip.
  • --->
  • <cfif (
  • (NOT Len( LOCAL.CSS[ LOCAL.Property ] )) AND
  • THIS.IsValidValue( LOCAL.Property, LOCAL.Token )
  • )>
  •  
  • <!--- Assign to property. --->
  • <cfset LOCAL.CSS[ LOCAL.Property ] = LOCAL.Token />
  •  
  • <!--- Move to next token. --->
  • <cfbreak />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Loop over local CSS to apply property. --->
  • <cfloop
  • item="LOCAL.Property"
  • collection="#LOCAL.CSS#">
  •  
  • <!--- Set properties. --->
  • <cfif Len( LOCAL.CSS[ LOCAL.Property ] )>
  • <cfset VARIABLES.Instance.CSS[ LOCAL.Property ] = LOCAL.CSS[ LOCAL.Property ] />
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="SetMargin"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Parses the margin short hand and sets the equivalent properties.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The margin short hand value."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Parse the quad metric value. --->
  • <cfset LOCAL.Metrics = THIS.ParseQuadMetric( ARGUMENTS.Value ) />
  •  
  • <!--- Set properties. --->
  • <cfif IsValidValue( "margin-top", LOCAL.Metrics[ 1 ] )>
  • <cfset VARIABLES.Instance.CSS[ "margin-top" ] = LOCAL.Metrics[ 1 ] />
  • </cfif>
  •  
  • <cfif IsValidValue( "margin-right", LOCAL.Metrics[ 2 ] )>
  • <cfset VARIABLES.Instance.CSS[ "margin-right" ] = LOCAL.Metrics[ 2 ] />
  • </cfif>
  •  
  • <cfif IsValidValue( "margin-bottom", LOCAL.Metrics[ 3 ] )>
  • <cfset VARIABLES.Instance.CSS[ "margin-bottom" ] = LOCAL.Metrics[ 3 ] />
  • </cfif>
  •  
  • <cfif IsValidValue( "margin-left", LOCAL.Metrics[ 4 ] )>
  • <cfset VARIABLES.Instance.CSS[ "margin-left" ] = LOCAL.Metrics[ 4 ] />
  • </cfif>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="SetPadding"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Parses the padding short hand and sets the equivalent properties.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="The padding short hand value."
  • />
  •  
  • <!--- Set up local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Parse the quad metric value. --->
  • <cfset LOCAL.Metrics = THIS.ParseQuadMetric( ARGUMENTS.Value ) />
  •  
  • <!--- Set properties. --->
  • <cfif IsValidValue( "padding-top", LOCAL.Metrics[ 1 ] )>
  • <cfset VARIABLES.Instance.CSS[ "padding-top" ] = LOCAL.Metrics[ 1 ] />
  • </cfif>
  •  
  • <cfif IsValidValue( "padding-right", LOCAL.Metrics[ 2 ] )>
  • <cfset VARIABLES.Instance.CSS[ "padding-right" ] = LOCAL.Metrics[ 2 ] />
  • </cfif>
  •  
  • <cfif IsValidValue( "padding-bottom", LOCAL.Metrics[ 3 ] )>
  • <cfset VARIABLES.Instance.CSS[ "padding-bottom" ] = LOCAL.Metrics[ 3 ] />
  • </cfif>
  •  
  • <cfif IsValidValue( "padding-left", LOCAL.Metrics[ 4 ] )>
  • <cfset VARIABLES.Instance.CSS[ "padding-left" ] = LOCAL.Metrics[ 4 ] />
  • </cfif>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>



Reader Comments

Feb 4, 2008 at 11:22 AM // reply »
28 Comments

Ben, I'm familiar with using POI to generate Excel docs, but I've never heard of the use of CSS with it. Does Excel actually use CSS rules?

Otherwise, I think your CSS parser/generator is rather handy if you have to create dynamic styles. Nice work!


Feb 4, 2008 at 11:30 AM // reply »
11,314 Comments

@Tom,

The POI library doesn't use CSS inherently. However, through my POIUtility.cfc, you can use some CSS to format the Excel. The POIUtility.cfc will translate that CSS into something that the Excel file can use.

But, what I really want to do here is create a standard, programmatic representation for CSS that can be used by several projects, including updates to my POIUtility.cfc. The hard part is parsing the CSS, so I wanted to encapsulate that into something cohesive, reusable, and highly transportable.


Feb 4, 2008 at 6:56 PM // reply »
11,314 Comments

Oops! I was just deleting a bunch of spam (I was attacked) and deleted Sana's legit comment:


Hi Ben,

Nice tutorial; Only one thing I really don't like is (LOCAL) word, I bitten by this many times. So I would suggest don't use this word.

Thanks


Feb 4, 2008 at 6:58 PM // reply »
11,314 Comments

@Sana,

Do you have another suggestion outside of LOCAL? I have found it a very useful as a visual delimiter from non-local values. The only time that I have ever been hurt by it is when performing a ColdFusion query of queries. Other than that, I can't think of where it would go wrong.


Feb 4, 2008 at 6:58 PM // reply »
11,314 Comments

... and just to say, you can get around the LOCAL in Query of Queries by escaping [LOCAL].


Feb 5, 2008 at 7:53 AM // reply »
110 Comments

What's with all the spammers targeting your site? They managed to get another post in at the Anonymous CF survey post.


Feb 5, 2008 at 7:55 AM // reply »
11,314 Comments

@Gareth,

I don't know! Some dude hit me last night on like 50 different posts. So frustrating. I think it was just one person / bot. Seems to have subsided. What the heck is wrong with these people?


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
Jun 19, 2013 at 10:18 AM
ColdFusion Path Usage And Manipulation Overview
Anyone happen to know if the file created by getTempFile will be automatically removed at any point? Nothing mentioned in the docs, and restarting CF doesn't remove them, so it seems it needs manu ... read »
Jun 19, 2013 at 9:41 AM
Working With Inherited Collections In AngularJS
I actually just ran into this same situation with a demo I was putting together. Your implementation of multi-lvl $scope's > Mine :) ... read »
Jun 19, 2013 at 8:17 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
@Prateek, to match a word or text you should use .toContain('word') that's a jasmine reference. website is : http://pivotal.github.io/jasmine/ ... read »
Jun 19, 2013 at 8:10 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
Hi Guys, Actually i am doing e2e test of angular js of my project but i am not getting one thing that is how to press enter key through the test when my form is filled as i am not using a button but ... read »
Jun 18, 2013 at 9:20 PM
Mapping AngularJS Routes Onto URL Parameters And Client-Side Events
I couldn't find examples of passing multiple arguments using the when() routing statement so figured out through trial and error that you can pass multiple arguments using the following format: .whe ... read »
Jun 18, 2013 at 3:39 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
Hi Ben, THANKS! While not bleeding edge, it is new to me & I like learning new things every day! ... read »
Jun 18, 2013 at 12:30 PM
Disabling Auto-Correct And Auto-Capitalize Features On iPhone Inputs
Also spellcheck="false" should be mentioned as part of html5 specs ... read »
Jun 18, 2013 at 8:40 AM
Using Named Functions Within Self-Executing Function Blocks In Javascript
Hi Ben, you forgot to mention the most important thing for named self-executing functions - they can be referenced by name ONLY inside their execution context (which is parens in this case), it mean ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools