It seems elements selected using :contains(sub)
with sub
containing <
or >
cannot get to their parents.
The following example should illustrate the problem I'm having in both Safari and Camino (Gecko on Mac):
<html>
<head>
<scri开发者_开发百科pt type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
</head>
<body>
<p><strong>bar</strong></p>
<p><strong><foo></strong></p>
<script type="text/javascript">
alert($('body strong:contains("bar")').length);
alert($('body strong:contains("bar")').parent().length);
alert($('body strong:contains("<foo>")').length);
alert($('body strong:contains("<foo>")').parent().length); // this fails
alert($('body strong').length);
alert($('body strong').parent().length); // two p elements
alert($('body strong').parent().parent().length); // one body
</script>
</body>
</html>
Output is:
1
1
1
0
2
2
1
Any ideas why the fourth one is 0
instead of 1
, or how I can circumvent this?
This page mentions escaping names in selectors, but that didn't work either (also, I'm not sure if it's applicable).
Disclaimer: This isn't a solution/workaround use Tatu's answer for that, this is just a description of the problem and what's going on to cause the weird behavior for those who are curious.
The root of the problem is here, the regex that jQuery uses to identify an HTML Fragment:
/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/
Which does match your selector:
body strong:contains("<foo>")
You can try it out:
alert(/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/.test('body strong:contains("<foo>")'));
So it thinks it's an HTML fragment..so overall these are currently equivalent:
$('body strong:contains("<foo>")');
$('<foo>');
Seeing the second is a clearer illustration that it's a document fragment...which has no parent. It takes the 2nd position in the match array, which you can see is just <foo>
, again try it out:
alert(/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/.exec('body strong:contains("<foo>")')[1]);
This results in you ultimately ending up at this code path in jQuery's $()
, building a fragment.
So in short yes, I'd consider this a jQuery bug.
No idea what's causing contains()
to fail, but you can use .filter()
as an alternative:
alert($('body strong').filter(function() {
return /<foo>/.test( $(this).text() );
}).parent().length);
That returns 1 as expected. It's ugly, but works.
This is clearly an issue with jQuery's selector parsing. If <
and >
are present in the selector, jQuery identifies the argument as a document fragment instead of a selector. The result is an element with a tagName of "FOO", and the same selectors would have the same issue:
$('body <foo>')
$('body strong:not(<foo>')
The only difference in your case is that you've used a valid selector and jQuery is identifying it incorrectly.
I made several attempts at a selector-based workaround, but Tatu Ulmanen's was the only one that worked.
EDIT: it seems you can also use .find():
$(document.body).find('strong:contains("<foo>")')
The <foo>
is being evaluated as a tag, thus the parent of it contains nothing except an empty tag, thus the 0.
EDIT: To fully understand look at this: $('body strong:contains("foo")').text().length
which yeilds 5. The 1 from $('body strong:contains("<foo>")').length
says there is one text node within the strong so length of that is 1 and the length of the text is 5.
The thing that gets interesting is the trailing >
which cannot be selected correctly as the >
nor the >
seem to work due to the >
which is used as a css selector. So, <foo
works but not <foo>
Here is a fiddle page to play with it: http://jsfiddle.net/2XEUg/1/
精彩评论