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
-~----------~----~----~----~------~----~------~--~---

Reply via email to