diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..133be3e
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,259 @@
+# AGENTS.md — HtmlUnit-CSSParser
+
+## Project Overview
+
+HtmlUnit-CSSParser is a **CSS parser for Java** that reads CSS source text and produces a DOM-style object tree. It is the CSS parser powering [HtmlUnit](https://www.htmlunit.org/) since version 1.30. The project originated as a fork of [CSSParser 0.9.25](http://cssparser.sourceforge.net/), with the SAC (`org.w3c.css.sac`) dependency removed and a more flexible object model introduced.
+
+- **Group/Artifact:** `org.htmlunit:htmlunit-cssparser`
+- **License:** Apache License 2.0
+- **Default branch:** `master`
+- **Java version:** JDK 17+ (version 5.x, current development); JDK 8+ for 4.x releases
+- **Build system:** Maven
+
+## Repository Structure
+
+```
+htmlunit-cssparser/
+├── pom.xml # Maven build configuration
+├── checkstyle.xml # Checkstyle rules (enforced on build)
+├── checkstyle_suppressions.xml # Checkstyle suppression rules
+├── README.md
+├── LICENSE # Apache 2.0
+├── .github/
+│ ├── workflows/
+│ │ └── codeql.yml # CodeQL security scanning (Java)
+│ ├── dependabot.yml # Dependabot dependency updates
+│ └── FUNDING.yml # Sponsorship info
+├── src/
+│ ├── main/
+│ │ ├── java/org/htmlunit/cssparser/
+│ │ │ ├── dom/ # CSS DOM implementation classes
+│ │ │ ├── parser/ # Core parser classes
+│ │ │ │ ├── condition/ # CSS selector conditions
+│ │ │ │ ├── selector/ # CSS selector model
+│ │ │ │ └── media/ # Media query support
+│ │ │ └── util/ # Utility classes
+│ │ └── javacc/
+│ │ └── CSS3Parser.jj # JavaCC grammar file (generates the parser)
+│ └── test/
+│ ├── java/ # JUnit 5 test classes
+│ └── resources/ # CSS test fixture files
+└── target/ # Build output (not committed)
+```
+
+## Build and Test
+
+### Prerequisites
+
+- **Maven 3.6.3+**
+- **JDK 17+** (for current master / version 5.x)
+
+### Commands
+
+```bash
+# Compile (this also runs JavaCC to generate the parser from CSS3Parser.jj)
+mvn compile
+
+# Run all tests
+mvn test
+
+# Full build with checkstyle verification
+mvn -U clean test
+
+# Check for dependency/plugin updates
+mvn versions:display-plugin-updates
+mvn versions:display-dependency-updates
+```
+
+### Generated Code
+
+The CSS parser is generated from a **JavaCC grammar file** at `src/main/javacc/CSS3Parser.jj`. During the `generate-sources` phase, the `ph-javacc-maven-plugin` generates Java source files into `target/generated-sources/javacc/org/htmlunit/cssparser/parser/javacc/`. A post-processing step using the `maven-replacer-plugin` cleans up the generated code (removes dead code patterns produced by JavaCC).
+
+**Do not manually edit files in `target/generated-sources/`** — they are regenerated on every build. If parser behavior needs to change, edit `src/main/javacc/CSS3Parser.jj`.
+
+## Architecture and Key Packages
+
+### `org.htmlunit.cssparser.parser` — Core Parser
+
+The main entry point for users. Key classes:
+
+| Class | Purpose |
+|---|---|
+| `CSSOMParser` | High-level parser that produces a DOM-style tree from CSS input. Main public API. |
+| `AbstractCSSParser` | Base class with shared parsing logic; `CSS3Parser` (generated) extends this. |
+| `InputSource` | Wraps a `Reader` to feed CSS text to the parser. Replaces the old SAC `InputSource`. |
+| `LexicalUnit` / `LexicalUnitImpl` | Represents CSS values (lengths, colors, functions, etc.) as a linked list of lexical tokens. |
+| `CSSErrorHandler` | Interface for custom error handling during parsing. Replaces the old SAC `ErrorHandler`. |
+| `CSSException` / `CSSParseException` | Exception types for parse errors. |
+| `DocumentHandler` / `HandlerBase` | Event-based (SAX-like) callback interface for streaming CSS parsing. |
+| `Locator` / `Locatable` | Source location tracking (line/column numbers). |
+
+### `org.htmlunit.cssparser.parser.selector` — Selector Model
+
+Represents CSS selectors as an object model:
+
+- `Selector`, `SimpleSelector` — base types
+- `ElementSelector` — type selectors (`h1`, `div`, `*`)
+- `DescendantSelector`, `ChildSelector` — combinators (` `, `>`)
+- `DirectAdjacentSelector`, `GeneralAdjacentSelector` — combinators (`+`, `~`)
+- `PseudoElementSelector` — pseudo-elements (`::before`, `::after`)
+- `RelativeSelector` — for `:has()` relative selectors
+- `SelectorList` / `SelectorListImpl` — ordered list of selectors
+- `SelectorSpecificity` — calculates selector specificity
+- `Combinator` — enum of CSS combinator types
+
+### `org.htmlunit.cssparser.parser.condition` — Selector Conditions
+
+Conditions attached to selectors (class, id, attribute, pseudo-class matching):
+
+- `ClassCondition` (`.foo`), `IdCondition` (`#bar`)
+- `AttributeCondition` (`[attr=val]`), `PrefixAttributeCondition` (`[attr^=val]`), `SuffixAttributeCondition` (`[attr$=val]`), `SubstringAttributeCondition` (`[attr*=val]`), `OneOfAttributeCondition` (`[attr~=val]`), `BeginHyphenAttributeCondition` (`[attr|=val]`)
+- `PseudoClassCondition` (`:hover`, `:nth-child()`, etc.)
+- `NotPseudoClassCondition` (`:not()`), `IsPseudoClassCondition` (`:is()`), `HasPseudoClassCondition` (`:has()`), `WherePseudoClassCondition` (`:where()`)
+- `LangCondition` (`:lang()`)
+
+### `org.htmlunit.cssparser.parser.media` — Media Queries
+
+- `MediaQuery` — a single media query (`screen and (min-width: 768px)`)
+- `MediaQueryList` — a list of media queries
+
+### `org.htmlunit.cssparser.dom` — CSS DOM Implementation
+
+Implements a CSS object model (style sheets, rules, values):
+
+- `CSSStyleSheetImpl` — represents a complete stylesheet
+- `CSSStyleRuleImpl` — a style rule (`selector { declarations }`)
+- `CSSStyleDeclarationImpl` — a set of property declarations
+- `CSSMediaRuleImpl`, `CSSImportRuleImpl`, `CSSPageRuleImpl`, `CSSFontFaceRuleImpl`, `CSSCharsetRuleImpl`, `CSSUnknownRuleImpl` — at-rule implementations
+- `CSSRuleListImpl` — ordered list of rules
+- `CSSValueImpl` — wraps parsed CSS values
+- `Property` — a single CSS property with name, value, and priority
+- Color classes: `RGBColorImpl`, `HSLColorImpl`, `HWBColorImpl`, `LABColorImpl`, `LCHColorImpl` (plus `AbstractColor` base)
+- `RectImpl`, `CounterImpl` — CSS `rect()` and `counter()` value types
+- `MediaListImpl`, `CSSStyleSheetListImpl` — list types
+- `DOMExceptionImpl` — DOM exception handling
+
+### `org.htmlunit.cssparser.util` — Utilities
+
+- `ParserUtils` — string processing helpers used by the generated parser (trimming, unescaping)
+
+## Code Style and Quality
+
+### Checkstyle
+
+Checkstyle is **strictly enforced** via `checkstyle.xml` and runs during the build. Key rules:
+
+- **Line length:** 120 characters max
+- **Indentation:** 4-space tabs
+- **Braces:** opening brace on same line (`eol`), closing brace on its own line (`alone`)
+- **Naming conventions:**
+ - Member fields: `camelCase_` (trailing underscore)
+ - Static fields: `CamelCase_` (capital start, trailing underscore)
+ - Constants: `UPPER_SNAKE_CASE` (exception: `log`)
+ - Methods: `camelCase` (test methods may use underscores: `test[A-Z][a-zA-Z0-9_]+`)
+ - Catch parameters: `e`, `ex`, `ignored`, or `expected`
+- **Javadoc:** Required on all public/protected methods, types, and packages. Author tag format: `@author Firstname Lastname`
+- **Imports:** No star imports, no unused imports, no redundant imports
+- **License header:** Required on every source file:
+ ```
+ /*
+ * Copyright (c) 2019-2026 Ronald Brill.
+ *
+ * Licensed under the Apache License, Version 2.0 ...
+ */
+ ```
+- **No `serialVersionUID`** fields
+- **No `@version`** tags
+- **No `System.out`/`System.err`** in production code
+- **Final local variables** and parameters are enforced
+- **No trailing whitespace**, no tab characters, no double blank lines
+- Single empty line after package declaration, none before it
+
+Checkstyle suppressions (`checkstyle_suppressions.xml`):
+- Test files are exempt from `JavadocPackage`, `JavadocMethod`, and `LineLength`
+- Generated files in `target/generated-sources/javacc` are fully exempt
+- `CssCharStream.java` is fully exempt (special character stream handling)
+
+### Testing
+
+- **Framework:** JUnit Jupiter (JUnit 5), version 6.x
+- **Test dependency:** `commons-io` (test scope only)
+- **Test resources:** CSS fixture files in `src/test/resources/`
+- **Run tests:** `mvn test` (uses `maven-surefire-plugin`)
+
+## CI/CD
+
+- **CodeQL:** GitHub Actions workflow (`.github/workflows/codeql.yml`) runs security analysis on pushes/PRs to `master` and weekly (Mondays 23:34 UTC). Analyzes Java code only.
+- **Dependabot:** Configured via `.github/dependabot.yml` for automated dependency update PRs.
+- **Jenkins:** Primary CI runs on an external Jenkins server at `https://jenkins.wetator.org/job/HtmlUnit%20-%20CSS%20Parser/`.
+
+## Making Changes
+
+### Modifying Parser Behavior
+
+1. Edit the JavaCC grammar: `src/main/javacc/CSS3Parser.jj`
+2. Run `mvn compile` to regenerate and compile
+3. Add/update tests to cover the change
+4. Run `mvn test` to verify
+
+### Adding Support for New CSS Features
+
+New CSS features typically require changes in multiple layers:
+
+1. **Grammar** (`CSS3Parser.jj`) — add token definitions and production rules
+2. **Lexical units** (`LexicalUnit.java`, `LexicalUnitImpl.java`) — add new `LexicalUnitType` enum values if needed
+3. **Conditions** (`parser/condition/`) — for new pseudo-classes or attribute selectors
+4. **Selectors** (`parser/selector/`) — for new selector types or combinators
+5. **DOM** (`dom/`) — for new at-rule types or value types
+6. **Tests** — comprehensive tests for parsing, serialization, and error handling
+
+### Code Conventions for PRs
+
+- Run `mvn -U clean test` and ensure all tests pass
+- Run checkstyle: it's part of the build; fix all violations
+- Follow the naming conventions (especially trailing underscores on fields)
+- Add Javadoc to all new public/protected API
+- Keep the license header on all new files
+- Do not modify generated files in `target/`
+
+## Versioning and Releases
+
+- **Current development:** 5.0.0-SNAPSHOT (requires JDK 17+)
+- **Latest stable:** 4.21.0 (December 2025, JDK 8+)
+- **Artifacts:** Published to Maven Central via Sonatype Central Publishing
+- **Release process:** (from README)
+ 1. Ensure all tests pass
+ 2. Update version in `pom.xml` and `README.md`
+ 3. Commit, build, and deploy: `mvn -up clean deploy`
+ 4. Publish on Maven Central Portal
+ 5. Create GitHub release with signed JARs
+ 6. Bump to next SNAPSHOT version
+
+## Dependencies
+
+### Runtime
+
+**None.** The library has zero runtime dependencies — it is completely self-contained.
+
+### Test Only
+
+- `org.junit.jupiter:junit-jupiter-engine`
+- `org.junit.platform:junit-platform-launcher`
+- `commons-io:commons-io`
+
+## Key Design Decisions
+
+1. **No SAC dependency:** The `org.w3c.css.sac` API (stalled since 2008) was removed. All interfaces are built-in, giving the project full control over the object model.
+2. **JavaCC-based parser:** The CSS grammar is defined in `CSS3Parser.jj` and compiled by JavaCC. This provides robust, specification-aligned tokenization and parsing.
+3. **Event-based + DOM-based API:** The parser supports both SAX-like streaming (`DocumentHandler`) and tree-building (`CSSOMParser`) usage patterns.
+4. **Zero runtime dependencies:** Makes the library safe to embed anywhere without dependency conflicts.
+
+## Links
+
+- **Repository:** https://github.com/HtmlUnit/htmlunit-cssparser
+- **Maven Central:** https://central.sonatype.com/artifact/org.htmlunit/htmlunit-cssparser
+- **HtmlUnit:** https://www.htmlunit.org/
+- **Developer Blog:** https://htmlunit.github.io/htmlunit-blog/
+- **CI:** https://jenkins.wetator.org/job/HtmlUnit%20-%20CSS%20Parser/
+- **Sponsor:** https://github.com/sponsors/rbri
+- **Predecessor:** http://cssparser.sourceforge.net/
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..eef4bd2
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
\ No newline at end of file
diff --git a/README.md b/README.md
index f4dbb78..f6b5e3e 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,27 @@ We will try to stay in sync with CSSParser regarding the features in the future.
[HtmlUnit@mastodon](https://fosstodon.org/@HtmlUnit) | [HtmlUnit@bsky](https://bsky.app/profile/htmlunit.bsky.social) | [HtmlUnit@Twitter](https://twitter.com/HtmlUnit)
-### Latest release Version 4.19.0 / November 23, 2025
+#### Version 5
+
+Work on HtmlUnit-CSSParser 5.0 has started. This new major version will require **JDK 17 or higher**.
+
+
+#### Legacy Support (JDK 8)
+
+If you need to continue using **JDK 8**, please note that versions 4.x will remain available as-is. However,
+**ongoing maintenance and fixes for JDK 8 compatibility are only available through sponsorship**.
+
+Maintaining separate fix versions for JDK 8 requires significant additional effort for __backporting__, testing, and release management.
+
+**To enable continued JDK 8 support**, please contact me via email to discuss sponsorship options. Sponsorship provides:
+
+- __Backporting__ security and bug fixes to the 4.x branch
+- Maintaining compatibility with older Java versions
+- Timely releases for critical issues
+
+Without sponsorship, the 4.x branch will not receive updates. Your support ensures the long-term __sustainability__ of this project across multiple Java versions.
+
+### Latest release Version 4.21.0 / December 28, 2025
## Get it!
@@ -30,7 +50,7 @@ Add to your `pom.xml`:
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
+ * ignored (this is the default behavior implemented by
* HandlerBase). Applications may register a new or different handler in the
@@ -142,7 +142,7 @@ protected CSSErrorHandler getErrorHandler() {
* 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).
+
+**[JetBrains](https://www.jetbrains.com/)** for providing IntelliJ IDEA under their [open source development license](https://www.jetbrains.com/community/opensource/) and
+
+
+Eclipse Foundation for their Eclipse IDE
+
+
+to **[Syntevo](https://www.syntevo.com/)** for their excellent [SmartGit](https://www.smartgit.dev/)!
diff --git a/checkstyle.xml b/checkstyle.xml
index a85e3b7..9e90772 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -106,6 +106,10 @@
Applications may register a new or different handler in the * middle of a parse, and the CSS parser must begin using the new @@ -911,7 +911,7 @@ protected int intValue(final char op, final String s) { } /** - * Parses the sting into an double. + * Parses the sting into a double. * * @param op the sign char * @param s the string to parse @@ -944,7 +944,7 @@ protected int getLastNumPos(final String s) { /** * 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. @@ -1075,49 +1075,25 @@ public String unescape(final String s, final boolean unescapeDoubleQuotes) { } 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; - } + return switch (c) { + case '0' -> 0; + case '1' -> 1; + case '2' -> 2; + case '3' -> 3; + case '4' -> 4; + case '5' -> 5; + case '6' -> 6; + case '7' -> 7; + case '8' -> 8; + case '9' -> 9; + case 'a', 'A' -> 10; + case 'b', 'B' -> 11; + case 'c', 'C' -> 12; + case 'd', 'D' -> 13; + case 'e', 'E' -> 14; + case 'f', 'F' -> 15; + default -> -1; + }; } protected String normalizeAndValidatePagePseudoClass(final Token t) { diff --git a/src/main/java/org/htmlunit/cssparser/parser/DocumentHandler.java b/src/main/java/org/htmlunit/cssparser/parser/DocumentHandler.java index 136689c..df77f6a 100644 --- a/src/main/java/org/htmlunit/cssparser/parser/DocumentHandler.java +++ b/src/main/java/org/htmlunit/cssparser/parser/DocumentHandler.java @@ -29,7 +29,7 @@ public interface DocumentHandler { /** * Receive notification of the beginning of a style sheet. - * + *
* The CSS parser will invoke this method only once, before any other * methods in this interface. * @@ -41,7 +41,7 @@ public interface DocumentHandler { /** * Receive notification of the end of a document. - * + *
* The CSS parser will invoke this method only once, and it will be the * last method invoked during the parse. The parser shall not invoke this * method until it has either abandoned parsing (because of an @@ -126,7 +126,7 @@ void importStyle(String uri, MediaQueryList media, /** * Receive notification of the beginning of a font face statement. - * + *
* The Parser will invoke this method at the beginning of every font face * statement in the style sheet. there will be a corresponding endFontFace() * event for every startFontFace() event. @@ -139,7 +139,7 @@ void importStyle(String uri, MediaQueryList media, /** * Receive notification of the beginning of a page statement. - * + *
* The Parser will invoke this method at the beginning of every page * statement in the style sheet. there will be a corresponding endPage() * event for every startPage() event. @@ -154,7 +154,7 @@ void importStyle(String uri, MediaQueryList media, /** * Receive notification of the beginning of a media statement. - * + *
* The Parser will invoke this method at the beginning of every media
* statement in the style sheet. there will be a corresponding endMedia()
* event for every startElement() event.
diff --git a/src/main/java/org/htmlunit/cssparser/parser/LexicalUnit.java b/src/main/java/org/htmlunit/cssparser/parser/LexicalUnit.java
index 08474e6..1d183f6 100644
--- a/src/main/java/org/htmlunit/cssparser/parser/LexicalUnit.java
+++ b/src/main/java/org/htmlunit/cssparser/parser/LexicalUnit.java
@@ -71,6 +71,30 @@ enum LexicalUnitType {
VMIN,
/** VMAX. */
VMAX,
+ /** DVW. */
+ DVW,
+ /** DVH. */
+ DVH,
+ /** DVMIN. */
+ DVMIN,
+ /** DVMAX. */
+ DVMAX,
+ /** LVW. */
+ LVW,
+ /** LVH. */
+ LVH,
+ /** LVMIN. */
+ LVMIN,
+ /** LVMAX. */
+ LVMAX,
+ /** SVW. */
+ SVW,
+ /** SVH. */
+ SVH,
+ /** SVMIN. */
+ SVMIN,
+ /** SVMAX. */
+ SVMAX,
/** PIXEL. */
PIXEL,
/** INCH. */
diff --git a/src/main/java/org/htmlunit/cssparser/parser/LexicalUnitImpl.java b/src/main/java/org/htmlunit/cssparser/parser/LexicalUnitImpl.java
index 737ff3b..f3faa04 100644
--- a/src/main/java/org/htmlunit/cssparser/parser/LexicalUnitImpl.java
+++ b/src/main/java/org/htmlunit/cssparser/parser/LexicalUnitImpl.java
@@ -224,60 +224,46 @@ public double getDoubleValue() {
@Override
public String getDimensionUnitText() {
- switch (lexicalUnitType_) {
- case EM:
- return "em";
- case REM:
- return "rem";
- case EX:
- return "ex";
- case CH:
- return "ch";
- case VW:
- return "vw";
- case VH:
- return "vh";
- case VMIN:
- return "vmin";
- case VMAX:
- return "vmax";
- case PIXEL:
- return "px";
- case INCH:
- return "in";
- case CENTIMETER:
- return "cm";
- case MILLIMETER:
- return "mm";
- case POINT:
- return "pt";
- case PICA:
- return "pc";
- case QUATER:
- return "Q";
- case PERCENTAGE:
- return "%";
- case DEGREE:
- return "deg";
- case GRADIAN:
- return "grad";
- case RADIAN:
- return "rad";
- case TURN:
- return "turn";
- case MILLISECOND:
- return "ms";
- case SECOND:
- return "s";
- case HERTZ:
- return "Hz";
- case KILOHERTZ:
- return "kHz";
- case DIMENSION:
- return dimension_;
- default:
- return "";
- }
+ return switch (lexicalUnitType_) {
+ case EM -> "em";
+ case REM -> "rem";
+ case EX -> "ex";
+ case CH -> "ch";
+ case VW -> "vw";
+ case VH -> "vh";
+ case VMIN -> "vmin";
+ case VMAX -> "vmax";
+ case DVW -> "dvw";
+ case DVH -> "dvh";
+ case DVMIN -> "dvmin";
+ case DVMAX -> "dvmax";
+ case LVW -> "lvw";
+ case LVH -> "lvh";
+ case LVMIN -> "lvmin";
+ case LVMAX -> "lvmax";
+ case SVW -> "svw";
+ case SVH -> "svh";
+ case SVMIN -> "svmin";
+ case SVMAX -> "svmax";
+ case PIXEL -> "px";
+ case INCH -> "in";
+ case CENTIMETER -> "cm";
+ case MILLIMETER -> "mm";
+ case POINT -> "pt";
+ case PICA -> "pc";
+ case QUATER -> "Q";
+ case PERCENTAGE -> "%";
+ case DEGREE -> "deg";
+ case GRADIAN -> "grad";
+ case RADIAN -> "rad";
+ case TURN -> "turn";
+ case MILLISECOND -> "ms";
+ case SECOND -> "s";
+ case HERTZ -> "Hz";
+ case KILOHERTZ -> "kHz";
+ case DIMENSION -> dimension_;
+ default -> "";
+ };
}
@Override
@@ -363,6 +349,18 @@ public String getCssText() {
case VH:
case VMIN:
case VMAX:
+ case DVW:
+ case DVH:
+ case DVMIN:
+ case DVMAX:
+ case LVW:
+ case LVH:
+ case LVMIN:
+ case LVMAX:
+ case SVW:
+ case SVH:
+ case SVMIN:
+ case SVMAX:
case PIXEL:
case INCH:
case CENTIMETER:
@@ -584,6 +582,78 @@ public String toDebugString() {
.append(getDimensionUnitText())
.append(")");
break;
+ case DVW:
+ sb.append("DVW(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case DVH:
+ sb.append("DVH(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case DVMIN:
+ sb.append("DVMIN(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case DVMAX:
+ sb.append("DVMAX(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case LVW:
+ sb.append("LVW(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case LVH:
+ sb.append("LVH(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case LVMIN:
+ sb.append("LVMIN(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case LVMAX:
+ sb.append("LVMAX(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case SVW:
+ sb.append("SVW(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case SVH:
+ sb.append("SVH(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case SVMIN:
+ sb.append("SVMIN(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
+ case SVMAX:
+ sb.append("SVMAX(")
+ .append(getTrimedDoubleValue())
+ .append(getDimensionUnitText())
+ .append(")");
+ break;
case PIXEL:
sb.append("PIXEL(")
.append(getTrimedDoubleValue())
@@ -973,6 +1043,114 @@ public static LexicalUnit createVMax(final LexicalUnit prev, final double d) {
return new LexicalUnitImpl(prev, LexicalUnitType.VMAX, d);
}
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type dvw
+ */
+ public static LexicalUnit createDvw(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.DVW, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type dvh
+ */
+ public static LexicalUnit createDvh(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.DVH, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type dvmin
+ */
+ public static LexicalUnit createDvMin(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.DVMIN, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type dvmax
+ */
+ public static LexicalUnit createDvMax(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.DVMAX, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type lvw
+ */
+ public static LexicalUnit createLvw(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.LVW, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type lvh
+ */
+ public static LexicalUnit createLvh(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.LVH, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type lvmin
+ */
+ public static LexicalUnit createLvMin(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.LVMIN, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type lvmax
+ */
+ public static LexicalUnit createLvMax(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.LVMAX, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type svw
+ */
+ public static LexicalUnit createSvw(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.SVW, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type svh
+ */
+ public static LexicalUnit createSvh(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.SVH, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type svmin
+ */
+ public static LexicalUnit createSvMin(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.SVMIN, d);
+ }
+
+ /**
+ * @param prev the previous LexicalUnit
+ * @param d the double value
+ * @return lexical unit with type svmax
+ */
+ public static LexicalUnit createSvMax(final LexicalUnit prev, final double d) {
+ return new LexicalUnitImpl(prev, LexicalUnitType.SVMAX, d);
+ }
+
/**
* @param prev the previous LexicalUnit
* @param d the double value
diff --git a/src/main/java/org/htmlunit/cssparser/parser/Locator.java b/src/main/java/org/htmlunit/cssparser/parser/Locator.java
index e795e96..b469da4 100644
--- a/src/main/java/org/htmlunit/cssparser/parser/Locator.java
+++ b/src/main/java/org/htmlunit/cssparser/parser/Locator.java
@@ -103,10 +103,9 @@ public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
- if (!(obj instanceof Locator)) {
+ if (!(obj instanceof Locator l)) {
return false;
}
- final Locator l = (Locator) obj;
return (getColumnNumber() == l.getColumnNumber())
&& (getLineNumber() == l.getLineNumber())
&& ParserUtils.equals(getUri(), l.getUri());
diff --git a/src/main/java/org/htmlunit/cssparser/util/ParserUtils.java b/src/main/java/org/htmlunit/cssparser/util/ParserUtils.java
index ab14a24..a33e7d8 100644
--- a/src/main/java/org/htmlunit/cssparser/util/ParserUtils.java
+++ b/src/main/java/org/htmlunit/cssparser/util/ParserUtils.java
@@ -14,6 +14,8 @@
*/
package org.htmlunit.cssparser.util;
+import java.util.Objects;
+
/**
* Util methods.
*
@@ -63,7 +65,7 @@ public static int hashCode(final int seed, final Object obj) {
* @return true if the both objects are equals
*/
public static boolean equals(final Object obj1, final Object obj2) {
- return obj1 == null ? obj2 == null : obj1.equals(obj2);
+ return Objects.equals(obj1, obj2);
}
/**
diff --git a/src/main/javacc/CSS3Parser.jj b/src/main/javacc/CSS3Parser.jj
index aed93c0..b39dd79 100644
--- a/src/main/javacc/CSS3Parser.jj
+++ b/src/main/javacc/CSS3Parser.jj
@@ -1,3354 +1,3324 @@
-/*
- * 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.
- */
-
-options {
- IGNORE_CASE = true;
-
- UNICODE_INPUT = true;
- USER_CHAR_STREAM = true;
-
-// DEBUG_TOKEN_MANAGER = true;
-// DEBUG_PARSER = true;
-
- JDK_VERSION = "1.8";
-// JAVA_TEMPLATE_TYPE = "modern"
-}
-
-PARSER_BEGIN(CSS3Parser)
-
-package org.htmlunit.cssparser.parser.javacc;
-
-import java.util.LinkedList;
-import java.util.Locale;
-
-import org.htmlunit.cssparser.dom.CSSValueImpl;
-import org.htmlunit.cssparser.dom.Property;
-import org.htmlunit.cssparser.parser.AbstractCSSParser;
-import org.htmlunit.cssparser.parser.CSSParseException;
-import org.htmlunit.cssparser.parser.LexicalUnit;
-import org.htmlunit.cssparser.parser.LexicalUnitImpl;
-import org.htmlunit.cssparser.parser.LexicalUnit.LexicalUnitType;
-import org.htmlunit.cssparser.parser.Locatable;
-import org.htmlunit.cssparser.parser.Locator;
-import org.htmlunit.cssparser.parser.condition.AttributeCondition;
-import org.htmlunit.cssparser.parser.condition.BeginHyphenAttributeCondition;
-import org.htmlunit.cssparser.parser.condition.ClassCondition;
-import org.htmlunit.cssparser.parser.condition.Condition;
-import org.htmlunit.cssparser.parser.condition.IdCondition;
-import org.htmlunit.cssparser.parser.condition.LangCondition;
-import org.htmlunit.cssparser.parser.condition.HasPseudoClassCondition;
-import org.htmlunit.cssparser.parser.condition.IsPseudoClassCondition;
-import org.htmlunit.cssparser.parser.condition.NotPseudoClassCondition;
-import org.htmlunit.cssparser.parser.condition.OneOfAttributeCondition;
-import org.htmlunit.cssparser.parser.condition.PrefixAttributeCondition;
-import org.htmlunit.cssparser.parser.condition.PseudoClassCondition;
-import org.htmlunit.cssparser.parser.condition.SubstringAttributeCondition;
-import org.htmlunit.cssparser.parser.condition.SuffixAttributeCondition;
-import org.htmlunit.cssparser.parser.condition.WherePseudoClassCondition;
-import org.htmlunit.cssparser.parser.media.MediaQuery;
-import org.htmlunit.cssparser.parser.media.MediaQueryList;
-import org.htmlunit.cssparser.parser.selector.ChildSelector;
-import org.htmlunit.cssparser.parser.selector.Combinator;
-import org.htmlunit.cssparser.parser.selector.DescendantSelector;
-import org.htmlunit.cssparser.parser.selector.DirectAdjacentSelector;
-import org.htmlunit.cssparser.parser.selector.ElementSelector;
-import org.htmlunit.cssparser.parser.selector.GeneralAdjacentSelector;
-import org.htmlunit.cssparser.parser.selector.PseudoElementSelector;
-import org.htmlunit.cssparser.parser.selector.RelativeSelector;
-import org.htmlunit.cssparser.parser.selector.Selector;
-import org.htmlunit.cssparser.parser.selector.SelectorList;
-import org.htmlunit.cssparser.parser.selector.SelectorListImpl;
-import org.htmlunit.cssparser.parser.selector.SimpleSelector;
-import org.htmlunit.cssparser.util.ParserUtils;
-
-/**
- * @author David Schweinsberg
- * @author waldbaer
- * @author Ahmed Ashour
- * @author Ronald Brill
- */
-public class CSS3Parser extends AbstractCSSParser {
-
- public CSS3Parser() {
- this((CharStream) null);
- }
-
- @Override
- public String getParserVersion() {
- return "http://www.w3.org/Style/CSS/";
- }
-
- protected String getGrammarUri()
- {
- return "http://www.w3.org/TR/WD-css3-syntax-20030813";
- }
-}
-
-PARSER_END(CSS3Parser)
-
-TOKEN_MGR_DECLS :
-{
-}
-
- )? >
-}
-
- | )* ( )* | | | )*
- ( charsetRule() | importRule(false) | styleRule() | mediaRule() | pageRule() | fontFaceRule() | unknownAtRule() )
- ( )*
-}
-
-void charsetRule() :
-{
- Token t;
- Locator locator;
-}
-{
- try
- {
-
- t = )*
- ( t = )*
- ( mediaList(ml) )?
- )*
- mediaList(ml)
- {
- start = true;
- handleStartMedia(ml, locator);
- }
- )*
- ( mediaRuleList() )?
- )* { ml.add(mq); } mq = mediaQuery() )*
- { ml.add(mq); }
- }
- catch(ParseException e)
- {
- throw toCSSParseException("invalidMediaList", e);
- }
-}
-
-//
-// media_query
-// : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*
-// | expression [ AND S* expression ]*
-// ;
-//
-MediaQuery mediaQuery() :
-{
- String s;
- MediaQuery mq;
- Property p;
- boolean only = false;
- boolean not = false;
-}
-{
- (
- (
- (
- (
- )*
- )?
- s = medium()
- { mq = new MediaQuery(s, only, not); mq.setLocator(createLocator(token)); }
- (
- )*
- p = mediaExpression()
- {
- mq.addMediaProperty(p);
- }
- )*
- )
- |
- (
- p = mediaExpression()
- {
- mq = new MediaQuery(null, only, not);
- mq.setLocator(createLocator(token));
- mq.addMediaProperty(p);
- }
- (
- )*
- p = mediaExpression()
- {
- mq.addMediaProperty(p);
- }
- )*
- )
- )
- { return mq; }
-}
-
-//
-// expression
-// : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
-// ;
-//
-Property mediaExpression() :
-{
- String p;
- LexicalUnit e = null;
- Property prop;
- Token t;
-}
-{
- )*
- (
- t = identExcludingOnly() ( )* { p = unescape(t.image, false); }
- | t = )* { p = unescape(t.image, false); }
- )
- (
- )*
- e = expr()
- )?
- )*
- {
- if(e==null)
- {
- prop = new Property(p, null, false);
- }
- else
- {
- prop = new Property(p, new CSSValueImpl(e), false);
- }
- return prop;
- }
-}
-
-void mediaRuleList() :
-{
-}
-{
- ( ( styleRule() | mediaRule() | pageRule() | importRule(true) | unknownAtRule() ) ( )* )+
-}
-
-//
-// medium
-// : IDENT S*
-// ;
-//
-String medium() :
-{
- Token t;
-}
-{
- t = identExcludingOnly() ( )*
- {
- return unescape(t.image, false);
- }
-}
-
-//
-// page
-// : PAGE_SYM S* pseudo_page? S*
-// '{' S* declaration [ ';' S* declaration ]* '}' S*
-// ;
-//
-void pageRule() :
-{
- String sel = null;
- boolean start = false;
- Locator locator;
-}
-{
- try {
- )*
-
- ( sel = pageSelectorList() )?
-
- )*
- {
- start = true;
- handleStartPage(null, sel, locator);
- }
-
- styleDeclaration()
- )* sel = pageSelector() { selectors.add(sel); }
- )*
-
- { return String.join(", ", selectors); }
-}
-
-//
-// pageSelector
-// : pseudoPage+ | IDENT pseudoPage*
-// ;
-//
-String pageSelector() :
-{
- StringBuilder pseudos = new StringBuilder();
- String pseudo;
- Token ident;
-}
-{
- (
- pseudo = pseudoPage() { pseudos.append(pseudo); }
- |
- ident = ident() { pseudos.append(unescape(ident.image, false)); }
- )
- ( pseudo = pseudoPage() { pseudos.append(pseudo); } )*
- ( )*
-
- { return pseudos.toString(); }
-}
-
-//
-// pseudoPage
-// : ':' IDENT
-// ;
-//
-String pseudoPage() :
-{
- Token t;
-}
-{
- )*
- )* { start = true; handleStartFontFace(locator); }
- styleDeclaration()
- )* { return new LexicalUnitImpl(prev, LexicalUnitType.OPERATOR_SLASH); }
- | )* { return LexicalUnitImpl.createComma(prev); }
-}
-
-//
-// combinator
-// : PLUS S*
-// | GREATER S*
-// | TILDE S*
-// | S
-// ;
-//
-Combinator combinator() :
-{
- Combinator c = Combinator.DESCENDANT_COMBINATOR;
-}
-{
- (
- )*
- | )*
- | )*
- |
- (
( )*
- )?
- )
- { return c; }
-}
-
-Combinator relativeCombinator() :
-{
- Combinator c = Combinator.DESCENDANT_COMBINATOR;
-}
-{
- (
- )*
- | )*
- | )*
- )?
- { return c; }
-}
-
-//
-// unary_operator
-// : '-' | PLUS
-// ;
-//
-char unaryOperator() :
-{
-}
-{
- ( )*
- {
- start = true;
- handleStartSelector(selList, createLocator(t.next));
- }
- styleDeclaration()
- ( )*
- selectors = selectorList()
- )*
- { selList.add(sel); }
- sel = selector() { selList.setLocator(sel.getLocator()); }
- )*
- {
- selList.add(sel);
- return selList;
- }
-}
-
-SelectorList relativeSelectorList() :
-{
- SelectorListImpl selList = new SelectorListImpl();
- Selector sel;
- Combinator comb;
-}
-{
- comb = relativeCombinator()
- sel = selector() { selList.setLocator(sel.getLocator()); }
- ( )*
- { selList.add(new RelativeSelector(comb, sel)); }
-
- comb = relativeCombinator()
- sel = selector() { selList.setLocator(sel.getLocator()); }
- )*
- {
- selList.add(new RelativeSelector(comb, sel));
- return selList;
- }
-}
-
-//
-// selector
-// : simple_selector_sequence [ combinator simple_selector_sequence ]*
-// ;
-//
-Selector selector() :
-{
- Selector sel;
- Combinator comb;
-}
-{
- try {
- sel = simpleSelector(null, Combinator.DESCENDANT_COMBINATOR)
- ( LOOKAHEAD(2) comb = combinator() sel = simpleSelector(sel, comb) )* ( )*
- {
- return sel;
- }
- } catch (ParseException e) {
- throw toCSSParseException("invalidSelector", e);
- }
-}
-
-//
-// simple_selector
-// : element_name [ HASH | class | attrib | pseudo ]*
-// | [ HASH | class | attrib | pseudo ]+
-// ;
-//
-Selector simpleSelector(Selector sel, Combinator comb) :
-{
- ElementSelector elemSel = null;
- SimpleSelector simpleSel = null;
- Condition c = null;
- SimpleSelector pseudoElementSel = null;
- Object o = null;
-}
-{
- try
- {
- (
- ( elemSel = elementName()
- ( c = hash(null != pseudoElementSel) { elemSel.addCondition(c); }
- | c = _class(null != pseudoElementSel) { elemSel.addCondition(c); }
- | c = attrib(null != pseudoElementSel) { elemSel.addCondition(c); }
- | (
- o = pseudo(null != pseudoElementSel)
- { if (o instanceof Condition)
- { elemSel.addCondition((Condition) o);
- } else {
- pseudoElementSel = (SimpleSelector) o;
- }
- }
- )
- )*
- )
- |
- ( { elemSel = new ElementSelector(null, createLocator(token)); }
- ( c = hash(null != pseudoElementSel) { elemSel.addCondition(c); }
- | c = _class(null != pseudoElementSel) { elemSel.addCondition(c); }
- | c = attrib(null != pseudoElementSel) { elemSel.addCondition(c); }
- | (
- o = pseudo(null != pseudoElementSel)
- { if (o instanceof Condition)
- { elemSel.addCondition((Condition) o);
- } else {
- pseudoElementSel = (SimpleSelector) o;
- }
- }
- )
- )+
- )
- )
-
- {
- simpleSel = elemSel;
- if (sel == null) {
- sel = simpleSel;
- } else {
- switch (comb) {
- case DESCENDANT_COMBINATOR:
- sel = new DescendantSelector(sel, simpleSel);
- break;
- case NEXT_SIBLING_COMBINATOR:
- sel = new DirectAdjacentSelector(sel, simpleSel);
- break;
- case CHILD_COMBINATOR:
- sel = new ChildSelector(sel, simpleSel);
- break;
- case SUBSEQUENT_SIBLING_COMBINATOR:
- sel = new GeneralAdjacentSelector(sel, simpleSel);
- break;
- }
- }
- if (pseudoElementSel != null)
- {
- sel = new DescendantSelector(sel, pseudoElementSel);
- }
-
- return sel;
- }
- }
- catch (ParseException e)
- {
- throw toCSSParseException("invalidSimpleSelector", e);
- }
-}
-
-//
-// class
-// : '.' IDENT
-// ;
-//
-Condition _class(boolean pseudoElementFound) :
-{
- Token t;
- Locator locator;
- ParseException pe = null;
-}
-{
- try
- {
- { if (pseudoElementFound) { pe = generateParseException(); } }
-