56

I have a dropdown list that contains a series of options:

<select id=SomeDropdown>
  <option value="a'b]&lt;p>">a'b]&lt;p></option>
  <option value="easy">easy</option>
<select>

Notice that the option value/text contains some nasty stuff:

  • single quotes
  • closing square bracket
  • escaped html

I need to remove the a'b]<p> option but I'm having no luck writing the selector. Neither:

$("#SomeDropdown >option[value='a''b]&lt;p>']");

or

$("#SomeDropdown >option[value='a\'b]&lt;p>']");

are returning the option.

What is the correct way to escape values when using the "value=" selector?

Keavon
  • 6,837
  • 9
  • 51
  • 79
user53794
  • 3,800
  • 2
  • 30
  • 31
  • 5
    User could enter anything. Not likely they would... but if they do then the application should handle the input correctly. – user53794 Apr 11 '09 at 07:42

9 Answers9

53

I use this function to escape jquery selectors. It escapes basically everything questionable but may be too aggressive.

function escapeStr(str) 
{
    if (str)
        return str.replace(/([ #;?%&,.+*~\':"!^$[\]()=>|\/@])/g,'\\$1');      

    return str;
}
Rob
  • 1,687
  • 3
  • 22
  • 34
Sam Hendley
  • 871
  • 6
  • 10
  • 5
    Have you tested that? When I tested it, it only puts 1 slash before the meta-characters. Here's the one I wrote: `str.replace(/([ !"#$%&'()*+,.\/:;<=>?@[\\\]^\`{|}~])/g,'\\\\$1')`. – mpen Jul 17 '11 at 23:01
  • 1
    @Mark, using the code in the answer worked fine for escaping special characters in a jquery selector. – Justin Jul 20 '12 at 18:33
  • 2
    @Justin: You might be right. The docs deceived me. It says you need two slashes, but one is just for escaping the slash in a JavaScript string, the 2nd is for actually escaping the CSS selector. – mpen Jul 21 '12 at 01:33
  • 1
    I modified the regex to include matching `?`: `return str.replace(/([ #;?&,.+*~\':"!^$[\]()=>|\/@])/g,'\\$1');` – Mike Knoop Sep 19 '12 at 01:46
  • The percent `%` sign also needs to be escaped. – Anthony Hatzopoulos May 10 '13 at 22:42
38

I don't think you can. It should be:

#SomeDropdown >option[value='a\'b]<p>']

And this does work as a CSS selector (in modern browsers). Expressed in a JavaScript string literal you would naturally need another round of escaping:

$("#SomeDropdown >option[value='a\\'b]<p>']")

But this doesn't work in jQuery because its selector parser is not completely standards-compliant. It uses this regex to parse the value part of an [attr=value] condition:

(['"]*)(.*?)\3|)\s*\]

\3 being the group containing the opening quotes, which weirdly are allowed to be multiple opening quotes, or no opening quotes at all. The .*? then can parse any character, including quotes until it hits the first ‘]’ character, ending the match. There is no provision for backslash-escaping CSS special characters, so you can't match an arbitrary string value in jQuery.

(Once again, regex parsers lose.)

But the good news is you don't have to rely on jQuery selectors; there are perfectly good DOM methods you can use, in particular HTMLSelectElement.options:

var select= document.getElementById('SomeDropdown');
for (var i= select.options.length; i-->0;) {
    if (select.options[i].value=="a'b]<p>") {
        // do something with option
}   }

This is many times simpler and faster than asking jQuery to laboriously parse and implement your selector, and you can use any value string you like without having to worry about escaping special characters.

bobince
  • 528,062
  • 107
  • 651
  • 834
  • 16
    I will suggest this as a jQuery alternative to the pure DOM: $('option', '#SomeDropdown').filter(function() { return $(this).val() == "a'b]

    "; });

    – Paolo Bergantino Apr 14 '09 at 14:12
  • @PaoloBergantino Your Answer is the perfect solution for my case. Really appreciate for your comment. – Codemole Oct 01 '15 at 18:49
10

use .filter() with a custom function. txt should contain your nasty string, or you could just replace indexOf with any other function you choose.

$("#SomeDropdown option")
   .filter(function(i){
       return $(this).attr("value").indexOf(txt) != -1;
   })
   .remove();
Mohammed H
  • 6,880
  • 16
  • 81
  • 127
brainsucker
  • 169
  • 1
  • 3
  • 1
    It is hard to read your code sample. Please use new lines and start your next line with eight spaces (not tabs) to format your code so readers do not have to scroll. – carbontax Oct 21 '12 at 03:56
  • This is FAR better on the eyes than that mash of regex craziness (I'm no regexpert). This also came in handy in solving a nasty issue. So I think this answer is much better. – IncredibleHat Nov 07 '17 at 15:10
5

I find that you can use \ \ to escape selectors. Think of it as one \ for the regex and one to escape from the regex.

Example:

$(this).find('input[name=user\\[1\\]\\[name\\]]').val();
Strixy
  • 568
  • 5
  • 15
3

If you are trying to do the escaping programmatically, you only need one set of slashes. This won't work:

var key = 'user[1][name]';
$(this).find('select[name=' + key + ']');

But this will:

var key = 'user\[1\]\[name\]';
$(this).find('select[name=' + key + ']');

And so will this:

$(this).find('select[name=user\\[1\\]\\[name\\]]');

You can use this javascript to build a correctly escaped selector:

if(key.indexOf('[') !== -1) {
    key = key.replace(/([\[\]])/g, "\\$1");
}

Here's a JS Fiddle that shows some of the weird behavior:

http://jsfiddle.net/dH3cV/

Steve Tauber
  • 9,551
  • 5
  • 42
  • 46
2

jQuery.escapeSelector() was introduced in jQuery 3. To match the option with value a'b]&lt;p> in the question, you could use:

$("#SomeDropdown > option[value='" + $.escapeSelector("a'b]<p>") + "']")

In general, using jQuery.escapeSelector() is good practice for things like $('#' + $.escapeSelector(id)), where variable id may contain special CSS symbols.

MDMower
  • 808
  • 8
  • 27
2

The problem is due to HTML entities; the "&lt;" is seen by the browser as "<".

The same could be said for the example provided by bobince; please note that the following does not work with jQuery 1.32 on Win + FF3:

var select= document.getElementById('SomeDropdown');
for (var i= select.options.length; i-->0;) {
    if (select.options[i].value=="a'b]&lt;p>") {
        alert('found it');
    }   
}

However, changing the entity to a literal will indeed find the desired value:

var select= document.getElementById('SomeDropdown');
for (var i= select.options.length; i-->0;) {
    if (select.options[i].value=="a'b]<p>") {
        alert('found it');
    }   
}

Of course, there is a problem here, as the value that you're specifying is not the exact value that you're looking for. This can also be corrected with the addition of a helper function:

function html_entity_decode(str) {
    var decoder = document.createElement('textarea');
    decoder.innerHTML = str;
    return decoder.value;
}

All together now:

var srcValue = html_entity_decode("a'b]&lt;p>");
var select= document.getElementById('SomeDropdown');
for (var i= select.options.length; i-->0;) {
    if (select.options[i].value == srcValue) {
        alert('found it');
    }   
}

Any now, the input value that you're searching for exactly matches the value of the select element.

This can also be written using jQuery methods:

var srcValue = html_entity_decode("a'b]&lt;p>");
$($('#SomeDropdown').attr('options')).each(function() {
    if (this.value == srcValue)
    {
        $(this).remove();
    }
});

And then finally, as a plugin since they are so easy to make:

jQuery.fn.removeByValue = function( val )
{
    var decoder = document.createElement('textarea');
    decoder.innerHTML = val;    
    var srcValue = decoder.value;

    $( $(this)[0].options ).each(function() {
        if (this.value == srcValue) {
            $(this).remove();
        }
    });

    return this;
};

$('#SomeDropdown').removeByValue("a'b]&lt;p>");
ken
  • 3,650
  • 1
  • 30
  • 43
1

jQuery's forum has a nice solution for this:

https://learn.jquery.com/using-jquery-core/faq/how-do-i-select-an-element-by-an-id-that-has-characters-used-in-css-notation/

This slightly modified version of what they suggest is also nullsafe.

function jqid (id) {
  return (!id) ? null : '#' + id.replace(/(:|\.|\[|\]|,)/g, '\\$1');
}
theUtherSide
  • 3,338
  • 4
  • 36
  • 35
1

Safely escaping CSS string is not easy and can't be done with simple regex.

You can use CSS.escape() .

this is not supported by all browsers but a polyfill exist.

Yukulélé
  • 15,644
  • 10
  • 70
  • 94