diff --git a/lib/oga/css/parser.y b/lib/oga/css/parser.y index b7504e2..bdfc1f1 100644 --- a/lib/oga/css/parser.y +++ b/lib/oga/css/parser.y @@ -138,8 +138,76 @@ rule : node_name { s(:axis, 'attribute', s(:test, *val[0])) } ; + # The AST of these operators is mostly based on what + # `Nokogiri::CSS.xpath_for('...')` returns. operator - : attribute T_EQ string { s(:eq, val[0], val[2]) } + # a="b" + : attribute T_EQ string + { + s(:eq, val[0], val[2]) + } + + # a~="b" + | attribute T_SPACE_IN string + { + s( + :call, + 'contains', + s(:call, 'concat', s(:string, ' '), val[0], s(:string, ' ')), + s(:call, 'concat', s(:string, ' '), val[2], s(:string, ' ')) + ) + } + + # a^="b" + | attribute T_STARTS_WITH string + { + s(:call, 'starts-with', val[0], val[2]) + } + + # a$="b" + | attribute T_ENDS_WITH string + { + s( + :eq, + s( + :call, + 'substring', + val[0], + s( + :add, + s( + :sub, + s(:call, 'string-length', val[0]), + s(:call, 'string-length', val[2]) + ), + s(:int, 1) + ), + s(:call, 'string-length', val[2]) + ), + val[2] + ) + } + + # a*="b" + | attribute T_IN string + { + s(:call, 'contains', val[0], val[2]) + } + + # a|="b" + | attribute T_HYPHEN_IN string + { + s( + :or, + s(:eq, val[0], val[2]), + s( + :call, + 'starts-with', + val[0], + s(:call, 'concat', val[2], s(:string, '-')) + ) + ) + } ; class diff --git a/spec/oga/css/parser/operators_spec.rb b/spec/oga/css/parser/operators_spec.rb index 8f19344..514e78f 100644 --- a/spec/oga/css/parser/operators_spec.rb +++ b/spec/oga/css/parser/operators_spec.rb @@ -3,56 +3,40 @@ require 'spec_helper' describe Oga::CSS::Parser do context 'operators' do example 'parse the = operator' do - parse_css('x[a="b"]').should == s( - :test, - nil, - 'x', - s(:eq, s(:test, nil, 'a'), s(:string, 'b')) + parse_css('x[a="b"]').should == parse_xpath( + 'descendant-or-self::x[@a="b"]' ) end example 'parse the ~= operator' do - parse_css('x[a~="b"]').should == s( - :test, - nil, - 'x', - s(:space_in, s(:test, nil, 'a'), s(:string, 'b')) + parse_css('x[a~="b"]').should == parse_xpath( + 'descendant-or-self::x[contains(concat(" ", @a, " "), ' \ + 'concat(" ", "b", " "))]' ) end example 'parse the ^= operator' do - parse_css('x[a^="b"]').should == s( - :test, - nil, - 'x', - s(:starts_with, s(:test, nil, 'a'), s(:string, 'b')) + parse_css('x[a^="b"]').should == parse_xpath( + 'descendant-or-self::x[starts-with(@a, "b")]' ) end example 'parse the $= operator' do - parse_css('x[a$="b"]').should == s( - :test, - nil, - 'x', - s(:ends_with, s(:test, nil, 'a'), s(:string, 'b')) + parse_css('x[a$="b"]').should == parse_xpath( + 'descendant-or-self::x[substring(@a, string-length(@a) - ' \ + 'string-length("b") + 1, string-length("b")) = "b"]' ) end example 'parse the *= operator' do - parse_css('x[a*="b"]').should == s( - :test, - nil, - 'x', - s(:in, s(:test, nil, 'a'), s(:string, 'b')) + parse_css('x[a*="b"]').should == parse_xpath( + 'descendant-or-self::x[contains(@a, "b")]' ) end example 'parse the |= operator' do - parse_css('x[a|="b"]').should == s( - :test, - nil, - 'x', - s(:hyphen_in, s(:test, nil, 'a'), s(:string, 'b')) + parse_css('x[a|="b"]').should == parse_xpath( + 'descendant-or-self::x[@a = "b" or starts-with(@a, concat("b", "-"))]' ) end end