org.apache.myfaces.trinidadinternal.style.util.CSSGenerationUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of trinidad-impl Show documentation
Show all versions of trinidad-impl Show documentation
Private implementation of the Apache MyFaces Trinidad project
/*
* 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 org.apache.myfaces.trinidadinternal.style.util;
import java.beans.Beans;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.SkinSelectors;
import org.apache.myfaces.trinidadinternal.style.StyleContext;
import org.apache.myfaces.trinidadinternal.style.xml.parse.PropertyNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleNode;
/**
* CSS-generation related utilities used when we write out our css-2 stylesheet
* document based on the skin's css-3 stylesheet document.
*
* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/style/util/CSSUtils.java#0 $) $Date: 10-nov-2005.18:58:49 $
*/
public class CSSGenerationUtils
{
/**
* Returns a MatchingStyles object of normalized propertyString to the StyleNodes that have them as properties.
* MatchingStyles segregates the clientRules from the normal selectors for ease of css rendering.
* MatchingStyles object uses LinkedHashMap within, to indicate that relative ordering is preserved between the first
* appearance of selectors with unshared properties.
* However, a later style rule that shares its properties with a much earlier style rule will be defined with that
* style rule, thus having its definition moved up in the file. Therefore, customers should rely on
* specificity rather than ordering to override styles.
* defined with
* @param styleNodes
* @return
*/
private static MatchingStyles _buildMatchingStylesMap(List styleNodes)
{
int styleCount = styleNodes.size();
MatchingStyles matchingStyles = new MatchingStyles(styleCount);
// at this point the styles List can contain both Styles with
// non-null selector or non-null name(aka alias). We only generate
// the styles where getSelector is non-null.
for (StyleNode styleNode : styleNodes)
{
if (styleNode.getSelector() != null)
{
// Get the property string (properties are sorted so that
// the order doesn't affect whether styles match).
String propertyString = _getSortedPropertyString(styleNode);
if (!("".equals(propertyString)))
{
if (styleNode.hasClientRule())
{
matchingStyles.addStyle(styleNode.getClientRule(), propertyString, styleNode);
}
else
{
matchingStyles.addStyle(propertyString, styleNode);
}
}
}
}
return matchingStyles;
}
/**
* Writes the properties of a merged set of style rules
* @param out
* @param properties
* @param styleSheetName
* @param compressStyles
* @param baseURI
* @return
*/
private static void _writeMergedProperties(
PrintWriter out, Iterable properties, String styleSheetName, boolean compressStyles, String baseURI)
{
out.print(" {");
boolean first = true;
for (PropertyNode property : properties)
{
String propName = property.getName();
String propValue = property.getValue();
if ((propName != null) &&
(propValue != null) &&
(propValue.length() > 0 ))
{
// insert separator before all except the first property
if (first)
first = false;
else
out.print(';');
out.print(propName);
out.print(':');
String resolvedPropValue = CSSUtils.resolvePropertyValue(styleSheetName, baseURI, propName, propValue);
out.print(resolvedPropValue);
}
}
out.print('}');
// take out the newlines for performance
if (!compressStyles)
{
out.println();
}
}
/**
* Writes out a List of valid selectors
* @param out
* @param validSelectors
*/
private static void _writeValidSelectors(PrintWriter out, List validSelectors)
{
boolean first = true;
// Write out all of the style selectors for this property string
for (String validSelector : validSelectors)
{
// insert separator before all except the first property
if (first)
first = false;
else
out.print(',');
out.print(validSelector);
}
}
/**
* Given a List of mappedSelectors, returns a List of valid selectors to write
* @param compressStyles
* @param shortStyleClassMap
* @param namespacePrefixArray
* @param mappedSelectors
* @return
*/
private static List _calculateValidSelectors(
boolean compressStyles, Map shortStyleClassMap,
String[] namespacePrefixArray, List mappedSelectors)
{
List validSelectors = new ArrayList(mappedSelectors.size() * 2);
// TODO: figure out why we write both the uncompressed & compressed styles for styles
// without a '|' character, shouldn't the uncompressed be enough on its own? This results
// in some ugly code here.
// Write out all of the style selectors for this property string
for (String mappedSelector : mappedSelectors)
{
String validFullNameSelector = null;
// write out the full selector if we aren't compressing styles or
// it doesn't have a '|' in the name which means it may be a user's public styleclass
// and we don't want to compress those; we will also write out the compressed
// version the public styleclasses in the next step.
if (!compressStyles || (mappedSelector.indexOf('|') == -1))
{
validFullNameSelector = getValidFullNameSelector(mappedSelector, namespacePrefixArray);
if (validFullNameSelector != null)
{
validSelectors.add(validFullNameSelector);
}
}
if (compressStyles)
{
String shortSelector = getShortSelector(shortStyleClassMap, namespacePrefixArray, mappedSelector);
// if the transformed full name is different than the shortSelector
// then write out the shortSelector, too.
if (shortSelector != null)
{
String validShortSelector = getValidFullNameSelector(shortSelector, namespacePrefixArray);
// if we wrote out a full style, check to see if we need to write out the short, too.
// if it is something different, write out the short, too.
if (validFullNameSelector != null)
{
//Since validFullNameSelector is not null, we know we wrote out a full style
// we write out a short style too in this case if it is different
// example: .PublicStyleClass is written out fully even in compressed mode, but
// it is different in compressed mode, so we write that out, too.
if (!validFullNameSelector.equals(validShortSelector))
{
validSelectors.add(validShortSelector);
}
}
else
{
validSelectors.add(validShortSelector);
}
}
}
}
return validSelectors;
}
/**
* Writes the merged selectors represented by a mappedSelectors, returning how many selectors were written. If the number
* of selectors that would be written is greater to or equal than maxSelectors, nothing will be written and
* 0 will be returned. It is up to the caller to retry with this entry later.
* @param out
* @param compressStyles
* @param shortStyleClassMap
* @param namespacePrefixArray
* @param maxSelectors
* @param mappedSelectors
* @return
*/
private static int _writeMergedSelectors(
PrintWriter out, boolean compressStyles, Map shortStyleClassMap,
String[] namespacePrefixArray, int maxSelectors, List mappedSelectors)
{
// to make this atomic, we first calculate all of the selectors that we will write out. If we have space, we will
// then write the selectors out
List validSelectors = _calculateValidSelectors(compressStyles, shortStyleClassMap, namespacePrefixArray,
mappedSelectors);
int selectorsToWrite = validSelectors.size();
if (selectorsToWrite >= maxSelectors)
{
// not enough space, so abort
return 0;
}
else
{
_writeValidSelectors(out, validSelectors);
return selectorsToWrite;
}
}
/**
* For a List of matching StyleNode, return the mapped version of their Selectors
*/
private static List _calculateMappedSelectors(
String[] namespacePrefixArray, Map afSelectorMap, List matchingStyleNodes)
{
List mappedSelectors = new ArrayList(matchingStyleNodes.size());
for (StyleNode matchingNode : matchingStyleNodes)
{
String matchingSelector = matchingNode.getSelector();
// We should always have a selector at this point
assert (matchingSelector != null);
String mappedSelector = getMappedSelector(afSelectorMap, namespacePrefixArray, matchingSelector);
mappedSelectors.add(mappedSelector);
}
return mappedSelectors;
}
/**
* Writes a merged style rule represented by a mergedEntry, returning how many selectors were written. If the number
* of selectors that would be written is greater to or equal than maxSelectors, nothing will be written and
* 0 will be returned. It is up to the caller to retry with this entry later.
* @param styleSheetName
* @param compressStyles
* @param shortStyleClassMap
* @param namespacePrefixArray
* @param afSelectorMap
* @param out
* @param maxSelectors
* @param mergedEntry
* @return
*/
private static int _writeMergedEntry(
String styleSheetName,
String baseURI,
boolean compressStyles,
Map shortStyleClassMap,
String[] namespacePrefixArray,
Map afSelectorMap,
PrintWriter out,
int maxSelectors,
Map.Entry> mergedEntry
)
{
String propertyString = mergedEntry.getKey();
// we shouldn't have any null or empty Strings
assert (propertyString != null);
assert (!"".equals(propertyString));
// Get all of the styles which share this property string.
List matchingStyleNodes = mergedEntry.getValue();
// Actually, we should always have at least one StyleNode here
assert (!matchingStyleNodes.isEmpty());
List mappedSelectors = _calculateMappedSelectors(namespacePrefixArray, afSelectorMap, matchingStyleNodes);
// Write out all of the style selectors for this property string. If 0 is returned, we've aborted
int numberSelectorsWritten = _writeMergedSelectors(out, compressStyles, shortStyleClassMap, namespacePrefixArray,
maxSelectors, mappedSelectors);
// Now that we have written out the selectors, write out
// the properties
if (numberSelectorsWritten > 0)
{
// At this point, we could just write out the property string
// that we already created, but this string contains the properties
// in sorted order. We prefer to attempt to preserve the order
// of the properties as specified in the XSS document. So,
// we get the properties from the StyleNode object instead of
// using the propertyString
// because of this lameness, we use the order from the original StyleNode
Iterable properties = matchingStyleNodes.get(0).getProperties();
_writeMergedProperties(out, properties, styleSheetName, compressStyles, baseURI);
}
return numberSelectorsWritten;
}
/**
* Returns the maximum number of selectors per file that this agent supports
* @param context
* @return
*/
private static int _getMaxSelectorsPerFile(StyleContext context)
{
if (TrinidadAgent.Application.IEXPLORER == context.getAgent().getAgentApplication())
{
return _MSIE_SELECTOR_LIMIT;
}
else
{
return Integer.MAX_VALUE;
}
}
/**
* Converts the specified set of StyleNodes to CSS. We output either full styleclass names or
* compressed styleclass names.
*
* @param context The current StyleContext
* @param styleSheetName The stylesheet name that is registered with the skin.
* e.g. skins/purple/purpleSkin.css
* @param styleNodes The style nodes to convert
* @param writerFactory The factory to obtain {@link PrintWriter} instances
* @param compressStyles This tells us whether or not we want to output the short names.
* @param shortStyleClassMap A Map which maps style
* class names to short names.
* @param namespacePrefixArray an array with the namespace prefixes of our
* special selectors. e.g., "af|" or "tr|" .
* @param afSelectorMap A Map which maps the namespaced component selectors
* to their base names (e.g., 'af|menuPath::step' maps to 'af|menuPath A')
*/
public static void writeCSS(
StyleContext context,
String styleSheetName,
List styleNodes,
StyleWriterFactory writerFactory,
boolean compressStyles,
Map shortStyleClassMap,
String[] namespacePrefixArray,
Map afSelectorMap)
{
// writeCSS() attempts to produce a minimal set of style rules
// by combining selectors with identical properties into a
// single rule. For example, we convert the following two
// styles:
//
// .OraBGAccentVeryDark,.x8 {background-color:#999966}
// .OraTable,.x1x {background-color:#999966}
//
// into a single style:
//
// .OraBGAccentVeryDark,.x8,.OraTable,.x1x {background-color:#999966}
//
// The intention of this optimization is to improve performance by
// reducing the overall size of the generated CSS file - and hopefully
// speed up download time.
//
// To implement this optimization, we perform two passes over the
// set of styles. During the first pass, we build up a hash map
// which maps from the property string to an array of StyleNodes
// which share the properties defined by the property string.
// During the second pass over the styles, we write out all matching
// selectors for each style followed by the shared set of properties.
// This is the first pass where MatchingStyles object is created from
// the styleNodes. MatchingStyles object segregates the normal selectors
// rendered as selector {prop: value} and clientRule selectors which are
// rendered as clientRule { selector { prop: value }}
// The segregation of clientRules allow us to segregate the rendering logic.
MatchingStyles matchingStyles = _buildMatchingStylesMap(styleNodes);
// This is the second pass in which we write out the style rules
// Get the baseURI up front so we don't have to recalculate it every time we find
// a property value that contains url() and need to resolve the uri.
String baseURI = CSSUtils.getBaseSkinStyleSheetURI(styleSheetName);
// Because of IE's selector limit, we need a slightly more complicated scheme
// for writing out the styles, so that we can create new CSS files as necessary
final int maxSelectorsPerFile = _getMaxSelectorsPerFile(context);
int fileSelectorsWritten = 0;
// read out the normal selectors, we render these first
LinkedHashMap> noClientRuleMap = matchingStyles.getNonClientRuleMap();
Iterator>> noClientRuleEntries = noClientRuleMap.entrySet().iterator();
PrintWriter out = writerFactory.createWriter();
if (out == null)
{
return;
}
_beginCssFile(out, compressStyles);
// loop over all of the entries
while (noClientRuleEntries.hasNext())
{
Map.Entry> abortedEntry = null;
// loop over all of the entries we can fit in a CSS file
while (abortedEntry == null && noClientRuleEntries.hasNext())
{
// get the entry to write out. This handles retrying any aborted entry
Map.Entry> currEntry;
// aborted entry is set when the max number of selectors have already
// written out for the current css file. if aborted entry is set then
// a new file needs to be created to write remaining selectors
if (abortedEntry != null)
{
currEntry = abortedEntry;
abortedEntry = null;
}
else
{
currEntry = noClientRuleEntries.next();
}
// write the entry
int selectorsLeft = maxSelectorsPerFile - fileSelectorsWritten;
int entrySelectorsWritten = _writeMergedEntry(styleSheetName, baseURI, compressStyles, shortStyleClassMap,
namespacePrefixArray, afSelectorMap, out, selectorsLeft,
currEntry);
// detect abort
if (entrySelectorsWritten == 0)
{
// no selectors written, so we have reached the threshold for this css file
// set the aborted entry so that we will create a new css file
abortedEntry = currEntry;
}
else
{
// add up the selectors written to track the threshold for the css file
fileSelectorsWritten += entrySelectorsWritten;
}
}
if (abortedEntry != null)
{
// abort detected, close the current out and create a new one
_endCssFile(out, styleSheetName, compressStyles, fileSelectorsWritten);
fileSelectorsWritten = 0;
// create new out
out = writerFactory.createWriter();
if (out == null)
{
return;
}
// put in the headers
_beginCssFile(out, compressStyles);
}
}
Set clientRules = matchingStyles.getClientRules();
// while writing a client rule, all selectors within the clien rule has to fit into the current css file.
// If that cannot happen, then we need to create a new css file and continue writing.
for (String clientRule : clientRules)
{
// write out all client rule entries into a temp out, so that we will know if it can be fitted in
// the current css file
StringWriter tempStringWriter = new StringWriter();
PrintWriter tempWriter = new PrintWriter(tempStringWriter);
// we cannot count the selectors within the client rule to decide the number of selectors
// there are cases where both compressed and non-compressed selectors get written
// so we create a temp StringWriter and get the selectors and properties written into that
// we check if all selectors enclosed in a particular client rule can be incorporated in the current out
// otherwise we create a new out and write out the client rule followed by the selectors within
int clientRuleSelectorsWritten = _writeClientRuleStyles(tempWriter, clientRule, matchingStyles, maxSelectorsPerFile,
fileSelectorsWritten, styleSheetName, baseURI, compressStyles,
shortStyleClassMap,namespacePrefixArray, afSelectorMap);
// if no selectors were written, then we need a new file to contain the contents of the current clientRule
if (clientRuleSelectorsWritten == 0)
{
// all selectors inside client rule cannot be accommodated in current file
_endCssFile(out, styleSheetName, compressStyles, fileSelectorsWritten);
fileSelectorsWritten = 0;
// create new out
out = writerFactory.createWriter();
if (out == null)
{
return;
}
// write headers
_beginCssFile(out, compressStyles);
// close the tempWriter and create a new one
// some selectors inside the client rule may have got written to tempWriter
tempWriter.close();
tempStringWriter = new StringWriter();
tempWriter = new PrintWriter(tempStringWriter);
// now that we recreated the writer, so it should have space to write all the selectors in the client rule
clientRuleSelectorsWritten = _writeClientRuleStyles(tempWriter, clientRule, matchingStyles,
maxSelectorsPerFile, fileSelectorsWritten, styleSheetName,
baseURI, compressStyles, shortStyleClassMap,
namespacePrefixArray, afSelectorMap);
}
// all selectors inside the current client rule should now be written to tempWriter
assert (clientRuleSelectorsWritten != 0);
if (tempWriter.checkError())
{
// check for errors in tempWriter and log it and return
_LOG.severe("Error writing stylesheet:" + styleSheetName);
return;
}
// the client rule can now be written to the actual out
out.print(clientRule);
_writeString(out, " {", compressStyles);
// write the selectors and properties to the actual out
out.print(tempStringWriter.toString());
// closing braces for client rule
_writeString(out, "}", compressStyles);
fileSelectorsWritten += clientRuleSelectorsWritten;
tempWriter.close();
}
_endCssFile(out, styleSheetName, compressStyles, fileSelectorsWritten);
}
/**
* tests if all the selectors under the client rule can fit into the current css file.
* writes the selectors and properties into the PrintWriter passed.
*
* @param out
* @param clientRule
* @param matchingStyles
* @param maxSelectorsPerFile
* @param fileSelectorsWritten
* @param styleSheetName
* @param baseURI
* @param compressStyles
* @param shortStyleClassMap
* @param namespacePrefixArray
* @param afSelectorMap
* @return number of selectors written into the css file. Returns 0, if all selectors does not fit
*
*/
private static int _writeClientRuleStyles(PrintWriter out,
String clientRule,
MatchingStyles matchingStyles,
int maxSelectorsPerFile,
int fileSelectorsWritten,
String styleSheetName,
String baseURI,
boolean compressStyles,
Map shortStyleClassMap,
String[] namespacePrefixArray,
Map afSelectorMap)
{
int selectorsWritten = 0;
int entrySelectorsWritten = 0;
// we know this cannot be null because the caller is using the client rule keySet from matchingStyles
LinkedHashMap> clientRuleMap = matchingStyles.getClientRuleMap(clientRule);
Iterator>> clientRuleEntries = clientRuleMap.entrySet().iterator();
while (clientRuleEntries.hasNext())
{
Map.Entry> currEntry = clientRuleEntries.next();
int selectorsLeft = maxSelectorsPerFile - (fileSelectorsWritten + selectorsWritten);
entrySelectorsWritten = _writeMergedEntry(styleSheetName, baseURI, compressStyles, shortStyleClassMap,
namespacePrefixArray, afSelectorMap, out, selectorsLeft,
currEntry);
// we want to write all selectors in clientRuleEntries into the same css file
// so, if we cannot write any one of these selector we need to abort
if (entrySelectorsWritten == 0)
{
return 0;
}
else
{
selectorsWritten += entrySelectorsWritten;
}
}
return selectorsWritten;
}
/**
* outputs header for a css file
* @param out
* @param compressStyles
*/
private static void _beginCssFile(PrintWriter out, boolean compressStyles)
{
if (!compressStyles)
{
Date date = new Date();
// write out the header with a time stamp
out.println("/* This CSS file generated on " + date + " */");
}
}
/**
* outputs the footer for a css file
* @param out
* @param styleSheetName
* @param compressStyles
* @param fileSelectorsWritten
*/
private static void _endCssFile(PrintWriter out, String styleSheetName, boolean compressStyles, int fileSelectorsWritten)
{
if (!compressStyles)
{
out.print("/* The number of CSS selectors in this file is ");
out.print(fileSelectorsWritten);
out.println(" */");
}
if (out.checkError())
{
_LOG.severe("Error writing stylesheet:" + styleSheetName);
}
out.close();
}
/**
* Util method to output a outStr based on compressStyles
* @param out
* @param outStr
* @param compressStyles
*/
private static void _writeString(PrintWriter out, String outStr, boolean compressStyles)
{
if (compressStyles)
out.print(outStr); // take out the newlines for performance
else
out.println(outStr);
}
/**
* Shorten (compress) the selector.
* @param shortStyleClassMap
* @param namespacePrefixArray
* @param selector
* @return the shortened selector, or selector if nothing could be shortened.
*/
public static String getShortSelector(
Map shortStyleClassMap,
String[] namespacePrefixArray,
String selector)
{
// shorten all the css-2 style class selectors (those that start with
// '.' and don't have a namespace prefix in it)
// and return the shortened string.
// e.g., selector of '.OraBulletedList A' is shortened to '.xj A'
// e.g., selector of af|inputText::content is not shortened since
// it has no css-2 style class selector piece that starts with '.'.
// e.g., selector of af|foo.Bar will shorten the '.Bar' piece
// af|foo.xz
// e.g., .Foo:hover -> .x0:hover
String shortSelector = _getShortNonNamespacedSelector(selector,
shortStyleClassMap);
if (shortSelector == null)
shortSelector = selector;
// run it through a shortener one more time to shorten any
// of the af component selectors.
// e.g., 'af|menuPath' is shortened to '.x11'
if (_hasNamespacePrefix(shortSelector, namespacePrefixArray))
{
String[] shortSelectorArray = StyleUtils.splitStringByWhitespace(shortSelector);
shortSelector = _getMappedNSSelector(shortStyleClassMap,
namespacePrefixArray,
shortSelectorArray,
true);
}
return shortSelector;
}
/**
* Tests whether the specified selector is a single style class
* selector. A single style class selector is something like
* ".AFInstructionText". Examples that are not single style class
* selectors are "af|inputText" or ".AFFoo .AFBar" or ".foo:hover"
*/
public static boolean isSingleStyleClassSelector(String selector)
{
if ((selector == null) ||
(selector.length() < 2) ||
(selector.charAt(0) != '.'))
{
return false;
}
for (int i = 1; i < selector.length(); i++)
{
if (_isStyleClassTerminator(selector.charAt(i)))
return false;
}
return true;
}
/**
* Returns an
* Iterator of all of the style class selectors included in the
* specified selector. For example, ".OraNav1Enabled" returns
* a single element Iterator with the string "OraNav1Enabled".
* "P.OraNav1Enabled SPAN.text" returns a two element Iterator
* with "OraNav1Enabled" and "text".
* .OraLink:visited returns "OraLink"
* .star.moon returns "star" and "moon"
* namespaced style classes like af|table are not returned
*/
public static Iterator getNonNamespacedStyleClasses(String selector)
{
ArrayList styleClasses = null;
boolean inAttributeSet = false;
int styleClassStartIndex = -1;
int length = selector.length();
for (int i = 0; i < length; i++)
{
char c = selector.charAt(i);
// attribute set can be id["something"] or img["foo/bar/baz.gif"]
// inAttributeSet is set to true when we find '['
// we want to skip till ']' so that we skip the entire attribute set
if (inAttributeSet)
{
if (c == ']')
{
inAttributeSet = false;
}
// skip until and including ']'
continue;
}
// If we aren't inside of a style class yet,
// check to see if we are about to enter one.
// Otherwise, check to see if we are at the
// end of one.
if (styleClassStartIndex == -1)
{
if (c == '.')
styleClassStartIndex = i;
else if (c == '[')
inAttributeSet = true;
}
else
{
boolean end = _isStyleClassTerminator(c);
if (!end)
{
// Check to see if we are at the end of the string
if (i == (length - 1))
{
// We need to increment our counter to make sure
// we include this last character when we pull out
// the substring
i++;
end = true;
}
}
// If we're at the end of the style class, add it to the list
if (end)
{
// if we are at the end due to '[' we want to skip processing the entire
// attributeSet before we start looking for next selector
if (c == '[')
inAttributeSet = true;
String styleClass = selector.substring(styleClassStartIndex + 1, i);
if (styleClasses == null)
styleClasses = new ArrayList(3);
styleClasses.add(styleClass);
// if c is ., then this means we've found another styleclass
if (c == '.')
styleClassStartIndex = i;
else
styleClassStartIndex = -1;
}
}
}
if (styleClasses == null)
return null;
return styleClasses.iterator();
}
/**
* Called when creating the shortened styleclass map.
* Returns an Iterator of all of the component selectors that begin with the
* given namespace. All styleclasses and pseudo-classes are ignored and NOT
* returned.
* For example, ".OraNav1Enabled af|menuPath" returns
* a single element Iterator with the string "af|menuPath".
* "af|menuPath.OraNav1Enabled af|treeTable.text" returns a two
* element Iterator with "af|menuPath" and "af|treeTable".
* af|inputText:disabled af|inputText::content returns two
* "af|inputText" and "af|inputText::content".
* It also looks at the afSelectorMap to map any special selectors if
* necessary.
* e.g., "af|menuPath::step" maps to "af|menuPath A", so
* ".OraNav1Enabled af|menuPath::step" returns "af|menuPath"
*/
public static Iterator getNamespacedSelectors(
String selector,
String namespace,
Map afSelectorMap)
{
if (selector == null)
return null;
int afIndex = selector.indexOf(namespace);
// no namespace in the selector, so just return null
if (afIndex == -1)
return null;
ArrayList afUnmappedSelectorList = new ArrayList();
// now find each af| component selector and map
// e.g., af|menuPath::step maps to af|menuPath A
// order the pseudo elements and classes
String base = selector.substring(afIndex);
String[] afSelectors =
_orderPseudoElementsAndClasses(base);
// loop through each of the af| parts.
for (int i=0; i < afSelectors.length; i++)
{
if (afSelectors[i].startsWith(namespace))
{
// get the component selector, which is just the main part, nothing
// that includes a '.' ':', or a ' '.
String afComponentSelector =
_getNSComponentSelector(afSelectors[i], false);
afUnmappedSelectorList.add(afComponentSelector);
}
}
// ok, now everything in afSelectorList at this point is the unmapped
// af|xxx component selectors. Now map them, and return the base key
// in the selector.
// loop through again and map them
ArrayList afSelectorList = new ArrayList();
int size = afUnmappedSelectorList.size();
for (int i=0; i < size; i++)
{
String afComponentSelector = afUnmappedSelectorList.get(i);
String mappedSelector = null;
if (afSelectorMap != null)
{
mappedSelector = afSelectorMap.get(afComponentSelector);
}
if (mappedSelector != null)
{
// In case the mapped selector does not start with the namespace,
// start the string where the namespace starts.
int namespaceIndex = mappedSelector.indexOf(namespace);
if (namespaceIndex > -1)
{
// get the selector up until the space.
String[] baseSelector = StyleUtils.splitStringByWhitespace(mappedSelector.substring(namespaceIndex));
afComponentSelector = baseSelector[0];
afSelectorList.add(afComponentSelector);
}
}
else
{
afSelectorList.add(afComponentSelector);
}
}
return afSelectorList.iterator();
}
/**
* Add to the namespacePrefixes Set any namespace prefixes found in this selector.
* @param namespacePrefixes
* @param selector
*/
public static void getNamespacePrefixes(
Set namespacePrefixes,
String selector)
{
int length = selector.length();
int startSubstringIndex = 0;
// Loop through each character of the selector looking for namespace prefixes.
for (int i = 0; i < length; i++)
{
char c = selector.charAt(i);
if (c == '|')
{
String prefix = selector.substring(startSubstringIndex, i+1);
startSubstringIndex = i+1;
// protect against just | in the prefix by checking length.
if (prefix.length() > 1)
namespacePrefixes.add(prefix);
}
else if(!_isStyleClassTerminator(c))
{
// keep going if it isn't a terminating character
}
else
{
// update the startSubstring index.
startSubstringIndex = i+1;
}
}
return;
}
/**
* Called from getNamespacedSelectors.
* Given a single selector that begins with a namespace prefix (most likely
* af|), return the main portion -- the prefix+component+pseudo-element.
* Styleclasses and pseudo-classes and anything after a space will be ignored,
* and won't be returned.
* @param singleAfSelector
* @param allowPseudoClass
* @return
*/
private static String _getNSComponentSelector(
String singleAfSelector,
boolean allowPseudoClass)
{
int colonIndex = singleAfSelector.indexOf("::");
// get the part after the ::.
// colonIndex will be 0 if there is no ::.
if (colonIndex != -1)
{
colonIndex += 2;
}
else
colonIndex = 0;
String afterDoubleColon = singleAfSelector.substring(colonIndex);
// now find the part with a single ':', a ' ', or a '.'. That is where
// I want to chop it to get my component selector.
boolean end = false;
int afterLength = afterDoubleColon.length();
int endIndex=0;
for (; ((endIndex < afterLength) && !end); endIndex++)
{
char c = afterDoubleColon.charAt(endIndex);
if (allowPseudoClass)
end = Character.isWhitespace(c);
else
end = _isStyleClassTerminator(c);
}
String afComponentSelector = null;
if (end)
{
afComponentSelector =
singleAfSelector.substring(0, colonIndex + endIndex-1);
}
else
{
// there was no end, so just take the entire string.
afComponentSelector = singleAfSelector;
}
return afComponentSelector;
}
// Converts all style class selectors that begin with '.'
// to the short version if
// there is a short version. does not shorten styles that start with the
// namespace
// returns null if it can't shorten the selector
private static String _getShortNonNamespacedSelector(
String selector,
Map shortStyleClassMap)
{
if (shortStyleClassMap == null)
return null;
// This will see if the selector is a single styleClass, and if so,
// it will run it through the shortStyleClassMap and return the shortened
// style class. A single style class selector is something like
// ".AFInstructionText". Examples that are not single style class
// selectors are "af|inputText" or ".AFFoo .AFBar" or ".foo:hover"
if (isSingleStyleClassSelector(selector))
{
String shortStyleClass = shortStyleClassMap.get(selector.substring(1));
return (shortStyleClass == null) ? null : "." + shortStyleClass;
}
// This will run if we do not have a one class selector definition
// but a af|XXX class or a command class like .AFXYZ .AFzzz or
// ".foo:hover" It shortens all the .xxx pieces.
boolean isShorter = false;
int length = selector.length();
StringBuffer buffer = new StringBuffer(length);
int styleClassStartIndex = -1;
// loop through each character. If we don't come across a '.' character,
// return the selector unchanged.
for (int i = 0; i < length; i++)
{
char c = selector.charAt(i);
if (styleClassStartIndex == -1)
{
if (c == '.')
styleClassStartIndex = i;
buffer.append(c);
}
else
{
// We are currently inside of a style class substring.
// Check to see if the style class has been terminated.
boolean end = _isStyleClassTerminator(c);
if (!end)
{
// Check to see if we are at the end of the string
if (i == (length - 1))
{
// We need to increment our counter to make sure
// we include this last character when we pull out
// the substring
i++;
end = true;
}
}
if (end)
{
// If we are at the end of the style class, check to
// see if we've got a shorter version
String styleClass = selector.substring(styleClassStartIndex + 1, i);
String shortStyleClass = null;
// don't shorten the styles that contain a namespace, because we
// do this in another step.
if (styleClass.indexOf('|') == -1)
shortStyleClass = shortStyleClassMap.get(styleClass);
if (shortStyleClass == null)
{
buffer.append(styleClass);
}
else
{
buffer.append(shortStyleClass);
isShorter = true;
}
// Don't forget the terminator character
if (i < (length - 1))
buffer.append(c);
// if c is ., then this means we've found another styleclass
if (c == '.')
styleClassStartIndex = i;
else
styleClassStartIndex = -1;
}
}
}
// return the original selector if this isn't shorter.
return isShorter ? buffer.toString() : selector;
}
/**
* Runs a selector through a map. It returns the selector unchanged (except for converted
* pseudo-classes) if there is no namespace in the selector.
* This could be a map to convert the
* public no-html selector to an internal selector that has html-specifics
* (e.g., 'af|menuPath::step:hover' -> 'af|menuPath A:hover')
* or it could be a map to shorten the selector
* (e.g., 'af|menuPath A:hover' -> '.x11 A:hover')
* We call this method first with the public->internal map, and then
* to shorten it.
* Only the pieces of the selector that start with the namespace are mapped.
* @param afSelectorMap if shortenPass is true, then this map shortens the
* af| selector. else, it maps the public af| selector
* to the internal selector (a selector that is closer to what is written to the
* CSS file.
* e.g., af|inputText:error::content becomes
* af|inputText.p_AFError af|inputText::content
* @param namespacePrefixArray most likely, "af|". The selectors with this namespace
* are the ones we map.
* @param selector selector to map.
*/
public static String getMappedSelector (
Map afSelectorMap,
String[] namespacePrefixArray,
String selector)
{
String mappedSelector;
if (_hasNamespacePrefix(selector, namespacePrefixArray))
{
String[] selectorArray =
_orderPseudoElementsAndClasses(selector);
// map selectors, if needed
// any of the selectors that start with a namespace prefix will
// be mapped.
// e.g., "af|menuPath::step" maps to "af|menuPath A"
// if the selector does not need to be 'mapped', it will be returned
// untouched. e.g., .AFInstructionText maps to .AFInstructionText
mappedSelector = _getMappedNSSelector(afSelectorMap,
namespacePrefixArray,
selectorArray,
false);
}
else
{
// there are no namespaces in this selector, but we still need to convert pseudo-classes.
//Separate pseudoclasses and then call it
int start = 0;
StringBuilder b = new StringBuilder();
for(int i = 0; i < selector.length(); i++)
{
char c = selector.charAt(i);
if (c == ' ' )
{
if (start == i)
{
//group of spaces
start = i+1; //Skip space
}
else
{
String subSelector = selector.substring(start,i);
subSelector = _convertPseudoClassesInSelector(subSelector, selector);
start = i+1; //Skip space
b.append(subSelector);
b.append(' ');
}
}
} // end for loop
// We reached the end of the selector, now convert the last bit
if (start == 0)
{
//there is no space in selector, but we still need to map pseudo-classes.
mappedSelector = _convertPseudoClassesInSelector(selector, selector);
}
else
{
String subSelector = selector.substring(start);
subSelector = _convertPseudoClassesInSelector(subSelector, selector);
b.append(subSelector);
mappedSelector = b.toString();
}
}
return mappedSelector;
}
/**
* Runs a namespaced selector through a map.
* This could be a map to convert the
* public no-html selector to an internal selector that has html-specifics
* (e.g., 'af|menuPath::step:hover' -> 'af|menuPath A:hover')
* or it could be a map to shorten the selector
* (e.g., 'af|menuPath A:hover' -> '.x11 A:hover')
* We call this method first with the public->internal map, and then
* to shorten it.
* Only the pieces of the selector that start with the namespace are mapped.
* @param map if shortenPass is true, then this map shortens the
* af| selector. else, it maps the public af| selector
* to the internal selector.
* @param selectorArray selectorArray is the selector split into pieces based on the ' '
* @param shorten if true, then we'll add the "." to the mapped selector.
* @return the selector, mapped.
*/
private static String _getMappedNSSelector (
Map map,
String[] nsPrefixArray,
String[] selectorArray,
boolean shorten)
{
// selectorArray is the selector broken into pieces.
// Map each piece, then put back together.
for (int i=0; i < selectorArray.length; i++)
{
// Assumption is the selector 'piece' only contains one namespace
// prefix. af|foo.tr|bar is not a valid selector, so this assumption
// is fine.
int nsIndex = -1;
int numNsPrefixes = nsPrefixArray.length;
for (int j=0; (j < numNsPrefixes) && (nsIndex == -1); j++)
{
nsIndex = selectorArray[i].indexOf(nsPrefixArray[j]);
}
if (nsIndex > -1)
{
selectorArray[i] = _getEachMappedSelector(map,
nsIndex,
selectorArray[i],
shorten);
}
}
// build back the selectorArray into a string
return StyleUtils.arrayToStringWithSpaces(selectorArray);
}
private static String _getEachMappedSelector(
Map map,
int indexOfNSPrefix,
String selector,
boolean shorten)
{
// break apart the selector into 2 parts:
// main (af|foo::pseudo-element)
// end (anything after, ':' or including and after '.')
// map the main and the ending (pseudo-classes could be mapped to private
// styleclasses, :disabled -> .p_AFDisabled
// piece back together.
if (indexOfNSPrefix == -1)
return selector;
if (map == null)
return selector;
String wholeAfSelector = selector.substring(indexOfNSPrefix);
// get main piece
int firstDoubleColonIndex = wholeAfSelector.indexOf("::");
// get the part after the ::.
// colonIndex will be 0 if there is no ::.
if (firstDoubleColonIndex != -1)
{
int lastDoubleColonIndex = wholeAfSelector.lastIndexOf("::");
// We exceptionally support multiple occurence of double colon for the browser builtin pseudo elements
if (lastDoubleColonIndex != firstDoubleColonIndex &&
! _BUILT_IN_PSEUDO_ELEMENTS.contains(wholeAfSelector.substring(lastDoubleColonIndex)))
{
_LOG.warning("UNSUPPORTED_CONSECUTIVE_SUB_ELEMENT_SYNTAX", selector);
}
firstDoubleColonIndex += 2;
}
else
firstDoubleColonIndex = 0;
String afterDoubleColon = wholeAfSelector.substring(firstDoubleColonIndex);
// now find the part with a single ':', a ' ', a '[', or a '.'. That is where
// I want to chop it to get my 'main' piece of the component selector.
boolean end = false;
int afterLength = afterDoubleColon.length();
int endIndex=0;
char c;
for (; ((endIndex < afterLength) && !end); endIndex++)
{
c = afterDoubleColon.charAt(endIndex);
end = (Character.isWhitespace(c)) || (c == '.') || (c == ':') || (c == '[');
}
// Set the main piece in the pieces object
String mainSelector;
if (endIndex == afterLength)
{
mainSelector = wholeAfSelector.substring(0, firstDoubleColonIndex + endIndex);
}
else
{
mainSelector = wholeAfSelector.substring(0, firstDoubleColonIndex + endIndex-1);
}
String afterMain = null;
if (endIndex != afterLength)
{
// If I got here, that means there are characters after the 'main' part
// of the selector.
afterMain = wholeAfSelector.substring(firstDoubleColonIndex + endIndex-1);
// map the afterMain part. It includes styleclasses and pseudo-classes.
// we don't need to convert the pseudo-classes if we are shortening.
// Also very likely we are dealing with inbuilt pseudo elements that
// should not be converted, do not process them as pseudo classes.
if (!shorten && ! _BUILT_IN_PSEUDO_ELEMENTS.contains(afterMain))
afterMain = _convertPseudoClassesInSelector(afterMain, wholeAfSelector);
}
// piece back together the string
StringBuffer buffer = new StringBuffer();
// beginning. add '.' if needed.
if (indexOfNSPrefix > 0)
{
buffer.append(selector.substring(0, indexOfNSPrefix));
if (shorten && selector.charAt(indexOfNSPrefix-1) != '.')
buffer.append('.');
}
else if (shorten)
buffer.append('.');
// map the mainSelector, and append the afterMain part.
buffer.append(_runThroughMap(map, mainSelector));
if (afterMain != null)
buffer.append(afterMain);
return buffer.toString();
}
private static boolean _hasNamespacePrefix(
String selector,
String[] nsPrefixArray)
{
if (selector == null) return false;
boolean hasNamespacePrefix = false;
int numNamespaces = nsPrefixArray.length;
for (int i=0; (i < numNamespaces )&& !hasNamespacePrefix; i++)
if (selector.indexOf(nsPrefixArray[i]) > -1)
hasNamespacePrefix = true;
return hasNamespacePrefix;
}
/*
* if I see this: af|foo:class:class::element, return this
* af|foo:class:class and af|foo::element in a String array.
* if I see this: af|foo:p-class.StyleClass::element, return this:
* 'af|foo:p-class.StyleClass' 'af|foo::element'
* af|foo.StyleClass::element -> 'af|foo.StyleClass' 'af|foo::element'
* If I see thiss: af|foo::p-element.StyleClass, return this
* af|foo::p-element.StyleClass (leave alone).
*/
private static String[] _orderPseudoElementsAndClasses(
String selector)
{
String[] input = StyleUtils.splitStringByWhitespace(selector);
List output = new ArrayList();
for (int i=0; i < input.length; i++)
{
int indexOfDoubleColon = input[i].indexOf("::");
if (indexOfDoubleColon == -1)
{
// no double colon (aka pseudo-element)
output.add(input[i]);
}
else
{
// you have a double colon index. Now look to see if we need to
// reorder. We have to reorder if the pseudo-element comes after
// the pseudo-class or composite style classes.
int indexOfFirstColon = input[i].indexOf(':');
int indexOfDot = input[i].indexOf('.');
boolean pseudoClassBeforePseudoElement =
(indexOfFirstColon < indexOfDoubleColon);
boolean styleClassBeforePseudoElement =
(indexOfDot != -1 && indexOfDot < indexOfDoubleColon);
if (!(pseudoClassBeforePseudoElement ||
styleClassBeforePseudoElement))
{
output.add(input[i]);
}
else
{
if (indexOfFirstColon == indexOfDoubleColon)
indexOfFirstColon = -1;
int indexOfClass = Math.min(indexOfFirstColon, indexOfDot);
if (indexOfClass == -1)
indexOfClass = Math.max(indexOfFirstColon, indexOfDot);
// we have the condition where pseudo-class or styleClass is before
// pseudo-element: af|foo:psdo-class::psdo-element
// e.g.,
// af|inputText:disabled::content
// indexOfColon = 12, indexOfDoubleColon = 21
// main = 'af|inputText'
// mainPlusClasses = 'af|inputText:disabled'
// end '::content'
String main = input[i].substring(0, indexOfClass);
String mainPlusClasses = input[i].substring(0, indexOfDoubleColon);
String end = input[i].substring(indexOfDoubleColon);
output.add(mainPlusClasses);
output.add(main + end);
}
}
}
return output.toArray(new String[output.size()]);
}
/**
* get rid of the | and :: that browsers don't like, and add the
* '.' where needed to make the af| component selector
* into a style class selector that is valid to be written to the css file.
* @param selector
* @return
*/
public static String getValidFullNameSelector(
String selector,
String[] namespacePrefixArray)
{
if (selector.indexOf('|') == -1)
return selector;
// split on spaces.
String[] spacerArray = StyleUtils.splitStringByWhitespace(selector);
for (int i=0; i < spacerArray.length; i++)
{
// if this starts with any of our namespaces, then add a '.'
if (spacerArray[i].indexOf('|') > -1)
{
for (int j=0; j < namespacePrefixArray.length; j++)
{
String nsPrefix = namespacePrefixArray[j];
if (spacerArray[i].startsWith(nsPrefix))
{
spacerArray[i] = ".".concat(spacerArray[i]);
break;
}
}
}
}
return StyleUtils.convertToValidSelector(
StyleUtils.arrayToStringWithSpaces(spacerArray));
}
// Tests whether the specified character or pseudo-class terminates a
// style class selector
private static boolean _isStyleClassTerminator(char c)
{
return (Character.isWhitespace(c) || (c == ':') || (c == '.') || (c == '['));
}
private static int _computeBuilderSize(PropertyNode[] properties)
{
// Compute the StringBuilder size
int builderSize = 0;
for (int i = 0; i < properties.length; i++)
{
PropertyNode property = properties[i];
String name = property.getName();
String value = property.getValue();
if ((name != null) && (value != null))
{
builderSize += property.getName().length();
builderSize += property.getValue().length();
// Leave room for a separator
builderSize++;
}
}
return builderSize;
}
private static String _getSortedPropertyString(PropertyNode[] sortedProperties)
{
int builderSize = _computeBuilderSize(sortedProperties);
StringBuilder builder = new StringBuilder(builderSize);
boolean first = true;
for (int i = 0; i < sortedProperties.length; i++)
{
PropertyNode property = sortedProperties[i];
String name = property.getName();
String value = property.getValue();
if ((name != null) && (value != null))
{
if (!first)
builder.append(';');
else
first = false;
builder.append(name);
builder.append(':');
builder.append(value);
}
}
return builder.toString();
}
/**
* Returns the properties of the specified StyleNode in property name sorted order as a String.
* If the Stylenode contains no properties, the empty String is returned
*/
private static String _getSortedPropertyString(StyleNode styleNode)
{
Collection nodeProperties = styleNode.getProperties();
int propertyCount = nodeProperties.size();
if (propertyCount == 0)
return "";
// =-= bts the extra step of copying the PropertyNodes into the array is kind of lame. I'm wondering
// if it would be better for the StyleNodes to sort the properties for us
List properties = new ArrayList(nodeProperties);
PropertyNode[] sortedProperties = properties.toArray(new PropertyNode[propertyCount]);
// Sort the properties so that the order of the properties won't
// come into play when comparing property strings.
Arrays.sort(sortedProperties, PropertyNodeNameComparator.sharedInstance());
return _getSortedPropertyString(sortedProperties);
}
/**
* Given a selector, convert the pseudo-classes. Non css-2 pseudo-classes
* get converted to styleclasses so they don't get passed through to the generated css.
*
* @param selector String input selector. The assumption is there are no spaces.
* If the original selector has spaces, then break the selector up into the pieces before
* calling this method.
* @param completeSelector to figure out if the selector is AF namespaced
* @return String the selector with the pseudo-classes converted, if needed.
* e.g., .StyleClass:error:active -> .StyleClass.p_AFError:active
*/
private static String _convertPseudoClassesInSelector(String selector, String completeSelector)
{
if (selector == null || completeSelector ==null) return selector;
boolean afNamespacedSelector = _isAFNamespacedSelector(completeSelector);
StringBuffer completeBuffer = new StringBuffer();
StringBuffer pseudoClassBuffer = new StringBuffer();
boolean inPseudoClass = false;
for (int i=0; i < selector.length(); i++)
{
char x = selector.charAt(i);
if ((x == ':') || (x == '.') || (x == '['))
{
if (inPseudoClass)
{
// if we are in a pseudo-class already, and we get a ':' or '.' that means
// this pseudo-class is complete. Get ready for another one.
String convertedPseudoClass = _convertPseudoClass(completeSelector, pseudoClassBuffer.toString(), afNamespacedSelector);
completeBuffer.append(convertedPseudoClass);
pseudoClassBuffer = new StringBuffer();
inPseudoClass = false;
}
if (x == ':')
{
inPseudoClass = true;
pseudoClassBuffer.append(x);
}
else if (x == '.' || x == '[')
{
completeBuffer.append(x);
}
}
else
{
if (!inPseudoClass)
completeBuffer.append(x);
else
pseudoClassBuffer.append(x);
}
}
if (inPseudoClass)
{
String mappedPseudoClass = _convertPseudoClass(completeSelector, pseudoClassBuffer.toString(), afNamespacedSelector);
completeBuffer.append(mappedPseudoClass);
}
return completeBuffer.toString();
}
/**
* Returns true if the passed selector is a 'af' selector.
*
* @param completeSelector
* @return
*/
private static boolean _isAFNamespacedSelector(String completeSelector)
{
Set namespaces = new HashSet();
getNamespacePrefixes(namespaces, completeSelector);
if (namespaces.isEmpty() && completeSelector.startsWith(_DEFAULT_AF_SELECTOR))
return true;
if (namespaces.contains(_DEFAULT_NAMESPACE))
return true;
return false;
}
// run through the map. If it's not in the map, return the selector unchanged.
private static String _runThroughMap(Map map, String selector)
{
String mappedSelector = map.get(selector);
return (mappedSelector != null) ? mappedSelector : selector;
}
// Comparator that sorts PropertyNodes by name
private static class PropertyNodeNameComparator implements Comparator
{
public static Comparator sharedInstance()
{
return _INSTANCE;
}
@Override
public int compare(PropertyNode o1, PropertyNode o2)
{
String name1 = (o1 == null) ? null : o1.getName();
String name2 = (o2 == null) ? null : o2.getName();
if ((name1 == null) || (name2 == null) )
{
if (name1 == name2)
return 0;
else if (name1 != null)
return 1;
return -1;
}
return name1.compareTo(name2);
}
@Override
public boolean equals(Object o)
{
// we only have our singleton instance
return (this == o);
}
private PropertyNodeNameComparator() {}
private static final Comparator _INSTANCE = new PropertyNodeNameComparator();
}
/**
* Converts the pseudo class into a "p_AF" prefixed styleclass.
* This is because certain pseudo-classes are internal and are not recognized by any browsers.
* The renderers are responsible for rendering out the same styleclass format.
* Some examples are:
* :screen-reader becomes .p_AFScreenReader
* :disabled becomes .p_AFDisabled
*
* Exception is CSS built in pseudo classes, which are recognized by browsers. These are rendered as is.
*
* @param pseudoClass
* @param afNamespacedSelector true if the pseudoClass is part of AF namespaced selector
* @return
*/
static private String _convertPseudoClass(String completeSelector, String pseudoClass, boolean afNamespacedSelector)
{
// The design time needs the browser-supported pseudo-classes to be converted so they
// can show a preview of the skinned component.
String builtInPseudoClass = pseudoClass;
int parenthesesIndex = pseudoClass.indexOf("(");
// if there are no open parentheses check for closing parentheses
if (parenthesesIndex == -1)
parenthesesIndex = pseudoClass.indexOf(")");
// if at least one parentheses exists strip the pseudoClass off it
if (parenthesesIndex != -1)
builtInPseudoClass = pseudoClass.substring(0, parenthesesIndex);
if (_BUILT_IN_PSEUDO_CLASSES.contains(builtInPseudoClass) && !Beans.isDesignTime())
return pseudoClass;
// skip the pseudo selectors in _AT_PAGE_PSEUDO_CLASSES only for @page client rule
if (completeSelector.contains(_AT_PAGE_SELECTOR) && _AT_PAGE_PSEUDO_CLASSES.contains(builtInPseudoClass))
return pseudoClass;
// _BACKWARD_COMPATIBLE_CSS3_PSEUDO_CLASSES is treated differently
// for namespaced selectors we render it prefixed with "p_AF"
// for non-namespaced selectors we render it directly
if (!afNamespacedSelector && _BACKWARD_COMPATIBLE_CSS3_PSEUDO_CLASSES.contains(builtInPseudoClass))
return pseudoClass;
StringBuilder builder = new StringBuilder(pseudoClass.length() + 3);
builder.append(".");
builder.append(SkinSelectors.STATE_PREFIX);
for (String content : _DASH_PATTERN.split(pseudoClass.substring(1)))
{
if (content.length() > 0)
{
builder.append(Character.toUpperCase(content.charAt(0)));
builder.append(content.substring(1));
}
}
return builder.toString();
}
/**
* encapsulates the propertyString vs StyleNode maps separately for clientRule and normal selectors
* ClientRule selectors the styles are rendered as:
* clientRule { selector: {property1: value1, property2: value2 ... } }
* Normal selectors are rendered as
* selector: {property1: value1 ... }
*/
private static final class MatchingStyles
{
private MatchingStyles(int styleCount)
{
this._nonClientRuleEntry = new MatchingEntry(styleCount);
this._clientRuleEntries = new LinkedHashMap();
}
public LinkedHashMap> getNonClientRuleMap()
{
return _nonClientRuleEntry.getMatchingStyles();
}
public LinkedHashMap> getClientRuleMap(String clientRule)
{
return _clientRuleEntries.get(clientRule).getMatchingStyles();
}
public Set getClientRules()
{
return _clientRuleEntries.keySet();
}
public void addStyle(String propertyString, StyleNode styleNode)
{
_nonClientRuleEntry.addMatchingStyle(propertyString, styleNode);
}
public void addStyle(String clientRule, String propertyString, StyleNode styleNode)
{
MatchingEntry matchingStylesForClientRule = _clientRuleEntries.get(clientRule);
if (matchingStylesForClientRule == null)
{
matchingStylesForClientRule = new MatchingEntry(1);
_clientRuleEntries.put(clientRule, matchingStylesForClientRule);
}
matchingStylesForClientRule.addMatchingStyle(propertyString, styleNode);
}
// matching styles map with no client rules
private final MatchingEntry _nonClientRuleEntry;
// matching styles map with client rules
private final LinkedHashMap _clientRuleEntries;
}
/**
* encapsulates propertyString vs StyleNode map
* manages addition of StyleNode for a matching propertyString
*/
private final static class MatchingEntry
{
public MatchingEntry(int initialSize)
{
_matchingStyles = new LinkedHashMap>(initialSize);
}
public void addMatchingStyle(String propertyString, StyleNode styleNode)
{
if (propertyString == null || propertyString.equals(""))
return;
// See if we already have a StyleNode with the same properties
List matchingStyles = _matchingStyles.get(propertyString);
if (matchingStyles == null)
{
// If we don't already have matching StyleNodes, create a new match list and cache it
matchingStyles = new ArrayList(1);
_matchingStyles.put(propertyString, matchingStyles);
}
matchingStyles.add(styleNode);
}
public LinkedHashMap> getMatchingStyles()
{
return _matchingStyles;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MatchingEntry that = (MatchingEntry) o;
if (_matchingStyles != null ? !_matchingStyles.equals(that._matchingStyles) : that._matchingStyles != null)
return false;
return true;
}
@Override
public int hashCode()
{
return _matchingStyles != null ? _matchingStyles.hashCode() : 0;
}
private LinkedHashMap> _matchingStyles;
}
// We want to output to the css the browser-supported pseudo-classes as is
static private final Set _BUILT_IN_PSEUDO_CLASSES = new HashSet();
static private final Set _BACKWARD_COMPATIBLE_CSS3_PSEUDO_CLASSES = new HashSet();
// We want to output to the css the browser-supported pseudo-elements (HTML5/ CSS3) as is
static private final Set _BUILT_IN_PSEUDO_ELEMENTS = new HashSet();
static private final Set _AT_PAGE_PSEUDO_CLASSES = new HashSet();
static
{
/** CSS 2 pseudo classes */
_BUILT_IN_PSEUDO_CLASSES.add(":first-child");
_BUILT_IN_PSEUDO_CLASSES.add(":link");
_BUILT_IN_PSEUDO_CLASSES.add(":visited");
_BUILT_IN_PSEUDO_CLASSES.add(":hover");
_BUILT_IN_PSEUDO_CLASSES.add(":active");
_BUILT_IN_PSEUDO_CLASSES.add(":focus");
_BUILT_IN_PSEUDO_CLASSES.add(":-moz-placeholder"); // FF <= 18
_BUILT_IN_PSEUDO_CLASSES.add(":-ms-input-placeholder"); // IE
/** Special case CSS2 pseudo-elements used as pseudo-classes
* for compatibility reasons.
* Refer: http://www.w3.org/TR/selectors/#pseudo-elements */
_BUILT_IN_PSEUDO_CLASSES.add(":after");
_BUILT_IN_PSEUDO_CLASSES.add(":before");
_BUILT_IN_PSEUDO_CLASSES.add(":first-line");
_BUILT_IN_PSEUDO_CLASSES.add(":first-letter");
/** CSS 3 pseudo classes */
_BUILT_IN_PSEUDO_CLASSES.add(":nth-child");
_BUILT_IN_PSEUDO_CLASSES.add(":nth-last-child");
_BUILT_IN_PSEUDO_CLASSES.add(":nth-of-type");
_BUILT_IN_PSEUDO_CLASSES.add(":nth-last-of-type");
_BUILT_IN_PSEUDO_CLASSES.add(":last-child");
_BUILT_IN_PSEUDO_CLASSES.add(":first-of-type");
_BUILT_IN_PSEUDO_CLASSES.add(":last-of-type");
_BUILT_IN_PSEUDO_CLASSES.add(":only-child");
_BUILT_IN_PSEUDO_CLASSES.add(":only-of-type");
_BUILT_IN_PSEUDO_CLASSES.add(":root");
_BUILT_IN_PSEUDO_CLASSES.add(":target");
_BUILT_IN_PSEUDO_CLASSES.add(":enabled");
_BUILT_IN_PSEUDO_CLASSES.add(":checked");
_BUILT_IN_PSEUDO_CLASSES.add(":not");
_BUILT_IN_PSEUDO_CLASSES.add(":lang");
_BUILT_IN_PSEUDO_CLASSES.add(":indeterminate");
/** CSS3 pseudo classes for backward compatibility*/
_BACKWARD_COMPATIBLE_CSS3_PSEUDO_CLASSES.add(":disabled");
_BACKWARD_COMPATIBLE_CSS3_PSEUDO_CLASSES.add(":empty");
/** CSS3 pseudo elements */
_BUILT_IN_PSEUDO_ELEMENTS.add("::outside");
_BUILT_IN_PSEUDO_ELEMENTS.add("::before");
_BUILT_IN_PSEUDO_ELEMENTS.add("::after");
_BUILT_IN_PSEUDO_ELEMENTS.add("::alternate");
_BUILT_IN_PSEUDO_ELEMENTS.add("::first-line");
_BUILT_IN_PSEUDO_ELEMENTS.add("::first-letter");
_BUILT_IN_PSEUDO_ELEMENTS.add("::marker");
_BUILT_IN_PSEUDO_ELEMENTS.add("::line-marker");
_BUILT_IN_PSEUDO_ELEMENTS.add("::selection");
_BUILT_IN_PSEUDO_ELEMENTS.add("::-webkit-input-placeholder"); // Chrome
_BUILT_IN_PSEUDO_ELEMENTS.add("::-moz-placeholder"); // FF >= 19
/** @page pseudo classes*/
_AT_PAGE_PSEUDO_CLASSES.add(":first");
_AT_PAGE_PSEUDO_CLASSES.add(":left");
_AT_PAGE_PSEUDO_CLASSES.add(":right");
}
private static final Pattern _DASH_PATTERN = Pattern.compile("-");
private static final int _MSIE_SELECTOR_LIMIT = 4095;
private static final String _DEFAULT_NAMESPACE = "af|";
private static final String _DEFAULT_AF_SELECTOR = ".AF";
private static final String _AT_PAGE_SELECTOR = "@page";
private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(CSSGenerationUtils.class);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy