css3-selectors

changeset 70:0c04c468e014

Autoconfiscated the project, organizing the sources in a more sane way
author Emanuele Aina <em@nerd.ocracy.org>
date Tue Dec 30 22:26:25 2008 +0100 (19 months ago)
parents fe189fede98d
children 5d6a86f7f2b1
files AUTHORS COPYING ChangeLog Css3SelectorExperiment.cs LICENSE LxmlTestCssPy.cs Makefile Makefile.am NEWS README autogen.sh configure.ac css3-selectors.pc.in lxml/css_shakespear.html lxml/test_css.py lxml/test_css.txt lxml/test_css_select.txt selectors.txt src/AssemblyInfo.cs.in src/Css3Selectors.cs src/Makefile.am tests/LxmlTestCssPy.cs tests/Makefile.am tests/TestsRunner.cs tests/lxml/css_shakespear.html tests/lxml/test_css.py tests/lxml/test_css.txt tests/lxml/test_css_select.txt tests/selectors.txt
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/AUTHORS	Tue Dec 30 22:26:25 2008 +0100
     1.3 @@ -0,0 +1,1 @@
     1.4 +Emanuele Aina <em@nerd.ocracy.org>
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/COPYING	Tue Dec 30 22:26:25 2008 +0100
     2.3 @@ -0,0 +1,21 @@
     2.4 +Template: http://www.opensource.org/licenses/mit-license.html
     2.5 +
     2.6 +Copyright (c) 2008-2009 Emanuele Aina <em@nerd.ocracy.org>
     2.7 +
     2.8 +Permission is hereby granted, free of charge, to any person obtaining a copy
     2.9 +of this software and associated documentation files (the "Software"), to deal
    2.10 +in the Software without restriction, including without limitation the rights
    2.11 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    2.12 +copies of the Software, and to permit persons to whom the Software is
    2.13 +furnished to do so, subject to the following conditions:
    2.14 +
    2.15 +The above copyright notice and this permission notice shall be included in
    2.16 +all copies or substantial portions of the Software.
    2.17 +
    2.18 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    2.19 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    2.20 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    2.21 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    2.22 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    2.23 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    2.24 +THE SOFTWARE.
     3.1 --- a/Css3SelectorExperiment.cs	Tue Dec 30 18:36:08 2008 +0100
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,363 +0,0 @@
     3.4 -/* CSS3 Selectors to XPath converter
     3.5 - *
     3.6 - * Emanuele Aina <em@nerd.ocracy.org>
     3.7 - * 
     3.8 - * Inspired by HTML::Selector::XPath from Tatsuhiko Miyagawa
     3.9 - * http://search.cpan.org/~miyagawa/HTML-Selector-XPath/lib/HTML/Selector/XPath.pm
    3.10 - */
    3.11 -using System;
    3.12 -using System.Collections.Generic;
    3.13 -using System.Linq;
    3.14 -using System.IO;
    3.15 -using System.Text.RegularExpressions;
    3.16 -
    3.17 -public static class Css {
    3.18 -    public class Element {
    3.19 -        public virtual string ToCss() {
    3.20 -            return "";
    3.21 -        }
    3.22 -
    3.23 -        public virtual string ToXPath() {
    3.24 -            return "";
    3.25 -        }
    3.26 -        public override string ToString() {
    3.27 -            return base.ToString() + "(" + ToCss() + ")";
    3.28 -        }
    3.29 -    }
    3.30 -
    3.31 -    public class Combinator : Element {
    3.32 -        public string Mode { get; set; }
    3.33 -        public override string ToCss() {
    3.34 -            return Mode;
    3.35 -        }
    3.36 -
    3.37 -        public override string ToXPath() {
    3.38 -            switch(Mode) {
    3.39 -                case " ": return "//";
    3.40 -                case ">": return "/";
    3.41 -                case "+": return "/following-sibling::*[1]/self::";
    3.42 -                case "~": return "/following-sibling::";
    3.43 -            }
    3.44 -            throw new Exception();
    3.45 -        }
    3.46 -    }
    3.47 -
    3.48 -    public class Comma : Element {
    3.49 -    }
    3.50 -
    3.51 -    public class SimpleSelector : Element {
    3.52 -    }
    3.53 -
    3.54 -    public class TypeSelector : SimpleSelector {
    3.55 -        public string Namespace { get; set; }
    3.56 -        public string Type { get; set; }
    3.57 -        public override string ToCss() {
    3.58 -            if(Namespace == null)
    3.59 -                return Type;
    3.60 -            return Namespace + "|" + Type;
    3.61 -        }
    3.62 -
    3.63 -        public override string ToXPath() {
    3.64 -            if(Namespace == null)
    3.65 -                return Type;
    3.66 -            return Namespace + ":" + Type;
    3.67 -        }
    3.68 -    }
    3.69 -
    3.70 -    public class UniversalSelector : SimpleSelector {
    3.71 -        public override string ToCss() {
    3.72 -            return "*";
    3.73 -        }
    3.74 -
    3.75 -        public override string ToXPath() {
    3.76 -            return "*";
    3.77 -        }
    3.78 -    }
    3.79 -
    3.80 -    public class AttributeSelector : SimpleSelector {
    3.81 -        public string Attribute { get; set; }
    3.82 -        public string Comparison { get; set; }
    3.83 -        public string Value { get; set; }
    3.84 -        public override string ToCss() {
    3.85 -            if(Comparison == null)
    3.86 -                return "[" + Attribute + "]";
    3.87 -            return "[" + Attribute + Comparison + "'" + Value + "']";
    3.88 -        }
    3.89 -
    3.90 -        public override string ToXPath() {
    3.91 -            switch(Comparison) {
    3.92 -                case null: return "[@" + Attribute + "]";
    3.93 -                case "=" : return "[@" + Attribute + "='" + Value + "']";
    3.94 -                case "|=": return String.Format("[@{0}='{1}' or starts-with(@{0},'{1}-')]", Attribute, Value);
    3.95 -                case "~=": return String.Format("[contains(concat(' ',@{0},' '),' {1} ')]", Attribute, Value);
    3.96 -                case "^=": return String.Format("[starts-with(@{0},'{1}')]", Attribute, Value);
    3.97 -                // TODO: add the ends-with() XPath function
    3.98 -                case "$=": return String.Format("[ends-with(@{0},'{1}')]", Attribute, Value);
    3.99 -                case "*=": return String.Format("[contains(@{0},'{1}')]", Attribute, Value);
   3.100 -                // NOTE: non-standard!
   3.101 -                case "!=": return String.Format("[not(@{0}='{1}')]", Attribute, Value);
   3.102 -            }
   3.103 -            throw new Exception();
   3.104 -        }
   3.105 -    }
   3.106 -
   3.107 -    public class ClassSelector : SimpleSelector {
   3.108 -        public string Class { get; set; }
   3.109 -        public override string ToCss() {
   3.110 -            return "." + Class;
   3.111 -        }
   3.112 -
   3.113 -        public override string ToXPath() {
   3.114 -            return String.Format("[contains(concat(' ',@class,' '),' {0} ')]", Class);
   3.115 -        }
   3.116 -    }
   3.117 -
   3.118 -    public class IdSelector : SimpleSelector {
   3.119 -        public string Id { get; set; }
   3.120 -        public override string ToCss() {
   3.121 -            return "#" + Id;
   3.122 -        }
   3.123 -
   3.124 -        public override string ToXPath() {
   3.125 -            return String.Format("[@id='{0}']", Id);
   3.126 -        }
   3.127 -    }
   3.128 -
   3.129 -    public class ContentSelector : SimpleSelector {
   3.130 -        // This public class intentionally left blank
   3.131 -        // See http://www.w3.org/TR/css3-selectors/#content-selectors
   3.132 -    }
   3.133 -
   3.134 -    public class PseudoClassSelector : SimpleSelector {
   3.135 -        public string PseudoClass { get; set; }
   3.136 -
   3.137 -        public override string ToCss() {
   3.138 -            return ":"+PseudoClass;
   3.139 -        }
   3.140 -
   3.141 -        public override string ToXPath() {
   3.142 -            switch(PseudoClass) {
   3.143 -                case "only-child": return "[count(../*)=1]";
   3.144 -                case "first-child": return "*[1]/self::";
   3.145 -                case "last-child": return "*[last()]/self::";
   3.146 -            }
   3.147 -            throw new Exception();
   3.148 -        }
   3.149 -    }
   3.150 -
   3.151 -    public class PseudoClassSelector<T> : PseudoClassSelector {
   3.152 -        public T Argument { get; set; }
   3.153 -
   3.154 -        private string ToCssString(object obj) {
   3.155 -            SimpleSelector selector = obj as SimpleSelector;
   3.156 -
   3.157 -            if(selector != null) return selector.ToCss();
   3.158 -            IEnumerable<Element> tokens = obj as IEnumerable<Css.Element>;
   3.159 -
   3.160 -            if(tokens != null) return tokens.Select(t => t.ToCss()).Aggregate((a,b) => a+b);
   3.161 -            return obj.ToString();
   3.162 -        }
   3.163 -
   3.164 -        private string ToXPathString(object obj) {
   3.165 -            SimpleSelector selector = obj as SimpleSelector;
   3.166 -            if(selector != null) return selector.ToXPath();
   3.167 -
   3.168 -            IEnumerable<Element> tokens = obj as IEnumerable<Css.Element>;
   3.169 -            if(tokens != null) return Regex.Replace(tokens.ToXPath(), @"^//", "./self::");
   3.170 -
   3.171 -            return obj.ToString();
   3.172 -        }
   3.173 -
   3.174 -        public override string ToCss() {
   3.175 -            return ":"+PseudoClass + "(" + ToCssString(Argument) + ")";
   3.176 -        }
   3.177 -
   3.178 -        public override string ToXPath() {
   3.179 -            switch(PseudoClass) {
   3.180 -                case "not": return String.Format("[not({0})]", ToXPathString(Argument));
   3.181 -                case "nth-child": return String.Format("[count(preceding-sibling::*)={0}-1]", Argument);
   3.182 -                case "lang": return String.Format("[@xml:lang='{0}' or starts-with(@xml:lang,'{0}-')]", Argument);
   3.183 -                // TODO: removed from the CSS3 draft, underspecified, check its behaviour against other implementations
   3.184 -                case "contains": return String.Format("[contains(text(),'{0}')]", Argument);
   3.185 -            }
   3.186 -            throw new Exception();
   3.187 -        }
   3.188 -    }
   3.189 -
   3.190 -    public class PseudoElement {
   3.191 -    }
   3.192 -
   3.193 -    private static string Unquote(string s) {
   3.194 -        Regex singleQuotes = new Regex("'(.*)'");
   3.195 -        Regex doubleQuotes = new Regex("\"(.*)\"");
   3.196 -
   3.197 -        if(singleQuotes.Match(s).Success)
   3.198 -            return singleQuotes.Replace(s, "$1");
   3.199 -        if(doubleQuotes.Match(s).Success)
   3.200 -            return doubleQuotes.Replace(s, "$1");
   3.201 -        return s;
   3.202 -    }
   3.203 -
   3.204 -    public static IEnumerable<Element> Parse(string selector)
   3.205 -    {
   3.206 -        string tmp = selector;
   3.207 -
   3.208 -        var parseTable = new[] {
   3.209 -            // tag name/id/class
   3.210 -            new[]{@"^([#.]?)([a-z0-9\\*_-]+)((\|)([a-z0-9\\*_-]*))?", "Element"},
   3.211 -            // pseudo classes
   3.212 -            new[]{@"^:([a-z0-9_-]+)(\((.*)\))?", "Pseudo" },
   3.213 -            // attribute value match
   3.214 -            new[]{@"^\[\s*([^\~\|^$*!=\s]+)\s*(([~\|^$*!]?=)\s*(.+))?\]", "Attr"},
   3.215 -            // adjacency/direct descendance
   3.216 -            new[]{@"^\s*([>+\~\s])\s*", "Combinator"},
   3.217 -            // rules union 
   3.218 -            new[]{@"^\s*,", "Comma"}
   3.219 -        };
   3.220 -
   3.221 -        while(!String.IsNullOrEmpty(tmp)) {
   3.222 -            Match match = null;
   3.223 -            string type = null;
   3.224 -
   3.225 -            Console.WriteLine("  rest -> '{0}'", tmp);
   3.226 -
   3.227 -            foreach(var item in parseTable) {
   3.228 -                Regex r = new Regex(item[0], RegexOptions.Compiled | RegexOptions.IgnoreCase);
   3.229 -                match = r.Match(tmp);
   3.230 -                if(match.Success) {
   3.231 -                    type = item[1];
   3.232 -                    tmp = r.Replace(tmp, "");
   3.233 -                    break;
   3.234 -                }
   3.235 -            }
   3.236 -
   3.237 -            Console.WriteLine("  {0} -> '{1}'", type, match.Value);
   3.238 -            switch(type) {
   3.239 -                case "Element":
   3.240 -                    switch(match.Groups[1].Value) {
   3.241 -                        case "":
   3.242 -                            if(match.Groups[5].Success) {
   3.243 -                                yield return new TypeSelector{Namespace = match.Groups[2].Value, Type = match.Groups[5].Value};
   3.244 -                            }
   3.245 -                            else {
   3.246 -                                yield return new TypeSelector{Type = match.Groups[2].Value};
   3.247 -                            }
   3.248 -                            break;
   3.249 -                        case "#":
   3.250 -                            yield return new IdSelector{Id = match.Groups[2].Value};
   3.251 -                            break;
   3.252 -                        case ".":
   3.253 -                            yield return new ClassSelector{Class = match.Groups[2].Value};
   3.254 -                            break;
   3.255 -                    }
   3.256 -                    break;
   3.257 -                case "Attr":
   3.258 -                    if(match.Groups[3].Success) {
   3.259 -                        yield return new AttributeSelector{
   3.260 -                            Attribute = Unquote(match.Groups[1].Value.Trim()),
   3.261 -                            Comparison = match.Groups[3].Value,
   3.262 -                            Value = Unquote(match.Groups[4].Value)};
   3.263 -                    }
   3.264 -                    else {
   3.265 -                        yield return new AttributeSelector{Attribute = match.Groups[1].Value};
   3.266 -                    }
   3.267 -                    break;
   3.268 -                case "Pseudo":
   3.269 -                    if(match.Groups[3].Success)
   3.270 -                        switch(match.Groups[1].Value) {
   3.271 -                            case "not":
   3.272 -                                IEnumerable<Element> negated = Parse(match.Groups[3].Value);
   3.273 -                                yield return new PseudoClassSelector<IEnumerable<Element>>
   3.274 -                                    {PseudoClass = match.Groups[1].Value, Argument = negated};
   3.275 -                                break;
   3.276 -                            default:
   3.277 -                                yield return new PseudoClassSelector<string>{PseudoClass = match.Groups[1].Value, Argument = match.Groups[3].Value};
   3.278 -                                break;
   3.279 -                        }
   3.280 -                    else
   3.281 -                        yield return new PseudoClassSelector{PseudoClass = match.Groups[1].Value };
   3.282 -                    break;
   3.283 -                case "Combinator":
   3.284 -                    yield return new Combinator{Mode = match.Groups[1].Value};
   3.285 -                    break;
   3.286 -                case "Comma":
   3.287 -                    yield return new Comma();
   3.288 -                    break;
   3.289 -                default:
   3.290 -                    throw new ArgumentException(String.Format("invalid selector: {0}", selector));
   3.291 -            }
   3.292 -        }
   3.293 -    }
   3.294 -
   3.295 -    public static string ToXPath(this IEnumerable<Element> tokens) {
   3.296 -        var parts = new [] {"//", "*"}.ToList();
   3.297 -        var index = 1;
   3.298 -
   3.299 -        foreach(Element token in tokens) {
   3.300 -            if (token is SimpleSelector) {
   3.301 -                SimpleSelector selector = (SimpleSelector)token;
   3.302 -
   3.303 -                if (selector is TypeSelector) {
   3.304 -                    parts[index] = selector.ToXPath();
   3.305 -                }
   3.306 -                else if (selector is PseudoClassSelector) {
   3.307 -                    PseudoClassSelector pseudo = (PseudoClassSelector)selector;
   3.308 -                    switch(pseudo.PseudoClass) {
   3.309 -                        case "first-child":
   3.310 -                        case "last-child":
   3.311 -                            int last = parts.Count - 1;
   3.312 -                            parts[last] = pseudo.ToXPath() + parts[last];
   3.313 -                            break;
   3.314 -                        default:
   3.315 -                            parts.Add(pseudo.ToXPath());
   3.316 -                            break;
   3.317 -                    }
   3.318 -                }
   3.319 -                else {
   3.320 -                    parts.Add(selector.ToXPath());
   3.321 -                }
   3.322 -            }
   3.323 -            else if (token is Combinator) {
   3.324 -                Combinator combinator = (Combinator)token;
   3.325 -                parts.Add(combinator.ToXPath());
   3.326 -
   3.327 -                index = parts.Count;
   3.328 -                parts.Add("*");
   3.329 -            }
   3.330 -            else if (token is Comma) {
   3.331 -                parts.Add(" | ");
   3.332 -            }
   3.333 -            else {
   3.334 -                throw new Exception();
   3.335 -            }
   3.336 -        }
   3.337 -
   3.338 -        return String.Join("", parts.ToArray());
   3.339 -    }
   3.340 -}
   3.341 -
   3.342 -class Css3SelectorExperiment {
   3.343 -    public static void Main (string[] args)
   3.344 -    {
   3.345 -        using(TextReader selectors = new StreamReader("selectors.txt")) {
   3.346 -            string line;
   3.347 -            while ((line = selectors.ReadLine()) != null) {
   3.348 -                string selector, reference, xpath;
   3.349 -                var split = line.Split(new[]{','}, 2);
   3.350 -                selector = split[0];
   3.351 -                reference = split[1].Trim();
   3.352 -
   3.353 -                Console.WriteLine("CSS: {0}", selector);
   3.354 -                var tokens = Css.Parse(selector).ToList();
   3.355 -                Console.WriteLine("  tokens -> {0}", tokens.Select(t => t.ToString()).Aggregate((a,b) => a+" "+b));
   3.356 -                xpath = tokens.ToXPath();
   3.357 -                if(xpath == reference)
   3.358 -                    Console.WriteLine("OK '{0}' -> '{1}'", selector, reference);
   3.359 -                else
   3.360 -                    Console.WriteLine("ERROR on '{0}' -> '{1}' (expected '{2}')", selector, xpath, reference);
   3.361 -            }
   3.362 -        }
   3.363 -
   3.364 -        LxmlTestCssPy.Run();
   3.365 -    }
   3.366 -}
     4.1 --- a/LICENSE	Tue Dec 30 18:36:08 2008 +0100
     4.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 @@ -1,21 +0,0 @@
     4.4 -Template: http://www.opensource.org/licenses/mit-license.html
     4.5 -
     4.6 -Copyright (c) 2008 Emanuele Aina <em@nerd.ocracy.org>
     4.7 -
     4.8 -Permission is hereby granted, free of charge, to any person obtaining a copy
     4.9 -of this software and associated documentation files (the "Software"), to deal
    4.10 -in the Software without restriction, including without limitation the rights
    4.11 -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    4.12 -copies of the Software, and to permit persons to whom the Software is
    4.13 -furnished to do so, subject to the following conditions:
    4.14 -
    4.15 -The above copyright notice and this permission notice shall be included in
    4.16 -all copies or substantial portions of the Software.
    4.17 -
    4.18 -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    4.19 -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    4.20 -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    4.21 -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    4.22 -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    4.23 -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    4.24 -THE SOFTWARE.
     5.1 --- a/LxmlTestCssPy.cs	Tue Dec 30 18:36:08 2008 +0100
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,75 +0,0 @@
     5.4 -/* Tests for the CSS3 Selectors to XPath converter
     5.5 - *
     5.6 - * Parse the tests in the test_css.py python file from lxml and run them
     5.7 - *
     5.8 - * Emanuele Aina <em@nerd.ocracy.org>
     5.9 - */
    5.10 -using System;
    5.11 -using System.Xml;
    5.12 -using System.Collections.Generic;
    5.13 -using System.Linq;
    5.14 -using System.IO;
    5.15 -using System.Text.RegularExpressions;
    5.16 -
    5.17 -public static class LxmlTestCssPy {
    5.18 -    public static void Run()
    5.19 -    {
    5.20 -        XmlDocument doc = new XmlDocument();
    5.21 -        doc.Load("lxml/css_shakespear.html");
    5.22 -
    5.23 -        XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
    5.24 -        ns.AddNamespace("_", "http://www.w3.org/1999/xhtml");
    5.25 -
    5.26 -        // Remove the <head> tag, as it is not counted when matching in html
    5.27 -        XmlNode head = doc.SelectSingleNode("//_:head", ns);
    5.28 -        Console.WriteLine("{0} {1} {2}", head, ns.DefaultNamespace, doc.NameTable.Get(String.Empty));
    5.29 -        head.ParentNode.RemoveChild(head);
    5.30 -
    5.31 -        // Then match selectors starting from the <html> tag
    5.32 -        XmlNode html = doc.SelectSingleNode("//_:html", ns);
    5.33 -
    5.34 -        using(TextReader py = new StreamReader("lxml/test_css.py")) {
    5.35 -            string line;
    5.36 -            while ((line = py.ReadLine()) != null) {
    5.37 -                if(line.Trim().StartsWith("selectors"))
    5.38 -                    break;
    5.39 -            }
    5.40 -            while ((line = py.ReadLine()) != null) {
    5.41 -                if(line.Trim().StartsWith("#"))
    5.42 -                    continue;
    5.43 -                if(line.Trim().StartsWith("]"))
    5.44 -                    break;
    5.45 -                Match m = Regex.Match(line, @"\('(.*)', (\d+)\)");
    5.46 -                if(!m.Success)
    5.47 -                    throw new Exception();
    5.48 -                string selector = m.Groups[1].Value;
    5.49 -                int reference = Int32.Parse(m.Groups[2].Value);
    5.50 -
    5.51 -                Console.WriteLine("selector, reference: {0}, {1}", selector, reference);
    5.52 -
    5.53 -                int count = -1;
    5.54 -                string xpath = null;
    5.55 -                Exception exception = null;
    5.56 -                try {
    5.57 -                    xpath = Css.Parse(selector).Select(s => {
    5.58 -                        if (s is Css.TypeSelector)
    5.59 -                            ((Css.TypeSelector)s).Namespace = "_";
    5.60 -                        return s;
    5.61 -                    }).ToXPath();
    5.62 -                    // Prefix the xpath expression with the current node, otherwise the <html> tag 
    5.63 -                    // will be matched too
    5.64 -                    count = html.SelectNodes("."+xpath, ns).Count;
    5.65 -                } catch (Exception e){
    5.66 -                    exception = e;
    5.67 -                }
    5.68 -
    5.69 -                if(count == reference)
    5.70 -                    Console.WriteLine("OK '{0}' -> '{1}' ({2})", selector, xpath, count);
    5.71 -                else if(exception != null)
    5.72 -                    Console.WriteLine("ERROR on '{0}' -> '{1}' (got {2}, expected '{3}', {4})", selector, xpath, count, reference, exception);
    5.73 -                else
    5.74 -                    Console.WriteLine("ERROR on '{0}' -> '{1}' (got {2}, expected '{3}')", selector, xpath, count, reference);
    5.75 -            }
    5.76 -        }
    5.77 -    }
    5.78 -}
     6.1 --- a/Makefile	Tue Dec 30 18:36:08 2008 +0100
     6.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.3 @@ -1,17 +0,0 @@
     6.4 -SOURCES := Css3SelectorExperiment.cs LxmlTestCssPy.cs
     6.5 -EXE := Css3SelectorExperiment.exe
     6.6 -REFERENCES := $(addprefix -r:, System.Core)
     6.7 -
     6.8 -MCS := gmcs
     6.9 -MONO := mono --debug
    6.10 -
    6.11 -all: $(EXE)
    6.12 -
    6.13 -run: $(EXE)
    6.14 -	@${MONO} $(EXE)
    6.15 -
    6.16 -clean:
    6.17 -	-rm -Rf $(EXE)
    6.18 -
    6.19 -$(EXE): $(SOURCES)
    6.20 -	$(MCS) $(REFERENCES) -out:"$@" $^
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/Makefile.am	Tue Dec 30 22:26:25 2008 +0100
     7.3 @@ -0,0 +1,12 @@
     7.4 +SUBDIRS = src tests
     7.5 +EXTRA_DIST = css3-selectors.pc.in
     7.6 +
     7.7 +pkgconfigdir = $(libdir)/pkgconfig
     7.8 +pkgconfig_DATA = css3-selectors.pc
     7.9 +
    7.10 +DISTCLEANFILES = config.guess config.sub Makefile.in configure COPYING INSTALL install-sh missing aclocal.m4
    7.11 +
    7.12 +
    7.13 +MONO := mono --debug
    7.14 +run: all
    7.15 +	@MONO_PATH=src/ ${MONO} tests/testsrunner.exe
     8.1 --- a/README	Tue Dec 30 18:36:08 2008 +0100
     8.2 +++ b/README	Tue Dec 30 22:26:25 2008 +0100
     8.3 @@ -1,18 +1,22 @@
     8.4 -CSS3 to XPath converter
     8.5 -=======================
     8.6 +CSS3 Selectors
     8.7 +==============
     8.8  
     8.9 -This is an incomplete converter from CSS3 selectors to XPath
    8.10 -expressions written in C# using Mono.
    8.11 +This is a .NET library to convert extendend CSS Level 3 selectors to XPath
    8.12 +expression written in C#.
    8.13 +This enable the use of CSS selectors to select nodes in the XML DOM, in
    8.14 +XML to Linq trees or with any interface that supports XPath expressions.
    8.15  
    8.16 -It parses CSS3 selectors to a list of elements and then it translates
    8.17 -this list to the corresponding XPath expression.
    8.18 +The code is released under a MIT/X11-style license (see COPYING).
    8.19  
    8.20 -It currently handles type, id (#foo), and class (.foo) selectors,
    8.21 -attribute selectors with various comparison methods (e.g.
    8.22 -[foo~='bar'], [foo]), some pseudo-classes (e.g. :first-child,
    8.23 -:nth-child, :lang, :not), and combinators (e.g. '+', '>').
    8.24 +Development
    8.25 +===========
    8.26  
    8.27 -It should be fairly easy to extend as the parsing and tranlsation
    8.28 -phases are clearly separated.
    8.29 +The code has been developed and tested with Mono, but should work even
    8.30 +with other .NET implementation.
    8.31  
    8.32 -The code is released under a MIT/X11 style license (see LICENSE).
    8.33 +The source code is available in a Mercurial repository at:
    8.34 +
    8.35 +http://techn.ocracy.org/css3-selectors
    8.36 +
    8.37 +Feel free to ask anything about this library to
    8.38 +Emanuele Aina <em@nerd.ocracy.org>
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/autogen.sh	Tue Dec 30 22:26:25 2008 +0100
     9.3 @@ -0,0 +1,84 @@
     9.4 +#! /bin/sh
     9.5 +
     9.6 +PROJECT=css3-selector
     9.7 +FILE=src/Css3Selectors.cs
     9.8 +CONFIGURE=configure.ac
     9.9 +
    9.10 +: ${AUTOCONF=autoconf}
    9.11 +: ${AUTOHEADER=autoheader}
    9.12 +: ${AUTOMAKE=automake}
    9.13 +: ${LIBTOOLIZE=libtoolize}
    9.14 +: ${ACLOCAL=aclocal}
    9.15 +: ${LIBTOOL=libtool}
    9.16 +
    9.17 +srcdir=`dirname $0`
    9.18 +test -z "$srcdir" && srcdir=.
    9.19 +
    9.20 +ORIGDIR=`pwd`
    9.21 +cd $srcdir
    9.22 +TEST_TYPE=-f
    9.23 +aclocalinclude="-I . $ACLOCAL_FLAGS"
    9.24 +
    9.25 +DIE=0
    9.26 +
    9.27 +($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
    9.28 +        echo
    9.29 +        echo "You must have autoconf installed to compile $PROJECT."
    9.30 +        echo "Download the appropriate package for your distribution,"
    9.31 +        echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/"
    9.32 +        DIE=1
    9.33 +}
    9.34 +
    9.35 +($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
    9.36 +        echo
    9.37 +        echo "You must have automake installed to compile $PROJECT."
    9.38 +        echo "Get ftp://sourceware.cygnus.com/pub/automake/automake-1.4.tar.gz"
    9.39 +        echo "(or a newer version if it is available)"
    9.40 +        DIE=1
    9.41 +}
    9.42 +
    9.43 +(grep "^AM_PROG_LIBTOOL" $CONFIGURE >/dev/null) && {
    9.44 +  ($LIBTOOL --version) < /dev/null > /dev/null 2>&1 || {
    9.45 +    echo
    9.46 +    echo "**Error**: You must have \`libtool' installed to compile $PROJECT."
    9.47 +    echo "Get ftp://ftp.gnu.org/pub/gnu/libtool-1.2d.tar.gz"
    9.48 +    echo "(or a newer version if it is available)"
    9.49 +    DIE=1
    9.50 +  }
    9.51 +}
    9.52 +
    9.53 +if test "$DIE" -eq 1; then
    9.54 +        exit 1
    9.55 +fi
    9.56 +                                                                                
    9.57 +test $TEST_TYPE $FILE || {
    9.58 +        echo "You must run this script in the top-level $PROJECT directory"
    9.59 +        exit 1
    9.60 +}
    9.61 +
    9.62 +if test -z "$*"; then
    9.63 +        echo "I am going to run ./configure with no arguments - if you wish "
    9.64 +        echo "to pass any to it, please specify them on the $0 command line."
    9.65 +fi
    9.66 +
    9.67 +case $CC in
    9.68 +*xlc | *xlc\ * | *lcc | *lcc\ *) am_opt=--include-deps;;
    9.69 +esac
    9.70 +
    9.71 +(grep "^AM_PROG_LIBTOOL" $CONFIGURE >/dev/null) && {
    9.72 +    echo "Running $LIBTOOLIZE ..."
    9.73 +    $LIBTOOLIZE --force --copy
    9.74 +}
    9.75 +
    9.76 +echo "Running $ACLOCAL $aclocalinclude ..."
    9.77 +$ACLOCAL $aclocalinclude
    9.78 +
    9.79 +echo "Running $AUTOMAKE --gnu $am_opt ..."
    9.80 +$AUTOMAKE --add-missing --gnu $am_opt
    9.81 +
    9.82 +echo "Running $AUTOCONF ..."
    9.83 +$AUTOCONF
    9.84 +
    9.85 +echo Running $srcdir/configure $conf_flags "$@" ...
    9.86 +$srcdir/configure --enable-maintainer-mode $conf_flags "$@" \
    9.87 +
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/configure.ac	Tue Dec 30 22:26:25 2008 +0100
    10.3 @@ -0,0 +1,39 @@
    10.4 +AC_INIT(README)
    10.5 +AC_CANONICAL_SYSTEM
    10.6 +AM_INIT_AUTOMAKE(css3-selectors, 0.1)
    10.7 +AM_MAINTAINER_MODE
    10.8 +
    10.9 +AC_PROG_INSTALL
   10.10 +
   10.11 +dnl pkg-config
   10.12 +AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
   10.13 +if test "x$PKG_CONFIG" = "xno"; then
   10.14 +	AC_MSG_ERROR([You need to install pkg-config])
   10.15 +fi
   10.16 +
   10.17 +AC_PATH_PROG(CSC, csc, no)
   10.18 +AC_PATH_PROG(MCS, gmcs, no)
   10.19 +AC_PATH_PROG(MONO, mono, no)
   10.20 +
   10.21 +CS="C#"
   10.22 +if test "x$CSC" = "xno" -a "x$MCS" = "xno"  ; then
   10.23 +	AC_MSG_ERROR([No $CS compiler found])
   10.24 +fi
   10.25 +
   10.26 +if test "x$MCS" = "xno" ; then
   10.27 +	MCS=$CSC
   10.28 +fi
   10.29 +
   10.30 +if test "x$MONO" = "xno"; then
   10.31 +	AC_MSG_ERROR([No mono runtime found])
   10.32 +fi
   10.33 +
   10.34 +AC_SUBST(MCS)
   10.35 +
   10.36 +AC_OUTPUT([
   10.37 +Makefile
   10.38 +css3-selectors.pc
   10.39 +src/Makefile
   10.40 +tests/Makefile
   10.41 +src/AssemblyInfo.cs
   10.42 +])
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/css3-selectors.pc.in	Tue Dec 30 22:26:25 2008 +0100
    11.3 @@ -0,0 +1,11 @@
    11.4 +prefix=@prefix@
    11.5 +exec_prefix=@exec_prefix@
    11.6 +libdir=@libdir@
    11.7 +includedir=@includedir@
    11.8 +Libraries=@libdir@/css3-selectors/css3-selectors.dll
    11.9 +
   11.10 +Name: css3-selectors
   11.11 +Description: css3-selectors - CSS Level 3 Selectors to XPath converter
   11.12 +Version: @VERSION@
   11.13 +Libs: -r:@libdir@/css3-selectors/css3-selectors.dll
   11.14 +
    12.1 --- a/lxml/css_shakespear.html	Tue Dec 30 18:36:08 2008 +0100
    12.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.3 @@ -1,526 +0,0 @@
    12.4 -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    12.5 -	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    12.6 -
    12.7 -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" debug="true">
    12.8 -<head>
    12.9 -	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
   12.10 -</head>
   12.11 -
   12.12 -<body>
   12.13 -	
   12.14 -	<div id="test">
   12.15 -	<div class="dialog">
   12.16 -	<h2>As You Like It</h2>
   12.17 -	<div id="playwright">
   12.18 -
   12.19 -	  by William Shakespeare
   12.20 -
   12.21 -
   12.22 -	</div>
   12.23 -	<div class="dialog scene thirdClass" id="scene1">
   12.24 -
   12.25 -	  <h3>ACT I, SCENE III. A room in the palace.</h3>
   12.26 -
   12.27 -	  <div class="dialog">
   12.28 -	  <div class="direction">Enter CELIA and ROSALIND</div>
   12.29 -
   12.30 -	  </div>
   12.31 -
   12.32 -	  <div id="speech1" class="character">CELIA</div>
   12.33 -
   12.34 -	  <div class="dialog">
   12.35 -	  <div id="scene1.3.1">Why, cousin! why, Rosalind! Cupid have mercy! not a word?</div>
   12.36 -
   12.37 -	  </div>
   12.38 -
   12.39 -	  <div id="speech2" class="character">ROSALIND</div>
   12.40 -
   12.41 -	  <div class="dialog">
   12.42 -	  <div id="scene1.3.2">Not one to throw at a dog.</div>
   12.43 -
   12.44 -	  </div>
   12.45 -
   12.46 -	  <div id="speech3" class="character">CELIA</div>
   12.47 -
   12.48 -	  <div class="dialog">
   12.49 -	  <div id="scene1.3.3">No, thy words are too precious to be cast away upon</div>
   12.50 -
   12.51 -	  <div id="scene1.3.4">curs; throw some of them at me; come, lame me with reasons.</div>
   12.52 -
   12.53 -	  </div>
   12.54 -
   12.55 -	  <div id="speech4" class="character">ROSALIND</div>
   12.56 -
   12.57 -	  <div id="speech5" class="character">CELIA</div>
   12.58 -
   12.59 -	  <div class="dialog">
   12.60 -	  <div id="scene1.3.8">But is all this for your father?</div>
   12.61 -
   12.62 -	  </div>
   12.63 -
   12.64 -	  <div class="dialog">
   12.65 -	  <div id="scene1.3.5">Then there were two cousins laid up; when the one</div>
   12.66 -	  <div id="scene1.3.6">should be lamed with reasons and the other mad</div>
   12.67 -
   12.68 -	  <div id="scene1.3.7">without any.</div>
   12.69 -	  </div>
   12.70 -
   12.71 -	  <div id="speech6" class="character">ROSALIND</div>
   12.72 -
   12.73 -	  <div class="dialog">
   12.74 -	  <div id="scene1.3.9">No, some of it is for my child's father. O, how</div>
   12.75 -
   12.76 -	  <div id="scene1.3.10">full of briers is this working-day world!</div>
   12.77 -
   12.78 -	  </div>
   12.79 -
   12.80 -	  <div id="speech7" class="character">CELIA</div>
   12.81 -
   12.82 -	  <div class="dialog">
   12.83 -
   12.84 -	  <div id="scene1.3.11">They are but burs, cousin, thrown upon thee in</div>
   12.85 -	  <div id="scene1.3.12">holiday foolery: if we walk not in the trodden</div>
   12.86 -
   12.87 -	  <div id="scene1.3.13">paths our very petticoats will catch them.</div>
   12.88 -
   12.89 -	  </div>
   12.90 -
   12.91 -	  <div id="speech8" class="character">ROSALIND</div>
   12.92 -
   12.93 -	  <div class="dialog">
   12.94 -	  <div id="scene1.3.14">I could shake them off my coat: these burs are in my heart.</div>
   12.95 -	  </div>
   12.96 -
   12.97 -	  <div id="speech9" class="character">CELIA</div>
   12.98 -
   12.99 -	  <div class="dialog">
  12.100 -	  <div id="scene1.3.15">Hem them away.</div>
  12.101 -
  12.102 -	  </div>
  12.103 -
  12.104 -	  <div id="speech10" class="character">ROSALIND</div>
  12.105 -
  12.106 -	  <div class="dialog">
  12.107 -
  12.108 -	  <div id="scene1.3.16">I would try, if I could cry 'hem' and have him.</div>
  12.109 -	  </div>
  12.110 -
  12.111 -	  <div id="speech11" class="character">CELIA</div>
  12.112 -
  12.113 -	  <div class="dialog">
  12.114 -	  <div id="scene1.3.17">Come, come, wrestle with thy affections.</div>
  12.115 -
  12.116 -	  </div>
  12.117 -
  12.118 -	  <div id="speech12" class="character">ROSALIND</div>
  12.119 -	  <div class="dialog">
  12.120 -	  <div id="scene1.3.18">O, they take the part of a better wrestler than myself!</div>
  12.121 -
  12.122 -	  </div>
  12.123 -
  12.124 -	  <div id="speech13" class="character">CELIA</div>
  12.125 -
  12.126 -	  <div class="dialog">
  12.127 -
  12.128 -	  <div id="scene1.3.19">O, a good wish upon you! you will try in time, in</div>
  12.129 -	  <div id="scene1.3.20">despite of a fall. But, turning these jests out of</div>
  12.130 -	  <div id="scene1.3.21">service, let us talk in good earnest: is it</div>
  12.131 -
  12.132 -	  <div id="scene1.3.22">possible, on such a sudden, you should fall into so</div>
  12.133 -
  12.134 -	  <div id="scene1.3.23">strong a liking with old Sir Rowland's youngest son?</div>
  12.135 -
  12.136 -	  </div>
  12.137 -
  12.138 -	  <div id="speech14" class="character">ROSALIND</div>
  12.139 -	  <div class="dialog">
  12.140 -	  <div id="scene1.3.24">The duke my father loved his father dearly.</div>
  12.141 -
  12.142 -	  </div>
  12.143 -
  12.144 -	  <div id="speech15" class="character">CELIA</div>
  12.145 -
  12.146 -	  <div class="dialog">
  12.147 -
  12.148 -	  <div id="scene1.3.25">Doth it therefore ensue that you should love his son</div>
  12.149 -
  12.150 -	  <div id="scene1.3.26">dearly? By this kind of chase, I should hate him,</div>
  12.151 -
  12.152 -	  <div id="scene1.3.27">for my father hated his father dearly; yet I hate</div>
  12.153 -
  12.154 -	  <div id="scene1.3.28">not Orlando.</div>
  12.155 -
  12.156 -	  </div>
  12.157 -
  12.158 -	  <div id="speech16" class="character">ROSALIND</div>
  12.159 -
  12.160 -	  <div title="wtf" class="dialog">
  12.161 -
  12.162 -	  <div id="scene1.3.29">No, faith, hate him not, for my sake.</div>
  12.163 -
  12.164 -	  </div>
  12.165 -
  12.166 -	  <div id="speech17" class="character">CELIA</div>
  12.167 -
  12.168 -	  <div class="dialog">
  12.169 -	  <div id="scene1.3.30">Why should I not? doth he not deserve well?</div>
  12.170 -
  12.171 -	  </div>
  12.172 -
  12.173 -	  <div id="speech18" class="character">ROSALIND</div>
  12.174 -
  12.175 -	  <div class="dialog">
  12.176 -	  <div id="scene1.3.31">Let me love him for that, and do you love him</div>
  12.177 -	  <div id="scene1.3.32">because I do. Look, here comes the duke.</div>
  12.178 -	  </div>
  12.179 -
  12.180 -	  <div id="speech19" class="character">CELIA</div>
  12.181 -
  12.182 -	  <div class="dialog">
  12.183 -
  12.184 -	  <div id="scene1.3.33">With his eyes full of anger.</div>
  12.185 -	  <div class="direction">Enter DUKE FREDERICK, with Lords</div>
  12.186 -	  </div>
  12.187 -
  12.188 -	  <div id="speech20" class="character">DUKE FREDERICK</div>
  12.189 -
  12.190 -	  <div class="dialog">
  12.191 -
  12.192 -	  <div id="scene1.3.34">Mistress, dispatch you with your safest haste</div>
  12.193 -
  12.194 -	  <div id="scene1.3.35">And get you from our court.</div>
  12.195 -	  </div>
  12.196 -
  12.197 -	  <div id="speech21" class="character">ROSALIND</div>
  12.198 -
  12.199 -	  <div class="dialog">
  12.200 -
  12.201 -	  <div id="scene1.3.36">Me, uncle?</div>
  12.202 -
  12.203 -	  </div>
  12.204 -
  12.205 -	  <div id="speech22" class="character">DUKE FREDERICK</div>
  12.206 -	  <div class="dialog">
  12.207 -	  <div id="scene1.3.37">You, cousin</div>
  12.208 -
  12.209 -	  <div id="scene1.3.38">Within these ten days if that thou be'st found</div>
  12.210 -
  12.211 -	  <div id="scene1.3.39">So near our public court as twenty miles,</div>
  12.212 -
  12.213 -	  <div id="scene1.3.40">Thou diest for it.</div>
  12.214 -
  12.215 -	  </div>
  12.216 -
  12.217 -	  <div id="speech23" class="character">ROSALIND</div>
  12.218 -
  12.219 -	  <div class="dialog">
  12.220 -
  12.221 -	  <div id="scene1.3.41">                  I do beseech your grace,</div>
  12.222 -
  12.223 -	  <div id="scene1.3.42">Let me the knowledge of my fault bear with me:</div>
  12.224 -	  <div id="scene1.3.43">If with myself I hold intelligence</div>
  12.225 -
  12.226 -	  <div id="scene1.3.44">Or have acquaintance with mine own desires,</div>
  12.227 -
  12.228 -	  <div id="scene1.3.45">If that I do not dream or be not frantic,--</div>
  12.229 -
  12.230 -	  <div id="scene1.3.46">As I do trust I am not--then, dear uncle,</div>
  12.231 -
  12.232 -	  <div id="scene1.3.47">Never so much as in a thought unborn</div>
  12.233 -
  12.234 -	  <div id="scene1.3.48">Did I offend your highness.</div>
  12.235 -
  12.236 -	  </div>
  12.237 -
  12.238 -	  <div id="speech24" class="character">DUKE FREDERICK</div>
  12.239 -
  12.240 -	  <div class="dialog">
  12.241 -	  <div id="scene1.3.49">Thus do all traitors:</div>
  12.242 -
  12.243 -	  <div id="scene1.3.50">If their purgation did consist in words,</div>
  12.244 -
  12.245 -	  <div id="scene1.3.51">They are as innocent as grace itself:</div>
  12.246 -
  12.247 -	  <div id="scene1.3.52">Let it suffice thee that I trust thee not.</div>
  12.248 -
  12.249 -	  </div>
  12.250 -
  12.251 -	  <div id="speech25" class="character">ROSALIND</div>
  12.252 -
  12.253 -	  <div class="dialog">
  12.254 -
  12.255 -	  <div id="scene1.3.53">Yet your mistrust cannot make me a traitor:</div>
  12.256 -
  12.257 -	  <div id="scene1.3.54">Tell me whereon the likelihood depends.</div>
  12.258 -
  12.259 -	  </div>
  12.260 -
  12.261 -	  <div id="speech26" class="character">DUKE FREDERICK</div>
  12.262 -	  <div class="dialog">
  12.263 -
  12.264 -	  <div id="scene1.3.55">Thou art thy father's daughter; there's enough.</div>
  12.265 -
  12.266 -	  </div>
  12.267 -
  12.268 -	  <div id="speech27" class="character">ROSALIND</div>
  12.269 -
  12.270 -	  <div class="dialog">
  12.271 -	  <div id="scene1.3.56">So was I when your highness took his dukedom;</div>
  12.272 -	  <div id="scene1.3.57">So was I when your highness banish'd him:</div>
  12.273 -
  12.274 -	  <div id="scene1.3.58">Treason is not inherited, my lord;</div>
  12.275 -
  12.276 -	  <div id="scene1.3.59">Or, if we did derive it from our friends,</div>
  12.277 -
  12.278 -	  <div id="scene1.3.60">What's that to me? my father was no traitor:</div>
  12.279 -
  12.280 -	  <div id="scene1.3.61">Then, good my liege, mistake me not so much</div>
  12.281 -	  <div id="scene1.3.62">To think my poverty is treacherous.</div>
  12.282 -
  12.283 -	  </div>
  12.284 -
  12.285 -	  <div id="speech28" class="character">CELIA</div>
  12.286 -	  <div class="dialog">
  12.287 -
  12.288 -	  <div id="scene1.3.63">Dear sovereign, hear me speak.</div>
  12.289 -
  12.290 -	  </div>
  12.291 -
  12.292 -	  <div id="speech29" class="character">DUKE FREDERICK</div>
  12.293 -
  12.294 -	  <div class="dialog">
  12.295 -	  <div id="scene1.3.64">Ay, Celia; we stay'd her for your sake,</div>
  12.296 -	  <div id="scene1.3.65">Else had she with her father ranged along.</div>
  12.297 -
  12.298 -	  </div>
  12.299 -
  12.300 -	  <div id="speech30" class="character">CELIA</div>
  12.301 -
  12.302 -	  <div class="dialog">
  12.303 -
  12.304 -	  <div id="scene1.3.66">I did not then entreat to have her stay;</div>
  12.305 -	  <div id="scene1.3.67">It was your pleasure and your own remorse:</div>
  12.306 -	  <div id="scene1.3.68">I was too young that time to value her;</div>
  12.307 -
  12.308 -	  <div id="scene1.3.69">But now I know her: if she be a traitor,</div>
  12.309 -
  12.310 -	  <div id="scene1.3.70">Why so am I; we still have slept together,</div>
  12.311 -
  12.312 -	  <div id="scene1.3.71">Rose at an instant, learn'd, play'd, eat together,</div>
  12.313 -	  <div id="scene1.3.72">And wheresoever we went, like Juno's swans,</div>
  12.314 -
  12.315 -	  <div id="scene1.3.73">Still we went coupled and inseparable.</div>
  12.316 -	  </div>
  12.317 -
  12.318 -	  <div id="speech31" class="character">DUKE FREDERICK</div>
  12.319 -
  12.320 -	  <div class="dialog">
  12.321 -	  <div id="scene1.3.74">She is too subtle for thee; and her smoothness,</div>
  12.322 -	  <div id="scene1.3.75">Her very silence and her patience</div>
  12.323 -	  <div id="scene1.3.76">Speak to the people, and they pity her.</div>
  12.324 -	  <div id="scene1.3.77">Thou art a fool: she robs thee of thy name;</div>
  12.325 -
  12.326 -	  <div id="scene1.3.78">And thou wilt show more bright and seem more virtuous</div>
  12.327 -
  12.328 -	  <div id="scene1.3.79">When she is gone. Then open not thy lips:</div>
  12.329 -	  <div id="scene1.3.80">Firm and irrevocable is my doom</div>
  12.330 -	  <div id="scene1.3.81">Which I have pass'd upon her; she is banish'd.</div>
  12.331 -	  </div>
  12.332 -
  12.333 -	  <div id="speech32" class="character">CELIA</div>
  12.334 -
  12.335 -	  <div class="dialog">
  12.336 -	  <div id="scene1.3.82">Pronounce that sentence then on me, my liege:</div>
  12.337 -	  <div id="scene1.3.83">I cannot live out of her company.</div>
  12.338 -	  </div>
  12.339 -
  12.340 -	  <div id="speech33" class="character">DUKE FREDERICK</div>
  12.341 -
  12.342 -	  <div class="dialog">
  12.343 -	  <div id="scene1.3.84">You are a fool. You, niece, provide yourself:</div>
  12.344 -
  12.345 -	  <div id="scene1.3.85">If you outstay the time, upon mine honour,</div>
  12.346 -	  <div id="scene1.3.86">And in the greatness of my word, you die.</div>
  12.347 -	  <div class="direction">Exeunt DUKE FREDERICK and Lords</div>
  12.348 -	  </div>
  12.349 -
  12.350 -	  <div id="speech34" class="character">CELIA</div>
  12.351 -	  <div class="dialog">
  12.352 -
  12.353 -	  <div id="scene1.3.87">O my poor Rosalind, whither wilt thou go?</div>
  12.354 -
  12.355 -	  <div id="scene1.3.88">Wilt thou change fathers? I will give thee mine.</div>
  12.356 -	  <div id="scene1.3.89">I charge thee, be not thou more grieved than I am.</div>
  12.357 -
  12.358 -	  </div>
  12.359 -
  12.360 -	  <div id="speech35" class="character">ROSALIND</div>
  12.361 -
  12.362 -	  <div class="dialog">
  12.363 -
  12.364 -	  <div id="scene1.3.90">I have more cause.</div>
  12.365 -	  </div>
  12.366 -
  12.367 -	  <div id="speech36" class="character">CELIA</div>
  12.368 -
  12.369 -	  <div class="dialog">
  12.370 -	  <div id="scene1.3.91">                  Thou hast not, cousin;</div>
  12.371 -
  12.372 -	  <div id="scene1.3.92">Prithee be cheerful: know'st thou not, the duke</div>
  12.373 -
  12.374 -	  <div id="scene1.3.93">Hath banish'd me, his daughter?</div>
  12.375 -
  12.376 -	  </div>
  12.377 -
  12.378 -	  <div id="speech37" class="character">ROSALIND</div>
  12.379 -	  <div class="dialog">
  12.380 -	  <div id="scene1.3.94">That he hath not.</div>
  12.381 -
  12.382 -	  </div>
  12.383 -
  12.384 -	  <div id="speech38" class="character">CELIA</div>
  12.385 -
  12.386 -	  <div class="dialog">
  12.387 -
  12.388 -	  <div id="scene1.3.95">No, hath not? Rosalind lacks then the love</div>
  12.389 -
  12.390 -	  <div id="scene1.3.96">Which teacheth thee that thou and I am one:</div>
  12.391 -	  <div id="scene1.3.97">Shall we be sunder'd? shall we part, sweet girl?</div>
  12.392 -
  12.393 -	  <div id="scene1.3.98">No: let my father seek another heir.</div>
  12.394 -
  12.395 -	  <div id="scene1.3.99">Therefore devise with me how we may fly,</div>
  12.396 -
  12.397 -	  <div id="scene1.3.100">Whither to go and what to bear with us;</div>
  12.398 -	  <div id="scene1.3.101">And do not seek to take your change upon you,</div>
  12.399 -	  <div id="scene1.3.102">To bear your griefs yourself and leave me out;</div>
  12.400 -
  12.401 -	  <div id="scene1.3.103">For, by this heaven, now at our sorrows pale,</div>
  12.402 -
  12.403 -	  <div id="scene1.3.104">Say what thou canst, I'll go along with thee.</div>
  12.404 -
  12.405 -	  </div>
  12.406 -
  12.407 -	  <div id="speech39" class="character">ROSALIND</div>
  12.408 -	  <div class="dialog">
  12.409 -
  12.410 -	  <div id="scene1.3.105">Why, whither shall we go?</div>
  12.411 -
  12.412 -	  </div>
  12.413 -
  12.414 -	  <div id="speech40" class="character">CELIA</div>
  12.415 -
  12.416 -	  <div class="dialog">
  12.417 -	  <div id="scene1.3.106">To seek my uncle in the forest of Arden.</div>
  12.418 -	  </div>
  12.419 -
  12.420 -	  <div id="speech41" class="character">ROSALIND</div>
  12.421 -
  12.422 -	  <div class="dialog">
  12.423 -
  12.424 -	  <div id="scene1.3.107">Alas, what danger will it be to us,</div>
  12.425 -
  12.426 -	  <div id="scene1.3.108">Maids as we are, to travel forth so far!</div>
  12.427 -	  <div id="scene1.3.109">Beauty provoketh thieves sooner than gold.</div>
  12.428 -
  12.429 -	  </div>
  12.430 -
  12.431 -	  <div id="speech42" class="character">CELIA</div>
  12.432 -
  12.433 -	  <div class="dialog">
  12.434 -	  <div id="scene1.3.110">I'll put myself in poor and mean attire</div>
  12.435 -
  12.436 -	  <div id="scene1.3.111">And with a kind of umber smirch my face;</div>
  12.437 -	  <div id="scene1.3.112">The like do you: so shall we pass along</div>
  12.438 -
  12.439 -	  <div id="scene1.3.113">And never stir assailants.</div>
  12.440 -
  12.441 -	  </div>
  12.442 -
  12.443 -	  <div id="speech43" class="character">ROSALIND</div>
  12.444 -	  <div class="dialog">
  12.445 -
  12.446 -	  <div id="scene1.3.114">Were it not better,</div>
  12.447 -
  12.448 -	  <div id="scene1.3.115">Because that I am more than common tall,</div>
  12.449 -
  12.450 -	  <div id="scene1.3.116">That I did suit me all points like a man?</div>
  12.451 -
  12.452 -	  <div id="scene1.3.117">A gallant curtle-axe upon my thigh,</div>
  12.453 -
  12.454 -	  <div id="scene1.3.118">A boar-spear in my hand; and--in my heart</div>
  12.455 -
  12.456 -	  <div id="scene1.3.119">Lie there what hidden woman's fear there will--</div>
  12.457 -
  12.458 -	  <div id="scene1.3.120">We'll have a swashing and a martial outside,</div>
  12.459 -
  12.460 -	  <div id="scene1.3.121">As many other mannish cowards have</div>
  12.461 -
  12.462 -	  <div id="scene1.3.122">That do outface it with their semblances.</div>
  12.463 -
  12.464 -	  </div>
  12.465 -
  12.466 -	  <div id="speech44" class="character">CELIA</div>
  12.467 -
  12.468 -	  <div class="dialog">
  12.469 -
  12.470 -	  <div id="scene1.3.123">What shall I call thee when thou art a man?</div>
  12.471 -	  </div>
  12.472 -
  12.473 -	  <div id="speech45" class="character">ROSALIND</div>
  12.474 -
  12.475 -	  <div class="dialog">
  12.476 -	  <div id="scene1.3.124">I'll have no worse a name than Jove's own page;</div>
  12.477 -
  12.478 -	  <div id="scene1.3.125">And therefore look you call me Ganymede.</div>
  12.479 -
  12.480 -	  <div id="scene1.3.126">But what will you be call'd?</div>
  12.481 -
  12.482 -	  </div>
  12.483 -
  12.484 -	  <div id="speech46" class="character">CELIA</div>
  12.485 -
  12.486 -	  <div class="dialog">
  12.487 -
  12.488 -	  <div id="scene1.3.127">Something that hath a reference to my state</div>
  12.489 -	  <div id="scene1.3.128">No longer Celia, but Aliena.</div>
  12.490 -
  12.491 -	  </div>
  12.492 -
  12.493 -	  <div id="speech47" class="character">ROSALIND</div>
  12.494 -	  <div class="dialog">
  12.495 -
  12.496 -	  <div id="scene1.3.129">But, cousin, what if we assay'd to steal</div>
  12.497 -
  12.498 -	  <div id="scene1.3.130">The clownish fool out of your father's court?</div>
  12.499 -
  12.500 -	  <div id="scene1.3.131">Would he not be a comfort to our travel?</div>
  12.501 -
  12.502 -	  </div>
  12.503 -
  12.504 -	  <div id="speech48" class="character">CELIA</div>
  12.505 -
  12.506 -	  <div class="dialog">
  12.507 -
  12.508 -	  <div id="scene1.3.132">He'll go along o'er the wide world with me;</div>
  12.509 -
  12.510 -	  <div id="scene1.3.133">Leave me alone to woo him. Let's away,</div>
  12.511 -	  <div id="scene1.3.134">And get our jewels and our wealth together,</div>
  12.512 -
  12.513 -	  <div id="scene1.3.135">Devise the fittest time and safest way</div>
  12.514 -
  12.515 -	  <div id="scene1.3.136">To hide us from pursuit that will be made</div>
  12.516 -
  12.517 -	  <div id="scene1.3.137">After my flight. Now go we in content</div>
  12.518 -
  12.519 -	  <div id="scene1.3.138">To liberty and not to banishment.</div>
  12.520 -	  <div class="direction">Exeunt</div>
  12.521 -
  12.522 -	  </div>
  12.523 -
  12.524 -	</div>
  12.525 -	</div>
  12.526 -</div>
  12.527 -</body>
  12.528 -
  12.529 -</html>
  12.530 \ No newline at end of file
    13.1 --- a/lxml/test_css.py	Tue Dec 30 18:36:08 2008 +0100
    13.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.3 @@ -1,133 +0,0 @@
    13.4 -import unittest, sys
    13.5 -from lxml.tests.common_imports import doctest, make_doctest, HelperTestCase
    13.6 -from lxml import html
    13.7 -from lxml import cssselect
    13.8 -import os
    13.9 -
   13.10 -doc_fn = os.path.join(os.path.dirname(__file__),
   13.11 -                      'css_shakespear.html')
   13.12 -
   13.13 -try:
   13.14 -    basestring = __builtins__['basestring']
   13.15 -except (NameError, KeyError):
   13.16 -    basestring = (str, bytes)
   13.17 -
   13.18 -# Data borrowed from http://mootools.net/slickspeed/
   13.19 -
   13.20 -class CSSTestCase(HelperTestCase):
   13.21 -    
   13.22 -    selectors = [
   13.23 -        ## Changed from original; probably because I'm only searching the body
   13.24 -        #('*', 252),
   13.25 -        ('*', 246),
   13.26 -        ('div:only-child', 22), # ?
   13.27 -        ## Changed from original, because the original doesn't make sense.
   13.28 -        ## There really aren't that many occurrances of 'celia'
   13.29 -        #('div:contains(CELIA)', 243),
   13.30 -        ('div:contains(CELIA)', 30),
   13.31 -        ('div:nth-child(even)', 106),
   13.32 -        ('div:nth-child(2n)', 106),
   13.33 -        ('div:nth-child(odd)', 137),
   13.34 -        ('div:nth-child(2n+1)', 137),
   13.35 -        ('div:nth-child(n)', 243),
   13.36 -        ('div:last-child', 53),
   13.37 -        ('div:first-child', 51),
   13.38 -        ('div > div', 242),
   13.39 -        ('div + div', 190),
   13.40 -        ('div ~ div', 190),
   13.41 -        ('body', 1),
   13.42 -        ('body div', 243),
   13.43 -        ('div', 243),
   13.44 -        ('div div', 242),
   13.45 -        ('div div div', 241),
   13.46 -        ('div, div, div', 243),
   13.47 -        ('div, a, span', 243),
   13.48 -        ('.dialog', 51),
   13.49 -        ('div.dialog', 51),
   13.50 -        ('div .dialog', 51),
   13.51 -        ('div.character, div.dialog', 99),
   13.52 -        ('div.direction.dialog', 0),
   13.53 -        ('div.dialog.direction', 0),
   13.54 -        ('div.dialog.scene', 1),
   13.55 -        ('div.scene.scene', 1),
   13.56 -        ('div.scene .scene', 0),
   13.57 -        ('div.direction .dialog ', 0),
   13.58 -        ('div .dialog .direction', 4),
   13.59 -        ('div.dialog .dialog .direction', 4),
   13.60 -        ('#speech5', 1),
   13.61 -        ('div#speech5', 1),
   13.62 -        ('div #speech5', 1),
   13.63 -        ('div.scene div.dialog', 49),
   13.64 -        ('div#scene1 div.dialog div', 142),
   13.65 -        ('#scene1 #speech1', 1),
   13.66 -        ('div[class]', 103),
   13.67 -        ('div[class=dialog]', 50),
   13.68 -        ('div[class^=dia]', 51),
   13.69 -        ('div[class$=log]', 50),
   13.70 -        ('div[class*=sce]', 1),
   13.71 -        ('div[class|=dialog]', 50), # ? Seems right
   13.72 -        ('div[class!=madeup]', 243), # ? Seems right
   13.73 -        ('div[class~=dialog]', 51), # ? Seems right
   13.74 -        ]
   13.75 -
   13.76 -    def __init__(self, index):
   13.77 -        self.index = index
   13.78 -        super(HelperTestCase, self).__init__()
   13.79 -
   13.80 -    def all(cls):
   13.81 -        for i in range(len(cls.selectors)):
   13.82 -            yield cls(i)
   13.83 -    all = classmethod(all)
   13.84 -
   13.85 -    def runTest(self):
   13.86 -        f = open(doc_fn, 'rb')
   13.87 -        c = f.read()
   13.88 -        f.close()
   13.89 -        doc = html.document_fromstring(c)
   13.90 -        body = doc.xpath('//body')[0]
   13.91 -        bad = []
   13.92 -        selector, count = self.selectors[self.index]
   13.93 -        xpath = cssselect.css_to_xpath(cssselect.parse(selector))
   13.94 -        try:
   13.95 -            results = body.xpath(xpath)
   13.96 -        except Exception:
   13.97 -            e = sys.exc_info()[1]
   13.98 -            e.args = ("%s for xpath %r" % (e, xpath))
   13.99 -            raise
  13.100 -        found = {}
  13.101 -        for item in results:
  13.102 -            if item in found:
  13.103 -                assert 0, (
  13.104 -                    "Element shows up multiple times: %r" % item)
  13.105 -            found[item] = None
  13.106 -        if isinstance(results, basestring):
  13.107 -            assert 0, (
  13.108 -                "Got string result (%r), not element, for xpath %r"
  13.109 -                % (results[:20], str(xpath)))
  13.110 -        if len(results) != count:
  13.111 -            #if self.shortDescription() == 'div.character, div.dialog':
  13.112 -            #    import pdb; pdb.set_trace()
  13.113 -            assert 0, (
  13.114 -                "Did not get expected results (%s) instead %s for xpath %r"
  13.115 -                % (count, len(results), str(xpath)))
  13.116 -
  13.117 -    def shortDescription(self):
  13.118 -        return self.selectors[self.index][0]
  13.119 -
  13.120 -def unique(s):
  13.121 -    found = {}
  13.122 -    result = []
  13.123 -    for item in s:
  13.124 -        if item in found:
  13.125 -            continue
  13.126 -        found[item] = None
  13.127 -        result.append(s)
  13.128 -    return result
  13.129 -        
  13.130 -def test_suite():
  13.131 -    suite = unittest.TestSuite()
  13.132 -    if sys.version_info >= (2,4):
  13.133 -        suite.addTests([make_doctest('test_css_select.txt')])
  13.134 -    suite.addTests([make_doctest('test_css.txt')])
  13.135 -    suite.addTests(list(CSSTestCase.all()))
  13.136 -    return suite
    14.1 --- a/lxml/test_css.txt	Tue Dec 30 18:36:08 2008 +0100
    14.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.3 @@ -1,140 +0,0 @@
    14.4 -A quick test of tokenizing:
    14.5 -
    14.6 -    >>> from lxml.cssselect import tokenize, parse
    14.7 -    >>> def ptok(s):
    14.8 -    ...     for item in tokenize(s):
    14.9 -    ...         print(repr(item).replace("u'", "'"))
   14.10 -    >>> ptok('E > f[a~="y\\"x"]')
   14.11 -    Symbol('E', 0)
   14.12 -    Token('>', 2)
   14.13 -    Symbol('f', 4)
   14.14 -    Token('[', 5)
   14.15 -    Symbol('a', 6)
   14.16 -    Token('~=', 7)
   14.17 -    String('y"x', 9)
   14.18 -    Token(']', 15)
   14.19 -
   14.20 -Then of parsing:
   14.21 -
   14.22 -    >>> parse('td.foo, .bar')
   14.23 -    Or([Class[Element[td].foo], CombinedSelector[Element[*] <followed> Class[Element[*].bar]]])
   14.24 -    >>> parse('div, td.foo, div.bar span')
   14.25 -    Or([Element[div], Class[Element[td].foo], CombinedSelector[Class[Element[div].bar] <followed> Element[span]]])
   14.26 -    >>> parse('div > p')
   14.27 -    CombinedSelector[Element[div] > Element[p]]
   14.28 -    >>> parse('td:first')
   14.29 -    Pseudo[Element[td]:first]
   14.30 -    >>> parse('a[name]')
   14.31 -    Attrib[Element[a][name]]
   14.32 -    >>> repr(parse('a[rel="include"]')).replace("u'", "'")
   14.33 -    "Attrib[Element[a][rel = String('include', 6)]]"
   14.34 -    >>> repr(parse('a[hreflang |= \'en\']')).replace("u'", "'")
   14.35 -    "Attrib[Element[a][hreflang |= String('en', 14)]]"
   14.36 -    >>> parse('div:nth-child(10)')
   14.37 -    Function[Element[div]:nth-child(10)]
   14.38 -    >>> parse('div:nth-of-type(10)')
   14.39 -    Function[Element[div]:nth-of-type(10)]
   14.40 -    >>> parse('div div:nth-of-type(10) .aclass')
   14.41 -    CombinedSelector[CombinedSelector[Element[div] <followed> Function[Element[div]:nth-of-type(10)]] <followed> Class[Element[*].aclass]]
   14.42 -    >>> parse('label:only')
   14.43 -    Pseudo[Element[label]:only]
   14.44 -    >>> parse('a:lang(fr)')
   14.45 -    Function[Element[a]:lang(Element[fr])]
   14.46 -    >>> repr(parse('div:contains("foo")')).replace("u'", "'")
   14.47 -    "Function[Element[div]:contains(String('foo', 13))]"
   14.48 -    >>> parse('div#foobar')
   14.49 -    Hash[Element[div]#foobar]
   14.50 -    >>> parse('div:not(div.foo)')
   14.51 -    Function[Element[div]:not(Class[Element[div].foo])]
   14.52 -    >>> parse('td ~ th')
   14.53 -    CombinedSelector[Element[td] ~ Element[th]]
   14.54 -
   14.55 -Now of translation:
   14.56 -
   14.57 -    >>> def xpath(css):
   14.58 -    ...     print(parse(css).xpath())
   14.59 -    >>> xpath('*')
   14.60 -    *
   14.61 -    >>> xpath('E')
   14.62 -    e
   14.63 -    >>> xpath('E[foo]')
   14.64 -    e[@foo]
   14.65 -    >>> xpath('E[foo="bar"]')
   14.66 -    e[@foo = 'bar']
   14.67 -    >>> xpath('E[foo~="bar"]')
   14.68 -    e[contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]
   14.69 -    >>> xpath('E[foo^="bar"]')
   14.70 -    e[starts-with(@foo, 'bar')]
   14.71 -    >>> xpath('E[foo$="bar"]')
   14.72 -    e[substring(@foo, string-length(@foo)-2) = 'bar']
   14.73 -    >>> xpath('E[foo*="bar"]')
   14.74 -    e[contains(@foo, 'bar')]
   14.75 -    >>> xpath('E[hreflang|="en"]')
   14.76 -    e[@hreflang = 'en' or starts-with(@hreflang, 'en-')]
   14.77 -    >>> #xpath('E:root')
   14.78 -    >>> xpath('E:nth-child(1)')
   14.79 -    */*[name() = 'e' and (position() = 1)]
   14.80 -    >>> xpath('E:nth-last-child(1)')
   14.81 -    */*[name() = 'e' and (position() = last() - 1)]
   14.82 -    >>> xpath('E:nth-last-child(2n+2)')
   14.83 -    */*[name() = 'e' and ((position() +2) mod -2 = 0 and position() < (last() -2))]
   14.84 -    >>> xpath('E:nth-of-type(1)')
   14.85 -    */e[position() = 1]
   14.86 -    >>> xpath('E:nth-last-of-type(1)')
   14.87 -    */e[position() = last() - 1]
   14.88 -    >>> xpath('E:nth-last-of-type(1)')
   14.89 -    */e[position() = last() - 1]
   14.90 -    >>> xpath('div E:nth-last-of-type(1) .aclass')
   14.91 -    div/descendant::e[position() = last() - 1]/descendant::*[contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]
   14.92 -    >>> xpath('E:first-child')
   14.93 -    */*[name() = 'e' and (position() = 1)]
   14.94 -    >>> xpath('E:last-child')
   14.95 -    */*[name() = 'e' and (position() = last())]
   14.96 -    >>> xpath('E:first-of-type')
   14.97 -    */e[position() = 1]
   14.98 -    >>> xpath('E:last-of-type')
   14.99 -    */e[position() = last()]
  14.100 -    >>> xpath('E:only-child')
  14.101 -    */*[name() = 'e' and (last() = 1)]
  14.102 -    >>> xpath('E:only-of-type')
  14.103 -    e[last() = 1]
  14.104 -    >>> xpath('E:empty')
  14.105 -    e[not(*) and not(normalize-space())]
  14.106 -    >>> xpath('E:contains("foo")')
  14.107 -    e[contains(css:lower-case(string(.)), 'foo')]
  14.108 -    >>> xpath('E.warning')
  14.109 -    e[contains(concat(' ', normalize-space(@class), ' '), ' warning ')]
  14.110 -    >>> xpath('E#myid')
  14.111 -    e[@id = 'myid']
  14.112 -    >>> xpath('E:not(:contains("foo"))')
  14.113 -    e[not(contains(css:lower-case(string(.)), 'foo'))]
  14.114 -    >>> xpath('E F')
  14.115 -    e/descendant::f
  14.116 -    >>> xpath('E > F')
  14.117 -    e/f
  14.118 -    >>> xpath('E + F')
  14.119 -    e/following-sibling::*[name() = 'f' and (position() = 1)]
  14.120 -    >>> xpath('E ~ F')
  14.121 -    e/following-sibling::f
  14.122 -    >>> xpath('div#container p')
  14.123 -    div[@id = 'container']/descendant::p
  14.124 -    >>> xpath('p *:only-of-type')
  14.125 -    Traceback (most recent call last):
  14.126 -        ...
  14.127 -    NotImplementedError: *:only-of-type is not implemented
  14.128 -
  14.129 -Then of parse_series:
  14.130 -
  14.131 -    >>> from lxml.cssselect import parse_series
  14.132 -    >>> parse_series('1n+3')
  14.133 -    (1, 3)
  14.134 -    >>> parse_series('n-5')
  14.135 -    (1, -5)
  14.136 -    >>> parse_series('odd')
  14.137 -    (2, 1)
  14.138 -    >>> parse_series('3n')
  14.139 -    (3, 0)
  14.140 -    >>> parse_series('n')
  14.141 -    (1, 0)
  14.142 -    >>> parse_series('5')
  14.143 -    (0, 5)
    15.1 --- a/lxml/test_css_select.txt	Tue Dec 30 18:36:08 2008 +0100
    15.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.3 @@ -1,150 +0,0 @@
    15.4 -This is a test of CSS selectors.  We setup a document we'll use for
    15.5 -all our selections, and a function make querying simpler:
    15.6 -
    15.7 -    >>> from lxml.cssselect import CSSSelector
    15.8 -    >>> from lxml.etree import HTML
    15.9 -    >>> doc = HTML('''
   15.10 -    ... <html><head></head><body>
   15.11 -    ... <div id="outer-div">
   15.12 -    ...  <a id="name-anchor" name="foo"></a>
   15.13 -    ...  <a id="tag-anchor" rel="tag" href="http://localhost/foo">link</a>
   15.14 -    ...  <a id="nofollow-anchor" rel="nofollow" href="https://example.org">link</a>
   15.15 -    ...  <ol id="first-ol" class="a b c">
   15.16 -    ...    <li id="first-li">content</li>
   15.17 -    ...    <li id="second-li" lang="en-US">
   15.18 -    ...      <div id="li-div">
   15.19 -    ...      </div>
   15.20 -    ...    </li>
   15.21 -    ...    <li id="third-li" class="ab c"></li>
   15.22 -    ...    <li id="fourth-li" class="ab
   15.23 -    ... c"></li>
   15.24 -    ...    <li id="fifth-li"></li>
   15.25 -    ...    <li id="sixth-li"></li>
   15.26 -    ...    <li id="seventh-li">  </li>
   15.27 -    ...  </ol>
   15.28 -    ...  <p id="paragraph">
   15.29 -    ...    <b id="p-b">hi</b> <em id="p-em">there</em>
   15.30 -    ...    <b id="p-b2">guy</b></p>
   15.31 -    ...  <ol id="second-ol">
   15.32 -    ...  </ol>
   15.33 -    ... </div>
   15.34 -    ... <div id="foobar-div" foobar="ab bc
   15.35 -    ... cde"><span id="foobar-span"></span></div>
   15.36 -    ... </body></html>''')
   15.37 -    >>> order = {}
   15.38 -    >>> for count, el in enumerate(doc.getiterator()):
   15.39 -    ...     order[el] = count
   15.40 -    >>> def select_ids(selector):
   15.41 -    ...     items = CSSSelector(selector)(doc)
   15.42 -    ...     if not items:
   15.43 -    ...         return 'empty'
   15.44 -    ...     items = CSSSelector(selector)(doc)
   15.45 -    ...     items.sort(key=lambda el: order[el])
   15.46 -    ...     return ', '.join([el.get('id', 'nil') for el in items])
   15.47 -    >>> def pcss(main, *selectors):
   15.48 -    ...     result = select_ids(main)
   15.49 -    ...     for selector in selectors:
   15.50 -    ...         sel_result = select_ids(selector)
   15.51 -    ...         if sel_result != result:
   15.52 -    ...             print('Selector %r returns %s' % (selector, sel_result))
   15.53 -    ...     print(result)
   15.54 -
   15.55 -Now, the tests:
   15.56 -
   15.57 -    >>> pcss('*') # doctest: +ELLIPSIS
   15.58 -    nil, nil, nil, outer-div, ... foobar-span
   15.59 -    >>> pcss('div')
   15.60 -    outer-div, li-div, foobar-div
   15.61 -    >>> pcss('a[name]')
   15.62 -    name-anchor
   15.63 -    >>> pcss('a[rel]')
   15.64 -    tag-anchor, nofollow-anchor
   15.65 -    >>> pcss('a[rel="tag"]')
   15.66 -    tag-anchor
   15.67 -    >>> pcss('a[href*="localhost"]')
   15.68 -    tag-anchor
   15.69 -    >>> pcss('a[href^="http"]')
   15.70 -    tag-anchor, nofollow-anchor
   15.71 -    >>> pcss('a[href^="http:"]')
   15.72 -    tag-anchor
   15.73 -    >>> pcss('a[href$="org"]')
   15.74 -    nofollow-anchor
   15.75 -    >>> pcss('div[foobar~="bc"]', 'div[foobar~="cde"]')
   15.76 -    foobar-div
   15.77 -    >>> pcss('div[foobar~="cd"]')
   15.78 -    empty
   15.79 -    >>> pcss('*[lang|="en"]', '*[lang|="en-US"]')
   15.80 -    second-li
   15.81 -    >>> pcss('*[lang|="e"]')
   15.82 -    empty
   15.83 -    >>> pcss('li:nth-child(3)')
   15.84 -    third-li
   15.85 -    >>> pcss('li:nth-child(10)')
   15.86 -    empty
   15.87 -    >>> pcss('li:nth-child(2n)', 'li:nth-child(even)', 'li:nth-child(2n+0)')
   15.88 -    second-li, fourth-li, sixth-li
   15.89 -    >>> pcss('li:nth-child(+2n+1)', 'li:nth-child(odd)')
   15.90 -    first-li, third-li, fifth-li, seventh-li
   15.91 -    >>> pcss('li:nth-child(2n+4)')
   15.92 -    fourth-li, sixth-li
   15.93 -    >>> # FIXME: I'm not 100% sure this is right:
   15.94 -    >>> pcss('li:nth-child(3n+1)')
   15.95 -    first-li, fourth-li, seventh-li
   15.96 -    >>> # FIXME: I'm not sure if nth-last-child(1) or nth-last-child(1)
   15.97 -    >>> # should be equivalent to nth-last-child()
   15.98 -    >>> pcss('li:nth-last-child()', 'li:nth-last-child(0)')
   15.99 -    seventh-li
  15.100 -    >>> pcss('li:nth-last-child(2n)', 'li:nth-last-child(even)')
  15.101 -    second-li, fourth-li, sixth-li
  15.102 -    >>> pcss('li:nth-last-child(2n+2)')
  15.103 -    second-li, fourth-li
  15.104 -    >>> pcss('ol:first-of-type')
  15.105 -    first-ol
  15.106 -    >>> pcss('ol:nth-child(1)')
  15.107 -    empty
  15.108 -    >>> pcss('ol:nth-of-type(2)')
  15.109 -    second-ol
  15.110 -    >>> # FIXME: like above, (1) or (2)?
  15.111 -    >>> pcss('ol:nth-last-of-type(1)')
  15.112 -    first-ol
  15.113 -    >>> pcss('span:only-child')
  15.114 -    foobar-span
  15.115 -    >>> pcss('li div:only-child')
  15.116 -    li-div
  15.117 -    >>> pcss('div *:only-child')
  15.118 -    foobar-span
  15.119 -    >>> pcss('p *:only-of-type')
  15.120 -    Traceback (most recent call last):
  15.121 -        ...
  15.122 -    NotImplementedError: *:only-of-type is not implemented
  15.123 -    >>> pcss('p:only-of-type')
  15.124 -    paragraph
  15.125 -    >>> pcss('a:empty')
  15.126 -    name-anchor
  15.127 -    >>> pcss('li:empty')
  15.128 -    third-li, fourth-li, fifth-li, sixth-li, seventh-li
  15.129 -    >>> pcss('*:contains("link")')
  15.130 -    nil, nil, outer-div, tag-anchor, nofollow-anchor
  15.131 -    >>> pcss('*:contains("E")')
  15.132 -    nil, nil, outer-div, first-ol, first-li, paragraph, p-em
  15.133 -    >>> pcss('.a', '.b', '*.a', 'ol.a')
  15.134 -    first-ol
  15.135 -    >>> pcss('.c', '*.c')
  15.136 -    first-ol, third-li, fourth-li
  15.137 -    >>> pcss('ol *.c', 'ol li.c', 'li ~ li.c', 'ol > li.c')
  15.138 -    third-li, fourth-li
  15.139 -    >>> pcss('#first-li', 'li#first-li', '*#first-li')
  15.140 -    first-li
  15.141 -    >>> # Need some tests of :not()
  15.142 -    >>> pcss('li div', 'li > div', 'div div')
  15.143 -    li-div
  15.144 -    >>> pcss('div > div')
  15.145 -    empty
  15.146 -    >>> pcss('div + div')
  15.147 -    foobar-div
  15.148 -    >>> pcss('a ~ a')
  15.149 -    tag-anchor, nofollow-anchor
  15.150 -    >>> pcss('a[rel="tag"] ~ a')
  15.151 -    nofollow-anchor
  15.152 -    >>> pcss('ol#first-ol li:last-child', 'ol#first-ol *:last-child')
  15.153 -    seventh-li
    16.1 --- a/selectors.txt	Tue Dec 30 18:36:08 2008 +0100
    16.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.3 @@ -1,22 +0,0 @@
    16.4 -*,                  //*
    16.5 -E,                  //E
    16.6 -E F,                //E//F
    16.7 -E > F,              //E/F
    16.8 -E:first-child,      //*[1]/self::E
    16.9 -E:nth-child(3),     //E[count(preceding-sibling::*)=3-1]
   16.10 -E:lang(c),          //E[@xml:lang='c' or starts-with(@xml:lang,'c-')]
   16.11 -E + F,              //E/following-sibling::*[1]/self::F
   16.12 -E[foo],             //E[@foo]
   16.13 -E[foo="warning"],   //E[@foo='warning']
   16.14 -E[foo~="warning"],  //E[contains(concat(' ',@foo,' '),' warning ')]
   16.15 -E[lang|="en"],      //E[@lang='en' or starts-with(@lang,'en-')]
   16.16 -E:not([foo="warning"]),  //E[not(./self::*[@foo='warning'])]
   16.17 -E:not(.warning),         //E[not(./self::*[contains(concat(' ',@class,' '),' warning ')])]
   16.18 -DIV.warning,        //DIV[contains(concat(' ',@class,' '),' warning ')]
   16.19 -E#myid,             //E[@id='myid']
   16.20 -p > *,              //p/*
   16.21 -#foo,               //*[@id='foo']
   16.22 -.foo,               //*[contains(concat(' ',@class,' '),' foo ')]
   16.23 -*[title],           //*[@title]
   16.24 -p > *:first-child,  //p/*[1]/self::*
   16.25 -p[bar] + .content-top[foo],              //p[@bar]/following-sibling::*[1]/self::*[contains(concat(' ',@class,' '),' content-top ')][@foo]
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/src/AssemblyInfo.cs.in	Tue Dec 30 22:26:25 2008 +0100
    17.3 @@ -0,0 +1,15 @@
    17.4 +//
    17.5 +// AssemblyInfo.cs.in for project 'css3-selectors'
    17.6 +//
    17.7 +// Authors:
    17.8 +//	Emanuele Aina <em@nerd.ocracy.org>
    17.9 +//
   17.10 +
   17.11 +using System.Reflection;
   17.12 +using System.Runtime.CompilerServices;
   17.13 +
   17.14 +[assembly: AssemblyVersion("@VERSION@")]
   17.15 +[assembly: AssemblyTitle ("css3-selectors")]
   17.16 +[assembly: AssemblyDescription ("css3-selectors - CSS Level 3 Selectors to XPath converter")]
   17.17 +[assembly: AssemblyCopyright ("Copyright (c) 2008-2009 Emanuele Aina <em@nerd.ocracy.org>")]
   17.18 +[assembly: AssemblyCompany ("Studio Associato Di Nunzio e Di Gregorio")]
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/src/Css3Selectors.cs	Tue Dec 30 22:26:25 2008 +0100
    18.3 @@ -0,0 +1,340 @@
    18.4 +/* CSS3 Selectors to XPath converter
    18.5 + *
    18.6 + * Emanuele Aina <em@nerd.ocracy.org>
    18.7 + * 
    18.8 + * Inspired by HTML::Selector::XPath from Tatsuhiko Miyagawa
    18.9 + * http://search.cpan.org/~miyagawa/HTML-Selector-XPath/lib/HTML/Selector/XPath.pm
   18.10 + */
   18.11 +using System;
   18.12 +using System.Collections.Generic;
   18.13 +using System.Linq;
   18.14 +using System.Text.RegularExpressions;
   18.15 +
   18.16 +namespace Css3Selectors {
   18.17 +
   18.18 +public static class Css {
   18.19 +    public class Element {
   18.20 +        public virtual string ToCss() {
   18.21 +            return "";
   18.22 +        }
   18.23 +
   18.24 +        public virtual string ToXPath() {
   18.25 +            return "";
   18.26 +        }
   18.27 +        public override string ToString() {
   18.28 +            return base.ToString() + "(" + ToCss() + ")";
   18.29 +        }
   18.30 +    }
   18.31 +
   18.32 +    public class Combinator : Element {
   18.33 +        public string Mode { get; set; }
   18.34 +        public override string ToCss() {
   18.35 +            return Mode;
   18.36 +        }
   18.37 +
   18.38 +        public override string ToXPath() {
   18.39 +            switch(Mode) {
   18.40 +                case " ": return "//";
   18.41 +                case ">": return "/";
   18.42 +                case "+": return "/following-sibling::*[1]/self::";
   18.43 +                case "~": return "/following-sibling::";
   18.44 +            }
   18.45 +            throw new Exception();
   18.46 +        }
   18.47 +    }
   18.48 +
   18.49 +    public class Comma : Element {
   18.50 +    }
   18.51 +
   18.52 +    public class SimpleSelector : Element {
   18.53 +    }
   18.54 +
   18.55 +    public class TypeSelector : SimpleSelector {
   18.56 +        public string Namespace { get; set; }
   18.57 +        public string Type { get; set; }
   18.58 +        public override string ToCss() {
   18.59 +            if(Namespace == null)
   18.60 +                return Type;
   18.61 +            return Namespace + "|" + Type;
   18.62 +        }
   18.63 +
   18.64 +        public override string ToXPath() {
   18.65 +            if(Namespace == null)
   18.66 +                return Type;
   18.67 +            return Namespace + ":" + Type;
   18.68 +        }
   18.69 +    }
   18.70 +
   18.71 +    public class UniversalSelector : SimpleSelector {
   18.72 +        public override string ToCss() {
   18.73 +            return "*";
   18.74 +        }
   18.75 +
   18.76 +        public override string ToXPath() {
   18.77 +            return "*";
   18.78 +        }
   18.79 +    }
   18.80 +
   18.81 +    public class AttributeSelector : SimpleSelector {
   18.82 +        public string Attribute { get; set; }
   18.83 +        public string Comparison { get; set; }
   18.84 +        public string Value { get; set; }
   18.85 +        public override string ToCss() {
   18.86 +            if(Comparison == null)
   18.87 +                return "[" + Attribute + "]";
   18.88 +            return "[" + Attribute + Comparison + "'" + Value + "']";
   18.89 +        }
   18.90 +
   18.91 +        public override string ToXPath() {
   18.92 +            switch(Comparison) {
   18.93 +                case null: return "[@" + Attribute + "]";
   18.94 +                case "=" : return "[@" + Attribute + "='" + Value + "']";
   18.95 +                case "|=": return String.Format("[@{0}='{1}' or starts-with(@{0},'{1}-')]", Attribute, Value);
   18.96 +                case "~=": return String.Format("[contains(concat(' ',@{0},' '),' {1} ')]", Attribute, Value);
   18.97 +                case "^=": return String.Format("[starts-with(@{0},'{1}')]", Attribute, Value);
   18.98 +                // TODO: add the ends-with() XPath function
   18.99 +                case "$=": return String.Format("[ends-with(@{0},'{1}')]", Attribute, Value);
  18.100 +                case "*=": return String.Format("[contains(@{0},'{1}')]", Attribute, Value);
  18.101 +                // NOTE: non-standard!
  18.102 +                case "!=": return String.Format("[not(@{0}='{1}')]", Attribute, Value);
  18.103 +            }
  18.104 +            throw new Exception();
  18.105 +        }
  18.106 +    }
  18.107 +
  18.108 +    public class ClassSelector : SimpleSelector {
  18.109 +        public string Class { get; set; }
  18.110 +        public override string ToCss() {
  18.111 +            return "." + Class;
  18.112 +        }
  18.113 +
  18.114 +        public override string ToXPath() {
  18.115 +            return String.Format("[contains(concat(' ',@class,' '),' {0} ')]", Class);
  18.116 +        }
  18.117 +    }
  18.118 +
  18.119 +    public class IdSelector : SimpleSelector {
  18.120 +        public string Id { get; set; }
  18.121 +        public override string ToCss() {
  18.122 +            return "#" + Id;
  18.123 +        }
  18.124 +
  18.125 +        public override string ToXPath() {
  18.126 +            return String.Format("[@id='{0}']", Id);
  18.127 +        }
  18.128 +    }
  18.129 +
  18.130 +    public class ContentSelector : SimpleSelector {
  18.131 +        // This public class intentionally left blank
  18.132 +        // See http://www.w3.org/TR/css3-selectors/#content-selectors
  18.133 +    }
  18.134 +
  18.135 +    public class PseudoClassSelector : SimpleSelector {
  18.136 +        public string PseudoClass { get; set; }
  18.137 +
  18.138 +        public override string ToCss() {
  18.139 +            return ":"+PseudoClass;
  18.140 +        }
  18.141 +
  18.142 +        public override string ToXPath() {
  18.143 +            switch(PseudoClass) {
  18.144 +                case "only-child": return "[count(../*)=1]";
  18.145 +                case "first-child": return "*[1]/self::";
  18.146 +                case "last-child": return "*[last()]/self::";
  18.147 +            }
  18.148 +            throw new Exception();
  18.149 +        }
  18.150 +    }
  18.151 +
  18.152 +    public class PseudoClassSelector<T> : PseudoClassSelector {
  18.153 +        public T Argument { get; set; }
  18.154 +
  18.155 +        private string ToCssString(object obj) {
  18.156 +            SimpleSelector selector = obj as SimpleSelector;
  18.157 +
  18.158 +            if(selector != null) return selector.ToCss();
  18.159 +            IEnumerable<Element> tokens = obj as IEnumerable<Css.Element>;
  18.160 +
  18.161 +            if(tokens != null) return tokens.Select(t => t.ToCss()).Aggregate((a,b) => a+b);
  18.162 +            return obj.ToString();
  18.163 +        }
  18.164 +
  18.165 +        private string ToXPathString(object obj) {
  18.166 +            SimpleSelector selector = obj as SimpleSelector;
  18.167 +            if(selector != null) return selector.ToXPath();
  18.168 +
  18.169 +            IEnumerable<Element> tokens = obj as IEnumerable<Css.Element>;
  18.170 +            if(tokens != null) return Regex.Replace(tokens.ToXPath(), @"^//", "./self::");
  18.171 +
  18.172 +            return obj.ToString();
  18.173 +        }
  18.174 +
  18.175 +        public override string ToCss() {
  18.176 +            return ":"+PseudoClass + "(" + ToCssString(Argument) + ")";
  18.177 +        }
  18.178 +
  18.179 +        public override string ToXPath() {
  18.180 +            switch(PseudoClass) {
  18.181 +                case "not": return String.Format("[not({0})]", ToXPathString(Argument));
  18.182 +                case "nth-child": return String.Format("[count(preceding-sibling::*)={0}-1]", Argument);
  18.183 +                case "lang": return String.Format("[@xml:lang='{0}' or starts-with(@xml:lang,'{0}-')]", Argument);
  18.184 +                // TODO: removed from the CSS3 draft, underspecified, check its behaviour against other implementations
  18.185 +                case "contains": return String.Format("[contains(text(),'{0}')]", Argument);
  18.186 +            }
  18.187 +            throw new Exception();
  18.188 +        }
  18.189 +    }
  18.190 +
  18.191 +    public class PseudoElement {
  18.192 +    }
  18.193 +
  18.194 +    private static string Unquote(string s) {
  18.195 +        Regex singleQuotes = new Regex("'(.*)'");
  18.196 +        Regex doubleQuotes = new Regex("\"(.*)\"");
  18.197 +
  18.198 +        if(singleQuotes.Match(s).Success)
  18.199 +            return singleQuotes.Replace(s, "$1");
  18.200 +        if(doubleQuotes.Match(s).Success)
  18.201 +            return doubleQuotes.Replace(s, "$1");
  18.202 +        return s;
  18.203 +    }
  18.204 +
  18.205 +    public static IEnumerable<Element> Parse(string selector)
  18.206 +    {
  18.207 +        string tmp = selector;
  18.208 +
  18.209 +        var parseTable = new[] {
  18.210 +            // tag name/id/class
  18.211 +            new[]{@"^([#.]?)([a-z0-9\\*_-]+)((\|)([a-z0-9\\*_-]*))?", "Element"},
  18.212 +            // pseudo classes
  18.213 +            new[]{@"^:([a-z0-9_-]+)(\((.*)\))?", "Pseudo" },
  18.214 +            // attribute value match
  18.215 +            new[]{@"^\[\s*([^\~\|^$*!=\s]+)\s*(([~\|^$*!]?=)\s*(.+))?\]", "Attr"},
  18.216 +            // adjacency/direct descendance
  18.217 +            new[]{@"^\s*([>+\~\s])\s*", "Combinator"},
  18.218 +            // rules union 
  18.219 +            new[]{@"^\s*,", "Comma"}
  18.220 +        };
  18.221 +
  18.222 +        while(!String.IsNullOrEmpty(tmp)) {
  18.223 +            Match match = null;
  18.224 +            string type = null;
  18.225 +
  18.226 +            Console.WriteLine("  rest -> '{0}'", tmp);
  18.227 +
  18.228 +            foreach(var item in parseTable) {
  18.229 +                Regex r = new Regex(item[0], RegexOptions.Compiled | RegexOptions.IgnoreCase);
  18.230 +                match = r.Match(tmp);
  18.231 +                if(match.Success) {
  18.232 +                    type = item[1];
  18.233 +                    tmp = r.Replace(tmp, "");
  18.234 +                    break;
  18.235 +                }
  18.236 +            }
  18.237 +
  18.238 +            Console.WriteLine("  {0} -> '{1}'", type, match.Value);
  18.239 +            switch(type) {
  18.240 +                case "Element":
  18.241 +                    switch(match.Groups[1].Value) {
  18.242 +                        case "":
  18.243 +                            if(match.Groups[5].Success) {
  18.244 +                                yield return new TypeSelector{Namespace = match.Groups[2].Value, Type = match.Groups[5].Value};
  18.245 +                            }
  18.246 +                            else {
  18.247 +                                yield return new TypeSelector{Type = match.Groups[2].Value};
  18.248 +                            }
  18.249 +                            break;
  18.250 +                        case "#":
  18.251 +                            yield return new IdSelector{Id = match.Groups[2].Value};
  18.252 +                            break;
  18.253 +                        case ".":
  18.254 +                            yield return new ClassSelector{Class = match.Groups[2].Value};
  18.255 +                            break;
  18.256 +                    }
  18.257 +                    break;
  18.258 +                case "Attr":
  18.259 +                    if(match.Groups[3].Success) {
  18.260 +                        yield return new AttributeSelector{
  18.261 +                            Attribute = Unquote(match.Groups[1].Value.Trim()),
  18.262 +                            Comparison = match.Groups[3].Value,
  18.263 +                            Value = Unquote(match.Groups[4].Value)};
  18.264 +                    }
  18.265 +                    else {
  18.266 +                        yield return new AttributeSelector{Attribute = match.Groups[1].Value};
  18.267 +                    }
  18.268 +                    break;
  18.269 +                case "Pseudo":
  18.270 +                    if(match.Groups[3].Success)
  18.271 +                        switch(match.Groups[1].Value) {
  18.272 +                            case "not":
  18.273 +                                IEnumerable<Element> negated = Parse(match.Groups[3].Value);
  18.274 +                                yield return new PseudoClassSelector<IEnumerable<Element>>
  18.275 +                                    {PseudoClass = match.Groups[1].Value, Argument = negated};
  18.276 +                                break;
  18.277 +                            default:
  18.278 +                                yield return new PseudoClassSelector<string>{PseudoClass = match.Groups[1].Value, Argument = match.Groups[3].Value};
  18.279 +                                break;
  18.280 +                        }
  18.281 +                    else
  18.282 +                        yield return new PseudoClassSelector{PseudoClass = match.Groups[1].Value };
  18.283 +                    break;
  18.284 +                case "Combinator":
  18.285 +                    yield return new Combinator{Mode = match.Groups[1].Value};
  18.286 +                    break;
  18.287 +                case "Comma":
  18.288 +                    yield return new Comma();
  18.289 +                    break;
  18.290 +                default:
  18.291 +                    throw new ArgumentException(String.Format("invalid selector: {0}", selector));
  18.292 +            }
  18.293 +        }
  18.294 +    }
  18.295 +
  18.296 +    public static string ToXPath(this IEnumerable<Element> tokens) {
  18.297 +        var parts = new [] {"//", "*"}.ToList();
  18.298 +        var index = 1;
  18.299 +
  18.300 +        foreach(Element token in tokens) {
  18.301 +            if (token is SimpleSelector) {
  18.302 +                SimpleSelector selector = (SimpleSelector)token;
  18.303 +
  18.304 +                if (selector is TypeSelector) {
  18.305 +                    parts[index] = selector.ToXPath();
  18.306 +                }
  18.307 +                else if (selector is PseudoClassSelector) {
  18.308 +                    PseudoClassSelector pseudo = (PseudoClassSelector)selector;
  18.309 +                    switch(pseudo.PseudoClass) {
  18.310 +                        case "first-child":
  18.311 +                        case "last-child":
  18.312 +                            int last = parts.Count - 1;
  18.313 +                            parts[last] = pseudo.ToXPath() + parts[last];
  18.314 +                            break;
  18.315 +                        default:
  18.316 +                            parts.Add(pseudo.ToXPath());
  18.317 +                            break;
  18.318 +                    }
  18.319 +                }
  18.320 +                else {
  18.321 +                    parts.Add(selector.ToXPath());
  18.322 +                }
  18.323 +            }
  18.324 +            else if (token is Combinator) {
  18.325 +                Combinator combinator = (Combinator)token;
  18.326 +                parts.Add(combinator.ToXPath());
  18.327 +
  18.328 +                index = parts.Count;
  18.329 +                parts.Add("*");
  18.330 +            }
  18.331 +            else if (token is Comma) {
  18.332 +                parts.Add(" | ");
  18.333 +            }
  18.334 +            else {
  18.335 +                throw new Exception();
  18.336 +            }
  18.337 +        }
  18.338 +
  18.339 +        return String.Join("", parts.ToArray());
  18.340 +    }
  18.341 +}
  18.342 +
  18.343 +}
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/src/Makefile.am	Tue Dec 30 22:26:25 2008 +0100
    19.3 @@ -0,0 +1,15 @@
    19.4 +css3selectorsdir=$(pkglibdir)
    19.5 +css3selectors_SCRIPTS = css3-selectors.dll
    19.6 +EXTRA_DIST = $(css3selectors_sources) $(css3selectors_sources_in)
    19.7 +CLEANFILES = css3-selectors.dll
    19.8 +DISTCLEANFILES = AssemblyInfo.cs Makefile.in
    19.9 +
   19.10 +css3selectors_sources_in = AssemblyInfo.cs.in
   19.11 +css3selectors_generated_sources = $(css3selectors_sources_in:.in=)
   19.12 +css3selectors_sources = Css3Selectors.cs
   19.13 +
   19.14 +css3selectors_build_sources = $(addprefix $(srcdir)/, $(css3selectors_sources))
   19.15 +css3selectors_build_sources += $(css3selectors_generated_sources)
   19.16 +
   19.17 +css3-selectors.dll: $(css3selectors_build_sources)
   19.18 +	$(MCS) -target:library -out:$@ $(css3selectors_build_sources)
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/tests/LxmlTestCssPy.cs	Tue Dec 30 22:26:25 2008 +0100
    20.3 @@ -0,0 +1,77 @@
    20.4 +/* Tests for the CSS3 Selectors to XPath converter
    20.5 + *
    20.6 + * Parse the tests in the test_css.py python file from lxml and run them
    20.7 + *
    20.8 + * Emanuele Aina <em@nerd.ocracy.org>
    20.9 + */
   20.10 +using System;
   20.11 +using System.Xml;
   20.12 +using System.Collections.Generic;
   20.13 +using System.Linq;
   20.14 +using System.IO;
   20.15 +using System.Text.RegularExpressions;
   20.16 +
   20.17 +using Css3Selectors;
   20.18 +
   20.19 +public static class LxmlTestCssPy {
   20.20 +    public static void Run()
   20.21 +    {
   20.22 +        XmlDocument doc = new XmlDocument();
   20.23 +        doc.Load("tests/lxml/css_shakespear.html");
   20.24 +
   20.25 +        XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
   20.26 +        ns.AddNamespace("_", "http://www.w3.org/1999/xhtml");
   20.27 +
   20.28 +        // Remove the <head> tag, as it is not counted when matching in html
   20.29 +        XmlNode head = doc.SelectSingleNode("//_:head", ns);
   20.30 +        Console.WriteLine("{0} {1} {2}", head, ns.DefaultNamespace, doc.NameTable.Get(String.Empty));
   20.31 +        head.ParentNode.RemoveChild(head);
   20.32 +
   20.33 +        // Then match selectors starting from the <html> tag
   20.34 +        XmlNode html = doc.SelectSingleNode("//_:html", ns);
   20.35 +
   20.36 +        using(TextReader py = new StreamReader("tests/lxml/test_css.py")) {
   20.37 +            string line;
   20.38 +            while ((line = py.ReadLine()) != null) {
   20.39 +                if(line.Trim().StartsWith("selectors"))
   20.40 +                    break;
   20.41 +            }
   20.42 +            while ((line = py.ReadLine()) != null) {
   20.43 +                if(line.Trim().StartsWith("#"))
   20.44 +                    continue;
   20.45 +                if(line.Trim().StartsWith("]"))
   20.46 +                    break;
   20.47 +                Match m = Regex.Match(line, @"\('(.*)', (\d+)\)");
   20.48 +                if(!m.Success)
   20.49 +                    throw new Exception();
   20.50 +                string selector = m.Groups[1].Value;
   20.51 +                int reference = Int32.Parse(m.Groups[2].Value);
   20.52 +
   20.53 +                Console.WriteLine("selector, reference: {0}, {1}", selector, reference);
   20.54 +
   20.55 +                int count = -1;
   20.56 +                string xpath = null;
   20.57 +                Exception exception = null;
   20.58 +                try {
   20.59 +                    xpath = Css.Parse(selector).Select(s => {
   20.60 +                        if (s is Css.TypeSelector)
   20.61 +                            ((Css.TypeSelector)s).Namespace = "_";
   20.62 +                        return s;
   20.63 +                    }).ToXPath();
   20.64 +                    // Prefix the xpath expression with the current node, otherwise the <html> tag 
   20.65 +                    // will be matched too
   20.66 +                    count = html.SelectNodes("."+xpath, ns).Count;
   20.67 +                } catch (Exception e){
   20.68 +                    exception = e;
   20.69 +                }
   20.70 +
   20.71 +                if(count == reference)
   20.72 +                    Console.WriteLine("OK '{0}' -> '{1}' ({2})", selector, xpath, count);
   20.73 +                else if(exception != null)
   20.74 +                    Console.WriteLine("ERROR on '{0}' -> '{1}' (got {2}, expected '{3}', {4})", selector, xpath, count, reference, exception);
   20.75 +                else
   20.76 +                    Console.WriteLine("ERROR on '{0}' -> '{1}' (got {2}, expected '{3}')", selector, xpath, count, reference);
   20.77 +            }
   20.78 +        }
   20.79 +    }
   20.80 +}
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/tests/Makefile.am	Tue Dec 30 22:26:25 2008 +0100
    21.3 @@ -0,0 +1,21 @@
    21.4 +testsrunnerdir=$(pkglibdir)
    21.5 +noinst_SCRIPTS = testsrunner.exe
    21.6 +EXTRA_DIST = $(testsrunner_sources) $(testsrunner_sources_in)
    21.7 +CLEANFILES = testsrunner.exe
    21.8 +DISTCLEANFILES = AssemblyInfo.cs Makefile.in
    21.9 +testsrunner_references=System.Core $(srcdir)/../src/css3-selectors.dll
   21.10 +
   21.11 +#testsrunner_sources_in = AssemblyInfo.cs.in
   21.12 +#testsrunner_generated_sources = $(testsrunner_sources_in:.in=)
   21.13 +testsrunner_sources = TestsRunner.cs LxmlTestCssPy.cs
   21.14 +
   21.15 +testsrunner_build_references = $(addprefix -r:, $(testsrunner_references))
   21.16 +
   21.17 +testsrunner_build_sources = $(addprefix $(srcdir)/, $(testsrunner_sources))
   21.18 +testsrunner_build_sources += $(testsrunner_generated_sources)
   21.19 +#resources = $(srcdir)/../resources/dummy.txt
   21.20 +
   21.21 +embedded= $(foreach res,$(resources), $(addprefix -resource:,$(res)),$(notdir $(res)))
   21.22 +
   21.23 +testsrunner.exe: $(testsrunner_build_sources) $(resources)
   21.24 +	$(MCS) $(testsrunner_build_references) $(CSS3SELECTORS_LIBS) $(embedded) -out:$@ $(testsrunner_build_sources)
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/tests/TestsRunner.cs	Tue Dec 30 22:26:25 2008 +0100
    22.3 @@ -0,0 +1,31 @@
    22.4 +using System;
    22.5 +using System.Collections.Generic;
    22.6 +using System.Linq;
    22.7 +using System.IO;
    22.8 +using Css3Selectors;
    22.9 +
   22.10 +class TestRunner {
   22.11 +    public static void Main (string[] args)
   22.12 +    {
   22.13 +        using(TextReader selectors = new StreamReader("tests/selectors.txt")) {
   22.14 +            string line;
   22.15 +            while ((line = selectors.ReadLine()) != null) {
   22.16 +                string selector, reference, xpath;
   22.17 +                var split = line.Split(new[]{','}, 2);
   22.18 +                selector = split[0];
   22.19 +                reference = split[1].Trim();
   22.20 +
   22.21 +                Console.WriteLine("CSS: {0}", selector);
   22.22 +                var tokens = Css.Parse(selector).ToList();
   22.23 +                Console.WriteLine("  tokens -> {0}", tokens.Select(t => t.ToString()).Aggregate((a,b) => a+" "+b));
   22.24 +                xpath = tokens.ToXPath();
   22.25 +                if(xpath == reference)
   22.26 +                    Console.WriteLine("OK '{0}' -> '{1}'", selector, reference);
   22.27 +                else
   22.28 +                    Console.WriteLine("ERROR on '{0}' -> '{1}' (expected '{2}')", selector, xpath, reference);
   22.29 +            }
   22.30 +        }
   22.31 +
   22.32 +        LxmlTestCssPy.Run();
   22.33 +    }
   22.34 +}
    23.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.2 +++ b/tests/lxml/css_shakespear.html	Tue Dec 30 22:26:25 2008 +0100
    23.3 @@ -0,0 +1,526 @@
    23.4 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    23.5 +	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    23.6 +
    23.7 +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" debug="true">
    23.8 +<head>
    23.9 +	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
   23.10 +</head>
   23.11 +
   23.12 +<body>
   23.13 +	
   23.14 +	<div id="test">
   23.15 +	<div class="dialog">
   23.16 +	<h2>As You Like It</h2>
   23.17 +	<div id="playwright">
   23.18 +
   23.19 +	  by William Shakespeare
   23.20 +
   23.21 +
   23.22 +	</div>
   23.23 +	<div class="dialog scene thirdClass" id="scene1">
   23.24 +
   23.25 +	  <h3>ACT I, SCENE III. A room in the palace.</h3>
   23.26 +
   23.27 +	  <div class="dialog">
   23.28 +	  <div class="direction">Enter CELIA and ROSALIND</div>
   23.29 +
   23.30 +	  </div>
   23.31 +
   23.32 +	  <div id="speech1" class="character">CELIA</div>
   23.33 +
   23.34 +	  <div class="dialog">
   23.35 +	  <div id="scene1.3.1">Why, cousin! why, Rosalind! Cupid have mercy! not a word?</div>
   23.36 +
   23.37 +	  </div>
   23.38 +
   23.39 +	  <div id="speech2" class="character">ROSALIND</div>
   23.40 +
   23.41 +	  <div class="dialog">
   23.42 +	  <div id="scene1.3.2">Not one to throw at a dog.</div>
   23.43 +
   23.44 +	  </div>
   23.45 +
   23.46 +	  <div id="speech3" class="character">CELIA</div>
   23.47 +
   23.48 +	  <div class="dialog">
   23.49 +	  <div id="scene1.3.3">No, thy words are too precious to be cast away upon</div>
   23.50 +
   23.51 +	  <div id="scene1.3.4">curs; throw some of them at me; come, lame me with reasons.</div>
   23.52 +
   23.53 +	  </div>
   23.54 +
   23.55 +	  <div id="speech4" class="character">ROSALIND</div>
   23.56 +
   23.57 +	  <div id="speech5" class="character">CELIA</div>
   23.58 +
   23.59 +	  <div class="dialog">
   23.60 +	  <div id="scene1.3.8">But is all this for your father?</div>
   23.61 +
   23.62 +	  </div>
   23.63 +
   23.64 +	  <div class="dialog">
   23.65 +	  <div id="scene1.3.5">Then there were two cousins laid up; when the one</div>
   23.66 +	  <div id="scene1.3.6">should be lamed with reasons and the other mad</div>
   23.67 +
   23.68 +	  <div id="scene1.3.7">without any.</div>
   23.69 +	  </div>
   23.70 +
   23.71 +	  <div id="speech6" class="character">ROSALIND</div>
   23.72 +
   23.73 +	  <div class="dialog">
   23.74 +	  <div id="scene1.3.9">No, some of it is for my child's father. O, how</div>
   23.75 +
   23.76 +	  <div id="scene1.3.10">full of briers is this working-day world!</div>
   23.77 +
   23.78 +	  </div>
   23.79 +
   23.80 +	  <div id="speech7" class="character">CELIA</div>
   23.81 +
   23.82 +	  <div class="dialog">
   23.83 +
   23.84 +	  <div id="scene1.3.11">They are but burs, cousin, thrown upon thee in</div>
   23.85 +	  <div id="scene1.3.12">holiday foolery: if we walk not in the trodden</div>
   23.86 +
   23.87 +	  <div id="scene1.3.13">paths our very petticoats will catch them.</div>
   23.88 +
   23.89 +	  </div>
   23.90 +
   23.91 +	  <div id="speech8" class="character">ROSALIND</div>
   23.92 +
   23.93 +	  <div class="dialog">
   23.94 +	  <div id="scene1.3.14">I could shake them off my coat: these burs are in my heart.</div>
   23.95 +	  </div>
   23.96 +
   23.97 +	  <div id="speech9" class="character">CELIA</div>
   23.98 +
   23.99 +	  <div class="dialog">
  23.100 +	  <div id="scene1.3.15">Hem them away.</div>
  23.101 +
  23.102 +	  </div>
  23.103 +
  23.104 +	  <div id="speech10" class="character">ROSALIND</div>
  23.105 +
  23.106 +	  <div class="dialog">
  23.107 +
  23.108 +	  <div id="scene1.3.16">I would try, if I could cry 'hem' and have him.</div>
  23.109 +	  </div>
  23.110 +
  23.111 +	  <div id="speech11" class="character">CELIA</div>
  23.112 +
  23.113 +	  <div class="dialog">
  23.114 +	  <div id="scene1.3.17">Come, come, wrestle with thy affections.</div>
  23.115 +
  23.116 +	  </div>
  23.117 +
  23.118 +	  <div id="speech12" class="character">ROSALIND</div>
  23.119 +	  <div class="dialog">
  23.120 +	  <div id="scene1.3.18">O, they take the part of a better wrestler than myself!</div>
  23.121 +
  23.122 +	  </div>
  23.123 +
  23.124 +	  <div id="speech13" class="character">CELIA</div>
  23.125 +
  23.126 +	  <div class="dialog">
  23.127 +
  23.128 +	  <div id="scene1.3.19">O, a good wish upon you! you will try in time, in</div>
  23.129 +	  <div id="scene1.3.20">despite of a fall. But, turning these jests out of</div>
  23.130 +	  <div id="scene1.3.21">service, let us talk in good earnest: is it</div>
  23.131 +
  23.132 +	  <div id="scene1.3.22">possible, on such a sudden, you should fall into so</div>
  23.133 +
  23.134 +	  <div id="scene1.3.23">strong a liking with old Sir Rowland's youngest son?</div>
  23.135 +
  23.136 +	  </div>
  23.137 +
  23.138 +	  <div id="speech14" class="character">ROSALIND</div>
  23.139 +	  <div class="dialog">
  23.140 +	  <div id="scene1.3.24">The duke my father loved his father dearly.</div>
  23.141 +
  23.142 +	  </div>
  23.143 +
  23.144 +	  <div id="speech15" class="character">CELIA</div>
  23.145 +
  23.146 +	  <div class="dialog">
  23.147 +
  23.148 +	  <div id="scene1.3.25">Doth it therefore ensue that you should love his son</div>
  23.149 +
  23.150 +	  <div id="scene1.3.26">dearly? By this kind of chase, I should hate him,</div>
  23.151 +
  23.152 +	  <div id="scene1.3.27">for my father hated his father dearly; yet I hate</div>
  23.153 +
  23.154 +	  <div id="scene1.3.28">not Orlando.</div>
  23.155 +
  23.156 +	  </div>
  23.157 +
  23.158 +	  <div id="speech16" class="character">ROSALIND</div>
  23.159 +
  23.160 +	  <div title="wtf" class="dialog">
  23.161 +
  23.162 +	  <div id="scene1.3.29">No, faith, hate him not, for my sake.</div>
  23.163 +
  23.164 +	  </div>
  23.165 +
  23.166 +	  <div id="speech17" class="character">CELIA</div>
  23.167 +
  23.168 +	  <div class="dialog">
  23.169 +	  <div id="scene1.3.30">Why should I not? doth he not deserve well?</div>
  23.170 +
  23.171 +	  </div>
  23.172 +
  23.173 +	  <div id="speech18" class="character">ROSALIND</div>
  23.174 +
  23.175 +	  <div class="dialog">
  23.176 +	  <div id="scene1.3.31">Let me love him for that, and do you love him</div>
  23.177 +	  <div id="scene1.3.32">because I do. Look, here comes the duke.</div>
  23.178 +	  </div>
  23.179 +
  23.180 +	  <div id="speech19" class="character">CELIA</div>
  23.181 +
  23.182 +	  <div class="dialog">
  23.183 +
  23.184 +	  <div id="scene1.3.33">With his eyes full of anger.</div>
  23.185 +	  <div class="direction">Enter DUKE FREDERICK, with Lords</div>
  23.186 +	  </div>
  23.187 +
  23.188 +	  <div id="speech20" class="character">DUKE FREDERICK</div>
  23.189 +
  23.190 +	  <div class="dialog">
  23.191 +
  23.192 +	  <div id="scene1.3.34">Mistress, dispatch you with your safest haste</div>
  23.193 +
  23.194 +	  <div id="scene1.3.35">And get you from our court.</div>
  23.195 +	  </div>
  23.196 +
  23.197 +	  <div id="speech21" class="character">ROSALIND</div>
  23.198 +
  23.199 +	  <div class="dialog">
  23.200 +
  23.201 +	  <div id="scene1.3.36">Me, uncle?</div>
  23.202 +
  23.203 +	  </div>
  23.204 +
  23.205 +	  <div id="speech22" class="character">DUKE FREDERICK</div>
  23.206 +	  <div class="dialog">
  23.207 +	  <div id="scene1.3.37">You, cousin</div>
  23.208 +
  23.209 +	  <div id="scene1.3.38">Within these ten days if that thou be'st found</div>
  23.210 +
  23.211 +	  <div id="scene1.3.39">So near our public court as twenty miles,</div>
  23.212 +
  23.213 +	  <div id="scene1.3.40">Thou diest for it.</div>
  23.214 +
  23.215 +	  </div>
  23.216 +
  23.217 +	  <div id="speech23" class="character">ROSALIND</div>
  23.218 +
  23.219 +	  <div class="dialog">
  23.220 +
  23.221 +	  <div id="scene1.3.41">                  I do beseech your grace,</div>
  23.222 +
  23.223 +	  <div id="scene1.3.42">Let me the knowledge of my fault bear with me:</div>
  23.224 +	  <div id="scene1.3.43">If with myself I hold intelligence</div>
  23.225 +
  23.226 +	  <div id="scene1.3.44">Or have acquaintance with mine own desires,</div>
  23.227 +
  23.228 +	  <div id="scene1.3.45">If that I do not dream or be not frantic,--</div>
  23.229 +
  23.230 +	  <div id="scene1.3.46">As I do trust I am not--then, dear uncle,</div>
  23.231 +
  23.232 +	  <div id="scene1.3.47">Never so much as in a thought unborn</div>
  23.233 +
  23.234 +	  <div id="scene1.3.48">Did I offend your highness.</div>
  23.235 +
  23.236 +	  </div>
  23.237 +
  23.238 +	  <div id="speech24" class="character">DUKE FREDERICK</div>
  23.239 +
  23.240 +	  <div class="dialog">
  23.241 +	  <div id="scene1.3.49">Thus do all traitors:</div>
  23.242 +
  23.243 +	  <div id="scene1.3.50">If their purgation did consist in words,</div>
  23.244 +
  23.245 +	  <div id="scene1.3.51">They are as innocent as grace itself:</div>
  23.246 +
  23.247 +	  <div id="scene1.3.52">Let it suffice thee that I trust thee not.</div>
  23.248 +
  23.249 +	  </div>
  23.250 +
  23.251 +	  <div id="speech25" class="character">ROSALIND</div>
  23.252 +
  23.253 +	  <div class="dialog">
  23.254 +
  23.255 +	  <div id="scene1.3.53">Yet your mistrust cannot make me a traitor:</div>
  23.256 +
  23.257 +	  <div id="scene1.3.54">Tell me whereon the likelihood depends.</div>
  23.258 +
  23.259 +	  </div>
  23.260 +
  23.261 +	  <div id="speech26" class="character">DUKE FREDERICK</div>
  23.262 +	  <div class="dialog">
  23.263 +
  23.264 +	  <div id="scene1.3.55">Thou art thy father's daughter; there's enough.</div>
  23.265 +
  23.266 +	  </div>
  23.267 +
  23.268 +	  <div id="speech27" class="character">ROSALIND</div>
  23.269 +
  23.270 +	  <div class="dialog">
  23.271 +	  <div id="scene1.3.56">So was I when your highness took his dukedom;</div>
  23.272 +	  <div id="scene1.3.57">So was I when your highness banish'd him:</div>
  23.273 +
  23.274 +	  <div id="scene1.3.58">Treason is not inherited, my lord;</div>
  23.275 +
  23.276 +	  <div id="scene1.3.59">Or, if we did derive it from our friends,</div>
  23.277 +
  23.278 +	  <div id="scene1.3.60">What's that to me? my father was no traitor:</div>
  23.279 +
  23.280 +	  <div id="scene1.3.61">Then, good my liege, mistake me not so much</div>
  23.281 +	  <div id="scene1.3.62">To think my poverty is treacherous.</div>
  23.282 +
  23.283 +	  </div>
  23.284 +
  23.285 +	  <div id="speech28" class="character">CELIA</div>
  23.286 +	  <div class="dialog">
  23.287 +
  23.288 +	  <div id="scene1.3.63">Dear sovereign, hear me speak.</div>
  23.289 +
  23.290 +	  </div>
  23.291 +
  23.292 +	  <div id="speech29" class="character">DUKE FREDERICK</div>
  23.293 +
  23.294 +	  <div class="dialog">
  23.295 +	  <div id="scene1.3.64">Ay, Celia; we stay'd her for your sake,</div>
  23.296 +	  <div id="scene1.3.65">Else had she with her father ranged along.</div>
  23.297 +
  23.298 +	  </div>
  23.299 +
  23.300 +	  <div id="speech30" class="character">CELIA</div>
  23.301 +
  23.302 +	  <div class="dialog">
  23.303 +
  23.304 +	  <div id="scene1.3.66">I did not then entreat to have her stay;</div>
  23.305 +	  <div id="scene1.3.67">It was your pleasure and your own remorse:</div>
  23.306 +	  <div id="scene1.3.68">I was too young that time to value her;</div>
  23.307 +
  23.308 +	  <div id="scene1.3.69">But now I know her: if she be a traitor,</div>
  23.309 +
  23.310 +	  <div id="scene1.3.70">Why so am I; we still have slept together,</div>
  23.311 +
  23.312 +	  <div id="scene1.3.71">Rose at an instant, learn'd, play'd, eat together,</div>
  23.313 +	  <div id="scene1.3.72">And wheresoever we went, like Juno's swans,</div>
  23.314 +
  23.315 +	  <div id="scene1.3.73">Still we went coupled and inseparable.</div>
  23.316 +	  </div>
  23.317 +
  23.318 +	  <div id="speech31" class="character">DUKE FREDERICK</div>
  23.319 +
  23.320 +	  <div class="dialog">
  23.321 +	  <div id="scene1.3.74">She is too subtle for thee; and her smoothness,</div>
  23.322 +	  <div id="scene1.3.75">Her very silence and her patience</div>
  23.323 +	  <div id="scene1.3.76">Speak to the people, and they pity her.</div>
  23.324 +	  <div id="scene1.3.77">Thou art a fool: she robs thee of thy name;</div>
  23.325 +
  23.326 +	  <div id="scene1.3.78">And thou wilt show more bright and seem more virtuous</div>
  23.327 +
  23.328 +	  <div id="scene1.3.79">When she is gone. Then open not thy lips:</div>
  23.329 +	  <div id="scene1.3.80">Firm and irrevocable is my doom</div>
  23.330 +	  <div id="scene1.3.81">Which I have pass'd upon her; she is banish'd.</div>
  23.331 +	  </div>
  23.332 +
  23.333 +	  <div id="speech32" class="character">CELIA</div>
  23.334 +
  23.335 +	  <div class="dialog">
  23.336 +	  <div id="scene1.3.82">Pronounce that sentence then on me, my liege:</div>
  23.337 +	  <div id="scene1.3.83">I cannot live out of her company.</div>
  23.338 +	  </div>
  23.339 +
  23.340 +	  <div id="speech33" class="character">DUKE FREDERICK</div>
  23.341 +
  23.342 +	  <div class="dialog">
  23.343 +	  <div id="scene1.3.84">You are a fool. You, niece, provide yourself:</div>
  23.344 +
  23.345 +	  <div id="scene1.3.85">If you outstay the time, upon mine honour,</div>
  23.346 +	  <div id="scene1.3.86">And in the greatness of my word, you die.</div>
  23.347 +	  <div class="direction">Exeunt DUKE FREDERICK and Lords</div>
  23.348 +	  </div>
  23.349 +
  23.350 +	  <div id="speech34" class="character">CELIA</div>
  23.351 +	  <div class="dialog">
  23.352 +
  23.353 +	  <div id="scene1.3.87">O my poor Rosalind, whither wilt thou go?</div>
  23.354 +
  23.355 +	  <div id="scene1.3.88">Wilt thou change fathers? I will give thee mine.</div>
  23.356 +	  <div id="scene1.3.89">I charge thee, be not thou more grieved than I am.</div>
  23.357 +
  23.358 +	  </div>
  23.359 +
  23.360 +	  <div id="speech35" class="character">ROSALIND</div>
  23.361 +
  23.362 +	  <div class="dialog">
  23.363 +
  23.364 +	  <div id="scene1.3.90">I have more cause.</div>
  23.365 +	  </div>
  23.366 +
  23.367 +	  <div id="speech36" class="character">CELIA</div>
  23.368 +
  23.369 +	  <div class="dialog">
  23.370 +	  <div id="scene1.3.91">                  Thou hast not, cousin;</div>
  23.371 +
  23.372 +	  <div id="scene1.3.92">Prithee be cheerful: know'st thou not, the duke</div>
  23.373 +
  23.374 +	  <div id="scene1.3.93">Hath banish'd me, his daughter?</div>
  23.375 +
  23.376 +	  </div>
  23.377 +
  23.378 +	  <div id="speech37" class="character">ROSALIND</div>
  23.379 +	  <div class="dialog">
  23.380 +	  <div id="scene1.3.94">That he hath not.</div>
  23.381 +
  23.382 +	  </div>
  23.383 +
  23.384 +	  <div id="speech38" class="character">CELIA</div>
  23.385 +
  23.386 +	  <div class="dialog">
  23.387 +
  23.388 +	  <div id="scene1.3.95">No, hath not? Rosalind lacks then the love</div>
  23.389 +
  23.390 +	  <div id="scene1.3.96">Which teacheth thee that thou and I am one:</div>
  23.391 +	  <div id="scene1.3.97">Shall we be sunder'd? shall we part, sweet girl?</div>
  23.392 +
  23.393 +	  <div id="scene1.3.98">No: let my father seek another heir.</div>
  23.394 +
  23.395 +	  <div id="scene1.3.99">Therefore devise with me how we may fly,</div>
  23.396 +
  23.397 +	  <div id="scene1.3.100">Whither to go and what to bear with us;</div>
  23.398 +	  <div id="scene1.3.101">And do not seek to take your change upon you,</div>
  23.399 +	  <div id="scene1.3.102">To bear your griefs yourself and leave me out;</div>
  23.400 +
  23.401 +	  <div id="scene1.3.103">For, by this heaven, now at our sorrows pale,</div>
  23.402 +
  23.403 +	  <div id="scene1.3.104">Say what thou canst, I'll go along with thee.</div>
  23.404 +
  23.405 +	  </div>
  23.406 +
  23.407 +	  <div id="speech39" class="character">ROSALIND</div>
  23.408 +	  <div class="dialog">
  23.409 +
  23.410 +	  <div id="scene1.3.105">Why, whither shall we go?</div>
  23.411 +
  23.412 +	  </div>
  23.413 +
  23.414 +	  <div id="speech40" class="character">CELIA</div>
  23.415 +
  23.416 +	  <div class="dialog">
  23.417 +	  <div id="scene1.3.106">To seek my uncle in the forest of Arden.</div>
  23.418 +	  </div>
  23.419 +
  23.420 +	  <div id="speech41" class="character">ROSALIND</div>
  23.421 +
  23.422 +	  <div class="dialog">
  23.423 +
  23.424 +	  <div id="scene1.3.107">Alas, what danger will it be to us,</div>
  23.425 +
  23.426 +	  <div id="scene1.3.108">Maids as we are, to travel forth so far!</div>
  23.427 +	  <div id="scene1.3.109">Beauty provoketh thieves sooner than gold.</div>
  23.428 +
  23.429 +	  </div>
  23.430 +
  23.431 +	  <div id="speech42" class="character">CELIA</div>
  23.432 +
  23.433 +	  <div class="dialog">
  23.434 +	  <div id="scene1.3.110">I'll put myself in poor and mean attire</div>
  23.435 +
  23.436 +	  <div id="scene1.3.111">And with a kind of umber smirch my face;</div>
  23.437 +	  <div id="scene1.3.112">The like do you: so shall we pass along</div>
  23.438 +
  23.439 +	  <div id="scene1.3.113">And never stir assailants.</div>
  23.440 +
  23.441 +	  </div>
  23.442 +
  23.443 +	  <div id="speech43" class="character">ROSALIND</div>
  23.444 +	  <div class="dialog">
  23.445 +
  23.446 +	  <div id="scene1.3.114">Were it not better,</div>
  23.447 +
  23.448 +	  <div id="scene1.3.115">Because that I am more than common tall,</div>
  23.449 +
  23.450 +	  <div id="scene1.3.116">That I did suit me all points like a man?</div>
  23.451 +
  23.452 +	  <div id="scene1.3.117">A gallant curtle-axe upon my thigh,</div>
  23.453 +
  23.454 +	  <div id="scene1.3.118">A boar-spear in my hand; and--in my heart</div>
  23.455 +
  23.456 +	  <div id="scene1.3.119">Lie there what hidden woman's fear there will--</div>
  23.457 +
  23.458 +	  <div id="scene1.3.120">We'll have a swashing and a martial outside,</div>
  23.459 +
  23.460 +	  <div id="scene1.3.121">As many other mannish cowards have</div>
  23.461 +
  23.462 +	  <div id="scene1.3.122">That do outface it with their semblances.</div>
  23.463 +
  23.464 +	  </div>
  23.465 +
  23.466 +	  <div id="speech44" class="character">CELIA</div>
  23.467 +
  23.468 +	  <div class="dialog">
  23.469 +
  23.470 +	  <div id="scene1.3.123">What shall I call thee when thou art a man?</div>
  23.471 +	  </div>
  23.472 +
  23.473 +	  <div id="speech45" class="character">ROSALIND</div>
  23.474 +
  23.475 +	  <div class="dialog">
  23.476 +	  <div id="scene1.3.124">I'll have no worse a name than Jove's own page;</div>
  23.477 +
  23.478 +	  <div id="scene1.3.125">And therefore look you call me Ganymede.</div>
  23.479 +
  23.480 +	  <div id="scene1.3.126">But what will you be call'd?</div>
  23.481 +
  23.482 +	  </div>
  23.483 +
  23.484 +	  <div id="speech46" class="character">CELIA</div>
  23.485 +
  23.486 +	  <div class="dialog">
  23.487 +
  23.488 +	  <div id="scene1.3.127">Something that hath a reference to my state</div>
  23.489 +	  <div id="scene1.3.128">No longer Celia, but Aliena.</div>
  23.490 +
  23.491 +	  </div>
  23.492 +
  23.493 +	  <div id="speech47" class="character">ROSALIND</div>
  23.494 +	  <div class="dialog">
  23.495 +
  23.496 +	  <div id="scene1.3.129">But, cousin, what if we assay'd to steal</div>
  23.497 +
  23.498 +	  <div id="scene1.3.130">The clownish fool out of your father's court?</div>
  23.499 +
  23.500 +	  <div id="scene1.3.131">Would he not be a comfort to our travel?</div>
  23.501 +
  23.502 +	  </div>
  23.503 +
  23.504 +	  <div id="speech48" class="character">CELIA</div>
  23.505 +
  23.506 +	  <div class="dialog">
  23.507 +
  23.508 +	  <div id="scene1.3.132">He'll go along o'er the wide world with me;</div>
  23.509 +
  23.510 +	  <div id="scene1.3.133">Leave me alone to woo him. Let's away,</div>
  23.511 +	  <div id="scene1.3.134">And get our jewels and our wealth together,</div>
  23.512 +
  23.513 +	  <div id="scene1.3.135">Devise the fittest time and safest way</div>
  23.514 +
  23.515 +	  <div id="scene1.3.136">To hide us from pursuit that will be made</div>
  23.516 +
  23.517 +	  <div id="scene1.3.137">After my flight. Now go we in content</div>
  23.518 +
  23.519 +	  <div id="scene1.3.138">To liberty and not to banishment.</div>
  23.520 +	  <div class="direction">Exeunt</div>
  23.521 +
  23.522 +	  </div>
  23.523 +
  23.524 +	</div>
  23.525 +	</div>
  23.526 +</div>
  23.527 +</body>
  23.528 +
  23.529 +</html>
  23.530 \ No newline at end of file
    24.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.2 +++ b/tests/lxml/test_css.py	Tue Dec 30 22:26:25 2008 +0100
    24.3 @@ -0,0 +1,133 @@
    24.4 +import unittest, sys
    24.5 +from lxml.tests.common_imports import doctest, make_doctest, HelperTestCase
    24.6 +from lxml import html
    24.7 +from lxml import cssselect
    24.8 +import os
    24.9 +
   24.10 +doc_fn = os.path.join(os.path.dirname(__file__),
   24.11 +                      'css_shakespear.html')
   24.12 +
   24.13 +try:
   24.14 +    basestring = __builtins__['basestring']
   24.15 +except (NameError, KeyError):
   24.16 +    basestring = (str, bytes)
   24.17 +
   24.18 +# Data borrowed from http://mootools.net/slickspeed/
   24.19 +
   24.20 +class CSSTestCase(HelperTestCase):
   24.21 +    
   24.22 +    selectors = [
   24.23 +        ## Changed from original; probably because I'm only searching the body
   24.24 +        #('*', 252),
   24.25 +        ('*', 246),
   24.26 +        ('div:only-child', 22), # ?
   24.27 +        ## Changed from original, because the original doesn't make sense.
   24.28 +        ## There really aren't that many occurrances of 'celia'
   24.29 +        #('div:contains(CELIA)', 243),
   24.30 +        ('div:contains(CELIA)', 30),
   24.31 +        ('div:nth-child(even)', 106),
   24.32 +        ('div:nth-child(2n)', 106),
   24.33 +        ('div:nth-child(odd)', 137),
   24.34 +        ('div:nth-child(2n+1)', 137),
   24.35 +        ('div:nth-child(n)', 243),
   24.36 +        ('div:last-child', 53),
   24.37 +        ('div:first-child', 51),
   24.38 +        ('div > div', 242),
   24.39 +        ('div + div', 190),
   24.40 +        ('div ~ div', 190),
   24.41 +        ('body', 1),
   24.42 +        ('body div', 243),
   24.43 +        ('div', 243),
   24.44 +        ('div div', 242),
   24.45 +        ('div div div', 241),
   24.46 +        ('div, div, div', 243),
   24.47 +        ('div, a, span', 243),
   24.48 +        ('.dialog', 51),
   24.49 +        ('div.dialog', 51),
   24.50 +        ('div .dialog', 51),
   24.51 +        ('div.character, div.dialog', 99),
   24.52 +        ('div.direction.dialog', 0),
   24.53 +        ('div.dialog.direction', 0),
   24.54 +        ('div.dialog.scene', 1),
   24.55 +        ('div.scene.scene', 1),
   24.56 +        ('div.scene .scene', 0),
   24.57 +        ('div.direction .dialog ', 0),
   24.58 +        ('div .dialog .direction', 4),
   24.59 +        ('div.dialog .dialog .direction', 4),
   24.60 +        ('#speech5', 1),
   24.61 +        ('div#speech5', 1),
   24.62 +        ('div #speech5', 1),
   24.63 +        ('div.scene div.dialog', 49),
   24.64 +        ('div#scene1 div.dialog div', 142),
   24.65 +        ('#scene1 #speech1', 1),
   24.66 +        ('div[class]', 103),
   24.67 +        ('div[class=dialog]', 50),
   24.68 +        ('div[class^=dia]', 51),
   24.69 +        ('div[class$=log]', 50),
   24.70 +        ('div[class*=sce]', 1),
   24.71 +        ('div[class|=dialog]', 50), # ? Seems right
   24.72 +        ('div[class!=madeup]', 243), # ? Seems right
   24.73 +        ('div[class~=dialog]', 51), # ? Seems right
   24.74 +        ]
   24.75 +
   24.76 +    def __init__(self, index):
   24.77 +        self.index = index
   24.78 +        super(HelperTestCase, self).__init__()
   24.79 +
   24.80 +    def all(cls):
   24.81 +        for i in range(len(cls.selectors)):
   24.82 +            yield cls(i)
   24.83 +    all = classmethod(all)
   24.84 +
   24.85 +    def runTest(self):
   24.86 +        f = open(doc_fn, 'rb')
   24.87 +        c = f.read()
   24.88 +        f.close()
   24.89 +        doc = html.document_fromstring(c)
   24.90 +        body = doc.xpath('//body')[0]
   24.91 +        bad = []
   24.92 +        selector, count = self.selectors[self.index]
   24.93 +        xpath = cssselect.css_to_xpath(cssselect.parse(selector))
   24.94 +        try:
   24.95 +            results = body.xpath(xpath)
   24.96 +        except Exception:
   24.97 +            e = sys.exc_info()[1]
   24.98 +            e.args = ("%s for xpath %r" % (e, xpath))
   24.99 +            raise
  24.100 +        found = {}
  24.101 +        for item in results:
  24.102 +            if item in found:
  24.103 +                assert 0, (
  24.104 +                    "Element shows up multiple times: %r" % item)
  24.105 +            found[item] = None
  24.106 +        if isinstance(results, basestring):
  24.107 +            assert 0, (
  24.108 +                "Got string result (%r), not element, for xpath %r"
  24.109 +                % (results[:20], str(xpath)))
  24.110 +        if len(results) != count:
  24.111 +            #if self.shortDescription() == 'div.character, div.dialog':
  24.112 +            #    import pdb; pdb.set_trace()
  24.113 +            assert 0, (
  24.114 +                "Did not get expected results (%s) instead %s for xpath %r"
  24.115 +                % (count, len(results), str(xpath)))
  24.116 +
  24.117 +    def shortDescription(self):
  24.118 +        return self.selectors[self.index][0]
  24.119 +
  24.120 +def unique(s):
  24.121 +    found = {}
  24.122 +    result = []
  24.123 +    for item in s:
  24.124 +        if item in found:
  24.125 +            continue
  24.126 +        found[item] = None
  24.127 +        result.append(s)
  24.128 +    return result
  24.129 +        
  24.130 +def test_suite():
  24.131 +    suite = unittest.TestSuite()
  24.132 +    if sys.version_info >= (2,4):
  24.133 +        suite.addTests([make_doctest('test_css_select.txt')])
  24.134 +    suite.addTests([make_doctest('test_css.txt')])
  24.135 +    suite.addTests(list(CSSTestCase.all()))
  24.136 +    return suite
    25.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.2 +++ b/tests/lxml/test_css.txt	Tue Dec 30 22:26:25 2008 +0100
    25.3 @@ -0,0 +1,140 @@
    25.4 +A quick test of tokenizing:
    25.5 +
    25.6 +    >>> from lxml.cssselect import tokenize, parse
    25.7 +    >>> def ptok(s):
    25.8 +    ...     for item in tokenize(s):
    25.9 +    ...         print(repr(item).replace("u'", "'"))
   25.10 +    >>> ptok('E > f[a~="y\\"x"]')
   25.11 +    Symbol('E', 0)
   25.12 +    Token('>', 2)
   25.13 +    Symbol('f', 4)
   25.14 +    Token('[', 5)
   25.15 +    Symbol('a', 6)
   25.16 +    Token('~=', 7)
   25.17 +    String('y"x', 9)
   25.18 +    Token(']', 15)
   25.19 +
   25.20 +Then of parsing:
   25.21 +
   25.22 +    >>> parse('td.foo, .bar')
   25.23 +    Or([Class[Element[td].foo], CombinedSelector[Element[*] <followed> Class[Element[*].bar]]])
   25.24 +    >>> parse('div, td.foo, div.bar span')
   25.25 +    Or([Element[div], Class[Element[td].foo], CombinedSelector[Class[Element[div].bar] <followed> Element[span]]])
   25.26 +    >>> parse('div > p')
   25.27 +    CombinedSelector[Element[div] > Element[p]]
   25.28 +    >>> parse('td:first')
   25.29 +    Pseudo[Element[td]:first]
   25.30 +    >>> parse('a[name]')
   25.31 +    Attrib[Element[a][name]]
   25.32 +    >>> repr(parse('a[rel="include"]')).replace("u'", "'")
   25.33 +    "Attrib[Element[a][rel = String('include', 6)]]"
   25.34 +    >>> repr(parse('a[hreflang |= \'en\']')).replace("u'", "'")
   25.35 +    "Attrib[Element[a][hreflang |= String('en', 14)]]"
   25.36 +    >>> parse('div:nth-child(10)')
   25.37 +    Function[Element[div]:nth-child(10)]
   25.38 +    >>> parse('div:nth-of-type(10)')
   25.39 +    Function[Element[div]:nth-of-type(10)]
   25.40 +    >>> parse('div div:nth-of-type(10) .aclass')
   25.41 +    CombinedSelector[CombinedSelector[Element[div] <followed> Function[Element[div]:nth-of-type(10)]] <followed> Class[Element[*].aclass]]
   25.42 +    >>> parse('label:only')
   25.43 +    Pseudo[Element[label]:only]
   25.44 +    >>> parse('a:lang(fr)')
   25.45 +    Function[Element[a]:lang(Element[fr])]
   25.46 +    >>> repr(parse('div:contains("foo")')).replace("u'", "'")
   25.47 +    "Function[Element[div]:contains(String('foo', 13))]"
   25.48 +    >>> parse('div#foobar')
   25.49 +    Hash[Element[div]#foobar]
   25.50 +    >>> parse('div:not(div.foo)')
   25.51 +    Function[Element[div]:not(Class[Element[div].foo])]
   25.52 +    >>> parse('td ~ th')
   25.53 +    CombinedSelector[Element[td] ~ Element[th]]
   25.54 +
   25.55 +Now of translation:
   25.56 +
   25.57 +    >>> def xpath(css):
   25.58 +    ...     print(parse(css).xpath())
   25.59 +    >>> xpath('*')
   25.60 +    *
   25.61 +    >>> xpath('E')
   25.62 +    e
   25.63 +    >>> xpath('E[foo]')
   25.64 +    e[@foo]
   25.65 +    >>> xpath('E[foo="bar"]')
   25.66 +    e[@foo = 'bar']
   25.67 +    >>> xpath('E[foo~="bar"]')
   25.68 +    e[contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]
   25.69 +    >>> xpath('E[foo^="bar"]')
   25.70 +    e[starts-with(@foo, 'bar')]
   25.71 +    >>> xpath('E[foo$="bar"]')
   25.72 +    e[substring(@foo, string-length(@foo)-2) = 'bar']
   25.73 +    >>> xpath('E[foo*="bar"]')
   25.74 +    e[contains(@foo, 'bar')]
   25.75 +    >>> xpath('E[hreflang|="en"]')
   25.76 +    e[@hreflang = 'en' or starts-with(@hreflang, 'en-')]
   25.77 +    >>> #xpath('E:root')
   25.78 +    >>> xpath('E:nth-child(1)')
   25.79 +    */*[name() = 'e' and (position() = 1)]
   25.80 +    >>> xpath('E:nth-last-child(1)')
   25.81 +    */*[name() = 'e' and (position() = last() - 1)]
   25.82 +    >>> xpath('E:nth-last-child(2n+2)')
   25.83 +    */*[name() = 'e' and ((position() +2) mod -2 = 0 and position() < (last() -2))]
   25.84 +    >>> xpath('E:nth-of-type(1)')
   25.85 +    */e[position() = 1]
   25.86 +    >>> xpath('E:nth-last-of-type(1)')
   25.87 +    */e[position() = last() - 1]
   25.88 +    >>> xpath('E:nth-last-of-type(1)')
   25.89 +    */e[position() = last() - 1]
   25.90 +    >>> xpath('div E:nth-last-of-type(1) .aclass')
   25.91 +    div/descendant::e[position() = last() - 1]/descendant::*[contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]
   25.92 +    >>> xpath('E:first-child')
   25.93 +    */*[name() = 'e' and (position() = 1)]
   25.94 +    >>> xpath('E:last-child')
   25.95 +    */*[name() = 'e' and (position() = last())]
   25.96 +    >>> xpath('E:first-of-type')
   25.97 +    */e[position() = 1]
   25.98 +    >>> xpath('E:last-of-type')
   25.99 +    */e[position() = last()]
  25.100 +    >>> xpath('E:only-child')
  25.101 +    */*[name() = 'e' and (last() = 1)]
  25.102 +    >>> xpath('E:only-of-type')
  25.103 +    e[last() = 1]
  25.104 +    >>> xpath('E:empty')
  25.105 +    e[not(*) and not(normalize-space())]
  25.106 +    >>> xpath('E:contains("foo")')
  25.107 +    e[contains(css:lower-case(string(.)), 'foo')]
  25.108 +    >>> xpath('E.warning')
  25.109 +    e[contains(concat(' ', normalize-space(@class), ' '), ' warning ')]
  25.110 +    >>> xpath('E#myid')
  25.111 +    e[@id = 'myid']
  25.112 +    >>> xpath('E:not(:contains("foo"))')
  25.113 +    e[not(contains(css:lower-case(string(.)), 'foo'))]
  25.114 +    >>> xpath('E F')
  25.115 +    e/descendant::f
  25.116 +    >>> xpath('E > F')
  25.117 +    e/f
  25.118 +    >>> xpath('E + F')
  25.119 +    e/following-sibling::*[name() = 'f' and (position() = 1)]
  25.120 +    >>> xpath('E ~ F')
  25.121 +    e/following-sibling::f
  25.122 +    >>> xpath('div#container p')
  25.123 +    div[@id = 'container']/descendant::p
  25.124 +    >>> xpath('p *:only-of-type')
  25.125 +    Traceback (most recent call last):
  25.126 +        ...
  25.127 +    NotImplementedError: *:only-of-type is not implemented
  25.128 +
  25.129 +Then of parse_series:
  25.130 +
  25.131 +    >>> from lxml.cssselect import parse_series
  25.132 +    >>> parse_series('1n+3')
  25.133 +    (1, 3)
  25.134 +    >>> parse_series('n-5')
  25.135 +    (1, -5)
  25.136 +    >>> parse_series('odd')
  25.137 +    (2, 1)
  25.138 +    >>> parse_series('3n')
  25.139 +    (3, 0)
  25.140 +    >>> parse_series('n')
  25.141 +    (1, 0)
  25.142 +    >>> parse_series('5')
  25.143 +    (0, 5)
    26.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.2 +++ b/tests/lxml/test_css_select.txt	Tue Dec 30 22:26:25 2008 +0100
    26.3 @@ -0,0 +1,150 @@
    26.4 +This is a test of CSS selectors.  We setup a document we'll use for
    26.5 +all our selections, and a function make querying simpler:
    26.6 +
    26.7 +    >>> from lxml.cssselect import CSSSelector
    26.8 +    >>> from lxml.etree import HTML
    26.9 +    >>> doc = HTML('''
   26.10 +    ... <html><head></head><body>
   26.11 +    ... <div id="outer-div">
   26.12 +    ...  <a id="name-anchor" name="foo"></a>
   26.13 +    ...  <a id="tag-anchor" rel="tag" href="http://localhost/foo">link</a>
   26.14 +    ...  <a id="nofollow-anchor" rel="nofollow" href="https://example.org">link</a>
   26.15 +    ...  <ol id="first-ol" class="a b c">
   26.16 +    ...    <li id="first-li">content</li>
   26.17 +    ...    <li id="second-li" lang="en-US">
   26.18 +    ...      <div id="li-div">
   26.19 +    ...      </div>
   26.20 +    ...    </li>
   26.21 +    ...    <li id="third-li" class="ab c"></li>
   26.22 +    ...    <li id="fourth-li" class="ab
   26.23 +    ... c"></li>
   26.24 +    ...    <li id="fifth-li"></li>
   26.25 +    ...    <li id="sixth-li"></li>
   26.26 +    ...    <li id="seventh-li">  </li>
   26.27 +    ...  </ol>
   26.28 +    ...  <p id="paragraph">
   26.29 +    ...    <b id="p-b">hi</b> <em id="p-em">there</em>
   26.30 +    ...    <b id="p-b2">guy</b></p>
   26.31 +    ...  <ol id="second-ol">
   26.32 +    ...  </ol>
   26.33 +    ... </div>
   26.34 +    ... <div id="foobar-div" foobar="ab bc
   26.35 +    ... cde"><span id="foobar-span"></span></div>
   26.36 +    ... </body></html>''')
   26.37 +    >>> order = {}
   26.38 +    >>> for count, el in enumerate(doc.getiterator()):
   26.39 +    ...     order[el] = count
   26.40 +    >>> def select_ids(selector):
   26.41 +    ...     items = CSSSelector(selector)(doc)
   26.42 +    ...     if not items:
   26.43 +    ...         return 'empty'
   26.44 +    ...     items = CSSSelector(selector)(doc)
   26.45 +    ...     items.sort(key=lambda el: order[el])
   26.46 +    ...     return ', '.join([el.get('id', 'nil') for el in items])
   26.47 +    >>> def pcss(main, *selectors):
   26.48 +    ...     result = select_ids(main)
   26.49 +    ...     for selector in selectors:
   26.50 +    ...         sel_result = select_ids(selector)
   26.51 +    ...         if sel_result != result:
   26.52 +    ...             print('Selector %r returns %s' % (selector, sel_result))
   26.53 +    ...     print(result)
   26.54 +
   26.55 +Now, the tests:
   26.56 +
   26.57 +    >>> pcss('*') # doctest: +ELLIPSIS
   26.58 +    nil, nil, nil, outer-div, ... foobar-span
   26.59 +    >>> pcss('div')
   26.60 +    outer-div, li-div, foobar-div
   26.61 +    >>> pcss('a[name]')
   26.62 +    name-anchor
   26.63 +    >>> pcss('a[rel]')
   26.64 +    tag-anchor, nofollow-anchor
   26.65 +    >>> pcss('a[rel="tag"]')
   26.66 +    tag-anchor
   26.67 +    >>> pcss('a[href*="localhost"]')
   26.68 +    tag-anchor
   26.69 +    >>> pcss('a[href^="http"]')
   26.70 +    tag-anchor, nofollow-anchor
   26.71 +    >>> pcss('a[href^="http:"]')
   26.72 +    tag-anchor
   26.73 +    >>> pcss('a[href$="org"]')
   26.74 +    nofollow-anchor
   26.75 +    >>> pcss('div[foobar~="bc"]', 'div[foobar~="cde"]')
   26.76 +    foobar-div
   26.77 +    >>> pcss('div[foobar~="cd"]')
   26.78 +    empty
   26.79 +    >>> pcss('*[lang|="en"]', '*[lang|="en-US"]')
   26.80 +    second-li
   26.81 +    >>> pcss('*[lang|="e"]')
   26.82 +    empty
   26.83 +    >>> pcss('li:nth-child(3)')
   26.84 +    third-li
   26.85 +    >>> pcss('li:nth-child(10)')
   26.86 +    empty
   26.87 +    >>> pcss('li:nth-child(2n)', 'li:nth-child(even)', 'li:nth-child(2n+0)')
   26.88 +    second-li, fourth-li, sixth-li
   26.89 +    >>> pcss('li:nth-child(+2n+1)', 'li:nth-child(odd)')
   26.90 +    first-li, third-li, fifth-li, seventh-li
   26.91 +    >>> pcss('li:nth-child(2n+4)')
   26.92 +    fourth-li, sixth-li
   26.93 +    >>> # FIXME: I'm not 100% sure this is right:
   26.94 +    >>> pcss('li:nth-child(3n+1)')
   26.95 +    first-li, fourth-li, seventh-li
   26.96 +    >>> # FIXME: I'm not sure if nth-last-child(1) or nth-last-child(1)
   26.97 +    >>> # should be equivalent to nth-last-child()
   26.98 +    >>> pcss('li:nth-last-child()', 'li:nth-last-child(0)')
   26.99 +    seventh-li
  26.100 +    >>> pcss('li:nth-last-child(2n)', 'li:nth-last-child(even)')
  26.101 +    second-li, fourth-li, sixth-li
  26.102 +    >>> pcss('li:nth-last-child(2n+2)')
  26.103 +    second-li, fourth-li
  26.104 +    >>> pcss('ol:first-of-type')
  26.105 +    first-ol
  26.106 +    >>> pcss('ol:nth-child(1)')
  26.107 +    empty
  26.108 +    >>> pcss('ol:nth-of-type(2)')
  26.109 +    second-ol
  26.110 +    >>> # FIXME: like above, (1) or (2)?
  26.111 +    >>> pcss('ol:nth-last-of-type(1)')
  26.112 +    first-ol
  26.113 +    >>> pcss('span:only-child')
  26.114 +    foobar-span
  26.115 +    >>> pcss('li div:only-child')
  26.116 +    li-div
  26.117 +    >>> pcss('div *:only-child')
  26.118 +    foobar-span
  26.119 +    >>> pcss('p *:only-of-type')
  26.120 +    Traceback (most recent call last):
  26.121 +        ...
  26.122 +    NotImplementedError: *:only-of-type is not implemented
  26.123 +    >>> pcss('p:only-of-type')
  26.124 +    paragraph
  26.125 +    >>> pcss('a:empty')
  26.126 +    name-anchor
  26.127 +    >>> pcss('li:empty')
  26.128 +    third-li, fourth-li, fifth-li, sixth-li, seventh-li
  26.129 +    >>> pcss('*:contains("link")')
  26.130 +    nil, nil, outer-div, tag-anchor, nofollow-anchor
  26.131 +    >>> pcss('*:contains("E")')
  26.132 +    nil, nil, outer-div, first-ol, first-li, paragraph, p-em
  26.133 +    >>> pcss('.a', '.b', '*.a', 'ol.a')
  26.134 +    first-ol
  26.135 +    >>> pcss('.c', '*.c')
  26.136 +    first-ol, third-li, fourth-li
  26.137 +    >>> pcss('ol *.c', 'ol li.c', 'li ~ li.c', 'ol > li.c')
  26.138 +    third-li, fourth-li
  26.139 +    >>> pcss('#first-li', 'li#first-li', '*#first-li')
  26.140 +    first-li
  26.141 +    >>> # Need some tests of :not()
  26.142 +    >>> pcss('li div', 'li > div', 'div div')
  26.143 +    li-div
  26.144 +    >>> pcss('div > div')
  26.145 +    empty
  26.146 +    >>> pcss('div + div')
  26.147 +    foobar-div
  26.148 +    >>> pcss('a ~ a')
  26.149 +    tag-anchor, nofollow-anchor
  26.150 +    >>> pcss('a[rel="tag"] ~ a')
  26.151 +    nofollow-anchor
  26.152 +    >>> pcss('ol#first-ol li:last-child', 'ol#first-ol *:last-child')
  26.153 +    seventh-li
    27.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    27.2 +++ b/tests/selectors.txt	Tue Dec 30 22:26:25 2008 +0100
    27.3 @@ -0,0 +1,22 @@
    27.4 +*,                  //*
    27.5 +E,                  //E
    27.6 +E F,                //E//F
    27.7 +E > F,              //E/F
    27.8 +E:first-child,      //*[1]/self::E
    27.9 +E:nth-child(3),     //E[count(preceding-sibling::*)=3-1]
   27.10 +E:lang(c),          //E[@xml:lang='c' or starts-with(@xml:lang,'c-')]
   27.11 +E + F,              //E/following-sibling::*[1]/self::F
   27.12 +E[foo],             //E[@foo]
   27.13 +E[foo="warning"],   //E[@foo='warning']
   27.14 +E[foo~="warning"],  //E[contains(concat(' ',@foo,' '),' warning ')]
   27.15 +E[lang|="en"],      //E[@lang='en' or starts-with(@lang,'en-')]
   27.16 +E:not([foo="warning"]),  //E[not(./self::*[@foo='warning'])]
   27.17 +E:not(.warning),         //E[not(./self::*[contains(concat(' ',@class,' '),' warning ')])]
   27.18 +DIV.warning,        //DIV[contains(concat(' ',@class,' '),' warning ')]
   27.19 +E#myid,             //E[@id='myid']
   27.20 +p > *,              //p/*
   27.21 +#foo,               //*[@id='foo']
   27.22 +.foo,               //*[contains(concat(' ',@class,' '),' foo ')]
   27.23 +*[title],           //*[@title]
   27.24 +p > *:first-child,  //p/*[1]/self::*
   27.25 +p[bar] + .content-top[foo],              //p[@bar]/following-sibling::*[1]/self::*[contains(concat(' ',@class,' '),' content-top ')][@foo]

To download these repositories, get Mercurial and then type something like:

hg clone http://techn.ocracy.org/repository-name/

You can also click the "zip" or "gz" links to get an archive of the latest revision without installing anything.

The change logs of the repositories are aggregated at techn.ocracy.org/planet.

We have also some darcs repositories at techn.ocracy.org/darcs.