@@ -345,16 +345,42 @@ def position_dependency(predicate_expr)
345345 # [number], [position()=number], [position() < number], [position() > number]
346346 return :simple if position_operation ( predicate_expr )
347347
348- # expressions that return number.
349- return :complex if %i[ div mod mult plus minus neg ] . include? ( predicate_expr [ 0 ] )
350- return :complex if predicate_expr [ 0 ] == :function && %w[ number ceiling round floor string-length sum count ] . include? ( predicate_expr [ 1 ] )
351- # Numeric literal including Integer and Float: [2] means [position() = 2]
352- return :complex if predicate_expr [ 0 ] == :literal && Numeric === predicate_expr [ 1 ]
353- # A variable could resolve to a number at runtime: [$n] means [position() = $n].
354- return :complex if predicate_expr [ 0 ] == :variable
355-
356348 # expressions that contain position-dependent functions
357349 return :complex if calls_position_dependent_function? ( predicate_expr )
350+
351+ # Even if expression is followed by path steps, the analysis of
352+ # position dependency is the same as the expression itself.
353+ case predicate_expr [ 0 ]
354+ when :union , :or , :and , :eq , :neq , :lt , :lteq , :gt , :gteq , :not
355+ # Expressions that doesn't evaluate to number is position independent
356+ # if it doesn't contain position-dependent functions.
357+ nil
358+ when :div , :mod , :mult , :plus , :minus , :neg
359+ # expressions that return number. eg. `[position() = @attr + 1]`
360+ :complex
361+ when :literal
362+ # Integer literal is handled in `position_operation(predicate_expr)`.
363+ # We treat this as complex(no-optimization) because this is not a normal case
364+ # eg. `/foo["hi"]` `/bar[true]` `/baz[3.14]`
365+ :complex
366+ when :variable
367+ # A variable could resolve to a number at runtime.
368+ # It's possible to optimize this by checking the actual value of the variable.
369+ :complex
370+ when :function
371+ # functions that return number is position dependent. eg. `[position() = string-length(@attr)]`
372+ %w[ number ceiling round floor string-length sum count ] . include? ( predicate_expr [ 1 ] ) ? :complex : nil
373+ when :group
374+ position_dependency ( predicate_expr [ 1 ] )
375+ when :descendant , :descendant_or_self , :ancestor , :ancestor_or_self ,
376+ :following , :following_sibling , :preceding , :preceding_sibling ,
377+ :document , :child , :self , :parent , :attribute , :namespace
378+ # paths are position independent. `foo[path[1]]` doesn't depend on the position of `foo`
379+ nil
380+ else
381+ # Every other unhandled expressions are treated as complex for safety
382+ :complex
383+ end
358384 end
359385
360386 # Recursively checks if the expression contains position-dependent functions such as position() or last()
@@ -514,15 +540,14 @@ def non_optimized_nodesets_select(nodesets, tester, selector)
514540 seen . to_a
515541 else
516542 operator , value = selector
517- nodes = nodesets . flatten
518543 nodes =
519544 case operator
520545 when :==
521546 nodesets . map { |nodeset | nodeset [ value - 1 ] if value >= 1 } . compact
522547 when :<
523548 nodesets . map { |nodeset | nodeset [ 0 ...value - 1 ] if value >= 1 } . compact . flatten
524549 when :>
525- nodesets . map { |nodeset | value < 0 ? nodeset : nodeset [ value ..- 1 ] } . flatten
550+ nodesets . map { |nodeset | value < 0 ? nodeset : nodeset . drop ( value ) } . flatten
526551 end
527552 seen = Set . new . compare_by_identity
528553 nodes . each { |node | seen << node }
0 commit comments