com.helger.css.reader.CSSReader Maven / Gradle / Ivy
/**
* 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:
*
* - 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.
* - If the CSS content contains a valid @charset rule, the defined charset
* is returned even if a different BOM is present.
* - If the CSS content does not contain a valid @charset rule than the
* charset of the BOM is returned (if any).
* - Otherwise
null
is returned.
*
*
* @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