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

com.helger.css.reader.CSSReader Maven / Gradle / Ivy

There is a newer version: 5.0.12
Show newest version
/**
 * Copyright (C) 2014-2016 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed 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 com.helger.css.reader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.WillClose;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.PresentForCodeCoverage;
import com.helger.commons.charset.CCharset;
import com.helger.commons.charset.CharsetManager;
import com.helger.commons.charset.EUnicodeBOM;
import com.helger.commons.collection.ArrayHelper;
import com.helger.commons.concurrent.SimpleReadWriteLock;
import com.helger.commons.io.IHasInputStream;
import com.helger.commons.io.IHasReader;
import com.helger.commons.io.provider.IReaderProvider;
import com.helger.commons.io.resource.FileSystemResource;
import com.helger.commons.io.resource.IReadableResource;
import com.helger.commons.io.stream.NonBlockingPushbackInputStream;
import com.helger.commons.io.stream.NonBlockingStringReader;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.io.streamprovider.StringInputStreamProvider;
import com.helger.commons.io.streamprovider.StringReaderProvider;
import com.helger.css.ECSSVersion;
import com.helger.css.decl.CascadingStyleSheet;
import com.helger.css.handler.CSSHandler;
import com.helger.css.handler.DoNothingCSSParseExceptionCallback;
import com.helger.css.handler.ICSSParseExceptionCallback;
import com.helger.css.handler.LoggingCSSParseExceptionCallback;
import com.helger.css.parser.CSSCharStream;
import com.helger.css.parser.CSSNode;
import com.helger.css.parser.CSSParseHelper;
import com.helger.css.parser.CharStream;
import com.helger.css.parser.ParseException;
import com.helger.css.parser.ParserCSS21;
import com.helger.css.parser.ParserCSS21TokenManager;
import com.helger.css.parser.ParserCSS30;
import com.helger.css.parser.ParserCSS30TokenManager;
import com.helger.css.parser.ParserCSSCharsetDetector;
import com.helger.css.parser.ParserCSSCharsetDetectorTokenManager;
import com.helger.css.parser.TokenMgrError;
import com.helger.css.reader.errorhandler.ICSSParseErrorHandler;
import com.helger.css.reader.errorhandler.ThrowingCSSParseErrorHandler;

/**
 * This is the central user class for reading and parsing CSS from different
 * sources. This class reads full CSS declarations only. To read only a
 * declaration list (like from an HTML <style> attribute) the
 * {@link CSSReaderDeclarationList} is available.
 *
 * @author Philip Helger
 */
@ThreadSafe
public final class CSSReader
{
  private static final Logger s_aLogger = LoggerFactory.getLogger (CSSReader.class);
  private static final SimpleReadWriteLock s_aRWLock = new SimpleReadWriteLock ();

  // Use the ThrowingCSSParseErrorHandler for maximum backward compatibility
  @GuardedBy ("s_aRWLock")
  private static ICSSParseErrorHandler s_aDefaultParseErrorHandler = new ThrowingCSSParseErrorHandler ();

  // Use the LoggingCSSParseExceptionHandler for maximum backward compatibility
  @GuardedBy ("s_aRWLock")
  private static ICSSParseExceptionCallback s_aDefaultParseExceptionHandler = new LoggingCSSParseExceptionCallback ();

  @PresentForCodeCoverage
  @SuppressWarnings ("unused")
  private static final CSSReader s_aInstance = new CSSReader ();

  private CSSReader ()
  {}

  /**
   * @return The default CSS parse error handler. May be null. For
   *         backwards compatibility reasons this is be default an instance of
   *         {@link ThrowingCSSParseErrorHandler}.
   */
  @Nullable
  public static ICSSParseErrorHandler getDefaultParseErrorHandler ()
  {
    s_aRWLock.readLock ().lock ();
    try
    {
      return s_aDefaultParseErrorHandler;
    }
    finally
    {
      s_aRWLock.readLock ().unlock ();
    }
  }

  /**
   * Set the default CSS parse error handler (for recoverable errors).
   *
   * @param aDefaultParseErrorHandler
   *        The new default error handler to be used. May be null
   *        to indicate that no special error handler should be used.
   */
  public static void setDefaultParseErrorHandler (@Nullable final ICSSParseErrorHandler aDefaultParseErrorHandler)
  {
    s_aRWLock.writeLock ().lock ();
    try
    {
      s_aDefaultParseErrorHandler = aDefaultParseErrorHandler;
    }
    finally
    {
      s_aRWLock.writeLock ().unlock ();
    }
  }

  /**
   * @return The default CSS parse exception handler. May not be
   *         null. For backwards compatibility reasons this is be
   *         default an instance of {@link LoggingCSSParseExceptionCallback}.
   * @since 3.7.4
   */
  @Nonnull
  public static ICSSParseExceptionCallback getDefaultParseExceptionHandler ()
  {
    s_aRWLock.readLock ().lock ();
    try
    {
      return s_aDefaultParseExceptionHandler;
    }
    finally
    {
      s_aRWLock.readLock ().unlock ();
    }
  }

  /**
   * Set the default CSS parse exception handler (for unrecoverable errors).
   *
   * @param aDefaultParseExceptionHandler
   *        The new default exception handler to be used. May not be
   *        null.
   * @since 3.7.4
   */
  public static void setDefaultParseExceptionHandler (@Nonnull final ICSSParseExceptionCallback aDefaultParseExceptionHandler)
  {
    ValueEnforcer.notNull (aDefaultParseExceptionHandler, "DefaultParseExceptionHandler");

    s_aRWLock.writeLock ().lock ();
    try
    {
      s_aDefaultParseExceptionHandler = aDefaultParseExceptionHandler;
    }
    finally
    {
      s_aRWLock.writeLock ().unlock ();
    }
  }

  /**
   * Main reading of the CSS
   *
   * @param aCharStream
   *        The stream to read from. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomErrorHandler
   *        A custom handler for recoverable errors. May be null.
   * @param aCustomExceptionHandler
   *        A custom handler for unrecoverable errors. May not be
   *        null.
   * @param bBrowserCompliantMode
   *        true for browser compliant parsing, false
   *        for default parsing.
   * @return null if parsing failed with an unrecoverable error
   *         (and no throwing exception handler is used), or null
   *         if a recoverable error occurred and no
   *         {@link com.helger.css.reader.errorhandler.ThrowingCSSParseErrorHandler}
   *         was used or non-null if parsing succeeded.
   */
  @Nullable
  private static CSSNode _readStyleSheet (@Nonnull final CharStream aCharStream,
                                          @Nonnull final ECSSVersion eVersion,
                                          @Nullable final ICSSParseErrorHandler aCustomErrorHandler,
                                          @Nonnull final ICSSParseExceptionCallback aCustomExceptionHandler,
                                          final boolean bBrowserCompliantMode)
  {
    try
    {
      switch (eVersion)
      {
        case CSS21:
        {
          final ParserCSS21TokenManager aTokenHdl = new ParserCSS21TokenManager (aCharStream);
          final ParserCSS21 aParser = new ParserCSS21 (aTokenHdl);
          aParser.setCustomErrorHandler (aCustomErrorHandler);
          aParser.setBrowserCompliantMode (bBrowserCompliantMode);
          // Main parsing
          return aParser.styleSheet ();
        }
        case CSS30:
        {
          final ParserCSS30TokenManager aTokenHdl = new ParserCSS30TokenManager (aCharStream);
          final ParserCSS30 aParser = new ParserCSS30 (aTokenHdl);
          aParser.setCustomErrorHandler (aCustomErrorHandler);
          aParser.setBrowserCompliantMode (bBrowserCompliantMode);
          // Main parsing
          return aParser.styleSheet ();
        }
        default:
          throw new IllegalArgumentException ("Unsupported CSS version " + eVersion);
      }
    }
    catch (final ParseException ex)
    {
      // Unrecoverable error
      aCustomExceptionHandler.onException (ex);
      return null;
    }
    catch (final TokenMgrError ex)
    {
      // As e.g. indicated by https://github.com/phax/ph-css/issues/9
      aCustomExceptionHandler.onException (new ParseException (ex.getMessage ()));
      return null;
    }
  }

  /**
   * Check if the passed CSS file can be parsed without error
   *
   * @param aFile
   *        The file to be parsed. May not be null.
   * @param aFallbackCharset
   *        The charset to be used for reading the CSS file in case neither a
   *        @charset rule nor a BOM is present. May not be
   *        null.
   * @param eVersion
   *        The CSS version to be used for scanning. May not be
   *        null.
   * @return true if the file can be parsed without error,
   *         false if not
   */
  public static boolean isValidCSS (@Nonnull final File aFile,
                                    @Nonnull final Charset aFallbackCharset,
                                    @Nonnull final ECSSVersion eVersion)
  {
    return isValidCSS (new FileSystemResource (aFile), aFallbackCharset, eVersion);
  }

  /**
   * Check if the passed CSS resource can be parsed without error
   *
   * @param aRes
   *        The resource to be parsed. May not be null.
   * @param aFallbackCharset
   *        The charset to be used for reading the CSS file in case neither a
   *        @charset rule nor a BOM is present. May not be
   *        null.
   * @param eVersion
   *        The CSS version to be used for scanning. May not be
   *        null.
   * @return true if the file can be parsed without error,
   *         false if not
   */
  public static boolean isValidCSS (@Nonnull final IReadableResource aRes,
                                    @Nonnull final Charset aFallbackCharset,
                                    @Nonnull final ECSSVersion eVersion)
  {
    ValueEnforcer.notNull (aRes, "Resource");
    ValueEnforcer.notNull (aFallbackCharset, "FallbackCharset");
    ValueEnforcer.notNull (eVersion, "Version");

    final Reader aReader = aRes.getReader (aFallbackCharset);
    if (aReader == null)
    {
      s_aLogger.warn ("Failed to open CSS reader " + aRes);
      return false;
    }
    return isValidCSS (aReader, eVersion);
  }

  /**
   * Check if the passed input stream can be resembled to valid CSS content.
   * This is accomplished by fully parsing the CSS file each time the method is
   * called. This is similar to calling
   * {@link #readFromStream(IHasInputStream,Charset, ECSSVersion)} and checking
   * for a non-null result.
   *
   * @param aIS
   *        The input stream to use. Is automatically closed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @return true if the CSS is valid according to the version,
   *         false if not
   */
  public static boolean isValidCSS (@Nonnull @WillClose final InputStream aIS,
                                    @Nonnull final Charset aFallbackCharset,
                                    @Nonnull final ECSSVersion eVersion)
  {
    ValueEnforcer.notNull (aIS, "InputStream");
    ValueEnforcer.notNull (aFallbackCharset, "FallbackCharset");

    return isValidCSS (StreamHelper.createReader (aIS, aFallbackCharset), eVersion);
  }

  /**
   * Check if the passed String can be resembled to valid CSS content. This is
   * accomplished by fully parsing the CSS file each time the method is called.
   * This is similar to calling
   * {@link #readFromString(String, Charset, ECSSVersion)} and checking for a
   * non-null result.
   *
   * @param sCSS
   *        The CSS string to scan. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @return true if the CSS is valid according to the version,
   *         false if not
   */
  public static boolean isValidCSS (@Nonnull final String sCSS, @Nonnull final ECSSVersion eVersion)
  {
    ValueEnforcer.notNull (sCSS, "CSS");

    return isValidCSS (new NonBlockingStringReader (sCSS), eVersion);
  }

  /**
   * Check if the passed reader can be resembled to valid CSS content. This is
   * accomplished by fully parsing the CSS each time the method is called. This
   * is similar to calling
   * {@link #readFromStream(IHasInputStream, Charset, ECSSVersion)} and checking
   * for a non-null result.
   *
   * @param aReader
   *        The reader to use. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @return true if the CSS is valid according to the version,
   *         false if not
   */
  public static boolean isValidCSS (@Nonnull @WillClose final Reader aReader, @Nonnull final ECSSVersion eVersion)
  {
    ValueEnforcer.notNull (aReader, "Reader");
    ValueEnforcer.notNull (eVersion, "Version");

    try
    {
      final CSSCharStream aCharStream = new CSSCharStream (aReader);
      final CSSNode aNode = _readStyleSheet (aCharStream,
                                             eVersion,
                                             getDefaultParseErrorHandler (),
                                             new DoNothingCSSParseExceptionCallback (),
                                             false);
      return aNode != null;
    }
    finally
    {
      StreamHelper.close (aReader);
    }
  }

  /**
   * Read the CSS from the passed String using a byte stream.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromString (@Nonnull final String sCSS,
                                                    @Nonnull final Charset aFallbackCharset,
                                                    @Nonnull final ECSSVersion eVersion)
  {
    return readFromStringStream (sCSS,
                                 new CSSReaderSettings ().setFallbackCharset (aFallbackCharset)
                                                         .setCSSVersion (eVersion));
  }

  /**
   * Read the CSS from the passed String using a byte stream.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomErrorHandler
   *        An optional custom error handler that can be used to collect the
   *        recoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromString (@Nonnull final String sCSS,
                                                    @Nonnull final Charset aFallbackCharset,
                                                    @Nonnull final ECSSVersion eVersion,
                                                    @Nullable final ICSSParseErrorHandler aCustomErrorHandler)
  {
    return readFromStringStream (sCSS,
                                 new CSSReaderSettings ().setFallbackCharset (aFallbackCharset)
                                                         .setCSSVersion (eVersion)
                                                         .setCustomErrorHandler (aCustomErrorHandler));
  }

  /**
   * Read the CSS from the passed String using a byte stream.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomExceptionHandler
   *        An optional custom exception handler that can be used to collect the
   *        unrecoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromString (@Nonnull final String sCSS,
                                                    @Nonnull final Charset aFallbackCharset,
                                                    @Nonnull final ECSSVersion eVersion,
                                                    @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler)
  {
    return readFromStringStream (sCSS,
                                 new CSSReaderSettings ().setFallbackCharset (aFallbackCharset)
                                                         .setCSSVersion (eVersion)
                                                         .setCustomExceptionHandler (aCustomExceptionHandler));
  }

  /**
   * Read the CSS from the passed String using a byte stream.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomErrorHandler
   *        An optional custom error handler that can be used to collect the
   *        recoverable parsing errors. May be null.
   * @param aCustomExceptionHandler
   *        An optional custom exception handler that can be used to collect the
   *        unrecoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   * @since 3.7.3
   */
  @Nullable
  public static CascadingStyleSheet readFromString (@Nonnull final String sCSS,
                                                    @Nonnull final Charset aFallbackCharset,
                                                    @Nonnull final ECSSVersion eVersion,
                                                    @Nullable final ICSSParseErrorHandler aCustomErrorHandler,
                                                    @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler)
  {
    return readFromStringStream (sCSS,
                                 new CSSReaderSettings ().setFallbackCharset (aFallbackCharset)
                                                         .setCSSVersion (eVersion)
                                                         .setCustomErrorHandler (aCustomErrorHandler)
                                                         .setCustomExceptionHandler (aCustomExceptionHandler));
  }

  /**
   * Read the CSS from the passed String using a byte stream.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param aSettings
   *        The settings to be used for reading the CSS. May not be
   *        null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   * @since 3.8.2
   */
  @Nullable
  public static CascadingStyleSheet readFromStringStream (@Nonnull final String sCSS,
                                                          @Nonnull final CSSReaderSettings aSettings)
  {
    return readFromStream (new StringInputStreamProvider (sCSS, aSettings.getFallbackCharset ()), aSettings);
  }

  /**
   * Read the CSS from the passed String using a character stream. An eventually
   * contained @charset rule is ignored.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   * @since 3.7.3
   */
  @Nullable
  public static CascadingStyleSheet readFromString (@Nonnull final String sCSS, @Nonnull final ECSSVersion eVersion)
  {
    return readFromStringReader (sCSS, new CSSReaderSettings ().setCSSVersion (eVersion));
  }

  /**
   * Read the CSS from the passed String using a character stream. An eventually
   * contained @charset rule is ignored.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomErrorHandler
   *        An optional custom error handler that can be used to collect the
   *        recoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   * @since 3.7.3
   */
  @Nullable
  public static CascadingStyleSheet readFromString (@Nonnull final String sCSS,
                                                    @Nonnull final ECSSVersion eVersion,
                                                    @Nullable final ICSSParseErrorHandler aCustomErrorHandler)
  {
    return readFromStringReader (sCSS,
                                 new CSSReaderSettings ().setCSSVersion (eVersion)
                                                         .setCustomErrorHandler (aCustomErrorHandler));
  }

  /**
   * Read the CSS from the passed String using a character stream. An eventually
   * contained @charset rule is ignored.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomExceptionHandler
   *        An optional custom exception handler that can be used to collect the
   *        unrecoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   * @since 3.7.3
   */
  @Nullable
  public static CascadingStyleSheet readFromString (@Nonnull final String sCSS,
                                                    @Nonnull final ECSSVersion eVersion,
                                                    @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler)
  {
    return readFromStringReader (sCSS,
                                 new CSSReaderSettings ().setCSSVersion (eVersion)
                                                         .setCustomExceptionHandler (aCustomExceptionHandler));
  }

  /**
   * Read the CSS from the passed String using a character stream. An eventually
   * contained @charset rule is ignored.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomErrorHandler
   *        An optional custom error handler that can be used to collect the
   *        recoverable parsing errors. May be null.
   * @param aCustomExceptionHandler
   *        An optional custom exception handler that can be used to collect the
   *        unrecoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   * @since 3.7.3
   */
  @Nullable
  public static CascadingStyleSheet readFromString (@Nonnull final String sCSS,
                                                    @Nonnull final ECSSVersion eVersion,
                                                    @Nullable final ICSSParseErrorHandler aCustomErrorHandler,
                                                    @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler)
  {
    return readFromStringReader (sCSS,
                                 new CSSReaderSettings ().setCSSVersion (eVersion)
                                                         .setCustomErrorHandler (aCustomErrorHandler)
                                                         .setCustomExceptionHandler (aCustomExceptionHandler));
  }

  /**
   * Read the CSS from the passed String using a character stream. An eventually
   * contained @charset rule is ignored.
   *
   * @param sCSS
   *        The source string containing the CSS to be parsed. May not be
   *        null.
   * @param aSettings
   *        The settings to be used for reading the CSS. May not be
   *        null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   * @since 3.8.2
   */
  @Nullable
  public static CascadingStyleSheet readFromStringReader (@Nonnull final String sCSS,
                                                          @Nonnull final CSSReaderSettings aSettings)
  {
    return readFromReader (new StringReaderProvider (sCSS), aSettings);
  }

  /**
   * Read the CSS from the passed File.
   *
   * @param aFile
   *        The file containing the CSS to be parsed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromFile (@Nonnull final File aFile,
                                                  @Nonnull final Charset aFallbackCharset,
                                                  @Nonnull final ECSSVersion eVersion)
  {
    return readFromFile (aFile,
                         new CSSReaderSettings ().setFallbackCharset (aFallbackCharset).setCSSVersion (eVersion));
  }

  /**
   * Read the CSS from the passed File.
   *
   * @param aFile
   *        The file containing the CSS to be parsed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomErrorHandler
   *        An optional custom error handler that can be used to collect the
   *        recoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromFile (@Nonnull final File aFile,
                                                  @Nonnull final Charset aFallbackCharset,
                                                  @Nonnull final ECSSVersion eVersion,
                                                  @Nullable final ICSSParseErrorHandler aCustomErrorHandler)
  {
    return readFromFile (aFile,
                         new CSSReaderSettings ().setFallbackCharset (aFallbackCharset)
                                                 .setCSSVersion (eVersion)
                                                 .setCustomErrorHandler (aCustomErrorHandler));
  }

  /**
   * Read the CSS from the passed File.
   *
   * @param aFile
   *        The file containing the CSS to be parsed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomExceptionHandler
   *        An optional custom exception handler that can be used to collect the
   *        unrecoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromFile (@Nonnull final File aFile,
                                                  @Nonnull final Charset aFallbackCharset,
                                                  @Nonnull final ECSSVersion eVersion,
                                                  @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler)
  {
    return readFromFile (aFile,
                         new CSSReaderSettings ().setFallbackCharset (aFallbackCharset)
                                                 .setCSSVersion (eVersion)
                                                 .setCustomExceptionHandler (aCustomExceptionHandler));
  }

  /**
   * Read the CSS from the passed File.
   *
   * @param aFile
   *        The file containing the CSS to be parsed. May not be
   *        null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomErrorHandler
   *        An optional custom error handler that can be used to collect the
   *        recoverable parsing errors. May be null.
   * @param aCustomExceptionHandler
   *        An optional custom exception handler that can be used to collect the
   *        unrecoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromFile (@Nonnull final File aFile,
                                                  @Nonnull final Charset aFallbackCharset,
                                                  @Nonnull final ECSSVersion eVersion,
                                                  @Nullable final ICSSParseErrorHandler aCustomErrorHandler,
                                                  @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler)
  {
    return readFromFile (aFile,
                         new CSSReaderSettings ().setFallbackCharset (aFallbackCharset)
                                                 .setCSSVersion (eVersion)
                                                 .setCustomErrorHandler (aCustomErrorHandler)
                                                 .setCustomExceptionHandler (aCustomExceptionHandler));
  }

  /**
   * Read the CSS from the passed File.
   *
   * @param aFile
   *        The file containing the CSS to be parsed. May not be
   *        null.
   * @param aSettings
   *        The settings to be used for reading the CSS. May not be
   *        null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   * @since 3.8.2
   */
  @Nullable
  public static CascadingStyleSheet readFromFile (@Nonnull final File aFile, @Nonnull final CSSReaderSettings aSettings)
  {
    return readFromStream (new FileSystemResource (aFile), aSettings);
  }

  /**
   * Read the CSS from the passed {@link IHasInputStream}. If the CSS contains
   * an explicit charset, the whole CSS is parsed again, with the charset found
   * inside the file, so the passed {@link IHasInputStream} must be able to
   * create a new input stream on second invocation!
   *
   * @param aISP
   *        The input stream provider to use. Must be able to create new input
   *        streams on every invocation, in case an explicit charset node was
   *        found. May not be null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromStream (@Nonnull final IHasInputStream aISP,
                                                    @Nonnull final Charset aFallbackCharset,
                                                    @Nonnull final ECSSVersion eVersion)
  {
    return readFromStream (aISP,
                           new CSSReaderSettings ().setFallbackCharset (aFallbackCharset).setCSSVersion (eVersion));
  }

  /**
   * Read the CSS from the passed {@link IHasInputStream}. If the CSS contains
   * an explicit charset, the whole CSS is parsed again, with the charset found
   * inside the file, so the passed {@link IHasInputStream} must be able to
   * create a new input stream on second invocation!
   *
   * @param aISP
   *        The input stream provider to use. Must be able to create new input
   *        streams on every invocation, in case an explicit charset node was
   *        found. May not be null.
   * @param aFallbackCharset
   *        The charset to be used in case neither a @charset rule
   *        nor a BOM is present. May not be null.
   * @param eVersion
   *        The CSS version to use. May not be null.
   * @param aCustomErrorHandler
   *        An optional custom error handler that can be used to collect the
   *        recoverable parsing errors. May be null.
   * @return null if reading failed, the CSS declarations
   *         otherwise.
   */
  @Nullable
  public static CascadingStyleSheet readFromStream (@Nonnull final IHasInputStream aISP,
                                                    @Nonnull final Charset aFallbackCharset,
                                                    @Nonnull final ECSSVersion eVersion,
                                                    @Nullable final ICSSParseErrorHandler aCustomErrorHandler)
  {
    return readFromStream (aISP,
                           new CSSReaderSettings ().setFallbackCharset (aFallbackCharset)
                                                   .setCSSVersion (eVersion)
                                                   .setCustomErrorHandler (aCustomErrorHandler));
  }

  private static final class InputStreamAndCharset implements IHasInputStream
  {
    private final InputStream m_aIS;
    private final Charset m_aCharset;

    public InputStreamAndCharset (@Nullable final InputStream aIS, @Nullable final Charset aCharset)
    {
      m_aIS = aIS;
      m_aCharset = aCharset;
    }

    @Nullable
    public InputStream getInputStream ()
    {
      return m_aIS;
    }

    public boolean hasInputStream ()
    {
      return m_aIS != null;
    }

    @Nullable
    public Charset getCharset ()
    {
      return m_aCharset;
    }
  }

  /**
   * Open the {@link InputStream} provided by the passed {@link IHasInputStream}
   * . If a BOM is present in the {@link InputStream} it is read and if possible
   * the charset is automatically determined from the BOM.
   *
   * @param aISP
   *        The input stream provider to use. May not be null.
   * @return null if no InputStream could be opened, the pair with
   *         non-null {@link InputStream} and a potentially
   *         null {@link Charset} otherwise.
   */
  @Nullable
  private static InputStreamAndCharset _getInputStreamWithoutBOM (@Nonnull final IHasInputStream aISP)
  {
    // Try to open input stream
    final InputStream aIS = aISP.getInputStream ();
    if (aIS == null)
      return null;

    // Check for BOM
    final int nMaxBOMBytes = EUnicodeBOM.getMaximumByteCount ();
    final NonBlockingPushbackInputStream aPIS = new NonBlockingPushbackInputStream (aIS, nMaxBOMBytes);
    try
    {
      final byte [] aBOM = new byte [nMaxBOMBytes];
      final int nReadBOMBytes = aPIS.read (aBOM);
      Charset aDeterminedCharset = null;
      if (nReadBOMBytes > 0)
      {
        // Some byte BOMs were read
        final EUnicodeBOM eBOM = EUnicodeBOM.getFromBytesOrNull (ArrayHelper.getCopy (aBOM, 0, nReadBOMBytes));
        if (eBOM == null)
        {
          // Unread the whole BOM
          aPIS.unread (aBOM, 0, nReadBOMBytes);
        }
        else
        {
          // Unread the unnecessary parts of the BOM
          final int nBOMBytes = eBOM.getByteCount ();
          if (nBOMBytes < nReadBOMBytes)
            aPIS.unread (aBOM, nBOMBytes, nReadBOMBytes - nBOMBytes);

          // Use the Charset of the BOM - maybe null!
          aDeterminedCharset = eBOM.getCharset ();
        }
      }
      return new InputStreamAndCharset (aPIS, aDeterminedCharset);
    }
    catch (final IOException ex)
    {
      s_aLogger.error ("Failed to determine BOM", ex);
      return null;
    }
  }

  /**
   * Determine the charset to read the CSS file. The logic is as follows:
   * 
    *
  1. Determine the charset used to read the @charset from the stream. If a * BOM is present and a matching Charset is present, this charset is used. As * a fallback the CSS file is initially read with ISO-8859-1.
  2. *
  3. If the CSS content contains a valid @charset rule, the defined charset * is returned even if a different BOM is present.
  4. *
  5. If the CSS content does not contain a valid @charset rule than the * charset of the BOM is returned (if any).
  6. *
  7. Otherwise null is returned.
  8. *
* * @param aISP * The input stream provider to read from. May not be null * . * @return null if the input stream could not be opened or if * neither a BOM nor a charset is specified. Otherwise a non- * null Charset is returned. * @throws IllegalStateException * if an invalid charset is supplied */ @Nullable public static Charset getCharsetDeclaredInCSS (@Nonnull final IHasInputStream aISP) { ValueEnforcer.notNull (aISP, "InputStreamProvider"); // Open input stream final InputStreamAndCharset aISAndBOM = _getInputStreamWithoutBOM (aISP); if (aISAndBOM == null || !aISAndBOM.hasInputStream ()) { // Failed to open stream, so no charset! return null; } final InputStream aIS = aISAndBOM.getInputStream (); final Charset aBOMCharset = aISAndBOM.getCharset (); Charset aStreamCharset = aBOMCharset; if (aStreamCharset == null) { // Always read as ISO-8859-1 as everything contained in the CSS charset // declaration can be handled by this charset // A known problem is when the file is UTF-16, UTF-16BE, UTF-16LE etc. // encoded. In this case a BOM must be present to read the file correctly! aStreamCharset = CCharset.CHARSET_ISO_8859_1_OBJ; } final Reader aReader = StreamHelper.createReader (aIS, aStreamCharset); try { // Read with the Stream charset final CSSCharStream aCharStream = new CSSCharStream (aReader); final ParserCSSCharsetDetectorTokenManager aTokenHdl = new ParserCSSCharsetDetectorTokenManager (aCharStream); final ParserCSSCharsetDetector aParser = new ParserCSSCharsetDetector (aTokenHdl); final String sCharsetName = aParser.styleSheetCharset ().getText (); if (sCharsetName == null) { // No charset specified - use the one from the BOM (may be null) return aISAndBOM.getCharset (); } // Remove leading and trailing quotes from value final String sPlainCharsetName = CSSParseHelper.extractStringValue (sCharsetName); final Charset aReadCharset = CharsetManager.getCharsetFromName (sPlainCharsetName); if (aBOMCharset != null && !aBOMCharset.equals (aReadCharset)) { // BOM charset different from read charset s_aLogger.warn ("The charset found in the CSS data (" + aReadCharset.name () + ") differs from the charset determined by the BOM (" + aBOMCharset.name () + ") -> Using the read charset"); } return aReadCharset; } catch (final ParseException ex) { // Should never occur, as the parse exception is caught inside the // grammar! throw new IllegalStateException ("Failed to parse CSS charset definition", ex); } catch (final TokenMgrError ex) { // As e.g. indicated by https://github.com/phax/ph-css/issues/9 throw new IllegalStateException ("Failed to parse CSS charset definition", ex); } finally { StreamHelper.close (aReader); } } /** * Read the CSS from the passed {@link IHasInputStream}. If the CSS contains * an explicit charset, the whole CSS is parsed again, with the charset found * inside the file, so the passed {@link IHasInputStream} must be able to * create a new input stream on second invocation! * * @param aISP * The input stream provider to use. Must be able to create new input * streams on every invocation, in case an explicit charset node was * found. May not be null. * @param aFallbackCharset * The charset to be used in case neither a @charset rule * nor a BOM is present. May not be null. * @param eVersion * The CSS version to use. May not be null. * @param aCustomExceptionHandler * An optional custom exception handler that can be used to collect the * unrecoverable parsing errors. May be null. * @return null if reading failed, the CSS declarations * otherwise. */ @Nullable public static CascadingStyleSheet readFromStream (@Nonnull final IHasInputStream aISP, @Nonnull final Charset aFallbackCharset, @Nonnull final ECSSVersion eVersion, @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler) { return readFromStream (aISP, new CSSReaderSettings ().setFallbackCharset (aFallbackCharset) .setCSSVersion (eVersion) .setCustomExceptionHandler (aCustomExceptionHandler)); } /** * Read the CSS from the passed {@link IHasInputStream}. If the CSS contains * an explicit charset, the whole CSS is parsed again, with the charset found * inside the file, so the passed {@link IHasInputStream} must be able to * create a new input stream on second invocation! * * @param aISP * The input stream provider to use. Must be able to create new input * streams on every invocation, in case an explicit charset node was * found. May not be null. * @param aFallbackCharset * The charset to be used in case neither a @charset rule * nor a BOM is present. May not be null. * @param eVersion * The CSS version to use. May not be null. * @param aCustomErrorHandler * An optional custom error handler that can be used to collect the * recoverable parsing errors. May be null. * @param aCustomExceptionHandler * An optional custom exception handler that can be used to collect the * unrecoverable parsing errors. May be null. * @return null if reading failed, the CSS declarations * otherwise. */ @Nullable public static CascadingStyleSheet readFromStream (@Nonnull final IHasInputStream aISP, @Nonnull final Charset aFallbackCharset, @Nonnull final ECSSVersion eVersion, @Nullable final ICSSParseErrorHandler aCustomErrorHandler, @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler) { return readFromStream (aISP, new CSSReaderSettings ().setFallbackCharset (aFallbackCharset) .setCSSVersion (eVersion) .setCustomErrorHandler (aCustomErrorHandler) .setCustomExceptionHandler (aCustomExceptionHandler)); } /** * Read the CSS from the passed {@link IHasInputStream}. If the CSS contains * an explicit charset, the whole CSS is parsed again, with the charset found * inside the file, so the passed {@link IHasInputStream} must be able to * create a new input stream on second invocation! * * @param aISP * The input stream provider to use. Must be able to create new input * streams on every invocation, in case an explicit charset node was * found. May not be null. * @param aSettings * The settings to be used for reading the CSS. May not be * null. * @return null if reading failed, the CSS declarations * otherwise. * @since 3.8.2 */ @Nullable public static CascadingStyleSheet readFromStream (@Nonnull final IHasInputStream aISP, @Nonnull final CSSReaderSettings aSettings) { ValueEnforcer.notNull (aISP, "InputStreamProvider"); ValueEnforcer.notNull (aSettings, "Settings"); Charset aCharsetToUse; // Check if the CSS contains a declared charset or as an alternative use the // Charset from the BOM Charset aDeclaredCharset; try { aDeclaredCharset = getCharsetDeclaredInCSS (aISP); } catch (final IllegalStateException ex) { // Failed to parse CSS at a very low level return null; } if (aDeclaredCharset != null) { if (s_aLogger.isDebugEnabled ()) s_aLogger.debug ("Reading CSS definition again with explicit charset '" + aDeclaredCharset.name () + "'"); aCharsetToUse = aDeclaredCharset; } else { // No charset declared - use fallback aCharsetToUse = aSettings.getFallbackCharset (); } // Open input stream final InputStreamAndCharset aISAndBOM = _getInputStreamWithoutBOM (aISP); if (aISAndBOM == null || !aISAndBOM.hasInputStream ()) { // Failed to open stream! return null; } final InputStream aIS = aISAndBOM.getInputStream (); final Reader aReader = StreamHelper.createReader (aIS, aCharsetToUse); final ECSSVersion eVersion = aSettings.getCSSVersion (); try { final CSSCharStream aCharStream = new CSSCharStream (aReader); // Use the default CSS parse error handler if none is provided ICSSParseErrorHandler aRealErrorHandler = aSettings.getCustomErrorHandler (); if (aRealErrorHandler == null) aRealErrorHandler = getDefaultParseErrorHandler (); // Use the default CSS exception handler if none is provided ICSSParseExceptionCallback aRealExceptionHandler = aSettings.getCustomExceptionHandler (); if (aRealExceptionHandler == null) aRealExceptionHandler = getDefaultParseExceptionHandler (); final boolean bBrowserCompliantMode = aSettings.isBrowserCompliantMode (); final CSSNode aNode = _readStyleSheet (aCharStream, eVersion, aRealErrorHandler, aRealExceptionHandler, bBrowserCompliantMode); // Failed to interpret content as CSS? if (aNode == null) return null; // Convert the AST to a domain object return CSSHandler.readCascadingStyleSheetFromNode (eVersion, aNode); } finally { StreamHelper.close (aReader); } } /** * Read the CSS from the passed {@link IReaderProvider}. If the CSS contains * an explicit @charset rule, it is ignored and the charset used * to create the reader is used instead! * * @param aRP * The reader provider to use. The reader is retrieved exactly once and * closed anyway. May not be null. * @param eVersion * The CSS version to use. May not be null. * @param aCustomErrorHandler * An optional custom error handler that can be used to collect the * recoverable parsing errors. May be null. * @param aCustomExceptionHandler * An optional custom exception handler that can be used to collect the * unrecoverable parsing errors. May be null. * @return null if reading failed, the CSS declarations * otherwise. * @since 3.7.3 */ @Nullable public static CascadingStyleSheet readFromReader (@Nonnull final IHasReader aRP, @Nonnull final ECSSVersion eVersion, @Nullable final ICSSParseErrorHandler aCustomErrorHandler, @Nullable final ICSSParseExceptionCallback aCustomExceptionHandler) { return readFromReader (aRP, new CSSReaderSettings ().setCSSVersion (eVersion) .setCustomErrorHandler (aCustomErrorHandler) .setCustomExceptionHandler (aCustomExceptionHandler)); } /** * Read the CSS from the passed {@link IReaderProvider}. If the CSS contains * an explicit @charset rule, it is ignored and the charset used * to create the reader is used instead! Also the fallback charset from the * {@link CSSReaderSettings} is ignored. * * @param aRP * The reader provider to use. The reader is retrieved exactly once and * closed anyway. May not be null. * @param aSettings * The settings to be used for reading the CSS. May not be * null. * @return null if reading failed, the CSS declarations * otherwise. * @since 3.8.2 */ @Nullable public static CascadingStyleSheet readFromReader (@Nonnull final IHasReader aRP, @Nonnull final CSSReaderSettings aSettings) { ValueEnforcer.notNull (aRP, "ReaderProvider"); ValueEnforcer.notNull (aSettings, "Settings"); // Create the reader final Reader aReader = aRP.getReader (); if (aReader == null) { // Failed to open reader return null; } // No charset determination, as the Reader already has an implicit Charset final ECSSVersion eVersion = aSettings.getCSSVersion (); try { final CSSCharStream aCharStream = new CSSCharStream (aReader); // Use the default CSS parse error handler if none is provided ICSSParseErrorHandler aRealErrorHandler = aSettings.getCustomErrorHandler (); if (aRealErrorHandler == null) aRealErrorHandler = getDefaultParseErrorHandler (); // Use the default CSS exception handler if none is provided ICSSParseExceptionCallback aRealExceptionHandler = aSettings.getCustomExceptionHandler (); if (aRealExceptionHandler == null) aRealExceptionHandler = getDefaultParseExceptionHandler (); final boolean bBrowserCompliantMode = aSettings.isBrowserCompliantMode (); final CSSNode aNode = _readStyleSheet (aCharStream, eVersion, aRealErrorHandler, aRealExceptionHandler, bBrowserCompliantMode); // Failed to interpret content as CSS? if (aNode == null) return null; // Convert the AST to a domain object return CSSHandler.readCascadingStyleSheetFromNode (eVersion, aNode); } finally { StreamHelper.close (aReader); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy