Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public abstract class AbstractCSSParser {
private DocumentHandler documentHandler_;
private CSSErrorHandler errorHandler_;
private InputSource source_;
private ParserContext parserContext_ = new ParserContext();

private static final HashMap<String, String> PARSER_MESSAGES_ = new HashMap<>();

Expand Down Expand Up @@ -165,6 +166,15 @@ protected InputSource getInputSource() {
return source_;
}

/**
* <p>getParserContext.</p>
*
* @return the parser context
*/
protected ParserContext getParserContext() {
return parserContext_;
}

/**
* @param key the lookup key
* @return the parser message
Expand Down Expand Up @@ -284,7 +294,11 @@ protected CSSParseException toCSSParseException(final String key, final ParseExc
message.append(MessageFormat.format(messagePattern2, invalid, expected));
}
message.append(")");
return new CSSParseException(message.toString(),

// Add contextual information
final String contextualMessage = parserContext_.buildContextualMessage(message.toString());

return new CSSParseException(contextualMessage,
getInputSource().getURI(), e.currentToken.next.beginLine,
e.currentToken.next.beginColumn);
}
Expand Down
159 changes: 159 additions & 0 deletions src/main/java/org/htmlunit/cssparser/parser/ParserContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2019-2024 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
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.cssparser.parser;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

import org.htmlunit.cssparser.parser.javacc.Token;

/**
* Tracks parsing context to provide detailed error messages.
*
* @author Ronald Brill
*/
public class ParserContext {
private final Deque<String> ruleStack_ = new ArrayDeque<>();
private Token currentToken_;
private final List<String> expectedTokens_ = new ArrayList<>();
private String currentProperty_;

/**
* Enter a new parsing rule context.
*
* @param ruleName the name of the rule being entered
*/
public void enterRule(final String ruleName) {
ruleStack_.push(ruleName);
}

/**
* Exit the current parsing rule context.
*/
public void exitRule() {
if (!ruleStack_.isEmpty()) {
ruleStack_.pop();
}
}

/**
* Set the current token being processed.
*
* @param token the current token
*/
public void setCurrentToken(final Token token) {
currentToken_ = token;
}

/**
* Add an expected token to the list.
*
* @param token the expected token description
*/
public void addExpectedToken(final String token) {
expectedTokens_.add(token);
}

/**
* Clear the list of expected tokens.
*/
public void clearExpectedTokens() {
expectedTokens_.clear();
}

/**
* Set the current property being parsed.
*
* @param property the property name
*/
public void setCurrentProperty(final String property) {
currentProperty_ = property;
}

/**
* Build a contextual error message by appending context information
* to the base message.
*
* @param baseMessage the base error message
* @return the contextual error message
*/
public String buildContextualMessage(final String baseMessage) {
final StringBuilder sb = new StringBuilder(baseMessage);

// Add rule context
if (!ruleStack_.isEmpty()) {
sb.append(" (in ");
final List<String> stack = new ArrayList<>(ruleStack_);
// Reverse to show from root to current
for (int i = stack.size() - 1; i >= 0; i--) {
if (i < stack.size() - 1) {
sb.append(" > ");
}
sb.append(stack.get(i));
}
sb.append(")");
}

// Add current token info
if (currentToken_ != null) {
sb.append(" at '");
sb.append(currentToken_.image);
sb.append("'");
}

// Add expected tokens
if (!expectedTokens_.isEmpty()) {
sb.append(". Expected: ");
for (int i = 0; i < expectedTokens_.size(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(expectedTokens_.get(i));
}
}

// Add property context if available
if (currentProperty_ != null) {
sb.append(" (property: ");
sb.append(currentProperty_);
sb.append(")");
}

return sb.toString();
}

/**
* Get the current parsing context as a string.
*
* @return the current context description
*/
public String getCurrentContext() {
if (ruleStack_.isEmpty()) {
return "root";
}
final StringBuilder sb = new StringBuilder();
final List<String> stack = new ArrayList<>(ruleStack_);
// Reverse to show from root to current
for (int i = stack.size() - 1; i >= 0; i--) {
if (i < stack.size() - 1) {
sb.append(" > ");
}
sb.append(stack.get(i));
}
return sb.toString();
}
}
33 changes: 31 additions & 2 deletions src/main/javacc/CSS3Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ void mediaRule() :
<MEDIA_SYM>
{
locator = createLocator(token);
getParserContext().enterRule("@media");
}
( <S> )*
mediaList(ml)
Expand All @@ -694,6 +695,8 @@ void mediaRule() :
}
catch(ParseException e)
{
getParserContext().addExpectedToken("media-type");
getParserContext().addExpectedToken("{");
CSSParseException cpe = toCSSParseException("invalidMediaRule", e);
getErrorHandler().error(cpe);
error_skipblock("ignoringRule", cpe);
Expand All @@ -703,6 +706,8 @@ void mediaRule() :
if (start) {
handleEndMedia(ml);
}
getParserContext().exitRule();
getParserContext().clearExpectedTokens();
}
}

Expand Down Expand Up @@ -1060,6 +1065,7 @@ void styleRule() :
try {
{
t = token;
getParserContext().enterRule("style-rule");
}
selList = selectorList()
<LBRACE> ( <S> )*
Expand All @@ -1077,6 +1083,8 @@ void styleRule() :
}
catch(ParseException e)
{
getParserContext().addExpectedToken("selector");
getParserContext().addExpectedToken("{");
CSSParseException cpe = toCSSParseException("invalidStyleRule", e);
getErrorHandler().error(cpe);
error_skipblock("ignoringFollowingDeclarations", cpe);
Expand All @@ -1085,6 +1093,8 @@ void styleRule() :
if (start) {
handleEndSelector(selList);
}
getParserContext().exitRule();
getParserContext().clearExpectedTokens();
}
}

Expand Down Expand Up @@ -1568,20 +1578,31 @@ void declaration() :
{
try
{
{
getParserContext().enterRule("declaration");
}
// at the moment i have no better idea how to handle the
// infamous css-star-hack (http://en.wikipedia.org/wiki/CSS_filter#Star_hack)
// smart (means: ignoring only one decl)
( <ASTERISK> { starHack = createLocator(token); } )?
(
(
t = ident() { p = unescape(t.image, false); locator = createLocator(t); }
t = ident() {
p = unescape(t.image, false);
locator = createLocator(t);
getParserContext().setCurrentProperty(p);
}
( <S> )*
<COLON> ( <S> )*
e = expr()
)
|
(
t = <CUSTOM_PROPERTY_NAME> ( <S> )* { p = unescape(t.image, false); locator = createLocator(t); }
t = <CUSTOM_PROPERTY_NAME> ( <S> )* {
p = unescape(t.image, false);
locator = createLocator(t);
getParserContext().setCurrentProperty(p);
}
<COLON> ( <S> )*
( e = expr() )?
)
Expand Down Expand Up @@ -1616,10 +1637,18 @@ void declaration() :
}
catch (ParseException ex)
{
getParserContext().addExpectedToken("property-name");
getParserContext().addExpectedToken(":");
CSSParseException cpe = toCSSParseException("invalidDeclaration", ex);
getErrorHandler().error(cpe);
error_skipdecl();
}
finally
{
getParserContext().setCurrentProperty(null);
getParserContext().exitRule();
getParserContext().clearExpectedTokens();
}
}

//
Expand Down
18 changes: 13 additions & 5 deletions src/test/java/org/htmlunit/cssparser/parser/CSS3ParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ public void atRules2() throws Exception {

assertEquals(1, errorHandler.getErrorCount());
final String expected = "@import rule must occur before all other rules, except the @charset rule."
+ " (Invalid token \"@import\". Was expecting: <S>.)";
+ " (Invalid token \"@import\". Was expecting: <S>.) (in @media)";
assertEquals(expected, errorHandler.getErrorMessage());
assertEquals("3", errorHandler.getErrorLines());
assertEquals("3", errorHandler.getErrorColumns());
Expand Down Expand Up @@ -852,7 +852,7 @@ public void atRules2b() throws Exception {

assertEquals(1, errorHandler.getErrorCount());
final String expected = "@import rule must occur before all other rules, except the @charset rule."
+ " (Invalid token \"@import\". Was expecting: <S>.)";
+ " (Invalid token \"@import\". Was expecting: <S>.) (in @media)";
assertEquals(expected, errorHandler.getErrorMessage());
assertEquals("4", errorHandler.getErrorLines());
assertEquals("3", errorHandler.getErrorColumns());
Expand Down Expand Up @@ -1597,7 +1597,9 @@ public void malformedDeclaration() throws Exception {

assertEquals(7, errorHandler.getErrorCount());
final String expected = "Error in declaration. (Invalid token \"}\". Was expecting one of: <S>, \":\".)"
+ " (in style-rule > declaration). Expected: property-name, : (property: background)"
+ " Error in declaration. (Invalid token \";\". Was expecting one of: <S>, \":\".)"
+ " (in style-rule > declaration). Expected: property-name, : (property: background)"
+ " Error in expression. (Invalid token \"}\". Was expecting one of: <S>, \"only\", <NUMBER>, \"inherit\", \"none\", \"from\", "
+ "<IDENT>, <STRING>, \"-\", \"+\", <HASH>, <EMS>, <REM>, <EXS>, <CH>, "
+ "<VW>, <VH>, <VMIN>, <VMAX>, "
Expand All @@ -1606,6 +1608,7 @@ public void malformedDeclaration() throws Exception {
+ "<TIME_MS>, <TIME_S>, <FREQ_HZ>, <FREQ_KHZ>, <RESOLUTION_DPI>, <RESOLUTION_DPCM>, <PERCENTAGE>, "
+ "<DIMENSION>, <UNICODE_RANGE>, <URI>, <FUNCTION_CALC>, <FUNCTION_VAR>, "
+ "<FUNCTION_RGB>, <FUNCTION_HSL>, <FUNCTION_HWB>, <FUNCTION_LAB>, <FUNCTION_LCH>, <FUNCTION>, \"progid:\".)"
+ " (in style-rule > declaration) (property: background)"
+ " Error in expression. (Invalid token \";\". Was expecting one of: <S>, \"only\", <NUMBER>, \"inherit\", \"none\", \"from\", "
+ "<IDENT>, <STRING>, \"-\", \"+\", <HASH>, <EMS>, <REM>, <EXS>, <CH>, "
+ "<VW>, <VH>, <VMIN>, <VMAX>, "
Expand All @@ -1614,9 +1617,13 @@ public void malformedDeclaration() throws Exception {
+ "<TIME_MS>, <TIME_S>, <FREQ_HZ>, <FREQ_KHZ>, <RESOLUTION_DPI>, <RESOLUTION_DPCM>, <PERCENTAGE>, "
+ "<DIMENSION>, <UNICODE_RANGE>, <URI>, <FUNCTION_CALC>, <FUNCTION_VAR>, "
+ "<FUNCTION_RGB>, <FUNCTION_HSL>, <FUNCTION_HWB>, <FUNCTION_LAB>, <FUNCTION_LCH>, <FUNCTION>, \"progid:\".)"
+ " (in style-rule > declaration) (property: background)"
+ " Error in declaration. (Invalid token \"{\". Was expecting one of: <S>, \":\".)"
+ " (in style-rule > declaration). Expected: property-name, : (property: background)"
+ " Error in style rule. (Invalid token \" \". Was expecting one of: <EOF>, \"}\", \";\".)"
+ " Error in declaration. (Invalid token \"{\". Was expecting one of: <S>, \":\".)";
+ " (in style-rule). Expected: selector, {"
+ " Error in declaration. (Invalid token \"{\". Was expecting one of: <S>, \":\".)"
+ " (in style-rule > declaration). Expected: property-name, : (property: background)";
assertEquals(expected, errorHandler.getErrorMessage());
assertEquals("2 3 4 5 6 6 7", errorHandler.getErrorLines());
assertEquals("29 28 30 29 28 48 28", errorHandler.getErrorColumns());
Expand Down Expand Up @@ -1709,7 +1716,7 @@ public void malformedStatements() throws Exception {

assertEquals(1, errorHandler.getErrorCount());
final String expected = "Error in style rule. "
+ "(Invalid token \"@here\". Was expecting one of: <S>, \"{\", \",\".)";
+ "(Invalid token \"@here\". Was expecting one of: <S>, \"{\", \",\".) (in style-rule). Expected: selector, {";
assertEquals(expected, errorHandler.getErrorMessage());
assertEquals("2", errorHandler.getErrorLines());
assertEquals("3", errorHandler.getErrorColumns());
Expand Down Expand Up @@ -1911,7 +1918,8 @@ public void unexpectedEndOfString() throws Exception {
+ "<ANGLE_DEG>, <ANGLE_RAD>, <ANGLE_GRAD>, <ANGLE_TURN>, "
+ "<TIME_MS>, <TIME_S>, <FREQ_HZ>, <FREQ_KHZ>, <RESOLUTION_DPI>, <RESOLUTION_DPCM>, <PERCENTAGE>, "
+ "<DIMENSION>, <UNICODE_RANGE>, <URI>, <FUNCTION_CALC>, <FUNCTION_VAR>, "
+ "<FUNCTION_RGB>, <FUNCTION_HSL>, <FUNCTION_HWB>, <FUNCTION_LAB>, <FUNCTION_LCH>, <FUNCTION>, \"progid:\".)";
+ "<FUNCTION_RGB>, <FUNCTION_HSL>, <FUNCTION_HWB>, <FUNCTION_LAB>, <FUNCTION_LCH>, <FUNCTION>, \"progid:\".)"
+ " (in style-rule > declaration) (property: font-family)";
assertEquals(expected, errorHandler.getErrorMessage());
assertEquals("3", errorHandler.getErrorLines());
assertEquals("16", errorHandler.getErrorColumns());
Expand Down
Loading