CSS2HTML

PN (20040912)

CSS2HTML is an attempt to solve the problem of managing the styles of many websites. CSS provides a convenient and condensed description of style compared to what would have to be written into HTML tags. But the browser world is splitting into two camps. The mega browser for the home PC and the micro/lite browser for the overworked PC, the Palm and other small devices. It is now more important than ever to write the best HTML that stands by itself and produces the best page possible.

WJR - Would another way to approach this problem be to keep the HTML as structured as possible and use CSS media types [L1 ] to describe the presentation for different browsers?

So, how to embed the CSS back into the HTML for delivery to lite browsers ?

I propose

  proc css2html {tag style} {
    . . .
  }

style is the name of a Tcl global object which holds the CSS. tag is the name of the tag which is to be extracted from the CSS.

CSS2HTML returns the opening HTML tag.

Here is an example of the BODY tag - first the CSS.

  body {
    margin: 1em;
    line-height: 1.1em;
    font-family: "MS Sans Serif", sans-serif;
    font-size: 8pt;
    background-color: white;
    color: #222;
  }
  a:link { color: #777 }
  a:visited { color: #a86 }
  a:active { color: #8a6 }
  h2 { color: #77b }
  h3 { color: #66a }
  . . . .

The CSS is read into a Tcl array object so it looks like:

  array set style { {1 body} {margin 1em 
                              line-height 1.1em 
                              font-family {"MS Sans Serif", sans-serif}
                              font-size 8pt 
                              background-color white
                              color #222}
                    {2 a:link} {color #777}
                    {3 a:visited} {color #a86}
                    {4 a:active} {color #8a6}
                    {5 h2} {color #77b}
                    {6 h3} {color #66a}
                      . . . .
                   }

The structure is an array list of array lists. A line number is added to preserve the order of the CSS statement in the style list.

  > "css2html body" style
  <body vlink='#0ff' bgcolor='white' style='font-family: "MS Sans Serif", sans-serif; 
  line-height: 1.1em; margin: 1em; font-size: 8pt; ' text='#222' link='#00f' alink='#f00'>

Note that a style attribute is produced to hold the stuff that doesnt fit in HTML in this example. If a browser is going to read the style attribute it will be able to read the stylesheet itself. So the style attribute could be considered redundant. However a non-CSS browser could chose to read the style attribute if it implemented some features available in CSS. Lite browsers are going to expand their features using CSS as a base without taking on the whole of CSS and becoming big fat squishy things that bulge out the sides of my Palmy.

The body tag is easy because there is no need for id or class attributes but other tags are going to use these and more. I dont intend to implement the whole of CSS myself. I have other demands on my time. I will implement as much as I need to, to get my job done and no more.

However the general principle is interesting and aids the management of large websites and their ability to deliver high quality pages to lite application platforms.

As for the code - here is what does the above example:

 #creates a new global array name which begins with the word in arg p followed by an integer
 proc globalarray p {
    for { set i 1} { [uplevel #0 info exists ${p}$i] } { incr i } {}
    global ${p}$i
    set ${p}${i}(0) {}
    unset ${p}${i}(0)
    return ${p}$i
 }

 # creates a tcl style global array object from a CSS file
 # returns the name of the tcl style global array (object)
 proc css2tcl file {

    set f [ open $file r ]
    set css [ read $f ]
    close $f

    # create the array object
    set style [globalarray style]
    global $style

    # first we get rid of comments - css comments are not allowed to go over a line
    regsub -all {/\*[^\n]*\*/} $css "" newcss

    # we want to grab every string of the form 
    #     "foo foo1 . . { attr1: val1 val2 ...; .... attrn: valn1 valn2 ...[;] }" 
    # and turn it into 
    #     "style(stmt# foo foo1 ...) { attr1 {val1 val2 ...} .... attrn {valn1 valn2 ...} }

    for { set i 1 } {[regexp {^([^\{]*)\{([^\}]*)\}} $newcss match elements attrs]} { incr i } {
        # strip the match from newcss
        set newcss [ string range $newcss [string length $match] end ]
        # parse the attrs - "attr: val1 val2 ...; .... attrn: valn1 valn2 ...[;]" 
        # becomes "attr {val1 val2 ...} .... attrn {valn1 valn2 ...}"
        # split and trim each element
        set newattrs {}
        foreach  j [ split $attrs ":;" ] {
            lappend newattrs [ string trim $j ]
        }
        # remove the odd trailer if there was a terminating ;
        if { [lindex $newattrs end ] == {} } { set newattrs [ lrange $newattrs 0 end-1] }
        # dummy test of array list
        if { [ catch {array set a $newattrs} msg ] } {
            global errorInfo
            bgerror "Error in stylesheet $file
 The following attribute list does not pair up and has been ignored:
 $newattrs
 $errorInfo"
        } else {
            set "${style}($i [string trim $elements])" $newattrs
        }
    }
    # newcss should now be empty
    if { [string trim $newcss] != {} } { 
        global errorInfo
        bgerror "Error in stylesheet $file
 The following was not parsed:
 [ string trim $newcss ]
 $errorInfo"
    }
   return $style
 }

 # this routine reads tcl array versions of a style sheet and returns an html BODY tag
 # style is the name of a tcl array containing the CSS style
 proc "css2html body" style {
    upvar $style css
    # set defaults
    array set body {bgcolor #000 text #fff link #00f vlink #0ff alink #f00}
    # get the body
    foreach s [ lsort -integer [array names css "* body*" ]] {
        array set cssbody $css($s)
        foreach {c h d} { background-color  bgcolor color 
                                  background-image background url 
                                  color text color
                                } {
            if { [info exists cssbody($c)] } {
                set body($h) [ "css2html $d" $cssbody($c) ]
                unset cssbody($c)
            }
        }
        # have not handled the CSS background attribute     

        # put remaining body attributes into style     
        set str ""
        foreach {i j} [array get cssbody] {
               append str "$i: $j; "
        }
        if { $str != {} } { set body(style) $str }
        unset cssbody
    }

    foreach {i j } { a:link link a:visited vlink a:active alink } {
        if { [ info exists css($i) ] } {
            array set cssbody $css($i)
            if { [ info exists cssbody(color) ] } {
                set body($j) [ "css2html color" $cssbody(color) ]
            }
            unset cssbody
        }
    }
    return [ "css2html make" body ]
 }

 # make the html opening tag for the tag type named tag
 proc "css2html make" tag {
    upvar $tag t
    set str ""
    foreach i [ array names t ] {
        append str " $i='$t($i)'"
    }
    return "<${tag}$str>"
 }

 # for now assume that CSS only uses HTML color specs
 proc "css2html color" color {
    return $color
 }

 proc "css2html url" url {
    regexp {url\((.*)\)} $url a b
    return $b
 }

All contributions gratefully received. PN


escargo - Any idea how to go the other way? It would be interesting to see how to factor existing HTML to remove duplicate formatting and put it into a style sheet. I've got that problem with a web site I'm maintaining now.


Category Internet