From 6792127600c9e0a25585c10a3a71abb4803ceb99 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 7 Oct 2014 22:47:47 +0200 Subject: [PATCH] Reworked CSS parser rules. This includes better rules for parsing separate path members, pseudo class arguments and some changes to remove all remaining parsing conflicts. --- lib/oga/css/parser.y | 57 +++++++++++++--------- spec/oga/css/parser/axes_spec.rb | 45 +++++++++++++++++ spec/oga/css/parser/pseudo_classes_spec.rb | 8 +++ 3 files changed, 86 insertions(+), 24 deletions(-) diff --git a/lib/oga/css/parser.y b/lib/oga/css/parser.y index 71c4507..c612752 100644 --- a/lib/oga/css/parser.y +++ b/lib/oga/css/parser.y @@ -3,30 +3,44 @@ # class Oga::CSS::Parser +token T_IDENT T_PIPE T_LBRACK T_RBRACK T_COLON T_SPACE T_LPAREN T_RPAREN T_MINUS +token T_EQ T_SPACE_IN T_STARTS_WITH T_ENDS_WITH T_IN T_HYPHEN_IN +token T_CHILD T_FOLLOWING T_FOLLOWING_DIRECT +token T_NTH T_INT T_STRING T_ODD T_EVEN + options no_result_var +prechigh + left T_COLON + left T_CHILD T_FOLLOWING T_FOLLOWING_DIRECT +preclow + rule css - : path { val[0] } + : expression { val[0] } | /* none */ { nil } ; + expression + : path + | path_member + ; + + path + : path_members { s(:path, *val[0]) } + ; + + path_members + : path_member T_SPACE path_member { [val[0], val[2]] } + | path_member T_SPACE path_members { [val[0], *val[2]] } + ; + path_member : node_test | axis | pseudo_class ; - path_members - : path_member path_member { [val[0], val[1]] } - | path_member path_members { [val[0], *val[1]] } - ; - - path - : path_members { s(:path, *val[0]) } - | path_member - ; - node_test : node_name { s(:test, *val[0]) } | node_name predicate { s(:test, *val[0], val[1]) } @@ -68,19 +82,19 @@ rule axis # x > y - : node_test T_CHILD node_test + : path_member T_CHILD path_member { s(:axis, 'child', val[0], val[2]) } # x + y - | node_test T_FOLLOWING node_test + | path_member T_FOLLOWING path_member { s(:axis, 'following', val[0], val[2]) } # x ~ y - | node_test T_FOLLOWING_DIRECT node_test + | path_member T_FOLLOWING_DIRECT path_member { s(:axis, 'following-direct', val[0], val[2]) } @@ -88,20 +102,15 @@ rule pseudo_class # x:root - : node_test T_COLON T_IDENT { s(:pseudo, val[2], val[0]) } + : path_member T_COLON T_IDENT { s(:pseudo, val[2], val[0]) } # x:nth-child(2) - | node_test T_COLON T_IDENT T_LPAREN pseudo_args T_RPAREN + | path_member T_COLON T_IDENT T_LPAREN pseudo_arg T_RPAREN { - s(:pseudo, val[2], val[0], *val[4]) + s(:pseudo, val[2], val[0], val[4]) } ; - pseudo_args - : pseudo_args pseudo_arg { val[0] << val[1] } - | pseudo_arg { val } - ; - pseudo_arg : integer | odd @@ -129,8 +138,8 @@ rule ; integer - : T_INT { s(:int, val[0].to_i) } - ; + : T_INT { s(:int, val[0].to_i) } + ; end ---- inner diff --git a/spec/oga/css/parser/axes_spec.rb b/spec/oga/css/parser/axes_spec.rb index c13ef71..22022a8 100644 --- a/spec/oga/css/parser/axes_spec.rb +++ b/spec/oga/css/parser/axes_spec.rb @@ -11,6 +11,15 @@ describe Oga::CSS::Parser do ) end + example 'parse the > axis called on another > axis' do + parse_css('a > b > c').should == s( + :axis, + 'child', + s(:axis, 'child', s(:test, nil, 'a'), s(:test, nil, 'b')), + s(:test, nil, 'c') + ) + end + example 'parse the + axis' do parse_css('x + y').should == s( :axis, @@ -20,6 +29,15 @@ describe Oga::CSS::Parser do ) end + example 'parse the + axis called on another + axis' do + parse_css('a + b + c').should == s( + :axis, + 'following-direct', + s(:axis, 'following-direct', s(:test, nil, 'a'), s(:test, nil, 'b')), + s(:test, nil, 'c') + ) + end + example 'parse the ~ axis' do parse_css('x ~ y').should == s( :axis, @@ -36,5 +54,32 @@ describe Oga::CSS::Parser do s(:test, nil, 'z') ) end + + example 'parse the ~ axis called on another ~ axis' do + parse_css('a ~ b ~ c').should == s( + :axis, + 'following', + s(:axis, 'following', s(:test, nil, 'a'), s(:test, nil, 'b')), + s(:test, nil, 'c') + ) + end + + example 'parse a pseudo class followed by the ~ axis' do + parse_css('x:root ~ a').should == s( + :axis, + 'following', + s(:pseudo, 'root', s(:test, nil, 'x')), + s(:test, nil, 'a') + ) + end + + example 'parse the ~ axis followed by a pseudo class' do + parse_css('a ~ x:root').should == s( + :axis, + 'following', + s(:test, nil, 'a'), + s(:pseudo, 'root', s(:test, nil, 'x')) + ) + end end end diff --git a/spec/oga/css/parser/pseudo_classes_spec.rb b/spec/oga/css/parser/pseudo_classes_spec.rb index 702f332..de90e4c 100644 --- a/spec/oga/css/parser/pseudo_classes_spec.rb +++ b/spec/oga/css/parser/pseudo_classes_spec.rb @@ -86,5 +86,13 @@ describe Oga::CSS::Parser do s(:nth, s(:int, -2), s(:int, -1)) ) end + + example 'parse two pseudo selectors' do + parse_css('x:focus:hover').should == s( + :pseudo, + 'hover', + s(:pseudo, 'focus', s(:test, nil, 'x')) + ) + end end end