I have a css array that I want to merge
I have an HTML class that I use to create templates. My class works like this:
<?php
$page = \TEST\HTML::dispense(':html');
$page->mainWrapper(':div') //creates a child object by using __call() and sets the "div" model
->id('mainWrapper') //sets id开发者_运维问答
->style('background','red') //adds a style
->text('blah') //adds a text
->addClass('someClass'); //adds a class
->someSpan(':span')
->addClass('spanClass')->addClass('someClass')
->style('font-size','12pt')
->style('border-bottom','1pt dashed black')
->style('background','red');
?>
This allows me for rapid development of html markup without worrying about about a missing character or a misquoted property. Everything gets cached and I have no performance issues.
Now I'd like to take this one step further. In production mode, everything works fine, but for the final output, I'd like to strip out all the inline "style" properties and minimize them and cache them in a css file. Now, I have a function that loops through all my HTML objects, and aggregates the data according to tag, id, and classes. My question is: once I have my neat css array in that form:
$style['tag']['.class']['#id']['styleKey'] = styleValue
How do I trim out redundant values so I am left with a relevant css file? Minifying and gzipping can come at a later stage. What I want now is to compare values and optimize the array before dumping it so all 'styleKeys' common to all elements that have the same tag/id/class are grouped together.
So in the example above, for example, since two elements (the div and the span) share the style "background: red" and the class "someClass", I would have a "someClass" CSS rule with "background:red"
If it is of any interest, here is my "extractstyles" function:
<?php
public static function extractStyles($element, array &$styles=array()){
if($element instanceof \TEST\HTML){$element = $element->htmlData();}
$tag = isset($element['#acronym']) ? $element['#acronym'] : NULL;
$id = isset($element['#id']) ? '#'.$element['#id'] : NULL;
$classes = isset($element['#class']) ? $element['#class'] : NULL;
if(isset($element['#style']) && ($tag || $id || $class)){
$ref = &$styles;
if($id){if(!isset($ref[$id])){$ref[$id] = array();};$ref = &$ref[$id];}
if($classes){
if(\is_array($classes)){$classes = '.'.implode('.',$classes);}
if(!isset($ref[$classes])){$ref[$classes] = array();};$ref = &$ref[$classes];
}
if($tag){if(!isset($ref[$tag])){$ref[$tag] = array();};$ref = &$ref[$tag];}
foreach($element[self::ATTRIBUTES]['#style'] as $style=>$value){
$ref[$style] = $value;
}
}
if(isset($element[self::CHILDREN]) && count($element[self::CHILDREN])){
foreach($element[self::CHILDREN] as $child){
self::extractStyles($child, $styles);
}
}
return $styles;
}
?>
Any pointer would be more than welcome...I am really lost. I don't even know if what I am looking for is doable.
As said above, performance is not an issue for now. If it works, I will find a way to optimize it. Also, please no links to xCSS and other frameworks, as they work on strings and my CSS is created as an array. Thanks in advance for any help you can give me!
A first order optimization is to Build a hierarchy tree. A parent to child relationship in this tree is a child is a superset of the parent. The root node of this tree is an empty style (which you won't display).
Thus if you had
.parent {
background: red;
}
.childA {
background: red;
border: 1px solid black;
}
.childB {
background: red;
font-weight: 800;
}
The parent is set as the lowest common denominator in the tree. This can then be compressed into 3 classes with less text. The children elements will have all the classes in the path, If you originally had <span class="childA">
you would then get <span class="parent childA">
The compressed classes look like:
.parent {
background: red;
}
.childA {
border: 1px solid black;
}
.childB {
font-weight: 800;
}
A note on IDs, IDs will always be a child of the most appropriate class. Thus if you had
#menu {
background: red;
border: 1px solid black;
margin: 15px 40px;
color: white;
}
It would become the child of ChildA, and its css would be reduced to
#menu {
margin: 15px 40px;
color: white;
}
And displayed as <ul id="menu" class="parent childA">
To create the tree, you will need an object that will store an array of the same children objects (recursively) And a function that when given two objects can determine if their css is a subset, equal or superset, the number of differences, or if there is no commonality.
If you are not familiar with binary search trees, this would be a good time to bone up on that, even though this will be more complex than that, it will be a good start in the right direction.
A second order optimization is determining if nesting of child elements can further reduce the need of classes. For example if all your <li>
inside <ul id="#menu">
were styled similarly it would make sense that you could create a rule for #menu li
To do this, you need to go to each node, and analyze its children. If all the children of the same node type share a common style element (use the set comparer above), extract the common set element as a parent. The differences become the children.
Lets say you have this as an example (note is has already gone through pass 1):
<ul id="menu" class="parent childA">
<li class="top menuli">Item</li>
<li class="menuli">Item</li>
<li class="menuli">Item</li>
<li class="menuli">Item</li>
<li class="bottom menuli">Item</li>
</ul>
We note that all the <li>
have a common element .menuli, this means we can eliminate this class that was created in pass 1 and replace it with a flat rule of #menu li
. We do this by removing the menuli class from each child li, and replacing the .menuli
rule with the #menu li
rule.
Our css changes like from:
#menu {
margin: 15px 40px;
color: white;
}
.menuli {
font-size: 30px;
font-weight: 800;
margin: 8px 0;
}
to
#menu {
margin: 15px 40px;
color: white;
}
#menu li {
font-size: 30px;
font-weight: 800;
margin: 8px 0;
}
And the html looses the class menuli
<ul id="menu" class="parent childA">
<li class="top">Item</li>
<li>Item</li>
<li>Item</li>
<li>Item</li>
<li class="bottom">Item</li>
</ul>
Remember to use a breadth first search when searching down your node tree instead of depth first search. If you are aggressive, you can keep checking 2nd levels for similar tags across many paths, a common 2nd level search might reveal similar classes for #menu li a
or #container div p
etc. This becomes an NP hard problem if you allow unlimited depth searching.
Hope this helps. If this is the direction you want to go, I'd be happy to help with more code concerning the set comparator and possibly the tree searcher, although that is significantly more complex.
精彩评论