I am working on a PHP script that takes a plain text file task list and parses things like:
Project name heading because it ends with a semi-colon: == a project
Tasks begin with a - task name
#tag-name gets wrapped with <span class="tag">#tag-name</span>
#done gets wrapped with <span class="tag done">#done</span>
and more
This PHP function below is what does the replacements...
function get_marked_up_todo($todo){
$todo = htmlspecialchars($todo,ENT_QUOTES)."\n\n";
$search = array('/(.+:)(.+)\n\n/sU', // Get projects
'/(- ([^\#\n]+).+)/', // Get todos
'/(.+:)/', // Get headings
'/\n([^\<\n].+)/', // Get notes
'/- (.+#done)/', // Get done
'/(#due\([^\)]+\))/', // Get due tags
'/(#(?!due)[^\s]+)/', // Get tags
"/\t/",
'/`(.*?)`/', // inline code
);
$replace = array("<div class=\"project\">\n$1$2\n</div>\n\n",
'<span class="todo"><input type="checkbox" value="'.trim('\2').'"> \1</span>',
'<h1>\1</h1>',
"\n\t<span class=\"note\">$1</span>",
'<span class="bullet-done">? ? ??</span> - <strike>\1</strike>',
'<span class="tag due">\1</span>',
'<span class="tag">\1</span>',
"\t<span class=\"tab\"></span>",
'<code>\1</code>',
);
return preg_replace($search, $replace, $todo);
}
In the above search and replace arrays the very last item in both arrays is a new pattern I added to find inline code wrapped in backticks like Markdown inline code.
The problem is, on the output each task item row gets a checkbox input field added to the front of the task row and in the value of this checkbox my code is being parsed.
All the other replacement RegEx does not show up in the checkbox values only when I add an item like this inline code or any other item I have added such as RegEx for bold text and italic.
Why does mine show up in the checkbox value HTML and none of the others do?
I have setup a demo to show the output here of the PHP -
https://3v4l.org/f0e8W#output
Here is the full code as well
<?php
echo "<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>TODO.todo</title></head>
<body>
<style>
.project {
line-height: 4px;
}
.bullet-done {
font-weight: bold;
font-style: normal;
color: rgba(0,114,62,1.0);
}
.note{
display: block;
color: rgba(133,130,102,1.0);
font-weight: normal;
font-style: normal;
}
.todo {
display: inline-block;
}
.tag {
font-weight: bold;
font-style: normal;
color: rgba(160,46,43,0.6);
}
body {
background: rgba(239,233,183,1.0);
color: rgba(0,0,0,0.31);
font-weight: normal;
font-style: normal;
}
h1 {
font-weight: bold;
font-style: normal;
background: rgba(0,0,0,0.06);
color: rgba(188,100,74,1.0);
width: 100%;
line-height: 34px;
}
.tab{
display: inline-block;
width:0px;
height: 0px;
background: #000000;
</style><pre>";
$todo = 'Version 1:
This file is in TaskPaper format.
Tabs are used to indent.
Each task begins with a "- ".
Projects end with a ":".
Tags are in the format "#tag_name".
All other lines (such as these) are considered as notes,
and are to be ignored.
- User signup
- Register for an account
- Log in #done
- Forget password
- Manage users
- Create users #in_progress
- Delete users
- User profile page #40%
- Blog
- Creating new posts #done
- Comments #done
- Moderating comments #done
This is my todo list:
This is a note about the list.
- this is an item #done
- and this is #me
this is a note about my done item
- this is the last #java #shopping #item #done
This is a second list:
- Add more funk to something #work #java
- Send something somewhere #work #email #due(12 Aug 07)
- this is an example
- dfgdfg
ggg
hfghf
- hgh
- dfygdfgdf
List:
- gdfgdf `inline code` hhf
- gdfgdf
- dfgdfg #done
';
echo get_marked_up_todo($todo);
echo '</pre></body></html>';
function get_marked_up_todo($todo){
$todo = htmlspecialchars($todo,ENT_QUOTES)."\n\n";
$search = array('/(.+:)(.+)\n\n/sU', // Get projects
'/(- ([^\#\n]+).+)/', // Get todos
'/(.+:)/', // Get headings
'/\n([^\<\n].+)/', // Get notes
'/- (.+#done)/', // Get done
'/(#due\([^\)]+\))/', // Get due tags
'/(#(?!due)[^\s]+)/', // Get tags
"/\t/",
'/`(.*?)`/', // inline code
);
$replace = array("<div class=\"project\">\n$1$2\n</div>\n\n",
'<span class="todo"><input type="checkbox" value="'.trim('\2').'"> \1</span>',
'<h1>\1</h1>',
"\n\t<span class=\"note\">$1</span>",
'<span class="bullet-done">? ? ??</span> - <strike>\1</strike>',
'<span class="tag due">\1</span>',
'<span class="tag">\1</span>',
"\t<span class=\"tab\"></span>",
'<code>\1</code>',
);
return preg_replace($search, $replace, $todo);
}
If I understood the issue correctly, your issue is that when you use your own replacement for <code>, the <input> tag looks like this:
<input type="checkbox" value="gdfgdf <code>inline code</code> hh">
but you want to it to NOT include the <code>...</code> part, like this:
<input type="checkbox" value="gdfgdf ">.
If my understanding is correct, then you fix you simply need to fix the RegExp that's in charge of rendering the <input> tag, which is this one:
'/(- ([^\#\n]+).+)/', // Get todos
The way it works is by taking everything from - until either a # or a newline (\n) shows up. You want to add do it the backtick:
'/(- ([^\#\n]+).+)/', // Get todos
This will make the RegExp stop capturing when it encounters the first ` and will fix your issue (again, if I understood it correctly).
Related
I'm extracting some exif infos from an image and show them on my page (html generated by php).
Below that I have some text fields, where user can add some infos to an image.
Some images have none, some others only 1 and some other multiple exif informations.
Final target is to fill up the text fields, either manually or with already known - but unstructured - exif data.
Having only 1 text value, it is not a problem to use the native browser drag&drop support to move from list of exif data into text input field..
But with 2 or more text values like "water flower, red & green", it is more difficult for handicaped people.
So I was thinking about a possibility to bound such multiple text values together.
Tried with a linked text, but dropping drops the href source and not the anchor text
Any ideas, examples and help is welcome.
Finally I got it.
Maybe it is helpfull for others too, so here below how I did it.
The target:
Allow to drag and drop a combination of words without the needs of first marking them before.
1) the JS part
function allowDrop(ev)
{
ev.preventDefault();
}
function drag(ev)
{
ev.dataTransfer.setData("text", ev.target.id);
}
function drop(ev)
{
ev.preventDefault();
var id = ev.dataTransfer.getData("text");
var addtext = document.getElementById(id).innerHTML;
var oldtext = document.getElementById(ev.target.id).value;
if ( oldtext )
{
ev.target.value = oldtext + ', ' + addtext;
}
else
{
ev.target.value = addtext;
}
$( '#' + id ).addClass( 'dropped' );
}
This base logic I found on w3schools website.
I adapted and enhanced drop function to get the anchor text and extend target. In addition I add a specific class to dropped links to indicate that this link has been used already.
2) the CSS
a.exif_items {
border-radius: 25px;
background: #73AD21;
padding-top: 5px;
padding-right: 15px;
padding-bottom: 5px;
padding-left: 15px;
word-break: keep-all;
word-wrap: normal;
hyphens: none;
}
a.exif_items:visited ,
a.exif_items:hover ,
a.exif_items:active ,
a.exif_items:link {
text-decoration: none;
}
a.exif_items:hover {
font-weight: 600;
color: black;
}
a.dropped {
background: #21AEAE;
}
nothing special. Just a green box around the links. Dropped links get a blue box. The word-break, word-warp and hyphens are set to avoid line breaks in the boxes.
3) the links for the exifs
<a id='{{ unique_exif_item_key }}'
href='#'
class='exif_items'
draggable='true'
ondragstart='drag(event)'
>{{ exif_item_text }}</a>
the {{ ... }} fields needs to be set by you accordingly!
Also nothing special here, just the 2 tags draggable and ondragstart.
4) the target inputs
<input type='text'
name='{{ unique_field_name }}'
id='{{ unique_field_id }}'
size='35'
ondrop='drop(event)'
ondragover='allowDrop(event)'
value='{{ field_value }}'
/>
Again the {{ ... }} fields needs to be set by you accordingly!
Here we have the 2 drop counterparts ondrop and ondragover.
Well, quite simple and easy after you get it. But until you find the info ... ;-)
Hope it is helpfull for others too.
A while ago i have written a small php script to get the information from MySQL and put it in XML.Now one the description of the item in the MySQL was up untill now something like
<p>
the item description ...etc.</p>
so far so good, i used the following :
preg_match('#<p>(.*?)</p>#s',$stro, $disp);
and that worked fine as expected.
now today the admin has added the new items in the database like this
<p style="font-family: Tahoma; font-size: 13px; line-height: 19.5px;">
item description...etc</p>
and now my "trick" above doesnt work
now i tried (found this on Stackoverflow)
//first line should strip the "style" part to only
$kulaka = preg_replace('/(<[^>]+) style=".*?"/i', '$1', $stro);
// and here we should remove the p tag
preg_match('#<p>(.*?)</p>#s',$kulaka, $disp);
it 'almost' works but it gives me
"style=font-family: Tahoma; font-size: 13px; line-height: 19.5px;> item
desctiption "
any suggestions are welcomed,and i want to do it generally for all styles atributes not only for this particular one, as the Admin can change the size or font etc
This will drop the <p> starting tag (and any attributes within it) as well as the closing tag:
$stro = '<p style="font-family: Tahoma; font-size: 13px; line-height: 19.5px;">'
. 'item description...etc</p>';
preg_match('#^<p.*?>(.*)</p>$#is', $stro, $disp);
echo $disp[1] . PHP_EOL;
Output:
item description...etc
It is not totally solid, as it would fail if there would be any paragraph attribute present with an > in their value, but it may be enough for you in this case.
I have the following CSS code:
#np-toolbar-l a{ position: relative; padding: 0px 8px; line-height: 30px; display: inline-block; font-size: 12px; }
#np-toolbar-l a:hover{ background-color: #EFEFEF; opacity: 0.7; }
#np-toolbar-l a *:hover{ cursor: pointer; }
/** Icon */
#np-toolbar [class^="icon-"]{ font-size: 18px; vertical-align: middle; margin-right: 5px; }
I am trying to replace np-toolbar to another ID,
I just want to match np-toolbar not np-toolbar-l or something like this,
here's a tutoral about how to capture all the IDs from CSS:
#-?[_a-zA-Z]+[_a-zA-Z0-9-]*(?=[^}]*\{)
source: Regex to match ids in a css file?
I couldn't find out how do I search a "compelete ID" by regex,
here's my example: (It'll still count np-toolbar-l in)
/(?<=(#|\.))(np-toolbar)+(?=( |:||\[))/
EDIT: ummm, jsut added some symbols behind it,
it looks like the answer which I am looking for
/(?<=(#|\.))(np-toolbar)+(?=( |:|\[|\{|\.|,))/
If you're looking for a specific ID, you can greatly simplify your regex:
/(#np-toolbar)(?=[{.,\s])/g
That should find the ID wherever it appears in the CSS. It first matches the ID exactly, then only allows { (for beginning the style), , (for listing selectors), . (for appended classes), or space/tab/newline characters afterwards, preventing it from matching IDs like np-toolbar-1 etc.
Maybe you could try this regexp:
/#np-toolbar(?![-\w])/
which means: match all "#np-toolbar" inclusions if inclusion doesn't end with "-" or any \w symbol
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.
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.