<?php
$er 
error_reporting(E_ALL & ~E_NOTICE);
#StructuredText parser, by Keith Devens, version .99g.
#http://www.keithdevens.com/software/
#See release notes and To Do at http://www.keithdevens.com/software/markup_history.php
#
#This code is not being developed anymore. It still works and is still in use on my site,
# but I've begun development of version 2, which will replace this version when its released.
# Because this code is monolithic and not all that great, I don't even necessarily want my name
# associated with it - so, I declare it public domain. Do what you want with it, remove my name
# if you release it with something else (please) :), etc.

if(isset($old_error_reporting)) error_reporting($old_error_reporting);

define("MARKUP_STATE_NOTHING",    0);
define("MARKUP_STATE_LIST",       1);
define("MARKUP_STATE_BLOCKQUOTE"2);
define("MARKUP_STATE_PARA",       3);
define("MARKUP_STATE_TABLE",      4);
define("MARKUP_STATE_HR",         5);
define("MARKUP_STATE_DIRECTIVE",  6);
define("MARKUP_STATE_HEADING",    7);
define("MARKUP_STATE_PRE",        8);
define("MARKUP_STATE_RAW",        9);

global 
$markup_list_types$markup_list_subtypes$markup_escape_chars;

$markup_list_types    = array("*" => "ul""+" => "ul""#" => "ol");
$markup_list_subtypes = array("1" => "decimal""a" => "lower-alpha""A" => "upper-alpha""i" => "lower-roman""I" => "upper-roman""~" => "none");
$markup_escape_chars = array('\\*'=>"*"'\\_'=>'_''\\='=>'=''\\-'=>'-'"\\&quot;"=>"&quot;""\\]" => "]","\\[" => "[",'\\]'=>']'"\\`"=>"`");
function 
make_image($prefix$image$alt$alignment ""){
    
$output .= '<img ';
    if(
$alignment){
        
$alignment strtolower($alignment);
        if(
$alignment == "r"){
            
$output .= 'style="float: right" ';
        }elseif(
$alignment == "l"){
            
$output .= 'style="float: left" ';
        }elseif(
$alignment == 'c'){
            
$output .= 'style="display: block; margin-left: auto; margin-right: auto" ';
        }
    }
    return 
$output."src=\"$prefix$image\" alt=\"$alt\" />";
}
function 
long_url_cut($escaped_url$max_len){
    
$url html_entity_decode($escaped_url);
    return 
'<a href="'.$escaped_url.'">'.
        ((
$len strlen($url)) > $max_len ?
            
htmlspecialchars(substr($url,0,ceil($max_len/2)).'...'.substr($url,$len-(floor($max_len/2)-3))) :
            
$escaped_url)
        .
'</a>';
}
function & 
markup($str$flags = array()){
    
$er error_reporting(E_ALL & ~E_NOTICE);
    global 
$markup_list_types$markup_escape_chars;
    static 
$regexes$styles$searches$replaces$extensions;
    
$previous_list_stack = array();
    
$footnotes = array();
    
$heading_count = array(0,0,0,0,0,0);
    
$heading_master_count 0;
    
$heading_current_count 0;
    
$footnote_number 1;
    
$debug 0;

    
$extensions = array("code"=>"markup_code");

    
#set missing flags:
    
if(!isset($flags['only_get_title']))    {$flags['only_get_title']    = false;}
    if(!isset(
$flags['page_number']))       {$flags['page_number']       = 0;} #zero means get all pages
    
if(!isset($flags['get_page_info']))     {$flags['get_page_info']     = false;}
    
#Location offset is the directory offset from your root. This allows you to not have to be at the root of your
    # web space for you to use absolute links. By default it is set to "", which means the root.
    
if(!isset($flags['location_offset']))   {$flags['location_offset']   = '';}
    
#Main location is the permanent location of this document.
    #It's used for lead-ins.
    
if(!isset($flags['main_location']))     {$flags['main_location']     = '';}
    if(!isset(
$flags['footnotes_heading'])) {$flags['footnotes_heading'] = "Footnotes";} #Maybe someone would want "References"
    
if(!isset($flags['leadin_cont_text']))  {$flags['leadin_cont_text']  = "Read more";}
    if(!isset(
$flags["output_directly"]))   {$flags['output_directly']   = false;}

    if(!isset(
$flags['autonumber_headings'])){$flags['autonumber_headings'] = false;}
    if(!isset(
$flags['enable_raw_html']))   {$flags['enable_raw_html']   = true;}
    if(!isset(
$flags['enable_abs_links']))  {$flags['enable_abs_links']  = false;}
    if(!isset(
$flags['enable_TOC']))        {$flags['enable_TOC']        = true;}
    if(!isset(
$flags['enable_headings']))   {$flags['enable_headings']   = true;}
    if(!isset(
$flags['enable_images']))     {$flags['enable_images']     = true;}
    if(!isset(
$flags['enable_lists']))      {$flags['enable_lists']      = true;}
    if(!isset(
$flags['enable_tables']))     {$flags['enable_tables']     = true;}
    if(!isset(
$flags['enable_styles']))     {$flags['enable_styles']     = true;}
    if(!isset(
$flags['enable_mono']))       {$flags['enable_mono']       = true;}
    if(!isset(
$flags['enable_pre']))        {$flags['enable_pre']        = true;}
    if(!isset(
$flags['enable_hr']))         {$flags['enable_hr']         = true;}
    if(!isset(
$flags['enable_smileys']))    {$flags['enable_smileys']    = true;}
    if(!isset(
$flags['enable_comments']))   {$flags['enable_comments']   = true;}
    if(!isset(
$flags['enable_leadin']))     {$flags['enable_leadin']     = true;}
    if(!isset(
$flags['enable_footnotes']))  {$flags['enable_footnotes']  = true;}
    if(!isset(
$flags['enable_extensions'])) {$flags['enable_extensions'] = true;}
    if(!isset(
$flags['enable_settings']))   {$flags['enable_settings']   = true;}

    if(!isset(
$flags['show_leadin']))       {$flags['show_leadin']       = false;}

    if(!isset(
$flags['max_url_length']))    {$flags['max_url_length']    = 80;}

#    if(!isset($flags['link_back_headers'])) {$flags['link_back_headers'] = true;}

    
if($flags["enable_abs_links"]){
        
$url_prefix "http://$_SERVER[HTTP_HOST]$flags[location_offset]";
        
$flags["main_location"] = $url_prefix.$flags["main_location"];
    }else{
        
$url_prefix $flags['location_offset'];
    }

    
#----------------------------------------------------------------------------------
    # Inline Styles
    #----------------------------------------------------------------------------------
    
if(!$regexes){
        
$t '\/\\\\.:&;!?%0-9A-Za-z=+_^\\[\\]\\\'\\"\\$'#characters to match
        
$e '&*=`_\s-.:;,?!)(<>\\[\\]'#characters to exclude
        
$regexes = array();
        
$searches = array();
        
$regexes[] = "/(?<=^|[>$e])(?<!\\\\)`([$t<-].*?[$t>-]?)`/"#kbd
        
$styles[] = '<kbd>$1</kbd>';
        
$regexes[] = "/(?<=^|[>$e])(?<!\\\\)\*{2}([$t<-].*?[$t>-]?)\*{2}(?=[<$e]|$)/"#strong
        
$styles[] = '<strong>$1</strong>';
        
$regexes[] = "/(?<=^|\*{2}|[>$e])(?<!\\\\)\*([$t<-].*?[$t>-]?)\*(?=\*{2}|[<$e]|$)/"#emphasis
        
$styles[] = '<em>$1</em>';
        
$regexes[] = "/(?<=^|[>$e])(?<!\\\\)_([$t<*-].*?[$t>*-]?)_(?=[<$e]|$)/"#underline
        
$styles[] = '<em style="font-style: normal; text-decoration: underline">$1</em>';

    
#    $regexes[] = "/(?<=^|[>$e])(?<!\\\\)(-1,2})([$t<*].*?[$t>*])\\1(?=[<$e]|$)/"; #strikeout
        
$regexes[] = "/(?<=^|[>$e])(?<!\\\\)-{2}([$t<*].*?[$t>*])-{2}(?=[<$e]|$)/"#strikeout
        
$styles[] = '<strike>$1</strike>';

#        if($flags["enable_mono"]){
#            $regexes[] = "/(?<=^|[>$e])(?<!\\\\)=([$t<*-].*?[$t>*-])=(?=[<$e]|$)/"; #monospace
#            $styles[] = '<tt>$1</tt>';
#        }
        
if($flags["enable_styles"]){
    
#        $regexes[] = '/(?<=\\W|)(?<!\\\\)\$([A-Za-z<].*?[>A-Za-z0-9;])\$([A-Za-z<].*?[>A-Za-z])\$(?=\\W|)/'; #any style
            
$regexes[] = "/(?<=\\W|)(?<!\\\\)\\$([A-Za-z<].*?[>A-Za-z0-9;])(?<!\\\\)\\$([<$t].*?[$t>])(?<!\\\\)\\$(?=\\W|)/"#any style
            
$styles[] = '<span style="$1; display: inline; position: static">$2</span>';
        }
        if(
$flags["enable_images"]){
            
$regexes[] = "/img([lLrRcC]?)[:=](?<!\\\\)&quot;(.+?)(?<!\\\\)&quot;(?:[:=]?(?<!\\\\)&quot;(.+?)(?<!\\\\)&quot;)?/e"#images in the format img:"url":"alttext"
            
$styles[] = "make_image('$url_prefix', '$2', '$3', '$1')";
        }

#        $regexes[] = "/(?:(?<=^|[\s({]))(\w+:\/\/[\w.,\/?+~&=_:;#$%{}-]+[^\s.?!:,()<>[\]'\"&]+?)/e"; #urls
#        $styles[] = '<a href="$1">$1</a>';

        
$regexes[] = "/(?<=^|[\s({])(\w+:\/\/[\w.,\/?+~&=_:;#$%{}()'-|]+[^\s.?!:,()<>[\]'\"&]+?)/e"#urls
        
$styles[] = 'long_url_cut(\'$1\','.$flags['max_url_length'].');';

#        #email addresses
#        $regexes[] = "/(([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?))/e";
#        $styles[] = '"<a href=\"mailto:" . cryptemail("$1") . "\">" . cryptemail("$1") . "</a>"';
    
}

    
#----------------------------------------------------------------------------------
    # End Inline Styles
    #----------------------------------------------------------------------------------
    #----------------------------------------------------------------------------------
    # Simple string replacements
    #----------------------------------------------------------------------------------
    
if(!$searches){
        
$searches $replaces = array();
        if(
$flags["enable_smileys"]){
            
$searches[] = ':)';
            
$replaces[] = '<img class="smiley" src="/images/smiley_side.gif" alt="Smiley" />';
            
$searches[] = ':-D';
            
$replaces[] = '<img class="smiley" src="/images/smiley_happy.gif" alt="Smiley (big smile)" />';
            
$searches[] = ':D';
            
$replaces[] = '<img class="smiley" src="/images/smiley_happy.gif" alt="Smiley (big smile)" />';
            
$searches[] = ' :P';
            
$replaces[] = '<img class="smiley" src="/images/smiley_tongue.gif" alt="Smiley sticking out its tongue" />';
            
$searches[] = ':(';
            
$replaces[] = '<img class="smiley" src="/images/smiley_frown.gif" alt="Smiley frowning" />';
            
$searches[] = '&gt;:|';
            
$replaces[] = '<img class="smiley" src="/images/smiley_mad.gif" alt="Smiley mad" />';
            
$searches[] = '&gt;:(';
            
$replaces[] = '<img class="smiley" src="/images/smiley_mad.gif" alt="Smiley mad" />';
            
$searches[] = ':|';
            
$replaces[] = '<img class="smiley" src="/images/smiley_indifferent.gif" alt="Smiley indifferent" />';
            
$searches[] = ' ;)';
            
$replaces[] = ' <img class="smiley" src="/images/smiley_wink_animated.gif" alt="Smiley winking" />';
            
$searches[] = ' :/';
            
$replaces[] = ' <img class="smiley" src="/images/smiley_ohwell.gif" alt="Smiley \'oh well\'" />';
            
$searches[] = ' :\\';
            
$replaces[] = ' <img class="smiley" src="/images/smiley_ohwell.gif" alt="Smiley \'oh well\'" />';
        }
    }
    
#----------------------------------------------------------------------------------
    # End Simple string replacements
    #----------------------------------------------------------------------------------

    #split the $str into separate lines if it's not already
    #find out if it's faster to replace all crlf's with \n's first, then explode
    #rather than split according to a regex
    
if(!is_array($str)){
        
$str_arr preg_split('/\n|(?:\r\n?)/'$str);
    }else{
        
$str_arr = &$str;
        for(
$n 0$n<count($str_arr);$n++)
            
$str_arr[$n] = chop($str_arr[$n]); #make sure there are no line returns
    
}

    
$line_count count($str_arr);
    if(!
$line_count) return ''#noop
    
$n 0;

    
#----------------------------------------------------------------------------------
    # Preprocessing of directives
    #----------------------------------------------------------------------------------
    
if($flags["get_page_info"]){ #if you just want page information
     #improve this to a generic "get structure" flag that will return title, all page info, and all headings in each page
        
$pages = array();
        for(
$n 0$n<$line_count$n++)
            if(
substr($str_arr[$n], 05) == "?page")
                
$pages[] = trim(substr($str_arr[$n], 5));
        return 
$pages;
    }
    if(
substr($str_arr[0], 06) == "?title"){ #the title has to be the first line in the entire document
        
$title htmlspecialchars(trim(substr($str_arr[0], 6)));
        
$str_arr[0] = "<h3 class=\"st-markup\">$title</h3>\n\n";
        
$n++;
    }
    if(
$flags["only_get_title"]) return $title;
    if(
$flags["page_number"] > 0){
        
#get the title for the first page
        
$page_count 1;
        if(
$flags["page_number"] == 1){
            if(
substr($str_arr[$n], 05) == "?page"){ #check the first line to see if it has a page
                
$page_title trim(substr($str_arr[$n], 5));
                
$n++;
            }
        }else{
            
$n++; #skip past the first line of the file, because whether it's a ?page directive or not, it's in the first page.
            
while($n $line_count and $page_count $flags["page_number"]){
                if(
substr($str_arr[$n], 05) == "?page"$page_count++; #found a new page.
                
$n++;
            }
            
$page_title trim(substr($str_arr[$n], 5));
        }
        
$m $n;
        
#I have to go to the end to find out where to stop
        
while($m $line_count and substr($str_arr[$m], 05) != "?page"$m++;
        
#slice the array from $n (the first line of the content after the page declaration)
        # to $line_count - $m spaces before the end. $line_count - $m will be the remaining spaces in the array
        
$str_arr array_slice($str_arr$n, -($line_count-$m));
        
$line_count $m-$n;
        
$n 0;
    }
    
#----------------------------------------------------------------------------------
    # End Preprocessing of directives
    #----------------------------------------------------------------------------------
    
$list_level 0;
    for(; 
$n<$line_count$n++){
        
$line $str_arr[$n];
        
$previous_state $current_state;
        
$current_state $prepend $append '';

        if(
$line{0} == ";" and $flags["enable_raw_html"]){
            
$line substr($line1)."\n";
            
$current_state 'raw_html';
        }else{
            if(
substr($line0,2) != '?!' or !$flags['enable_extensions']){ #if it's not an extension
                
$line = &htmlspecialchars($line);
                
#first, simple string replacements, preferred over regexes if possible (they're faster)
                
$line = &str_replace($searches$replaces$line);
                
$line = &preg_replace($regexes$styles$line);
            }
        }

        
#First, processing to be done regardless of the other contents of the line.
        # This ensures that even if the line is a blockquote, for instance, links will still
        # be linked.

/*        $quote_position = 6; # no sense in starting within the first seven characters
        #from the beginning of the line: &quot;A&quot;=&quot;url&quot; is the minimal link.
        #Count the characters and you'll see why I want to start at position seven
        while(($quote_position = strpos($line, "&quot;=&quot;", $quote_position+1)) !=== false){
            if($line{$quote_position-1} == "\\"){
                continue;
            }else{
            }
*/

        
if(($quote_position strpos($line'&quot;')) !== false){ #if there's a quote on the line
            
$m 0;
            
#there's got to be a better way to do this....
            # Find the "=", split on the equals, and look for a quote forward and back from there.
            # Try that later, see if the algorithm is simpler
            
while($quote_position !== false){
                if(
$quote_position == or ($quote_position and $line{$quote_position-1} != '\\')){
                    
#handle escaped quotes
                    
$quotes[$m++] = $quote_position;
                }
                
$quote_position strpos($line'&quot;'$quote_position+6);
            }
            
$num_quotes $m;
            
$begin 0;
            
$line_copy '';
            for(
$m=1$m<$num_quotes$m++){
                if(
substr($line$quotes[$m]+67) == '=&quot;' or substr($line$quotes[$m]+67) == ':&quot;'){
                    
#if the character after the quote was an equals sign and another quote
                    #there'll always be a quote before it, so as long as there's another quote after it...
                    
if($m+!= $num_quotes){ #there have to be two quotes left!
                        #make a link
                        
$url      substr($line$quotes[$m+1]+6, ($quotes[$m+2] - $quotes[$m+1])-6);
                        if(
$url_prefix and $url and $url{0} != '/' and !strstr($url"://"))
                            
$url $url_prefix $url;
                        
$linktext substr($line$quotes[$m-1]+6, ($quotes[$m]   - $quotes[$m-1])-6);
                        
$quotestring "<a href=\"$url\">$linktext</a>";
                        
$line_copy .= substr($line$begin$quotes[$m-1] - $begin) . $quotestring;
                        
$begin $quotes[$m+2] + 6;
                    }
                }
            }
            
$line $line_copy.substr($line$begin);
            unset(
$quotes);
        }

        
#look for footnotes
        
if($flags['enable_footnotes']){
            
$footnote_positions = array();
            
$close_footnote_position 0;
            
$line_length strlen($line);
            while(
                
$close_footnote_position $line_length
                
and (($open_footnote_position strpos($line"[#", ($close_footnote_position 1))) !== false)){
                if(
$line{$open_footnote_position-1} == "\\"){
                    
#it's escaped
                    
$close_footnote_position $open_footnote_position+2;
                    continue;
                }else{
                    
$temp_pos $open_footnote_position+2;
                    while((
$close_footnote_position strpos($line"]"$temp_pos)) !== false){
                        if(
$line{$close_footnote_position-1} == "\\"){$temp_pos $close_footnote_position+1; continue;} #escaped

                        
$footnote_length $close_footnote_position - ($open_footnote_position+2);
                        
$full_footnote substr($line$open_footnote_position+2$footnote_length);
                        list(
$footnote$footnote_text) = explode(" "$full_footnote2);
                        if(!
$footnote_text) break; #if it's not a valid footnote

                        #autonumber footnotes
                        
$footnote = !$footnote $footnote_number++ : normalize_ids($footnote"fn_");
                        
$footnotes[$footnote] = $footnote_text;
                        
$replacement_text "<sup><a id=\"fnp$footnote\" href=\"#fn$footnote\">[$footnote]</a></sup>";
                        
$replacement_length strlen($replacement_text);
                        
$line substr_replace($line$replacement_text$open_footnote_position$footnote_length 3); # the 3 is plus the opening bracket and # and the closing bracket
                        
$close_footnote_position $open_footnote_position $replacement_length 1;
                        
$line_length $line_length - ($footnote_length $replacement_length);
                        break; 
#we want to stop once we've found the close footnote position that isn't escaped
                    
}
                }
            }
        }
        
#----------------------------------------------------------------------------------
        # Determine our state
        #----------------------------------------------------------------------------------
        
if($current_state == 'raw_html'){}
        elseif(
preg_match('/^---(?:(\d{1,2})([lLrR])?)?$/'$line$matches)){
            
$current_state MARKUP_STATE_HR;
        }elseif(
preg_match('/^((?:(?:[*+]~?|#[1aAiI]?)\s*)+)\s?(.*)$/'$line$matches)){
            
$current_state MARKUP_STATE_LIST;
            
preg_match_all('/[*+]~?|#[1aAiI]?/'$matches[1], $list_begins);
        }elseif(
substr($line,0,4) == '&gt;'){
            
$current_state MARKUP_STATE_BLOCKQUOTE;
            
$prepend $previous_state == MARKUP_STATE_BLOCKQUOTE "<br />\n" '<blockquote class="st-markup"><p>';
        }elseif(
substr($line,0,1) == '|' and substr($line,-1) == '|'){
            
$current_state MARKUP_STATE_TABLE;
            if(
$previous_state != MARKUP_STATE_TABLE){$prepend '<table border="1" class="st-markup">'."\n";}
        }elseif(
$line{0} == '!'){
            
$current_state MARKUP_STATE_HEADING;
        }elseif(
$line{0} == "="){
            
$current_state MARKUP_STATE_PRE;
            if(
$previous_state != MARKUP_STATE_PRE){$prepend '<pre class="st-markup">';}
        }elseif(
$line{0} == '?'){
            
$current_state MARKUP_STATE_DIRECTIVE;
        }elseif(!
$line){
            
$current_state MARKUP_STATE_NOTHING;
        }else{
            
$current_state MARKUP_STATE_PARA;
            
$prepend $previous_state == MARKUP_STATE_PARA "<br />\n" '<p class="st-markup">';
        }

        if(
$debug){if(!$previous_state){echo "<table border=\"1\"><tr><th>Line Number</th><th>Current State</th><th>Previous State</th></tr>\n";}
        echo 
"<tr><td>".($n 1)."</td><td>$current_state</td><td>$previous_state</td></tr>\n";}

        
#these are all states that can potentially continue until the next line.
        #Make sure you correctly close any of the preceding states
        
if(    $previous_state == MARKUP_STATE_LIST  and $current_state != MARKUP_STATE_LIST) {$prepend end_lists(count($list_begins[0]), $list_begins[0]).$prepend$previous_list_stack = array();}
        elseif(
$previous_state == MARKUP_STATE_TABLE and $current_state != MARKUP_STATE_TABLE){$prepend "</table>\n\n$prepend";}
        elseif(
$previous_state == MARKUP_STATE_PRE   and $current_state != MARKUP_STATE_PRE)  {$prepend "</pre>\n\n$prepend";}
        elseif(
$previous_state == MARKUP_STATE_PARA  and $current_state != MARKUP_STATE_PARA) {$prepend "</p>\n\n$prepend";}
        elseif(
$previous_state == MARKUP_STATE_BLOCKQUOTE and $current_state != MARKUP_STATE_BLOCKQUOTE){$prepend "</p></blockquote>\n\n$prepend";}
        
#----------------------------------------------------------------------------------
        # Finish determining our state
        #----------------------------------------------------------------------------------

        
if($current_state == MARKUP_STATE_LIST and $flags['enable_lists']){ #lists
            
$line list_item($previous_list_stack$list_begins[0], $matches[2]);
        }elseif(
$current_state == MARKUP_STATE_TABLE and $flags["enable_tables"]){ #tables
            
$cells explode("|"substr($line1,-1));
            
$line "\t<tr>";
            for(
$m 0$m count($cells); $m++){
                
$colspan 1;
                
$cell $cells[$m];
                while(isset(
$cells[$m+1]) and $cells[$m+1] == ''){ #if the next cell is empty
                    
$colspan++;
                    
$m++;
                }
                
$line .= "<td".($colspan " colspan=\"$colspan\"" '').">$cell</td>";
            }
            
$line .= "</tr>\n";
        }elseif(
$current_state == MARKUP_STATE_HEADING and $flags["enable_headings"]){ #heading
            
if(!$headings[$heading_current_count]){
                
$headings[$heading_current_count] = extract_heading_info($line$flags["autonumber_headings"], $heading_count$heading_current_count);
            }
            
$line "<h{$headings[$heading_current_count][0]} id=\"{$headings[$heading_current_count][2]}\" class=\"st-markup\">{$headings[$heading_current_count][3]} {$headings[$heading_current_count][1]}</h{$headings[$heading_current_count][0]}>\n\n";
            
$heading_current_count++;
        }elseif(
$current_state == MARKUP_STATE_HR and $flags["enable_hr"]){
            
$num $matches[1]; #left over from the test above
            
if($num){ #if there are one or two numeric characters characters
                
if(strlen($num) == 1$num .= "0";
                
$line "<hr class=\"st-markup\" style=\"width: $num%";
                if(
$matches[2])
                    
$line .= strtoupper($matches[2]) == 'L' '; float: left' '; float:right';
                
$line .= "\" />\n\n";
            }else
                
$line "<hr class=\"st-markup\" />\n\n";
        }elseif(
$current_state