- Features
- Installation
- Usage
- API Reference
- Contributing
- Testing
- License
- Support
- Donate
- Acknowledgements
- Comprehensive QSS Validation: Detects syntax errors including missing semicolons, unclosed braces, properties outside blocks, invalid selectors, and unsupported pseudo-states or pseudo-elements.
- Variable Support: Parses
@variables
blocks, resolves variable references (e.g.,var(--primary-color)
), and handles nested variables with circular reference detection. - Structured Parsing: Converts QSS into
QSSRule
andQSSProperty
objects for easy programmatic manipulation of selectors and properties. - Style Extraction: Retrieves styles for Qt widgets based on object names, class names, attribute selectors (e.g.,
[data-value="complex string"]
), pseudo-states (e.g.,:hover
), and pseudo-elements (e.g.,::handle
). - Advanced Selector Handling: Supports complex selectors, including attribute selectors with spaces or special characters, composite selectors (e.g.,
QPushButton#myButton
), and duplicate selector filtering with error reporting. - Pseudo-State and Pseudo-Element Validation: Ensures pseudo-states (e.g.,
:hover
,:focus
) and pseudo-elements (e.g.,::tab
,::indicator
) conform to Qt's supported list, with detailed error messages. - Lightweight and Dependency-Free: Requires no external dependencies, ensuring seamless integration into any Python project.
- Plugin-Based Architecture: Extensible design with plugins for selectors, properties, and variables, enabling custom parsing logic.
- Robust Testing: Includes a comprehensive test suite covering validation, parsing, style extraction, variable resolution, and edge cases like duplicate selectors.
Install qss-parser
using pip
:
pip install qss-parser
- Python 3.6 or higher
- No external dependencies for core functionality.
- For Qt integration, install
PyQt5
,PyQt6
,PySide2
, orPySide6
(not included in package dependencies).
To install with Qt support (e.g., PyQt5):
pip install qss-parser PyQt5
The qss-parser
library offers an intuitive API for validating, parsing, and applying QSS styles. Below are examples showcasing its capabilities, focusing on the parse()
, on()
, to_string()
, and get_styles_for()
methods.
Explore a complete example in the examples directory.
Parse a QSS string, extract styles for a widget, and convert parsed rules to a formatted string.
from qss_parser import QSSParser, ParserEvent
from unittest.mock import Mock
# Mock widget setup
widget = Mock()
widget.objectName.return_value = "myButton"
widget.metaObject.return_value.className.return_value = "QPushButton"
# Initialize parser
parser = QSSParser()
# Sample QSS
qss = """
#myButton {
color: red;
}
QPushButton {
background: blue;
}
QPushButton:hover {
background: yellow;
}
"""
# Parse QSS
parser.parse(qss)
# Get styles for widget
styles = parser.get_styles_for(widget, include_class_if_object_name=True)
print("Styles for widget:")
print(styles)
# Convert parsed rules to string
formatted_qss = parser.to_string()
print("\nFormatted QSS:")
print(formatted_qss)
Output:
Styles for widget:
#myButton {
color: red;
}
QPushButton {
background: blue;
}
QPushButton:hover {
background: yellow;
}
Formatted QSS:
#myButton {
color: red;
}
QPushButton {
background: blue;
}
QPushButton:hover {
background: yellow;
}
Use the on()
method to subscribe to parser events (rule_added
, error_found
, variable_defined
, parse_completed
).
from qss_parser import QSSParser, ParserEvent
from unittest.mock import Mock
# Mock widget
widget = Mock()
widget.objectName.return_value = "myButton"
widget.metaObject.return_value.className.return_value = "QPushButton"
# Initialize parser
parser = QSSParser()
# Define event handlers
def on_rule_added(rule):
print(f"Rule added: {rule.selector}")
def on_error_found(error):
print(f"Error: {error}")
def on_variable_defined(name, value):
print(f"Variable defined: {name} = {value}")
def on_parse_completed():
print("Parsing completed")
# Register event handlers
parser.on(ParserEvent.RULE_ADDED, on_rule_added)
parser.on(ParserEvent.ERROR_FOUND, on_error_found)
parser.on(ParserEvent.VARIABLE_DEFINED, on_variable_defined)
parser.on(ParserEvent.PARSE_COMPLETED, on_parse_completed)
# Sample QSS with variables and an error
qss = """
@variables {
--primary-color: blue;
}
#myButton {
color: var(--primary-color);
}
QPushButton {
background: green
}
"""
# Parse QSS
parser.parse(qss)
# Get styles for widget
styles = parser.get_styles_for(widget)
print("\nStyles for widget:")
print(styles)
Output:
Variable defined: --primary-color = blue
Rule added: #myButton
Rule added: QPushButton
Error: Error on line 8: Property missing ';': background: green
Parsing completed
Styles for widget:
#myButton {
color: blue;
}
Use parses.on with error event to validate QSS syntax and report errors.
from qss_parser import QSSParser, ParserEvent
# Initialize parser
parser = QSSParser()
# Register error handler
def on_error_found(error):
print(f"Error: {error}")
parser.on(ParserEvent.ERROR_FOUND, on_error_found)
# Sample QSS with syntax error
qss = """
QPushButton {
color: blue
}
QFrame {
font-size: 12px;
color: blue
}
QFrame {
font-size: 12px
color: blue
}
"""
# Validate QSS
parser.parse(qss)
# Convert parsed rules to string (only valid rules)
formatted_qss = parser.to_string()
print("\nFormatted QSS (valid rules):")
print(formatted_qss)
Output:
Error: Error on line 10: Property missing ';': font-size: 12px
Parsing completed
Formatted QSS (valid rules):
QPushButton {
color: blue;
}
QFrame {
font-size: 12px;
color: blue;
}
Parse QSS with complex attribute selectors, pseudo-states, and pseudo-elements, and extract styles for a widget.
from qss_parser import QSSParser, ParserEvent
from unittest.mock import Mock
# Mock widget
widget = Mock()
widget.objectName.return_value = "myButton"
widget.metaObject.return_value.className.return_value = "QPushButton"
# Initialize parser
parser = QSSParser()
# Register parse completion handler
def on_parse_completed():
print("Parsing completed")
parser.on(ParserEvent.PARSE_COMPLETED, on_parse_completed)
# Sample QSS
qss = """
QPushButton[data-value="complex string"] {
color: blue;
}
QPushButton:hover {
background: yellow;
}
QPushButton::indicator {
border: 2px solid gray;
}
#myButton QPushButton {
font-weight: bold;
}
"""
# Parse QSS
parser.parse(qss)
# Get styles with additional selectors
styles = parser.get_styles_for(
widget,
additional_selectors=["QPushButton"],
include_class_if_object_name=True
)
print("Styles for widget:")
print(styles)
# Convert parsed rules to string
formatted_qss = parser.to_string()
print("\nFormatted QSS:")
print(formatted_qss)
Output:
Parsing completed
Styles for widget:
#myButton QPushButton {
font-weight: bold;
}
QPushButton[data-value="complex string"] {
color: blue;
}
QPushButton:hover {
background: yellow;
}
QPushButton::indicator {
border: 2px solid gray;
}
Formatted QSS:
QPushButton[data-value="complex string"] {
color: blue;
}
QPushButton:hover {
background: yellow;
}
QPushButton::indicator {
border: 2px solid gray;
}
#myButton QPushButton {
font-weight: bold;
}
Parse QSS with a @variables
block, resolve nested variables, and extract styles.
from qss_parser import QSSParser, ParserEvent
from unittest.mock import Mock
# Mock widget
widget = Mock()
widget.objectName.return_value = "myButton"
widget.metaObject.return_value.className.return_value = "QPushButton"
# Initialize parser
parser = QSSParser()
# Register variable defined handler
def on_variable_defined(name, value):
print(f"Variable defined: {name} = {value}")
parser.on(ParserEvent.VARIABLE_DEFINED, on_variable_defined)
# Sample QSS
qss = """
@variables {
--base-color: #0000ff;
--primary-color: var(--base-color);
--font-size: 14px;
}
#myButton {
color: var(--primary-color);
font-size: var(--font-size);
background: white;
}
QPushButton:hover {
background: yellow;
}
"""
# Parse QSS
parser.parse(qss)
# Get styles for widget
styles = parser.get_styles_for(widget, include_class_if_object_name=True)
print("\nStyles for widget:")
print(styles)
# Convert parsed rules to string
formatted_qss = parser.to_string()
print("\nFormatted QSS:")
print(formatted_qss)
Output:
Variable defined: --base-color = #0000ff
Variable defined: --primary-color = #0000ff
Variable defined: --font-size = 14px
Styles for widget:
#myButton {
color: #0000ff;
font-size: 14px;
background: white;
}
QPushButton:hover {
background: yellow;
}
Formatted QSS:
#myButton {
color: #0000ff;
font-size: 14px;
background: white;
}
QPushButton:hover {
background: yellow;
}
Apply parsed QSS styles to a PyQt5 widget, using event handlers for debugging.
from PyQt5.QtWidgets import QApplication, QPushButton
from qss_parser import QSSParser, ParserEvent
import sys
# Initialize Qt application
app = QApplication(sys.argv)
# Initialize parser
parser = QSSParser()
# Register event handlers
def on_error_found(error):
print(f"Error: {error}")
def on_parse_completed():
print("Parsing completed")
parser.on(ParserEvent.ERROR_FOUND, on_error_found)
parser.on(ParserEvent.PARSE_COMPLETED, on_parse_completed)
# Load QSS from file
with open("styles.qss", "r", encoding="utf-8") as f:
qss = f.read()
# Parse QSS
parser.parse(qss)
# Create button
button = QPushButton("Click Me")
button.setObjectName("myButton")
# Apply styles
styles = parser.get_styles_for(button, include_class_if_object_name=True)
button.setStyleSheet(styles)
# Print formatted QSS
formatted_qss = parser.to_string()
print("Formatted QSS:")
print(formatted_qss)
# Show button
button.show()
# Run application
sys.exit(app.exec_())
Example styles.qss
:
QPushButton {
background: blue;
}
#myButton {
color: red;
}
QPushButton:hover {
background: yellow;
}
Output:
Parsing completed
Formatted QSS:
QPushButton {
background: blue;
}
#myButton {
color: red;
}
QPushButton:hover {
background: yellow;
}
Main class for parsing and managing QSS.
- Methods:
parse(qss_text: str) -> None
: Parses QSS intoQSSRule
objects.get_styles_for(widget: WidgetProtocol, fallback_class: Optional[str] = None, additional_selectors: Optional[List[str]] = None, include_class_if_object_name: bool = False) -> str
: Retrieves QSS styles for a widget.on(event: ParserEvent, handler: Callable[..., None]) -> None
: Registers handlers for events (rule_added
,error_found
,variable_defined
,parse_completed
).to_string() -> str
: Returns formatted QSS for all parsed rules.
Represents a QSS rule with selector and properties.
-
Attributes:
selector: str
: Selector (e.g.,#myButton
,QPushButton[data-value="value"]
).properties: List[QSSProperty]
: List of properties.object_name: Optional[str]
: Object name (e.g.,myButton
for#myButton
).class_name: Optional[str]
: Class name (e.g.,QPushButton
).attributes: List[str]
: Attribute selectors (e.g.,[data-value="value"]
).pseudo_states: List[str]
: Pseudo-states (e.g.,:hover
).
-
Methods:
add_property(name: str, value: str) -> None
: Adds a property.clone_without_pseudo_elements() -> QSSRule
: Returns a copy without pseudo-elements.
Represents a QSS property.
-
Attributes:
name: str
: Property name (e.g.,color
).value: str
: Property value (e.g.,blue
).
-
Methods:
to_dict() -> QSSPropertyDict
: Converts to dictionary.
Utility for selector parsing and validation.
- Static Methods:
is_complete_rule(line: str) -> bool
: Checks if a line is a complete rule.extract_attributes(selector: str) -> List[str]
: Extracts attribute selectors.normalize_selector(selector: str) -> str
: Normalizes selector formatting.parse_selector(selector: str) -> Tuple[Optional[str], Optional[str], List[str], List[str]]
: Parses selector components.validate_selector_syntax(selector: str, line_num: int) -> List[str]
: Validates selector syntax.
Manages QSS variables.
- Methods:
parse_variables(block: str, start_line: int = 1, on_variable_defined: Optional[Callable[[str, str], None]] = None) -> List[str]
: Parses variable block.resolve_variable(value: str) -> Tuple[str, Optional[str]]
: Resolves variable references.
Selects styles for widgets.
- Methods:
get_styles_for(rules: List[QSSRule], widget: WidgetProtocol, ...) -> str
: Retrieves styles for a widget.
QSSParserPlugin
: Abstract base class for plugins.SelectorPlugin
: Handles selectors and rules, including duplicate selector filtering.PropertyPlugin
: Processes property declarations.VariablePlugin
: Manages@variables
blocks.
Contributions are welcome! To contribute:
- Fork the repository.
- Create a branch (
git checkout -b feature/my-feature
). - Implement changes, adhering to the coding style.
- Run tests (
python -m unittest discover tests
). - Submit a pull request with a clear description.
See CONTRIBUTING.md for details.
- Adhere to PEP 8.
- Use type hints per PEP 484.
- Provide clear, concise docstrings for public methods and classes.
The test suite in tests/
covers validation, parsing, style extraction, and variable resolution. Run tests with:
python -m unittest discover tests
For multi-version testing, use tox
:
pip install tox
tox
Ensure all tests pass before submitting pull requests.
Licensed under the MIT License. See LICENSE for details.
For issues or questions:
π Support QSS Parser with a Donation! π
If you find QSS Parser helpful and want to support its development, consider making a donation via Web3 wallets. Your contributions help maintain and improve this open-source project! π
How to Donate:
- Copy the wallet address for your preferred blockchain.
- Send your donation using a Web3 wallet (e.g., MetaMask, Trust Wallet).
- Include a note with your GitHub username (optional) for a shoutout in our Acknowledgements!
Thank you for supporting QSS Parser! π
- Gratitude to the Qt community for QSS documentation.
- Inspired by the need for programmatic QSS handling in PyQt/PySide.
- Thanks to contributors and users for feedback and enhancements.