git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@3012 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
348 lines
12 KiB
PHP
348 lines
12 KiB
PHP
<?php
|
|
/** DomFramework
|
|
@package domframework
|
|
@author Dominique Fournier <dominique@fournier38.fr> */
|
|
|
|
/** Convert the Markdown text to html format */
|
|
class markdown
|
|
{
|
|
/** To debug the markdown analyzer, activate the option */
|
|
public $debug = false;
|
|
|
|
private $blockid = array ("<h1>","<h2>","<h3>","<h4>","<h5>","<h6>",
|
|
"<hr/>");
|
|
|
|
/** Convert the markdown text to html */
|
|
public function html ($markdown)
|
|
{
|
|
$markdown = rtrim ($markdown);
|
|
$markdown = htmlentities ($markdown);
|
|
|
|
// Here are the regexp on multilines
|
|
$search = array ();
|
|
$replace = array ();
|
|
// Titles with underline (SeText)
|
|
// Titre1
|
|
// ======
|
|
$search[] = "/(.+)\\n==+$/Um";
|
|
$replace[] = "</p><h1>\\1</h1>\n<p>";
|
|
// Titre2
|
|
// ------
|
|
$search[] = "/(.+)\\n--+$/Um";
|
|
$replace[] = "</p><h2>\\1</h2>\n<p>";
|
|
|
|
// SEPARATORS : *** --- ___ * * * - - - _ _ _
|
|
// Must be placed before EMPHASIS
|
|
$search[] = "/^[*_-] ?[*_-] ?[*_-]$/Um";
|
|
$replace[] = "</p><hr/>\n<p>";
|
|
|
|
$markdown = preg_replace ($search, $replace, $markdown);
|
|
|
|
$textArray = explode ("\n", $markdown);
|
|
$pos = 0;
|
|
$html = $this->detectBlock ($textArray, 0, $pos);
|
|
$html = str_replace ("<p></p>", "", $html);
|
|
$html = str_replace ("<p> </p>", "", $html);
|
|
$html = trim ($html);
|
|
return $html;
|
|
}
|
|
|
|
/** Search and replace in the paragraph on one line */
|
|
private function searchReplace ($line)
|
|
{
|
|
if ($this->debug)
|
|
echo "CALL searchReplace ($line)\n";
|
|
$search = array ();
|
|
$replace = array ();
|
|
// Titles short
|
|
// == TITRE1
|
|
$search[] = "/^==+ (.+)( ==+)?$/Um";
|
|
$replace[] = "</p>\n<h1>\\1</h1>\n<p>";
|
|
// -- TITRE2
|
|
$search[] = "/^--+ (.+)( --+)?$/Um";
|
|
$replace[] = "</p>\n<h2>\\1</h2>\n<p>";
|
|
// EMPHASIS : _ or * for normal, __ or ** for strong
|
|
$search[] = "/__(.+)__/U"; $replace[] = "<strong>\\1</strong>";
|
|
$search[] = "/_(.+)_/U"; $replace[] = "<em>\\1</em>";
|
|
$search[] = "/\*\*(.+)\*\*/U"; $replace[] = "<strong>\\1</strong>";
|
|
$search[] = "/\*(.+)\*/U"; $replace[] = "<em>\\1</em>";
|
|
|
|
// CODE : `code` -> <code>
|
|
$search[] = "/\\n?\`((\\n|.)+)\`/Um";
|
|
$replace[] = "<code>\\1</code>";
|
|
|
|
// LINKS (can be relative)
|
|
// images
|
|
$search[] = "(!\[(.+)\]\((.+)\))";
|
|
$replace[] = "<img src='\\2' alt='\\1'/>";
|
|
// [Google Site](http://google.fr/ "With help bubble")
|
|
$search[] = "(\[(.+)\]\((.+) \"(.+)\"\))";
|
|
$replace[] = "<a href='\\2' title='\\3'>\\1</a>";
|
|
// [Google Site](http://google.fr/)
|
|
$search[] = "(\[(.+)\]\((.+)\))"; $replace[] = "<a href='\\2'>\\1</a>";
|
|
// Automatics links :
|
|
// <http://dominique.fournier38.fr>
|
|
// <dominique@fournier38.fr>
|
|
$search[] = "#<(https?://.+)>#U";
|
|
$replace[] = "<a href='\\1'>\\1</a>";
|
|
$search[] = "#<(.+@.+)>#U";
|
|
$replace[] = "<a href='mailto:\\1'>\\1</a>";
|
|
// The links must not allow the <em> : redo the conversion
|
|
$search[] = "#(<a href='.*)<em>(.*)</em>(.*'>.*)<em>(.*)</em>(.*</a>)#";
|
|
$replace[] = "\\1_\\2_\\3_\\4_\\5";
|
|
// TODO : Links by reference :
|
|
// Voici un petit texte écrit par [Michel Fortin][mf].
|
|
// [mf]: http://michelf.ca/ "Mon site web"
|
|
|
|
// TITLES
|
|
// Titles ATX (Optionnal sharp at the end)
|
|
// ###### Title6
|
|
$search[] = "/^###### (.+)( +#+)?$/Um";
|
|
$replace[] = "</p><h6>\\1</h6><p>";
|
|
// ##### Title5
|
|
$search[] = "/^##### (.+)( +#+)?$/Um";
|
|
$replace[] = "</p><h5>\\1</h5><p>";
|
|
// #### Title4
|
|
$search[] = "/^#### (.+)( +#+)?$/Um";
|
|
$replace[] = "</p><h4>\\1</h4><p>";
|
|
// ### Title3
|
|
$search[] = "/^### (.+)( +#+)?$/Um";
|
|
$replace[] = "</p><h3>\\1</h3><p>";
|
|
// ## Title2
|
|
$search[] = "/^## (.+)( +#+)?$/Um";
|
|
$replace[] = "</p><h2>\\1</h2><p>";
|
|
// # Title1
|
|
$search[] = "/^# (.+)( +#+)?$/Um";
|
|
$replace[] = "</p><h1>\\1</h1><p>";
|
|
return preg_replace ($search, $replace, $line);
|
|
}
|
|
|
|
/** Return HTML code corresponding to the code block
|
|
@param array $text The Markdown text to translate split by \n
|
|
@param integer $depth The depth of current bloc (in number of space)
|
|
@param integer $pos The start line number of the bloc */
|
|
private function typeCode ($text, $depth, &$pos)
|
|
{
|
|
if ($this->debug) echo "CALL typeCode (\$text, $depth, $pos)\n";
|
|
$posStart = $pos;
|
|
$content = "";
|
|
// End of code block : end of markdown text / depth lighter than $depth
|
|
while (isset ($text[$pos]) &&
|
|
$this->depth($text[$pos]) >= $depth)
|
|
{
|
|
// The Code blocks can't be imbricated
|
|
if ($pos > $posStart)
|
|
$content .= "\n";
|
|
$content .= substr ($text[$pos], $depth);
|
|
$pos++;
|
|
}
|
|
// Insert Geshi on $content
|
|
if ($this->debug)
|
|
echo "RETURN typeCode : <pre><code>$content</code></pre>\n";
|
|
return "<pre><code>$content</code></pre>\n";
|
|
}
|
|
|
|
/** Return HTML code corresponding to the OL block
|
|
@param array $text The Markdown text to translate split by \n
|
|
@param integer $depth The depth of current bloc (in number of space)
|
|
@param integer $pos The start line number of the bloc */
|
|
private function typeOL ($text, $depth, &$pos)
|
|
{
|
|
if ($this->debug) echo "CALL typeOL (\$text, $depth, $pos)\n";
|
|
$content = $this->typeOLUL ($text, $depth, $pos, "ol");
|
|
if ($this->debug) echo "RETURN typeOL : $content\n";
|
|
return $content;
|
|
}
|
|
|
|
/** Return HTML code corresponding to the UL block
|
|
@param array $text The Markdown text to translate split by \n
|
|
@param integer $depth The depth of current bloc (in number of space)
|
|
@param integer $pos The start line number of the bloc */
|
|
private function typeUL ($text, $depth, &$pos)
|
|
{
|
|
if ($this->debug) echo "CALL typeUL (\$text, $depth, $pos)\n";
|
|
$content = $this->typeOLUL ($text, $depth, $pos, "ul");
|
|
if ($this->debug) echo "RETURN typeUL : $content\n";
|
|
return $content;
|
|
}
|
|
|
|
private function typeOLUL ($text, $depth, &$pos, $type)
|
|
{
|
|
if ($this->debug) echo "CALL typeOLUL (\$text, $depth, $pos, $type)\n";
|
|
$content = "";
|
|
// End of OL/UL block : end of markdown text / depth lighter than $depth /
|
|
// linetype changed
|
|
$blockStart = $pos;
|
|
$blockContent = "";
|
|
while (isset ($text[$pos]) &&
|
|
$this->depth($text[$pos]) >= $depth &&
|
|
$this->lineType ($text[$pos]) === $type)
|
|
{
|
|
if ($this->debug)
|
|
echo "Start while $pos\n";
|
|
if (1)
|
|
{
|
|
$content .= str_repeat (" ", ($depth+2))."<li>";
|
|
$blockContent .= $text[$pos];
|
|
$pos++;
|
|
// Look at continuous lines
|
|
while (isset ($text[$pos]) &&
|
|
$this->lineType ($text[$pos]) !== "NONE" &&
|
|
$this->lineType ($text[$pos]) !== $type &&
|
|
$this->depth($text[$pos]) === $depth)
|
|
{
|
|
if ($this->debug)
|
|
echo "Continuous line : ".$pos."\n";
|
|
$blockContent .= " ".$text[$pos];
|
|
$pos++;
|
|
continue;
|
|
}
|
|
// Indent the li and remove the number and dot and space at start
|
|
if ($type === "ol")
|
|
preg_match ("/^( *)[0-9]+\. +(.*)/", $blockContent, $matches);
|
|
else
|
|
preg_match ("/^( *)[-+*] +(.*)/", $blockContent, $matches);
|
|
if (!isset ($matches[2]))
|
|
$lineTxt = $blockContent;
|
|
else
|
|
$lineTxt = $matches[2];
|
|
$lineTxt = $this->searchReplace ($lineTxt);
|
|
$content .= $lineTxt;
|
|
$blockStart = $pos;
|
|
$blockContent = "";
|
|
}
|
|
if (isset ($text[$pos]) && $this->depth($text[$pos]) > $depth)
|
|
{
|
|
if ($this->debug)
|
|
echo "Detect Block\n";
|
|
$content .= "\n".
|
|
$this->detectBlock ($text, $this->depth($text[$pos]), $pos).
|
|
str_repeat (" ", ($depth+2))."</li>\n";
|
|
}
|
|
else
|
|
{
|
|
$content .= "</li>\n";
|
|
}
|
|
}
|
|
if ($this->debug) echo "RETURN typeOLUL : <$type>\n$content</$type>\n";
|
|
return "<$type>\n$content".str_repeat (" ", $depth)."</$type>\n";
|
|
|
|
}
|
|
|
|
/** Return HTML code corresponding to the NONE block
|
|
The NONE type exists only on empty strings. Just skip the current and
|
|
empty line, and return an empty string */
|
|
private function typeNONE ($text, $depth, &$pos)
|
|
{
|
|
if ($this->debug) echo "CALL typeNONE (\$text, $depth, $pos)\n";
|
|
$pos++;
|
|
return "";
|
|
}
|
|
|
|
/** Return HTML code corresponding to the P block
|
|
@param array $text The Markdown text to translate split by \n
|
|
@param integer $depth The depth of current bloc (in number of space)
|
|
@param integer $pos The start line number of the bloc */
|
|
private function typeP ($text, $depth, &$pos)
|
|
{
|
|
if ($this->debug) echo "CALL typeP (\$text, $depth, $pos)\n";
|
|
$content = "";
|
|
// End of P block : end of markdown text / depth lighter than $depth /
|
|
// linetype changed
|
|
$Pinc = $pos;
|
|
while (isset ($text[$pos]) &&
|
|
$this->depth($text[$pos]) == $depth &&
|
|
$this->lineType ($text[$pos]) === "p")
|
|
{
|
|
if (substr ($text[$pos], -2) === " ")
|
|
{
|
|
// Two spaces at end of line : add <br/>
|
|
$content .= $this->searchReplace (substr ($text[$pos], 0, -2)) ."<br/>";
|
|
}
|
|
elseif ($pos > $Pinc && substr ($content, -5) !== "<br/>")
|
|
{
|
|
// Add a space between two lines from the same block, if this is not
|
|
// the continuity of the block
|
|
$content .= " ".$this->searchReplace ($text[$pos]);
|
|
}
|
|
else
|
|
{
|
|
$content .= $this->searchReplace ($text[$pos]);
|
|
}
|
|
$pos++;
|
|
}
|
|
if ($this->debug) echo "RETURN typeP : <p>$content</p>\n";
|
|
return "<p>$content</p>\n";
|
|
}
|
|
|
|
/** Detect the type of the text and call the appropriate function *
|
|
@param array $text The Markdown text to translate split by \n
|
|
@param integer $depth The depth of current bloc (in number of space)
|
|
@param integer $pos The start line number of the bloc
|
|
@return the HTML code */
|
|
private function detectBlock ($text, $depth, &$pos)
|
|
{
|
|
if ($this->debug) echo "CALL detectBlock (\$text, $depth, $pos)\n";
|
|
$content = "";
|
|
$blockContent = "";
|
|
// detect the type and call the right type function
|
|
while (isset ($text[$pos]))
|
|
{
|
|
if ($this->depth ($text[$pos]) > $depth && $depth === 0)
|
|
{
|
|
// New block code
|
|
if ($this->debug)
|
|
echo "New block code\n";
|
|
$content .= $this->typeCode ($text, $this->depth ($text[$pos]), $pos);
|
|
continue;
|
|
}
|
|
elseif ($this->depth ($text[$pos]) > $depth)
|
|
{
|
|
if ($this->debug)
|
|
echo "CALL DEPTH > MINDEPTH (".$this->depth ($text[$pos]).
|
|
" > $depth)\n";
|
|
$content .= $this->detectBlock ($text, $this->depth ($text[$pos]),
|
|
$pos);
|
|
continue;
|
|
}
|
|
elseif ($this->depth ($text[$pos]) < $depth)
|
|
{
|
|
if ($this->debug)
|
|
echo "CALL DEPTH > MINDEPTH (".$this->depth ($text[$pos]).
|
|
" < $depth)\n";
|
|
return $content;
|
|
}
|
|
|
|
$type = $this->lineType ($text[$pos]);
|
|
$func = "type$type";
|
|
if ($this->debug)
|
|
echo "FROM DETECT : CALL $func (line=".$text[$pos].")\n";
|
|
$content .= str_repeat (" ", $depth). $this->$func ($text, $depth, $pos);
|
|
}
|
|
return $content;
|
|
}
|
|
|
|
/** Return the Type of object in the provided line
|
|
p, ul, ol, code */
|
|
private function lineType ($line)
|
|
{
|
|
if (! isset ($line{0}))
|
|
return "NONE";
|
|
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";
|
|
}
|
|
|
|
/** Return the depth of the provided line
|
|
@param $line Line to analyze
|
|
@return the depth of the line */
|
|
private function depth ($line)
|
|
{
|
|
return strspn ($line, " ");
|
|
}
|
|
}
|