2010-02-22 21:56:57 -08:00
|
|
|
module Sass
|
|
|
|
# A namespace for nodes in the parse tree for selectors.
|
|
|
|
module Selector
|
2010-02-23 22:37:50 -08:00
|
|
|
# The abstract superclass for simple selectors
|
|
|
|
# (that is, those that don't compose multiple selectors).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# Returns a representation of the node
|
|
|
|
# as an array of strings and potentially {Sass::Script::Node}s
|
|
|
|
# (if there's interpolation in the selector).
|
|
|
|
# When the interpolation is resolved and the strings are joined together,
|
|
|
|
# this will be the string representation of this node.
|
|
|
|
#
|
|
|
|
# @return [Array<String, Sass::Script::Node>]
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
2010-02-23 22:37:50 -08:00
|
|
|
raise NotImplementedError.new("All subclasses of Sass::Selector::Node must override #to_a.")
|
2010-02-22 21:56:57 -08:00
|
|
|
end
|
2010-02-23 23:52:11 -08:00
|
|
|
|
|
|
|
# Returns a string representation of the node.
|
|
|
|
# This is basically the selector string.
|
|
|
|
#
|
|
|
|
# @return [String]
|
|
|
|
def inspect
|
|
|
|
to_a.map {|e| e.is_a?(Sass::Script::Node) ? "\#{#{e.to_sass}}" : e}.join
|
|
|
|
end
|
2010-02-22 21:56:57 -08:00
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# A comma-separated sequence of selectors.
|
|
|
|
class CommaSequence
|
|
|
|
# The comma-separated selector sequences
|
|
|
|
# represented by this class.
|
|
|
|
#
|
|
|
|
# @return [Array<Sequence>]
|
|
|
|
attr_reader :members
|
|
|
|
|
|
|
|
# @param seqs [Array<Sequence>] See \{#members}
|
|
|
|
def initialize(seqs)
|
|
|
|
@members = seqs
|
|
|
|
end
|
|
|
|
|
|
|
|
# Resolves the {Parent} selectors within this selector
|
|
|
|
# by replacing them with the given parent selector,
|
|
|
|
# handling commas appropriately.
|
|
|
|
#
|
|
|
|
# @param super_cseq [CommaSequence] The parent selector
|
|
|
|
# @return [CommaSequence] This selector, with parent references resolved
|
|
|
|
# @raise [Sass::SyntaxError] If a parent selector is invalid
|
|
|
|
def resolve_parent_refs(super_cseq)
|
|
|
|
if super_cseq.nil?
|
|
|
|
if @members.any? do |sel|
|
|
|
|
sel.members.any? do |sel_or_op|
|
|
|
|
sel_or_op.is_a?(SimpleSequence) && sel_or_op.members.any? {|ssel| ssel.is_a?(Parent)}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '&'.")
|
|
|
|
end
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
|
|
|
CommaSequence.new(
|
|
|
|
super_cseq.members.map do |super_seq|
|
|
|
|
@members.map {|seq| seq.resolve_parent_refs(super_seq)}
|
|
|
|
end.flatten)
|
|
|
|
end
|
2010-02-23 23:52:11 -08:00
|
|
|
|
|
|
|
# Returns a string representation of the sequence.
|
|
|
|
# This is basically the selector string.
|
|
|
|
#
|
|
|
|
# @return [String]
|
|
|
|
def inspect
|
|
|
|
members.map {|m| m.inspect}.join(", ")
|
|
|
|
end
|
2010-02-23 22:37:50 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
# An operator-separated sequence of
|
|
|
|
# {SimpleSequence simple selector sequences}.
|
|
|
|
class Sequence
|
|
|
|
# The array of {SimpleSequence simple selector sequences}, operators, and newlines.
|
|
|
|
# The operators are strings such as `"+"` and `">"`
|
|
|
|
# representing the corresponding CSS operators.
|
|
|
|
# Newlines are also newline strings;
|
|
|
|
# these aren't semantically relevant,
|
|
|
|
# but they do affect formatting.
|
|
|
|
#
|
|
|
|
# @return [Array<SimpleSequence, String>]
|
|
|
|
attr_reader :members
|
|
|
|
|
|
|
|
# @param seqs_and_ops [Array<SimpleSequence, String>] See \{#members}
|
|
|
|
def initialize(seqs_and_ops)
|
|
|
|
@members = seqs_and_ops
|
|
|
|
end
|
|
|
|
|
|
|
|
# Resolves the {Parent} selectors within this selector
|
|
|
|
# by replacing them with the given parent selector,
|
|
|
|
# handling commas appropriately.
|
|
|
|
#
|
|
|
|
# @param super_seq [Sequence] The parent selector sequence
|
|
|
|
# @return [Sequence] This selector, with parent references resolved
|
|
|
|
# @raise [Sass::SyntaxError] If a parent selector is invalid
|
|
|
|
def resolve_parent_refs(super_seq)
|
|
|
|
members = @members
|
|
|
|
members.slice!(0) if nl = (members.first == "\n")
|
|
|
|
unless members.any? do |seq_or_op|
|
|
|
|
seq_or_op.is_a?(SimpleSequence) && seq_or_op.members.first.is_a?(Parent)
|
|
|
|
end
|
|
|
|
members = []
|
|
|
|
members << "\n" if nl
|
|
|
|
members << SimpleSequence.new([Parent.new])
|
|
|
|
members += @members
|
|
|
|
end
|
|
|
|
|
|
|
|
Sequence.new(
|
|
|
|
members.map do |seq_or_op|
|
|
|
|
next seq_or_op unless seq_or_op.is_a?(SimpleSequence)
|
|
|
|
seq_or_op.resolve_parent_refs(super_seq)
|
|
|
|
end.flatten)
|
|
|
|
end
|
|
|
|
|
|
|
|
# @see Node#to_a
|
|
|
|
def to_a
|
|
|
|
ary = @members.map {|seq_or_op| seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op}
|
|
|
|
ary = Haml::Util.intersperse(ary, " ")
|
|
|
|
ary = Haml::Util.substitute(ary, [" ", "\n", " "], ["\n"])
|
|
|
|
ary.flatten.compact
|
|
|
|
end
|
2010-02-23 23:52:11 -08:00
|
|
|
|
|
|
|
# Returns a string representation of the sequence.
|
|
|
|
# This is basically the selector string.
|
|
|
|
#
|
|
|
|
# @return [String]
|
|
|
|
def inspect
|
|
|
|
members.map {|m| m.inspect}.join(" ")
|
|
|
|
end
|
2010-02-23 22:37:50 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
# A unseparated sequence of selectors
|
|
|
|
# that all apply to a single element.
|
|
|
|
# For example, `.foo#bar[attr=baz]` is a simple sequence
|
|
|
|
# of the selectors `.foo`, `#bar`, and `[attr=baz]`.
|
|
|
|
class SimpleSequence
|
|
|
|
# The array of individual selectors.
|
|
|
|
#
|
|
|
|
# @return [Array<Node>]
|
|
|
|
attr_reader :members
|
|
|
|
|
|
|
|
# @param selectors [Array<Node>] See \{#members}
|
|
|
|
def initialize(selectors)
|
|
|
|
@members = selectors
|
|
|
|
end
|
|
|
|
|
|
|
|
# Resolves the {Parent} selectors within this selector
|
|
|
|
# by replacing them with the given parent selector,
|
|
|
|
# handling commas appropriately.
|
|
|
|
#
|
|
|
|
# @param super_seq [Sequence] The parent selector sequence
|
|
|
|
# @return [Array<SimpleSequence>] This selector, with parent references resolved.
|
|
|
|
# This is an array because the parent selector is itself a {Sequence}
|
|
|
|
# @raise [Sass::SyntaxError] If a parent selector is invalid
|
|
|
|
def resolve_parent_refs(super_seq)
|
|
|
|
# Parent selector only appears as the first selector in the sequence
|
|
|
|
return [self] unless @members.first.is_a?(Parent)
|
|
|
|
|
|
|
|
return super_seq.members if @members.size == 1
|
|
|
|
unless super_seq.members.last.is_a?(SimpleSequence)
|
|
|
|
raise Sass::SyntaxError.new("Invalid parent selector: " + super_seq.to_a.join)
|
|
|
|
end
|
|
|
|
|
|
|
|
super_seq.members[0...-1] +
|
|
|
|
[SimpleSequence.new(super_seq.members.last.members + @members[1..-1])]
|
|
|
|
end
|
|
|
|
|
|
|
|
# @see Node#to_a
|
|
|
|
def to_a
|
|
|
|
@members.map {|sel| sel.to_a}.flatten
|
|
|
|
end
|
2010-02-23 23:52:11 -08:00
|
|
|
|
|
|
|
# Returns a string representation of the sequence.
|
|
|
|
# This is basically the selector string.
|
|
|
|
#
|
|
|
|
# @return [String]
|
|
|
|
def inspect
|
|
|
|
members.map {|m| m.inspect}.join
|
|
|
|
end
|
2010-02-23 22:37:50 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
# A parent-referencing selector (`&` in Sass).
|
|
|
|
# The function of this is to be replaced by the parent selector
|
|
|
|
# in the nested hierarchy.
|
2010-02-22 21:56:57 -08:00
|
|
|
class Parent < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
["&"]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# A class selector (e.g. `.foo`).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Class < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @param name [String] The class name
|
2010-02-22 21:56:57 -08:00
|
|
|
def initialize(name)
|
|
|
|
@name = name
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
[".", @name]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# An id selector (e.g. `#foo`).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Id < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @param name [String] The id name
|
2010-02-22 21:56:57 -08:00
|
|
|
def initialize(name)
|
|
|
|
@name = name
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
["#", @name]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# A universal selector (`*` in CSS).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Universal < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @param namespace [String, nil] The namespace of the universal selector.
|
|
|
|
# `nil` means the default namespace, `""` means no namespace
|
2010-02-22 21:56:57 -08:00
|
|
|
def initialize(namespace)
|
|
|
|
@namespace = namespace
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
@namespace ? [@namespace, "|*"] : ["*"]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# An element selector (e.g. `h1`).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Element < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @param name [String] The element name
|
|
|
|
# @param namespace [String, nil] The namespace of the universal selector.
|
|
|
|
# `nil` means the default namespace, `""` means no namespace
|
2010-02-22 21:56:57 -08:00
|
|
|
def initialize(name, namespace)
|
|
|
|
@name = name
|
|
|
|
@namespace = namespace
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
@namespace ? [@namespace, "|", @name] : [@name]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# Selector interpolation (`#{}` in Sass).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Interpolation < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @param script [Sass::Script::Node] The script to run
|
2010-02-22 21:56:57 -08:00
|
|
|
def initialize(script)
|
|
|
|
@script = script
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
[@script]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# An attribute selector (e.g. `[href^="http://"]`).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Attribute < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @param name [String] The attribute name
|
|
|
|
# @param namespace [String, nil] The namespace of the universal selector.
|
|
|
|
# `nil` means the default namespace, `""` means no namespace
|
|
|
|
# @param operator [String] The matching operator, e.g. `"="` or `"^="`
|
|
|
|
# @param value [Array<String, Sass::Script::Node>] The left-hand side of the operator
|
2010-02-22 21:56:57 -08:00
|
|
|
def initialize(name, namespace, operator, value)
|
|
|
|
@name = name
|
|
|
|
@namespace = namespace
|
|
|
|
@operator = operator
|
|
|
|
@value = value
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
res = ["["]
|
|
|
|
res << @namespace << "|" if @namespace
|
|
|
|
res << @name
|
2010-02-23 22:37:50 -08:00
|
|
|
(res << @operator).concat @value if @value
|
2010-02-22 21:56:57 -08:00
|
|
|
res << "]"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# A pseudoclass (e.g. `:visited`) or pseudoelement (e.g. `::first-line`) selector.
|
|
|
|
# It can have arguments (e.g. `:nth-child(2n+1)`).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Pseudo < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @param type [Symbol] `:class` if this is a pseudoclass,
|
|
|
|
# `:element` if this is a pseudoelement
|
|
|
|
# @param name [String] The name of the selector
|
|
|
|
# @param arg [nil, Array<String, Sass::Script::Node>] The argument to the selector,
|
|
|
|
# or nil if no argument was given
|
2010-02-22 21:56:57 -08:00
|
|
|
def initialize(type, name, arg)
|
|
|
|
@type = type
|
|
|
|
@name = name
|
|
|
|
@arg = arg
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
res = [@type == :class ? ":" : "::", @name]
|
2010-02-23 22:37:50 -08:00
|
|
|
(res << "(").concat(@arg) << ")" if @arg
|
2010-02-22 21:56:57 -08:00
|
|
|
res
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# A negation pseudoclass selector (e.g. `:not(.foo)`).
|
2010-02-22 21:56:57 -08:00
|
|
|
class Negation < Node
|
2010-02-23 22:37:50 -08:00
|
|
|
# @param [Node] The selector to negate
|
2010-02-22 21:56:57 -08:00
|
|
|
def initialize(selector)
|
|
|
|
@selector = selector
|
|
|
|
end
|
|
|
|
|
2010-02-23 22:37:50 -08:00
|
|
|
# @see Node#to_a
|
2010-02-22 21:56:57 -08:00
|
|
|
def to_a
|
|
|
|
[":not("] + @selector.to_a + [")"]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|