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

com.gargoylesoftware.css.parser.AbstractCSSParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2019-2021 Ronald Brill.
 *
 * 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.gargoylesoftware.css.parser;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;

import org.w3c.dom.DOMException;

import com.gargoylesoftware.css.parser.javacc.CharStream;
import com.gargoylesoftware.css.parser.javacc.ParseException;
import com.gargoylesoftware.css.parser.javacc.Token;
import com.gargoylesoftware.css.parser.javacc.TokenMgrError;
import com.gargoylesoftware.css.parser.media.MediaQueryList;
import com.gargoylesoftware.css.parser.selector.SelectorList;

/**
 * Base implementation.
 *
 * @author Ronald Brill
 */
public abstract class AbstractCSSParser {
    private DocumentHandler documentHandler_;
    private CSSErrorHandler errorHandler_;
    private InputSource source_;

    private static final HashMap parserMessages_ = new HashMap<>();

    static {
        parserMessages_.put("invalidExpectingOne", "Invalid token \"{0}\". Was expecting: {1}.");
        parserMessages_.put("invalidExpectingMore", "Invalid token \"{0}\". Was expecting one of: {1}.");
        parserMessages_.put("invalidColor", "Invalid color \"{0}\".");
        parserMessages_.put("invalidStyleSheet", "Error in style sheet.");
        parserMessages_.put("invalidRule", "Error in rule.");
        parserMessages_.put("invalidUnknownRule", "Error in unknown at-rule.");
        parserMessages_.put("invalidCharsetRule", "Error in @charset rule.");
        parserMessages_.put("misplacedCharsetRule", "The @charset must be the first element in the style sheet.");
        parserMessages_.put("invalidImportRule", "Error in @import rule.");
        parserMessages_.put("invalidImportRuleIgnored", "@import rule must occur before all other rules.");
        parserMessages_.put("invalidImportRuleIgnored2",
                                "@import rule must occur before all other rules, except the @charset rule.");
        parserMessages_.put("invalidPageRule", "Error in @page rule.");
        parserMessages_.put("invalidFontFaceRule", "Error in @font-face rule.");
        parserMessages_.put("invalidMediaList", "Error in media list.");
        parserMessages_.put("invalidMediaRule", "Error in @media rule.");
        parserMessages_.put("invalidStyleRule", "Error in style rule.");
        parserMessages_.put("invalidStyleDeclaration", "Error in style declaration.");
        parserMessages_.put("invalidDeclaration", "Error in declaration.");
        parserMessages_.put("invalidDeclarationInvalidChar", "Error in declaration; invalid character \"{0}\" found.");
        parserMessages_.put("invalidDeclarationStarHack",
                                    "Error in declaration. ''*'' is not allowed as first char of a property.");
        parserMessages_.put("invalidSelectorList", "Error in selector list.");
        parserMessages_.put("invalidSelector", "Error in selector.");
        parserMessages_.put("invalidSimpleSelector", "Error in simple selector.");
        parserMessages_.put("invalidClassSelector", "Error in class selector.");
        parserMessages_.put("invalidElementName", "Error in element name.");
        parserMessages_.put("invalidAttrib", "Error in attribute selector.");
        parserMessages_.put("invalidPseudo", "Error in pseudo class or element.");
        parserMessages_.put("duplicatePseudo", "Duplicate pseudo class \":{0}\" or pseudo class \":{0}\" not at end.");
        parserMessages_.put("invalidHash", "Error in hash.");
        parserMessages_.put("invalidExpr", "Error in expression.");
        parserMessages_.put("invalidExprColon", "Error in expression; '':'' found after identifier \"{0}\".");
        parserMessages_.put("invalidPrio", "Error in priority.");

        parserMessages_.put("invalidPagePseudoClass",
                "Invalid page pseudo class \"{0}\"; valid values are \"blank\", \"first\", \"left\", and \"right\".");

        parserMessages_.put("invalidCaseInSensitivelyIdentifier",
                "Invalid case-insensitively identifier \"{0}\" found; valid values are \"i\", and \"s\".");

        parserMessages_.put("ignoringRule", "Ignoring the whole rule.");
        parserMessages_.put("ignoringFollowingDeclarations", "Ignoring the following declarations in this rule.");

        parserMessages_.put("tokenMgrError", "Lexical error.");
        parserMessages_.put("domException", "DOM exception: ''{0}''");
    }

    private static final String NUM_CHARS = "0123456789.";

    /**
     * @return the document handler
     */
    protected DocumentHandler getDocumentHandler() {
        if (documentHandler_ == null) {
            setDocumentHandler(new HandlerBase());
        }
        return documentHandler_;
    }

    /**
     * Allow an application to register a document event handler.
     *
     * 

If the application does not register a document handler, all * document events reported by the CSS parser will be silently * ignored (this is the default behaviour implemented by * HandlerBase).

* *

Applications may register a new or different handler in the * middle of a parse, and the CSS parser must begin using the new * handler immediately.

* * @param handler The document handler. * @see DocumentHandler */ public void setDocumentHandler(final DocumentHandler handler) { documentHandler_ = handler; } /** * @return the error handler */ protected CSSErrorHandler getErrorHandler() { if (errorHandler_ == null) { setErrorHandler(new HandlerBase()); } return errorHandler_; } /** * Allow an application to register an error event handler. * *

If the application does not register an error event handler, * all error events reported by the CSS parser will be silently * ignored, except for fatalError, which will throw a CSSException * (this is the default behaviour implemented by HandlerBase).

* *

Applications may register a new or different handler in the * middle of a parse, and the CSS parser must begin using the new * handler immediately.

* * @param handler The error handler. * @see CSSErrorHandler * @see CSSException */ public void setErrorHandler(final CSSErrorHandler handler) { errorHandler_ = handler; } /** * @return the input source */ protected InputSource getInputSource() { return source_; } /** * @param key the lookup key * @return the parser message */ protected String getParserMessage(final String key) { final String msg = parserMessages_.get(key); if (msg == null) { return "[[" + key + "]]"; } return msg; } /** * Returns a new locator for the given token. * @param t the token to generate the locator for * @return a new locator */ protected Locator createLocator(final Token t) { return new Locator(getInputSource().getURI(), t == null ? 0 : t.beginLine, t == null ? 0 : t.beginColumn); } /** * Escapes some chars in the given string. * @param str the input * @return a new string with the escaped values */ protected String addEscapes(final String str) { final StringBuilder sb = new StringBuilder(); char ch; for (int i = 0; i < str.length(); i++) { ch = str.charAt(i); switch (ch) { case 0 : continue; case '\b': sb.append("\\b"); continue; case '\t': sb.append("\\t"); continue; case '\n': sb.append("\\n"); continue; case '\f': sb.append("\\f"); continue; case '\r': sb.append("\\r"); continue; case '\"': sb.append("\\\""); continue; case '\'': sb.append("\\\'"); continue; case '\\': sb.append("\\\\"); continue; default: if (ch < 0x20 || ch > 0x7e) { final String s = "0000" + Integer.toString(ch, 16); sb.append("\\u").append(s.substring(s.length() - 4)); } else { sb.append(ch); } continue; } } return sb.toString(); } /** * * @param key the message lookup key * @param e the parse exception * @return a new CSSParseException */ protected CSSParseException toCSSParseException(final String key, final ParseException e) { final String messagePattern1 = getParserMessage("invalidExpectingOne"); final String messagePattern2 = getParserMessage("invalidExpectingMore"); int maxSize = 0; final StringBuilder expected = new StringBuilder(); for (int i = 0; i < e.expectedTokenSequences.length; i++) { if (maxSize < e.expectedTokenSequences[i].length) { maxSize = e.expectedTokenSequences[i].length; } for (int j = 0; j < e.expectedTokenSequences[i].length; j++) { expected.append(e.tokenImage[e.expectedTokenSequences[i][j]]); } if (i < e.expectedTokenSequences.length - 1) { expected.append(", "); } } final StringBuilder invalid = new StringBuilder(); Token tok = e.currentToken.next; for (int i = 0; i < maxSize; i++) { if (i != 0) { invalid.append(" "); } if (tok.kind == 0) { invalid.append(e.tokenImage[0]); break; } invalid.append(addEscapes(tok.image)); tok = tok.next; } final StringBuilder message = new StringBuilder(getParserMessage(key)); message.append(" ("); if (e.expectedTokenSequences.length == 1) { message.append(MessageFormat.format(messagePattern1, invalid, expected)); } else { message.append(MessageFormat.format(messagePattern2, invalid, expected)); } message.append(")"); return new CSSParseException(message.toString(), getInputSource().getURI(), e.currentToken.next.beginLine, e.currentToken.next.beginColumn); } /** * @param e the DOMException * @return a new CSSParseException */ protected CSSParseException toCSSParseException(final DOMException e) { final String messagePattern = getParserMessage("domException"); return new CSSParseException( MessageFormat.format(messagePattern, e.getMessage()), getInputSource().getURI(), 1, 1); } /** * @param e the TokenMgrError * @return a new CSSParseException */ protected CSSParseException toCSSParseException(final TokenMgrError e) { final String messagePattern = getParserMessage("tokenMgrError"); return new CSSParseException(messagePattern, getInputSource().getURI(), 1, 1); } /** * @param messageKey the message key * @param msgParams the params * @param locator the locator * @return a new CSSParseException */ protected CSSParseException toCSSParseException(final String messageKey, final Object[] msgParams, final Locator locator) { final String messagePattern = getParserMessage(messageKey); return new CSSParseException(MessageFormat.format(messagePattern, msgParams), locator); } /** * @param messageKey the message key * @param e a CSSParseException * @return a new CSSParseException */ protected CSSParseException createSkipWarning(final String messageKey, final CSSParseException e) { return new CSSParseException(getParserMessage(messageKey), e.getURI(), e.getLineNumber(), e.getColumnNumber()); } /** * Parse a CSS document. * *

The application can use this method to instruct the CSS parser * to begin parsing an CSS document from any valid input * source (a character stream, a byte stream, or a URI).

* *

Applications may not invoke this method while a parse is in * progress (they should create a new Parser instead for each * additional CSS document). Once a parse is complete, an * application may reuse the same Parser object, possibly with a * different input source.

* * @param source The input source for the top-level of the * CSS document. * @exception CSSException Any CSS exception, possibly * wrapping another exception. * @exception java.io.IOException An IO exception from the parser, * possibly from a byte stream or character stream * supplied by the application. * @see InputSource * @see #setDocumentHandler * @see #setErrorHandler */ public void parseStyleSheet(final InputSource source) throws IOException { source_ = source; ReInit(getCharStream(source)); try { styleSheet(); } catch (final ParseException e) { getErrorHandler().error(toCSSParseException("invalidStyleSheet", e)); } catch (final TokenMgrError e) { getErrorHandler().error(toCSSParseException(e)); } catch (final CSSParseException e) { getErrorHandler().error(e); } } /** * Parse a CSS style declaration (without '{' and '}'). * * @param source source to be parsed * @exception CSSException Any CSS exception, possibly * wrapping another exception. * @exception java.io.IOException An IO exception from the parser, * possibly from a byte stream or character stream * supplied by the application. */ public void parseStyleDeclaration(final InputSource source) throws IOException { source_ = source; ReInit(getCharStream(source)); try { styleDeclaration(); } catch (final ParseException e) { getErrorHandler().error(toCSSParseException("invalidStyleDeclaration", e)); } catch (final TokenMgrError e) { getErrorHandler().error(toCSSParseException(e)); } catch (final CSSParseException e) { getErrorHandler().error(e); } } /** * Parse a CSS rule. * * @param source source to be parsed * @exception CSSException Any CSS exception, possibly * wrapping another exception. * @exception java.io.IOException An IO exception from the parser, * possibly from a byte stream or character stream * supplied by the application. */ public void parseRule(final InputSource source) throws IOException { source_ = source; ReInit(getCharStream(source)); try { styleSheetRuleSingle(); } catch (final ParseException e) { getErrorHandler().error(toCSSParseException("invalidRule", e)); } catch (final TokenMgrError e) { getErrorHandler().error(toCSSParseException(e)); } catch (final CSSParseException e) { getErrorHandler().error(e); } } /** * Parse a comma separated list of selectors. * * @param source source to be parsed * @return a selector list * @exception CSSException Any CSS exception, possibly * wrapping another exception. * @exception java.io.IOException An IO exception from the parser, * possibly from a byte stream or character stream * supplied by the application. */ public SelectorList parseSelectors(final InputSource source) throws IOException { source_ = source; ReInit(getCharStream(source)); SelectorList sl = null; try { sl = parseSelectorsInternal(); } catch (final ParseException e) { getErrorHandler().error(toCSSParseException("invalidSelectorList", e)); } catch (final TokenMgrError e) { getErrorHandler().error(toCSSParseException(e)); } catch (final CSSParseException e) { getErrorHandler().error(e); } return sl; } /** * Parse a CSS property value. * * @param source source to be parsed * @return a lexical unit * @exception CSSException Any CSS exception, possibly * wrapping another exception. * @exception java.io.IOException An IO exception from the parser, * possibly from a byte stream or character stream * supplied by the application. */ public LexicalUnit parsePropertyValue(final InputSource source) throws IOException { source_ = source; ReInit(getCharStream(source)); LexicalUnit lu = null; try { lu = expr(); } catch (final ParseException e) { getErrorHandler().error(toCSSParseException("invalidExpr", e)); } catch (final TokenMgrError e) { getErrorHandler().error(toCSSParseException(e)); } catch (final CSSParseException e) { getErrorHandler().error(e); } return lu; } /** * Parse a CSS priority value (e.g. "!important"). * * @param source source to be parsed * @return true or flase * @exception CSSException Any CSS exception, possibly * wrapping another exception. * @exception java.io.IOException An IO exception from the parser, * possibly from a byte stream or character stream * supplied by the application. */ public boolean parsePriority(final InputSource source) throws IOException { source_ = source; ReInit(getCharStream(source)); boolean b = false; try { b = prio(); } catch (final ParseException e) { getErrorHandler().error(toCSSParseException("invalidPrio", e)); } catch (final TokenMgrError e) { getErrorHandler().error(toCSSParseException(e)); } catch (final CSSParseException e) { getErrorHandler().error(e); } return b; } /** * Parse the given input source and return the media list. * @param source the input source * @return new media list * @throws IOException in case of errors */ public MediaQueryList parseMedia(final InputSource source) throws IOException { source_ = source; ReInit(getCharStream(source)); final MediaQueryList ml = new MediaQueryList(); try { mediaList(ml); } catch (final ParseException e) { getErrorHandler().error(toCSSParseException("invalidMediaList", e)); } catch (final TokenMgrError e) { getErrorHandler().error(toCSSParseException(e)); } catch (final CSSParseException e) { getErrorHandler().error(e); } return ml; } private static CharStream getCharStream(final InputSource source) throws IOException { if (source.getReader() != null) { return new CssCharStream(source.getReader(), 1, 1); } if (source.getURI() != null) { final InputStreamReader reader = new InputStreamReader(new URL(source.getURI()).openStream()); return new CssCharStream(reader, 1, 1); } return null; } /** * @return a string about which CSS language is supported by this * parser. For CSS Level 1, it returns "http://www.w3.org/TR/REC-CSS1", for * CSS Level 2, it returns "http://www.w3.org/TR/REC-CSS2". Note that a * "CSSx" parser can return lexical unit other than those allowed by CSS * Level x but this usage is not recommended. */ public abstract String getParserVersion(); /** * Re intit the stream. * @param charStream the stream */ protected abstract void ReInit(CharStream charStream); /** * Process a style sheet. * * @throws CSSParseException in case of error * @throws ParseException in case of error */ protected abstract void styleSheet() throws CSSParseException, ParseException; /** * Process a style sheet declaration. * * @throws ParseException in case of error */ protected abstract void styleDeclaration() throws ParseException; /** * Process a style sheet rule. * * @throws ParseException in case of error */ protected abstract void styleSheetRuleSingle() throws ParseException; /** * Process a selector list. * * @return the selector list * @throws ParseException in case of error */ protected abstract SelectorList parseSelectorsInternal() throws ParseException; /** * Process an expression. * * @return the lexical unit * @throws ParseException in case of error */ protected abstract LexicalUnit expr() throws ParseException; /** * Process a prio. * * @return true or false * @throws ParseException in case of error */ protected abstract boolean prio() throws ParseException; /** * Process a media list. * * @param ml the media list * @throws ParseException in case of error */ protected abstract void mediaList(MediaQueryList ml) throws ParseException; /** * start document handler. */ protected void handleStartDocument() { getDocumentHandler().startDocument(getInputSource()); } /** * end document handler. */ protected void handleEndDocument() { getDocumentHandler().endDocument(getInputSource()); } /** * ignorable at rule handler. * * @param s the rule * @param locator the locator */ protected void handleIgnorableAtRule(final String s, final Locator locator) { getDocumentHandler().ignorableAtRule(s, locator); } /** * charset handler. * * @param characterEncoding the encoding * @param locator the locator */ protected void handleCharset(final String characterEncoding, final Locator locator) { getDocumentHandler().charset(characterEncoding, locator); } /** * import style handler. * * @param uri the uri * @param media the media query list * @param defaultNamespaceURI the namespace uri * @param locator the locator */ protected void handleImportStyle(final String uri, final MediaQueryList media, final String defaultNamespaceURI, final Locator locator) { getDocumentHandler().importStyle(uri, media, defaultNamespaceURI, locator); } /** * start media handler. * * @param media the media query list * @param locator the locator */ protected void handleStartMedia(final MediaQueryList media, final Locator locator) { getDocumentHandler().startMedia(media, locator); } /** * end media handler. * * @param media the media query list */ protected void handleEndMedia(final MediaQueryList media) { getDocumentHandler().endMedia(media); } /** * start page handler. * * @param name the name * @param pseudoPage the pseudo page * @param locator the locator */ protected void handleStartPage(final String name, final String pseudoPage, final Locator locator) { getDocumentHandler().startPage(name, pseudoPage, locator); } /** * end page handler. * * @param name the name * @param pseudoPage the pseudo page */ protected void handleEndPage(final String name, final String pseudoPage) { getDocumentHandler().endPage(name, pseudoPage); } /** * start font face handler. * * @param locator the locator */ protected void handleStartFontFace(final Locator locator) { getDocumentHandler().startFontFace(locator); } /** * end font face handler. */ protected void handleEndFontFace() { getDocumentHandler().endFontFace(); } /** * selector start handler. * * @param selectors the selector list * @param locator the locator */ protected void handleStartSelector(final SelectorList selectors, final Locator locator) { getDocumentHandler().startSelector(selectors, locator); } /** * selector end handler. * * @param selectors the selector list */ protected void handleEndSelector(final SelectorList selectors) { getDocumentHandler().endSelector(selectors); } /** * property handler. * * @param name the name * @param value the value * @param important important flag * @param locator the locator */ protected void handleProperty(final String name, final LexicalUnit value, final boolean important, final Locator locator) { getDocumentHandler().property(name, value, important, locator); } /** * Process a function decl. * * @param prev the previous lexical unit * @param funct the function * @param params the params * @return a lexical unit */ protected LexicalUnit functionInternal(final LexicalUnit prev, final String funct, final LexicalUnit params) { if ("counter(".equalsIgnoreCase(funct)) { return LexicalUnitImpl.createCounter(prev, params); } else if ("counters(".equalsIgnoreCase(funct)) { return LexicalUnitImpl.createCounters(prev, params); } else if ("attr(".equalsIgnoreCase(funct)) { return LexicalUnitImpl.createAttr(prev, params.getStringValue()); } else if ("rect(".equalsIgnoreCase(funct)) { return LexicalUnitImpl.createRect(prev, params); } else if ("calc(".equalsIgnoreCase(funct)) { return LexicalUnitImpl.createCalc(prev, params); } return LexicalUnitImpl.createFunction( prev, funct.substring(0, funct.length() - 1), params); } protected LexicalUnit rgbColorInternal(final LexicalUnit prev, final String funct, final LexicalUnit param) { return LexicalUnitImpl.createRgbColor(prev, funct, param); } protected LexicalUnit hslColorInternal(final LexicalUnit prev, final String funct, final LexicalUnit param) { return LexicalUnitImpl.createHslColor(prev, funct, param); } /** * Processes a hexadecimal color definition. * * @param prev the previous lexical unit * @param t the token * @return a new lexical unit */ protected LexicalUnit hexColorInternal(final LexicalUnit prev, final Token t) { // Step past the hash at the beginning final int i = 1; int r = 0; int g = 0; int b = 0; Double a = null; final int len = t.image.length() - 1; try { if (len == 3) { r = Integer.parseInt(t.image.substring(i + 0, i + 1), 16); g = Integer.parseInt(t.image.substring(i + 1, i + 2), 16); b = Integer.parseInt(t.image.substring(i + 2, i + 3), 16); r = (r << 4) | r; g = (g << 4) | g; b = (b << 4) | b; } else if (len == 4) { r = Integer.parseInt(t.image.substring(i + 0, i + 1), 16); g = Integer.parseInt(t.image.substring(i + 1, i + 2), 16); b = Integer.parseInt(t.image.substring(i + 2, i + 3), 16); int ai = Integer.parseInt(t.image.substring(i + 3, i + 4), 16); r = (r << 4) | r; g = (g << 4) | g; b = (b << 4) | b; ai = (ai << 4) | ai; a = ai / 255d; } else if (len == 6) { r = Integer.parseInt(t.image.substring(i + 0, i + 2), 16); g = Integer.parseInt(t.image.substring(i + 2, i + 4), 16); b = Integer.parseInt(t.image.substring(i + 4, i + 6), 16); } else if (len == 8) { r = Integer.parseInt(t.image.substring(i + 0, i + 2), 16); g = Integer.parseInt(t.image.substring(i + 2, i + 4), 16); b = Integer.parseInt(t.image.substring(i + 4, i + 6), 16); final int ai = Integer.parseInt(t.image.substring(i + 6, i + 8), 16); a = ai / 255d; } else { final String pattern = getParserMessage("invalidColor"); throw new CSSParseException(MessageFormat.format( pattern, t), getInputSource().getURI(), t.beginLine, t.beginColumn); } // Turn into an "rgb()" final LexicalUnit lr = LexicalUnitImpl.createNumber(null, r); final LexicalUnit lg = LexicalUnitImpl.createNumber(LexicalUnitImpl.createComma(lr), g); final LexicalUnit lb = LexicalUnitImpl.createNumber(LexicalUnitImpl.createComma(lg), b); if (a != null) { a = Math.round(a * 1000d) / 1000d; LexicalUnitImpl.createNumber(LexicalUnitImpl.createComma(lb), a); return LexicalUnitImpl.createRgbColor(prev, "rgba", lr); } return LexicalUnitImpl.createRgbColor(prev, "rgb", lr); } catch (final NumberFormatException ex) { final String pattern = getParserMessage("invalidColor"); throw new CSSParseException(MessageFormat.format( pattern, t), getInputSource().getURI(), t.beginLine, t.beginColumn, ex); } } /** * Parses the sting into an integer. * * @param op the sign char * @param s the string to parse * @return the int value */ protected int intValue(final char op, final String s) { final int result = Integer.parseInt(s); if (op == '-') { return -1 * result; } return result; } /** * Parses the sting into an double. * * @param op the sign char * @param s the string to parse * @return the double value */ protected double doubleValue(final char op, final String s) { final double result = Double.parseDouble(s); if (op == '-') { return -1 * result; } return result; } /** * Returns the pos of the last numeric char in the given string. * * @param s the string to parse * @return the pos */ protected int getLastNumPos(final String s) { int i = 0; for ( ; i < s.length(); i++) { if (NUM_CHARS.indexOf(s.charAt(i)) < 0) { break; } } return i - 1; } /** * Unescapes escaped characters in the specified string, according to the * CSS specification. * * This could be done directly in the parser, but portions of the lexer would have to be moved * to the parser, meaning that the grammar would no longer match the standard grammar specified * by the W3C. This would make the parser and lexer much less maintainable. * * @param s the string to unescape * @param unescapeDoubleQuotes if true unescape double quotes also * @return the unescaped string */ public String unescape(final String s, final boolean unescapeDoubleQuotes) { if (s == null) { return s; } // avoid creation of new string if possible StringBuilder buf = null; int index = -1; int len = s.length(); len--; if (unescapeDoubleQuotes) { while (index < len) { final char c = s.charAt(++index); if (c == '\\' || (c == '\"')) { buf = new StringBuilder(len); buf.append(s, 0, index); index--; break; } } } else { while (index < len) { if ('\\' == s.charAt(++index)) { buf = new StringBuilder(len); buf.append(s, 0, index); index--; break; } } } if (null == buf) { return s; } // ok, we have to construct a new string int numValue = -1; int hexval; int digitCount = 0; while (index < len) { final char c = s.charAt(++index); if (numValue > -1) { hexval = hexval(c); if (hexval != -1) { numValue = (numValue * 16) + hexval; if (++digitCount < 6) { continue; } if (numValue > 0xFFFF || numValue == 0) { numValue = 0xFFFD; } buf.append((char) numValue); numValue = -1; continue; } if (digitCount > 0) { if (numValue > 0xFFFF || numValue == 0) { numValue = 0xFFFD; } buf.append((char) numValue); if (c == ' ' || c == '\t') { numValue = -1; continue; } } numValue = -1; if (digitCount == 0 && c == '\\') { buf.append('\\'); continue; } if (c == '\n' || c == '\f') { continue; } if (c == '\r') { if (index < len) { if (s.charAt(index + 1) == '\n') { index++; } } continue; } } if (c == '\\') { numValue = 0; digitCount = 0; continue; } if (c == '\"' && !unescapeDoubleQuotes) { buf.append('\\'); } buf.append(c); } if (numValue > -1) { if (digitCount == 0) { buf.append('\\'); } else { if (numValue > 0xFFFF || numValue == 0) { numValue = 0xFFFD; } buf.append((char) numValue); } } return buf.toString(); } private static int hexval(final char c) { switch (c) { case '0' : return 0; case '1' : return 1; case '2' : return 2; case '3' : return 3; case '4' : return 4; case '5' : return 5; case '6' : return 6; case '7' : return 7; case '8' : return 8; case '9' : return 9; case 'a' : case 'A' : return 10; case 'b' : case 'B' : return 11; case 'c' : case 'C' : return 12; case 'd' : case 'D' : return 13; case 'e' : case 'E' : return 14; case 'f' : case 'F' : return 15; default : return -1; } } protected String normalizeAndValidatePagePseudoClass(final Token t) { final String pseudo = unescape(t.image, false); final String pseudoLC = pseudo.toLowerCase(Locale.ROOT); if ("blank".equals(pseudoLC) || "left".equals(pseudoLC) || "first".equals(pseudoLC) || "right".equals(pseudoLC)) { return pseudoLC; } final String pattern = getParserMessage("invalidPagePseudoClass"); throw new CSSParseException(MessageFormat.format(pattern, pseudo), getInputSource().getURI(), t.beginLine, t.beginColumn); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy