All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidadinternal.skin.SkinStyleSheetParserUtils Maven / Gradle / Ivy

There is a newer version: 2.2.1
Show newest version
/*
 * 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.skin;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import java.net.URL;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.share.io.InputStreamProvider;
import org.apache.myfaces.trinidad.share.io.NameResolver;

import org.apache.myfaces.trinidad.util.URLUtils;
import org.apache.myfaces.trinidadinternal.share.xml.ParseContext;
import org.apache.myfaces.trinidadinternal.share.xml.XMLUtils;
import org.apache.myfaces.trinidadinternal.style.util.CSSUtils;
import org.apache.myfaces.trinidadinternal.style.util.StyleUtils;
import org.apache.myfaces.trinidadinternal.style.xml.parse.EmbeddedIncludePropertyNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.IconNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.IncludePropertyNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.IncludeStyleNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.PropertyNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleNode;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleSheetDocument;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleSheetNode;
import org.apache.myfaces.trinidadinternal.util.nls.LocaleUtils;


/**
 * Utility class for creating a StyleSheetDocument.
 * The main method is parseCSSSource which creates a StyleSheetEntry.
 * The interim object is SkinStyleSheetNode
 * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/skin/SkinStyleSheetParserUtils.java#0 $) $Date: 10-nov-2005.18:59:00 $
 */
class SkinStyleSheetParserUtils
{
  /**
   * Parses a Skin style-sheet that is in the CSS-3 format.
   * @param context      the current ParseContext. Simply a place to set/get properties
   * @param resolver     a NameResolver to locate the target
   *                     ( Given a name, returns an InputStreamProvider.)
   * @param sourceName   the name of the target, relative to the current file
   * @param expectedType the expected Java type of the target.
   */
  static public StyleSheetEntry parseCSSSource(
    ParseContext  context,
    NameResolver  resolver,
    String        sourceName,
    Class      expectedType) throws IOException
  {

    if (expectedType == null)
      throw new NullPointerException();
    if (resolver == null)
      throw new NullPointerException();
    if (sourceName == null)
      throw new NullPointerException();
    if (context == null)
      throw new NullPointerException();

    InputStreamProvider provider = resolver.getProvider(sourceName);
    Object cached = provider.getCachedResult();
    if ((cached != null) && expectedType.isInstance(cached))
      return (StyleSheetEntry)cached;

    InputStream stream = provider.openInputStream();

    try
    {
      // Store a resolver relative to the file we're about to parse. This will be used for imports.
      // Store the inputStreamProvider on the context;
      // this will be used to get the document's timestamp later on
      XMLUtils.setResolver(context, resolver.getResolver(sourceName));
      XMLUtils.setInputStreamProvider(context, provider);

      // PARSE!
      // create a SkinStyleSheetNode
      // (contains a namespaceMap and a List of SkinSelectorPropertiesNodes
      // and additional information like direction, locale, etc.)
      // (selectorName + a css propertyList))
      BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
      SkinCSSParser parser = new SkinCSSParser();
      // Send over the ParseContext so that we can get the resolver from it in case we encounter 
      // an @import in the CSS file.
      SkinCSSDocumentHandler documentHandler = new SkinCSSDocumentHandler(context);
      parser.parseCSSDocument(reader, documentHandler);
      List  skinSSNodeList = documentHandler.getSkinStyleSheetNodes();
      reader.close();

      // process the SkinStyleSheetNodes to create a StyleSheetEntry object
      StyleSheetEntry styleSheetEntry =
        _createStyleSheetEntry(context, sourceName, skinSSNodeList);

      // Store the cached result (if successful)
      // otherwise, if we don't do this, we will keep reparsing. Somehow
      // this affects whether the file has been modified. STRANGE!
      //     if (value != null)
      //    provider.setCachedResult(value);

      //    return value;
      provider.setCachedResult(styleSheetEntry);

      return styleSheetEntry;
    }
    finally
    {
      stream.close();
    }
  }

  /**
   * Trim the leading/ending quotes, if any.
   * We trim only matching quotes, as opposed to trimQuotes which trims any quotes.
   */
  public static String trimQuotes(String in)
  {
    if ( in == null )
      return in;

    boolean startsWithDoubleQuote = in.startsWith( "\"" );
    boolean startsWithSingleQuote = in.startsWith( "\'" );
    boolean endsWithDoubleQuote = in.endsWith( "\"" );
    boolean endsWithSingleQuote = in.endsWith( "\'" );
    
    if (( startsWithDoubleQuote && endsWithSingleQuote ) ||
       ( startsWithSingleQuote && endsWithDoubleQuote ))
    {
      if (_LOG.isWarning())
        _LOG.warning("ERR_PARSING", in);
    }
                                                          
    if ( startsWithDoubleQuote && endsWithDoubleQuote )
      return in.substring( 1, in.length() - 1 );
    if ( startsWithSingleQuote && endsWithSingleQuote )
      return in.substring( 1, in.length() - 1 );
    
    return in;
  }  


  /**
   * Given a List of SkinStyleSheetNode, create StyleSheetEntry.
   * A StyleSheetEntry is an object that contains:
   * styleSheetName, StyleSheetDocument
   * A StyleSheetDocument contains StyleSheetNodes. A StyleSheetNode contains
   * StyleNodes, IconNodes, and SkinPropertyNodes and additional info like
   * the direction, locale, etc. for this list of selectors.
   * @param context
   * @param sourceName
   * @param skinSSNodeList
   * @return
   */
  private static StyleSheetEntry _createStyleSheetEntry(
    ParseContext  context,
    String        sourceName,
    List  skinSSNodeList
    )
  {
    // Get each SkinStyleSheetNode, and for each SkinStyleSheetNode get a
    // styleNodeList. Also, build one iconNodeList and one skinPropertyNodeList.

    // initialize
    List ssNodeList = new ArrayList();
    String baseSourceURI = CSSUtils.getBaseSkinStyleSheetURI(sourceName);

    // loop through the selectors and its properties
    for (SkinStyleSheetNode skinSSNode : skinSSNodeList)
    {
      // client rule such as @media, @keyframes
      String clientRule = skinSSNode.getClientRule();
      // selector and its properties
      List  selectorNodeList =
        skinSSNode.getSelectorNodeList();
      //Map namespaceMap = styleSheetNode.getNamespaceMap();

      // initialize
      List  styleNodeList = new ArrayList();
      List iconNodeList = new ArrayList();


      // process each selector and all its name+values
      for (SkinSelectorPropertiesNode cssSelector : selectorNodeList)
      {

        String selectorName = cssSelector.getSelectorName();
        // PropertyNode is the name+value, like font-size: 8px
        List propertyList = cssSelector.getPropertyNodes();
        int direction     = skinSSNode.getDirection();

        ResolvedSkinProperties resolvedProperties =
          _resolveProperties(propertyList);

        // trSkinPropertyNodeList, e.g., af|foo {-tr-show-last-item: true}
        // PropertyNode(-tr-show-last-item, true)
        List trSkinPropertyNodeList = new ArrayList();
        trSkinPropertyNodeList = resolvedProperties.getSkinPropertyNodeList();

        List noTrPropertyList =
          resolvedProperties.getNoTrPropertyList();

        if (StyleUtils.isIcon(selectorName))
        {
          // knock off the '.' if it is the first character.
          // do it only for "Icon:alias", because the style could be something like
          // ".AFSomeAlias af|style-icon" or ".AFSomeAlias af|style-icon:hover"
          // and further more, it may not represent an Icon style.
          // We log a warning in the console that such naming should be used only for Icons,
          // but we do not enforce this. So user can be already using such a selector as
          // "af|style-icon" and now want to specify it using a nested selector
          // this use case will not work if we remove the leading dot.
          String selectorNameForIcon = selectorName;
          if (selectorNameForIcon.charAt(0) == '.' &&  selectorNameForIcon.indexOf("Icon:alias") > -1)
            selectorNameForIcon = selectorNameForIcon.substring(1);
          // strip out :alias
          selectorNameForIcon = selectorNameForIcon.replaceFirst(":alias", "");
          // add :rtl if the direction is rtl
          if (direction == LocaleUtils.DIRECTION_RIGHTTOLEFT)
            selectorNameForIcon = selectorNameForIcon.concat(StyleUtils.RTL_CSS_SUFFIX);

          // create an IconNode object and add it to the iconNodeList
          // This method returns hasContentProperty=false if there isn't a content attribute.
          boolean hasContentProperty = _addIconNode(sourceName,
                                                    baseSourceURI,
                                                    selectorNameForIcon,
                                                    clientRule,
                                                    noTrPropertyList,
                                                    resolvedProperties.getTrRuleRefList(),
                                                    resolvedProperties.getInhibitedProperties(),
                                                    iconNodeList);

          if (!hasContentProperty)
          {
            // if it doesn't have any includes AND it doesn't have properties, it shouldn't pass
            // the _isIcon test, so log a warning. This means the developer used the wrong 
            // selector name. It should end in 'icon-style' instead of 'icon', for example.
            
            if (resolvedProperties.getTrRuleRefList() == null || 
                resolvedProperties.getTrRuleRefList().isEmpty())
            {
              if (_LOG.isInfo())
                _LOG.info("SELECTOR_SHOULD_NOT_END_IN_ICON", selectorName);
            }
            _addStyleNode(selectorName,
                          clientRule,
                          noTrPropertyList,
                          trSkinPropertyNodeList,
                          resolvedProperties.getTrRuleRefList(),
                          resolvedProperties.getIncludedPropertiesList(),
                          resolvedProperties.getEmbeddedIncludePropertiesList(),
                          resolvedProperties.getInhibitedProperties(),
                          resolvedProperties.isTrTextAntialias(),
                          styleNodeList);
          }
        }
        else
        {

          _addStyleNode(selectorName,
                        clientRule,
                        noTrPropertyList,
                        trSkinPropertyNodeList,
                        resolvedProperties.getTrRuleRefList(),
                        resolvedProperties.getIncludedPropertiesList(),
                        resolvedProperties.getEmbeddedIncludePropertiesList(),
                        resolvedProperties.getInhibitedProperties(),
                        resolvedProperties.isTrTextAntialias(),
                        styleNodeList);

        }
      }

      if ((styleNodeList.size() > 0) || (iconNodeList.size() > 0))
      {
        // we need to deal with the styleNodeList by building a StyleSheetNode
        // with this information.
        // create a StyleSheetNode, add to the ssNodeList
        StyleNode[] styleNodeArray = styleNodeList.toArray(new StyleNode[0]);
        StyleSheetNode ssNode =
          new StyleSheetNode(styleNodeArray,
                             iconNodeList,
                             skinSSNode.getLocales(),
                             skinSSNode.getDirection(),
                             skinSSNode.getAgentMatcher(),
                             skinSSNode.getPlatforms(),
                             skinSSNode.getMode(),
                             skinSSNode.getAcessibilityProperties());
        ssNodeList.add(ssNode);
      }

    } // end for each SkinStyleSheetNode


    // StyleSheetDocument contains StyleSheetNode[] styleSheets
    StyleSheetDocument ssDocument =
      _createStyleSheetDocument(context, ssNodeList);

    return new StyleSheetEntry(sourceName,
                               ssDocument);


  }

  /**
   * Loop thru every property in the propertyList and store them in
   * the ResolvedSkinProperties inner class.
   * @param propertyNodeList
   * @return
   */
  private static ResolvedSkinProperties _resolveProperties(
    List propertyNodeList)
  {

    List noTrPropertyList = new ArrayList();
    List trRuleRefList = new ArrayList();
    Set inhibitedPropertySet = new TreeSet();
    List includedPropertiesList =
      new ArrayList();
    List embeddedIncludedPropertiesList =
      new ArrayList();
    List skinPropertyNodeList =
      new ArrayList();

    boolean trTextAntialias = false;

    // loop through each property in the propertyList
    // and resolve into
    // noTrPropertyList (properties that do not start with -tr-.
    //                  (or -ora- for backwards compatibility))
    // trRuleRefList (properties that start with -tr-rule-ref
    //                (or -ora-rule-ref for backwards compatibility))
    // boolean trTextAntialias (property value for -tr-text-antialias
    //                      (or -ora-text-antialias for backwards compatibility)
    // skinPropertyNodeList (all other properties that start with -tr-
    //                       (or -ora- for backwards compatibility))
    // includedPropertiesList (all the included property 'values')
    // These properties are stored in the ResolvedSkinProperties inner class.

    for(PropertyNode propertyNode : propertyNodeList)
    {
      String propertyName = propertyNode.getName();
      String propertyValue = propertyNode.getValue();

      if(propertyName != null && propertyValue != null)
      {
        // Check for special propertyNames (-tr-rule-ref, -tr- skin properties)
        // or propertyValue (-tr-property-ref)
        
        // Check for -tr-property-ref first, either standalone or embedded
        if(propertyValue.indexOf(_INCLUDE_PROPERTY) != -1)
        {
          List values = _separateCompositeValues(propertyValue);
          
          // border-color: -tr-property-ref(".AFRed:alias", "color") -> (size == 1)
          // versus 
          // background: -ms-linear-gradient(top, -tr-property-ref(".AFRed:alias", "color") 1%, -tr-property-ref(".AFGreen:alias","color") 100%); -> (size > 1)
          if (values.size() == 1)
          {
            // include property
            IncludePropertyNode node = _createIncludePropertyNode(
              propertyName, propertyValue.substring(_INCLUDE_PROPERTY.length()));
            includedPropertiesList.add(node);
          }
          else
          {
            // We parse the value, and substitute each entire -tr-property-ref block with a unique 
            // token. The tokens are substituted later with the respective resolved value of the 
            // entire tr-property-ref block. This is a map with the key being token and value being 
            // the included property node that corresponds to the token.
            Map embeddedIncludePropNodes = new HashMap();
            // this list is all the non-whitespace segments in the entire value (includes placeholder
            //  that we substitute for each -tr-property-ref segment)
            List propertyValueChunks = new ArrayList();
            int count = 0;
            for (String value : values)
            {
              if (value.startsWith(_INCLUDE_PROPERTY))
              {
                IncludePropertyNode node = _createIncludePropertyNode(
                  propertyName, value.substring(_INCLUDE_PROPERTY.length()));
                // the placeholder tokens will be of form -tr-0, -tr-1, -tr-2 etc.
                String embedPlaceholder = _TR_PROPERTY_PREFIX + count++;
                embeddedIncludePropNodes.put(embedPlaceholder, node);
                propertyValueChunks.add(embedPlaceholder);
              }
              else
              {
                propertyValueChunks.add(value);
              }
            }
            
            EmbeddedIncludePropertyNode eIPNode = 
              new EmbeddedIncludePropertyNode(propertyValueChunks, 
                                              embeddedIncludePropNodes, 
                                              propertyName);
            
            embeddedIncludedPropertiesList.add(eIPNode);
                        
          }
        }
        else
        {
          boolean oraProperty = propertyName.startsWith(_ORA_PROPERTY_PREFIX);
          boolean trProperty = propertyName.startsWith(_TR_PROPERTY_PREFIX);
          if( oraProperty || trProperty)
          {
            int suffixIndex = (oraProperty)?_ORA_PROPERTY_PREFIX.length(): _TR_PROPERTY_PREFIX.length();
            String propertyNameSuffix = propertyName.substring(suffixIndex);
            if (propertyNameSuffix.equals(_PROPERTY_RULE_REF))
            {
              // add the rule ref value to the list
              trRuleRefList.add(propertyValue);
            }
            else if (propertyNameSuffix.equals(_PROPERTY_TEXT_ANTIALIAS))
            {
              if ("true".equals(propertyValue))
                trTextAntialias = true;

            }
            else if (propertyNameSuffix.equals(_PROPERTY_INHIBIT))
            {
              for (String value : _SPACE_PATTERN.split(propertyValue))
              {
                inhibitedPropertySet.add(value);
              }
            }
            else
            {
              // create the PropertyNode for skin properties (e.g., -tr-show-last-item: true)
              PropertyNode node = new PropertyNode(propertyName, propertyValue);
              skinPropertyNodeList.add(node);
            }
          }
          else
          {
            noTrPropertyList.add(propertyNode);
          }
        }
      }
    }

    return new ResolvedSkinProperties(
      noTrPropertyList,
      trRuleRefList,
      includedPropertiesList,
      embeddedIncludedPropertiesList,
      inhibitedPropertySet,
      skinPropertyNodeList,
      trTextAntialias);
  }

  /**
   * Separates a composite value into a list of strings. This will be recontructed later in 
   * StyleSheetDocument._createCompositeValue()
   * @param propertyValue The full value of the property
   * @return A list of deconstructed strings  Each entry is either a contiguous block of 
   *  non-whitespace characters, or a full segment of '-tr-property-ref("af|foo","color")'
   */
  private static List _separateCompositeValues(String propertyValue)
  {
    // although unusual, skin authors may omit a space before -tr-property-ref, we split by space
    //  char and we do not want to miss processing -tr-property-ref. Handle this case by adding an
    //  extra space before all -tr-property-ref, regardless if it had one already.
    propertyValue = propertyValue.replaceAll(_INCLUDE_PROPERTY, " " + _INCLUDE_PROPERTY);
    
    // the delimiter is the whole set of consecutive white spaces
    String[] test = _SPACE_PATTERN.split(propertyValue);
    List propertyValueNoSpaces = new ArrayList();
    boolean inTr = false;
    int inTrIndex = 0;
    for (int i=0; i < test.length; i++)
    {
      String string = test[i];
      // see if we are dealing with a -tr-property-ref now
      //  if it does not end with a closing brace, this segment either contains much more than the
      //  entire -tr-property-ref block, or otherwise contains only partly the -tr-property-ref block
      if (string.startsWith(_INCLUDE_PROPERTY) && !string.endsWith(")"))
      {
         // keep looping through the pieces 
         // until we get to a string that contains ")".
         inTr = true;
         inTrIndex = i;            
      }
      
      if (inTr)
      {
        int closeBraceIndex = string.indexOf(')');
        // the current string has the end segment of -tr-property-ref but this segment could be in 
        //  middle (if it was not followed by a space character in the original property value)
        if (closeBraceIndex != -1)
        {
          StringBuilder builder = new StringBuilder();
          // account for all previous chunks that belonged to -tr-property-ref that we skipped 
          //  processing so far
          for (int j=inTrIndex; j < i; j++)
          {
             builder.append(test[j]);
          }
          // add first part of the current processed string until the closing braces to the 
          //  -tr-property-ref segment
          builder.append(string.substring(0, closeBraceIndex+1));
          
          inTr = false;
          propertyValueNoSpaces.add(builder.toString());
          
          // If there was a second part of the string after the closing braces, add it as another 
          //  value chunk in the list of property values
          if (closeBraceIndex != (string.length() - 1))
          {
            propertyValueNoSpaces.add(string.substring(closeBraceIndex + 1));
          }
        }
      }
      // split() on a space pattern can leave the first entry as empty, exclude this empty value
      else if (!test[i].isEmpty())
      {
        propertyValueNoSpaces.add(string);
      } 
    }
    return propertyValueNoSpaces;
  }

  /**
   * Create an IconNode and add it to the iconNodeList.
   * @param sourceName
   * @param baseSourceURI
   * @param selectorName
   * @param trRuleRefList -> This is -tr-rule-ref: selector(). 
   * Currently not supported for icons. See https://issues.apache.org/jira/browse/TRINIDAD-17
   * @param noTrPropertyNodeList -> these are properties, like width: 100px.
   * @param iconNodeList Once the IconNode is created, it is added to the iconNodeList to be
   * used outside this method.
   * @return boolean Returns true if an IconNode was created and added to iconNodeList. 
   * If false, then it means that the properties did not contain 'content', 
   * so it only had css styles. It could have a -tr-rule-ref that includes a 'content'.
   * TODO what to do about that???
   */
  private static boolean _addIconNode(
    String             sourceName,
    String             baseSourceURI,
    String             selectorName,
    String             clientRule,
    List noTrPropertyNodeList,
    List       trRuleRefList,
    Set        inhibitedProperties,
    List     iconNodeList)
  {

    // these are icon properties.
    // create an IconNode.
    // get content property value. This is how i decide if it is an url or a text icon.
    //
    // loop through all the properties
    // TextIcons take text, rtlText, inlineStyle, styleClass
    // url icons take uri, rtluri, width, height, styleClass, inlineStyle
    // Icon selectors that end with :rtl will be a separate Icon object.
    // I won't combine :rtl icons with regular icons into the same object
    // like we did in 2.2.
    // af|breadCrumbs::separatorIcon {content: ">"}
    // af|breadCrumbs::separatorIcon:rtl {content: "<"}
    // this will create
    // key=af|breadCrumbs::separatorIcon with TextIcon(">", ">", style, inlineStyle)
    // and
    // key=af|breadCrumbs::separatorIcon:rtl with TextIcon("<", "<", rtlstyle, rtlinlineStyle)
    // then when I go to get the icon af|breadCrumbs::separatorIcon, the skin
    // will know to ask for af|breadCrumbs::separatorIcon:rtl or af|breadCrumbs::separatorIcon
    // depending upon the DIRECTION that is set on the context.
    // The current Icon classes code will not have to change.

    boolean hasContentProperty = false;
    
    // Create the propertyNode array now.
    PropertyNode[] propertyNodeArray = new PropertyNode[noTrPropertyNodeList.size()];
    int i = 0;

    // Loop through each property until we find the 'content' property, then change the url to
    // something we can use in StyleSheetDocument.
    for(PropertyNode propertyNode : noTrPropertyNodeList)
    {
      String propertyName = propertyNode.getName();
      String propertyValue = propertyNode.getValue();
      
      // fix up the url
      if (propertyName.equals("content") && propertyValue != null)
      {
        hasContentProperty = true;
        // is it a text or uri
        if (_isURLValue(propertyValue))
        {
          
          // get the string that is inside of the 'url()'
          String uriString = _getURIString(propertyValue);
          boolean isAbsoluteURI = CSSUtils.isAbsoluteURI(uriString);
          
          // a leading / indicates context-relative
          //      (auto-prefix the servlet context)
          // a leading // indicates server-relative
          //      (don't auto-prefix the servlet context).
          if (!isAbsoluteURI)
          {
            boolean startsWithTwoSlashes = uriString.startsWith("//");
  
            if (!startsWithTwoSlashes && uriString.startsWith("/"))
            {
              uriString = uriString.substring(1);
            }
            else
            {
              // a. if it has two slashes, strip off one.
              // b. if it is an absolute url, don't do anything
              // c. if it a relative url, then it should be relative to
              // the skin file since they wrote the relative url in the skin file.
              
              if (startsWithTwoSlashes)
                uriString = uriString.substring(1);
              else
                uriString = CSSUtils.getAbsoluteURIValue(sourceName, baseSourceURI, uriString);
  
            }
          }
          // At this point, URIImageIcons start with '/' or 'http:',
          // whereas ContextImageIcons uri do not. This is how we will know which type of 
          // Icon to create in StyleSheetDocument. Wrap back up with the 'url()' string so that
          // we will know this is not a TextIcon.
          propertyNodeArray[i] = new PropertyNode(propertyName, _wrapWithURLString(uriString));
        }      
        else if (propertyValue.startsWith("inhibit"))
        {
          propertyNodeArray[i] = new PropertyNode(propertyName, propertyValue);
        }
        else
        {
          String text = trimQuotes(propertyValue);
          propertyNodeArray[i] = new PropertyNode(propertyName, text);
        }
        

      } // end if 'content'
      else
        propertyNodeArray[i] = new PropertyNode(propertyName, propertyValue);
      i++;
    }
 
    // TODO - Add -tr-rule-ref capability for icons.
    // I purposely do not include the -tr-rule-ref at this time.
    // See TRINIDAD-17 for details why. There is a hitch. Even though we treat selectors 
    // with -icon as Icon objects, if there is no content attribute, we also treat them as styles.
    // Well, if they use -tr-rule-ref to import 'content', then we will think it is a style, 
    // and we'll have extra styles. Yet we don't want to resolve selectors just to see if it is
    // really an icon. But we don't want to hurt the person that didn't abide by the -icon rule
    // because this wasn't an enforced rule.
    //
    // if the trRuleRefList is not empty, create IncludeStyleNodes.
    List includeStyleNodes = new ArrayList();

    for(String value : trRuleRefList)
    {
      // parse the value, which will be of this form:
      // -tr-rule-ref: selector(".AFBaseFont:alias") selector(".Foo")
      // where you have more than one selector in an -tr-rule-ref definition
      // or -tr-rule-ref: selector(".AFBaseFont:alias")
      // where you have only one selector in an -tr-rule-ref definition.
      // I want each selector value to be an IncludeStyleNode.

      _addIncludeStyleNodes(value, includeStyleNodes);

    }

    if (selectorName != null)
    {
      // Create a styleNode that we will add to the IconNode.
      StyleNode styleNode =
        new StyleNode(null, // name
                      selectorName,
                      clientRule,
                      propertyNodeArray,
                      null, //TODO jmw trSkinPropertyNodes for icons
                      includeStyleNodes.toArray(new IncludeStyleNode[0]),
                      null, //TODO jmw includePropertyNodes for icons
                      null, //TODO jmw includeCompactPropertyNodes for icons
                      inhibitedProperties
                      );
      
      // Create a new IconNode.
      // don't bother creating the Icon object now (the second property to new IconNode()); 
      // we create the Icon object when we resolve IconNodes in StyleSheetDocument
      // See StyleSheetDocument#getIcons(StyleContext context).      
      iconNodeList.add(new IconNode(selectorName, null, styleNode));
    }

    return hasContentProperty;

  }

  /**
   * Creates a StyleNode object and adds it to the styleNodeList.
   * The StyleNode object gets completely resolved (the trRuleRefList, includeProperyNodes, etc.,
   * get resolved in StyleSheetDocument#_resolveStyleNode). The StyleNode here is the unresolved
   * StyleNode - basically what we have in the skin css file.
   * @param selectorName
   * @param propertyNodeList
   * @param skinPropertyNodeList
   * @param trRuleRefList
   * @param includePropertyNodes
   * @param embeddedIncludePropertyNodes
   * @param inhibitedProperties
   * @param trTextAntialias
   * @param styleNodeList Once the StyleNode is created, it is added to the iconNodeList to be
   * used outside this method.
   *
   */
  private static void _addStyleNode(
    String                    selectorName,
    String                    clientRule,
    List        propertyNodeList,
    List        skinPropertyNodeList,
    List              trRuleRefList,
    List includePropertyNodes,
    List embeddedIncludePropertyNodes,
    Set               inhibitedProperties,
    boolean                   trTextAntialias,
    List           styleNodeList)
  {

    StyleNode styleNode = _createStyleNode(selectorName,
                                           clientRule,
                                           propertyNodeList,
                                           skinPropertyNodeList,
                                           trRuleRefList, 
                                           includePropertyNodes,
                                           embeddedIncludePropertyNodes,
                                           inhibitedProperties, 
                                           trTextAntialias);

    styleNodeList.add(styleNode);

  }

  private static StyleNode _createStyleNode(
    String                    selectorName,
    String                    clientRule,
    List        propertyNodeList,
    List        skinPropertyNodeList,
    List              trRuleRefList,
    List includePropertyNodes,
    List embeddedIncludePropertyNodes,
    Set               inhibitedProperties,
    boolean                   trTextAntialias)
  {
    // these are the styles.
    // At this point I have a selector name and the properties.
    // create a StyleNode based on this information.

    String name = null;
    String selector = null;
    int aliasIndex = selectorName.indexOf(":alias");
    if (aliasIndex > -1)
    {
      if (!selectorName.startsWith("."))
      {
        _LOG.warning("ALIAS_DEFINITION_NOT_STARTING_WITH_DOT", new Object[] { selectorName });
      }
      // :alias means do not output style; it is a namedStyle, so we set
      // the name and not the selector.
      // first, strip off the '.' at the beginning and the :alias bit.
      name = selectorName.substring(1, aliasIndex);
    }
    else
      selector = selectorName;

    // add text-antialias if it is set
    if (trTextAntialias)
    {
      propertyNodeList.add(new PropertyNode("text-antialias", "true"));
    }
    // convert to a PropertyNode[], because StyleNode takes this type.
    PropertyNode[] propertyArray =
      propertyNodeList.toArray(new PropertyNode[propertyNodeList.size()]);

    // if the trRuleRefList is not empty, create IncludeStyleNodes.
    int length = trRuleRefList.size();
    List includeStyleNodes = new ArrayList();

    if (length > 0)
    {
      for(String value : trRuleRefList)
      {
        // parse the value, which will be of this form:
        // -tr-rule-ref: selector(".AFBaseFont:alias") selector(".Foo")
        // where you have more than one selector in an -tr-rule-ref definition
        // or -tr-rule-ref: selector(".AFBaseFont:alias")
        // where you have only one selector in an -tr-rule-ref definition.
        // I want each selector value to be an IncludeStyleNode.

        _addIncludeStyleNodes(value, includeStyleNodes);

      }
    }

    // create a StyleNode
    StyleNode styleNode =
      new StyleNode(name,
                    selector,
                    clientRule,
                    propertyArray,
                    skinPropertyNodeList.isEmpty() ?
                      null : skinPropertyNodeList.toArray(new PropertyNode[0]),
                    includeStyleNodes.toArray(new IncludeStyleNode[0]),
                    includePropertyNodes.isEmpty() ? 
                      null : includePropertyNodes.toArray(new IncludePropertyNode[0]),
                    embeddedIncludePropertyNodes.isEmpty() ? 
                      null : embeddedIncludePropertyNodes.toArray(new EmbeddedIncludePropertyNode[0]),
                    inhibitedProperties,
                    false);
    
    return styleNode;
  }

  /**
   * Create an IncludePropertyNode.
   * The syntax for including a property from another selector is
   * af|commandButton { background-color: -tr-property-ref(".AFTestBackgroundColor:alias"); }
   * .AFTestForegroundColor:alias {color: yellow; font-style:italic}
   * .AFTestBackgroundColor:alias {background-color: -tr-property-ref(".AFTestForegroundColor:alias","color")}
   * @param propertyName name of the property like background-color, color
   * @param propertyValue value that want to include. This is stripped of the
   * -tr-property-ref, so it is of the form ("...")
   */
  private static IncludePropertyNode _createIncludePropertyNode(
    String propertyName, 
    String propertyValue)
  {
    // do a quick sanity check
    if (propertyValue == null || propertyValue.length() < 2)
      return null;
    if (propertyValue.startsWith("(") && propertyValue.endsWith(")"))
    {
      String valueString = propertyValue.substring(1, propertyValue.length() - 1);
      String values[] = valueString.split(",");
      String selectorValue = trimQuotes(values[0]);
      String includedProperty;
      if (values.length == 1)
      {
        includedProperty = propertyName;
      }
      else
      {
        includedProperty = trimQuotes(values[1].trim());
      }

      String selector = null;
      String name = null;
      if (selectorValue.endsWith(":alias"))
      {
        int aliasEndIndex = selectorValue.indexOf(":alias");
        int aliasStartIndex = 0;
        if (selectorValue.startsWith("."))
          aliasStartIndex = 1;
        name = selectorValue.substring(aliasStartIndex, aliasEndIndex);
      }
      else
      {
        selector = selectorValue;
      }
      return new IncludePropertyNode(name, selector, includedProperty, propertyName);
    }
    return null;
  }

  // This is for -tr-rule-ref properties on styles.
  private static void _addIncludeStyleNodes(
    String value,
    List  includeStyleNodes )
  {

    if (value != null)
    {
      // parse string and create IncludeStyleNode for each selector value.
      // the string will be of this form:
      // selector(".AFBaseFont:alias") selector(".MyDarkBackground")
      // or a single selector:
      // selector(".AFBaseFont:alias")
      // if it ends with :alias, it is a namedstyle.

      List selectors = new ArrayList();

      _parseValueIntoSelectors(value, selectors);

      // now take the selector List and convert it to IncludeStyleNodes.
      for (String includeStyle : selectors)
      {
        // if it has :alias at the end it is a named style
        if (includeStyle.endsWith(":alias"))
        {
          // strip off :alias first and the . at the beginning
          
          int endIndex = includeStyle.indexOf(":alias");
          int startIndex = 0;
          if (includeStyle.charAt(0) == '.')
            startIndex = 1;
          else
            _LOG.warning("ALIAS_REFERENCE_NOT_STARTING_WITH_DOT", new Object[] { includeStyle });

          includeStyleNodes.add(new IncludeStyleNode(
                                includeStyle.substring(startIndex, endIndex),
                                null));
        }
        else
          includeStyleNodes.add(new IncludeStyleNode(null, includeStyle));
      }

    }
  }

  /**
   * Parse the value into a List of selector names.
   * @param value - String of the form: selector(".AFBaseFont:alias") selector(".MyDarkBackground")
   * or a single selector: selector(".AFBaseFont:alias")
   * @param selectors a List of selector names. This will be filled in during this method
   * call.
   */
  private static void _parseValueIntoSelectors(
    String value, 
    List selectors)
  {
    String[] test = _SELECTOR_PATTERN.split(value);
    for (int i=0; i < test.length; i++)
    {
      int endIndex = test[i].indexOf(")");
      if (endIndex > -1)
      {
        String selectorValue = test[i].substring(0, endIndex);
        selectorValue = trimQuotes(selectorValue);
        selectors.add(selectorValue);
      }
    }
  }

  private static StyleSheetDocument _createStyleSheetDocument(
    ParseContext context,
    List ssNodeList)
  {

    long timestamp = _getDocumentTimestamp(context);

    return new StyleSheetDocument(ssNodeList.toArray(new StyleSheetNode[0]),
                                    null,
                                    timestamp);
  }

  // Returns the document timestamp for the style sheet that
  // is currently being parsed, taking into account timestamps
  // of any imported style sheets. (copied from StyleSheetDocumentParser)
  private static long _getDocumentTimestamp(ParseContext parseContext)
  {

    long timestamp = StyleSheetDocument.UNKNOWN_TIMESTAMP;

    // The only way to get the timestamp is through the
    // InputStreamProvider.
    InputStreamProvider provider = XMLUtils.getInputStreamProvider(parseContext);

    if (provider != null)
    {
      // And this only works if we are using a File-based or URL-based InputStream
      Object identifier = provider.getIdentifier();
      if (identifier instanceof File)
        timestamp = ((File)identifier).lastModified();
      else if (identifier instanceof URL)
      {
        try
        {
          timestamp = URLUtils.getLastModified((URL) identifier);
        }
        catch (IOException io)
        {
          _LOG.warning("CANNOT_GET_STYLESHEET_DOCUMENT_TIMESTAMP");
        }

      }
    }

    return timestamp;
  }




  // Tests whether the specified property value is an "url" property.
  private static boolean _isURLValue(String propertyValue)
  {
    // URL property values start with "url("
    return propertyValue.startsWith("url(");
  }

  // Returns the uri portion of the url property value
  private static String _getURIString(String propertyValue)
  {
    assert(_isURLValue(propertyValue));

    int uriEnd = propertyValue.indexOf(')');
    String uri = propertyValue.substring(4, uriEnd);

    return trimQuotes(uri);
  }
  
  // wraps the uri in 'url( )' and returns the new String.
  private static String _wrapWithURLString(String uri)
  {
    StringBuilder builder = new StringBuilder(5 + uri.length());
    builder.append("url(");
    builder.append(uri);
    builder.append(")");
    return builder.toString();
    
  }
  
  private static class ResolvedSkinProperties
  {


    ResolvedSkinProperties(
      List               noTrPropertyList,
      List                     trRuleRefList,
      List        includedPropertiesList,
      List embeddedIncludePropertiesList,
      Set                      inhibitedPropertySet,
      List               skinPropertyNodeList,
      boolean trTextAntialias)
    {
      _noTrPropertyList = noTrPropertyList;
      _trRuleRefList = trRuleRefList;
      _includedPropertiesList = includedPropertiesList;
      _embeddedIncludePropertiesList = embeddedIncludePropertiesList;
      _inhibitedPropertySet = inhibitedPropertySet;
      _skinPropertyNodeList = skinPropertyNodeList;
      _trTextAntialias = trTextAntialias;
    }

    public List getNoTrPropertyList()
    {
      return _noTrPropertyList;
    }

    public List getTrRuleRefList()
    {
      return _trRuleRefList;
    }

    public List getIncludedPropertiesList()
    {
      return _includedPropertiesList;
    }

    public List getEmbeddedIncludePropertiesList()
    {
      return _embeddedIncludePropertiesList;
    }

    public List getSkinPropertyNodeList()
    {
      return _skinPropertyNodeList;
    }

    public Set getInhibitedProperties()
    {
      return _inhibitedPropertySet;
    }

    public boolean isTrTextAntialias()
    {
      return _trTextAntialias;
    }

    private Set               _inhibitedPropertySet;
    private List        _noTrPropertyList;
    private List              _trRuleRefList;
    private List _includedPropertiesList;
    private List _embeddedIncludePropertiesList;
    private List        _skinPropertyNodeList;
    private boolean                   _trTextAntialias;
  }

  // Custom Trinidad css properties:
  //-tr-rule-ref, -tr-inhibit, -tr-text-antialias
  private static final String _TR_PROPERTY_PREFIX = "-tr-";
  // For backwards compatibility, keep the -ora- css properties in
  // addition to the -tr- css properties.
  private static final String _ORA_PROPERTY_PREFIX = "-ora-";
  private static final String _PROPERTY_RULE_REF = "rule-ref";
  private static final String _PROPERTY_INHIBIT = "inhibit";
  private static final String _INCLUDE_PROPERTY = "-tr-property-ref";
  private static final String _PROPERTY_TEXT_ANTIALIAS = "text-antialias";

  private static final Pattern _SPACE_PATTERN = Pattern.compile("[\\s]+");
  private static final Pattern _SELECTOR_PATTERN = Pattern.compile("selector\\(");

  static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
    SkinStyleSheetParserUtils.class);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy