diff --git a/markdown.php b/markdown.php new file mode 100644 index 0000000..2d69220 --- /dev/null +++ b/markdown.php @@ -0,0 +1,248 @@ + */ + +function debug ($msg) +{ + $trace = debug_backtrace(); + $back = reset ($trace); + file_put_contents ("/tmp/debug", "[".$back["line"]."] $msg\n", FILE_APPEND); +} +error_reporting (E_ALL); +/** Markdown management */ +class markdown +{ + /** Convert the markdown language to HTML + Return the HTML string + @param string $mark Message in markdown syntax to display */ + public function html ($mark) + { + $res = ""; + $search = array (); + $replace = array (); + + // VALIDATION +// if (substr ($mark, -1) !== "\n") +// $mark .= "\n"; + + // SEPARATORS : *** --- ___ * * * - - - _ _ _ + // Must be placed before EMPHASIS + $search[] = "/\\n^[*_-] ?[*_-] ?[*_-]$/Um"; + $replace[] = "

\n
\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 : + // + // + $search[] = "(\<(https?://.+)\>)"; $replace[] = "\\1"; + $search[] = "(\<(.+@.+)\>)"; $replace[] = "\\1"; + // 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[] = "/\\n^###### ([^#]+)(#*)$\\n/Um"; + $replace[] = "

\n
\\1
\n

"; + // ##### Title5 + $search[] = "/\\n^##### ([^#]+)(#*)$\\n/Um"; + $replace[] = "

\n
\\1
\n

"; + // #### Title4 + $search[] = "/\\n^#### ([^#]+)(#*)$\\n/Um"; + $replace[] = "

\n

\\1

\n

"; + // ### Title3 + $search[] = "/\\n^### ([^#]+)(#*)$\\n/Um"; + $replace[] = "

\n

\\1

\n

"; + // ## Title2 + $search[] = "/\\n^## ([^#]+)(#*)$\\n/Um"; + $replace[] = "

\n

\\1

\n

"; + // # Title1 + $search[] = "/\\n^# ([^#]+)(#*)$\\n/Um"; + $replace[] = "

\n

\\1

\n

"; + // Titles with underline (SeText) + // Titre1 + // ====== + $search[] = "/\\n^(.+)\\n==+$\\n/Um"; + $replace[] = "

\n

\\1

\n

"; + // Titre2 + // ------ + $search[] = "/\\n^(.+)\\n--+$\\n/Um"; + $replace[] = "

\n

\\1

\n

"; + + // End of line with double space :
+ $search[] = "/( )$/Um"; $replace[] = "
"; + + $res = preg_replace ($search, $replace, $mark); + + $res = $this->paragraph ($res); + return $res; + } + + /** Recursive function to translate the paragraphs + return the html */ + private function paragraph ($mark) + { + $debug = 1; + $mark = str_replace ("\t", " ", $mark); + $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); + foreach ($lines as $line) + { + debug ( "DEBUT:$line"); + $type = $this->paragraphType ($line); + debug ( "DEBUT: Type='$type'"); + $matches = array (); + switch ($type) + { + case "ol" : + preg_match ("/^( *)[0-9]+\. +(.*)/", $line, $matches); + $lineTxt = $matches[2]; + break ; + case "ul" : + preg_match ("/^( *)[-+*] +(.*)/", $line, $matches); + $lineTxt = $matches[2]; + break ; + default: + $lineTxt = $line; + } + $indent = strspn ($line, " "); + debug ( "DEBUT: Indent='$indent'"); + + // Spacing + if ($indent < end ($indentStack)) + { + debug ( "DEB1 : Ending of block"); + if (end ($htmlStack) === "li") + { + debug ("Pending

  • : closing"); + debug ("
  • "); + $res .= ""; + array_pop ($htmlStack); + } + $oldType = array_pop ($typeStack); + debug (str_repeat (" ", end ($indentStack)).""); + $res .= "\n".str_repeat (" ", end ($indentStack)).""; + array_pop ($htmlStack); + if ($type === "ol" || $type === "ul") + { + debug ("DEB2 : Pending
  • : closing"); + debug ("
  • "); + $res .= "\n"; + array_pop ($htmlStack); + debug ("DEB2 : Adding li"); + $htmlStack[] = "li"; + debug ( str_repeat (" ", $indent)."
  • "); + $res .= "\n".str_repeat (" ", $indent)."
  • "; + } + debug (str_repeat (" ", $indent)."$lineTxt"); + $res .= str_repeat (" ", $indent)."$lineTxt"; + array_pop ($indentStack); + } + elseif ($indent > end ($indentStack)) + { + debug ( "DEB1 : Starting a new block"); + array_push ($indentStack, $indent); + array_push ($typeStack, $type); + debug ( str_repeat (" ", $indent)."<$type>"); + $res .= "\n".str_repeat (" ", $indent)."<$type>"; + $htmlStack[] = $type; + if ($type === "ol" || $type === "ul") + { + debug ("DEB2 : Adding li"); + $htmlStack[] = "li"; + debug ( str_repeat (" ", $indent)."
  • "); + $res .= "\n".str_repeat (" ", $indent)."
  • "; + } + debug ( "$lineTxt"); + $res .= "$lineTxt"; + } + else + { + debug ( "DEB1 : Continuous block $type/".end ($typeStack)); + if (end ($htmlStack) === "li") + { + debug ("Pending
  • : closing"); + debug ("
  • "); + $res .= ""; + array_pop ($htmlStack); + } + debug ( "DEB1 : Continuous block $type/".end ($typeStack)." SECOND"); + if ($type !== end ($typeStack)) + { + debug ( "DEB2 : Continuous Block but type change"); + $oldType = array_pop ($typeStack); + debug (str_repeat (" ", end ($indentStack)).""); + $res .= "\n".str_repeat (" ", end ($indentStack)).""; + array_pop ($htmlStack); + + debug (str_repeat (" ", end ($indentStack))."<$type>"); + $res .= "\n".str_repeat (" ", end ($indentStack))."<$type>"; + $htmlStack[] = $type; + array_push ($indentStack, $indent); + array_push ($typeStack, $type); + } + + if ($type === "ol" || $type === "ul") + { + debug ("DEB2 : Adding li"); + $htmlStack[] = "li"; + debug ( str_repeat (" ", $indent)."
  • "); + $res .= "\n".str_repeat (" ", $indent)."
  • "; + } + + debug ("$lineTxt"); + $res .= "$lineTxt"; + } + } + + debug ( "DEB1 : End of loop"); + $htmlStack = array_reverse ($htmlStack); + foreach ($htmlStack as $type) + { + debug ( "FIN"); + $res .= "\n"; + } + + debug ( "-----------\n"); + return $res; + } + + /** Return the Type of object in the provided line + p, ul, ol, code */ + private function paragraphType ($line) + { + $line = ltrim ($line); + if (! isset ($line{0})) + return ""; + if ($line{0} === "*" || $line{0} === "-" || $line{0} === "+") + return "ul"; + if (preg_match ("/^[0-9]+\. /", $line) === 1) + return "ol"; + return "p"; + } +}