Christophe Porteneuve wrote:
> ...
> Thanks for the code. I'm myself working on implementing PC's, and
> looking at your work is a boon. I like the general code architecture
> you did (how work gets split around nthFind, etc.), yet I need to do
> more careful inspection on it to verify that regexes are sound (they
> appear to be so far), and benchmark efficiency.
>
Yah, I tend to just be throwing out ideas with my drafts. I hope it is
helpful. If nothing else it prevents you and others from going down
paths that don't work. :P
> This latter point is my main concern here, as your attached demo page
> runs at an atrociously slow pace on my box here
> (FF2/Debian/AMD-2.4GHz)... But then, maybe it's just your innerHTML
> construction code on the benchmarking side.
>
Yes, it was very slow for me too with any of the "nth-" predicates.
That nth routine relies on counting the number of previous siblings FOR
EVERY NODE. I'm not sure if that is avoidable, but I've added the
corresponding xpath... see below. The xpath syntax may need to be
reworked with previous-sibling or self, I'm not sure. Or this may just
be a wrong path :)
> Also, :lang relies on |=, not =, for the lang attribute check. It also
> primarily relies on xml:lang, not just lang. And *ideally*, it would
> examine DOM lineage for the node (but that's very much an edge case, I
> think).
>
> Finally, there are no :first and :last selectors in CSS3, although their
> addition is nice. I'm a little concerned as to what would happen if $$
> ended up supporting some CSS3 selectors in a not-quite-spec-compliant
> way (e.g. lang), and supporting *extra* selectors that people would then
> believe are usable straight in regular CSS...
>
> Thanks again for your work! I'll dive more into it in the coming days.
>
>
I agree. I don't know why I thought :first or :last were CSS3, but I
don't think we should add anything. $$ should be a method of
convenience, not the exclusive way to build a list of nodes; there is a
lot of power in methods such as Element.up(), Element.down(),
Enumerable.inject(), etc. Not to mention the fact that styling the
first and last element separately is great, but not such a great
practice when trying to separate presentation from logic with JS.
I don't know how well you'll be able to implement a CSS "not()"
equivalent in XPath. From what I could see, filtering out nodes in
XPath depends on the content of not(). Consider:
CSS3: div:not(#speech1)
XPath: .//[EMAIL PROTECTED]"speech1"]
CSS3: div.character:not([EMAIL PROTECTED])
XPath: .//div[contains(concat(' ', @class, ' '), ' character ') and
@id!='speech2']
And :empty seems tricky in XPath too. So Christophe, keep us posted with
any XPath magic you have up your sleeve.... This is it from me for now
with $$.
--Ken
Object.extend(NewSelector, {
xpath: {
descendant: "//",
child: "/",
adjacent: "/following-sibling::",
tagName: "#{2}",
className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
id: "[EMAIL PROTECTED]'#{1}']",
attrPresence: "[EMAIL PROTECTED]",
firstChild: "[position()=1]",
lastChild: "[position()=last()]",
// count(s)
empty: "/text()",
nth: function(m) { // m for matches
var isReverse = m[1]; // ('last-' or '')
var flavor = m[2]; // ('child' or 'of-type')
var notation = m[3]; // parens
var ab = NewSelector.handlers.parseNthNotation(notation);
if (flavor == 'child') {
if (isReverse == 'last-') {
return "[last()-position() mod "+ab[0]+"="+ab[1]+"]";
} else {
return "[position() mod "+ab[0]+"="+ab[1]+"]";
}
// use preceding-sibling::
} else { // 'of-type'
}
},
attr: function(m) {
return new Template(NewSelector.xpath.operators[m[2]]).evaluate(m);
},
not: function(m) { // m for matches
},
operators: {
'=': "[EMAIL PROTECTED]'#{3}']",
'!=': "[EMAIL PROTECTED]'#{3}']",
'^=': "[substring(@#{1}, 1, string-length('#{3}'))='#{3}']",
'$=': "[substring(@#{1}, (string-length(@#{1}) -
string-length('#{3}') + 1))='#{3}']",
'*=': "[contains(@#{1}, '#{3}')]",
'~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
'|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
}
},
criteria: {
tagName: 'n = h.tagName(n, r, "#{2}", d); d = false;',
className: 'n = h.className(n, r, "#{1}", d); d = false;',
id: 'n = h.id(n, r, "#{1}", d); d = false;',
attrPresence: 'n = h.attrPresence(n, r, "#{1}"); d = false;',
attr: 'n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); d = false;',
descendant: 'd.list = "desc";',
child: 'd.list = "child";',
adjacent: 'd.list = "adjacent";',
empty: 'n = h.empty(n); d = false;',
not: 'n = h.not(n, "#{1}"); d = false;',
firstChild: 'n = h.findNthNodes(n, r, "child", 1, 1, false); d =
false;',
lastChild: 'n = h.findNthNodes(n, r, "child", 1, 1, true); d =
false;',
lang: 'n = h.attr(n, r, "lang", "#{1}", "|="); d = false;',
nth: 'n = h.nth(n, r, "#{1}", "#{2}", "#{3}"); d = false;'
},
patterns: {
child: /^(\s+)?>\s*/,
adjacent: /^(\s+)?\+\s*/,
descendant: /^\s/,
tagName: /^(\s+)?(\*|[\w-]+)(\b|$)?/,
id: /^#([\w-\*]+)(\b|$)/,
className: /^\.([\w-\*]+)(\b|$)/,
attrPresence: /^\[([\w]+)\]/,
attr:
/\[(?:@)?([\w-:]+)\s?(?:(=|.=)\s?['"]?([^\]]*?)["']?)?\](\s?>\s?|[\/\s\]]|$)/,
firstChild: /^:first-child(\b|$)/,
lastChild: /^:last-child(\b|$)/,
empty: /^:empty(\b|$)/,
not: /^:not\(([^(]+|[^(]*:nth-[^(]+\([^()]+\))\)(\b|)/,
lang: /^:lang(\b|$)/,
nth:
/^:nth-(last-)?(child|of-type)\((\d*n[+-]\d+|\d+n|\d+|odd|even)\)(\b|$)/
/*
note: nesting of ":not" is disallowed (see
http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#negation)
":not" may contain other parens only if referring to the N format
*/
},
handlers: {
/**
* Given "nth-" notation, parse notation string and find nodes
*
* @param array nodes List of nodes to filter
* @param object root (not needed?)
* @param bool isReverse If true, counting should start from last
node and continue to first
* @param string flavor String containing 'child' or 'of-type'
* @param string notation Notation in form an+b
* @return array List of matching nodes
*/
nth: function(nodes, root, isReverse, flavor, notation) {
var groupAndOffset =
NewSelector.handlers.parseNthNotation(notation);
return NewSelector.handlers.findNthNodes(nodes, root, flavor,
groupAndOffset[0], groupAndOffset[1], isReverse == 'last-');
},
/**
* Given a string notation in the form an+b, return a and b
* a and b represent group size and offset in the css "nth-" notation
*
* @param string notation
* return array [a, b]
*/
parseNthNotation: function(notation) {
if (notation == 'odd') return [2,1];
else if (notation == 'even') return [2,0];
// parse notation
var matches = /^(\d*n)?([+-])?(\d+)?$/.exec(notation);
// $1 grouping size (optional)
// $2 plus or minus (optional)
// $3 offset (optional)
if (matches[2] && matches[2] == '-') matches[3] = matches[3] * -1;
return [parseFloat(matches[1] || 1), parseFloat(matches[3] || 0)];
},
/**
* Given "nth-" notation information, find nodes
*
* @param array nodes List of nodes to filter
* @param object root (not needed?)
* @param string flavor String containing 'child' or 'of-type'
* @param int groupSize Size of groups (a)
* @param int offset Offset within group (b)
* @param bool isReverse If true, counting should start from last
node and continue to first
* @return array List of filtered nodes
*/
findNthNodes: function(nodes, root, flavor, groupSize, offset,
isReverse) {
//console.log($A(arguments));
// is root necessary here? I think we can omit it
if (isReverse) nodes = nodes.reverse();
// create a function to count the prevous siblings
if (flavor == 'child') { // "nth-child" / "nth-last-child"
var countPrev = function(node) {
return node.previousSiblings().length;
};
} else { // "nth-of-type" / "nth-last-of-type" might be more
descriptively named "nth-child-with-matching-tagName"
var countPrev = function(node,root) {
//console.log(node.previousSiblings().findAll(function(n) { return
n.tagName == node.tagName; }));
return node.previousSiblings().findAll(function(n) {
return n.tagName == node.tagName; }).length;
};
}
// use our function to filter nodes
nodes = nodes.findAll(function(node) {
if (groupSize > 1 ) {
// for groupSizes of 2 or more, use modulus
return (countPrev(node,root) + 1) % groupSize == offset;
} else {
// for groupSizes of 1, a absolute offset
return countPrev(node,root) + 1 == offset;
}
});
if (isReverse) nodes = nodes.reverse();
return nodes;
},
not: function(nodes, innerExpression) {
var toRemove = NewSelector.matchElements(nodes, innerExpression);
var ret = nodes.reject(function(node) { return
toRemove.member(node); });
//console.log(ret);
return ret;
},
empty: function(nodes) {
return nodes.findAll(function(node) {
// could use innerHTML=='' instead but it seems ugly
return !node.firstChild && node.nodeValue === null;
});
},
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Prototype: Core" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/prototype-core?hl=en
-~----------~----~----~----~------~----~------~--~---