I have a css array that I want to merge - php

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.

Related

CSS: Dynamic classes / attributes and media queries - how to solve this dilemma?

Writing an extension for a Plugin I have the possibility to change all attributes of an HTML element using PHP.
$attributes["style"] .= 'padding-left:10px;';
array_push($attributes["class"], "long-container");
array_push($attributes["class"], "super smooth");
$attributes["data-whatever"] = "great";
Now I want to give a user the possibility to enter the width / height ratio of a div dynamically (the solution of how to do this is described in the answer by #Web_Designer here: Maintain the aspect ratio of a div with CSS).
Within the function where I can change the output of the third-party Plugin I wrote the following code for calculating the width height ratio according to the input. As the height of my boxes is :
if( !empty( $args['stretchy-desktop'] ) ) {
$sd = array_map('trim',explode(":",$args['stretchy-desktop']));
if(count($sd)==2) {
$sd[0] = floatval(str_replace(",",".",$sd[0]));
$sd[1] = floatval(str_replace(",",".",$sd[1]));
if($sd[0]>0 && $sd[1]>0) {
$padding = ($sd[1] / $sd[0])*100;
array_push($attributes['class'], 'stretchy-desktop');
$attributes['style'] .= 'padding-bottom:'.$padding.'%;';
}
}
}
Great right? However now the user wants a possibility to enter a different weight height ratio for mobile devices as well as a different dynamic min-height for mobile devices and this is there I am stuck.
1) It is not possible to give inline #media queries right now otherwise my solution would be like this (Is it possible to put CSS #media rules inline?):
$attributes['style'] .= '#media (min-width:540px) {padding-bottom:'.$padding.'%;}#media (max-width:539px) {padding-bottom:'.$padding_mobile.';}';
2) It is not possible to use HTML attribute values in CSS right now (CSS values using HTML5 data attribute) otherwise my solution would be like this:
$attributes['data-desktoppadding'] = $padding;
$attributes['data-mobilepadding'] = $padding_mobile;
In CSS:
#media (min-width:540px) {
.long-container {
padding-bottom: attr(data-desktoppadding);
}
}
#media (max-width:539px) {
.long-container {
padding-bottom: attr(data-mobilepadding);
}
}
3) As the values are dynamic numbers I can not define a CSS class for every possible existing float.
Of course I could use JavaScript but we all know the significant drawbacks (including ugly page load).
Can you think of any CSS solution for this dilemma?
Here is a solution I came up with. It involves creating a wrapper div around the target element. Basically, the way that this works is that the outer div is assigned the inline styles for the mobile mode, and the inner div is assigned the inline styles for desktop mode. When the browser window is resized to be below the threshold for desktop view, it resets the inner div's (desktop) inline styles to defaults so the outer div's (mobile) inline styles take over. When the browser window is resized to be above the threshold, it resets the outer div's (mobile) inline styles to defaults so the inner div's (desktop) inline styles take over. The way that it overrides the inline styles is by using the !important keyword in the rulesets in the CSS media queries.
I think it goes without saying that the inline styles in the snippet below would be replaced with your $attributes['style']. But since you will have separate mobile and desktop styles, I guess it would be $attributes['style-mobile'] and $attributes['style-desktop'].
#media (min-width:540px) {
.padding-mobile {
padding-bottom:0 !important;
width: auto !important;
}
}
#media (max-width:539px) {
.padding-desktop {
padding-bottom:0 !important;
width: auto !important;
}
}
<div class="padding-mobile" style="width:100%;background-color:red;padding-bottom:100%;">
<div class="padding-desktop" style="width:50%;background-color:red;padding-bottom:25%;">
</div>
</div>
An elegant approach that works in most major browsers is the usage of custom properties. They are basically variables in CSS. As of writing this (2017-03-27), only IE and Edge do not support this, although they are working on support for Edge.
You would add the variables to the $attributes['style'] and actually apply them in the stylesheet inside a media query. They are then used dynamically by the browser.
I have implemented a demo as a snippet, but because it is easier to change the viewport size on JSFiddle, also a copy of the demo there. Note that the responsive breakpoint is defined in CSS here, and the variables are defined in inline styles of the element.
.container {
width: 200px;
}
.block {
position: relative;
display: block;
box-sizing: border-box;
width: 100%;
height: 0;
padding: 10px;
padding-bottom: var(--desktop-ratio);
background: #bada55;
color: #444;
}
#media (min-width: 540px) {
.mobile {
display: none;
}
}
#media (max-width: 539px) {
.desktop {
display: none;
}
.block {
padding-bottom: var(--mobile-ratio);
}
}
<div class="container">
<div class="block" style="--desktop-ratio: 56.25%; --mobile-ratio: 200%;";>
My aspect ratio is set via custom properties. It is <span class="mobile">1:2</span><span class="desktop">16:9</span>.
</div>
</div>
It is (at least at the moment) apparently not possible to set a breakpoint using a variable. That is,
#media (min-width: var(--breakpoint)) { ... }
is apparently not understood by at least Firefox and Chrome at the moment.
Alternatively, building on Kodos Johnson's answer: if you have only a single breakpoint, you can make use of the padding-bottom and padding-top. One of the two defines the aspect ratio on small screens, and the other defines it on big screens. This removes the need to add a wrapper element. Here is an example, based on the one from Kodos' answer.
#media (min-width:540px) {
.block {
padding-top: 0 !important;
}
}
#media (max-width:539px) {
.block {
padding-bottom: 0 !important;
}
}
<div class="block" style="width: 50%;
padding-bottom: 25%;
padding-top: 100%;
background-color: red;">
</div>

How to insert colors into rows and columns

I am working on a piece of code that displays a table that shows different drug details. I have a color coding system, red = danger, yellow = alert, green = safe.
However I am not happy with the colors that I am using. Is there any way of inserting brighter colors into this code below?
<?php $row_class = "";
while($row = mysql_fetch_assoc($dbResult1))
{
if($row['total_mai'] <= 2)
$row_class = "success";
else if($row['total_mai'] >= 5)
$row_class = "danger";
else if($row['total_mai'] >= 3 and $row['total_mai'] < 5)
$row_class = "warning";
// echo $row_class;
?>
The colors actually aren't in your code here. The colors are defined in classes in your css. Somewhere in the header of your file, you'll need to look for a reference to a css file:
<link rel="stylesheet" type="text/css" href="mystyle.css">
It could also be in the actual html page between <style> tags.
<head>
<style>
body {
background-color: linen;
}
h1 {
color: maroon;
margin-left: 40px;
}
</style>
</head>
Though there will be a lot there, you're probably looking for sections which look something like this:
.success {
color: green;
}
.danger {
color: red;
}
.warning {
color: yellow;
}
There could be many other attributes defined too, but the .word "selector" tells the browser to render colors on the text with these classes you're adding. To change the colors, change the attributes. You might not see color names liek above. You could see hex colors (something like #d0e4fe;). The W3Schools page on CSS is a good place to get started. They also have a nice color reference for color names:
There are other ways the CSS might reference the classes, so you might want to look at a tutorial on Selectors. For instance, they might use tr.danger { ... } to reference only danger classes which are applied to table rows.
If using Bootstrap
Then I'm not an expert, so I can only help so much, but Bootstrap pre-defines some of these styles for you. You could make new classes and use !important to override the classes with new colors. Important can get confusing since it's a "nevermind" to previous CSS, but you need it here if bootstrap is applying the background to cells individually. I just found some code on another answer here about doing this in Bootstrap:
.table tbody tr > td.success {
background-color: #dff0d8 !important;
}
.table tbody tr > td.error {
background-color: #f2dede !important;
}
.table tbody tr > td.warning {
background-color: #fcf8e3 !important;
}
.table tbody tr > td.info {
background-color: #d9edf7 !important;
}
.table-hover tbody tr:hover > td.success {
background-color: #d0e9c6 !important;
}
.table-hover tbody tr:hover > td.error {
background-color: #ebcccc !important;
}
.table-hover tbody tr:hover > td.warning {
background-color: #faf2cc !important;
}
.table-hover tbody tr:hover > td.info {
background-color: #c4e3f3 !important;
}
This would be in your own CSS file preferably. If you start modifying the Bootstrap css file, it could get frustrating if you need to upgrade Bootstrap to a new version. The "important" takes care of your settings being more, well... important.
You could also use the links I added above to learn how to make your own classes and then add your own classes instead of Bootstraps.

How to filter children node values from parent div where style = "..." using Goutte and Symfony DomCrawler?

I am trying to scrape the quotes from a given wikiquote page using the php package Goutte, which wraps the Symfony Components: BrowserKit, CssSelector and DomCrawler.
However there are certain quotes which I do not want in my result set, the quotes from the misattributed section.
Here is what I have so far:
use Goutte\Client;
$client = new Client();
$crawler = $client->request('GET', 'http://en.wikiquote.org/wiki/Thomas_Jefferson');
//grab all the children li's from the wikiquote page
$quotes = $crawler->filter('ul > li');
$quoteArray = [];
//foreach li with a node value that does not start with a number, push the node value onto quote array
//this filters out the table of contents <li> node values which I do not want
foreach($quotes as $quote)
{
if(!is_numeric(substr($quote->nodeValue, 0, 1)))
{
array_push($quoteArray, $quote->nodeValue);
}
}
The problem that I am focusing on at this point is how to filter out the quotes from the misattributed section. This section is contained in a parent div which has the style attribute:
style="padding: .5em; border: 1px solid black; background-color:#FFE7CC"
I was thinking that if I can somehow grab the li node values from this specific section I can then filter them out from my above $quoteArray. The issue I am having is that I cannot figure out how to select the children li node values from this section.
I have tried selecting the children with variations of the following:
$badQuotes = $crawler->filter('div[style="padding: .5em; border: 1px solid black; background-color:#FFE7CC"] > ul > li');
But this is not returning the node values that I need. Does anyone know how to do this or what I am doing wrong?
DomCrawler filter method will
Filters the list of nodes with a CSS selector.
which is less powerfull than using xpath. I guess CSS selector couldn't convert your complex query into xpath expressions. So, a complex filter should be done by filterXPath method instead which will
Filters the list of nodes with an XPath expression.
So, in your case, try use the filterXPath method:
$crawler->filterXPath("//div[contains(#style,'padding: .5em; border: 1px solid black; background-color:#FFE7CC')]");

loop through an anchor id

I apologize is any of this does not look right, it is my first time asking a question on this site.
I am creating a webpage using html, css, and php. Specifically, I am trying to create subnavigation links on my page using information from a database.
Here is the code I have:
foreach ($subArr as $sub => $result)
{
if (mysql_num_rows($result) > 0)
{
$resultString .= '<a id="$sub" style="cursor: poimter; color: #0076cf;" href="$sub">'.' | '.$sub.' | '.'</a>';
}
}
$subArr is an array of subcategories that I would like the user to be able to click on the link with the subcategory's name and it will take them to that part of the same page. As of right now, all it does is create one giant link under all of the subcategory names instead of creating each individual link.
Obviously I need some sort of loop, but I am not sure how to look through the $resultString to change both the anchor id and href.
Any help is much appreciated!!
(Of topic, but important)
You have a typo, it should be :
style="cursor: pointer; ..."
Instead of :
style="cursor: poimter; ..."
There is an error in your code.
You put variable in '' which php won't parse to get proper result you need to put variable in "".
foreach ($subArr as $sub => $result)
{
if (mysql_num_rows($result) > 0)
{
$resultString .= '<a id="'.$sub.'" style="cursor: pointer; color: #0076cf;" href="'.$sub.'"> | '.$sub.' | </a>';
}
}
Moreover it looks weird to have same ID as href is.
I've noticed you use mysql_* function which are depracated and will be removed in future. Consider using PDO or MySQLi instead.
foreach ($subArr as $sub => $result)
{
if (mysql_num_rows($result) > 0)
{
$resultString = '<a id="$sub" style="cursor: pointer; color: #0076cf;" href="$sub">'.' | '.$sub.' | '.'</a>';
}
$resultstring="";
}
you seem to be on the right track but have a few things mixed up.
Menu
Firstly when making a menu you want to use an unordered list, then style it with CSS. A basic example of this is:
<ul class="menu">
<li>Test</li>
<li>Test 2</li>
<li>Test 3</li>
</ul>
You then style it with the following CSS
ul.menu, ul.menu * {
list-style: none;
padding: 0;
margin: 0;
}
ul.menu {
width: 100%;
height: 20px;
background: #ccc;
padding: 5px 0; /* Add padding top and bottom */
}
ul.menu > li {
height: 20px;
line-height: 20px;
float: left;
}
/* Make a tag fill the entire LI so users can click
anywhere, not just on the text. */
ul.menu > li > a {
display: block;
padding: 0 10px; /* Add padding between items */
color: #000;
text-decoration: none;
}
ul.menu > li > a:hover, ul.menu > li > a:active {
background: #000;
color: #FFF;
}
/* Add divider between items, except last item (Does not work with earlier versions of IE) */
ul.menu > li:not(:last-child) {
border-right: 1px solid #000;
}
PHP Loop
Firstly a note. You're using mysql, which is depreciated. That means that in a version of PHP some time soon it is not going to be available any more. A lot of people recommend you learn PDO. Personally I prefer MySQLi over prepared statements, but that's just my preference. Either is fine, but learn one of them.
Now for your loop. You seem to be checking for a result from your mysql query within your loop, that's wrong. I'm guessing above that you have a query which loads it's results into $subArr. You need to call mysql_num_rows before you load them into $subArr.
The loop itself is fine other than that once you apply it to a list as above. Your final code should look something like this. Note, I've used MySQLi in my example, I recommend you do the same although it shouldn't be too hard for you to convert it to MySQL if you wish to.
<?php
$subArr = array();
$query = "SELECT something FROM somewhere";
$result = $mysql->query($query);
if($result->num_rows) {
while($row = $result->fetch_assoc()) { //I personally prefer fetch_assoc over the others, but fetch_row or fetch_array are both fine here too.
$subArr = $row;
}
}
//Lets output the menu
$resultString .= '<ul class="menu">';
foreach($subArr as $sub => $result) {
$resultString .= '<li>' . $result['name'] . '</li>'
}
$resultString = '</ul>';
As one last point of note, you don't need to put a cursor: pointer on an a tag, it has that styling by default.
I hope this manages to clear some things up for you.

Merging multiple CSS files into one in PHP

i'm working on a script that generates multiple CSS into one. And Here is the script.
$full_string = "";
foreach($allfiles as $curfile => $file) {
$file = $PATH[$curfile] . $file;
$file_open = fopen($file , 'r');
$concat = "\n" . fread($file_open , filesize($file)) . "\n";
$full_string .= $concat;
fclose($file_open);
}
return $full_string;
Here i'm combining all the CSS files into one. But the problem now is i have to compare the current CSS($file) with another css(let's consider it as overrider.css). And if $file is having a style like,
h1 {
color: white;
background: teal;
FONT-FAMILY: arial, helvetica, lucida-sans, sans-serif;
FONT-SIZE: 18pt;
FONT-STYLE: normal;
FONT-VARIANT: normal;
}
body
{
font-family: arial;
FONT-SIZE: 14px;
}
and if overrider.css is having a style like,
body
{
font-family: Calibri;
color: #3E83F1;
}
Then final CSS(output.css) generated should be,
h1 {
color: white;
background: teal;
FONT-FAMILY: arial, helvetica, lucida-sans, sans-serif;
FONT-SIZE: 18pt;
FONT-STYLE: normal;
FONT-VARIANT: normal;
}
body
{
font-family: Calibri;
FONT-SIZE: 14px;
color: #3E83F1;
}
Here, since style for body in override.css have font-family, it replaces the font-family property in original CSS and since the color is a new property which is not present in ($file) which is original CSS file, so it should add the property to original CSS file. So how to achieve this in PHP, since i don't have any idea on parsing CSS. Any idea on this would be greatly appreciated.
Please note that i need to generate a new CSS file by giving the input as file1($file) and file2(override.css) and we need to genrate output.css with the styles overridden.
Thanks in advance.
There are a few CSS parsers available (google "php css parser"), like this one that I didn't try, but seems interesting. But personally I'd do the parsing myself - following that kind of pseudo-PHP algorithm
read all the files into one string Str, with all "\n", "\r" and "\t" replaced by a space (to make parsing (a bit) easier)
then, function to process (selector => rules)
func deal with selectors and rules:
rules = array()
do {
S = string from current pos to next `{` excluded (selectors)
R = string from '{' to next '}' (rules)
r = explode(';', R)
lr = make array of rules from r trimmed elements
s = explode (',', S)
ls = make array of [selector => lr]
// same sel: latest rule overwrite existing, added if not exist
merge ls into rules
} while (not '}' and not '#' and not EOF); // the '}' would be the closing media
return rules
Main function to deal with medias, and then call the above function
medias = array();
func deal with Str
do {
if (first non blank char is #) {
media = array of listed medias
eat fist '{'
}
else {
media = array ('GLOBAL')
}
selectorsrules = deal with selectors and rules(rest of Str)
foreach (media as m) {
merge in medias(m) selectorsrules, same procedure as above
}
} while (not EOF);
Interesting project but I don't have the time to implement it fully. Result is in medias array.
if you want font-family: arial; to be apply then add it as font-family: arial !important;
you do not need to worry about merging them because browser will automatically add color to body tag from second css of color found in first css then it will overwrite it with second css.
You have 2 choices:
The simple way is to change your css files and add !important where it is important to be there. It is correct to have for example "body" in css more than 1 time. And whenever you a style to be overridden leave it. Ofcourse this kind of approach is mostly manual. You have to know where it will be overwritten and where not.
The second approach requires string parsing, regular expressions, and i mean you should know your way around the string ad how to parse them. You should get each of the files content, save them to a string, and you should compare them using regex to if a tag is exists in both, then merge the tags content. This way is easy to say but hard to implement.

Categories