Skip to content

Commit a1b7644

Browse files
authored
Update and rename css2php.php to css2php.py
1 parent 5881436 commit a1b7644

1 file changed

Lines changed: 88 additions & 45 deletions

File tree

css2php.php renamed to css2php.py

Lines changed: 88 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class CSS2PHPConverter:
8484
print(f"Warning: Failed to fetch {source}: {e}")
8585
return None
8686

87-
def _parse_css(self, css_content: str) -> tuple[dict, ProcessingStats]:
87+
def _parse_css(self, css_content: str, split_selectors: bool = False) -> tuple[dict, ProcessingStats]: # split_selectors parameter added
8888
"""Parse CSS content and return class map with statistics"""
8989
start_time = time.time()
9090

@@ -96,8 +96,8 @@ class_map = defaultdict(lambda: {'default': {}, 'media': {}})
9696
output_size=0,
9797
parsing_time=0,
9898
class_count=0,
99-
media_queries=0,
100-
pseudo_classes=0,
99+
media_queries= 0,
100+
pseudo_classes= 0,
101101
file_path="",
102102
syntax_valid=False # Initialized as False, will be updated after validation
103103
)
@@ -107,27 +107,54 @@ class_count=0,
107107
# Handle normal style rules
108108
selectors = [s.strip() for s in rule.selectorText.split(',')]
109109
for selector in selectors:
110-
if selector.startswith('.'):
111-
class_name = selector.strip('.')
112-
if ':' in class_name:
113-
base_class, pseudo = class_name.split(':', 1)
114-
class_name = f"{base_class}:{pseudo}"
115-
stats.pseudo_classes += 1
116-
117-
properties = {
118-
prop.name: prop.value
119-
for prop in rule.style
120-
if prop.name and prop.value
121-
}
122-
123-
if media_query:
124-
if class_name not in class_map:
125-
class_map[class_name] = {'default': {}, 'media': {}} # Ensure class entry exists
126-
class_map[class_name]['media'][media_query] = properties
127-
stats.media_queries += 1
128-
else:
129-
class_map[class_name]['default'].update(properties)
130-
stats.class_count += 1
110+
if split_selectors: # Split selectors logic
111+
individual_selectors = [s.strip() for s in selector.split(' ')] # Split by space
112+
for individual_selector in individual_selectors:
113+
if individual_selector.startswith('.'):
114+
class_name = individual_selector.strip('.')
115+
if ':' in class_name:
116+
base_class, pseudo = class_name.split(':', 1)
117+
class_name = f"{base_class}:{pseudo}"
118+
stats.pseudo_classes += 1
119+
120+
properties = {
121+
prop.name: prop.value
122+
for prop in rule.style
123+
if prop.name and prop.value
124+
}
125+
126+
if media_query:
127+
if class_name not in class_map:
128+
class_map[class_name] = {'default': {}, 'media': {}} # Ensure class entry exists
129+
class_map[class_name]['media'][media_query] = properties
130+
stats.media_queries += 1
131+
else:
132+
class_map[class_name]['default'].update(properties)
133+
stats.class_count += 1
134+
135+
else: # Original logic - keep combined selectors
136+
if selector.startswith('.'):
137+
class_name = selector.strip('.')
138+
if ':' in class_name:
139+
base_class, pseudo = class_name.split(':', 1)
140+
class_name = f"{base_class}:{pseudo}"
141+
stats.pseudo_classes += 1
142+
143+
properties = {
144+
prop.name: prop.value
145+
for prop in rule.style
146+
if prop.name and prop.value
147+
}
148+
149+
if media_query:
150+
if class_name not in class_map:
151+
class_map[class_name] = {'default': {}, 'media': {}} # Ensure class entry exists
152+
class_map[class_name]['media'][media_query] = properties
153+
stats.media_queries += 1
154+
else:
155+
class_map[class_name]['default'].update(properties)
156+
stats.class_count += 1
157+
131158

132159
elif isinstance(rule, cssutils.css.CSSMediaRule):
133160
media_query_text = rule.media.mediaText
@@ -141,7 +168,7 @@ class_map[class_name]['default'].update(properties)
141168
stats.parsing_time = time.time()
142169
return dict(class_map), stats
143170

144-
def _generate_php(self, class_map: dict, source: str, stats: ProcessingStats) -> str:
171+
def _generate_php(self, class_map: dict, source: str, stats: ProcessingStats, split_selectors: bool = False) -> str:
145172
"""Generate PHP array code from class map with comprehensive header"""
146173
# Calculate additional stats
147174
compression_ratio = (stats.output_size / stats.input_size * 100) if stats.input_size > 0 else 0
@@ -177,13 +204,16 @@ class_map[class_name]['default'].update(properties)
177204
* Skip errors: {'Yes' if self.skip_errors else 'No'}
178205
* Timeout: {self.timeout}s
179206
* Overwrite enabled: {'Yes' if self.overwrite else 'No'}
207+
* Split Selectors: {'Yes' if split_selectors else 'No'}
180208
*
181209
* File Structure:
182210
* ------------------------------------------
183-
* - Array keys are CSS class names
184-
* - Each class has 'default' and optional 'media' properties
185-
* - Media queries are nested under 'media' key
186-
* - All properties are sorted alphabetically
211+
* - Array keys are CSS class selectors.
212+
* - {'Combined selectors are kept as single keys' if not split_selectors else 'Combined selectors are split into individual keys.'}
213+
* - Each key represents a CSS selector and its associated styles.
214+
* - Each class selector has 'default' and optional 'media' properties.
215+
* - Media queries are nested under 'media' key.
216+
* - All properties are sorted alphabetically.
187217
* ==========================================
188218
*/
189219
@@ -198,7 +228,7 @@ class_map[class_name]['default'].update(properties)
198228
clean_class = re.sub(r'\\([\.:/\-])', r'\1', clean_class)
199229
# Only escape single quotes for PHP
200230
escaped_class = clean_class.replace("'", "\\'")
201-
231+
202232
php_code += f" '{escaped_class}' => [\n"
203233

204234
if rules['default']:
@@ -348,7 +378,7 @@ class_map[class_name]['default'].update(properties)
348378
]
349379
return "\n".join(report)
350380

351-
def convert(self, source: str, output_path: str = None, name_prefix: str = '') -> Optional[ProcessingStats]:
381+
def convert(self, source: str, output_path: str = None, name_prefix: str = '', split_selectors: bool = False) -> Optional[ProcessingStats]: # split_selectors parameter added
352382
"""Convert CSS source to PHP array"""
353383
try:
354384
start_time = time.time()
@@ -372,8 +402,8 @@ class_map[class_name]['default'].update(properties)
372402
output_path.parent.mkdir(parents=True, exist_ok=True)
373403

374404
# Process CSS and generate PHP
375-
class_map, stats = self._parse_css(css_content)
376-
php_code = self._generate_php(class_map, source, stats)
405+
class_map, stats = self._parse_css(css_content, split_selectors=split_selectors) # Pass split_selectors to _parse_css
406+
php_code = self._generate_php(class_map, source, stats, split_selectors=split_selectors)
377407

378408
# Validate and try to fix PHP syntax
379409
is_valid, error_msg, fixed_code = self._validate_php_syntax(php_code)
@@ -468,7 +498,7 @@ class_map, stats = self._parse_css(css_content)
468498
print(f"📊 Summary:")
469499
print(f" • Files processed: {len(processed_files)}")
470500
print(f" • Unique classes: {len(merged_arrays)}")
471-
print(f" • Duplicates skipped: {len(duplicates)}") # Still report duplicates (though now merged)
501+
print(f" • Duplicates found: {len(duplicates)}") # Still report duplicates (though now merged)
472502
print(f" • Output file: {output_file}")
473503
print(f" • PHP Syntax: {'✅ Valid' if is_valid else '❌ Invalid'}")
474504

@@ -499,6 +529,16 @@ class_map, stats = self._parse_css(css_content)
499529
* Duplicate Information (Properties Merged):
500530
* ------------------------------------------
501531
{format_duplicate_info(duplicates)}
532+
* ==========================================
533+
*
534+
* File Structure:
535+
* ------------------------------------------
536+
* - Array keys are CSS class selectors.
537+
* - Combined selectors are kept as single keys.
538+
* - Each key represents a CSS selector and its associated styles.
539+
* - Each class selector has 'default' and optional 'media' properties.
540+
* - Media queries are nested under 'media' key.
541+
* - All properties are sorted alphabetically.
502542
* ==========================================
503543
*/
504544
@@ -608,32 +648,32 @@ class_map, stats = self._parse_css(css_content)
608648
lines = content.split('\n')
609649
indent_level = 0
610650
formatted_lines = []
611-
651+
612652
for line in lines:
613653
line = line.strip()
614654
if not line:
615655
continue
616-
656+
617657
# Adjust indentation based on brackets
618658
indent_level += line.count('[') - line.count(']')
619659
spaces = ' ' * indent_level
620660
formatted_lines.append(spaces + line)
621-
661+
622662
if ']' in line:
623663
indent_level = max(0, indent_level - line.count(']'))
624-
664+
625665
return '\n'.join(formatted_lines)
626666

627667
def format_duplicate_info(duplicates: Dict[str, List[str]]) -> str:
628668
"""Format duplicate information for header comment"""
629669
if not duplicates:
630670
return " * No duplicates found"
631-
671+
632672
lines = []
633673
for key, files in sorted(duplicates.items()):
634674
files_str = ', '.join(files)
635675
lines.append(f" * '{key}' found in: {files_str}")
636-
676+
637677
return '\n'.join(lines)
638678

639679
def main():
@@ -655,7 +695,7 @@ class_map, stats = self._parse_css(css_content)
655695
""",
656696
formatter_class=argparse.RawDescriptionHelpFormatter
657697
)
658-
698+
659699
# Keep existing arguments
660700
parser.add_argument('sources', nargs='*', help="CSS file paths or URLs")
661701
parser.add_argument('-o', '--output', help="Output directory path")
@@ -671,6 +711,7 @@ class_map, stats = self._parse_css(css_content)
671711
parser.add_argument('-mn', '--merge-name', default='main', help="Name prefix for merged file (default: main)")
672712
parser.add_argument('-v', '--version', action='version', version=f'%(prog)s {__version__}\nCreated by {__author__}\n{__repo__}')
673713
parser.add_argument('-i', '--info', action='store_true', help="Show detailed info")
714+
parser.add_argument('--split-selectors', action='store_true', help="Split combined CSS selectors into individual keys in PHP array (may lose specificity)") # Added split-selectors argument
674715
args = parser.parse_args()
675716

676717
# Handle merge operation if requested
@@ -687,7 +728,7 @@ class_map, stats = self._parse_css(css_content)
687728
if not args.sources:
688729
parser.print_help()
689730
return
690-
731+
691732
converter = CSS2PHPConverter(
692733
timeout=args.timeout,
693734
skip_errors=args.skip_errors,
@@ -703,7 +744,7 @@ class_map, stats = self._parse_css(css_content)
703744
for source in args.sources:
704745
print(f"\nProcessing: {source}")
705746
try:
706-
stats = converter.convert(source, args.output, args.name_prefix)
747+
stats = converter.convert(source, args.output, args.name_prefix, split_selectors=args.split_selectors) # Pass split_selectors
707748
if stats:
708749
results.append(stats)
709750
if stats.syntax_valid:
@@ -724,7 +765,8 @@ class_map, stats = self._parse_css(css_content)
724765
'Processing time': f"{stats.parsing_time:.3f}s",
725766
'Pseudo-classes': stats.pseudo_classes,
726767
'Syntax validation': '✅ Valid' if stats.syntax_valid else '❌ Invalid',
727-
'Syntax error details': stats.syntax_error_message # Show error details in info
768+
'Syntax error details': stats.syntax_error_message, # Show error details in info
769+
'Split Selectors': 'Yes' if args.split_selectors else 'No' # Show Split Selectors info in stats
728770
}
729771
for key, value in sorted(info.items()):
730772
print(f" • {key}: {value}")
@@ -754,7 +796,8 @@ class_map, stats = self._parse_css(css_content)
754796
'Total media queries': sum(s.media_queries for s in results),
755797
'Total output size': f"{total_output:,} bytes",
756798
'Total processing time': f"{total_time:.2f}s",
757-
'Total syntax errors': sum(1 for s in results if not s.syntax_valid) # Count total syntax errors
799+
'Total syntax errors': sum(1 for s in results if not s.syntax_valid), # Count total syntax errors
800+
'Split Selectors': 'Yes' if args.split_selectors else 'No' # Show Split Selectors info in summary
758801
}
759802

760803
for key, value in sorted(summary.items()):

0 commit comments

Comments
 (0)