RegEx help to change properties in CSS - css

I ned some help with RegEx, not sure what I am doing wrong.
What I would like to achieve is to change the value of one CSS property in a CSS file without altering anything within that CSS file.
<?php
$var = '
#css {
text-size: 14px;
color: red;
background: orange;
}
';
echo preg_replace('/#css(.*)color: (.*);(.*)}/is','#css$1color: black;$3 }',$var);
?>
The result I am hoping to see is this:
#css {
text-size: 14px;
color: red;
background: orange;
}
However what I get is this:
#css {
text-size: 14px;
color: black;
}
I am not an expert in RegEx at all but after reading manuals and examples online I thought I could use backreferences to do this and that $1 would be the result of the first match, $2 of the second, $3 of the third etc... In my example $1 matches everything between '#css' and 'color: ' and then $2 I don't use because I want to replace 'red' with 'black'. I thought $3 would be the result of everything between ';' and '}' but it gets lost somewhere or, more likely, I am lost somewhere ;-)
Thanks for the advice and support.

First of all, using the greedy .* is always problematic. In your case, it cosumes too much symbols so that you lose the background because it matches the last ; in your code (which comes after the background declaration). Instead use a negative character class which matches until the very next symbol you know should not be included in the match - in your case the ;. So the character class should look like: [^;]*. The same is true with matching the } symbol - use a negative character class instead.
Secondly, I would try to reduce the usage of capture groups.
And finally, I would reduce the clutter and put everything you don't want to replace into the capturegroups before and after so that you get a very simple result: '$1black$2'
Try the following regex:
preg_replace('/(#css.*?color:)[^;]*([^}]*)/is','$1black$2',$var);
See the demo
Note that this regex has one flaw: If you have different colors in your decplarations (background-color, border-color,...), it will break! So you should include an additional whitespace to make sure it only captures the "real" color declaration:
/(#css.*?\scolor:)[^;]*([^}]*)/is
This still might break if (which should not happen) per accident you have multiple color:xyz; declarations in your rule block. Only the first one gets replace then.

The problem here is that you're matching greedily for your second capture group. In doing so, it consumes everything up until the final semicolon in your text.
What you should be doing instead is using a lazy matching group instead which will match as few characters as possible. (eg. only up until the first semicolon, rather than the last)
preg_replace('/#css(.*)color: .*?;(.*)}/is','#css$1color: black;$2 }',$var);

As I understand your problem, I think this will solve your issue.
$var = '
#css {
text-size: 14px;
color: red;
background: orange;
}
';
preg_match_all('/(#css\s*\{[\sA-z0-9#:;-]*color\s*:\s*[A-z0-9#]*)/', $var, $output);
$temp = preg_replace('/(color\s*:\s*[A-z0-9#]*)/im', 'color: black', $output[0][0]);
echo preg_replace('/(#css\s*\{[\sA-z0-9#:;-]*color\s*:\s*[A-z0-9#]*)/im', $temp, $var);
?>

Related

Find CSS class names that are not inside comments using Regular Expressions

I'm trying to replace all class names in a CSS file. I am using JavaScript/Node.js.
My current solution is this: /\.[a-z][a-z0-9-_]*/g.
At the end of the file is a comment that references the source map file: /*# sourceMappingURL=style.css.map */. Now the file extensions of that URL also get replaced.
Given the following input:
.text-center { text-align: center; }
table.simple{background:#fff;}.bg-white{background:#fff;}
/*# sourceMappingURL=style.css.map */
/*
Example comment file.css
*/
the result is:
.a { text-align: center; }
table.a{background:#fff;}.a{background:#fff;}
/*# sourceMappingURL=style.a.a */
/*
Example comment file.a
*/
what I want is:
.a { text-align: center; }
table.a{background:#fff;}.a{background:#fff;}
/*# sourceMappingURL=style.css.map */
/*
Example comment file.css
*/
How do I have to change my RegEx so it does only match class names, that are outside of comments (// comments are not relevant here)?
Try this regex:
\.[^{\s]*(?=\s*{)
and replace each match with .a
Click for Demo
Explanation:
\. - matches a .
[^{\n]* - matches 0+ occurrences of any character that is neither a white-space nor {
(?=\s*{) - positive lookahead to make sure that the current position is followed by 0+ whitespaces followed by {.
Now, the above regex would fail if we have comments of the format
/*# sourceMappingURL=style.css.map {*/
i.e, { present after css.map inside a comment. So, to handle such a case, you can use the following regex(almost similar to the previous regex. Just one more negative lookahead to the regex)
\.[^{\s]*(?=\s*{)(?!(?:(?!\/\*)[\s\S])*\*\/)
Click for Demo
Explanation:
\.[^{\s]*(?=\s*{) - similar to the previous regex
(?!(?:(?!\/\*)[\s\S])*\*\/) - Negative lookahead to make sure that the current position(position right after the .classname) is not present within a comment of the form /*...*/
(?!.... - negative lookahead
(?:(?!\/\*)[\s\S])* - tempered greedy token to match 0+ occurrences of any character greedily, which does not begin with the sequence /*
\*\/ - matches */
Both of these approaches work in different cases like classes between ids or multiple classes in selection:
.class-a, #id-a, .class-b:hover {}
First approach
In this first approach you can match both comment section and CSS classes then replace class names while they are matched and leave comments intact:
\/\*[\s\S]*?\*\/|(\.[a-z_-][\w-]*)(?=[^{}]*{)
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Match comments Match and capture classes
Breakdown:
\/\*[\s\S]*?\*\/ # Match a comment block
| # Or
( # Start of capturing group #1
\.[a-z_-][\w-]* # Match a CSS class name
) # End of CG #1
(?= # Start of a positive lookahead
[^{}]*{ # Class should be followed by a `{` (may not be immediately)
) # End of lookahead
JS code:
var str = `.text-center { text-align: center; }
table.simple{background:#fff;}.bg-white{background:#fff;}
/*# sourceMappingURL=style.css.map */
/*
Example comment file.css
*/`
console.log(str.replace(/\/\*[\s\S]*?\*\/|(\.[a-z_-][\w-]*)(?=[^{}]*{[^{}]*})/g,
function($0, $1) {
return $1 ? '.a' : $0; // If a class is matched ...
}
));
Second approach
If you don't think in comments such occurrences will exist:
/* style.css {css file} */
you can omit matching comments in first approach:
\.[a-z_-][\w-]*(?=[^{}]*{[^{}]*})
^^^^^^^
Added for a more strict selection
RegEx live demo
JS code:
var str = `.text-center { text-align: center; }
table.simple{background:#fff;}.bg-white{background:#fff;}
/*# sourceMappingURL=style.css.map */
/*
Example comment file.css
*/`
console.log(str.replace(/\.[a-z_-][\w-]*(?=[^{}]*{[^{}]*})/g, '.a'));
I've got it to work with the following RegEx in pcre flavor
/(\/\*)?(?(1)(.*\*\/)|(\.[a-z][a-z0-9-_]*))/g
I'm using a conditional statement. Whenever it detects a start of a comment, it does not look for a class name but for the end of the comment. However, this does not work for comments on multiple lines. The class names are the third capturing group. So to replace class names, only touch matches that contain the capturing group 3.
I didn't notice I was using pcre instead of javascript on regex101.com and ended up with a syntax error in my JS code. I'm now working on finding a solution that works for JS.
Edit:
I now ported it to JS:
/(?=\/\*)|(\.[a-z][a-z0-9-_]*)(?!.*\*\/)/g
I still have to find a way to get it to work with multiple lines.
With the following regex you should be able to extract all class-names from your css file, which are not in comment.
It´s important that the class name start with a dot (.)
Regex:
/^.[a-z][a-z0-9-_].*?[\s]/mg

Regexp to find all CSS rules not ending with ";"

I'm trying to write regexp to find all rules in CSS filest that don't have semicolon at the end:
.abc {
height: 100%;
margin: auto;
text-align: center /* <-- like this one */
width: 100%;
}
i've tried this [^;\{\}]\n but it not excluding { and } from search. Any ideas?
What you need is a CSS parser.
However, if this is a one-off job, and your regex engine supports lookbehinds, you can use this regex:
^.*(?<![;{}])$
Visualization:
(?<![;{}]) is a negative lookbehind that asserts that the line should end with a character that's not ;, { or }.
Note that the regex is far from perfect and fails to match properly if a CSS block is used by more than one class/id or if every class/id is separated by a line break.
RegEx Demo
In perl you can easily do this,
e.g:
#!/usr/bin/perl
# open your file
open(FILE,"filename.css");
my #fileLines = <FILE>;
close FILE;
my $lineCount=1;
# check all lines of your file
foreach my $line(#fileLines){
# erase endline spaces
$line=~ s/\s+$//;
# erase begin of line
$line=~ s/^\s+//;
# simple perl regex, it doesn't watch for comments
if($line !~ /\;$/ && $line !~ /{$/ && $line !~/}$/){
print $lineCount.':'.$line.'\n';
}
$lineCount++;
}
This small program will show you lines which not finnish by ';'.
You could just run the file through http://csslint.net

How to change SASS output style in order to make a new line after each ending curly brace?

The processed code looks like this:
.body {
color: #eeeeee;
}
.someting {
color: #dddddd;
}
I want it to be:
.body {
color: #eeeeee;
}
.someting {
color: #dddddd;
}
Is there such a possibility? Google can't find an answer.
I bet you could write a simple regex find-replace looking for }'s and replacing with }\n, and have Grunt execute that on your css (post-compilation from SASS).
This looks like it'd do the trick:
https://npmjs.org/package/grunt-regex-replace
I think the closest you can get is expanded. The extra line break won't be there when you're nesting but your example code would output exactly like you demonstrated.
To answer this question, you can go to rubygems/gems/sass-3.4.9/lib/sass/tree/visitors/to_css.rb (or anywhere your to_css file is), and edit this:
output("}" + trailer) to output("}\n" + trailer)
And then remove this newline:
trailer = "\n" if node.group_end
It might have been an oversight when parsing the nesting, because the newline set on "trailer" applies to every other line (so you'd have double the lines without removing it if you don't nest anything).

How to make ctags + scss play nice

UPDATE:
This question has been modified to reflect the very helpful comments and answer below. I've accepted the answer, but the full functionality is not working to-date.
Contents of .ctags (in ~/)
-R
--exclude=.git
--exclude=log
--verbose=yes
--langdef=scss
--langmap=scss:.scss
--regex-scss=/^[ \t]*([^\t {][^{]{1,100})(\t| )*\{/| \1/d,definition/
--regex-scss=/^[#]mixin ([^ (]+).*/\1/m,mixing/
When I place my cursor under the target, vim says E426 tag not found: tag_name
Consider the following pattern:
footer{
.wrapper{
.general-info{
.footer-links{
a{#include ticker($bg, $white);}
}
}
}
}
In a separate file (modules.scss) in the directory, I have the definition for ticker:
#mixin ticker($color, $bg-color) {
color: $color;
background-color: $bg-color;
}
When I place my cursor under the target, vim still says E426 tag not found: tag_name
ctags does not index the ticker mixin. However I can use ctags to find methods from SCSS gem directly (e.g. darken).
adding a \ before the last { gives no warning when using ctags.
I don't know if the tags produced give the desired result since I don't know the language.
The result would be:
--langdef=scss
--langmap=scss:.scss
--regex-scss=/^[ \t]*([^\t {][^{]{1,100})(\t| )*\{/| \1/d,definition/
Update: like I mentioned above, I don't know the language, so it is hard to check the actual definition of the tags.
I looked online and the following code is listed as scss in some webpage.
Suppose the tags you want to get are the words following mixing.
#mixin table-scaffolding {
th {
text-align: center;
font-weight: bold;
}
td, th { padding: 2px; }
}
#mixin left($dist) {
float: left;
margin-left: $dist;
}
#data {
#include left(10px);
#include table-scaffolding;
}
then with the following:
--langdef=scss
--langmap=scss:.scss
--regex-scss=/^[ \t]*([^\t {][^{]{1,100})(\t| )*\{/| \1/d,definition/
--regex-scss=/^[#]mixin ([^ (]+).*/\1/m,mixin/
--regex-scss=/^[#]function ([^ (]+).*/\1/f,function/
you get the two new tags left and table-scaffolding.
So if I am in the word left inside data hit ctrl+] it jumps to the line where data is defined.
You have to be careful for the other keyword because it has a - in the word. So if you are on table and press ctrl+] you will get the same error tag not found. For this to work you have to add the following in your .vimrc
set iskeyword+=-
You should be able to generalize the above for other tags you need, or even build a general regular expression to handle all the tags, as you initially meant.
If you post a specific example of how the file you are trying to work with looks like, and what are the tags you are trying to obtain I am sure I or other people would be able to help figure out a expression for that.

CSS text-transform capitalize on all caps

Here is my HTML:
small caps &
ALL CAPS
Here is my CSS:
.link {text-transform: capitalize;}
The output is:
Small Caps & ALL CAPS
and I want the output to be:
Small Caps & All Caps
Any ideas?
You can almost do it with:
.link {
text-transform: lowercase;
}
.link:first-letter,
.link:first-line {
text-transform: uppercase;
}
It will give you the output:
Small Caps
All Caps
There is no way to do this with CSS, you could use PHP or Javascript for this.
PHP example:
$text = "ALL CAPS";
$text = ucwords(strtolower($text)); // All Caps
jQuery example (it's a plugin now!):
// Uppercase every first letter of a word
jQuery.fn.ucwords = function() {
return this.each(function(){
var val = $(this).text(), newVal = '';
val = val.split(' ');
for(var c=0; c < val.length; c++) {
newVal += val[c].substring(0,1).toUpperCase() + val[c].substring(1,val[c].length) + (c+1==val.length ? '' : ' ');
}
$(this).text(newVal);
});
}
$('a.link').ucwords();​
Convert with JavaScript using .toLowerCase() and capitalize would do the rest.
Interesting question!
capitalize transforms every first letter of a word to uppercase, but it does not transform the other letters to lowercase. Not even the :first-letter pseudo-class will cut it (because it applies to the first letter of each element, not each word), and I can't see a way of combining lowercase and capitalize to get the desired outcome.
So as far as I can see, this is indeed impossible to do with CSS.
#Harmen shows good-looking PHP and jQuery workarounds in his answer.
I'd like to sugest a pure CSS solution that is more useful than the first letter solution presented but is also very similar.
.link {
text-transform: lowercase;
display: inline-block;
}
.link::first-line {
text-transform: capitalize;
}
<div class="link">HELLO WORLD!</div>
<p class="link">HELLO WORLD!</p>
HELLO WORLD! ( now working! )
Although this is limited to the first line it may be useful for more use cases than the first letter solution since it applies capitalization to the whole line and not only the first word. (all words in the first line)
In the OP's specific case this could have solved it.
Notes: As mentioned in the first letter solution comments, the order of the CSS rules is important! Also note that I changed the <a> tag for a <div> tag because for some reason the pseudo-element ::first-line doesn't work with <a> tags natively but either <div> or <p> are fine.
EDIT: the <a> element will work if display: inline-block; is added to the .link class. Thanks to Dave Land for spotting that!
New Note: if the text wraps it will loose the capitalization because it is now in fact on the second line (first line is still ok).
JavaScript:
var links = document.getElementsByClassName("link");
for (var i = 0; i < links.length; i++) {
links[i].innerHTML = links[i].innerHTML.toLowerCase();
}
CSS:
.link { text-transform: capitalize; }
What Khan "ended up doing" (which is cleaner and worked for me) is down in the comments of the post marked as the answer.
captialize only effects the first letter of the word. http://www.w3.org/TR/CSS21/text.html#propdef-text-transform
You can do it with css first-letter!
eg I wanted it for the Menu:
a {display:inline-block; text-transorm:uppercase;}
a::first-letter {font-size:50px;}
It only runs with block elements - therefore the inline-block!
May be useful for java and jstl.
Initialize variable with localized message.
After that it is possible to use it in jstl toLowerCase function.
Transform with CSS.
In JSP
1.
<fmt:message key="some.key" var="item"/>
2.
<div class="content">
${fn:toLowerCase(item)}
</div>
In CSS
3.
.content {
text-transform:capitalize;
}
If the data is coming from a database, as in my case, you can lower it before sending it to a select list/drop down list. Shame you can't do it in CSS.
After researching a lot I found jquery function/expression to change text in first letter in uppercase only, I modify that code accordingly to make it workable for input field. When you will write something in input field and then move to another filed or element, the text of that field will change with 1st-letter capitalization only. No matter user type text in complete lower or upper case capitalization:
Follow this code:
Step-1: Call jquery library in html head:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
Step-2: Write code to change text of input fields:
<script>
$(document).ready(function(){
$("#edit-submitted-first-name,#edit-submitted-last-name,#edit-submitted-company-name, #edit-submitted-city").focusout(function(){
var str=$(this).val();
str = str.toLowerCase().replace(/\b[a-z]/g, function(letter) {
return letter.toUpperCase();
});
$(this).val(str);
});});
</script>
Step-3: Create HTML input fields with same id's you use in jquery code like:
<input type="text" id="edit-submitted-first-name" name="field name">
The id of this input field is: edit-submitted-first-name (It using in jquery code in step-2)
**Result:
Make sure the text will change after you move your focus from that input field at another element. Because we using focus out event of jquery here.
Result should like this: User Type: "thank you" it will change with "Thank You".
**
Best of luck
The PHP solution, in backend:
$string = 'UPPERCASE';
$lowercase = strtolower($string);
echo ucwords($lowercase);
I know this is a late response but if you want to compare the performance of various solutions I have a jsPerf that I created.
Regex solutions are the fastest for sure.
Here is the jsPerf: https://jsperf.com/capitalize-jwaz
There are 2 regex solutions.
The first one uses/\b[a-z]/g. Word boundary will capital words such as non-disclosure to Non-Disclosure.
If you only want to capitalize letters that are preceded by a space then use the second regex
/(^[a-z]|\s[a-z])/g
if you are using jQuery; this is one a way to do it:
$('.link').each(function() {
$(this).css('text-transform','capitalize').text($(this).text().toLowerCase());
});
Here is an easier to read version doing the same thing:
//Iterate all the elements in jQuery object
$('.link').each(function() {
//get text from element and make it lower-case
var string = $(this).text().toLowerCase();
//set element text to the new string that is lower-case
$(this).text(string);
//set the css to capitalize
$(this).css('text-transform','capitalize');
});
Demo
all wrong it does exist --> font-variant: small-caps;
text-transform:capitalize; just the first letter cap

Resources