/* 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\"> $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;
?>
text/plain
This content has been proxied by September (3851b).