Updated 2011-02-28; minor bug fix. Thanks, brian!

Every time I create or use a jQuery plugin, I realize that the assigning of behaviors to elements on the page is a design decision, not a programming one, and one that should be made by the guy in charge of the CSS, not the guy in charge of the javascript. Even when I'm the same guy, I want to wear each hat separately. But these presentational enhancements are written in javascript and applied in javascript. So my "presentational" code is in two different files:


.gallery a { border: 1px solid green }

$('.gallery a').lightbox({overlayBgColor: '#ddd'});

I could put the presentational javascript in the HTML with $.metadata but while that's fine for quick-and-dirty pages, it's evil in production since it completely violates the separation of data/structure/presentation/behavior.

As I and others have noted before, the application of these plugins belongs in the stylesheet, and I finally got it to work:


.gallery a {
  border: 1px solid green;
  -jquery-lightbox: {overlayBgColor: '#ddd'};

and a single call to start it off: $(document).parsecss($.parsecss.jquery) .

Download the code.

See the demo.


$(selector).parsecss(callback) scans all <style> and <link type="text/css"> elements in $(selector) or its descendents, parses each one and passes an object (details below) to the callback function. It uses a callback because external stylesheets and @import statements need to use AJAX, asynchronously. The parser is smart enough to understand media attributes and statements.

The supplied callback is the $.parsecss.jquery function. That function finds properties that start with -jquery and applies them to the matching elements. Generally, you would want to parse all the stylesheets in the document, $(document), so the simple call is: $(document).parsecss($.parsecss.jquery).


Some jargon: in the stylesheet

foo {
  bar: quux;
  -bar-foo: quux-2;

foo > foo {
  bar: quux 2;

foo { bar: quux; -bar-foo: quux-2} is a statement, foo is a selector, {bar: quux; -bar-foo: quux-2} is its CSStext, -bar-foo: quux-2 is a declaration, -bar-foo is its property and quux-2 is its value.


$.parsecss.jquery is the callback function that implements the custom-jQuery code. It looks for properties that start with -jquery. If the property is -jquery alone, then it executes the value as a javascript statement for each matched element, $(selector).each(Function(value)). For example:

span.important {
  -jquery: $(this).append('<span>I am important</span>')


$('span.important').each(function() {$(this).append('<span>I am important!</span>')})

If the property is -jquery-show or -jquery-hide, then the normal show() and hide() are overridden for the matched elements to use the value as the arguments. It will recognize plugins and jQuery UI effects. Examples (see below how it parses the arguments):

a {
  -jquery-show: fast; /* $('a').show() does $('a').show('fast') */
  -jquery-show: fadeIn 2000 /* $('a').show() does $('a').fadeIn(2000) */
  -jquery-show: fold {size: 5} slow /* $('a').show() does $('a').show('fold', {size: 5}, 'slow') */
  -jquery-hide: explode fast /* $('a').hide() does $('a').hide('explode', {}, 'fast') It's smart enough to know that jQuery UI effects need an options object inserted */

Using hide() or show() with any arguments overrides the CSS-imposed functions.

Otherwise, if the property matches the regular expression /-jquery-(.*)/ then the (.*) is taken as a plugin name and the value is used for the arguments (see below how it parses the arguments):

.gallery a {
  -jquery-lightbox: {overlayBgColor: '#ddd'}


$('.gallery a').lightbox({overlayBgColor: '#ddd'});

Valid CSS?

Does including the -jquery properties make the CSS invalid? Sort of. The easy answer is "Who cares?" The CSS syntax specifies that

User agents must handle unexpected tokens encountered while parsing a declaration by reading until the end of the declaration, while observing the rules for matching pairs of (), [], {}, "", and ''
That means that the extensions described here will never cause errors in interpreting the rest of the CSS. Also, the specification says
Keywords and property names beginning with -' or '_' are reserved for vendor-specific extensions.
So, -jquery-show, while not valid, is "expected" in real-life CSS, just like -moz-border-radius.

But, some people want CSS that passes the validator. I adopted a solution similar to Microsoft's conditional comments. Text surrounded by /*@ and */ will be parsed by the parser despite being ignored by the browser.

span.important {
      font-weight: bold;
/*@   -jquery-after: '<span>!!!</span>'; */ /* "hide" the jQuery CSS in the comments */


What makes the combination of CSS and javascript so powerful is that CSS is "live": changes in the DOM are immediately rendered on the page (this is why Microsoft, when they invented it, called it DHTML—Dynamic HTML). If the stylesheet says .important { font-weight: bold; color: red } then running $('#x').addClass('important') changes the text to red and bold. In addition, $('#x').removeClass('important') changes the text back. The browser keeps track of all the elements and the selector in the CSS, and when an element starts to match the CSS is applied, and when it no longer matches, the browser knows how to "undo" the styling.

jQuery plugins can't do this. The javascript is run at $(document).ready, applied if the elements match at that moment, and that's it. This makes it much less useful, especially for AJAX-y sites that are constantly changing their elements.

Enter Brandon Aaron's $.livequery plugin. It keeps track of the selectors used and overrides all the plugins that modify the DOM, and re-applies the javascript if need be. It also allows you to specify an "unmatched" function that will be run when the element no longer matches. Obviously, you or the plugin programmer has to provide the "undo" ability; there's no way for $.livequery to know how to do that.

If $.livequery is available, $.parsecss can use it. Separate the two parts (match code/unmatch code) with a ! (the exclamation point implies "not"):

.important {
  -jquery-css: font-weight bold ! font-weight normal ;
/* yes, in real life you'd do font-weight: bold;  and not rely on javascript */

You can leave either side of the ! blank:

.gallery a {
  -jquery-lightbox: {overlayBgColor: '#ddd'} ! ; /* the '!' means we'll use livequery */
  -jquery-unbind: ! click; /* remove the click handler when the element no longer matches

Some notes on $.livequery:

  • If $.livequery is not available, the value after the ! is ignored and the code is applied only when $.parsecss is called.
  • $.livequery checks every selector on every modification of the DOM. It can make complicated pages and script very slow. Be aware of the tradeoffs of using it.
  • jQuery 1.3 includes a $.live plugin. This is not the same as $.livequery! It uses event delegation to keep event handlers on new elements, but does not re-apply javascript to new elements. You have to use $.livequery for that.

The parser

The parser turns CSS into a javascript object in the obvious way:

div div:first {
  font-weight: bold;
  -jquery: each(function() {alert(this.tagName);})

div > span {
  color: red;

is passed to the callback as:

  'div div:first' : {
    'font-weight' : 'bold',
    '-jquery': 'each(function() {alert(this.tagName);})'
  'div > span' : {
    'color': 'red'

The callback is called separately for each <style> and <link> element, and for every @rule that includes CSS text (@import and @media). There is no guarantee of any particular order in calling the callback. The parser (and $.parsecss.jquery)does not attempt to obey the priority rules of the CSS cascade; it will execute all the relevant code.

The parser is very simple; it uses regular expressions and String.remove to pull out comments, strings, brace-delimited blocks and @-rules, then parsing the grammar by using String.split on } to get each statement, splitting on { to get the selector and cssText, splitting the cssText on ; to get each declaration and on : to get the properties and values. It's not a sophisticated finite-state automaton but it serves.


CSS values use space-delimited arguments, border: 1px solid black

. To use this in javascript, we use apply which requires an array: ['1px', 'solid', 'black']. The function $.parsecss.parseArguments({String}) returns that array. It tries to be sophisticated about arrays, objects and functions, using eval to see if the item is interpretable as is; if not, it is made a string. This means that global variables (this, window) or expressions will cause unexpected results (put them in quotes to be sure):

a b c                                       =>  ['a', 'b', 'c']
1 2 c                                       =>  [1, 2, 'c']
[1,2] {a: 'b c'}                          =>  [[1,2], {a: 'b c'} ]
{a: b, c: d}                              =>  ["{a: b, c: d}"] // inside arrays, objects and functions, you have to use real javascript syntax
{a: 'b', c: 'd'}                            =>  [{a:"b", c:"d"}] // the automagic quoting is only at the top level
1+2                                         =>  [3]
'1+2'                                        =>  ['1+2']
10 fast function() {$(this).hide}   =>  [10, "fast", (function () {$(this).hide;})]


$.parsecss.isValidSelector(selector {String}) is a function I wrote for Any Kent's JSS, which is sort of the opposite of $.parsecss.jquery: it goes through the stylesheets and finds valid CSS selectors that the browser cannot handle, and uses jQuery to implement them. Eric Meyer recently had a similar idea. $.parsecss.isValidSelector takes a string and returns true if the browser recognizes it as a CSS selector. Thus in IE6, $.parsecss.isValidSelector('div p') returns true and $.parsecss.isValidSelector('div > p') returns false. In Firefox 3, $.parsecss.isValidSelector('div > p') returns true and $.parsecss.isValidSelector('div:visible') returns false.

A simple (though very inefficient) version of JSS would be:

  for (var selector in css){
    if (! $.parsecss.isValidSelector(selector)) $(selector).css(css[selector]);

It works by creating a new stylesheet and creating a rule with that selector and seeing if the browser recognized it.


$.parsecss.mediumApplies(medium {String}) is similar to $.parsecss.isValidSelector, but it returns true if the argument is a valid media attribute on a <link> element or @media rule. Thus, for a regular computer, $.parsecss.mediumApplies('screen') returns true, $.parsecss.mediumApplies('all') returns true, $.parsecss.mediumApplies('print') returns false, and $.parsecss.mediumApplies('') returns true.

The second argument to $.fn.parsecss

parsecss accepts a second argument, parseAttributes {Boolean}. If it is true, the parser will attempt to parse the style attribute of each element and pass it to the callback.

This is problematic on many levels. The first is how to identify the relevant element. If the element is <div id="test" style="-jquery-show: fast">a div</div> we want the object {'#test' : {'-jquery-show': 'fast'} }. If the element has an id attribute, then it's easy. Otherwise, it counts the tags in the page and uses :eq(): thus if <div style="-jquery-show: fast">a div</div> is the fifth div element, the object generated includes {'div:eq(4)' : {'-jquery-show': 'fast'} }.

The real problem is that there is no way to get the actual style attributes for elements in the DOM. The source code is parsed and interpreted by the browser and any declarations that the browser does not understand are discarded. There's no way to get the style="-jquery-show: fast" using any of the tools available. What I've done is used AJAX to get the source code back again: $.get(location.pathname+location.search, 'text', function(html){/*code that scans the html for style attributes*/). This assumes that getting the file again will get the same code (which might not be true if the real page was retrieved with a POST request) and that the DOM has not been changed. Otherwise 'div:eq(4)' will not refer to the correct element. You can obviate this problem by using id's which should not change. Still, the approach is problematic and you might want to use $.metadata to add information to the HTML itself.

If anyone knows how to get the actual text of the "style" attribute, please let me know!


  1. Mattias Hising says:

    I love this approach to specify the visual/design aspects of javascripts in css. Good work.

  2. Danny says:

    Thanks. I like your idea of hosting it on github and making it formally open source.

  3. Thomas Jaggi says:

    Great work! Even if I don’t really understand what’s going on. ;)

    Is it possible getting the styles of a specific class?

    In the jQuery Google Group you have posted something like “var style = $.parsecss()[‘.myclassname’]; “
    Using this with the current version I get an error (“str has no properties”).

    Using the script version on http://bililite.nfshost.com/blog/blogfiles/cssparser/cssparser.jquery.js this works in Firefox but not in IE.

    Thanks for your help.

  4. Thomas Jaggi says:

    Whoups, my fault: I tried animating a z-index which killed IE.
    But still: Is this also possible with the new version (just getting the styles of a specific class)? I understand this is not the main purpose but anyway…

  5. Danny says:

    @Thomas Jaggi:
    The version I mentioned on the jQuery Google group actually returned a parsed object; it did not use a callback. It used synchronous Ajax, which is always a bad idea. The current version is better.
    Parsing the CSS is probably not the best way of getting the styles of a specific class. You can go through the stylesheets (see this discussion, but realize the code is untested: http://groups.google.com/group/jquery-en/browse_thread/thread/a95f419f185f63dd/03df7ac2f2c82bfe), using something like this:

    function classStyle (className){
      var re = new RegExp('(^|,)\\s*\\.'+className+'\\s*(,|$)'); 
      var style = "";
      $.each (document.styleSheets, function(){
        $.each (this.cssRules || this.rules, function() {
          if (re.test(this.selectorText)) style += this.style.cssText + ';';
      return style;

    or see the jQuery UI code for animating classes for a clever trick: create an element, record the styles, then apply the class name and see what changed.

  6. Thomas Jaggi says:

    Thanks a lot for your help, now I’m using the function above (with some changes to get it working in IE).

  7. James Vivian says:

    Am I missing something, or has the linked code been updated beyond this post? It seems like calls to $.cssparser should be $.parsecss instead.

  8. Danny says:

    @James Vivian:
    You’re absolutely right! I can’t imagine how I missed that. I’m changing everything to parsecss now.
    Thanks for catching my idiocy.

  9. MJ says:

    Could you help clarify which js file is the most recent. The Download code link doesn’t go the correct file and is named differently than what’s in the git repository. However, both files are labeled in the comments as version 1.0 with differences between the two. Thanks for the help in advance.

  10. Danny says:

    You are right. That’s the problem with changing the name of the file (which I did in response to James Vivian above). I’ve updated the link to the source code, made it version 1.1 (there was a minor bug fix) and removed the github link entirely, since it takes too much time to maintain it.

  11. ft says:

    Thanks for sharing! Is it theoretically possible to modify your code that it searches for say “border-radius” and apply according actions for ie?

  12. Danny says:

    Sure. Just use

    	for (var selector in css){
    		for (var property in css[selector]){
    			if (property == 'border-radius'){
    				// fix it appropriately; css[selector][property] is the value

    You still need to apply this only for IE (with conditional comments, $.browser or something. Up to you)

  13. Mark says:

    Very cool. I’d really like to use this, but it’s not working on the current version of jQuery (1.4.3). The most recent version that I can get it working with is 1.3.2. Any chance you’d be able to update it?

    Thanks a bunch.

  14. Danny says:

    The demo is using 1.3.1 (I didn’t realize it wasn’t using the most up-to-date version), though the site I’m maintaining (youngisrael-stl.org) uses the Google API to get the most recent version and it seems to work. I’ll try to look at it. Do you have a sense of what’s going wrong?

  15. Mark says:

    Thanks for the quick reply. I am really excited about this project and would really like to use it.
    I created two test pages that are identical except for (1) the title and (2) the call to the jQuery library.

    jQuery 1.3.2:

    jQuery 1.4.3:

  16. Danny says:

    I think I’ve got it, though I’m not in a position to try it. jQuery changed the definition of $() from $(document) to $([]), a change I argued for years ago but now broke my code.

    Try using $(document).parsecss($.parsecss.jquery); instead of $().parsecss and see if it works!


  17. Mark says:

    Thanks! Your solution worked.

    I’ve been interested in this approach since early 2008 when I saw a post talking about Ben Nolan’s Behaviors library [http://code.google.com/p/cruiser/wiki/Behaviors].

    Yours has some pluses for me (1) uses jQuery, (2) supports @imported style sheets, and (3) is in current development.

    Now I can use a stylesheet switcher to not only change the presentation, but also the behaviors.

    Thanks again.

  18. Danny says:

    I’m glad it worked. I updated the post to reflect the changes. Using a stylesheet switcher sounds useful, but you’ll have to make sure to run parsecss again after you switch and make sure that the old behaviors are removed. I think it will work well, but there is no “unparsecss” to go through and remove the old stuff. Let me know how it goes!

  19. links for 2010-11-12 at Here I Rule says:

    […] Hacking at 0300 : jQuery CSS parser (tags: awesome javascript jquery css parser) […]

  20. Mark says:


    You said “Generally, you would want to parse all the stylesheets in the document, $(document), so the simple call is: $(document).parsecss($.parsecss.jquery).”

    I noticed that my CSS files are being retrieved twice. Is there a way to specify which document(s) I’m parsing?
    But, it didn’t work.

    Thanks Again

  21. Danny says:

    The browser GETs the stylesheets once to apply it to the page, but parsecss doesn’t have access to that, so it has to use AJAX to GET it again. So yes, the stylesheet are retrieved twice. I don’t see a way around that. Doing $("LINK[href='/path/to.css']").parsecss(...) certainly should work. Are you sure that is the only parsecss call and that other stylesheets are being parsed? If that’s the case, then it’s a bug.

  22. Mark says:


    Wow, you’re very responsive.

    I did some checking. The reference to the css file was absolute. I change it to relative and it’s working.

    However, this uncovered something interesting. If I don’t call the css file via a link element, it doesn’t get parsed. It appears that the $(“LINK[href=’path/to.css’]”) must also be called in the head in a link element – even if the css file contains an @import to the css file.

    CSS Structure
    html has a link element to main.css
    main.css @imports reset.css, structure.css, presentation.css, and behaviors.css

    Even though main.css @imports behaviors.css, behaviors.css does not get parsed unless I either (1) use $(“LINK[href=’path/to/styles/main.css’]”).parsecss($.parsecss.jquery) or (2) place a link element in the head that references behaviors.css.

    I have two examples. They are identical with one exception.
    Example 1 – One Link Element which calls main.css

    Example 2 – Two Link Elements; one calls main.css, one calls behaviors.css

    Perhaps, this is a limitation of jQuery or JavaScript.

    Thanks again.

  23. Danny says:

    This may be a limitation of the “same origin” security policy. The browser will reject any AJAX attempt from an absolute URL (even if it really is from the same server). It doesn’t work that hard; the algorithm is starts with http: -> rejected. At least that’s the way it was when I wrote the code. To avoid errors that stop the parsing, the parser checks the url with !/^\w+:/.test($(this).attr('href')). Therefore, your @import statements will fail. Hope that makes sense.

    I’m not 100% sure that that test is the correct one to distinguish legal vs. illegal URL’s for AJAX, so if you want to correct me, go ahead. Also, the regular expression used to be /\w+:/; I very recently changed it to /^\w+:/. It shouldn’t make a difference for your application, though.


  24. Danny says:

    Looking into it more (e.g, Mozilla), the browsers claim that even an absolute URL with the protocol should be OK if it’s the same protocol and host. I remember it not working when I wrote this, but things may have changed, or I may have gotten it wrong. Anyway, try it with URL’s without the protocol and see if it works.

  25. Brian says:

    I’m trying to use just the parsscss piece of this for another purpose, not CSS, but something with CSS conforming syntax – but I’m running into a problem with the parser. My property values in this purpose occasionally contain something like (unquoted) something(foo:bar). I don’t see anything in the CSS recommendations/drafts which would disallow this, but the parser returns me only: “something(foo”.

    Is it a weakness/but in the parser – or is there something in the CSS standards that I’m missing?

  26. Danny says:

    The way I’m reading it, the grammar for CSS specifies that non=”identifier” characters ([-_A-Za-z0-9]) need to be escaped with backslashes, and the parser recognizes that. Try:
    The parser uses split on the ‘:’, so anything after the colon is lost if the colon is not escaped.

  27. Brian says:


    I think that that cannot be the case for functional values as even the CSS specs themselves have colons in, for example, the call to url(). See http://www.w3.org/TR/CSS21/syndata.html#uri


  28. Danny says:

    You are right about the URL; I always put the URL in quotes. Note that the W3C realize that URL’s aren’t consistent with the grammar as a whole and have to add the note about URIs may contain characters that would otherwise be used as delimiters….
    Fixing this is straightforward if tedious: the lines in parsedeclarations of

    decl = decl.split(':');
    parsed[restore(decl[0])] = restore(decl[1]);

    would have to replace decl[1] with decl.slice(1).join(':').
    I’ll get on it.

  29. Brian says:

    Excellent Danny. I haven’t looked at the implementation but I assume that you would hold that rule for really any functional notation as CSS3 attributes moves forward and beyond.

    Nice library.

  30. Mark says:


    I recently tried to upgrade to jQuery version 1.5.2 and ran into an error (str.replace is not a function – line 163).

    I have a test page working with version 1.4.4:

    I’ve tried putting a local copy of 1.5.2 on my server and using Google’s CDN. Both options result in the same error.

    Thanks for the great library!

  31. Danny says:

    I’m not getting any errors with jQuery 1.5.2 from the Google CDN; I just updated the demo to use the CDN and it’s working fine. Can you post a sample page using 1.5.2 (and including all the code directly rather than using head.js) and I can try to figure it out?

  32. Mark says:

    Thanks for looking into this. I think I have been running into caching on my server. It’s working great now.

  33. Hacking at 0300 : jQuery CSS parser - EtondeGroup Blog of Web Applications | EtondeGroup Blog of Web Applications says:

    […] more here: Hacking at 0300 : jQuery CSS parser Bookmark on Delicious Digg this post Recommend on Facebook share via Reddit Share with Stumblers […]

  34. Brad says:

    I think I have stumbled across a bug. At line 197, where type is being compared to ‘import’, there’s a single ‘=’ vs. ‘==’. FYI, this is causing problems with CSS web fonts (the @font-face selector).

  35. Danny says:

    Nice catch! I’m sure I tested this with @media and @import rules but didn’t try any other @ rules, which meant that the last condition was always true and I never knew. It will be corrected posthaste.

  36. Brad says:

    A quick question: when you request the page again, to parse attributes, the $get passes along ‘text’ as a query string parameter. Did you intend instead to have the request have a ‘text’ dataType (vs. ‘html’, which will execute JS)? If not, I’m curious as to the purpose of this query string parameter.

  37. Danny says:

    You are correct again. I got the parameter order backwards; I’ll correct that. Thanks!

  38. Brad says:

    Thank you for this terrific piece of code!

  39. Danny says:

    And thank you for analyzing it in such detail!
    I’m not a Real Programmer; no pair programming, no test-driven development, no source version control (which I keep kicking myself about and will no doubt come to regret). I just solve problems as I come across them and release what I’ve learned. It’s nice to have someone looking over my shoulder.

  40. Eve says:

    Maybe your parser could be used to write something like KSS (Kinetic Style Sheets) in jQuery.

    That would be great.

  41. Danny says:

    That’s exactly the sort of thing that the parser could do; it looks like KSS has its own CSS parser.

  42. Norman H says:

    Great piece of code. I this still project being maintained ?

    Also, Is there a way to parse back into CSS from a JSON object ? I’m planning on using this for a project that requires CSS to be parsed to a JSON object, and then back to CSS after a few changes have been made to the object. Sorry if this was a silly one, but I couldn’t find a way to parse the object back into CSS.

    – Norman

  43. Danny says:

    Yes, I’m still maintaining it but it’s been stable for a long time. I haven’t needed any new features.
    What do you mean by “back into CSS”? To apply the styles, just use jQuery: $(…).css(modified_object). Why would you need to create the text of the CSS?

  44. Raghavendra Chary says:

    Hi Danny,
    We are using your parsecss. It works great. But there was a memory leak. After calling it around 100 times chrome browser crashes. We looked at the code and observed that global varible munged is increasing the heap memory (700MB) and this causes the chrome browser to crash.
    We have reset this varible to empty dictionary in function as follows
    $.parsecss = function(e, t) {
    var n = {};
    e = munge(e).replace(/@(([^;`]|`[^b]|`b[^%])*(`b%)?);?/g, function(e, n) {
    processAtRule($.trim(n), t);
    return “”
    $.each(e.split(“`b%”), function(e, t) {
    t = t.split(“%b`”);
    if (t.length < 2) return;
    t[0] = restore(t[0]);
    n[t[0]] = $.extend(n[t[0]] || {}, parsedeclarations(t[1]))
    munged = {};
    It works good now. Let me know if it is OK to reset it.

  45. Danny says:

    @Raghavendra Chary:
    Thank you for looking at this. Would you create a pull request on this: https://github.com/dwachss/jquery-css-parser and I will incorporate it?

  46. Danny says:

    Actually, looking more closely at the code, I think the solution would be to make munged a local variable in $.parsecss, and pass it to all subsequent functions, and rely on the garbage collector to clean it up. I will create an issue ( https://github.com/dwachss/jquery-css-parser/issues/1 ) and I will look at it.
    Your solution (just resetting munged at the end of the function) works as long as you don’t have any @-rules in your CSS.

  47. Danny says:

    @Raghavendra Chary:
    I think I’ve solved this with the newest version, 1.6. See https://github.com/dwachss/jquery-css-parser and let me know if this closes the memory leak.

  48. Evert says:

    Thnak you! I was planning on writing something similar. However what I want is a seperate ‘style’-sheet that only contains behaviour (seperation = maintainability).
    So I was wondering, just glancing over the code, would it be enough to change the line:
    .findandfilter(‘link[type=”text/css”]’).each(function () {

    into something like :
    .findandfilter(‘link[data-type=”js”]’).each(function () {


  49. Danny says:

    That looks like it ought to work.

Leave a Reply

Warning: Undefined variable $user_ID in /home/public/blog/wp-content/themes/evanescence/comments.php on line 75