blob: 4eebb9f10afea9dd31290b7d21ca664c0f3ba7b4 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.styles
{
import mx.core.mx_internal;
/**
* Represents a selector node in a potential chain of selectors used to match
* CSS style declarations to components.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public class CSSSelector
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param subject The plain representation of this selector without
* conditions or ancestors. This is typically a fully-qualified class name; for example,
* "spark.components.Button". You can use "*" to match all components or "global" for a global selector.
*
* @param conditions An optional Array of objects of type CSSCondition that is used to match a
* subset of component instances. Currently only a single or a pair of
* conditions are supported.
*
* @param ancestor An optional selector to match on a component that
* descends from an arbitrary ancestor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function CSSSelector(subject:String,
conditions:Array=null, ancestor:CSSSelector=null)
{
_subject = subject;
_conditions = conditions;
_ancestor = ancestor;
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// ancestor
//----------------------------------
/**
* @private
*/
private var _ancestor:CSSSelector;
/**
* If this selector is part of a descendant selector it may have a further
* selector defined for an arbitrary ancestor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get ancestor():CSSSelector
{
return _ancestor;
}
//----------------------------------
// conditions
//----------------------------------
/**
* @private
*/
private var _conditions:Array; // of CSSCondition
/**
* This selector may match a subset of components by specifying further
* conditions (for example, a matching component must have a particular id,
* styleName (equivalent to a 'class' condition in CSS) or state
* (equivalent to a 'pseudo' condition in CSS)).
*
* <p>If no conditions are specified, this property is null.</p>
*
* @return Array of CSSCondition specified for this selector.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get conditions():Array // of CSSCondition
{
return _conditions;
}
//----------------------------------
// specificity
//----------------------------------
/**
* Calculates the specificity of a selector chain in order to determine
* the precedence when applying several matching style declarations. Note
* that id conditions contribute 100 points, pseudo and class conditions
* each contribute 10 points, types (including descendants in a chain of
* selectors) contribute 1 point. Universal selectors ("*") contribute
* nothing. The result is the sum of these contributions. Selectors with a
* higher specificity override selectors of lower specificity. If
* selectors have equal specificity, the declaration order determines
* the precedence (the last one wins).
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get specificity():int
{
var s:int = 0;
if ("*" != subject && "global" != subject && "" != subject)
s = 1;
if (conditions != null)
{
for each (var condition:CSSCondition in conditions)
{
s += condition.specificity;
}
}
if (ancestor != null)
s += ancestor.specificity;
return s;
}
//----------------------------------
// subject
//----------------------------------
/**
* @private
*/
private var _subject:String;
/**
* The subject of this selector node (only). To get a String representation
* of all conditions and descendants of this selector call the <code>toString()</code>
* method.
*
* <p>If this selector represents the root node of a potential chain of
* selectors, the subject also represents the subject of the entire selector
* expression.</p>
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function get subject():String
{
return _subject;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Determines whether this selector matches the given component.
*
* @param object The component to which the selector may apply.
* @return true if component is a match, or false if not.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function matchesStyleClient(object:IAdvancedStyleClient):Boolean
{
var match:Boolean = false;
var condition:CSSCondition = null;
// If we have an ancestor then this is part of a descendant selector
if (ancestor)
{
if (conditions)
{
// First, test if the conditions match
for each (condition in conditions)
{
match = condition.matchesStyleClient(object);
if (!match)
return false;
}
}
// Then reset and test if any ancestor matches
match = false;
var parent:IAdvancedStyleClient = object.styleParent;
while (parent != null)
{
if (parent.matchesCSSType(ancestor.subject)
|| "*" == ancestor.subject)
{
match = ancestor.matchesStyleClient(parent);
if (match)
break;
}
parent = parent.styleParent;
}
}
else
{
// Check the type selector matches
if (subject == "*" || subject == "" || object.matchesCSSType(subject))
{
match = true;
}
// Then check if any conditions match
if (match && conditions != null)
{
for each (condition in conditions)
{
match = condition.matchesStyleClient(object);
if (!match)
return false;
}
}
}
return match;
}
/**
* @private
*/
mx_internal function getPseudoCondition():String
{
var result:String = null;
if (conditions)
{
for each (var condition:CSSCondition in conditions)
{
if (condition.kind == CSSConditionKind.PSEUDO)
{
result = condition.value;
break;
}
}
}
return result;
}
/**
* Returns a String representation of this selector.
*
* @return A String representation of this selector including all of its
* syntax, conditions and ancestors.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function toString():String
{
var s:String;
if (ancestor != null)
{
s = ancestor.toString() + " " + subject;
}
else
{
s = subject;
}
if (conditions != null)
{
for each (var condition:CSSCondition in conditions)
{
s += condition.toString();
}
}
return s;
}
}
}