/* * This file is part of CSSTidy. * * CSSTidy is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * CSSTidy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CSSTidy; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "csspp_globals.hpp" using namespace std; /* is = in selector * ip = in property * iv = in value * instr = in string (-> ",',( => ignore } and ; etc.) * ic = in comment (ignore everything) * at = in @-block */ extern map all_properties,replace_colors; extern map< string, vector > shorthands; extern map at_rules; void csstidy::parse_css(string css_input) { input_size = css_input.length(); css_input = str_replace("\r\n","\n",css_input); // Replace all double-newlines css_input += "\n"; parse_status status = is, from; cur_property = ""; // if you can explain the need for this please do so string temp_add,cur_comment,temp; vector cur_sub_value_arr; char str_char; bool str_in_str = false; bool invalid_at = false; bool pn = false; int str_size = css_input.length(); for(int i = 0; i < str_size; ++i) { if(css_input[i] == '\n' || css_input[i] == '\r') { ++line; } switch(status) { /* Case in-at-block */ case at: if(is_token(css_input,i)) { if(css_input[i] == '/' && s_at(css_input,i+1) == '*') { status = ic; i += 2; from = at; } else if(css_input[i] == '{') { status = is; add_token(AT_START, cur_at); } else if(css_input[i] == ',') { cur_at += trim(cur_at) + ","; } else if(css_input[i] == '\\') { cur_at += unicode(css_input,i); } } else { int lastpos = cur_at.length()-1; if(lastpos == -1 || !( (ctype_space(cur_at[lastpos]) || is_token(cur_at,lastpos) && cur_at[lastpos] == ',') && ctype_space(css_input[i]))) { cur_at += css_input[i]; } } break; /* Case in-selector */ case is: if(is_token(css_input,i)) { if(css_input[i] == '/' && s_at(css_input,i+1) == '*' && trim(cur_selector) == "") { status = ic; ++i; from = is; } else if(css_input[i] == '@' && trim(cur_selector) == "") { // Check for at-rule invalid_at = true; for(map::iterator j = at_rules.begin(); j != at_rules.end(); ++j ) { if(strtolower(css_input.substr(i+1,j->first.length())) == j->first) { (j->second == at) ? cur_at = "@" + j->first : cur_selector = "@" + j->first; status = j->second; i += j->first.length(); invalid_at = false; } } if(invalid_at) { cur_selector = "@"; string invalid_at_name = ""; for(int j = i+1; j < str_size; ++j) { if(!ctype_alpha(css_input[j])) { break; } invalid_at_name += css_input[j]; } log("Invalid @-rule: " + invalid_at_name + " (removed)",Warning); } } else if(css_input[i] == '"' || css_input[i] == '\'') { cur_selector += css_input[i]; status = instr; str_char = css_input[i]; from = is; } else if(invalid_at && css_input[i] == ';') { invalid_at = false; status = is; } else if(css_input[i] == '{') { status = ip; add_token(SEL_START, cur_selector); ++selectors; } else if(css_input[i] == '}') { add_token(AT_END, cur_at); cur_at = ""; cur_selector = ""; sel_separate = vector(); } else if(css_input[i] == ',') { cur_selector = trim(cur_selector) + ","; sel_separate.push_back(cur_selector.length()); } else if(css_input[i] == '\\') { cur_selector += unicode(css_input,i); } else cur_selector += css_input[i]; } else { int lastpos = cur_selector.length()-1; if(!( (ctype_space(cur_selector[lastpos]) || is_token(cur_selector,lastpos) && cur_selector[lastpos] == ',') && ctype_space(css_input[i]))) { cur_selector += css_input[i]; } } break; /* Case in-property */ case ip: if(is_token(css_input,i)) { if(css_input[i] == ':' || css_input[i] == '=' && cur_property != "") // IE really accepts =, so csstidy will fix those mistakes { status = iv; bool valid = (all_properties.count(cur_property) > 0 && all_properties[cur_property].find(css_level,0) != string::npos); if(valid || !settings["discard_invalid_properties"]) { add_token(PROPERTY, cur_property); } } else if(css_input[i] == '/' && s_at(css_input,i+1) == '*' && cur_property == "") { status = ic; ++i; from = ip; } else if(css_input[i] == '}') { explode_selectors(); status = is; invalid_at = false; add_token(SEL_END, cur_selector); cur_selector = ""; cur_property = ""; } else if(css_input[i] == ';') { cur_property = ""; } else if(css_input[i] == '\\') { cur_property += unicode(css_input,i); } } else if(!ctype_space(css_input[i])) { cur_property += css_input[i]; } break; /* Case in-value */ case iv: pn = ((css_input[i] == '\n' || css_input[i] == '\r') && property_is_next(css_input,i+1) || i == str_size-1); if(pn) { log("Added semicolon to the end of declaration",Warning); } if(is_token(css_input,i) || pn) { if(css_input[i] == '/' && s_at(css_input,i+1) == '*') { status = ic; ++i; from = iv; } else if(css_input[i] == '"' || css_input[i] == '\'' || css_input[i] == '(') { cur_sub_value += css_input[i]; str_char = (css_input[i] == '(') ? ')' : css_input[i]; status = instr; from = iv; } else if(css_input[i] == '\\') { cur_sub_value += unicode(css_input,i); } else if(css_input[i] == ';' || pn) { if(cur_selector.substr(0,1) == "@" && at_rules.count(cur_selector.substr(1)) > 0 && at_rules[cur_selector.substr(1)] == iv) { cur_sub_value_arr.push_back(trim(cur_sub_value)); status = is; if(cur_selector == "@charset") charset = cur_sub_value_arr[0]; if(cur_selector == "@namespace") namesp = implode(" ",cur_sub_value_arr); if(cur_selector == "@import") import.push_back(implode(" ",cur_sub_value_arr)); cur_sub_value_arr.clear(); cur_sub_value = ""; cur_selector = ""; sel_separate = vector(); } else { status = ip; } } else if(css_input[i] != '}') { cur_sub_value += css_input[i]; } if( (css_input[i] == '}' || css_input[i] == ';' || pn) && !cur_selector.empty()) { ++properties; if(cur_at == "") { cur_at = "standard"; } // Kill all whitespace cur_at = trim(cur_at); cur_selector = trim(cur_selector); cur_value = trim(cur_value); cur_property = trim(cur_property); cur_sub_value = trim(cur_sub_value); // case settings if(settings["lowercase_s"]) { cur_selector = strtolower(cur_selector); } cur_property = strtolower(cur_property); if(cur_sub_value != "") { cur_sub_value = optimise_subvalue(cur_sub_value,cur_property); cur_sub_value_arr.push_back(cur_sub_value); cur_sub_value = ""; } cur_value = implode(" ",cur_sub_value_arr); // Compress !important temp = c_important(cur_value); if(temp != cur_value) { log("Optimised !important",Information); } cur_value = temp; // Optimise shorthand properties if(shorthands.count(cur_property) > 0) { temp = shorthand(cur_value); if(temp != cur_value) { log("Optimised shorthand notation (" + cur_property + "): Changed \"" + cur_value + "\" to \"" + temp + "\"",Information); } cur_value = temp; } // Compress font-weight (tiny compression) if(cur_property == "font-weight" && settings["compress_font-weight"]) { int c_fw = c_font_weight(cur_value); if(c_fw == 400) { log("Optimised font-weight: Changed \"bold\" to \"700\"",Information); } else if(c_fw == 700) { log("Optimised font-weight: Changed \"normal\" to \"400\"",Information); } } bool valid = (all_properties.count(cur_property) > 0 && all_properties[cur_property].find(css_level,0) != string::npos); if((!invalid_at || settings["preserve_css"]) && (!settings["discard_invalid_properties"] || valid)) { add(cur_at,cur_selector,cur_property,cur_value); add_token(VALUE, cur_value); // Further Optimisation if(cur_property == "background" && settings["optimise_shorthands"] > 1) { map temp = dissolve_short_bg(cur_value); css[cur_at][cur_selector].erase("background"); for(map::iterator it = temp.begin(); it != temp.end(); ++it ) { add(cur_at,cur_selector,it->first,it->second); } } if(shorthands.count(cur_property) > 0 && settings["optimise_shorthands"] > 0) { map temp = dissolve_4value_shorthands(cur_property,cur_value); for(map::iterator it = temp.begin(); it != temp.end(); ++it ) { add(cur_at,cur_selector,it->first,it->second); } if(shorthands[cur_property][0] != "0") { css[cur_at][cur_selector].erase(cur_property); } } } if(!valid) { if(settings["discard_invalid_properties"]) { log("Removed invalid property: " + cur_property,Warning); } else { log("Invalid property in " + strtoupper(css_level) + ": " + cur_property,Warning); } } //Split multiple selectors here if necessary cur_property = ""; cur_sub_value_arr.clear(); cur_value = ""; } if(css_input[i] == '}') { explode_selectors(); add_token(SEL_END, cur_selector); status = is; invalid_at = false; cur_selector = ""; } } else if(!pn) { cur_sub_value += css_input[i]; if(ctype_space(css_input[i])) { if(trim(cur_sub_value) != "") { cur_sub_value = optimise_subvalue(cur_sub_value,cur_property); cur_sub_value_arr.push_back(trim(cur_sub_value)); } cur_sub_value = ""; } } break; /* Case in-string */ case instr: if(str_char == ')' && css_input[i] == '"' && str_in_str == false && !escaped(css_input,i)) { str_in_str = true; } else if(str_char == ')' && css_input[i] == '"' && str_in_str == true && !escaped(css_input,i)) { str_in_str = false; } if(css_input[i] == str_char && !escaped(css_input,i) && str_in_str == false) { status = from; } temp_add = ""; temp_add += css_input[i]; if( (css_input[i] == '\n' || css_input[i] == '\r') && !(css_input[i-1] == '\\' && !escaped(css_input,i-1)) ) { temp_add = "\\A "; log("Fixed incorrect newline in string",Warning); } if(from == iv) { cur_sub_value += temp_add; } else if(from == is) { cur_selector += temp_add; } break; /* Case in-comment */ case ic: if(css_input[i] == '*' && s_at(css_input,i+1) == '/') { status = from; ++i; add_token(COMMENT, cur_comment); cur_comment = ""; } else { cur_comment += css_input[i]; } break; } } if(settings["merge_selectors"] > 1) { for(css_struct::iterator i = css.begin(); i != css.end(); i++ ) { merge_selectors(i->second); } } if(settings["optimise_shorthands"] > 0) { for(css_struct::iterator i = css.begin(); i != css.end(); ++i ) { for(sstore::iterator j = i->second.begin(); j != i->second.end();) { merge_4value_shorthands(i->first,j->first); if(settings["optimise_shorthands"] > 1) { merge_bg(j->second); } if(j->second.size() == 0) { i->second.erase(j); } else { ++j; } } } } } string csstidy::optimise_subvalue(string subvalue, const string property) { subvalue = trim(subvalue); string temp = compress_numbers(subvalue,property); if(temp != subvalue) { if(temp.length() > subvalue.length()) { log("Fixed invalid number: Changed \"" + subvalue + "\" to \"" + temp + "\"",Warning); } else { log("Optimised number: Changed \"" + subvalue + "\" to \"" + temp + "\"",Information); } subvalue = temp; } if(settings["compress_colors"]) { temp = cut_color(subvalue); if(temp != subvalue) { if(replace_colors.count(subvalue) > 0) { log("Fixed invalid color name: Changed \"" + subvalue + "\" to \"" + temp + "\"",Warning); } else { log("Optimised color: Changed \"" + subvalue + "\" to \"" + temp + "\"",Information); } subvalue = temp; } } return subvalue; }