Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ffa2d99
Merge remote-tracking branch 'origin/bug/ecssunit-dvh-missing'
jmini Dec 3, 2025
3fc5eda
[release] set version to "8.1.1-unblu-1"
jmini Dec 3, 2025
4fe3702
Set version to "8.1.1-SNAPSHOT"
jmini Dec 3, 2025
f0232dd
Merge remote-tracking branch 'upstream/master'
shagkur Mar 31, 2026
0f4b10d
#121 add support for CSS Houdini @property rule
shagkur Apr 2, 2026
c420cc9
#121 simplified variation of EXPR for property rule declaration
shagkur Apr 2, 2026
37b4104
#121 correct exception message
shagkur Apr 2, 2026
1097431
#121 catch exception for invalid declaration (invalid declarations sh…
shagkur Apr 3, 2026
12de637
#121 fix handling of invalid descriptors
shagkur Apr 3, 2026
37f1315
#121 drop invalid descriptors properly
shagkur Apr 3, 2026
70696bb
#121 add IN_PROPERTY_RULE lexer state
shagkur Apr 5, 2026
eef5b4a
#121 add missing property rule methods
shagkur Apr 6, 2026
4306b30
#121 properly handle SEMICOLON for last declaration in list
shagkur Apr 6, 2026
418d2ce
#121 manually incorporate unittest from @blutorange
shagkur Apr 6, 2026
b2485a6
#121 adjust parser file
shagkur Apr 7, 2026
30abdcc
#121 fix rule tests and syntax in parser definition
shagkur Apr 7, 2026
f29e858
#121 also switch to DEFAULT if IN_UNKNOWN_RULE (as it was before)
shagkur Apr 7, 2026
f498abf
#121 fix linked class name in test class
shagkur Apr 7, 2026
46280f0
#121 properly handle invalid descriptor(s) with ParseException
shagkur Apr 7, 2026
e3a4938
#121 introduce DASHED_IDENT when IN_PROPERTY_RULE to properly let the…
shagkur Apr 7, 2026
30d9b8f
#121 cleanup grammar
shagkur Apr 7, 2026
3964013
Merge remote-tracking branch 'upstream/master'
shagkur Apr 8, 2026
61b66bb
Merge branch 'master' into feature/add-property-rule-support
shagkur Apr 8, 2026
03fd7ae
#121 apply required changes for the nested styledeclaration support (…
shagkur Apr 8, 2026
56cd87f
#121 missed newline
shagkur Apr 8, 2026
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
5 changes: 5 additions & 0 deletions ph-css/src/main/java/com/helger/css/ICSSWriterSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ public interface ICSSWriterSettings
*/
boolean isWriteSupportsRules ();

/**
* @return <code>true</code> if {@link CSSPropertyRule @property rules} should be written, <code>false</code> if not
*/
boolean isWritePropertyRules ();

/**
* @return <code>true</code> if {@link CSSUnknownRule unknown @ rules} should be written, <code>false</code> if not
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,57 @@ public ICommonsList <CSSSupportsRule> getAllSupportsRules ()
return m_aRules.getAllMapped (CSSSupportsRule.class::isInstance, CSSSupportsRule.class::cast);
}

/**
* Check if at least one of the top-level rules is a property rule (implementing
* {@link CSSPropertyRule}).
*
* @return <code>true</code> if at least one <code>@property</code> rule is contained,
* <code>false</code> otherwise.
*/
public boolean hasPropertyRules ()
{
return m_aRules.containsAny (CSSPropertyRule.class::isInstance);
}

/**
* Get the number of top-level rules that are property rules (implementing
* {@link CSSPropertyRule}).
*
* @return The number of contained <code>@supports</code> rules. Always &ge; 0.
*/
@Nonnegative
public int getPropertyRuleCount ()
{
return m_aRules.getCount (CSSPropertyRule.class::isInstance);
}

/**
* Get the <code>@property</code> rule at the specified index.
*
* @param nIndex
* The index to be resolved. Should be &ge; 0 and &lt; {@link #getPropertyRuleCount()}.
* @return <code>null</code> if an invalid index was specified.
* @since 8.1.2
*/
@Nullable
public CSSPropertyRule getPropertyRuleAtIndex (@Nonnegative final int nIndex)
{
return m_aRules.getAtIndexMapped (CSSPropertyRule.class::isInstance, nIndex, CSSPropertyRule.class::cast);
}

/**
* Get a list of all top-level rules that are property rules (implementing
* {@link CSSPropertyRule}).
*
* @return A copy of all contained <code>@property</code> rules. Never <code>null</code>.
*/
@NonNull
@ReturnsMutableCopy
public ICommonsList <CSSPropertyRule> getAllPropertyRules ()
{
return m_aRules.getAllMapped (CSSPropertyRule.class::isInstance, CSSPropertyRule.class::cast);
}

/**
* Check if at least one of the top-level rules is an unknown rule (implementing
* {@link CSSUnknownRule}).
Expand Down
211 changes: 211 additions & 0 deletions ph-css/src/main/java/com/helger/css/decl/CSSPropertyRule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Copyright (C) 2014-2026 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.decl;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import com.helger.annotation.Nonempty;
import com.helger.annotation.Nonnegative;
import com.helger.annotation.style.ReturnsMutableCopy;
import com.helger.base.enforce.ValueEnforcer;
import com.helger.base.hashcode.HashCodeGenerator;
import com.helger.base.state.EChange;
import com.helger.base.string.StringHelper;
import com.helger.base.tostring.ToStringGenerator;
import com.helger.collection.commons.ICommonsList;
import com.helger.css.CSSSourceLocation;
import com.helger.css.ICSSSourceLocationAware;
import com.helger.css.ICSSWriterSettings;

/**
* Represents a single <code>@property</code> rule: a list of style rules only valid when a certain
* declaration is available. See {@link com.helger.css.ECSSSpecification#CSS3_CONDITIONAL}<br>
* Example:<br>
* <code>@supports (transition-property: color) {
div { color:red; }
}</code>
*
* @author Mike Wiedenbauer
*/
public class CSSPropertyRule extends AbstractHasTopLevelRules implements ICSSTopLevelRule, ICSSSourceLocationAware
{
private final String m_sIdentifier;
private final CSSPropertyRuleDeclarationList m_aDeclarations = new CSSPropertyRuleDeclarationList();
private CSSSourceLocation m_aSourceLocation;

public static boolean isValidIdentifier (@NonNull @Nonempty final String sIdentifier)
{
return StringHelper.startsWith (sIdentifier, "--");
}

public CSSPropertyRule (@NonNull @Nonempty final String sIdentifier)
{
ValueEnforcer.isTrue (isValidIdentifier (sIdentifier), "Identifier is invalid");
m_sIdentifier = sIdentifier;
}

@NonNull
@Nonempty
public String getIdentifier ()
{
return m_sIdentifier;
}

@NonNull
public CSSPropertyRule addDeclaration (@NonNull final CSSPropertyRuleDeclaration aDeclaration)
{
ValueEnforcer.notNull (aDeclaration, "PropertyRuleDeclaration");

m_aDeclarations.addDeclaration (aDeclaration);
return this;
}

@NonNull
public CSSPropertyRule addDeclaration (@Nonnegative final int nIndex, @NonNull final CSSPropertyRuleDeclaration aDeclaration)
{
ValueEnforcer.isGE0(nIndex, "Index");
ValueEnforcer.notNull (aDeclaration, "PropertyRuleDeclaration");

m_aDeclarations.addDeclaration (nIndex, aDeclaration);
return this;
}

@NonNull
public EChange removeDeclaration (@NonNull final CSSPropertyRuleDeclaration aDeclaration)
{
return m_aDeclarations.removeDeclaration (aDeclaration);
}

@NonNull
public EChange removeDeclaration (@Nonnegative final int nIndex)
{
return m_aDeclarations.removeDeclaration (nIndex);
}

@NonNull
public EChange removeAllDeclarations ()
{
return m_aDeclarations.removeAllDeclarations ();
}

@NonNull
@ReturnsMutableCopy
public ICommonsList <CSSPropertyRuleDeclaration> getAllDeclarations ()
{
return m_aDeclarations.getAllDeclarations();
}

@Nullable
public CSSPropertyRuleDeclaration getDeclarationAtIndex (@Nonnegative final int nIndex)
{
return m_aDeclarations.getDeclarationAtIndex (nIndex);
}

@NonNull
public CSSPropertyRule setDeclarationAtIndex (@Nonnegative final int nIndex, @NonNull final CSSPropertyRuleDeclaration aNewDeclaration)
{
m_aDeclarations.setDeclarationAtIndex (nIndex, aNewDeclaration);
return this;
}

public boolean hasDeclarations ()
{
return m_aDeclarations.hasDeclarations ();
}

@Nonnegative
public int getDeclarationCount ()
{
return m_aDeclarations.getDeclarationCount ();
}

@NonNull
@Nonempty
public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel)
{
// Always ignore Property rules?
if (!aSettings.isWritePropertyRules ())
return "";

final boolean bOptimizedOutput = aSettings.isOptimizedOutput ();
final int nDeclCount = m_aDeclarations.getDeclarationCount();

final StringBuilder aSB = new StringBuilder ("@property ").append (m_sIdentifier);
if (nDeclCount == 0)
{
aSB.append (bOptimizedOutput ? "{}" : " {}");
}
else
{
if (nDeclCount == 1)
{
aSB.append (bOptimizedOutput ? "{" : " { ");
aSB.append (m_aDeclarations.getAsCSSString (aSettings, nIndentLevel + 1));
aSB.append (bOptimizedOutput ? "}" : " }");
}
else
{
aSB.append (bOptimizedOutput ? "{" : " {" + aSettings.getNewLineString ());
if (!bOptimizedOutput)
aSB.append (aSettings.getIndent (nIndentLevel + 1));
aSB.append (m_aDeclarations.getAsCSSString (aSettings, nIndentLevel + 1));
if (!bOptimizedOutput)
aSB.append (aSettings.getNewLineString ()).append (aSettings.getIndent (nIndentLevel));
aSB.append ('}');
}
}
return aSB.toString();
}

@Nullable
public final CSSSourceLocation getSourceLocation ()
{
return m_aSourceLocation;
}

public final void setSourceLocation (@Nullable final CSSSourceLocation aSourceLocation)
{
m_aSourceLocation = aSourceLocation;
}

@Override
public boolean equals (final Object o)
{
if (o == this)
return true;
if (o == null || !getClass ().equals (o.getClass ()))
return false;
final CSSPropertyRule rhs = (CSSPropertyRule) o;
return m_sIdentifier.equals (rhs.m_sIdentifier) && m_aDeclarations.equals (rhs.m_aDeclarations);
}

@Override
public int hashCode ()
{
return new HashCodeGenerator (this).append (m_sIdentifier).append (m_aDeclarations).getHashCode ();
}

@Override
public String toString ()
{
return new ToStringGenerator (this).append ("identifier", m_sIdentifier)
.append ("declaration", m_aDeclarations)
.appendIfNotNull ("SourceLocation", m_aSourceLocation)
.getToString ();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.helger.css.decl;

import java.util.Locale;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import com.helger.annotation.Nonempty;
import com.helger.annotation.Nonnegative;
import com.helger.annotation.style.ReturnsMutableObject;
import com.helger.base.enforce.ValueEnforcer;
import com.helger.base.hashcode.HashCodeGenerator;
import com.helger.base.tostring.ToStringGenerator;
import com.helger.css.CCSS;
import com.helger.css.CSSSourceLocation;
import com.helger.css.ICSSSourceLocationAware;
import com.helger.css.ICSSWriteable;
import com.helger.css.ICSSWriterSettings;

public class CSSPropertyRuleDeclaration implements ICSSSourceLocationAware, ICSSWriteable
{
private String m_sDescriptor;
private CSSExpression m_aExpression;
private CSSSourceLocation m_aSourceLocation;

public CSSPropertyRuleDeclaration (@NonNull @Nonempty final String sDescriptor, @NonNull final CSSExpression aExpression)
{
setDescriptor(sDescriptor);
setExpression(aExpression);
}

@NonNull
@Nonempty
public final String getDescriptor ()
{
return m_sDescriptor;
}

@NonNull
public final CSSPropertyRuleDeclaration setDescriptor (@NonNull @Nonempty final String sDescriptor)
{
ValueEnforcer.notEmpty (sDescriptor, "Descriptor");
m_sDescriptor = sDescriptor.toLowerCase (Locale.ROOT);
return this;
}

@NonNull
@ReturnsMutableObject
public final CSSExpression getExpression ()
{
return m_aExpression;
}

@NonNull
public final String getExpressionAsCSSString ()
{
return m_aExpression.getAsCSSString ();
}

@NonNull
public final CSSPropertyRuleDeclaration setExpression (@NonNull final CSSExpression aExpression)
{
m_aExpression = ValueEnforcer.notNull (aExpression, "Expression");
return this;
}

@NonNull
@Nonempty
public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel)
{
return m_sDescriptor +
CCSS.SEPARATOR_PROPERTY_VALUE +
m_aExpression.getAsCSSString (aSettings, nIndentLevel);
}

@Nullable
public final CSSSourceLocation getSourceLocation ()
{
return m_aSourceLocation;
}

public final void setSourceLocation (@Nullable final CSSSourceLocation aSourceLocation)
{
m_aSourceLocation = aSourceLocation;
}

@Override
public boolean equals (final Object o)
{
if (o == this)
return true;
if (o == null || !getClass ().equals (o.getClass ()))
return false;
final CSSPropertyRuleDeclaration rhs = (CSSPropertyRuleDeclaration) o;
return m_sDescriptor.equals (rhs.m_sDescriptor) && m_aExpression.equals (rhs.m_aExpression);
}

@Override
public int hashCode ()
{
return new HashCodeGenerator (this).append (m_sDescriptor).append (m_aExpression).getHashCode ();
}

@Override
public String toString ()
{
return new ToStringGenerator (this).append ("descriptor", m_sDescriptor)
.append ("expression", m_aExpression)
.appendIfNotNull ("SourceLocation", m_aSourceLocation)
.getToString ();
}
}
Loading
Loading