SyntaxError: unmatched pseudo-class :last
They aren't implemented and probably won't.
Why not exactly? And is there a way of defining your own then?
It's because of the way CSSselect works: Selectors are turned into functions, which test specific elements. :first, :last and friends require the selection to be performed in multiple traversals (eg. div:first p will first look for divs, take the first one and query it for ps).
Thank you for the clarification.
On Mar 13, 2014 3:57 PM, "Felix B枚hm" [email protected] wrote:
It's because of the way CSSselect https://github.com/fb55/CSSselectworks: Selectors are turned into functions, which test specific elements.
:first, :last and friends require the selection to be performed in
multiple traversals (eg. div:first p will first look for divs, take the
first one and query it for ps).
Reply to this email directly or view it on GitHubhttps://github.com/MatthewMueller/cheerio/issues/408#issuecomment-37579248
.
Related to #362.
So is there an alternative?
sometimes you can get away with :nth-child(1).
Or $("blah").each(function(index) {
if (index === 0) {
//this is the first
}
});
@allain: why not $("blah").first()? I assume there's always an overhead if :first can't be used. nth-child() includes child elements, which is probably not what you want if you're retrieving the text of an element.
Good points.
Just to help someone else out if they run into this.
A selector like:
$('.page_nav:first() .single:last() b:last()').text();
Can be changed to this:
$('.page_nav').first().find('.single').last().find('b').last().text();
First has a valid use case with .not:
$('.whatever').not(':first')
is much more readable than
$('.whatever').not(':nth-child(1)')
and can not be replaced by first() function.
Edit: please see comments below. Nth-child works in my case, but is not a universal replacement.
Given that one of cheerio's strengths is the ability to test the same code in browser using jQuery, this omittance is unfortunate..
@lktvlm These two are far from equivalent. :first checks whether it is the first element in the selection. You can archive the same effect as the first selector using
$('.whatever').filter(i => i > 0)
:nth-child on the other hand checks whether the element is the first child of its parent element, which is an entirely different beast.
.not(':first') == .slice(1)
You could do:
$('.whatever').slice(1);
to get all except the first of a set of elements or:
$('.whatever').children().slice(1);
to get all children except the first child of a set of elements.
And for a :not(:last), this should work:
$('.foo').slice(0, -1)
Read all above, but I have one scenario that I have many configurations with selector string like 'ul li:first', and I wanna read from file and pass to cheerio, what can I do?
@mike442144 I'm not sure what you're asking. Can you be more verbose and provide an example? It seems to me like you could do $('ul li').first();
Hey, say I have a configuration.json file with content:
{
"selector1": "ul li:first",
"selector2": "ul > li:nth-child(4)",
}
And in a index.js maybe like below:
config cfg = require('configuration.json');
// here I want to apply the selector to the document directly and get what I want
$(cfg.selector1).text();
$(cfg.selector2).text();
In this way I can configure the selector in a json file rather than write in a javascript file, which I can only maintain the configuration file when the DOM changed. Do you have any idea?
How is that related to this issue?
Anyway, your approach seems alright.
let cfg = require('./configuration.json');
$(cfg.selector1).text();
Note that there is a module cache, so if you need to re-read your configuration file at runtime, then doing require(...) again won't cut it. You may want to read and parse the file instead:
const fs = require('fs');
let cfg = JSON.parse(fs.readFileSync('./configuration.json', { encoding: 'utf8' }))
@CoDEmanX I suggest you to reread the issue and all posts. I define selectors with pseudo-class in file, a file is just an example, what is important is predefined selector with pseudo-class, so it is certainly related to this issue isn't it?
I see, I did not actually look at the selectors
:first is not supported, so ul li:first is not a valid selector in cheerio and it doesn't look like it will be implemented. If you want to stick with cheeriojs and write such selectors in a file, then I guess you will have to write a selector parser and perform $('ul li').first() for instance. Or write selector functions and call them with $ as argument...
@mike442144 instead of a JSON file with an mapping of selectors, you could have a JS file like so (where each value is either a selector or a function):
module.exports = {
"selector1": (context) => context ? $(context).find("ul li").first() : $("ul li").first(),
"selector2": "ul > li:nth-child(4)",
};
or, even a mapping of all functions:
const dryContext = (context) => context ? $(context) : $('*'); // just to keep things less redundant
module.exports = {
"selector1": (c) => dryContext(c).find("ul li").first(),
"selector2": (c) => dryContext(c).find("ul > li:nth-child(4)"),
};
@SystemDisc Actuall I don't understand your point, selector1 makes sense, but why selector2? But using *.js file with functions is good idea.
Merely to keep things consistent. You may like the first example better. I just grabbed the same selectors you posted above.
What about ":first-of-type"?
Most helpful comment
Just to help someone else out if they run into this.
A selector like:
Can be changed to this: