Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class names with leading numbers via variables #1722

Open
cimmanon opened this issue May 9, 2015 · 7 comments
Open

Class names with leading numbers via variables #1722

cimmanon opened this issue May 9, 2015 · 7 comments
Labels
enhancement New feature or request planned We would like to add this feature at some point

Comments

@cimmanon
Copy link

cimmanon commented May 9, 2015

Sass is extremely unhelpful when it comes to class names that begin with numbers (they're invalid according to CSS, but Sass doesn't give a useful error message):

.007 {
    color: red;
}

Invalid CSS after ".": expected class name, was "007"

The W3C validator will raise 2 different errors depending on the type

Length:

In CSS1, a class name could start with a digit (".55ft"), unless it was a dimension (".55in"). In CSS2, such classes are parsed as unknown dimensions (to allow for future additions of new units) To make "22ltr-porche" a valid class, CSS2 requires the first digit to be escaped ".\32 2ltr-porche" [22ltr-porche]

Number:

Parse Error .007 { background: url(/img/cars/22ltr-porche.jpg) no-repeat; }

Add a backslash and both Sass and the Validator are satisfied:

.\007 {
    color: red;
}

But what happens when your class name is stored in a variable?

$name: '007';

.#{$name} { // nope, same error as above
  color: red;
}

.\#{$name} { // nope, but the error is different
  color: red;
}

.\\#{$name} { // compiles, but the class has double backslashes now
  color: red;
}

The only solution I could come up with was to write a really ugly looking function for escaping variables like this:

@function escape_leading_numbers($s) {
  $first-char: str_slice(#{$s}, 0, 1);
  $found: index('1' '2' '3' '4' '5' '6' '7' '8' '9' '0', $first-char);
  @return if($found, unquote(str-insert(str-slice(#{$s}, 2), "\\3#{$first-char} ", 1)), $s);
}

$name: '007';

.#{escape_leading_numbers($name)} {
  color: red;
}

This problem seems to be closely related to #1395, which is where I swiped the idea for injecting the backslash. While leading numbers are uncommon (I think most of us have been trained to not do that), going through this amount of effort to generate a valid class name is unreasonable.

@cvrebert
Copy link

Perhaps SassScript should include a function that's equivalent to JavaScript's CSS.escape()

@ezekg
Copy link

ezekg commented Jul 24, 2015

It might be ugly, but you could just do one of these:

$name: '007';

.#{"\\#{$name}"} {
  color: red;
}

.#{"\\" + $name} {
  color: red;
}

Outputs,

.\007 {
  color: red;
}

.\007 {
  color: red;
}

Or you could even just keep it simple and do:

$name: '\\007';

.#{$name} {
  color: red;
}

http://sassmeister.com/gist/d1e461c0e06242cdc620

@cimmanon
Copy link
Author

@ezekg All you've managed to do there is make something Sass can compile, but it doesn't work at all when you try and apply it to the HTML.

.\30 07 {
  color: red;
}

.\007 {
  border: 1px solid;
}

When applied to this markup:

<div class="007">Bond</div>

Only makes the text red, it doesn't apply the border.

http://sassmeister.com/gist/f4f56b15f56ba9d6ff45

@twalpole
Copy link

CSS allows for escaping most characters by putting a \ in front of it except for hexadecimal characters. This is because it also allows for escaping via a \ followed by a 1 to 6 digit hex number (followed by a space if less than 6 digits and the following character is a hexadecimal character). So to escape 007 for a class selector you need to do .\30 07 or .\00003007 as @cimmanon shows. .\007 would just be whatever character code 7 is.

@ezekg
Copy link

ezekg commented Jul 24, 2015

Okay, I misread what you were actually trying to do. I didn't realize that an escaped number is actually parsed as a character code. Disregard my comment.

@nex3
Copy link
Contributor

nex3 commented Aug 15, 2015

This seems like another good use-case for a character-code() function.

@nex3 nex3 added enhancement New feature or request planned We would like to add this feature at some point labels Aug 15, 2015
@enkhee-Osiris
Copy link

enkhee-Osiris commented Dec 19, 2020

I created function for this kind of issue. Tried to mirror css.escape.

.#{escape-css('-')} {
  color: red;
}
@use 'sass:list';
@use 'sass:string';

@function escape-css($string) {
  $string: string.quote(#{$string});
  $string-len: string.length($string);
  $first-char: string.slice($string, 1, 1);
  $control-chars: (
    '\0001',
    '\0002',
    '\0003',
    '\0004',
    '\0005',
    '\0006',
    '\0007',
    '\0008',
    '\0009',
    '\000A',
    '\000B',
    '\000C',
    '\000D',
    '\000E',
    '\000F',
    '\0010',
    '\0011',
    '\0012',
    '\0013',
    '\0014',
    '\0015',
    '\0016',
    '\0017',
    '\0018',
    '\0019',
    '\001A',
    '\001B',
    '\001C',
    '\001D',
    '\001E',
    '\001F'
  );
  $control-chars-escaped-as-string: (
    '\\1 ',
    '\\2 ',
    '\\3 ',
    '\\4 ',
    '\\5 ',
    '\\6 ',
    '\\7 ',
    '\\8 ',
    '\\9 ',
    '\\A ',
    '\\B ',
    '\\C ',
    '\\D ',
    '\\E ',
    '\\F ',
    '\\10 ',
    '\\11 ',
    '\\12 ',
    '\\13 ',
    '\\14 ',
    '\\15 ',
    '\\16 ',
    '\\17 ',
    '\\18 ',
    '\\19 ',
    '\\1A ',
    '\\1B ',
    '\\1C ',
    '\\1D ',
    '\\1E ',
    '\\1F '
  );
  $number-chars: (
    '\0030',
    '\0031',
    '\0032',
    '\0033',
    '\0034',
    '\0035',
    '\0036',
    '\0037',
    '\0038',
    '\0039'
  );
  $number-chars-escaped-as-string: (
    '\\30 ',
    '\\31 ',
    '\\32 ',
    '\\33 ',
    '\\34 ',
    '\\35 ',
    '\\36 ',
    '\\37 ',
    '\\38 ',
    '\\39 '
  );
  $symbols-should-escaped: (
    '\0020',
    '\0021',
    '\0022',
    '\0023',
    '\0024',
    '\0025',
    '\0026',
    '\0027',
    '\0028',
    '\0029',
    '\002A',
    '\002B',
    '\002C',
    '\002E',
    '\002F',
    '\003A',
    '\003B',
    '\003C',
    '\003D',
    '\003E',
    '\003F',
    '\0040',
    '\005B',
    '\005C',
    '\005D',
    '\005E',
    '\0060',
    '\007B',
    '\007C',
    '\007D',
    '\007E'
  );
  $result: '';

  @for $i from 1 through $string-len {
    $char: string.slice($string, $i, $i);

    @if $i == 1 and $string-len == 1 and $char == '\002D' {
      $result: $result + '\\'+ $char;
    } @else if $i == 1 and list.index($number-chars, $char) {
      $result: $result +
        list.nth($number-chars-escaped-as-string, list.index($number-chars, $char));
    } @else if $i == 2 and list.index($number-chars, $char) and $first-char == '\002D' {
      $result: $result +
        list.nth($number-chars-escaped-as-string, list.index($number-chars, $char));
    } @else if $char == '\0000' {
      $result: $result + '\FFFD';
    } @else if list.index($control-chars, $char) {
      $result: $result +
        list.nth($control-chars-escaped-as-string, list.index($control-chars, $char));
    } @else if list.index($symbols-should-escaped, $char) {
      $result: $result + '\\'+ $char;
    } @else if $char == '\007F' {
      $result: $result + '\\7F ';
    } @else {
      $result: $result + $char;
    }
  }

  @return string.unquote($result);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request planned We would like to add this feature at some point
Projects
None yet
Development

No branches or pull requests

6 participants