/* Convert gemtext to HTML

I thought this had come from Hundredrabbits, but couldn't find any reference

to it on their site. I originally stored it in a directory named

"gemini.sensorstation.co/" with a read-me file named

"computing.gemini.gmi-to-html.gmi". Similarly, I can find no reference to it

at gemini.sensorstation.co, so I don't know where I found it. It

includes no comments or metadata within the file to offer clues.

  gemini://gemini.circumlunar.space/users/hundredrabbits/

  gemini://gemini.sensorstation.co/

I found an archived version of the source at

 gemini://kennedy.gemi.dev/archive/cached?url=gemini%3a%2f%2fgemini.sensorstation.co%2fcomputing.gemini.gmi-to-html.gmi&t=637772460440000000&raw=False

I have, of course, modified it to better suit my purposes, and to correct

a few bugs.

It is NOT apparently derived from https://gmi.sbgodin.fr/htmgem/, regardless

of the comment in https://jdcard.com/blog.archive/2022-07-21T23-26.gmi

// == PARSE LINE

// A function to parse lines for inline phrasing like emphasis, strong, and code.

function parse_line($line_in) {

$line_out = "";

$pre = false;

$emp = false;

$str = false;

for ($i = 0, $length = mb_strlen($line_in); $i < $length; $i++) {

$char = mb_substr($line_in, $i, 1);

if($char == "`") {

  $pre = !$pre;

  if($pre) {

    $line_out .= "<code>";

    continue;

  } else {

    $line_out .= "</code>";

    continue;

  }

} elseif ($char == "_" && !$pre) {

  $emp = !$emp;

  if($emp) {

    $line_out .= "<em>";

    continue;

  } else {

    $line_out .= "</em>";

    continue;

  }

} else if($char == "*" && !$pre) {

  $str = !$str;

  if($str) {

    $line_out .= "<strong>";

    continue;

  } else {

    $line_out .= "</strong>";

    continue;

  }

}

$line_out .= $char;

}

return $line_out;

} // END parse_line

$file = fopen(".".$_SERVER['REDIRECT_URL'], "r") or die("Not found.");

$fname = $_SERVER['REDIRECT_URL'];

$line_no = 0;

$head_count = 0;

$preformatted = false;

$blockquote_open = false;

$list_open = false;

$last_br = -1;

$is_pdf = false;

$title = '';

$body = '';

$toc = " Contents of this page:\n";

$img_extensions = array("png", "apng", "avif", "bmp", "ico", "tif", "tiff", "jpg", "jpeg", "gif", "svg", "webp");

$aud_extensions = array("mp3", "wav", "ogg");

$vid_extensions = array("mp4", "ogg", "webm");

// == MAIN LOOP

while(!feof($file)) {

$line_no += 1;

$line_untrimmed = fgets($file);

$line = trim($line_untrimmed);

$line_len = strlen($line);

// == POLYGLOT gemtext/pdf

// If this is gemtext/pdf polyglot .gmi skip the first four lines; this only works with files generated by gemdoc from https://github.com/seifferth/gemdoc

if($line_no == 1) {

    if ($line == "%PDF-1.7") {

        $is_pdf = true;

        $body .= "<!-- source file type is a gemtext/pdf polyglot file -->\n";

        continue;

    } else { // ToDo: rework this: what if the first line of the file begins ">". "=>", "*", or "```"?

        $lntype = $line[0] == "#" ? "h1" : "p"; // index.gmi uses "👴 jdcard" as the <h1> level header, in other pages it is a plain line

        $body .= "<!-- source file type is a plain gemtext file -->\n";

        $title = htmlentities(substr($line, 2, $line_len-2));

        $body .= "    <$lntype>".htmlentities($lntype == "h1" ? substr($line, 2, $line_len-2) : $line)."</$lntype>\n"; // index.gmi 

        continue;

    }

}

// skip past the pdf data that we don't want to see

if($is_pdf == true and $line_no <5) {

    continue;

}

// If this is a gemtext/pdf polyglot file, create a link to view it as pdf, then stop processing the file at the end of the gemtext portion

if($is_pdf == true and $line == "endstream") {

    $pdf_file = str_replace(".gmi", ".pdf", $fname);                                           // create name for pdf symlink

    $pdf_file = substr($pdf_file, 1);                                                          // trim the leading "/" path character

    $fname = preg_replace("/\/.*\//", "", $fname);                                             // strip path, leave file-name

    $fname = preg_replace("/\/?/", "", $fname);                                                // previous line sometimes left an inital "/"

    symlink($fname, $pdf_file);                                                                // create symlink to this file with a .pdf extension

    $pdf_file = preg_replace("/^.*\//", "", $pdf_file);                                        // strip path, leave file-name

    $body .= "    <hr />\n    <p><a href=\"$pdf_file\">View this document as a PDF</a></p>\n"; // add a link in this document to the pdf file

    // $body .= "    <!-- $fname\n         ".substr($fname, 0, 1)."\n         $pdf_file  -->"; // DEBUG

    break;

}

// == THEMATIC BREAK - horizontal rule; ⁂  "Asterism" character, see https://en.wikipedia.org/wiki/Dinkus

if(substr($line, 0, 3) == "---" || $line == "⁂" ) {

  $body .= "    <hr>\n";

  continue;

}

// == PREFORMATTED

if($line_len >= 3 && substr($line, 0, 3) == "```") {

    if($blockquote_open && $line[0] != ">") {$blockquote_open = false;}

    $preformatted = !$preformatted;

    if($preformatted) {

        $body .= "    <pre>";

    } else {

        $body .= "</pre>\n";

    }

    continue;

}

if($preformatted) {

    $body .= htmlentities($line_untrimmed);

    continue;

}

// == LINKS

if(strpos($line, "=>") === 0) {

    $line = ltrim($line, "=> \t");

    if(strlen($line) == 0) {

        continue;

    }

    $first_space = strpos($line, " ");

    $first_tab = strpos($line, "\t");

    $link_target = "";

    $link_label = "";

    if($first_space === false && $first_tab === false) {

        $link_target = $line;

        $link_label = $line;

    } else {

        if($first_space === false) $first_space = 999999;

        if($first_tab === false) $first_tab = 999999;

        $parts = [];

        if($first_space < $first_tab) {

            $parts = explode(" ", $line, 2);

        } else {

            $parts = explode("\t", $line, 2);

        }

        $link_target = $parts[0];

        $link_label = htmlentities($parts[1]);

    }

    $extension = substr(strrchr($link_target, '.'), 1);

    // == INLINE DISPLAY of linked images, audio, and video content

    if(in_array(strtolower($extension), $img_extensions)) {

        // symlink("$fpath/$link_target", "$link_target"); // needed only on my development machine at home

        $body .= "    <p class=\"image\">&nbsp;$link_label<br><img src=\"$link_target\" alt=\"$link_label\"></p>\n";

    } elseif (in_array(strtolower($extension), $aud_extensions)) {

        $body .= "    <p class=\"audio\">$link_label<br><audio controls><source src=\"$link_target\" type=\"audio/mpeg\">$link_label</audio></p>\n";

    } elseif (in_array(strtolower($extension), $vid_extensions)) {

        $body .= "    <p class=\"video\">$link_label<br><video controls><source src=\"$link_target\">$link_label</video></p>\n";

    // == INLINE DISPLAY of CSV data

    } elseif (strtolower($extension) == "csv") {

        $csvfile = fopen("$link_target", "r");

          if ($csvfile !== false) {

          $body .= "    <h5>$link_label</h5>\n";

          $body .=  "    <table>\n";

          $csv_row_count = 0;

          while ($row = fgetcsv($csvfile)) {

              $body .=  "      <tr id=\"$link_target-$csv_row_count\">";

              $csv_row_count += 1;

              foreach ($row as $cell) {

                  if ($csv_row_count == 1) {

                      $body .=  "<th>".htmlspecialchars($cell)."</th>";

                  } else {

                      $body .=  "<td>".htmlspecialchars($cell)."</td>";

                  }

              }

              $body .=  "</tr>\n";

          }

          $body .=  "    </table>\n";

          fclose($csvfile);

        } else {

          if(strcmp($link_target, $link_label) != 0) {

              $link_label = $link_label . " <span class=\"link_target\">($link_target)</span>";

          }

          $body .= "    <p class=\"link\"><a href=\"$link_target\" rel=\"noopener\">$link_label</a></p>\n";

        }

    } else {

        if(strcmp($link_target, $link_label) != 0) {

            $link_label = $link_label . " <span class=\"link_target\">($link_target)</span>";

        }

        $body .= "    <p class=\"link\"><a href=\"$link_target\" rel=\"noopener\">$link_label</a></p>\n";

    }

    continue;

}

// == HEADLINES

$head_level = 0;

for($i = 0; $i < $line_len; $i++) {

    if($line_untrimmed[$i] == "#") {

        $head_level += 1;

    } else {

        break;

    }

}

if($head_level > 0) {

    $head_count +=1;

    $line = ltrim($line, "# \t");

    $body .= "    <h$head_level id=\"h$head_level-$head_count\">$line</h$head_level>\n";

    $toc  .= "        <a href=\"#h$head_level-$head_count\" class=\"toc$head_level\">$line</a><br>\n";

    if($title == "") {

        $title = $line;

    }

    continue;

}

// == LISTS

if(substr($line_untrimmed, 0, 2) == "* ") {

    if(!$list_open) {

        $list_open = true;

        if($blockquote_open && $line[0] != ">") {$blockquote_open = false;}

        $body .= "    <ul>\n";

    }

    $line = ltrim($line, "* \t");

    $body .= "      <li>$line</li>\n";

    continue;

} else if($list_open && $line[0] != "*") {

    $list_open = false;

    $body .= "    </ul>\n";

}

// == BLOCKQUOTES

if($line_untrimmed[0] == ">") {

    if(!$blockquote_open) {

        $blockquote_open = true;

        $body .= "    <blockquote>\n";

    }

    $line = ltrim(parse_line($line), "> \t");

} else if($blockquote_open && $line[0] != ">") {

    $blockquote_open = false;

    $body .= "    </blockquote>\n";

}

// == PARAGRAPHS

if($line_len > 0) {

    if($blockquote_open = true) {

        $body .= "      <p>".parse_line($line)."</p>\n";

    } else {

        $body .= "    <p>".parse_line($line)."</p>\n";

    }

} else {

    // == WHITESPACE

    // only insert <br> after 2 newlines

    if($last_br == $line_no - 1) {

        $body .= "    <br>\n";

    }

    $last_br = $line_no;

}

}

// == CLOSE ANY OPEN ELEMENTS

if($blockquote_open) {

$blockquote_open = false;

$body .= "    </blockquote>\n";

}

if($preformatted) {

$preformatted = false;

$body .= "</pre>";

}

if($list_open) {

$list_open = false;

$body .= "    </ul>\n";

}

$toc .= " \n";

fclose($file);

echo <<<END

<title>$title</title>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="stylesheet" href="css/site.css">

<link rel="stylesheet" href="css/gmi.css">

<!-- <script src="https://climateclock.world/widget-v2.js" async></script> -->

<p style="display: none;"><small><a rel="me" href="https://social.vivaldi.net/@jdcard">Mastodon</a> social.vivaldi.net 

  <a rel="me" href="https://social.linux.pizza/@jdcard">Mastodon</a> social.linux.pizza

  <a rel="me" href="https://qoto.org/@Jdcard">Mastodon</a> qoto.org

  <a rel="me" href="https://jdcard.tilde.team">🌐 jdcard.tilde.team</a></small><p> 

<p><a href="https://search.marginalia.nu/site/jdcard.com?view=docs">Search this site at marginalia.nu</a></p>

<hr>

<iframe src="https://duckduckgo.com/search.html?site=jdcard.com&prefill=Search%20this%20site%20at%20DuckDuckGo&kae=%23D8D0C8" style="overflow:hidden;margin:0;padding:0;height:40px;"></iframe>

<!-- <climate-clock /> -->

<!-- <script src="https://climateclock.world/widget-v2.js" async></script> -->

<!-- <script src="https://efreecode.com/js.js" id="eXF-jdcard52-0" async defer></script> -->

END;

?>

Proxy Information
Original URL
gemini://jdcard.com/gmi-to-html.php.txt
Status Code
Success (20)
Meta
text/plain
Capsule Response Time
628.641774 milliseconds
Gemini-to-HTML Time
5.806845 milliseconds

This content has been proxied by September (3851b).