Repository: symfony/symfony
File: src/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php (PHP)

Code before:
            ['e:nth-last-child(2n+2)', "*/*[(name() = 'e') and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]"],
            ['e:nth-of-type(1)', '*/e[position() = 1]'],
            ['e:nth-last-of-type(1)', '*/e[position() = last() - 0]'],
            ['div e:nth-last-of-type(1) .aclass', "div/descendant-or-self::*/e[position() = last() - 0]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]"],
            ['e:first-child', "*/*[(name() = 'e') and (position() = 1)]"],
            ['e:last-child', "*/*[(name() = 'e') and (position() = last())]"],
            ['e:first-of-type', '*/e[position() = 1]'],
            ['e:last-of-type', '*/e[position() = last()]'],
            ['e:only-child', "*/*[(name() = 'e') and (last() = 1)]"],
            ['e:only-of-type', 'e[count(preceding-sibling::e)=0 and count(following-sibling::e)=0]'],
            ['e:empty', 'e[not(*) and not(string-length())]'],
            ['e:EmPTY', 'e[not(*) and not(string-length())]'],
            ['e:root', 'e[not(parent::*)]'],
            ['e:hover', 'e[0]'],
            ['e:contains("foo")', "e[contains(string(.), 'foo')]"],
            ['e:ConTains(foo)', "e[contains(string(.), 'foo')]"],
            ['e.warning', "e[@class and contains(concat(' ', normalize-space(@class), ' '), ' warning ')]"],
            ['e#myid', "e[@id = 'myid']"],
            ['e:not(:nth-child(odd))', 'e[not(position() - 1 >= 0 and (position() - 1) mod 2 = 0)]'],
            ['e:nOT(*)', 'e[0]'],
            ['e f', 'e/descendant-or-self::*/f'],
            ['e > f', 'e/f'],
            ['e + f', "e/following-sibling::*[(name() = 'f') and (position() = 1)]"],
            ['e ~ f', 'e/following-sibling::f'],
            ['div#container p', "div[@id = 'container']/descendant-or-self::*/p"],
            ['div:has(div.foo)', "div[count(descendant-or-self::*[(@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')) and (name() = 'div')]) > 0]"],
        ];
    }

    public static function getXmlLangTestData()
    {
        return [
            [':lang("EN")', ['first', 'second', 'third', 'fourth']],
            [':lang("en-us")', ['second', 'fourth']],
            [':lang(en-nz)', ['third']],
            [':lang(fr)', ['fifth']],
            [':lang(ru)', ['sixth']],
            [":lang('ZH')", ['eighth']],
            [':lang(de) :lang(zh)', ['eighth']],
            [':lang(en), :lang(zh)', ['first', 'second', 'third', 'fourth', 'eighth']],
            [':lang(es)', []],
        ];
    }

    public static function getHtmlIdsTestData()
    {
        return [
            ['div', ['outer-div', 'li-div', 'foobar-div']],
            ['DIV', ['outer-div', 'li-div', 'foobar-div']],  // case-insensitive in HTML
            ['div div', ['li-div']],
            ['div, div div', ['outer-div', 'li-div', 'foobar-div']],

Review comment (suggestion):
we should also have tests covering more complex selectors in `:has()`, as well as relative selectors: `div:has(.foo .bar)`, `div:has(> .foo)`, `div:has(~ foo)`

Code after:
            ['e|f', 'e:f'],
            ['e[foo]', 'e[@foo]'],
            ['e[foo|bar]', 'e[@foo:bar]'],
            ['e[foo="bar"]', "e[@foo = 'bar']"],
            ['e[foo~="bar"]', "e[@foo and contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]"],
            ['e[foo^="bar"]', "e[@foo and starts-with(@foo, 'bar')]"],
            ['e[foo$="bar"]', "e[@foo and substring(@foo, string-length(@foo)-2) = 'bar']"],
            ['e[foo*="bar"]', "e[@foo and contains(@foo, 'bar')]"],
            ['e[foo!="bar"]', "e[not(@foo) or @foo != 'bar']"],
            ['e[foo!="bar"][foo!="baz"]', "e[(not(@foo) or @foo != 'bar') and (not(@foo) or @foo != 'baz')]"],
            ['e[hreflang|="en"]', "e[@hreflang and (@hreflang = 'en' or starts-with(@hreflang, 'en-'))]"],
            ['e:nth-child(1)', "*/*[(name() = 'e') and (position() = 1)]"],
            ['e:nth-last-child(1)', "*/*[(name() = 'e') and (position() = last() - 0)]"],
            ['e:nth-last-child(2n+2)', "*/*[(name() = 'e') and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]"],
            ['e:nth-of-type(1)', '*/e[position() = 1]'],
            ['e:nth-last-of-type(1)', '*/e[position() = last() - 0]'],
            ['div e:nth-last-of-type(1) .aclass', "div/descendant-or-self::*/e[position() = last() - 0]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]"],
            ['e:first-child', "*/*[(name() = 'e') and (position() = 1)]"],
            ['e:last-child', "*/*[(name() = 'e') and (position() = last())]"],
            ['e:first-of-type', '*/e[position() = 1]'],
            ['e:last-of-type', '*/e[position() = last()]'],
            ['e:only-child', "*/*[(name() = 'e') and (last() = 1)]"],
            ['e:only-of-type', 'e[count(preceding-sibling::e)=0 and count(following-sibling::e)=0]'],
            ['e:empty', 'e[not(*) and not(string-length())]'],
            ['e:EmPTY', 'e[not(*) and not(string-length())]'],
            ['e:root', 'e[not(parent::*)]'],
            ['e:hover', 'e[0]'],
            ['e:contains("foo")', "e[contains(string(.), 'foo')]"],
            ['e:ConTains(foo)', "e[contains(string(.), 'foo')]"],
            ['e.warning', "e[@class and contains(concat(' ', normalize-space(@class), ' '), ' warning ')]"],
            ['e#myid', "e[@id = 'myid']"],
            ['e:not(:nth-child(odd))', 'e[not(position() - 1 >= 0 and (position() - 1) mod 2 = 0)]'],
            ['e:nOT(*)', 'e[0]'],
            ['e f', 'e/descendant-or-self::*/f'],
            ['e > f', 'e/f'],
            ['e + f', "e/following-sibling::*[(name() = 'f') and (position() = 1)]"],
            ['e ~ f', 'e/following-sibling::f'],
            ['div#container p', "div[@id = 'container']/descendant-or-self::*/p"],
            [':scope > div[dataimg="<testmessage>"]', "*[1]/div[@dataimg = '<testmessage>']"],
            [':scope', '*[1]'],
            ['e:is(section, article) h1', "e[(name() = 'section') or (name() = 'article')]/descendant-or-self::*/h1"],
            ['e:where(section, article) h1', "e[(name() = 'section') or (name() = 'article')]/descendant-or-self::*/h1"],
            ['div:has(> .foo)', "div[./*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]]"],
            ['div:has(~ .foo)', "div[following-sibling::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]]"],
            ['div:has(+ .foo)', "div[following-sibling::*[(@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')) and (position() = 1)]]"],
            ['div:has(.foo)', "div[descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]]"],
            ['div:has(#bar)', "div[descendant-or-self::*[@id = 'bar']]"],
        ];
    }

    public static function getXmlLangTestData()