*/ error_reporting (E_ALL); /** Markdown management */ class markdown { /** debug variable */ public $debug = false; /** Convert the markdown language to HTML Return the HTML string @param string $mark Message in markdown syntax to display */ public function html ($mark) { if ($this->debug && file_exists ("/tmp/debugMD")) unlink ("/tmp/debugMD"); $res = ""; $mark = htmlentities ($mark, ENT_QUOTES); // Here are the regexp on multilines $search = array (); $replace = array (); // Titles with underline (SeText) // Titre1 // ====== $search[] = "/^(.+)\\n==+$\\n/Um"; $replace[] = "\n
\n
\n", "", $res); return $res; } /** Translate the Markdown paragraph in HTML return the html */ private function paragraph ($mark) { // Think thereis already htmlentities passed on $mark !!! $timeStart = microtime (TRUE); $timeregex = 0; // Initialization of convertions $search = array (); $replace = array (); // Titles short // == TITRE1 $search[] = "/^==+ (.+)( ==+)?$/Um"; $replace[] = "\n"; // -- TITRE2 $search[] = "/^--+ (.+)( --+)?$/Um"; $replace[] = "
\n";
// EMPHASIS : _ or * for normal, __ or ** for strong
$search[] = "/__(.+)__/"; $replace[] = "\\1";
$search[] = "/_(.+)_/"; $replace[] = "\\1";
$search[] = "/\*\*(.+)\*\*/"; $replace[] = "\\1";
$search[] = "/\*(.+)\*/"; $replace[] = "\\1";
// CODE : `code` ->
$search[] = "/\\n?\`((\\n|.)+)\`/Um";
$replace[] = "\\1";
// LINKS
// [Google Site](http://google.fr/ "With help bubble")
$search[] = "(\[(.+)\]\((https?://.+) \"(.+)\"\))";
$replace[] = "\\1";
// [Google Site](http://google.fr/)
$search[] = "(\[(.+)\]\((http.+)\))"; $replace[] = "\\1";
// Automatics links :
//
"; // ##### Title5 $search[] = "/^##### (.+)( +#*)$/Um"; $replace[] = "
"; // #### Title4 $search[] = "/^#### (.+)( +#*)$/Um"; $replace[] = "
"; // ### Title3 $search[] = "/^### (.+)( +#*)$/Um"; $replace[] = "
"; // ## Title2 $search[] = "/^## (.+)( +#*)$/Um"; $replace[] = "
"; // # Title1 $search[] = "/^# (.+)( +#*)$/Um"; $replace[] = "
";
// End of line with double space :
$search[] = "/( )$/Um"; $replace[] = "
";
// End of line with continuous on second line : add blank
// $search[] = "/(.)\\n([A-Za-z0-9])/Um"; $replace[] = "\\1 \\2";
// Cleanning the markdown text
$mark = str_replace ("\t", " ", $mark);
if (trim ($mark) === "")
return "";
$spacer = " ";
$res = "";
// P, OL, UL (but not LI !)
// Use to found the changing of types
$typeStack = array ();
// Number of spaces
$indentStack = array (-1);
// All the HTML stack (with LI)
$htmlStack = array ();
$lines = explode ("\n", $mark);
$timeInit = microtime (TRUE) - $timeStart;
$blockLI = false;
foreach ($lines as $nb=>$line)
{
$this->debugMKD ("DEBUT:$line");
if (substr (ltrim ($line), 0, 1) === "<")
{
$this->debugMKD ("HTML : Skipped");
$res .= $line;
continue;
}
$type = $this->paragraphType ($line);
$this->debugMKD ("DEBUT: Type='$type'");
$matches = array ();
switch ($type)
{
case "ol" :
preg_match ("/^( *)[0-9]+\. +(.*)/", $line, $matches);
if (!isset ($matches[2]))
$lineTxt = $line;
else
$lineTxt = $matches[2];
break ;
case "ul" :
preg_match ("/^( *)[-+*] +(.*)/", $line, $matches);
if (!isset ($matches[2]))
$lineTxt = $line;
else
$lineTxt = $matches[2];
break ;
default:
$lineTxt = $line;
}
$indent = strspn ($line, " ");
$this->debugMKD ("DEBUT: Indent='$indent'");
$this->debugMKD ("DEBUT: indentStack=".print_r ($indentStack, TRUE));
$this->debugMKD ("DEBUT: typeStack=".print_r ($typeStack, TRUE));
// Spacing
if ($indent < end ($indentStack))
{
$this->debugMKD ("DEB1 : Ending of block");
if (end ($htmlStack) === "li")
{
$this->debugMKD ("Pending
continue until the end of paragraph
$this->debugMKD ("DEB1 : Starting a new block");
if ($type === "")
{
$this->debugMKD ("No type : skipped");
continue;
}
if (end ($indentStack))
array_pop ($indentStack);
if ($type === "code")
{
// Code need a pre before code
if (end ($typeStack))
{
$this->debugMKD ("DEB2 : CODE : Close older HTML");
// Remove last \n to put closing tag at the end of line
$res = substr ($res, 0, -1);
$oldType = array_pop ($typeStack);
$this->debugMKD (str_repeat (" ", end ($indentStack))."$oldType>");
$res .= str_repeat (" ", end ($indentStack))."$oldType>";
array_pop ($indentStack);
array_pop ($htmlStack);
}
$typetmp = "pre>debugMKD (str_repeat (" ", $indent)."<$typetmp>");
$res .= "".str_repeat (" ", $indent)."<$typetmp>";
if ($type === "ol" || $type === "ul")
{
$this->debugMKD ("DEB2 : Adding li");
$htmlStack[] = "li";
$this->debugMKD (str_repeat (" ", $indent)."");
$res .= "\n".str_repeat (" ", $indent)." ";
}
}
if ($type === "" && end ($indentStack))
{
$this->debugMKD ("DEB2 : Empty type");
// Remove last \n to put closing tag at the end of line
$res = substr ($res, 0, -1);
$oldType = array_pop ($typeStack);
$this->debugMKD (str_repeat (" ", end ($indentStack))."$oldType>");
$res .= "\n".str_repeat (" ", end ($indentStack))."$oldType>";
array_pop ($htmlStack);
}
// If code, there is no emphasis, email, and other conversions
if ($type !== "code")
{
$timetmp = microtime (TRUE);
$lineTxt = preg_replace ($search, $replace, $lineTxt);
$timeregex += (microtime (TRUE) - $timetmp);
}
$this->debugMKD ("$lineTxt");
$res .= substr ($lineTxt, end ($indentStack))."";
if ($type === "code")
$res .= "\n";
}
$this->debugMKD ("DEB1 : End of loop");
$htmlStack = array_reverse ($htmlStack);
foreach ($htmlStack as $i=>$type)
{
if ($type === "code")
$res = substr ($res, 0, -1);
$this->debugMKD ("FIN$type>");
$res .= "$type>";
if (($i+1) < count ($htmlStack) && $type !== "code")
$res .= "\n";
}
$this->debugMKD ("TimeInit=".($timeInit*1000)."ms");
$this->debugMKD ("TimeRegex=".($timeregex*1000)."ms");
$this->debugMKD ("TimeAll=".((microtime (TRUE) - $timeStart)*1000)."ms");
$this->debugMKD ("-----------\n");
return $res;
}
/** Return the Type of object in the provided line
p, ul, ol, code */
private function paragraphType ($line)
{
if (! isset ($line{0}))
return "";
if (preg_match ("/^[ \t]*[+*-] /", $line) === 1)
return "ul";
if (preg_match ("/^[ \t]*[0-9]+\. /", $line) === 1)
return "ol";
if (preg_match ("/^( |\t)+/", $line) === 1)
return "code";
return "p";
}
/** Function to display the MarkDown debug */
private function debugMKD ($msg)
{
if ($this->debug === false) return;
$trace = debug_backtrace();
$back = reset ($trace);
file_put_contents ("/tmp/debugMD", "[".$back["line"]."] $msg\n",
FILE_APPEND);
}
}